summaryrefslogtreecommitdiffstats
path: root/dom
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 05:35:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 05:35:29 +0000
commit59203c63bb777a3bacec32fb8830fba33540e809 (patch)
tree58298e711c0ff0575818c30485b44a2f21bf28a0 /dom
parentAdding upstream version 126.0.1. (diff)
downloadfirefox-59203c63bb777a3bacec32fb8830fba33540e809.tar.xz
firefox-59203c63bb777a3bacec32fb8830fba33540e809.zip
Adding upstream version 127.0.upstream/127.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom')
-rw-r--r--dom/base/AbstractRange.cpp8
-rw-r--r--dom/base/BodyConsumer.cpp25
-rw-r--r--dom/base/ContentIterator.cpp2
-rw-r--r--dom/base/CrossShadowBoundaryRange.cpp115
-rw-r--r--dom/base/CrossShadowBoundaryRange.h89
-rw-r--r--dom/base/DOMIntersectionObserver.cpp3
-rw-r--r--dom/base/Document.cpp70
-rw-r--r--dom/base/Document.h5
-rw-r--r--dom/base/Element.cpp4
-rw-r--r--dom/base/EventSource.cpp6
-rw-r--r--dom/base/FocusModel.h41
-rw-r--r--dom/base/FragmentOrElement.cpp14
-rw-r--r--dom/base/Highlight.cpp40
-rw-r--r--dom/base/HighlightRegistry.cpp22
-rw-r--r--dom/base/MimeType.cpp14
-rw-r--r--dom/base/RadioGroupContainer.cpp8
-rw-r--r--dom/base/ResizeObserver.h1
-rw-r--r--dom/base/Selection.h24
-rw-r--r--dom/base/StaticRange.h12
-rw-r--r--dom/base/crashtests/1419902.html24
-rw-r--r--dom/base/crashtests/1741957.html22
-rw-r--r--dom/base/crashtests/crashtests.list3
-rw-r--r--dom/base/fragmentdirectives/lib.rs4
-rw-r--r--dom/base/fragmentdirectives/test.rs20
-rw-r--r--dom/base/moz.build3
-rw-r--r--dom/base/nsAttrValue.cpp32
-rw-r--r--dom/base/nsAttrValue.h4
-rw-r--r--dom/base/nsContentAreaDragDrop.cpp2
-rw-r--r--dom/base/nsContentUtils.cpp81
-rw-r--r--dom/base/nsContentUtils.h22
-rw-r--r--dom/base/nsCopySupport.cpp26
-rw-r--r--dom/base/nsCopySupport.h4
-rw-r--r--dom/base/nsDOMWindowUtils.cpp8
-rw-r--r--dom/base/nsFocusManager.cpp70
-rw-r--r--dom/base/nsGlobalWindowInner.cpp33
-rw-r--r--dom/base/nsGlobalWindowInner.h8
-rw-r--r--dom/base/nsIContent.h23
-rw-r--r--dom/base/nsJSEnvironment.cpp5
-rw-r--r--dom/base/nsRange.cpp46
-rw-r--r--dom/base/nsRange.h17
-rw-r--r--dom/base/nsTextFragment.cpp40
-rw-r--r--dom/base/nsTextFragment.h17
-rw-r--r--dom/base/test/browser_page_load_event_telemetry.js10
-rw-r--r--dom/base/test/chrome/bug418986-1.js2
-rw-r--r--dom/base/test/fullscreen/browser.toml3
-rw-r--r--dom/base/test/jsmodules/importmaps/bug_1893164_module_1.mjs3
-rw-r--r--dom/base/test/jsmodules/importmaps/bug_1893164_module_2.mjs5
-rw-r--r--dom/base/test/jsmodules/importmaps/bug_1893164_module_3.mjs1
-rw-r--r--dom/base/test/jsmodules/importmaps/bug_1894631_module_1.mjs5
-rw-r--r--dom/base/test/jsmodules/importmaps/bug_1894631_module_2.mjs3
-rw-r--r--dom/base/test/jsmodules/importmaps/bug_1894631_module_3.mjs1
-rw-r--r--dom/base/test/jsmodules/importmaps/bug_1894631_module_4.mjs1
-rw-r--r--dom/base/test/jsmodules/importmaps/mochitest.toml10
-rw-r--r--dom/base/test/jsmodules/importmaps/test_bug_1893164.html20
-rw-r--r--dom/base/test/jsmodules/importmaps/test_shared_submodules_with_modulepreload.html30
-rw-r--r--dom/base/test/test_anchor_target_blank_referrer.html3
-rw-r--r--dom/base/test/test_focus_radio.html69
-rw-r--r--dom/bindings/BindingUtils.cpp65
-rw-r--r--dom/bindings/Bindings.conf4
-rw-r--r--dom/bindings/Codegen.py24
-rw-r--r--dom/bindings/DOMString.h5
-rw-r--r--dom/bindings/Errors.msg1
-rw-r--r--dom/bindings/FakeString.h2
-rw-r--r--dom/bindings/mach_commands.py2
-rw-r--r--dom/bindings/test/TestFunctions.cpp10
-rw-r--r--dom/broadcastchannel/BroadcastChannel.cpp5
-rw-r--r--dom/cache/CacheChild.cpp2
-rw-r--r--dom/cache/CacheStorageChild.cpp2
-rw-r--r--dom/cache/CacheStreamControlParent.cpp4
-rw-r--r--dom/cache/CacheStreamControlParent.h5
-rw-r--r--dom/cache/Connection.cpp3
-rw-r--r--dom/cache/StreamList.cpp10
-rw-r--r--dom/cache/test/marionette/test_caches_delete_cleanup_after_shutdown.py116
-rw-r--r--dom/canvas/CanvasImageCache.cpp64
-rw-r--r--dom/canvas/CanvasImageCache.h15
-rw-r--r--dom/canvas/CanvasRenderingContext2D.cpp54
-rw-r--r--dom/canvas/CanvasRenderingContext2D.h2
-rw-r--r--dom/canvas/ClientWebGLContext.cpp17
-rw-r--r--dom/canvas/ClientWebGLContext.h14
-rw-r--r--dom/canvas/HostWebGLContext.h3
-rw-r--r--dom/canvas/ImageBitmap.cpp18
-rw-r--r--dom/canvas/OffscreenCanvas.cpp15
-rw-r--r--dom/canvas/OffscreenCanvasDisplayHelper.cpp26
-rw-r--r--dom/canvas/OffscreenCanvasDisplayHelper.h3
-rw-r--r--dom/canvas/WebGLContext.cpp318
-rw-r--r--dom/canvas/WebGLContext.h25
-rw-r--r--dom/canvas/WebGLContextTextures.cpp2
-rw-r--r--dom/canvas/WebGLIpdl.h28
-rw-r--r--dom/canvas/WebGLMethodDispatcher.h1
-rw-r--r--dom/canvas/WebGLQueueParamTraits.h41
-rw-r--r--dom/canvas/WebGLTypes.h4
-rw-r--r--dom/canvas/crashtests/crashtests.list2
-rw-r--r--dom/canvas/test/reftest/colors/_generated_reftest.list371
-rw-r--r--dom/canvas/test/reftest/colors/color_canvas.html1
-rw-r--r--dom/canvas/test/reftest/colors/generate_color_canvas_reftests.py44
-rw-r--r--dom/chrome-webidl/InspectorUtils.webidl50
-rw-r--r--dom/chrome-webidl/UniFFI.webidl8
-rw-r--r--dom/console/Console.h1
-rw-r--r--dom/console/ConsoleInstance.cpp23
-rw-r--r--dom/console/ConsoleInstance.h1
-rw-r--r--dom/console/tests/xpcshell/test_worker.js59
-rw-r--r--dom/console/tests/xpcshell/worker.mjs15
-rw-r--r--dom/console/tests/xpcshell/xpcshell.toml6
-rw-r--r--dom/crypto/WebCryptoTask.cpp21
-rw-r--r--dom/docs/ipc/process_model.rst2
-rw-r--r--dom/docs/workersAndStorage/PerformanceTesting.rst6
-rw-r--r--dom/encoding/test/reftest/reftest.list4
-rw-r--r--dom/events/Clipboard.cpp37
-rw-r--r--dom/events/Clipboard.h2
-rw-r--r--dom/events/ClipboardItem.cpp12
-rw-r--r--dom/events/ClipboardItem.h2
-rw-r--r--dom/events/DataTransfer.cpp4
-rw-r--r--dom/events/DataTransferItem.cpp8
-rw-r--r--dom/events/DataTransferItem.h2
-rw-r--r--dom/events/EventStateManager.cpp7
-rw-r--r--dom/events/IMEStateManager.cpp2
-rw-r--r--dom/events/WheelHandlingHelper.cpp7
-rw-r--r--dom/events/test/pointerevents/mochitest.toml1
-rw-r--r--dom/events/test/pointerevents/test_getCoalescedEvents.html43
-rw-r--r--dom/events/test/test_dom_storage_event.html4
-rw-r--r--dom/fetch/Fetch.cpp20
-rw-r--r--dom/fetch/FetchDriver.cpp7
-rw-r--r--dom/fetch/FetchDriver.h8
-rw-r--r--dom/fetch/FetchParent.cpp4
-rw-r--r--dom/fetch/FetchParent.h1
-rw-r--r--dom/fetch/FetchService.cpp13
-rw-r--r--dom/fetch/FetchService.h13
-rw-r--r--dom/fetch/PFetch.ipdl1
-rw-r--r--dom/file/BaseBlobImpl.h10
-rw-r--r--dom/file/Blob.cpp3
-rw-r--r--dom/file/FileReaderSync.cpp3
-rw-r--r--dom/fs/parent/FileSystemAccessHandle.cpp2
-rw-r--r--dom/html/HTMLAnchorElement.cpp37
-rw-r--r--dom/html/HTMLAnchorElement.h4
-rw-r--r--dom/html/HTMLButtonElement.cpp11
-rw-r--r--dom/html/HTMLButtonElement.h2
-rw-r--r--dom/html/HTMLCanvasElement.cpp14
-rw-r--r--dom/html/HTMLCanvasElement.h2
-rw-r--r--dom/html/HTMLDNSPrefetch.cpp138
-rw-r--r--dom/html/HTMLDNSPrefetch.h14
-rw-r--r--dom/html/HTMLDetailsElement.h2
-rw-r--r--dom/html/HTMLDialogElement.cpp2
-rw-r--r--dom/html/HTMLEmbedElement.cpp4
-rw-r--r--dom/html/HTMLEmbedElement.h2
-rw-r--r--dom/html/HTMLImageElement.cpp13
-rw-r--r--dom/html/HTMLImageElement.h2
-rw-r--r--dom/html/HTMLInputElement.cpp62
-rw-r--r--dom/html/HTMLInputElement.h26
-rw-r--r--dom/html/HTMLLinkElement.cpp2
-rw-r--r--dom/html/HTMLMediaElement.cpp48
-rw-r--r--dom/html/HTMLMediaElement.h24
-rw-r--r--dom/html/HTMLObjectElement.cpp3
-rw-r--r--dom/html/HTMLObjectElement.h2
-rw-r--r--dom/html/HTMLSelectElement.cpp5
-rw-r--r--dom/html/HTMLSelectElement.h2
-rw-r--r--dom/html/HTMLSharedElement.cpp12
-rw-r--r--dom/html/HTMLSummaryElement.cpp7
-rw-r--r--dom/html/HTMLSummaryElement.h2
-rw-r--r--dom/html/HTMLTextAreaElement.cpp5
-rw-r--r--dom/html/HTMLTextAreaElement.h2
-rw-r--r--dom/html/nsGenericHTMLElement.cpp40
-rw-r--r--dom/html/nsGenericHTMLElement.h11
-rw-r--r--dom/html/nsGenericHTMLFrameElement.cpp5
-rw-r--r--dom/html/nsGenericHTMLFrameElement.h12
-rw-r--r--dom/html/test/forms/mochitest.toml2
-rw-r--r--dom/html/test/forms/test_input_datetime_preventDefault.html23
-rw-r--r--dom/indexedDB/IDBFactory.cpp171
-rw-r--r--dom/indexedDB/IDBFactory.h32
-rw-r--r--dom/indexedDB/IDBObjectStore.cpp22
-rw-r--r--dom/indexedDB/Key.cpp125
-rw-r--r--dom/indexedDB/Key.h8
-rw-r--r--dom/indexedDB/crashtests/1499854-1.html2
-rw-r--r--dom/indexedDB/crashtests/1857979-1.html4
-rw-r--r--dom/indexedDB/test/perfdocs/index.rst2
-rw-r--r--dom/indexedDB/test/unit/test_invalid_version.js16
-rw-r--r--dom/indexedDB/test/unit/test_metadata2Restore.js40
-rw-r--r--dom/indexedDB/test/unit/test_metadataRestore.js24
-rw-r--r--dom/interfaces/base/nsIDOMWindowUtils.idl8
-rw-r--r--dom/interfaces/base/nsIFocusManager.idl28
-rw-r--r--dom/interfaces/notification/nsINotificationStorage.idl4
-rw-r--r--dom/interfaces/security/nsIContentSecurityPolicy.idl52
-rw-r--r--dom/ipc/BrowserChild.cpp30
-rw-r--r--dom/ipc/BrowserChild.h5
-rw-r--r--dom/ipc/BrowserParent.cpp99
-rw-r--r--dom/ipc/BrowserParent.h19
-rw-r--r--dom/ipc/ContentChild.cpp22
-rw-r--r--dom/ipc/ContentChild.h3
-rw-r--r--dom/ipc/ContentParent.cpp196
-rw-r--r--dom/ipc/ContentParent.h41
-rw-r--r--dom/ipc/DOMTypes.ipdlh8
-rw-r--r--dom/ipc/IPCTransferable.ipdlh9
-rw-r--r--dom/ipc/PBrowser.ipdl20
-rw-r--r--dom/ipc/PContent.ipdl38
-rw-r--r--dom/ipc/PWindowGlobal.ipdl4
-rw-r--r--dom/ipc/TabContext.cpp4
-rw-r--r--dom/ipc/WindowGlobalChild.cpp23
-rw-r--r--dom/ipc/WindowGlobalParent.cpp28
-rw-r--r--dom/ipc/WindowGlobalParent.h2
-rw-r--r--dom/l10n/DocumentL10n.cpp9
-rw-r--r--dom/l10n/tests/mochitest/document_l10n/test_docl10n_ready_rejected.html11
-rw-r--r--dom/locales/en-US/chrome/accessibility/win/accessible.properties9
-rw-r--r--dom/locales/en-US/chrome/security/csp.properties9
-rw-r--r--dom/mathml/MathMLElement.cpp7
-rw-r--r--dom/mathml/MathMLElement.h2
-rw-r--r--dom/media/AsyncLogger.h2
-rw-r--r--dom/media/AudioInputSource.cpp105
-rw-r--r--dom/media/AudioInputSource.h13
-rw-r--r--dom/media/AudioRingBuffer.cpp14
-rw-r--r--dom/media/AudioRingBuffer.h10
-rw-r--r--dom/media/AudioStream.cpp3
-rw-r--r--dom/media/AudioStream.h2
-rw-r--r--dom/media/ChannelMediaResource.cpp14
-rw-r--r--dom/media/CubebInputStream.cpp7
-rw-r--r--dom/media/CubebInputStream.h3
-rw-r--r--dom/media/CubebUtils.cpp45
-rw-r--r--dom/media/CubebUtils.h9
-rw-r--r--dom/media/DeviceInputTrack.cpp57
-rw-r--r--dom/media/DeviceInputTrack.h11
-rw-r--r--dom/media/EncoderTraits.cpp2
-rw-r--r--dom/media/ExternalEngineStateMachine.h4
-rw-r--r--dom/media/GraphDriver.cpp141
-rw-r--r--dom/media/GraphDriver.h48
-rw-r--r--dom/media/ImageConversion.cpp (renamed from dom/media/ImageToI420.cpp)55
-rw-r--r--dom/media/ImageConversion.h (renamed from dom/media/ImageToI420.h)6
-rw-r--r--dom/media/MediaFormatReader.cpp71
-rw-r--r--dom/media/MediaFormatReader.h12
-rw-r--r--dom/media/MediaInfo.h27
-rw-r--r--dom/media/MediaManager.cpp257
-rw-r--r--dom/media/MediaManager.h2
-rw-r--r--dom/media/MediaResult.h2
-rw-r--r--dom/media/MediaTimer.cpp4
-rw-r--r--dom/media/MediaTimer.h6
-rw-r--r--dom/media/MediaTrackGraph.cpp63
-rw-r--r--dom/media/MediaTrackGraph.h18
-rw-r--r--dom/media/MediaTrackGraphImpl.h13
-rw-r--r--dom/media/SeekJob.cpp4
-rw-r--r--dom/media/SeekJob.h4
-rw-r--r--dom/media/VideoFrameConverter.h2
-rw-r--r--dom/media/VideoUtils.cpp21
-rw-r--r--dom/media/VideoUtils.h5
-rw-r--r--dom/media/WavDumper.h15
-rw-r--r--dom/media/doctor/DDLifetime.cpp1
-rw-r--r--dom/media/doctor/test/gtest/moz.build7
-rw-r--r--dom/media/driftcontrol/AudioDriftCorrection.cpp9
-rw-r--r--dom/media/driftcontrol/AudioResampler.cpp17
-rw-r--r--dom/media/driftcontrol/AudioResampler.h21
-rw-r--r--dom/media/driftcontrol/DriftController.cpp32
-rw-r--r--dom/media/driftcontrol/DriftController.h33
-rw-r--r--dom/media/driftcontrol/DynamicResampler.cpp83
-rw-r--r--dom/media/driftcontrol/DynamicResampler.h148
-rw-r--r--dom/media/driftcontrol/gtest/TestAudioDriftCorrection.cpp60
-rw-r--r--dom/media/driftcontrol/gtest/TestAudioResampler.cpp50
-rw-r--r--dom/media/driftcontrol/gtest/TestDriftController.cpp97
-rw-r--r--dom/media/driftcontrol/gtest/TestDynamicResampler.cpp76
-rwxr-xr-xdom/media/driftcontrol/plot.py8
-rw-r--r--dom/media/encoder/VP8TrackEncoder.cpp2
-rw-r--r--dom/media/gmp/CDMStorageIdProvider.cpp2
-rw-r--r--dom/media/gmp/ChromiumCDMChild.h2
-rw-r--r--dom/media/gmp/GMPVideoDecoderChild.cpp55
-rw-r--r--dom/media/gmp/GMPVideoDecoderChild.h1
-rw-r--r--dom/media/gmp/GMPVideoEncoderChild.cpp43
-rw-r--r--dom/media/gmp/GMPVideoEncoderChild.h1
-rw-r--r--dom/media/gmp/mozIGeckoMediaPluginService.idl2
-rw-r--r--dom/media/gtest/AudioVerifier.h4
-rw-r--r--dom/media/gtest/GMPTestMonitor.h4
-rw-r--r--dom/media/gtest/MockCubeb.cpp301
-rw-r--r--dom/media/gtest/MockCubeb.h184
-rw-r--r--dom/media/gtest/TestAudioCallbackDriver.cpp457
-rw-r--r--dom/media/gtest/TestAudioInputProcessing.cpp175
-rw-r--r--dom/media/gtest/TestAudioInputSource.cpp114
-rw-r--r--dom/media/gtest/TestAudioRingBuffer.cpp50
-rw-r--r--dom/media/gtest/TestAudioTrackGraph.cpp740
-rw-r--r--dom/media/gtest/TestCDMStorage.cpp24
-rw-r--r--dom/media/gtest/TestDeviceInputTrack.cpp18
-rw-r--r--dom/media/gtest/TestMP4Demuxer.cpp4
-rw-r--r--dom/media/gtest/TestMediaDataEncoder.cpp2
-rw-r--r--dom/media/gtest/TestWebMWriter.cpp6
-rw-r--r--dom/media/ipc/MFCDMChild.cpp7
-rw-r--r--dom/media/ipc/MFCDMChild.h2
-rw-r--r--dom/media/ipc/RDDChild.cpp9
-rw-r--r--dom/media/ipc/RDDParent.cpp16
-rw-r--r--dom/media/ipc/RemoteImageHolder.h12
-rw-r--r--dom/media/mediacapabilities/KeyValueStorage.cpp2
-rw-r--r--dom/media/mediacontrol/ContentMediaController.cpp31
-rw-r--r--dom/media/mediacontrol/ContentMediaController.h3
-rw-r--r--dom/media/mediacontrol/MediaControlKeyManager.cpp7
-rw-r--r--dom/media/mediacontrol/MediaControlService.cpp1
-rw-r--r--dom/media/mediacontrol/MediaPlaybackStatus.cpp64
-rw-r--r--dom/media/mediacontrol/MediaPlaybackStatus.h16
-rw-r--r--dom/media/mediacontrol/MediaStatusManager.cpp29
-rw-r--r--dom/media/mediacontrol/MediaStatusManager.h17
-rw-r--r--dom/media/mediacontrol/tests/browser/browser_media_control_position_state.js243
-rw-r--r--dom/media/mediacontrol/tests/browser/head.js81
-rw-r--r--dom/media/mediasource/MediaSource.cpp3
-rw-r--r--dom/media/mediasource/MediaSourceDemuxer.cpp20
-rw-r--r--dom/media/mediasource/MediaSourceDemuxer.h11
-rw-r--r--dom/media/metrics.yaml24
-rw-r--r--dom/media/moz.build6
-rw-r--r--dom/media/ogg/OggDemuxer.cpp4
-rw-r--r--dom/media/platforms/EncoderConfig.cpp42
-rw-r--r--dom/media/platforms/EncoderConfig.h2
-rw-r--r--dom/media/platforms/MediaCodecsSupport.cpp9
-rw-r--r--dom/media/platforms/MediaCodecsSupport.h2
-rw-r--r--dom/media/platforms/PDMFactory.cpp62
-rw-r--r--dom/media/platforms/PDMFactory.h5
-rw-r--r--dom/media/platforms/SimpleMap.h86
-rw-r--r--dom/media/platforms/agnostic/AOMDecoder.cpp2
-rw-r--r--dom/media/platforms/agnostic/DAV1DDecoder.cpp2
-rw-r--r--dom/media/platforms/agnostic/TheoraDecoder.cpp2
-rw-r--r--dom/media/platforms/agnostic/VPXDecoder.cpp2
-rw-r--r--dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp32
-rw-r--r--dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h2
-rw-r--r--dom/media/platforms/android/AndroidDecoderModule.cpp68
-rw-r--r--dom/media/platforms/android/AndroidDecoderModule.h15
-rw-r--r--dom/media/platforms/android/AndroidEncoderModule.cpp3
-rw-r--r--dom/media/platforms/android/RemoteDataDecoder.cpp4
-rw-r--r--dom/media/platforms/apple/AppleEncoderModule.cpp3
-rw-r--r--dom/media/platforms/apple/AppleVTDecoder.cpp2
-rw-r--r--dom/media/platforms/ffmpeg/FFmpegAudioDecoder.cpp23
-rw-r--r--dom/media/platforms/ffmpeg/FFmpegAudioEncoder.cpp11
-rw-r--r--dom/media/platforms/ffmpeg/FFmpegDataDecoder.cpp21
-rw-r--r--dom/media/platforms/ffmpeg/FFmpegDataEncoder.h2
-rw-r--r--dom/media/platforms/ffmpeg/FFmpegEncoderModule.cpp7
-rw-r--r--dom/media/platforms/ffmpeg/FFmpegLibWrapper.cpp121
-rw-r--r--dom/media/platforms/ffmpeg/FFmpegLibWrapper.h4
-rw-r--r--dom/media/platforms/ffmpeg/FFmpegRuntimeLinker.cpp8
-rw-r--r--dom/media/platforms/ffmpeg/FFmpegUtils.h30
-rw-r--r--dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp92
-rw-r--r--dom/media/platforms/ffmpeg/FFmpegVideoDecoder.h3
-rw-r--r--dom/media/platforms/ffmpeg/FFmpegVideoEncoder.cpp4
-rw-r--r--dom/media/platforms/ffmpeg/FFmpegVideoEncoder.h2
-rw-r--r--dom/media/platforms/ffmpeg/ffmpeg61/include/COPYING.LGPLv2.1504
-rw-r--r--dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/avcodec.h3121
-rw-r--r--dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/avdct.h85
-rw-r--r--dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/bsf.h335
-rw-r--r--dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/codec.h382
-rw-r--r--dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/codec_desc.h134
-rw-r--r--dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/codec_id.h676
-rw-r--r--dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/codec_par.h250
-rw-r--r--dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/defs.h344
-rw-r--r--dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/packet.h871
-rw-r--r--dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/vdpau.h168
-rw-r--r--dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/version.h45
-rw-r--r--dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/version_major.h52
-rw-r--r--dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/attributes.h173
-rw-r--r--dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/avconfig.h6
-rw-r--r--dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/avutil.h363
-rw-r--r--dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/buffer.h324
-rw-r--r--dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/channel_layout.h804
-rw-r--r--dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/common.h587
-rw-r--r--dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/cpu.h153
-rw-r--r--dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/dict.h259
-rw-r--r--dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/error.h158
-rw-r--r--dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/frame.h1112
-rw-r--r--dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/hwcontext.h594
-rw-r--r--dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/hwcontext_drm.h169
-rw-r--r--dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/hwcontext_vaapi.h117
-rw-r--r--dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/intfloat.h73
-rw-r--r--dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/log.h388
-rw-r--r--dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/macros.h87
-rw-r--r--dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/mathematics.h305
-rw-r--r--dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/mem.h611
-rw-r--r--dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/pixfmt.h920
-rw-r--r--dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/rational.h226
-rw-r--r--dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/samplefmt.h275
-rw-r--r--dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/version.h121
-rw-r--r--dom/media/platforms/ffmpeg/ffmpeg61/moz.build47
-rw-r--r--dom/media/platforms/ffmpeg/ffvpx/FFVPXRuntimeLinker.cpp48
-rw-r--r--dom/media/platforms/ffmpeg/ffvpx/moz.build2
-rw-r--r--dom/media/platforms/ffmpeg/moz.build1
-rw-r--r--dom/media/platforms/omx/OmxDataDecoder.cpp6
-rw-r--r--dom/media/platforms/omx/OmxDataDecoder.h4
-rw-r--r--dom/media/platforms/wmf/MFTEncoder.cpp22
-rw-r--r--dom/media/platforms/wmf/WMF.h43
-rw-r--r--dom/media/platforms/wmf/WMFDataEncoderUtils.cpp221
-rw-r--r--dom/media/platforms/wmf/WMFDataEncoderUtils.h140
-rw-r--r--dom/media/platforms/wmf/WMFDecoderModule.cpp34
-rw-r--r--dom/media/platforms/wmf/WMFDecoderModule.h15
-rw-r--r--dom/media/platforms/wmf/WMFEncoderModule.cpp3
-rw-r--r--dom/media/platforms/wmf/WMFMediaDataEncoder.cpp399
-rw-r--r--dom/media/platforms/wmf/WMFMediaDataEncoder.h302
-rw-r--r--dom/media/platforms/wmf/WMFVideoMFTManager.cpp6
-rw-r--r--dom/media/platforms/wmf/moz.build2
-rw-r--r--dom/media/test/complete_length_worker.js80
-rw-r--r--dom/media/test/mochitest.toml4
-rw-r--r--dom/media/test/rdd_process_xpcom/RddProcessTest.cpp3
-rw-r--r--dom/media/test/reftest/reftest.list2
-rw-r--r--dom/media/test/test_complete_length.html49
-rw-r--r--dom/media/tests/crashtests/crashtests.list4
-rw-r--r--dom/media/utils/PerformanceRecorder.cpp26
-rw-r--r--dom/media/utils/PerformanceRecorder.h56
-rw-r--r--dom/media/utils/TelemetryProbesReporter.cpp28
-rw-r--r--dom/media/utils/TelemetryProbesReporter.h4
-rw-r--r--dom/media/webaudio/FFTBlock.cpp3
-rw-r--r--dom/media/webcodecs/DecoderAgent.cpp8
-rw-r--r--dom/media/webcodecs/DecoderTemplate.cpp102
-rw-r--r--dom/media/webcodecs/DecoderTemplate.h39
-rw-r--r--dom/media/webcodecs/EncoderTemplate.cpp195
-rw-r--r--dom/media/webcodecs/EncoderTemplate.h17
-rw-r--r--dom/media/webcodecs/VideoEncoder.cpp15
-rw-r--r--dom/media/webcodecs/crashtests/1889831.html21
-rw-r--r--dom/media/webcodecs/crashtests/crashtests.list2
-rw-r--r--dom/media/webrtc/MediaEnginePrefs.h2
-rw-r--r--dom/media/webrtc/MediaEngineWebRTCAudio.cpp125
-rw-r--r--dom/media/webrtc/MediaEngineWebRTCAudio.h36
-rw-r--r--dom/media/webrtc/jsapi/RTCTransformEventRunnable.cpp4
-rw-r--r--dom/media/webrtc/libwebrtcglue/VideoConduit.cpp14
-rw-r--r--dom/media/webrtc/libwebrtcglue/VideoStreamFactory.cpp5
-rw-r--r--dom/media/webrtc/metrics.yaml81
-rw-r--r--dom/media/webrtc/sdp/rsdparsa_capi/src/lib.rs2
-rw-r--r--dom/media/webrtc/sdp/rsdparsa_capi/src/types.rs1
-rw-r--r--dom/media/webrtc/tests/mochitests/head.js2
-rw-r--r--dom/media/webrtc/tests/mochitests/iceTestUtils.js21
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_glean.html182
-rw-r--r--dom/media/webrtc/third_party_build/default_config_env20
-rw-r--r--dom/media/webrtc/third_party_build/elm_rebase.sh25
-rw-r--r--dom/media/webrtc/third_party_build/fetch_github_repo.py11
-rw-r--r--dom/media/webrtc/third_party_build/loop-ff.sh6
-rw-r--r--dom/media/webrtc/third_party_build/prep_repo.sh48
-rw-r--r--dom/media/webrtc/third_party_build/verify_vendoring.sh29
-rw-r--r--dom/media/webrtc/transport/test/moz.build30
-rw-r--r--dom/media/webrtc/transport/test_nr_socket.cpp38
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs.c161
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/log/r_log.c20
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/registry/registry.c2
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/registry/registry_local.c36
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/registry/registrycb.c12
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_assoc.c18
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_crc32.c2
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_data.c4
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_list.c6
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_memory.c10
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_replace.c2
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/p_buf.c8
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/util.c83
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/util.h5
-rw-r--r--dom/media/webrtc/transport/transportlayerdtls.cpp103
-rw-r--r--dom/media/webrtc/transport/transportlayerdtls.h2
-rw-r--r--dom/media/webvtt/vtt.sys.mjs62
-rw-r--r--dom/metrics.yaml6
-rw-r--r--dom/network/ConnectionWorker.cpp6
-rw-r--r--dom/network/interfaces/nsITCPSocketCallback.idl8
-rw-r--r--dom/notification/Notification.cpp114
-rw-r--r--dom/notification/NotificationStorage.sys.mjs60
-rw-r--r--dom/notification/components.conf6
-rw-r--r--dom/notification/moz.build1
-rw-r--r--dom/notification/old/MemoryNotificationDB.sys.mjs21
-rw-r--r--dom/notification/old/NotificationDB.sys.mjs222
-rw-r--r--dom/notification/test/unit/head_notificationdb.js3
-rw-r--r--dom/notification/test/unit/test_notificationdb.js86
-rw-r--r--dom/performance/Performance.cpp20
-rw-r--r--dom/performance/PerformanceStorageWorker.cpp5
-rw-r--r--dom/promise/Promise.cpp8
-rw-r--r--dom/promise/PromiseNativeHandler.h5
-rw-r--r--dom/push/PushManager.cpp13
-rw-r--r--dom/push/PushSubscription.cpp38
-rw-r--r--dom/push/PushSubscriptionOptions.cpp7
-rw-r--r--dom/push/PushUtil.cpp36
-rw-r--r--dom/push/PushUtil.h39
-rw-r--r--dom/push/moz.build2
-rw-r--r--dom/quota/QuotaCommon.cpp6
-rw-r--r--dom/quota/QuotaCommon.h54
-rw-r--r--dom/quota/StorageManager.cpp8
-rw-r--r--dom/quota/test/gtest/TestEncryptedStream.cpp17
-rw-r--r--dom/security/FramingChecker.cpp4
-rw-r--r--dom/security/metrics.yaml14
-rw-r--r--dom/security/nsCSPContext.cpp82
-rw-r--r--dom/security/nsCSPContext.h38
-rw-r--r--dom/security/nsCSPParser.cpp159
-rw-r--r--dom/security/nsCSPParser.h2
-rw-r--r--dom/security/nsCSPUtils.cpp85
-rw-r--r--dom/security/nsCSPUtils.h48
-rw-r--r--dom/security/nsHTTPSOnlyUtils.cpp35
-rw-r--r--dom/security/test/gtest/TestCSPParser.cpp17
-rw-r--r--dom/security/test/https-first/browser_httpsfirst.js9
-rw-r--r--dom/serializers/nsDocumentEncoder.cpp33
-rw-r--r--dom/serviceworkers/ServiceWorkerEvents.cpp1
-rw-r--r--dom/serviceworkers/ServiceWorkerOp.cpp17
-rw-r--r--dom/serviceworkers/ServiceWorkerOp.h3
-rw-r--r--dom/serviceworkers/test/gtest/TestReadWrite.cpp9
-rw-r--r--dom/serviceworkers/test/test_serviceworker_interfaces.js1
-rw-r--r--dom/svg/SVGAElement.cpp7
-rw-r--r--dom/svg/SVGAElement.h2
-rw-r--r--dom/svg/SVGAnimatedLength.cpp21
-rw-r--r--dom/svg/SVGAnimatedLength.h3
-rw-r--r--dom/svg/SVGContentUtils.cpp28
-rw-r--r--dom/svg/SVGContentUtils.h13
-rw-r--r--dom/svg/SVGDefsElement.h2
-rw-r--r--dom/svg/SVGGeometryElement.cpp2
-rw-r--r--dom/svg/SVGGraphicsElement.cpp6
-rw-r--r--dom/svg/SVGGraphicsElement.h2
-rw-r--r--dom/svg/SVGLength.cpp36
-rw-r--r--dom/svg/SVGSwitchElement.cpp56
-rw-r--r--dom/svg/SVGSwitchElement.h3
-rw-r--r--dom/svg/SVGSymbolElement.cpp4
-rw-r--r--dom/svg/SVGSymbolElement.h2
-rw-r--r--dom/svg/SVGTests.cpp158
-rw-r--r--dom/svg/SVGTests.h44
-rw-r--r--dom/svg/test/test_switch.xhtml107
-rw-r--r--dom/svg/test/test_tabindex.html15
-rw-r--r--dom/tests/browser/browser.toml1
-rw-r--r--dom/tests/mochitest/chrome/test_focus.xhtml5
-rw-r--r--dom/tests/mochitest/general/frameStoragePrevented.html4
-rw-r--r--dom/tests/mochitest/general/storagePermissionsUtils.js133
-rw-r--r--dom/tests/mochitest/general/test_interfaces.js5
-rw-r--r--dom/tests/mochitest/general/test_storagePermissionsReject.html2
-rw-r--r--dom/tests/mochitest/general/window_storagePermissions.html2
-rw-r--r--dom/tests/mochitest/general/workerStorageAllowed.js131
-rw-r--r--dom/tests/mochitest/general/workerStoragePrevented.js114
-rw-r--r--dom/tests/mochitest/webcomponents/chrome.toml2
-rw-r--r--dom/tests/mochitest/webcomponents/test_custom_element_auto_import.html (renamed from dom/tests/mochitest/webcomponents/test_custom_element_ensure_custom_element.html)34
-rw-r--r--dom/webauthn/MacOSWebAuthnService.mm20
-rw-r--r--dom/webgpu/Adapter.h1
-rw-r--r--dom/webgpu/CanvasContext.cpp4
-rw-r--r--dom/webgpu/ComputePassEncoder.cpp60
-rw-r--r--dom/webgpu/ComputePassEncoder.h2
-rw-r--r--dom/webgpu/ComputePipeline.cpp7
-rw-r--r--dom/webgpu/Device.cpp49
-rw-r--r--dom/webgpu/RenderBundleEncoder.cpp103
-rw-r--r--dom/webgpu/RenderBundleEncoder.h4
-rw-r--r--dom/webgpu/RenderPassEncoder.cpp148
-rw-r--r--dom/webgpu/RenderPassEncoder.h2
-rw-r--r--dom/webgpu/RenderPipeline.cpp1
-rw-r--r--dom/webgpu/tests/cts/checkout/.github/pull_request_template.md1
-rw-r--r--dom/webgpu/tests/cts/checkout/.github/workflows/pr.yml11
-rw-r--r--dom/webgpu/tests/cts/checkout/.github/workflows/push.yml5
-rw-r--r--dom/webgpu/tests/cts/checkout/.gitignore1
-rw-r--r--dom/webgpu/tests/cts/checkout/Gruntfile.js210
-rw-r--r--dom/webgpu/tests/cts/checkout/cts.code-workspace5
-rw-r--r--dom/webgpu/tests/cts/checkout/docs/adding_timing_metadata.md95
-rw-r--r--dom/webgpu/tests/cts/checkout/docs/build.md55
-rw-r--r--dom/webgpu/tests/cts/checkout/docs/case_cache.md81
-rw-r--r--dom/webgpu/tests/cts/checkout/docs/fp_primer.md102
-rw-r--r--dom/webgpu/tests/cts/checkout/docs/intro/developing.md23
-rw-r--r--dom/webgpu/tests/cts/checkout/docs/terms.md2
-rw-r--r--dom/webgpu/tests/cts/checkout/package-lock.json3303
-rw-r--r--dom/webgpu/tests/cts/checkout/package.json15
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/framework/fixture.ts17
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/framework/test_config.ts18
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/internal/file_loader.ts4
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/internal/logging/log_message.ts51
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/internal/logging/logger.ts5
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/internal/logging/result.ts22
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/internal/logging/test_case_recorder.ts8
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/internal/query/compare.ts5
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/internal/query/parseQuery.ts43
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/internal/query/query.ts6
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/internal/test_group.ts10
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/internal/test_suite_listing.ts2
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/internal/tree.ts7
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/internal/websocket_logger.ts13
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/runtime/cmdline.ts52
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/runtime/helper/options.ts61
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/runtime/helper/test_worker-worker.ts49
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/runtime/helper/test_worker.ts202
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/runtime/helper/utils_worker.ts35
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/runtime/helper/wrap_for_worker.ts54
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/runtime/server.ts62
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/runtime/standalone.ts53
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/runtime/wpt.ts18
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/tools/crawl.ts56
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/tools/dev_server.ts13
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/tools/gen_cache.ts278
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/tools/gen_listings.ts11
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/tools/gen_listings_and_webworkers.ts89
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/tools/gen_wpt_cts_html.ts135
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/tools/merge_listing_times.ts12
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/tools/validate.ts29
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/util/crc32.ts57
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/util/parse_imports.ts36
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/util/util.ts5
-rw-r--r--dom/webgpu/tests/cts/checkout/src/external/README.md2
-rw-r--r--dom/webgpu/tests/cts/checkout/src/external/petamoriken/float16/LICENSE.txt2
-rw-r--r--dom/webgpu/tests/cts/checkout/src/external/petamoriken/float16/float16.d.ts27
-rw-r--r--dom/webgpu/tests/cts/checkout/src/external/petamoriken/float16/float16.js181
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/README.md100
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/hashes.json111
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/abs.binbin0 -> 17976 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/acos.binbin0 -> 26440 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/acosh.binbin0 -> 30248 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/asin.binbin0 -> 26440 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/asinh.binbin0 -> 11664 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/atan.binbin0 -> 20128 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/atan2.binbin0 -> 51608 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/atanh.binbin0 -> 21104 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_addition.binbin0 -> 237960 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_division.binbin0 -> 77496 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_logical.binbin0 -> 12456 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_addition.binbin0 -> 175672 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_matrix_multiplication.binbin0 -> 82608 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_scalar_multiplication.binbin0 -> 291528 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_subtraction.binbin0 -> 181112 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_vector_multiplication.binbin0 -> 152528 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_multiplication.binbin0 -> 207864 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_remainder.binbin0 -> 77496 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_subtraction.binbin0 -> 237960 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/ai_arithmetic.binbin0 -> 831992 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_addition.binbin0 -> 169680 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_division.binbin0 -> 120856 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_logical.binbin0 -> 15912 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_addition.binbin0 -> 557464 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_matrix_multiplication.binbin0 -> 1481368 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_scalar_multiplication.binbin0 -> 906440 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_subtraction.binbin0 -> 534232 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_vector_multiplication.binbin0 -> 601680 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_multiplication.binbin0 -> 171072 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_remainder.binbin0 -> 118944 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_subtraction.binbin0 -> 169680 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_addition.binbin0 -> 279984 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_division.binbin0 -> 174960 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_logical.binbin0 -> 18984 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_addition.binbin0 -> 991896 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_matrix_multiplication.binbin0 -> 2510560 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_scalar_multiplication.binbin0 -> 1534768 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_subtraction.binbin0 -> 995984 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_vector_multiplication.binbin0 -> 950336 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_multiplication.binbin0 -> 257976 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_remainder.binbin0 -> 171384 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_subtraction.binbin0 -> 279968 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/i32_arithmetic.binbin0 -> 727864 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/i32_comparison.binbin0 -> 3872 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/u32_arithmetic.binbin0 -> 225816 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/u32_comparison.binbin0 -> 2192 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/bitcast.binbin0 -> 2240896 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/ceil.binbin0 -> 16016 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/clamp.binbin0 -> 1048136 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/cos.binbin0 -> 20176 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/cosh.binbin0 -> 14904 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/cross.binbin0 -> 829096 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/degrees.binbin0 -> 18048 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/derivatives.binbin0 -> 11264 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/determinant.binbin0 -> 9944 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/distance.binbin0 -> 1731496 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/dot.binbin0 -> 469272 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/exp.binbin0 -> 34592 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/exp2.binbin0 -> 34592 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/faceForward.binbin0 -> 4214688 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/floor.binbin0 -> 16016 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/fma.binbin0 -> 886720 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/fract.binbin0 -> 16408 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/frexp.binbin0 -> 47072 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/inverseSqrt.binbin0 -> 78424 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/ldexp.binbin0 -> 33096 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/length.binbin0 -> 35696 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/log.binbin0 -> 148848 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/log2.binbin0 -> 148848 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/max.binbin0 -> 37944 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/min.binbin0 -> 37944 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/mix.binbin0 -> 5817432 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/modf.binbin0 -> 82448 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/normalize.binbin0 -> 47608 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/pack2x16float.binbin0 -> 1791400 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/pow.binbin0 -> 1308224 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/quantizeToF16.binbin0 -> 8904 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/radians.binbin0 -> 10992 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/reflect.binbin0 -> 277720 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/refract.binbin0 -> 2828888 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/round.binbin0 -> 14816 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/saturate.binbin0 -> 17096 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/sign.binbin0 -> 21224 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/sin.binbin0 -> 20176 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/sinh.binbin0 -> 14904 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/smoothstep.binbin0 -> 629408 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/sqrt.binbin0 -> 10024 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/step.binbin0 -> 33160 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/tan.binbin0 -> 21776 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/tanh.binbin0 -> 11448 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/transpose.binbin0 -> 129192 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/trunc.binbin0 -> 14696 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/af_arithmetic.binbin0 -> 30376 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/af_assignment.binbin0 -> 10296 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/ai_arithmetic.binbin0 -> 3256 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/ai_assignment.binbin0 -> 6936 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/bool_conversion.binbin0 -> 6480 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/f16_arithmetic.binbin0 -> 15208 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/f16_conversion.binbin0 -> 97912 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/f32_arithmetic.binbin0 -> 19544 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/f32_conversion.binbin0 -> 105744 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/i32_arithmetic.binbin0 -> 1648 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/i32_conversion.binbin0 -> 11856 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/u32_conversion.binbin0 -> 10680 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack2x16float.binbin0 -> 4832 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack2x16snorm.binbin0 -> 4968 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack2x16unorm.binbin0 -> 4968 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack4x8snorm.binbin0 -> 7416 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack4x8unorm.binbin0 -> 7416 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-hflip.mp4bin0 -> 3174 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-rotate-180.mp4bin16261 -> 3113 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-rotate-270.mp4bin16261 -> 3211 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-rotate-90.mp4bin16261 -> 3204 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-vflip.mp4bin0 -> 3174 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601.mp4bin16261 -> 3174 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/four-colors-theora-bt601.ogvbin44488 -> 0 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp8-bt601.webmbin17910 -> 2421 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-hflip.mp4bin0 -> 2077 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-rotate-180.mp4bin0 -> 2079 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-rotate-270.mp4bin0 -> 2016 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-rotate-90.mp4bin0 -> 2079 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-vflip.mp4bin0 -> 2077 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601.mp4bin0 -> 2077 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601.webmbin13116 -> 1847 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt709.webmbin12584 -> 1789 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/unittests/conversion.spec.ts14
-rw-r--r--dom/webgpu/tests/cts/checkout/src/unittests/crc32.spec.ts28
-rw-r--r--dom/webgpu/tests/cts/checkout/src/unittests/floating_point.spec.ts2285
-rw-r--r--dom/webgpu/tests/cts/checkout/src/unittests/maths.spec.ts44
-rw-r--r--dom/webgpu/tests/cts/checkout/src/unittests/parse_imports.spec.ts79
-rw-r--r--dom/webgpu/tests/cts/checkout/src/unittests/serialization.spec.ts69
-rw-r--r--dom/webgpu/tests/cts/checkout/src/unittests/texture_ok.spec.ts31
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/adapter/requestDevice.spec.ts59
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/command_buffer/copyTextureToTexture.spec.ts113
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/command_buffer/image_copy.spec.ts94
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/command_buffer/queries/occlusionQuery.spec.ts8
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/memory_sync/texture/readonly_depth_stencil.spec.ts329
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/reflection.spec.ts293
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/render_pipeline/sample_mask.spec.ts24
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/3d_texture_slices.spec.ts363
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/color_target_state.spec.ts8
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/depth.spec.ts9
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/depth_bias.spec.ts12
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/depth_clip_clamp.spec.ts20
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/by_copy.ts3
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/by_ds_test.ts3
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/by_sampling.ts38
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/texture_zero_init_test.ts548
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/texture_zero.spec.ts554
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/shader_module/compilation_info.spec.ts39
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/storage_texture/read_only.spec.ts626
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/storage_texture/read_write.spec.ts385
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/texture_view/format_reinterpretation.spec.ts10
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/texture_view/write.spec.ts347
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/buffer/mapping.spec.ts39
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/features/query_types.spec.ts56
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/limit_utils.ts119
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxBindGroups.spec.ts156
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxBindGroupsPlusVertexBuffers.spec.ts301
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupStorageSize.spec.ts9
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxInterStageShaderComponents.spec.ts8
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxSampledTexturesPerShaderStage.spec.ts50
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxSamplersPerShaderStage.spec.ts54
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxStorageBuffersPerShaderStage.spec.ts85
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxStorageTexturesPerShaderStage.spec.ts46
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxUniformBuffersPerShaderStage.spec.ts46
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxVertexBuffers.spec.ts68
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/compute_pipeline.spec.ts50
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createBindGroup.spec.ts49
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createBindGroupLayout.spec.ts47
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createTexture.spec.ts43
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createView.spec.ts11
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/cmds/copyTextureToTexture.spec.ts15
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/createRenderBundleEncoder.spec.ts44
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/encoder_open_state.spec.ts14
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/programmable/pipeline_bind_group_compat.spec.ts343
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/queries/general.spec.ts42
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/render_bundle.spec.ts14
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/error_scope.spec.ts18
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/gpu_external_texture_expiration.spec.ts35
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/image_copy.ts4
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/texture_related.spec.ts2
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/layout_shader_compat.spec.ts287
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/queue/copyToTexture/CopyExternalImageToTexture.spec.ts4
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/queue/destroyed/query_set.spec.ts53
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/queue/destroyed/texture.spec.ts9
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pass/attachment_compatibility.spec.ts7
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pass/render_pass_descriptor.spec.ts190
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/common.ts11
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/depth_stencil_state.spec.ts12
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/fragment_state.spec.ts77
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/inter_stage.spec.ts72
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/misc.spec.ts34
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/resource_compatibility.spec.ts95
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/resource_usages/texture/in_pass_encoder.spec.ts269
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/resource_usages/texture/in_render_common.spec.ts101
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/resource_usages/texture/in_render_misc.spec.ts307
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/shader_module/entry_point.spec.ts226
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/state/device_lost/destroy.spec.ts12
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/texture/bgra8unorm_storage.spec.ts31
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/utils.ts275
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/validation_test.ts25
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/capability_info.ts125
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/createBindGroup.spec.ts178
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/createBindGroupLayout.spec.ts34
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/encoding/cmds/copyTextureToBuffer.spec.ts9
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/encoding/cmds/copyTextureToTexture.spec.ts94
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/render_pipeline/depth_stencil_state.spec.ts53
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/render_pipeline/shader_module.spec.ts207
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/texture/createTexture.spec.ts131
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/constants.ts5
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/format_info.ts1147
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/gpu_test.ts232
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/idl/constructable.spec.ts54
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/listing_meta.json724
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/multisample_info.ts75
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/print_environment.spec.ts70
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/array/index.spec.ts354
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/matrix/index.spec.ts200
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/structure/index.spec.ts508
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/vector/components.spec.ts118
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/vector/index.spec.ts87
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_addition.cache.ts54
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_addition.spec.ts85
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_comparison.cache.ts90
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_comparison.spec.ts141
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_division.cache.ts57
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_division.spec.ts85
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_addition.cache.ts26
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_addition.spec.ts34
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_matrix_multiplication.cache.ts29
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_matrix_multiplication.spec.ts45
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_scalar_multiplication.cache.ts49
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_scalar_multiplication.spec.ts69
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_subtraction.cache.ts26
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_subtraction.spec.ts34
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_vector_multiplication.cache.ts51
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_vector_multiplication.spec.ts69
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_multiplication.cache.ts54
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_multiplication.spec.ts85
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_remainder.cache.ts57
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_remainder.spec.ts83
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_subtraction.cache.ts54
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_subtraction.spec.ts85
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/ai_arithmetic.cache.ts145
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/ai_arithmetic.spec.ts303
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/ai_comparison.spec.ts124
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/binary.ts8
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/bitwise.spec.ts505
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/bitwise_shift.spec.ts25
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/bool_logical.spec.ts18
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_addition.cache.ts60
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_addition.spec.ts81
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_comparison.cache.ts144
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_comparison.spec.ts159
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_division.cache.ts60
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_division.spec.ts81
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_addition.cache.ts23
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_addition.spec.ts34
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_matrix_multiplication.cache.ts25
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_matrix_multiplication.spec.ts36
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_scalar_multiplication.cache.ts44
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_scalar_multiplication.spec.ts59
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_subtraction.cache.ts23
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_subtraction.spec.ts34
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_vector_multiplication.cache.ts44
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_vector_multiplication.spec.ts59
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_multiplication.cache.ts60
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_multiplication.spec.ts81
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_remainder.cache.ts60
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_remainder.spec.ts81
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_subtraction.cache.ts60
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_subtraction.spec.ts81
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_addition.cache.ts60
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_addition.spec.ts81
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_comparison.cache.ts144
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_comparison.spec.ts159
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_division.cache.ts60
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_division.spec.ts81
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_addition.cache.ts23
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_addition.spec.ts34
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_matrix_multiplication.cache.ts25
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_matrix_multiplication.spec.ts36
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_scalar_multiplication.cache.ts44
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_scalar_multiplication.spec.ts59
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_subtraction.cache.ts23
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_subtraction.spec.ts34
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_vector_multiplication.cache.ts44
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_vector_multiplication.spec.ts59
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_multiplication.cache.ts60
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_multiplication.spec.ts81
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_remainder.cache.ts64
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_remainder.spec.ts81
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_subtraction.cache.ts60
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_subtraction.spec.ts81
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/i32_arithmetic.cache.ts306
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/i32_arithmetic.spec.ts392
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/i32_comparison.cache.ts21
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/i32_comparison.spec.ts36
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/u32_arithmetic.cache.ts293
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/u32_arithmetic.spec.ts379
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/u32_comparison.cache.ts21
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/u32_comparison.spec.ts36
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/abs.cache.ts26
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/abs.spec.ts64
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acos.cache.ts24
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acos.spec.ts57
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acosh.cache.ts24
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acosh.spec.ts56
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/all.spec.ts20
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/any.spec.ts20
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asin.cache.ts24
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asin.spec.ts57
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asinh.cache.ts18
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asinh.spec.ts41
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan.cache.ts25
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan.spec.ts52
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan2.cache.ts35
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan2.spec.ts56
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atanh.cache.ts32
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atanh.spec.ts63
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAdd.spec.ts4
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAnd.spec.ts4
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicCompareExchangeWeak.spec.ts18
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicExchange.spec.ts8
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicLoad.spec.ts4
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMax.spec.ts4
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMin.spec.ts4
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicOr.spec.ts4
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicStore.spec.ts8
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicSub.spec.ts4
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicXor.spec.ts4
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/harness.ts7
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/bitcast.cache.ts837
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/bitcast.spec.ts1142
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/builtin.ts8
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ceil.cache.ts26
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ceil.spec.ts79
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/clamp.cache.ts131
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/clamp.spec.ts140
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cos.cache.ts23
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cos.spec.ts57
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cosh.cache.ts23
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cosh.spec.ts47
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countLeadingZeros.spec.ts6
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countOneBits.spec.ts6
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countTrailingZeros.spec.ts6
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cross.cache.ts25
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cross.spec.ts79
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/degrees.cache.ts24
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/degrees.spec.ts52
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/derivatives.cache.ts14
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/derivatives.ts215
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/determinant.cache.ts99
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/determinant.spec.ts112
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/distance.cache.ts49
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/distance.spec.ts208
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot.cache.ts118
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot.spec.ts238
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot4I8Packed.spec.ts74
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot4U8Packed.spec.ts59
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdx.spec.ts16
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxCoarse.spec.ts16
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxFine.spec.ts16
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdy.spec.ts16
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyCoarse.spec.ts16
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyFine.spec.ts16
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp.cache.ts44
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp.spec.ts69
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp2.cache.ts44
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp2.spec.ts69
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/extractBits.spec.ts20
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/faceForward.cache.ts125
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/faceForward.spec.ts198
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstLeadingBit.spec.ts6
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstTrailingBit.spec.ts6
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/floor.cache.ts26
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/floor.spec.ts61
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fma.cache.ts26
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fma.spec.ts70
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fract.cache.ts50
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fract.spec.ts81
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/frexp.cache.ts103
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/frexp.spec.ts304
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/insertBits.spec.ts18
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/inversesqrt.cache.ts44
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/inversesqrt.spec.ts60
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ldexp.cache.ts61
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ldexp.spec.ts94
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/length.cache.ts42
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/length.spec.ts146
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log.cache.ts30
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log.spec.ts62
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log2.cache.ts30
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log2.spec.ts62
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/max.cache.ts18
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/max.spec.ts106
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/min.cache.ts18
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/min.spec.ts106
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/mix.cache.ts56
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/mix.spec.ts210
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/modf.cache.ts75
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/modf.spec.ts192
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/normalize.cache.ts25
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/normalize.spec.ts88
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16float.cache.ts55
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16float.spec.ts68
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16snorm.spec.ts15
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16unorm.spec.ts15
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8snorm.spec.ts23
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8unorm.spec.ts23
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xI8.spec.ts69
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xI8Clamp.spec.ts73
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xU8.spec.ts54
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xU8Clamp.spec.ts57
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pow.cache.ts24
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pow.spec.ts67
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/quantizeToF16.cache.ts41
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/quantizeToF16.spec.ts46
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/radians.cache.ts18
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/radians.spec.ts44
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reflect.cache.ts26
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reflect.spec.ts151
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/refract.cache.ts116
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/refract.spec.ts191
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reverseBits.spec.ts6
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/round.cache.ts24
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/round.spec.ts55
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/saturate.cache.ts18
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/saturate.spec.ts56
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/select.spec.ts129
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sign.cache.ts31
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sign.spec.ts66
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sin.cache.ts23
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sin.spec.ts57
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sinh.cache.ts23
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sinh.spec.ts47
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/smoothstep.cache.ts25
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/smoothstep.spec.ts71
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sqrt.cache.ts23
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sqrt.spec.ts47
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/step.cache.ts41
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/step.spec.ts66
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tan.cache.ts23
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tan.spec.ts57
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tanh.cache.ts18
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tanh.spec.ts41
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureDimension.spec.ts160
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureDimensions.spec.ts518
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSample.spec.ts99
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleBias.spec.ts22
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleCompare.spec.ts23
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/texture_utils.ts809
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/transpose.cache.ts27
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/transpose.spec.ts85
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/trunc.cache.ts17
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/trunc.spec.ts39
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16float.cache.ts20
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16float.spec.ts25
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm.cache.ts20
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm.spec.ts25
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm.cache.ts20
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm.spec.ts25
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm.cache.ts20
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm.spec.ts25
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm.cache.ts20
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm.spec.ts25
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4xI8.spec.ts56
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4xU8.spec.ts48
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/workgroupUniformLoad.spec.ts182
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/user/ptr_params.spec.ts849
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/case.ts440
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/case_cache.ts39
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/constructor/non_zero.spec.ts797
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/constructor/zero_value.spec.ts162
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/expectation.ts38
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/expression.ts800
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/interval_filter.ts8
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/precedence.spec.ts113
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/address_of_and_indirection.spec.ts171
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_arithmetic.cache.ts13
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_arithmetic.spec.ts30
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_assignment.cache.ts51
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_assignment.spec.ts66
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_arithmetic.cache.ts11
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_arithmetic.spec.ts30
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_assignment.cache.ts21
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_assignment.spec.ts65
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_complement.spec.ts32
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_conversion.cache.ts54
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_conversion.spec.ts87
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_logical.spec.ts4
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_arithmetic.cache.ts13
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_arithmetic.spec.ts18
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_conversion.cache.ts135
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_conversion.spec.ts226
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_arithmetic.cache.ts13
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_arithmetic.spec.ts18
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_conversion.cache.ts79
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_conversion.spec.ts113
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_arithmetic.cache.ts11
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_arithmetic.spec.ts15
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_complement.spec.ts17
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_conversion.cache.ts116
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_conversion.spec.ts158
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_complement.spec.ts17
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_conversion.cache.ts107
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_conversion.spec.ts144
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/unary.ts8
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/call.spec.ts113
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/for.spec.ts50
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/loop.spec.ts60
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/switch.spec.ts33
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/while.spec.ts54
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/memory_layout.spec.ts1059
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/memory_model/barrier.spec.ts179
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/memory_model/memory_model_setup.ts341
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/memory_model/texture_intra_invocation_coherence.spec.ts333
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/robust_access.spec.ts30
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/robust_access_vertex.spec.ts1
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/shader_io/compute_builtins.spec.ts175
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/shader_io/fragment_builtins.spec.ts1410
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/shader_io/user_io.spec.ts213
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/shader_io/workgroup_size.spec.ts150
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/stage.spec.ts133
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/statement/compound.spec.ts137
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/statement/discard.spec.ts645
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/zero_init.spec.ts4
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/types.ts206
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/const_assert/const_assert.spec.ts20
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/compound_statement.spec.ts98
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/const.spec.ts158
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/context_dependent_resolution.spec.ts338
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/let.spec.ts180
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/override.spec.ts178
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/var.spec.ts529
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/access/vector.spec.ts3
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/add_sub_mul.spec.ts320
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/and_or_xor.spec.ts182
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/bitwise_shift.spec.ts182
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/comparison.spec.ts186
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/div_rem.spec.ts279
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/abs.spec.ts114
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/acos.spec.ts162
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/acosh.spec.ts158
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/all.spec.ts191
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/any.spec.ts191
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/arrayLength.spec.ts109
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/asin.spec.ts161
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/asinh.spec.ts153
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atan.spec.ts146
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atan2.spec.ts293
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atanh.spec.ts169
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atomics.spec.ts235
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/barriers.spec.ts109
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/ceil.spec.ts18
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/clamp.spec.ts14
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/const_override_validation.ts292
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/cos.spec.ts59
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/cosh.spec.ts66
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/countLeadingZeros.spec.ts198
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/countOneBits.spec.ts198
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/countTrailingZeros.spec.ts198
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/cross.spec.ts122
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/degrees.spec.ts56
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/derivatives.spec.ts129
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/determinant.spec.ts95
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/distance.spec.ts149
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/dot4I8Packed.spec.ts66
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/dot4U8Packed.spec.ts66
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/exp.spec.ts122
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/exp2.spec.ts122
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/extractBits.spec.ts218
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/faceForward.spec.ts152
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/firstLeadingBit.spec.ts198
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/firstTrailingBit.spec.ts198
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/floor.spec.ts108
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/fract.spec.ts94
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/frexp.spec.ts94
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/insertBits.spec.ts241
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/inverseSqrt.spec.ts76
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/length.spec.ts58
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/log.spec.ts50
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/log2.spec.ts50
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/max.spec.ts91
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/min.spec.ts91
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/modf.spec.ts17
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/normalize.spec.ts146
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack2x16snorm.spec.ts58
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack2x16unorm.spec.ts58
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4x8snorm.spec.ts58
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4x8unorm.spec.ts58
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4xI8.spec.ts62
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4xI8Clamp.spec.ts62
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4xU8.spec.ts62
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4xU8Clamp.spec.ts62
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/quantizeToF16.spec.ts113
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/radians.spec.ts50
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/reflect.spec.ts131
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/reverseBits.spec.ts198
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/round.spec.ts31
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/saturate.spec.ts17
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/select.spec.ts250
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/shader_stage_utils.ts64
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sign.spec.ts63
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sin.spec.ts60
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sinh.spec.ts66
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/smoothstep.spec.ts241
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sqrt.spec.ts66
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/step.spec.ts108
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/tan.spec.ts76
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/tanh.spec.ts98
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureGather.spec.ts335
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureGatherCompare.spec.ts264
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureLoad.spec.ts370
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSample.spec.ts267
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleBaseClampToEdge.spec.ts54
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleBias.spec.ts309
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleCompare.spec.ts308
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleCompareLevel.spec.ts268
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleGrad.spec.ts317
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleLevel.spec.ts282
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureStore.spec.ts168
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/trunc.spec.ts94
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack2x16float.spec.ts62
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack2x16snorm.spec.ts62
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack2x16unorm.spec.ts62
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack4x8snorm.spec.ts62
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack4x8unorm.spec.ts62
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack4xI8.spec.ts61
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack4xU8.spec.ts61
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/workgroupUniformLoad.spec.ts122
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/overload_resolution.spec.ts268
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/precedence.spec.ts188
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/unary/address_of_and_indirection.spec.ts243
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/unary/arithmetic_negation.spec.ts114
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/unary/bitwise_complement.spec.ts114
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/unary/logical_negation.spec.ts114
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/extension/pointer_composite_access.spec.ts130
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/extension/readonly_and_readwrite_storage_textures.spec.ts48
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/functions/alias_analysis.spec.ts545
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/functions/restrictions.spec.ts313
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/break.spec.ts4
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/break_if.spec.ts141
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/compound.spec.ts52
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/continuing.spec.ts185
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/diagnostic.spec.ts260
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/enable.spec.ts18
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/must_use.spec.ts63
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/pipeline_stage.spec.ts42
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/requires.spec.ts103
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/semicolon.spec.ts15
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/shadow_builtins.spec.ts995
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/statement_behavior.spec.ts143
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/binding.spec.ts14
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/builtins.spec.ts4
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/group.spec.ts14
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/group_and_binding.spec.ts4
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/layout_constraints.spec.ts543
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/locations.spec.ts149
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/size.spec.ts20
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/alias.spec.ts122
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/array.spec.ts122
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/atomics.spec.ts145
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/matrix.spec.ts152
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/textures.spec.ts170
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/uniformity/uniformity.spec.ts209
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/util/binary_stream.ts10
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/util/check_contents.ts38
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/util/color_space_conversion.ts32
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/util/compare.ts32
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/util/constants.ts15
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/util/conversion.ts1688
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/util/device_pool.ts12
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/util/floating_point.ts601
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/util/math.ts465
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/util/pretty_diff_tables.ts37
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/util/shader.ts25
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/base.ts44
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/color_space_conversions.spec.ts108
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_data.spec.ts16
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_data.ts50
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_view.ts17
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texture_ok.ts20
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/canvas/configure.spec.ts5
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/canvas/getCurrentTexture.spec.ts75
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/canvas/readbackFromWebGPUCanvas.spec.ts155
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/copyToTexture/canvas.spec.ts6
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/copyToTexture/video.spec.ts86
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/external_texture/video.spec.ts349
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_colorspace_bgra8unorm.https.html1
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_colorspace_rgba16float.https.html2
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_colorspace_rgba8unorm.https.html1
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied_copy.https.html2
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied_draw.https.html2
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba16float_premultiplied_copy.https.html2
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba16float_premultiplied_draw.https.html2
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_premultiplied_copy.https.html2
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_premultiplied_draw.https.html2
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_image_rendering.https.html12
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/delay_get_texture.html.ts46
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/delay_get_texture.https.html10
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/ref/canvas_image_rendering-ref.html12
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/ref/delay_get_texture-ref.html17
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/util.ts430
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/worker/worker.spec.ts72
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/worker/worker.ts20
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/worker/worker_launcher.ts59
-rwxr-xr-xdom/webgpu/tests/cts/checkout/tools/gen_listings7
-rw-r--r--dom/webgpu/tests/cts/checkout/tools/gen_listings_and_webworkers8
-rwxr-xr-xdom/webgpu/tests/cts/checkout/tools/gen_version13
-rw-r--r--dom/webgpu/tests/cts/checkout/tools/gen_wpt_cfg_chunked2sec.json1
-rw-r--r--dom/webgpu/tests/cts/checkout/tools/gen_wpt_cfg_unchunked.json1
-rw-r--r--dom/webgpu/tests/cts/checkout/tools/server6
-rw-r--r--dom/webgpu/tests/cts/checkout_commit.txt2
-rw-r--r--dom/webgpu/tests/cts/vendor/Cargo.lock106
-rw-r--r--dom/webgpu/tests/cts/vendor/Cargo.toml2
-rw-r--r--dom/webgpu/tests/cts/vendor/src/fs.rs33
-rw-r--r--dom/webgpu/tests/cts/vendor/src/main.rs49
-rw-r--r--dom/webidl/AddonManager.webidl19
-rw-r--r--dom/webidl/CSPDictionaries.webidl2
-rw-r--r--dom/webidl/CSSMarginRule.webidl15
-rw-r--r--dom/webidl/CSSStyleRule.webidl4
-rw-r--r--dom/webidl/Clipboard.webidl2
-rw-r--r--dom/webidl/HTMLAnchorElement.webidl2
-rw-r--r--dom/webidl/HTMLAreaElement.webidl2
-rw-r--r--dom/webidl/HTMLSourceElement.webidl4
-rw-r--r--dom/webidl/IDBFactory.webidl10
-rw-r--r--dom/webidl/PushSubscription.webidl6
-rw-r--r--dom/webidl/RTCDataChannel.webidl2
-rw-r--r--dom/webidl/Selection.webidl2
-rw-r--r--dom/webidl/StorageEvent.webidl7
-rw-r--r--dom/webidl/WebGLRenderingContext.webidl5
-rw-r--r--dom/webidl/WebGPU.webidl3
-rw-r--r--dom/webidl/moz.build1
-rw-r--r--dom/webscheduling/WebTaskSchedulerWorker.cpp4
-rw-r--r--dom/websocket/WebSocket.cpp38
-rw-r--r--dom/webtransport/parent/WebTransportParent.cpp26
-rw-r--r--dom/workers/EventWithOptionsRunnable.cpp16
-rw-r--r--dom/workers/MessageEventRunnable.cpp112
-rw-r--r--dom/workers/MessageEventRunnable.h16
-rw-r--r--dom/workers/RuntimeService.cpp47
-rw-r--r--dom/workers/ScriptLoader.cpp6
-rw-r--r--dom/workers/Worker.cpp6
-rw-r--r--dom/workers/WorkerCSPEventListener.cpp5
-rw-r--r--dom/workers/WorkerDebugger.cpp8
-rw-r--r--dom/workers/WorkerDocumentListener.cpp7
-rw-r--r--dom/workers/WorkerError.cpp12
-rw-r--r--dom/workers/WorkerEventTarget.cpp7
-rw-r--r--dom/workers/WorkerNavigator.cpp2
-rw-r--r--dom/workers/WorkerPrivate.cpp267
-rw-r--r--dom/workers/WorkerPrivate.h55
-rw-r--r--dom/workers/WorkerRef.cpp5
-rw-r--r--dom/workers/WorkerRef.h2
-rw-r--r--dom/workers/WorkerRunnable.cpp563
-rw-r--r--dom/workers/WorkerRunnable.h304
-rw-r--r--dom/workers/WorkerScope.cpp22
-rw-r--r--dom/workers/WorkerThread.cpp87
-rw-r--r--dom/workers/WorkerThread.h11
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerChild.cpp6
-rw-r--r--dom/workers/test/test_worker_interfaces.js1
-rw-r--r--dom/xhr/XMLHttpRequestString.cpp3
-rw-r--r--dom/xhr/XMLHttpRequestWorker.cpp13
-rw-r--r--dom/xul/nsXULElement.cpp23
-rw-r--r--dom/xul/nsXULElement.h4
1342 files changed, 83942 insertions, 23006 deletions
diff --git a/dom/base/AbstractRange.cpp b/dom/base/AbstractRange.cpp
index c9138a19d2..f5d584599b 100644
--- a/dom/base/AbstractRange.cpp
+++ b/dom/base/AbstractRange.cpp
@@ -49,6 +49,8 @@ template nsresult AbstractRange::SetStartAndEndInternal(
const RawRangeBoundary& aEndBoundary, StaticRange* aRange);
template bool AbstractRange::MaybeCacheToReuse(nsRange& aInstance);
template bool AbstractRange::MaybeCacheToReuse(StaticRange& aInstance);
+template bool AbstractRange::MaybeCacheToReuse(
+ CrossShadowBoundaryRange& aInstance);
bool AbstractRange::sHasShutDown = false;
@@ -209,6 +211,12 @@ void AbstractRange::Shutdown() {
cachedRanges->Clear();
delete cachedRanges;
}
+ if (nsTArray<RefPtr<CrossShadowBoundaryRange>>* cachedRanges =
+ CrossShadowBoundaryRange::sCachedRanges) {
+ CrossShadowBoundaryRange::sCachedRanges = nullptr;
+ cachedRanges->Clear();
+ delete cachedRanges;
+ }
}
// static
diff --git a/dom/base/BodyConsumer.cpp b/dom/base/BodyConsumer.cpp
index 754586a08b..81c9bb113d 100644
--- a/dom/base/BodyConsumer.cpp
+++ b/dom/base/BodyConsumer.cpp
@@ -74,7 +74,7 @@ class ContinueConsumeBodyRunnable final : public MainThreadWorkerRunnable {
ContinueConsumeBodyRunnable(BodyConsumer* aBodyConsumer,
WorkerPrivate* aWorkerPrivate, nsresult aStatus,
uint32_t aLength, uint8_t* aResult)
- : MainThreadWorkerRunnable(aWorkerPrivate, "ContinueConsumeBodyRunnable"),
+ : MainThreadWorkerRunnable("ContinueConsumeBodyRunnable"),
mBodyConsumer(aBodyConsumer),
mStatus(aStatus),
mLength(aLength),
@@ -97,7 +97,7 @@ class AbortConsumeBodyControlRunnable final
public:
AbortConsumeBodyControlRunnable(BodyConsumer* aBodyConsumer,
WorkerPrivate* aWorkerPrivate)
- : MainThreadWorkerControlRunnable(aWorkerPrivate),
+ : MainThreadWorkerControlRunnable("AbortConsumeBodyControlRunnable"),
mBodyConsumer(aBodyConsumer) {
MOZ_ASSERT(NS_IsMainThread());
}
@@ -131,7 +131,7 @@ class MOZ_STACK_CLASS AutoFailConsumeBody final {
RefPtr<AbortConsumeBodyControlRunnable> r =
new AbortConsumeBodyControlRunnable(mBodyConsumer,
mWorkerRef->Private());
- if (!r->Dispatch()) {
+ if (!r->Dispatch(mWorkerRef->Private())) {
MOZ_CRASH("We are going to leak");
}
return;
@@ -159,8 +159,7 @@ class ContinueConsumeBlobBodyRunnable final : public MainThreadWorkerRunnable {
ContinueConsumeBlobBodyRunnable(BodyConsumer* aBodyConsumer,
WorkerPrivate* aWorkerPrivate,
BlobImpl* aBlobImpl)
- : MainThreadWorkerRunnable(aWorkerPrivate,
- "ContinueConsumeBlobBodyRunnable"),
+ : MainThreadWorkerRunnable("ContinueConsumeBlobBodyRunnable"),
mBodyConsumer(aBodyConsumer),
mBlobImpl(aBlobImpl) {
MOZ_ASSERT(NS_IsMainThread());
@@ -182,7 +181,7 @@ class AbortConsumeBlobBodyControlRunnable final
public:
AbortConsumeBlobBodyControlRunnable(BodyConsumer* aBodyConsumer,
WorkerPrivate* aWorkerPrivate)
- : MainThreadWorkerControlRunnable(aWorkerPrivate),
+ : MainThreadWorkerControlRunnable("AbortConsumeBlobBodyControlRunnable"),
mBodyConsumer(aBodyConsumer) {
MOZ_ASSERT(NS_IsMainThread());
}
@@ -227,7 +226,7 @@ class ConsumeBodyDoneObserver final : public nsIStreamLoaderObserver,
RefPtr<ContinueConsumeBodyRunnable> r = new ContinueConsumeBodyRunnable(
mBodyConsumer, mWorkerRef->Private(), aStatus, aResultLength,
nonconstResult);
- if (r->Dispatch()) {
+ if (r->Dispatch(mWorkerRef->Private())) {
// The caller is responsible for data.
return NS_SUCCESS_ADOPTED_DATA;
}
@@ -239,7 +238,7 @@ class ConsumeBodyDoneObserver final : public nsIStreamLoaderObserver,
RefPtr<AbortConsumeBodyControlRunnable> r =
new AbortConsumeBodyControlRunnable(mBodyConsumer,
mWorkerRef->Private());
- if (NS_WARN_IF(!r->Dispatch())) {
+ if (NS_WARN_IF(!r->Dispatch(mWorkerRef->Private()))) {
return NS_ERROR_FAILURE;
}
@@ -620,14 +619,14 @@ void BodyConsumer::DispatchContinueConsumeBlobBody(
new ContinueConsumeBlobBodyRunnable(this, aWorkerRef->Private(),
aBlobImpl);
- if (r->Dispatch()) {
+ if (r->Dispatch(aWorkerRef->Private())) {
return;
}
} else {
RefPtr<ContinueConsumeBodyRunnable> r = new ContinueConsumeBodyRunnable(
this, aWorkerRef->Private(), NS_ERROR_DOM_ABORT_ERR, 0, nullptr);
- if (r->Dispatch()) {
+ if (r->Dispatch(aWorkerRef->Private())) {
return;
}
}
@@ -638,7 +637,7 @@ void BodyConsumer::DispatchContinueConsumeBlobBody(
RefPtr<AbortConsumeBlobBodyControlRunnable> r =
new AbortConsumeBlobBodyControlRunnable(this, aWorkerRef->Private());
- Unused << NS_WARN_IF(!r->Dispatch());
+ Unused << NS_WARN_IF(!r->Dispatch(aWorkerRef->Private()));
}
/*
@@ -673,12 +672,14 @@ void BodyConsumer::ContinueConsumeBody(nsresult aStatus, uint32_t aResultLength,
if (NS_WARN_IF(NS_FAILED(aStatus))) {
// Per
- // https://fetch.spec.whatwg.org/#concept-read-all-bytes-from-readablestream
+ // https://streams.spec.whatwg.org/#readablestreamdefaultreader-read-all-bytes
// Decoding errors should reject with a TypeError
if (aStatus == NS_ERROR_INVALID_CONTENT_ENCODING) {
localPromise->MaybeRejectWithTypeError<MSG_DOM_DECODING_FAILED>();
} else if (aStatus == NS_ERROR_DOM_WRONG_TYPE_ERR) {
localPromise->MaybeRejectWithTypeError<MSG_FETCH_BODY_WRONG_TYPE>();
+ } else if (aStatus == NS_ERROR_NET_PARTIAL_TRANSFER) {
+ localPromise->MaybeRejectWithTypeError<MSG_FETCH_PARTIAL>();
} else {
localPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
}
diff --git a/dom/base/ContentIterator.cpp b/dom/base/ContentIterator.cpp
index 0b405f0348..80c795d137 100644
--- a/dom/base/ContentIterator.cpp
+++ b/dom/base/ContentIterator.cpp
@@ -715,7 +715,7 @@ nsIContent* ContentIteratorBase<NodeType>::GetNextSibling(
// For shadow root, instead of getting to the sibling of the parent
// directly, we need to get into the light tree of the parent to handle
// slotted contents.
- if (ShadowRoot* shadowRoot = ShadowRoot::FromNode(aNode)) {
+ if (aNode->IsShadowRoot()) {
if (nsIContent* child = parent->GetFirstChild()) {
return child;
}
diff --git a/dom/base/CrossShadowBoundaryRange.cpp b/dom/base/CrossShadowBoundaryRange.cpp
new file mode 100644
index 0000000000..33fa9760e6
--- /dev/null
+++ b/dom/base/CrossShadowBoundaryRange.cpp
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "mozilla/dom/CrossShadowBoundaryRange.h"
+#include "nsContentUtils.h"
+#include "nsINode.h"
+
+namespace mozilla::dom {
+template already_AddRefed<CrossShadowBoundaryRange>
+CrossShadowBoundaryRange::Create(const RangeBoundary& aStartBoundary,
+ const RangeBoundary& aEndBoundary);
+template already_AddRefed<CrossShadowBoundaryRange>
+CrossShadowBoundaryRange::Create(const RangeBoundary& aStartBoundary,
+ const RawRangeBoundary& aEndBoundary);
+template already_AddRefed<CrossShadowBoundaryRange>
+CrossShadowBoundaryRange::Create(const RawRangeBoundary& aStartBoundary,
+ const RangeBoundary& aEndBoundary);
+template already_AddRefed<CrossShadowBoundaryRange>
+CrossShadowBoundaryRange::Create(const RawRangeBoundary& aStartBoundary,
+ const RawRangeBoundary& aEndBoundary);
+
+template void CrossShadowBoundaryRange::DoSetRange(
+ const RangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary,
+ nsINode* aRootNode);
+template void CrossShadowBoundaryRange::DoSetRange(
+ const RangeBoundary& aStartBoundary, const RawRangeBoundary& aEndBoundary,
+ nsINode* aRootNode);
+template void CrossShadowBoundaryRange::DoSetRange(
+ const RawRangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary,
+ nsINode* aRootNode);
+template void CrossShadowBoundaryRange::DoSetRange(
+ const RawRangeBoundary& aStartBoundary,
+ const RawRangeBoundary& aEndBoundary, nsINode* aRootNode);
+
+template nsresult CrossShadowBoundaryRange::SetStartAndEnd(
+ const RangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary);
+template nsresult CrossShadowBoundaryRange::SetStartAndEnd(
+ const RangeBoundary& aStartBoundary, const RawRangeBoundary& aEndBoundary);
+template nsresult CrossShadowBoundaryRange::SetStartAndEnd(
+ const RawRangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary);
+template nsresult CrossShadowBoundaryRange::SetStartAndEnd(
+ const RawRangeBoundary& aStartBoundary,
+ const RawRangeBoundary& aEndBoundary);
+
+nsTArray<RefPtr<CrossShadowBoundaryRange>>*
+ CrossShadowBoundaryRange::sCachedRanges = nullptr;
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(CrossShadowBoundaryRange)
+
+NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_INTERRUPTABLE_LAST_RELEASE(
+ CrossShadowBoundaryRange,
+ DoSetRange(RawRangeBoundary(), RawRangeBoundary(), nullptr),
+ AbstractRange::MaybeCacheToReuse(*this))
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CrossShadowBoundaryRange)
+NS_INTERFACE_MAP_END_INHERITING(CrossShadowBoundaryRange)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(CrossShadowBoundaryRange)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(CrossShadowBoundaryRange,
+ StaticRange)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mCommonAncestor)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(CrossShadowBoundaryRange,
+ StaticRange)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCommonAncestor)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(CrossShadowBoundaryRange,
+ StaticRange)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+/* static */
+template <typename SPT, typename SRT, typename EPT, typename ERT>
+already_AddRefed<CrossShadowBoundaryRange> CrossShadowBoundaryRange::Create(
+ const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
+ const RangeBoundaryBase<EPT, ERT>& aEndBoundary) {
+ RefPtr<CrossShadowBoundaryRange> range;
+ if (!sCachedRanges || sCachedRanges->IsEmpty()) {
+ range = new CrossShadowBoundaryRange(aStartBoundary.Container());
+ } else {
+ range = sCachedRanges->PopLastElement().forget();
+ }
+
+ range->Init(aStartBoundary.Container());
+ range->DoSetRange(aStartBoundary, aEndBoundary, nullptr);
+ return range.forget();
+}
+
+template <typename SPT, typename SRT, typename EPT, typename ERT>
+void CrossShadowBoundaryRange::DoSetRange(
+ const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
+ const RangeBoundaryBase<EPT, ERT>& aEndBoundary, nsINode* aRootNode) {
+ // aRootNode is useless to CrossShadowBoundaryRange because aStartBoundary
+ // and aEndBoundary could have different roots.
+ StaticRange::DoSetRange(aStartBoundary, aEndBoundary, nullptr);
+
+ nsINode* startRoot = RangeUtils::ComputeRootNode(mStart.Container());
+ nsINode* endRoot = RangeUtils::ComputeRootNode(mEnd.Container());
+
+ if (startRoot == endRoot) {
+ // This should be the case when Release() is called.
+ MOZ_ASSERT(!startRoot && !endRoot);
+ mCommonAncestor = startRoot;
+ } else {
+ mCommonAncestor =
+ nsContentUtils::GetClosestCommonShadowIncludingInclusiveAncestor(
+ mStart.Container(), mEnd.Container());
+ }
+}
+} // namespace mozilla::dom
diff --git a/dom/base/CrossShadowBoundaryRange.h b/dom/base/CrossShadowBoundaryRange.h
new file mode 100644
index 0000000000..3ad4fa1793
--- /dev/null
+++ b/dom/base/CrossShadowBoundaryRange.h
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla_dom_CrossShadowBoundaryRange_h
+#define mozilla_dom_CrossShadowBoundaryRange_h
+
+#include "mozilla/RangeBoundary.h"
+#include "mozilla/RangeUtils.h"
+#include "mozilla/dom/AbstractRange.h"
+#include "mozilla/dom/StaticRange.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+class CrossShadowBoundaryRange final : public StaticRange {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_IMETHODIMP_(void) DeleteCycleCollectable(void) override;
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(
+ CrossShadowBoundaryRange, StaticRange)
+
+ CrossShadowBoundaryRange() = delete;
+ explicit CrossShadowBoundaryRange(const StaticRange& aOther) = delete;
+
+ template <typename SPT, typename SRT, typename EPT, typename ERT>
+ static already_AddRefed<CrossShadowBoundaryRange> Create(
+ const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
+ const RangeBoundaryBase<EPT, ERT>& aEndBoundary);
+
+ void NotifyNodeBecomesShadowHost(nsINode* aNode) {
+ if (aNode == mStart.Container()) {
+ mStart.NotifyParentBecomesShadowHost();
+ }
+
+ if (aNode == mEnd.Container()) {
+ mEnd.NotifyParentBecomesShadowHost();
+ }
+ }
+
+ nsINode* GetCommonAncestor() const { return mCommonAncestor; }
+
+ // CrossShadowBoundaryRange should have a very limited usage.
+ nsresult SetStartAndEnd(nsINode* aStartContainer, uint32_t aStartOffset,
+ nsINode* aEndContainer, uint32_t aEndOffset) = delete;
+
+ template <typename SPT, typename SRT, typename EPT, typename ERT>
+ nsresult SetStartAndEnd(const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
+ const RangeBoundaryBase<EPT, ERT>& aEndBoundary) {
+ return StaticRange::SetStartAndEnd(aStartBoundary, aEndBoundary);
+ }
+
+ private:
+ explicit CrossShadowBoundaryRange(nsINode* aNode) : StaticRange(aNode) {}
+ virtual ~CrossShadowBoundaryRange() = default;
+
+ /**
+ * DoSetRange() is called when `AbstractRange::SetStartAndEndInternal()` sets
+ * mStart and mEnd.
+ *
+ * @param aStartBoundary Computed start point. This must equals or be before
+ * aEndBoundary in the DOM tree order.
+ * @param aEndBoundary Computed end point.
+ * @param aRootNode The root node of aStartBoundary or aEndBoundary.
+ * It's useless to CrossShadowBoundaryRange.
+ */
+ template <typename SPT, typename SRT, typename EPT, typename ERT>
+ void DoSetRange(const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
+ const RangeBoundaryBase<EPT, ERT>& aEndBoundary,
+ nsINode* aRootNode);
+
+ // This is either NULL if this CrossShadowBoundaryRange has been
+ // reset by Release() or the closest common shadow-including ancestor
+ // of mStart and mEnd.
+ nsCOMPtr<nsINode> mCommonAncestor;
+
+ static nsTArray<RefPtr<CrossShadowBoundaryRange>>* sCachedRanges;
+
+ friend class AbstractRange;
+};
+} // namespace dom
+} // namespace mozilla
+
+#endif // #ifndef mozilla_dom_CrossShadowBoundaryRange_h
diff --git a/dom/base/DOMIntersectionObserver.cpp b/dom/base/DOMIntersectionObserver.cpp
index 12f7ee3029..37d17ddcd3 100644
--- a/dom/base/DOMIntersectionObserver.cpp
+++ b/dom/base/DOMIntersectionObserver.cpp
@@ -323,7 +323,8 @@ static Maybe<nsRect> ComputeTheIntersection(
//
// `intersectionRect` is kept relative to `target` during the loop.
auto inflowRect = nsLayoutUtils::GetAllInFlowRectsUnion(
- target, target, nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS);
+ target, target,
+ nsLayoutUtils::GetAllInFlowRectsFlag::AccountForTransforms);
// For content-visibility, we need to observe the overflow clip edge,
// https://drafts.csswg.org/css-contain-2/#close-to-the-viewport
if (aIsForProximityToViewport ==
diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp
index 8cbf8b8075..68e4100540 100644
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -16423,47 +16423,33 @@ WindowContext* Document::GetWindowContextForPageUseCounters() const {
return wc;
}
-void Document::UpdateIntersectionObservations(TimeStamp aNowTime) {
- if (mIntersectionObservers.IsEmpty()) {
- return;
- }
-
- DOMHighResTimeStamp time = 0;
- if (nsPIDOMWindowInner* win = GetInnerWindow()) {
- if (Performance* perf = win->GetPerformance()) {
- time = perf->TimeStampToDOMHighResForRendering(aNowTime);
+void Document::UpdateIntersections(TimeStamp aNowTime) {
+ if (!mIntersectionObservers.IsEmpty()) {
+ DOMHighResTimeStamp time = 0;
+ if (nsPIDOMWindowInner* win = GetInnerWindow()) {
+ if (Performance* perf = win->GetPerformance()) {
+ time = perf->TimeStampToDOMHighResForRendering(aNowTime);
+ }
}
- }
-
- const auto observers = ToTArray<nsTArray<RefPtr<DOMIntersectionObserver>>>(
- mIntersectionObservers);
- for (const auto& observer : observers) {
- if (observer) {
+ for (DOMIntersectionObserver* observer : mIntersectionObservers) {
observer->Update(*this, time);
}
+ Dispatch(NewRunnableMethod("Document::NotifyIntersectionObservers", this,
+ &Document::NotifyIntersectionObservers));
}
-}
-
-void Document::ScheduleIntersectionObserverNotification() {
- if (mIntersectionObservers.IsEmpty()) {
- return;
- }
- MOZ_RELEASE_ASSERT(NS_IsMainThread());
- nsCOMPtr<nsIRunnable> notification =
- NewRunnableMethod("Document::NotifyIntersectionObservers", this,
- &Document::NotifyIntersectionObservers);
- Dispatch(notification.forget());
+ EnumerateSubDocuments([aNowTime](Document& aDoc) {
+ aDoc.UpdateIntersections(aNowTime);
+ return CallState::Continue;
+ });
}
void Document::NotifyIntersectionObservers() {
const auto observers = ToTArray<nsTArray<RefPtr<DOMIntersectionObserver>>>(
mIntersectionObservers);
for (const auto& observer : observers) {
- if (observer) {
- // MOZ_KnownLive because the 'observers' array guarantees to keep it
- // alive.
- MOZ_KnownLive(observer)->Notify();
- }
+ // MOZ_KnownLive because the 'observers' array guarantees to keep it
+ // alive.
+ MOZ_KnownLive(observer)->Notify();
}
}
@@ -18647,6 +18633,13 @@ nsICookieJarSettings* Document::CookieJarSettings() {
if (!mCookieJarSettings) {
Document* inProcessParent = GetInProcessParentDocument();
+ auto shouldInheritFrom = [this](Document* aDoc) {
+ return aDoc && (this->NodePrincipal()->Equals(aDoc->NodePrincipal()) ||
+ this->NodePrincipal()->GetIsNullPrincipal());
+ };
+ RefPtr<BrowsingContext> opener =
+ GetBrowsingContext() ? GetBrowsingContext()->GetOpener() : nullptr;
+
if (inProcessParent) {
mCookieJarSettings = net::CookieJarSettings::Create(
inProcessParent->CookieJarSettings()->GetCookieBehavior(),
@@ -18674,6 +18667,18 @@ nsICookieJarSettings* Document::CookieJarSettings() {
->SetTopLevelWindowContextId(
net::CookieJarSettings::Cast(inProcessParent->CookieJarSettings())
->GetTopLevelWindowContextId());
+ } else if (opener && shouldInheritFrom(opener->GetDocument())) {
+ mCookieJarSettings = net::CookieJarSettings::Create(NodePrincipal());
+
+ nsTArray<uint8_t> randomKey;
+ nsresult rv = opener->GetDocument()
+ ->CookieJarSettings()
+ ->GetFingerprintingRandomizationKey(randomKey);
+
+ if (NS_SUCCEEDED(rv)) {
+ net::CookieJarSettings::Cast(mCookieJarSettings)
+ ->SetFingerprintingRandomizationKey(randomKey);
+ }
} else {
mCookieJarSettings = net::CookieJarSettings::Create(NodePrincipal());
@@ -18933,8 +18938,7 @@ void Document::AddPendingFrameStaticClone(nsFrameLoaderOwner* aElement,
}
bool Document::ShouldAvoidNativeTheme() const {
- return StaticPrefs::widget_non_native_theme_enabled() &&
- (!IsInChromeDocShell() || XRE_IsContentProcess());
+ return !IsInChromeDocShell() || XRE_IsContentProcess();
}
bool Document::UseRegularPrincipal() const {
diff --git a/dom/base/Document.h b/dom/base/Document.h
index 0b0d0ca3d0..e919f19be0 100644
--- a/dom/base/Document.h
+++ b/dom/base/Document.h
@@ -3709,8 +3709,9 @@ class Document : public nsINode,
return !mIntersectionObservers.IsEmpty();
}
- void UpdateIntersectionObservations(TimeStamp aNowTime);
- void ScheduleIntersectionObserverNotification();
+ // Update intersection observers in this document and all
+ // same-process subdocuments.
+ void UpdateIntersections(TimeStamp aNowTime);
MOZ_CAN_RUN_SCRIPT void NotifyIntersectionObservers();
DOMIntersectionObserver* GetLazyLoadObserver() { return mLazyLoadObserver; }
diff --git a/dom/base/Element.cpp b/dom/base/Element.cpp
index b6f5d5c3be..758134990b 100644
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -1088,7 +1088,7 @@ already_AddRefed<DOMRectList> Element::GetClientRects() {
nsLayoutUtils::RectListBuilder builder(rectList);
nsLayoutUtils::GetAllInFlowRects(
frame, nsLayoutUtils::GetContainingBlockForClientRect(frame), &builder,
- nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS);
+ nsLayoutUtils::GetAllInFlowRectsFlag::AccountForTransforms);
return rectList.forget();
}
@@ -1353,7 +1353,7 @@ already_AddRefed<ShadowRoot> Element::AttachShadowWithoutNameChecks(
for (const AbstractRange* range : *ranges) {
if (range->MayCrossShadowBoundary()) {
MOZ_ASSERT(range->IsDynamicRange());
- StaticRange* crossBoundaryRange =
+ CrossShadowBoundaryRange* crossBoundaryRange =
range->AsDynamicRange()->GetCrossShadowBoundaryRange();
MOZ_ASSERT(crossBoundaryRange);
// We may have previously selected this node before it
diff --git a/dom/base/EventSource.cpp b/dom/base/EventSource.cpp
index def3c90ec0..9e73f31fc6 100644
--- a/dom/base/EventSource.cpp
+++ b/dom/base/EventSource.cpp
@@ -1827,14 +1827,14 @@ nsresult EventSourceImpl::ParseCharacter(char16_t aChr) {
namespace {
-class WorkerRunnableDispatcher final : public WorkerRunnable {
+class WorkerRunnableDispatcher final : public WorkerThreadRunnable {
RefPtr<EventSourceImpl> mEventSourceImpl;
public:
WorkerRunnableDispatcher(RefPtr<EventSourceImpl>&& aImpl,
WorkerPrivate* aWorkerPrivate,
already_AddRefed<nsIRunnable> aEvent)
- : WorkerRunnable(aWorkerPrivate, "WorkerRunnableDispatcher"),
+ : WorkerThreadRunnable("WorkerRunnableDispatcher"),
mEventSourceImpl(std::move(aImpl)),
mEvent(std::move(aEvent)) {}
@@ -1928,7 +1928,7 @@ EventSourceImpl::Dispatch(already_AddRefed<nsIRunnable> aEvent,
RefPtr<WorkerRunnableDispatcher> event = new WorkerRunnableDispatcher(
this, mWorkerRef->Private(), event_ref.forget());
- if (!event->Dispatch()) {
+ if (!event->Dispatch(mWorkerRef->Private())) {
return NS_ERROR_FAILURE;
}
return NS_OK;
diff --git a/dom/base/FocusModel.h b/dom/base/FocusModel.h
new file mode 100644
index 0000000000..588c0503a2
--- /dev/null
+++ b/dom/base/FocusModel.h
@@ -0,0 +1,41 @@
+/* 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/. */
+
+#ifndef mozilla_FocusModel_h
+#define mozilla_FocusModel_h
+
+#include "mozilla/StaticPrefs_accessibility.h"
+#include "mozilla/TypedEnumBits.h"
+
+namespace mozilla {
+
+enum class IsFocusableFlags : uint8_t {
+ WithMouse = 1 << 0,
+ // Whether to treat an invisible frame as not focusable
+ IgnoreVisibility = 1 << 1,
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(IsFocusableFlags);
+
+enum class TabFocusableType : uint8_t {
+ TextControls = 1, // Textboxes and lists always tabbable
+ FormElements = 1 << 1, // Non-text form elements
+ Links = 1 << 2, // Links
+ Any = TextControls | FormElements | Links,
+};
+
+class FocusModel final {
+ public:
+ static bool AppliesToXUL() {
+ return StaticPrefs::accessibility_tabfocus_applies_to_xul();
+ }
+
+ static bool IsTabFocusable(TabFocusableType aType) {
+ return StaticPrefs::accessibility_tabfocus() & int32_t(aType);
+ }
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/base/FragmentOrElement.cpp b/dom/base/FragmentOrElement.cpp
index 87fd81bfa3..8338fc6b9e 100644
--- a/dom/base/FragmentOrElement.cpp
+++ b/dom/base/FragmentOrElement.cpp
@@ -105,8 +105,6 @@
using namespace mozilla;
using namespace mozilla::dom;
-int32_t nsIContent::sTabFocusModel = eTabFocus_any;
-bool nsIContent::sTabFocusModelAppliesToXUL = false;
uint64_t nsMutationGuard::sGeneration = 0;
NS_IMPL_CYCLE_COLLECTION_CLASS(nsIContent)
@@ -1022,7 +1020,7 @@ void nsIContent::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
}
}
-Element* nsIContent::GetAutofocusDelegate(bool aWithMouse) const {
+Element* nsIContent::GetAutofocusDelegate(IsFocusableFlags aFlags) const {
for (nsINode* node = GetFirstChild(); node; node = node->GetNextNode(this)) {
auto* descendant = Element::FromNode(*node);
if (!descendant || !descendant->GetBoolAttr(nsGkAtoms::autofocus)) {
@@ -1030,14 +1028,14 @@ Element* nsIContent::GetAutofocusDelegate(bool aWithMouse) const {
}
nsIFrame* frame = descendant->GetPrimaryFrame();
- if (frame && frame->IsFocusable(aWithMouse)) {
+ if (frame && frame->IsFocusable(aFlags)) {
return descendant;
}
}
return nullptr;
}
-Element* nsIContent::GetFocusDelegate(bool aWithMouse) const {
+Element* nsIContent::GetFocusDelegate(IsFocusableFlags aFlags) const {
const nsIContent* whereToLook = this;
if (ShadowRoot* root = GetShadowRoot()) {
if (!root->DelegatesFocus()) {
@@ -1055,7 +1053,7 @@ Element* nsIContent::GetFocusDelegate(bool aWithMouse) const {
return {};
}
- return frame->IsFocusable(aWithMouse);
+ return frame->IsFocusable(aFlags);
};
Element* potentialFocus = nullptr;
@@ -1097,7 +1095,7 @@ Element* nsIContent::GetFocusDelegate(bool aWithMouse) const {
if (auto* shadow = el->GetShadowRoot()) {
if (shadow->DelegatesFocus()) {
- if (Element* delegatedFocus = shadow->GetFocusDelegate(aWithMouse)) {
+ if (Element* delegatedFocus = shadow->GetFocusDelegate(aFlags)) {
if (autofocus) {
// This element has autofocus and we found an focus delegates
// in its descendants, so use the focus delegates
@@ -1114,7 +1112,7 @@ Element* nsIContent::GetFocusDelegate(bool aWithMouse) const {
return potentialFocus;
}
-Focusable nsIContent::IsFocusableWithoutStyle(bool aWithMouse) {
+Focusable nsIContent::IsFocusableWithoutStyle(IsFocusableFlags) {
// Default, not tabbable
return {};
}
diff --git a/dom/base/Highlight.cpp b/dom/base/Highlight.cpp
index cd0efccce5..0bac8193d7 100644
--- a/dom/base/Highlight.cpp
+++ b/dom/base/Highlight.cpp
@@ -101,24 +101,36 @@ already_AddRefed<Selection> Highlight::CreateHighlightSelection(
}
void Highlight::Add(AbstractRange& aRange, ErrorResult& aRv) {
+ // Manually check if the range `aKey` is already present in this highlight,
+ // because `SetlikeHelpers::Add()` doesn't indicate this.
+ // To keep the setlike and the mirrored array in sync, the range must not
+ // be added to `mRanges` if it was already present.
+ // `SetlikeHelpers::Has()` is much faster in checking this than
+ // `nsTArray<>::Contains()`.
+ if (Highlight_Binding::SetlikeHelpers::Has(this, aRange, aRv) ||
+ aRv.Failed()) {
+ return;
+ }
Highlight_Binding::SetlikeHelpers::Add(this, aRange, aRv);
if (aRv.Failed()) {
return;
}
- if (!mRanges.Contains(&aRange)) {
- mRanges.AppendElement(&aRange);
- AutoFrameSelectionBatcher selectionBatcher(__FUNCTION__,
- mHighlightRegistries.Count());
- for (const RefPtr<HighlightRegistry>& registry :
- mHighlightRegistries.Keys()) {
- auto frameSelection = registry->GetFrameSelection();
- selectionBatcher.AddFrameSelection(frameSelection);
- // since this is run in a context guarded by a selection batcher,
- // no strong reference is needed to keep `registry` alive.
- MOZ_KnownLive(registry)->MaybeAddRangeToHighlightSelection(aRange, *this);
- if (aRv.Failed()) {
- return;
- }
+
+ MOZ_ASSERT(!mRanges.Contains(&aRange),
+ "setlike and DOM mirror are not in sync");
+
+ mRanges.AppendElement(&aRange);
+ AutoFrameSelectionBatcher selectionBatcher(__FUNCTION__,
+ mHighlightRegistries.Count());
+ for (const RefPtr<HighlightRegistry>& registry :
+ mHighlightRegistries.Keys()) {
+ auto frameSelection = registry->GetFrameSelection();
+ selectionBatcher.AddFrameSelection(frameSelection);
+ // since this is run in a context guarded by a selection batcher,
+ // no strong reference is needed to keep `registry` alive.
+ MOZ_KnownLive(registry)->MaybeAddRangeToHighlightSelection(aRange, *this);
+ if (aRv.Failed()) {
+ return;
}
}
}
diff --git a/dom/base/HighlightRegistry.cpp b/dom/base/HighlightRegistry.cpp
index 035aadb78a..755977286f 100644
--- a/dom/base/HighlightRegistry.cpp
+++ b/dom/base/HighlightRegistry.cpp
@@ -133,18 +133,28 @@ void HighlightRegistry::AddHighlightSelectionsToFrameSelection() {
void HighlightRegistry::Set(const nsAString& aKey, Highlight& aValue,
ErrorResult& aRv) {
+ // manually check if the highlight `aKey` is already registered to be able to
+ // provide a fast path later that avoids calling `std::find_if()`.
+ const bool highlightAlreadyPresent =
+ HighlightRegistry_Binding::MaplikeHelpers::Has(this, aKey, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
HighlightRegistry_Binding::MaplikeHelpers::Set(this, aKey, aValue, aRv);
if (aRv.Failed()) {
return;
}
RefPtr<nsFrameSelection> frameSelection = GetFrameSelection();
RefPtr<nsAtom> highlightNameAtom = NS_AtomizeMainThread(aKey);
- auto foundIter =
- std::find_if(mHighlightsOrdered.begin(), mHighlightsOrdered.end(),
- [&highlightNameAtom](auto const& aElm) {
- return aElm.first() == highlightNameAtom;
- });
- if (foundIter != mHighlightsOrdered.end()) {
+ if (highlightAlreadyPresent) {
+ // If the highlight named `aKey` was present before, replace its value.
+ auto foundIter =
+ std::find_if(mHighlightsOrdered.begin(), mHighlightsOrdered.end(),
+ [&highlightNameAtom](auto const& aElm) {
+ return aElm.first() == highlightNameAtom;
+ });
+ MOZ_ASSERT(foundIter != mHighlightsOrdered.end(),
+ "webIDL maplike and DOM mirror are not in sync");
foundIter->second()->RemoveFromHighlightRegistry(*this, *highlightNameAtom);
if (frameSelection) {
frameSelection->RemoveHighlightSelection(highlightNameAtom);
diff --git a/dom/base/MimeType.cpp b/dom/base/MimeType.cpp
index da61489c1f..1e6d6a7cd4 100644
--- a/dom/base/MimeType.cpp
+++ b/dom/base/MimeType.cpp
@@ -328,13 +328,13 @@ template <typename char_type>
template <typename char_type>
void TMimeType<char_type>::Serialize(nsTSubstring<char_type>& aOutput) const {
aOutput.Assign(mType);
- aOutput.AppendLiteral("/");
+ aOutput.Append('/');
aOutput.Append(mSubtype);
for (uint32_t i = 0; i < mParameterNames.Length(); i++) {
auto name = mParameterNames[i];
- aOutput.AppendLiteral(";");
+ aOutput.Append(';');
aOutput.Append(name);
- aOutput.AppendLiteral("=");
+ aOutput.Append('=');
GetParameterValue(name, aOutput, true);
}
}
@@ -342,7 +342,7 @@ void TMimeType<char_type>::Serialize(nsTSubstring<char_type>& aOutput) const {
template <typename char_type>
void TMimeType<char_type>::GetEssence(nsTSubstring<char_type>& aOutput) const {
aOutput.Assign(mType);
- aOutput.AppendLiteral("/");
+ aOutput.Append('/');
aOutput.Append(mSubtype);
}
@@ -366,17 +366,17 @@ bool TMimeType<char_type>::GetParameterValue(
}
if (aWithQuotes && (value.mRequiresQuoting || value.IsEmpty())) {
- aOutput.AppendLiteral("\"");
+ aOutput.Append('"');
const char_type* vcur = value.BeginReading();
const char_type* vend = value.EndReading();
while (vcur < vend) {
if (*vcur == '"' || *vcur == '\\') {
- aOutput.AppendLiteral("\\");
+ aOutput.Append('\\');
}
aOutput.Append(*vcur);
vcur++;
}
- aOutput.AppendLiteral("\"");
+ aOutput.Append('"');
} else {
aOutput.Append(value);
}
diff --git a/dom/base/RadioGroupContainer.cpp b/dom/base/RadioGroupContainer.cpp
index 3bb4d7da20..988c8186f9 100644
--- a/dom/base/RadioGroupContainer.cpp
+++ b/dom/base/RadioGroupContainer.cpp
@@ -8,6 +8,7 @@
#include "mozilla/dom/RadioGroupContainer.h"
#include "mozilla/dom/TreeOrderedArrayInlines.h"
#include "mozilla/Assertions.h"
+#include "nsIFrame.h"
#include "nsIRadioVisitor.h"
#include "nsRadioVisitor.h"
@@ -125,7 +126,9 @@ nsresult RadioGroupContainer::GetNextRadioButton(
index = 0;
}
radio = radioGroup->mRadioButtons->ElementAt(index);
- } while (radio->Disabled() && radio != currentRadio);
+ } while ((radio->Disabled() || !radio->GetPrimaryFrame() ||
+ !radio->GetPrimaryFrame()->IsVisibleConsideringAncestors()) &&
+ radio != currentRadio);
radio.forget(aRadioOut);
return NS_OK;
@@ -135,7 +138,8 @@ HTMLInputElement* RadioGroupContainer::GetFirstRadioButton(
const nsAString& aName) {
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
for (HTMLInputElement* radio : radioGroup->mRadioButtons.AsList()) {
- if (!radio->Disabled()) {
+ if (!radio->Disabled() && radio->GetPrimaryFrame() &&
+ radio->GetPrimaryFrame()->IsVisibleConsideringAncestors()) {
return radio;
}
}
diff --git a/dom/base/ResizeObserver.h b/dom/base/ResizeObserver.h
index eaa0e1726d..2fd630c66c 100644
--- a/dom/base/ResizeObserver.h
+++ b/dom/base/ResizeObserver.h
@@ -121,7 +121,6 @@ class ResizeObservation final : public LinkedListElement<ResizeObservation> {
* https://drafts.csswg.org/resize-observer/#api
*/
class ResizeObserver final : public nsISupports, public nsWrapperCache {
-
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(ResizeObserver)
diff --git a/dom/base/Selection.h b/dom/base/Selection.h
index 08563993ac..101bada49f 100644
--- a/dom/base/Selection.h
+++ b/dom/base/Selection.h
@@ -386,6 +386,30 @@ class Selection final : public nsSupportsWeakReference,
return mStyledRanges.mRanges[0].mRange->Collapsed();
}
+ // Returns whether both normal range and cross-shadow-boundary
+ // range are collapsed.
+ //
+ // If StaticPrefs::dom_shadowdom_selection_across_boundary_enabled is
+ // disabled, this method always returns result as nsRange::IsCollapsed.
+ bool AreNormalAndCrossShadowBoundaryRangesCollapsed() const {
+ if (!IsCollapsed()) {
+ return false;
+ }
+
+ size_t cnt = mStyledRanges.Length();
+ if (cnt == 0) {
+ return true;
+ }
+
+ AbstractRange* range = mStyledRanges.mRanges[0].mRange;
+ MOZ_ASSERT_IF(
+ range->MayCrossShadowBoundary(),
+ !range->AsDynamicRange()->CrossShadowBoundaryRangeCollapsed());
+ // Returns false if nsRange::mCrossBoundaryRange exists,
+ // true otherwise.
+ return !range->MayCrossShadowBoundary();
+ }
+
// *JS() methods are mapped to Selection.*().
// They may move focus only when the range represents normal selection.
// These methods shouldn't be used by non-JS callers.
diff --git a/dom/base/StaticRange.h b/dom/base/StaticRange.h
index af7054f843..249c938b2f 100644
--- a/dom/base/StaticRange.h
+++ b/dom/base/StaticRange.h
@@ -19,7 +19,7 @@ class ErrorResult;
namespace dom {
-class StaticRange final : public AbstractRange {
+class StaticRange : public AbstractRange {
public:
StaticRange() = delete;
explicit StaticRange(const StaticRange& aOther) = delete;
@@ -71,16 +71,6 @@ class StaticRange final : public AbstractRange {
*/
bool IsValid() const;
- void NotifyNodeBecomesShadowHost(nsINode* aNode) {
- if (aNode == mStart.Container()) {
- mStart.NotifyParentBecomesShadowHost();
- }
-
- if (aNode == mEnd.Container()) {
- mEnd.NotifyParentBecomesShadowHost();
- }
- }
-
private:
// Whether the start and end points are in the same tree.
// They could be in different trees, i.e, cross shadow boundaries.
diff --git a/dom/base/crashtests/1419902.html b/dom/base/crashtests/1419902.html
index b0742b5be0..a0aa1b0698 100644
--- a/dom/base/crashtests/1419902.html
+++ b/dom/base/crashtests/1419902.html
@@ -1,23 +1 @@
-<html>
- <head>
- <script>
- var winsToClose = []
- onbeforeunload = function() {
- for (let win of winsToClose) {
- if (win) {
- win.close();
- }
- }
- };
- for (let i = 0; i < 38; i++) {
- customElements.define("custom-element_0", class extends HTMLElement {
- constructor() {
- try { o1 = document.createElement("custom-element_0") } catch (e) {}
- try { winsToClose.push(window.open("javascript:'<html><body>dummy</body></html>';")); } catch (e) {}
- }
- })
- try { o3 = document.createElement("custom-element_0") } catch (e) {}
- }
- </script>
- </head>
-</html>
+<!-- file is now located at testing/crashtest/final/dom/base/crashtests/1419902.html -->
diff --git a/dom/base/crashtests/1741957.html b/dom/base/crashtests/1741957.html
new file mode 100644
index 0000000000..56b695052e
--- /dev/null
+++ b/dom/base/crashtests/1741957.html
@@ -0,0 +1,22 @@
+<html><head><style>
+:root {
+ column-width: 0px;
+}
+* {
+ height: 0;
+}
+</style>
+<script>
+function go() {
+ document.execCommand("selectAll", false)
+ a.appendChild(b)
+}
+</script>
+<link href="moz-extension://1abf2dce-edd2-4c86-b0c5-2eeeaa9386d5/css/content.css" rel="stylesheet" type="text/css"></head><body onload="go()">
+
+<button>
+<font dir="rtl">x</font>
+<animatetransform id="a">
+<textarea id="b">x</textarea></animatetransform></button>
+<!-- x -->
+</body><div></div></html>
diff --git a/dom/base/crashtests/crashtests.list b/dom/base/crashtests/crashtests.list
index 22aaf50e6b..7aa5e6a92a 100644
--- a/dom/base/crashtests/crashtests.list
+++ b/dom/base/crashtests/crashtests.list
@@ -220,7 +220,7 @@ load 1406109-1.html
load 1411473.html
load 1413815.html
load 1419799.html
-skip-if(geckoview) skip-if(geckoview&&isDebugBuild) skip-if(AddressSanitizer) skip-if(ThreadSanitizer) pref(dom.disable_open_during_load,false) load 1419902.html # skip Bug 1419902. Bug 1563013 for GV+WR. Bug 1524493 GV+debug. Bug 1573281 asan
+# load 1419902.html # this test is run at the very end in testing/crashtest/final/crashtests.list
load 1422883.html
load 1428053.html
load 1441029.html
@@ -265,6 +265,7 @@ load 1757923.html
load 1766472.html
pref(dom.enable_web_task_scheduling,true) load 1780790.html
load 1700237.html
+load 1741957.html
load 1811939.html
load 1822717.html
load 1835886.html
diff --git a/dom/base/fragmentdirectives/lib.rs b/dom/base/fragmentdirectives/lib.rs
index 0003849eb7..5f9d5ebdd8 100644
--- a/dom/base/fragmentdirectives/lib.rs
+++ b/dom/base/fragmentdirectives/lib.rs
@@ -96,9 +96,7 @@ pub extern "C" fn parse_fragment_directive(
&url_as_rust_string,
)
{
- result
- .url_without_fragment_directive
- .assign(&stripped_url);
+ result.url_without_fragment_directive.assign(&stripped_url);
result.fragment_directive.assign(&fragment_directive);
result.text_directives.extend(
text_directives
diff --git a/dom/base/fragmentdirectives/test.rs b/dom/base/fragmentdirectives/test.rs
index d4509cb033..e5479d8022 100644
--- a/dom/base/fragmentdirectives/test.rs
+++ b/dom/base/fragmentdirectives/test.rs
@@ -145,13 +145,16 @@ mod test {
),
(
"http://example.com/page.html?query=irrelevant:~:#bar:~:text=foo",
- "http://example.com/page.html?query=irrelevant:~:#bar"
- )
+ "http://example.com/page.html?query=irrelevant:~:#bar",
+ ),
] {
let (stripped_url, fragment_directive, _) =
parse_fragment_directive_and_remove_it_from_hash(&url)
.expect("The parser must find a result");
- assert_eq!(stripped_url, stripped_url_ref, "The stripped url is not correct.");
+ assert_eq!(
+ stripped_url, stripped_url_ref,
+ "The stripped url is not correct."
+ );
assert_eq!(fragment_directive, "text=foo");
}
}
@@ -195,9 +198,8 @@ mod test {
#[test]
fn test_parse_multiple_text_fragments() {
let url = "#:~:text=prefix-,start,-suffix&text=foo&text=bar,-suffix";
- let (_, _, text_directives) =
- parse_fragment_directive_and_remove_it_from_hash(&url)
- .expect("The parser must find a result.");
+ let (_, _, text_directives) = parse_fragment_directive_and_remove_it_from_hash(&url)
+ .expect("The parser must find a result.");
assert_eq!(
text_directives.len(),
3,
@@ -339,8 +341,7 @@ mod test {
"text=prefix-,start",
"#:~:text=foo-,bar,-baz:~:text=foo",
] {
- let text_directives =
- parse_fragment_directive_and_remove_it_from_hash(&url);
+ let text_directives = parse_fragment_directive_and_remove_it_from_hash(&url);
assert!(
text_directives.is_none(),
"The fragment `{}` does not contain a valid or known fragment directive.",
@@ -369,8 +370,7 @@ mod test {
"#:~:text=,prefix,start",
"#:~:text=",
] {
- let text_directives =
- parse_fragment_directive_and_remove_it_from_hash(&url);
+ let text_directives = parse_fragment_directive_and_remove_it_from_hash(&url);
assert!(
text_directives.is_none(),
"The fragment directive `{}` does not contain a valid text directive.",
diff --git a/dom/base/moz.build b/dom/base/moz.build
index ffcfb0aaf6..e2cf2dfb78 100644
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -126,6 +126,7 @@ EXPORTS.mozilla += [
"ContentIterator.h",
"CORSMode.h",
"FlushType.h",
+ "FocusModel.h",
"FullscreenChange.h",
"GlobalTeardownObserver.h",
"IdentifierMapEntry.h",
@@ -166,6 +167,7 @@ EXPORTS.mozilla.dom += [
"CompressionStream.h",
"ContentFrameMessageManager.h",
"ContentProcessMessageManager.h",
+ "CrossShadowBoundaryRange.h",
"CustomElementRegistry.h",
"DecompressionStream.h",
"DirectionalityUtils.h",
@@ -336,6 +338,7 @@ UNIFIED_SOURCES += [
"ContentFrameMessageManager.cpp",
"ContentIterator.cpp",
"ContentProcessMessageManager.cpp",
+ "CrossShadowBoundaryRange.cpp",
"Crypto.cpp",
"CustomElementRegistry.cpp",
"DirectionalityUtils.cpp",
diff --git a/dom/base/nsAttrValue.cpp b/dom/base/nsAttrValue.cpp
index 01efef2eb9..e4bf977f56 100644
--- a/dom/base/nsAttrValue.cpp
+++ b/dom/base/nsAttrValue.cpp
@@ -141,7 +141,7 @@ bool MiscContainer::GetString(nsAString& aString) const {
}
if (isString) {
auto* buffer = static_cast<nsStringBuffer*>(ptr);
- buffer->ToString(buffer->StorageSize() / sizeof(char16_t) - 1, aString);
+ aString.Assign(buffer, buffer->StorageSize() / sizeof(char16_t) - 1);
} else {
static_cast<nsAtom*>(ptr)->ToString(aString);
}
@@ -280,11 +280,9 @@ void nsAttrValue::Shutdown() {
void nsAttrValue::Reset() {
switch (BaseType()) {
case eStringBase: {
- nsStringBuffer* str = static_cast<nsStringBuffer*>(GetPtr());
- if (str) {
+ if (auto* str = static_cast<nsStringBuffer*>(GetPtr())) {
str->Release();
}
-
break;
}
case eOtherBase: {
@@ -320,8 +318,7 @@ void nsAttrValue::SetTo(const nsAttrValue& aOther) {
switch (aOther.BaseType()) {
case eStringBase: {
ResetIfSet();
- nsStringBuffer* str = static_cast<nsStringBuffer*>(aOther.GetPtr());
- if (str) {
+ if (auto* str = static_cast<nsStringBuffer*>(aOther.GetPtr())) {
str->AddRef();
SetPtrValueAndType(str, eStringBase);
}
@@ -623,18 +620,16 @@ void nsAttrValue::ToString(nsAString& aResult) const {
switch (Type()) {
case eString: {
- nsStringBuffer* str = static_cast<nsStringBuffer*>(GetPtr());
- if (str) {
- str->ToString(str->StorageSize() / sizeof(char16_t) - 1, aResult);
+ if (auto* str = static_cast<nsStringBuffer*>(GetPtr())) {
+ aResult.Assign(str, str->StorageSize() / sizeof(char16_t) - 1);
} else {
aResult.Truncate();
}
break;
}
case eAtom: {
- nsAtom* atom = static_cast<nsAtom*>(GetPtr());
+ auto* atom = static_cast<nsAtom*>(GetPtr());
atom->ToString(aResult);
-
break;
}
case eInteger: {
@@ -895,8 +890,7 @@ nsAtom* nsAttrValue::AtomAt(int32_t aIndex) const {
uint32_t nsAttrValue::HashValue() const {
switch (BaseType()) {
case eStringBase: {
- nsStringBuffer* str = static_cast<nsStringBuffer*>(GetPtr());
- if (str) {
+ if (auto* str = static_cast<nsStringBuffer*>(GetPtr())) {
uint32_t len = str->StorageSize() / sizeof(char16_t) - 1;
return HashString(static_cast<char16_t*>(str->Data()), len);
}
@@ -1208,8 +1202,7 @@ bool nsAttrValue::SubstringCheck(const nsAString& aValue,
nsCaseTreatment aCaseSensitive) const {
switch (BaseType()) {
case eStringBase: {
- auto str = static_cast<nsStringBuffer*>(GetPtr());
- if (str) {
+ if (auto* str = static_cast<nsStringBuffer*>(GetPtr())) {
return F::Check(static_cast<char16_t*>(str->Data()),
str->StorageSize() / sizeof(char16_t) - 1, aValue,
aCaseSensitive);
@@ -1217,7 +1210,7 @@ bool nsAttrValue::SubstringCheck(const nsAString& aValue,
return aValue.IsEmpty();
}
case eAtomBase: {
- auto atom = static_cast<nsAtom*>(GetPtr());
+ auto* atom = static_cast<nsAtom*>(GetPtr());
return F::Check(atom->GetUTF16String(), atom->GetLength(), aValue,
aCaseSensitive);
}
@@ -2107,12 +2100,11 @@ already_AddRefed<nsStringBuffer> nsAttrValue::GetStringBuffer(
if (!len) {
return nullptr;
}
-
- RefPtr<nsStringBuffer> buf = nsStringBuffer::FromString(aValue);
- if (buf && (buf->StorageSize() / sizeof(char16_t) - 1) == len) {
+ if (nsStringBuffer* buf = aValue.GetStringBuffer();
+ buf && (buf->StorageSize() / sizeof(char16_t) - 1) == len) {
// We can only reuse the buffer if it's exactly sized, since we rely on
// StorageSize() to get the string length in ToString().
- return buf.forget();
+ return do_AddRef(buf);
}
return nsStringBuffer::Create(aValue.Data(), aValue.Length());
}
diff --git a/dom/base/nsAttrValue.h b/dom/base/nsAttrValue.h
index 4ef63c287f..b32dfc98fb 100644
--- a/dom/base/nsAttrValue.h
+++ b/dom/base/nsAttrValue.h
@@ -108,7 +108,9 @@ const uintptr_t NS_ATTRVALUE_BASETYPE_MASK = 3;
class nsCheapString : public nsString {
public:
explicit nsCheapString(nsStringBuffer* aBuf) {
- if (aBuf) aBuf->ToString(aBuf->StorageSize() / sizeof(char16_t) - 1, *this);
+ if (aBuf) {
+ Assign(aBuf, aBuf->StorageSize() / sizeof(char16_t) - 1);
+ }
}
};
diff --git a/dom/base/nsContentAreaDragDrop.cpp b/dom/base/nsContentAreaDragDrop.cpp
index 3ca21e725a..b2ccc8cb1c 100644
--- a/dom/base/nsContentAreaDragDrop.cpp
+++ b/dom/base/nsContentAreaDragDrop.cpp
@@ -295,7 +295,7 @@ nsContentAreaDragDropDataProvider::GetFlavorData(nsITransferable* aTransferable,
bool isPrivate = aTransferable->GetIsPrivateData();
- nsCOMPtr<nsIPrincipal> principal = aTransferable->GetRequestingPrincipal();
+ nsCOMPtr<nsIPrincipal> principal = aTransferable->GetDataPrincipal();
nsContentPolicyType contentPolicyType =
aTransferable->GetContentPolicyType();
nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp
index d2c863bd65..e4f4fadf9c 100644
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -3011,6 +3011,18 @@ nsINode* nsContentUtils::GetCommonAncestorHelper(nsINode* aNode1,
}
/* static */
+nsINode* nsContentUtils::GetClosestCommonShadowIncludingInclusiveAncestor(
+ nsINode* aNode1, nsINode* aNode2) {
+ if (aNode1 == aNode2) {
+ return aNode1;
+ }
+
+ return GetCommonAncestorInternal(aNode1, aNode2, [](nsINode* aNode) {
+ return aNode->GetParentOrShadowHostNode();
+ });
+}
+
+/* static */
nsIContent* nsContentUtils::GetCommonFlattenedTreeAncestorHelper(
nsIContent* aContent1, nsIContent* aContent2) {
return GetCommonAncestorInternal(
@@ -6998,25 +7010,6 @@ bool nsContentUtils::PlatformToDOMLineBreaks(nsString& aString,
return true;
}
-void nsContentUtils::PopulateStringFromStringBuffer(nsStringBuffer* aBuf,
- nsAString& aResultString) {
- MOZ_ASSERT(aBuf, "Expecting a non-null string buffer");
-
- uint32_t stringLen = NS_strlen(static_cast<char16_t*>(aBuf->Data()));
-
- // SANITY CHECK: In case the nsStringBuffer isn't correctly
- // null-terminated, let's clamp its length using the allocated size, to be
- // sure the resulting string doesn't sample past the end of the the buffer.
- // (Note that StorageSize() is in units of bytes, so we have to convert that
- // to units of PRUnichars, and subtract 1 for the null-terminator.)
- uint32_t allocStringLen = (aBuf->StorageSize() / sizeof(char16_t)) - 1;
- MOZ_ASSERT(stringLen <= allocStringLen,
- "string buffer lacks null terminator!");
- stringLen = std::min(stringLen, allocStringLen);
-
- aBuf->ToString(stringLen, aResultString);
-}
-
already_AddRefed<nsContentList> nsContentUtils::GetElementsByClassName(
nsINode* aRootNode, const nsAString& aClasses) {
MOZ_ASSERT(aRootNode, "Must have root node");
@@ -7442,8 +7435,12 @@ int32_t nsContentUtils::GetAdjustedOffsetInTextControl(nsIFrame* aOffsetFrame,
// static
bool nsContentUtils::IsPointInSelection(
const mozilla::dom::Selection& aSelection, const nsINode& aNode,
- const uint32_t aOffset) {
- if (aSelection.IsCollapsed()) {
+ const uint32_t aOffset, const bool aAllowCrossShadowBoundary) {
+ const bool selectionIsCollapsed =
+ !aAllowCrossShadowBoundary
+ ? aSelection.IsCollapsed()
+ : aSelection.AreNormalAndCrossShadowBoundaryRangesCollapsed();
+ if (selectionIsCollapsed) {
return false;
}
@@ -7457,7 +7454,8 @@ bool nsContentUtils::IsPointInSelection(
}
// Done when we find a range that we are in
- if (range->IsPointInRange(aNode, aOffset, IgnoreErrors())) {
+ if (range->IsPointInRange(aNode, aOffset, IgnoreErrors(),
+ aAllowCrossShadowBoundary)) {
return true;
}
}
@@ -8211,7 +8209,7 @@ nsresult nsContentUtils::IPCTransferableToTransferable(
aTransferable->SetCookieJarSettings(cookieJarSettings);
}
aTransferable->SetReferrerInfo(aIPCTransferable.referrerInfo());
- aTransferable->SetRequestingPrincipal(aIPCTransferable.requestingPrincipal());
+ aTransferable->SetDataPrincipal(aIPCTransferable.dataPrincipal());
aTransferable->SetContentPolicyType(aIPCTransferable.contentPolicyType());
return NS_OK;
@@ -8302,7 +8300,7 @@ nsresult nsContentUtils::CalculateBufferSizeForImage(
}
static already_AddRefed<DataSourceSurface> BigBufferToDataSurface(
- BigBuffer& aData, uint32_t aStride, const IntSize& aImageSize,
+ const BigBuffer& aData, uint32_t aStride, const IntSize& aImageSize,
SurfaceFormat aFormat) {
if (!aData.Size() || !aImageSize.width || !aImageSize.height) {
return nullptr;
@@ -8325,22 +8323,13 @@ static already_AddRefed<DataSourceSurface> BigBufferToDataSurface(
nsresult nsContentUtils::DeserializeTransferableDataImageContainer(
const IPCTransferableDataImageContainer& aData,
imgIContainer** aContainer) {
- const IntSize size(aData.width(), aData.height());
- size_t maxBufferSize = 0;
- size_t usedBufferSize = 0;
- nsresult rv = CalculateBufferSizeForImage(
- aData.stride(), size, aData.format(), &maxBufferSize, &usedBufferSize);
- NS_ENSURE_SUCCESS(rv, rv);
- if (usedBufferSize > aData.data().Size()) {
- return NS_ERROR_FAILURE;
- }
- RefPtr<DataSourceSurface> surface =
- CreateDataSourceSurfaceFromData(size, aData.format(), aData.data().Data(),
- static_cast<int32_t>(aData.stride()));
+ RefPtr<DataSourceSurface> surface = IPCImageToSurface(aData.image());
if (!surface) {
return NS_ERROR_FAILURE;
}
- RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(surface, size);
+
+ RefPtr<gfxDrawable> drawable =
+ new gfxSurfaceDrawable(surface, surface->GetSize());
nsCOMPtr<imgIContainer> imageContainer =
image::ImageOps::CreateFromDrawable(drawable);
imageContainer.forget(aContainer);
@@ -8472,23 +8461,16 @@ void nsContentUtils::TransferableToIPCTransferableData(
if (!dataSurface) {
continue;
}
- size_t length;
- int32_t stride;
- Maybe<BigBuffer> surfaceData =
- GetSurfaceData(*dataSurface, &length, &stride);
- if (surfaceData.isNothing()) {
+ auto imageData = nsContentUtils::SurfaceToIPCImage(*dataSurface);
+ if (!imageData) {
continue;
}
IPCTransferableDataItem* item =
aTransferableData->items().AppendElement();
item->flavor() = flavorStr;
-
- mozilla::gfx::IntSize size = dataSurface->GetSize();
- item->data() = IPCTransferableDataImageContainer(
- std::move(*surfaceData), size.width, size.height, stride,
- dataSurface->GetFormat());
+ item->data() = IPCTransferableDataImageContainer(std::move(*imageData));
continue;
}
@@ -8569,8 +8551,7 @@ void nsContentUtils::TransferableToIPCTransferable(
aIPCTransferable->data() = std::move(ipcTransferableData);
aIPCTransferable->isPrivateData() = aTransferable->GetIsPrivateData();
- aIPCTransferable->requestingPrincipal() =
- aTransferable->GetRequestingPrincipal();
+ aIPCTransferable->dataPrincipal() = aTransferable->GetDataPrincipal();
aIPCTransferable->cookieJarSettings() = std::move(cookieJarSettingsArgs);
aIPCTransferable->contentPolicyType() = aTransferable->GetContentPolicyType();
aIPCTransferable->referrerInfo() = aTransferable->GetReferrerInfo();
@@ -8617,7 +8598,7 @@ Maybe<IPCImage> nsContentUtils::SurfaceToIPCImage(DataSourceSurface& aSurface) {
}
already_AddRefed<DataSourceSurface> nsContentUtils::IPCImageToSurface(
- IPCImage&& aImage) {
+ const IPCImage& aImage) {
return BigBufferToDataSurface(aImage.data(), aImage.stride(),
aImage.size().ToUnknownSize(), aImage.format());
}
diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h
index 4291d2c5d1..a1f466fa8d 100644
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -115,7 +115,6 @@ class nsNodeInfoManager;
class nsParser;
class nsPIWindowRoot;
class nsPresContext;
-class nsStringBuffer;
class nsTextFragment;
class nsView;
class nsWrapperCache;
@@ -501,6 +500,9 @@ class nsContentUtils {
return GetCommonAncestorHelper(aNode1, aNode2);
}
+ static nsINode* GetClosestCommonShadowIncludingInclusiveAncestor(
+ nsINode* aNode1, nsINode* aNode2);
+
/**
* Returns the common flattened tree ancestor, if any, for two given content
* nodes.
@@ -2443,14 +2445,6 @@ class nsContentUtils {
[[nodiscard]] static bool PlatformToDOMLineBreaks(nsString& aString,
const mozilla::fallible_t&);
- /**
- * Populates aResultString with the contents of the string-buffer aBuf, up
- * to aBuf's null-terminator. aBuf must not be null. Ownership of the string
- * is not transferred.
- */
- static void PopulateStringFromStringBuffer(nsStringBuffer* aBuf,
- nsAString& aResultString);
-
static bool IsHandlingKeyBoardEvent() { return sIsHandlingKeyBoardEvent; }
static void SetIsHandlingKeyBoardEvent(bool aHandling) {
@@ -2778,9 +2772,13 @@ class nsContentUtils {
* check. aNode and aOffset can be computed with
* UIEvent::GetRangeParentContentAndOffset() if you want to
* check the click point.
+ * @param aAllowCrossShadowBoundary If true, this method allows the selection
+ * to have boundaries that cross shadow
+ * boundaries.
*/
static bool IsPointInSelection(const mozilla::dom::Selection& aSelection,
- const nsINode& aNode, const uint32_t aOffset);
+ const nsINode& aNode, const uint32_t aOffset,
+ const bool aAllowCrossShadowBoundary = false);
/**
* Takes a selection, and a text control element (<input> or <textarea>), and
@@ -3041,7 +3039,7 @@ class nsContentUtils {
static mozilla::Maybe<mozilla::dom::IPCImage> SurfaceToIPCImage(
mozilla::gfx::DataSourceSurface&);
static already_AddRefed<mozilla::gfx::DataSourceSurface> IPCImageToSurface(
- mozilla::dom::IPCImage&&);
+ const mozilla::dom::IPCImage&);
// Helpers shared by the implementations of nsContentUtils methods and
// nsIDOMWindowUtils methods.
@@ -3597,6 +3595,8 @@ class nsContentUtils {
aCallback);
static nsINode* GetCommonAncestorHelper(nsINode* aNode1, nsINode* aNode2);
+ static nsINode* GetCommonShadowIncludingAncestorHelper(nsINode* aNode1,
+ nsINode* aNode2);
static nsIContent* GetCommonFlattenedTreeAncestorHelper(
nsIContent* aContent1, nsIContent* aContent2);
diff --git a/dom/base/nsCopySupport.cpp b/dom/base/nsCopySupport.cpp
index 15c0cf4cf0..2e24d963c8 100644
--- a/dom/base/nsCopySupport.cpp
+++ b/dom/base/nsCopySupport.cpp
@@ -245,7 +245,7 @@ static nsresult CreateTransferable(
NS_ENSURE_TRUE(aTransferable, NS_ERROR_NULL_POINTER);
aTransferable->Init(aDocument.GetLoadContext());
- aTransferable->SetRequestingPrincipal(aDocument.NodePrincipal());
+ aTransferable->SetDataPrincipal(aDocument.NodePrincipal());
if (aEncodedDocumentWithContext.mUnicodeEncodingIsTextHTML) {
// Set up a format converter so that clipboard flavor queries work.
// This converter isn't really used for conversions.
@@ -332,7 +332,8 @@ static nsresult PutToClipboard(
rv = CreateTransferable(aEncodedDocumentWithContext, aDocument, transferable);
NS_ENSURE_SUCCESS(rv, rv);
- rv = clipboard->SetData(transferable, nullptr, aClipboardID);
+ rv = clipboard->SetData(transferable, nullptr, aClipboardID,
+ aDocument.GetWindowContext());
NS_ENSURE_SUCCESS(rv, rv);
return rv;
@@ -455,9 +456,9 @@ nsresult nsCopySupport::GetContents(const nsACString& aMimeType,
return docEncoder->EncodeToString(outdata);
}
-nsresult nsCopySupport::ImageCopy(nsIImageLoadingContent* aImageElement,
- nsILoadContext* aLoadContext,
- int32_t aCopyFlags) {
+nsresult nsCopySupport::ImageCopy(
+ nsIImageLoadingContent* aImageElement, nsILoadContext* aLoadContext,
+ int32_t aCopyFlags, mozilla::dom::WindowContext* aSettingWindowContext) {
nsresult rv;
nsCOMPtr<nsINode> imageNode = do_QueryInterface(aImageElement, &rv);
@@ -467,7 +468,7 @@ nsresult nsCopySupport::ImageCopy(nsIImageLoadingContent* aImageElement,
nsCOMPtr<nsITransferable> trans(do_CreateInstance(kCTransferableCID, &rv));
NS_ENSURE_SUCCESS(rv, rv);
trans->Init(aLoadContext);
- trans->SetRequestingPrincipal(imageNode->NodePrincipal());
+ trans->SetDataPrincipal(imageNode->NodePrincipal());
if (aCopyFlags & nsIDocumentViewerEdit::COPY_IMAGE_TEXT) {
// get the location from the element
@@ -527,11 +528,13 @@ nsresult nsCopySupport::ImageCopy(nsIImageLoadingContent* aImageElement,
// check whether the system supports the selection clipboard or not.
if (clipboard->IsClipboardTypeSupported(nsIClipboard::kSelectionClipboard)) {
// put the transferable on the clipboard
- rv = clipboard->SetData(trans, nullptr, nsIClipboard::kSelectionClipboard);
+ rv = clipboard->SetData(trans, nullptr, nsIClipboard::kSelectionClipboard,
+ aSettingWindowContext);
NS_ENSURE_SUCCESS(rv, rv);
}
- return clipboard->SetData(trans, nullptr, nsIClipboard::kGlobalClipboard);
+ return clipboard->SetData(trans, nullptr, nsIClipboard::kGlobalClipboard,
+ aSettingWindowContext);
}
static nsresult AppendString(nsITransferable* aTransferable,
@@ -928,7 +931,12 @@ bool nsCopySupport::FireClipboardEvent(EventMessage aEventMessage,
NS_ENSURE_TRUE(transferable, false);
// put the transferable on the clipboard
- nsresult rv = clipboard->SetData(transferable, nullptr, aClipboardType);
+ WindowContext* settingWindowContext = nullptr;
+ if (aPresShell && aPresShell->GetDocument()) {
+ settingWindowContext = aPresShell->GetDocument()->GetWindowContext();
+ }
+ nsresult rv = clipboard->SetData(transferable, nullptr, aClipboardType,
+ settingWindowContext);
if (NS_FAILED(rv)) {
return false;
}
diff --git a/dom/base/nsCopySupport.h b/dom/base/nsCopySupport.h
index a6b4b5e783..5552e1a607 100644
--- a/dom/base/nsCopySupport.h
+++ b/dom/base/nsCopySupport.h
@@ -23,6 +23,7 @@ class PresShell;
namespace dom {
class Document;
class Selection;
+class WindowContext;
} // namespace dom
} // namespace mozilla
@@ -46,7 +47,8 @@ class nsCopySupport {
mozilla::dom::Document* aDoc, nsAString& outdata);
static nsresult ImageCopy(nsIImageLoadingContent* aImageElement,
- nsILoadContext* aLoadContext, int32_t aCopyFlags);
+ nsILoadContext* aLoadContext, int32_t aCopyFlags,
+ mozilla::dom::WindowContext* aSettingWindowContext);
// Get the selection as a transferable.
// @param aSelection Can be nullptr.
diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp
index 9bc8340b90..16bf19534a 100644
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -2039,7 +2039,7 @@ nsDOMWindowUtils::GetBoundsWithoutFlushing(Element* aElement,
if (frame) {
nsRect r = nsLayoutUtils::GetAllInFlowRectsUnion(
frame, nsLayoutUtils::GetContainingBlockForClientRect(frame),
- nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS);
+ nsLayoutUtils::GetAllInFlowRectsFlag::AccountForTransforms);
rect->SetLayoutRect(r);
}
@@ -3333,12 +3333,6 @@ nsDOMWindowUtils::IsPartOfOpaqueLayer(Element* aElement, bool* aResult) {
}
NS_IMETHODIMP
-nsDOMWindowUtils::NumberOfAssignedPaintedLayers(
- const nsTArray<RefPtr<Element>>& aElements, uint32_t* aResult) {
- return NS_ERROR_FAILURE;
-}
-
-NS_IMETHODIMP
nsDOMWindowUtils::EnableDialogs() {
nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
diff --git a/dom/base/nsFocusManager.cpp b/dom/base/nsFocusManager.cpp
index 4e2d604693..56827a457f 100644
--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -45,6 +45,7 @@
#include "mozilla/AccessibleCaretEventHub.h"
#include "mozilla/ContentEvents.h"
+#include "mozilla/FocusModel.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/DocumentInlines.h"
@@ -173,7 +174,6 @@ bool nsFocusManager::sTestMode = false;
uint64_t nsFocusManager::sFocusActionCounter = 0;
static const char* kObservedPrefs[] = {"accessibility.browsewithcaret",
- "accessibility.tabfocus_applies_to_xul",
"focusmanager.testmode", nullptr};
nsFocusManager::nsFocusManager()
@@ -198,10 +198,6 @@ nsFocusManager::~nsFocusManager() {
nsresult nsFocusManager::Init() {
sInstance = new nsFocusManager();
- nsIContent::sTabFocusModelAppliesToXUL =
- Preferences::GetBool("accessibility.tabfocus_applies_to_xul",
- nsIContent::sTabFocusModelAppliesToXUL);
-
sTestMode = Preferences::GetBool("focusmanager.testmode", false);
Preferences::RegisterCallbacks(nsFocusManager::PrefChanged, kObservedPrefs,
@@ -229,10 +225,6 @@ void nsFocusManager::PrefChanged(const char* aPref) {
nsDependentCString pref(aPref);
if (pref.EqualsLiteral("accessibility.browsewithcaret")) {
UpdateCaretForCaretBrowsingMode();
- } else if (pref.EqualsLiteral("accessibility.tabfocus_applies_to_xul")) {
- nsIContent::sTabFocusModelAppliesToXUL =
- Preferences::GetBool("accessibility.tabfocus_applies_to_xul",
- nsIContent::sTabFocusModelAppliesToXUL);
} else if (pref.EqualsLiteral("focusmanager.testmode")) {
sTestMode = Preferences::GetBool("focusmanager.testmode", false);
}
@@ -3362,9 +3354,6 @@ nsresult nsFocusManager::DetermineElementToMoveFocus(
doc = aWindow->GetExtantDoc();
if (!doc) return NS_OK;
- LookAndFeel::GetInt(LookAndFeel::IntID::TabFocusModel,
- &nsIContent::sTabFocusModel);
-
// True if we are navigating by document (F6/Shift+F6) or false if we are
// navigating by element (Tab/Shift+Tab).
const bool forDocumentNavigation =
@@ -3674,12 +3663,10 @@ nsresult nsFocusManager::DetermineElementToMoveFocus(
}
} else {
if (aNavigateByKey) {
- // There is no parent, so call the tree owner. This will tell the
- // embedder or parent process that it should take the focus.
- bool tookFocus;
- docShell->TabToTreeOwner(forward, forDocumentNavigation, &tookFocus);
- // If the tree owner took the focus, blur the current element.
- if (tookFocus) {
+ // There is no parent, so move the focus to the parent process.
+ if (auto* child = BrowserChild::GetFrom(docShell)) {
+ child->SendMoveFocus(forward, forDocumentNavigation);
+ // Blur the current element.
RefPtr<BrowsingContext> focusedBC = GetFocusedBrowsingContext();
if (focusedBC && focusedBC->IsInProcess()) {
Blur(focusedBC, nullptr, true, true, false,
@@ -4303,20 +4290,22 @@ nsresult nsFocusManager::GetNextTabbableContent(
// Stepping out popover scope.
// For forward, search for the next tabbable content after invoker.
- // For backward, we should get back to the invoker.
+ // For backward, we should get back to the invoker if the invoker is
+ // focusable. Otherwise search for the next tabbable content after
+ // invoker.
if (oldTopLevelScopeOwner &&
IsOpenPopoverWithInvoker(oldTopLevelScopeOwner) &&
currentTopLevelScopeOwner != oldTopLevelScopeOwner) {
if (auto* popover = Element::FromNode(oldTopLevelScopeOwner)) {
RefPtr<nsIContent> invokerContent =
popover->GetPopoverData()->GetInvoker()->AsContent();
+ RefPtr<nsIContent> rootElement = invokerContent;
+ if (auto* doc = invokerContent->GetComposedDoc()) {
+ rootElement = doc->GetRootElement();
+ }
if (aForward) {
nsIFrame* frame = invokerContent->GetPrimaryFrame();
int32_t tabIndex = frame->IsFocusable().mTabIndex;
- RefPtr<nsIContent> rootElement = invokerContent;
- if (auto* doc = invokerContent->GetComposedDoc()) {
- rootElement = doc->GetRootElement();
- }
if (tabIndex >= 0 &&
(aIgnoreTabIndex || aCurrentTabIndex == tabIndex)) {
nsresult rv = GetNextTabbableContent(
@@ -4327,12 +4316,19 @@ nsresult nsFocusManager::GetNextTabbableContent(
return rv;
}
}
- } else if (invokerContent &&
- invokerContent->IsFocusableWithoutStyle()) {
- // FIXME(emilio): The check above should probably use
- // nsIFrame::IsFocusable, not IsFocusableWithoutStyle.
- invokerContent.forget(aResultContent);
- return NS_OK;
+ } else if (invokerContent) {
+ nsIFrame* frame = invokerContent->GetPrimaryFrame();
+ if (frame && frame->IsFocusable()) {
+ invokerContent.forget(aResultContent);
+ return NS_OK;
+ }
+ nsresult rv = GetNextTabbableContent(
+ aPresShell, rootElement, aOriginalStartContent, invokerContent,
+ false, 0, true, false, aNavigateByKey, true,
+ aReachedToEndForDocumentNavigation, aResultContent);
+ if (NS_SUCCEEDED(rv) && *aResultContent) {
+ return rv;
+ }
}
}
}
@@ -5518,6 +5514,14 @@ bool nsFocusManager::CanSkipFocus(nsIContent* aContent) {
return false;
}
+static IsFocusableFlags FocusManagerFlagsToIsFocusableFlags(uint32_t aFlags) {
+ auto flags = IsFocusableFlags(0);
+ if (aFlags & nsIFocusManager::FLAG_BYMOUSE) {
+ flags |= IsFocusableFlags::WithMouse;
+ }
+ return flags;
+}
+
/* static */
Element* nsFocusManager::GetTheFocusableArea(Element* aTarget,
uint32_t aFlags) {
@@ -5547,7 +5551,8 @@ Element* nsFocusManager::GetTheFocusableArea(Element* aTarget,
// 3. If focus target is a navigable container with a non-null content
// navigable
// nsIFrame::IsFocusable will effectively perform the checks for them.
- if (frame->IsFocusable(aFlags & FLAG_BYMOUSE)) {
+ IsFocusableFlags flags = FocusManagerFlagsToIsFocusableFlags(aFlags);
+ if (frame->IsFocusable(flags)) {
return aTarget;
}
@@ -5567,8 +5572,7 @@ Element* nsFocusManager::GetTheFocusableArea(Element* aTarget,
}
}
- if (Element* firstFocusable =
- root->GetFocusDelegate(aFlags & FLAG_BYMOUSE)) {
+ if (Element* firstFocusable = root->GetFocusDelegate(flags)) {
return firstFocusable;
}
}
@@ -5586,7 +5590,7 @@ bool nsFocusManager::IsAreaElementFocusable(HTMLAreaElement& aArea) {
// GetPrimaryFrame() is not relevant as to whether it is focusable or
// not, so we have to do all the relevant checks manually for them.
return frame->IsVisibleConsideringAncestors() &&
- aArea.IsFocusableWithoutStyle(false /* aWithMouse */);
+ aArea.IsFocusableWithoutStyle();
}
nsresult NS_NewFocusManager(nsIFocusManager** aResult) {
diff --git a/dom/base/nsGlobalWindowInner.cpp b/dom/base/nsGlobalWindowInner.cpp
index 7dcd265ca4..f263c8efcc 100644
--- a/dom/base/nsGlobalWindowInner.cpp
+++ b/dom/base/nsGlobalWindowInner.cpp
@@ -48,6 +48,7 @@
#include "mozilla/Attributes.h"
#include "mozilla/BaseProfilerMarkersPrerequisites.h"
#include "mozilla/BasicEvents.h"
+#include "mozilla/BounceTrackingStorageObserver.h"
#include "mozilla/CallState.h"
#include "mozilla/CycleCollectedJSContext.h"
#include "mozilla/DOMEventTargetHelper.h"
@@ -3502,18 +3503,7 @@ double nsGlobalWindowInner::GetDevicePixelRatio(CallerType aCallerType,
if (nsIGlobalObject::ShouldResistFingerprinting(
aCallerType, RFPTarget::WindowDevicePixelRatio)) {
- // Spoofing the DevicePixelRatio causes blurriness in some situations
- // on HiDPI displays. pdf.js is a non-system caller; but it can't
- // expose the fingerprintable information, so we can safely disable
- // spoofing in this situation. It doesn't address the issue for
- // web-rendered content (including pdf.js instances on the web.)
- // In the future we hope to have a better solution to fix all HiDPI
- // blurriness...
- nsAutoCString origin;
- nsresult rv = this->GetPrincipal()->GetOrigin(origin);
- if (NS_FAILED(rv) || origin != "resource://pdf.js"_ns) {
- return 1.0;
- }
+ return 2.0;
}
if (aCallerType == CallerType::NonSystem) {
@@ -4648,6 +4638,19 @@ already_AddRefed<nsICSSDeclaration> nsGlobalWindowInner::GetComputedStyleHelper(
aError, nullptr);
}
+void nsGlobalWindowInner::MaybeNotifyStorageKeyUsed() {
+ // Only notify once per window lifetime.
+ if (hasNotifiedStorageKeyUsed) {
+ return;
+ }
+ nsresult rv =
+ BounceTrackingStorageObserver::OnInitialStorageAccess(GetWindowContext());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+ hasNotifiedStorageKeyUsed = true;
+}
+
Storage* nsGlobalWindowInner::GetSessionStorage(ErrorResult& aError) {
nsIPrincipal* principal = GetPrincipal();
nsIPrincipal* storagePrincipal;
@@ -4770,6 +4773,8 @@ Storage* nsGlobalWindowInner::GetSessionStorage(ErrorResult& aError) {
}
}
+ MaybeNotifyStorageKeyUsed();
+
MOZ_LOG(gDOMLeakPRLogInner, LogLevel::Debug,
("nsGlobalWindowInner %p returns %p sessionStorage", this,
mSessionStorage.get()));
@@ -4938,6 +4943,8 @@ Storage* nsGlobalWindowInner::GetLocalStorage(ErrorResult& aError) {
new PartitionedLocalStorage(this, principal, storagePrincipal, cache);
}
+ MaybeNotifyStorageKeyUsed();
+
MOZ_ASSERT(mLocalStorage);
MOZ_ASSERT(
mLocalStorage->Type() ==
@@ -4957,6 +4964,8 @@ IDBFactory* nsGlobalWindowInner::GetIndexedDB(JSContext* aCx,
}
}
+ MaybeNotifyStorageKeyUsed();
+
return mIndexedDB;
}
diff --git a/dom/base/nsGlobalWindowInner.h b/dom/base/nsGlobalWindowInner.h
index 215e362dad..33e1264571 100644
--- a/dom/base/nsGlobalWindowInner.h
+++ b/dom/base/nsGlobalWindowInner.h
@@ -728,6 +728,9 @@ class nsGlobalWindowInner final : public mozilla::dom::EventTarget,
mozilla::ErrorResult& aError);
void Btoa(const nsAString& aBinaryData, nsAString& aAsciiBase64String,
mozilla::ErrorResult& aError);
+
+ void MaybeNotifyStorageKeyUsed();
+
mozilla::dom::Storage* GetSessionStorage(mozilla::ErrorResult& aError);
mozilla::dom::Storage* GetLocalStorage(mozilla::ErrorResult& aError);
mozilla::dom::Selection* GetSelection(mozilla::ErrorResult& aError);
@@ -1389,6 +1392,11 @@ class nsGlobalWindowInner final : public mozilla::dom::EventTarget,
mozilla::Maybe<mozilla::StorageAccess> mStorageAllowedCache;
uint32_t mStorageAllowedReasonCache;
+ // When window associated storage is accessed we need to notify the parent
+ // process. This flag is used to ensure we only do it once per window
+ // lifetime.
+ bool hasNotifiedStorageKeyUsed{false};
+
RefPtr<mozilla::dom::DebuggerNotificationManager>
mDebuggerNotificationManager;
diff --git a/dom/base/nsIContent.h b/dom/base/nsIContent.h
index 700855370f..e6fc8fc430 100644
--- a/dom/base/nsIContent.h
+++ b/dom/base/nsIContent.h
@@ -16,6 +16,7 @@ class nsTextFragment;
class nsIFrame;
namespace mozilla {
+enum class IsFocusableFlags : uint8_t;
class EventChainPreVisitor;
class HTMLEditor;
struct URLExtraData;
@@ -274,13 +275,14 @@ class nsIContent : public nsINode {
* some widgets may be focusable but removed from the tab order.
* @return whether the content is focusable via mouse, kbd or script.
*/
- virtual Focusable IsFocusableWithoutStyle(bool aWithMouse = false);
+ virtual Focusable IsFocusableWithoutStyle(
+ mozilla::IsFocusableFlags = mozilla::IsFocusableFlags(0));
// https://html.spec.whatwg.org/multipage/interaction.html#focus-delegate
- mozilla::dom::Element* GetFocusDelegate(bool aWithMouse) const;
+ mozilla::dom::Element* GetFocusDelegate(mozilla::IsFocusableFlags) const;
// https://html.spec.whatwg.org/multipage/interaction.html#autofocus-delegate
- mozilla::dom::Element* GetAutofocusDelegate(bool aWithMouse) const;
+ mozilla::dom::Element* GetAutofocusDelegate(mozilla::IsFocusableFlags) const;
/*
* Get desired IME state for the content.
@@ -753,21 +755,6 @@ class nsIContent : public nsINode {
virtual void DumpContent(FILE* out = stdout, int32_t aIndent = 0,
bool aDumpAll = true) const = 0;
#endif
-
- enum ETabFocusType {
- eTabFocus_textControlsMask =
- (1 << 0), // textboxes and lists always tabbable
- eTabFocus_formElementsMask = (1 << 1), // non-text form elements
- eTabFocus_linksMask = (1 << 2), // links
- eTabFocus_any = 1 + (1 << 1) + (1 << 2) // everything that can be focused
- };
-
- // Tab focus model bit field:
- static int32_t sTabFocusModel;
-
- // accessibility.tabfocus_applies_to_xul pref - if it is set to true,
- // the tabfocus bit field applies to xul elements.
- static bool sTabFocusModelAppliesToXUL;
};
NON_VIRTUAL_ADDREF_RELEASE(nsIContent)
diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp
index a14a22bcf0..035faa1ee1 100644
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -2139,6 +2139,11 @@ void nsJSContext::EnsureStatics() {
(void*)JSGC_PARALLEL_MARKING_THRESHOLD_MB);
Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackInt,
+ "javascript.options.mem.gc_max_parallel_marking_threads",
+ (void*)JSGC_MAX_MARKING_THREADS);
+
+ Preferences::RegisterCallbackAndCall(
SetMemoryGCSliceTimePrefChangedCallback,
"javascript.options.mem.gc_incremental_slice_ms");
diff --git a/dom/base/nsRange.cpp b/dom/base/nsRange.cpp
index cf15f239c5..e879bb5806 100644
--- a/dom/base/nsRange.cpp
+++ b/dom/base/nsRange.cpp
@@ -792,8 +792,17 @@ void nsRange::ParentChainChanged(nsIContent* aContent) {
DoSetRange(mStart, mEnd, newRoot);
}
+bool nsRange::IsShadowIncludingInclusiveDescendantOfCrossBoundaryRangeAncestor(
+ const nsINode& aContainer) const {
+ MOZ_ASSERT(mCrossShadowBoundaryRange &&
+ mCrossShadowBoundaryRange->GetCommonAncestor());
+ return aContainer.IsShadowIncludingInclusiveDescendantOf(
+ mCrossShadowBoundaryRange->GetCommonAncestor());
+}
+
bool nsRange::IsPointComparableToRange(const nsINode& aContainer,
uint32_t aOffset,
+ bool aAllowCrossShadowBoundary,
ErrorResult& aRv) const {
// our range is in a good state?
if (!mIsPositioned) {
@@ -801,7 +810,13 @@ bool nsRange::IsPointComparableToRange(const nsINode& aContainer,
return false;
}
- if (!aContainer.IsInclusiveDescendantOf(mRoot)) {
+ const bool isContainerInRange =
+ aContainer.IsInclusiveDescendantOf(mRoot) ||
+ (aAllowCrossShadowBoundary && mCrossShadowBoundaryRange &&
+ IsShadowIncludingInclusiveDescendantOfCrossBoundaryRangeAncestor(
+ aContainer));
+
+ if (!isContainerInRange) {
// TODO(emilio): Switch to ThrowWrongDocumentError, but IsPointInRange
// relies on the error code right now in order to suppress the exception.
aRv.Throw(NS_ERROR_DOM_WRONG_DOCUMENT_ERR);
@@ -832,8 +847,10 @@ bool nsRange::IsPointComparableToRange(const nsINode& aContainer,
}
bool nsRange::IsPointInRange(const nsINode& aContainer, uint32_t aOffset,
- ErrorResult& aRv) const {
- uint16_t compareResult = ComparePoint(aContainer, aOffset, aRv);
+ ErrorResult& aRv,
+ bool aAllowCrossShadowBoundary) const {
+ int16_t compareResult =
+ ComparePoint(aContainer, aOffset, aRv, aAllowCrossShadowBoundary);
// If the node isn't in the range's document, it clearly isn't in the range.
if (aRv.ErrorCodeIs(NS_ERROR_DOM_WRONG_DOCUMENT_ERR)) {
aRv.SuppressException();
@@ -844,8 +861,10 @@ bool nsRange::IsPointInRange(const nsINode& aContainer, uint32_t aOffset,
}
int16_t nsRange::ComparePoint(const nsINode& aContainer, uint32_t aOffset,
- ErrorResult& aRv) const {
- if (!IsPointComparableToRange(aContainer, aOffset, aRv)) {
+ ErrorResult& aRv,
+ bool aAllowCrossShadowBoundary) const {
+ if (!IsPointComparableToRange(aContainer, aOffset, aAllowCrossShadowBoundary,
+ aRv)) {
return 0;
}
@@ -853,11 +872,15 @@ int16_t nsRange::ComparePoint(const nsINode& aContainer, uint32_t aOffset,
MOZ_ASSERT(point.IsSetAndValid());
- if (Maybe<int32_t> order = nsContentUtils::ComparePoints(point, mStart);
+ if (Maybe<int32_t> order = nsContentUtils::ComparePoints(
+ point, aAllowCrossShadowBoundary ? MayCrossShadowBoundaryStartRef()
+ : StartRef());
order && *order <= 0) {
return int16_t(*order);
}
- if (Maybe<int32_t> order = nsContentUtils::ComparePoints(mEnd, point);
+ if (Maybe<int32_t> order = nsContentUtils::ComparePoints(
+ aAllowCrossShadowBoundary ? MayCrossShadowBoundaryEndRef() : EndRef(),
+ point);
order && *order == -1) {
return 1;
}
@@ -881,7 +904,9 @@ bool nsRange::IntersectsNode(nsINode& aNode, ErrorResult& aRv) {
return false;
}
- if (!IsPointComparableToRange(*parent, *nodeIndex, IgnoreErrors())) {
+ if (!IsPointComparableToRange(*parent, *nodeIndex,
+ false /* aAllowCrossShadowBoundary */,
+ IgnoreErrors())) {
return false;
}
@@ -2866,7 +2891,8 @@ static void CollectClientRectsForSubtree(
if (!aTextOnly || isText) {
nsLayoutUtils::GetAllInFlowRectsAndTexts(
frame, nsLayoutUtils::GetContainingBlockForClientRect(frame),
- aCollector, aTextList, nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS);
+ aCollector, aTextList,
+ nsLayoutUtils::GetAllInFlowRectsFlag::AccountForTransforms);
if (isText) {
return;
}
@@ -3504,7 +3530,7 @@ void nsRange::CreateOrUpdateCrossShadowBoundaryRangeIfNeeded(
if (!mCrossShadowBoundaryRange) {
mCrossShadowBoundaryRange =
- StaticRange::Create(aStartBoundary, aEndBoundary, IgnoreErrors());
+ CrossShadowBoundaryRange::Create(aStartBoundary, aEndBoundary);
return;
}
diff --git a/dom/base/nsRange.h b/dom/base/nsRange.h
index 94459087cb..3c8f43a7dc 100644
--- a/dom/base/nsRange.h
+++ b/dom/base/nsRange.h
@@ -14,6 +14,7 @@
#include "nsCOMPtr.h"
#include "mozilla/dom/AbstractRange.h"
#include "mozilla/dom/StaticRange.h"
+#include "mozilla/dom/CrossShadowBoundaryRange.h"
#include "prmon.h"
#include "nsStubMutationObserver.h"
#include "nsWrapperCache.h"
@@ -209,7 +210,8 @@ class nsRange final : public mozilla::dom::AbstractRange,
int16_t CompareBoundaryPoints(uint16_t aHow, const nsRange& aOtherRange,
ErrorResult& aRv);
int16_t ComparePoint(const nsINode& aContainer, uint32_t aOffset,
- ErrorResult& aRv) const;
+ ErrorResult& aRv,
+ bool aAllowCrossShadowBoundary = false) const;
void DeleteContents(ErrorResult& aRv);
already_AddRefed<mozilla::dom::DocumentFragment> ExtractContents(
ErrorResult& aErr);
@@ -223,7 +225,8 @@ class nsRange final : public mozilla::dom::AbstractRange,
void InsertNode(nsINode& aNode, ErrorResult& aErr);
bool IntersectsNode(nsINode& aNode, ErrorResult& aRv);
bool IsPointInRange(const nsINode& aContainer, uint32_t aOffset,
- ErrorResult& aRv) const;
+ ErrorResult& aRv,
+ bool aAllowCrossShadowBoundary = false) const;
void ToString(nsAString& aReturn, ErrorResult& aErr);
void Detach();
@@ -336,8 +339,14 @@ class nsRange final : public mozilla::dom::AbstractRange,
// document as the range, aContainer is a DOCUMENT_TYPE_NODE and
// aOffset doesn't exceed aContainer's length.
bool IsPointComparableToRange(const nsINode& aContainer, uint32_t aOffset,
+ bool aAllowCrossShadowBoundary,
ErrorResult& aErrorResult) const;
+ // @return true iff aContainer is a shadow including inclusive descendant of
+ // the common ancestor of the mCrossBoundaryRange.
+ bool IsShadowIncludingInclusiveDescendantOfCrossBoundaryRangeAncestor(
+ const nsINode& aContainer) const;
+
/**
* @brief Returns true if the range is part of exactly one |Selection|.
*/
@@ -424,7 +433,7 @@ class nsRange final : public mozilla::dom::AbstractRange,
: mEnd.GetChildAtOffset();
}
- mozilla::dom::StaticRange* GetCrossShadowBoundaryRange() const {
+ mozilla::dom::CrossShadowBoundaryRange* GetCrossShadowBoundaryRange() const {
return mCrossShadowBoundaryRange;
}
@@ -549,7 +558,7 @@ class nsRange final : public mozilla::dom::AbstractRange,
// just return one point when a collapse is needed.
// Bug https://bugzilla.mozilla.org/show_bug.cgi?id=1886028 is going
// to be used to improve mCrossShadowBoundaryRange.
- RefPtr<mozilla::dom::StaticRange> mCrossShadowBoundaryRange;
+ RefPtr<mozilla::dom::CrossShadowBoundaryRange> mCrossShadowBoundaryRange;
friend class mozilla::dom::AbstractRange;
};
diff --git a/dom/base/nsTextFragment.cpp b/dom/base/nsTextFragment.cpp
index 5cba2577b8..c3f3d388be 100644
--- a/dom/base/nsTextFragment.cpp
+++ b/dom/base/nsTextFragment.cpp
@@ -198,23 +198,22 @@ bool nsTextFragment::SetTo(const char16_t* aBuffer, uint32_t aLength,
}
if (aForce2b && mState.mIs2b && !m2b->IsReadonly()) {
+ // Try to re-use our existing StringBuffer.
uint32_t storageSize = m2b->StorageSize();
uint32_t neededSize = aLength * sizeof(char16_t);
if (!neededSize) {
if (storageSize < AutoStringDefaultStorageSize) {
// If we're storing small enough nsStringBuffer, let's preserve it.
-
static_cast<char16_t*>(m2b->Data())[0] = char16_t(0);
mState.mLength = 0;
mState.mIsBidi = false;
return true;
}
- } else if ((neededSize < storageSize) &&
- ((storageSize / 2) <
- (neededSize + AutoStringDefaultStorageSize))) {
- // Don't try to reuse the existing nsStringBuffer, if it would have
- // lots of unused space.
-
+ } else if (neededSize < storageSize &&
+ (storageSize / 2) <
+ (neededSize + AutoStringDefaultStorageSize)) {
+ // Don't try to reuse the existing nsStringBuffer, if it would have lots
+ // of unused space.
memcpy(m2b->Data(), aBuffer, neededSize);
static_cast<char16_t*>(m2b->Data())[aLength] = char16_t(0);
mState.mLength = aLength;
@@ -226,19 +225,18 @@ bool nsTextFragment::SetTo(const char16_t* aBuffer, uint32_t aLength,
}
}
- ReleaseText();
-
if (aLength == 0) {
+ ReleaseText();
return true;
}
char16_t firstChar = *aBuffer;
if (!aForce2b && aLength == 1 && firstChar < 256) {
+ ReleaseText();
m1b = sSingleCharSharedString + firstChar;
mState.mInHeap = false;
mState.mIs2b = false;
mState.mLength = 1;
-
return true;
}
@@ -266,6 +264,7 @@ bool nsTextFragment::SetTo(const char16_t* aBuffer, uint32_t aLength,
if (ucp == uend && endNewLine - start <= TEXTFRAG_MAX_NEWLINES &&
ucp - endNewLine <= TEXTFRAG_WHITE_AFTER_NEWLINE) {
+ ReleaseText();
char** strings = space == ' ' ? sSpaceSharedString : sTabSharedString;
m1b = strings[endNewLine - start];
@@ -287,27 +286,29 @@ bool nsTextFragment::SetTo(const char16_t* aBuffer, uint32_t aLength,
if (first16bit != -1) { // aBuffer contains no non-8bit character
// Use ucs2 storage because we have to
- CheckedUint32 m2bSize = CheckedUint32(aLength) + 1;
- if (!m2bSize.isValid()) {
+ CheckedUint32 size = CheckedUint32(aLength) + 1;
+ if (!size.isValid()) {
return false;
}
- m2bSize *= sizeof(char16_t);
- if (!m2bSize.isValid()) {
+ size *= sizeof(char16_t);
+ if (!size.isValid()) {
return false;
}
- m2b = nsStringBuffer::Alloc(m2bSize.value()).take();
- if (!m2b) {
+ RefPtr<nsStringBuffer> newBuffer = nsStringBuffer::Alloc(size.value());
+ if (!newBuffer) {
return false;
}
- memcpy(m2b->Data(), aBuffer, aLength * sizeof(char16_t));
- static_cast<char16_t*>(m2b->Data())[aLength] = char16_t(0);
+ ReleaseText();
+ memcpy(newBuffer->Data(), aBuffer, aLength * sizeof(char16_t));
+ static_cast<char16_t*>(newBuffer->Data())[aLength] = char16_t(0);
+
+ m2b = newBuffer.forget().take();
mState.mIs2b = true;
if (aUpdateBidi) {
UpdateBidiFlag(aBuffer + first16bit, aLength - first16bit);
}
-
} else {
// Use 1 byte storage because we can
char* buff = static_cast<char*>(malloc(aLength));
@@ -315,6 +316,7 @@ bool nsTextFragment::SetTo(const char16_t* aBuffer, uint32_t aLength,
return false;
}
+ ReleaseText();
// Copy data
LossyConvertUtf16toLatin1(Span(aBuffer, aLength), Span(buff, aLength));
m1b = buff;
diff --git a/dom/base/nsTextFragment.h b/dom/base/nsTextFragment.h
index 5330815683..91efa49254 100644
--- a/dom/base/nsTextFragment.h
+++ b/dom/base/nsTextFragment.h
@@ -113,8 +113,7 @@ class nsTextFragment final {
}
ReleaseText();
if (aForce2b && !aUpdateBidi) {
- nsStringBuffer* buffer = nsStringBuffer::FromString(aString);
- if (buffer) {
+ if (nsStringBuffer* buffer = aString.GetStringBuffer()) {
NS_ADDREF(m2b = buffer);
mState.mInHeap = true;
mState.mIs2b = true;
@@ -154,19 +153,13 @@ class nsTextFragment final {
const mozilla::fallible_t& aFallible) const {
if (mState.mIs2b) {
if (aString.IsEmpty()) {
- m2b->ToString(mState.mLength, aString);
+ aString.Assign(m2b, mState.mLength);
return true;
}
- bool ok = aString.Append(Get2b(), mState.mLength, aFallible);
- if (!ok) {
- return false;
- }
-
- return true;
- } else {
- return AppendASCIItoUTF16(Substring(m1b, mState.mLength), aString,
- aFallible);
+ return aString.Append(Get2b(), mState.mLength, aFallible);
}
+ return AppendASCIItoUTF16(Substring(m1b, mState.mLength), aString,
+ aFallible);
}
/**
diff --git a/dom/base/test/browser_page_load_event_telemetry.js b/dom/base/test/browser_page_load_event_telemetry.js
index 871e06026a..530d45db9a 100644
--- a/dom/base/test/browser_page_load_event_telemetry.js
+++ b/dom/base/test/browser_page_load_event_telemetry.js
@@ -34,6 +34,16 @@ add_task(async function () {
30,
"Should have at least 30 page load events"
);
+
+ // Ensure the events in the pageload ping are reasonable.
+ record.forEach(entry => {
+ Assert.equal(entry.name, "page_load");
+ Assert.greater(parseInt(entry.extra.load_time), 0);
+ Assert.ok(
+ entry.extra.using_webdriver,
+ "Webdriver field should be set to true."
+ );
+ });
});
// Perform page load 30 times to trigger the ping being sent
diff --git a/dom/base/test/chrome/bug418986-1.js b/dom/base/test/chrome/bug418986-1.js
index e7e3c63b5c..565ae418dd 100644
--- a/dom/base/test/chrome/bug418986-1.js
+++ b/dom/base/test/chrome/bug418986-1.js
@@ -35,7 +35,7 @@ var test = function (isContent) {
["screen.orientation.type", "'landscape-primary'"],
["screen.orientation.angle", 0],
["screen.mozOrientation", "'landscape-primary'"],
- ["devicePixelRatio", 1],
+ ["devicePixelRatio", 2],
];
// checkPair: tests if members of pair [a, b] are equal when evaluated.
diff --git a/dom/base/test/fullscreen/browser.toml b/dom/base/test/fullscreen/browser.toml
index dc883a4ac2..7c989bafcb 100644
--- a/dom/base/test/fullscreen/browser.toml
+++ b/dom/base/test/fullscreen/browser.toml
@@ -45,6 +45,9 @@ skip-if = [
["browser_fullscreen-sizemode.js"]
["browser_fullscreen-tab-close-race.js"]
+skip-if = [
+ "apple_silicon && !debug", # Bug 1877642
+]
["browser_fullscreen-tab-close.js"]
diff --git a/dom/base/test/jsmodules/importmaps/bug_1893164_module_1.mjs b/dom/base/test/jsmodules/importmaps/bug_1893164_module_1.mjs
new file mode 100644
index 0000000000..45894ce609
--- /dev/null
+++ b/dom/base/test/jsmodules/importmaps/bug_1893164_module_1.mjs
@@ -0,0 +1,3 @@
+/* eslint-disable import/no-named-default, import/no-unresolved */
+import { default as default_non } from "./non_existing.mjs";
+import { default as default_3 } from "./bug_1893164_module_3.mjs";
diff --git a/dom/base/test/jsmodules/importmaps/bug_1893164_module_2.mjs b/dom/base/test/jsmodules/importmaps/bug_1893164_module_2.mjs
new file mode 100644
index 0000000000..e6f5e9f9f0
--- /dev/null
+++ b/dom/base/test/jsmodules/importmaps/bug_1893164_module_2.mjs
@@ -0,0 +1,5 @@
+/* eslint-disable import/no-named-default */
+import { default as default_3 } from "./bug_1893164_module_3.mjs";
+
+module2_loaded = true;
+result = default_3;
diff --git a/dom/base/test/jsmodules/importmaps/bug_1893164_module_3.mjs b/dom/base/test/jsmodules/importmaps/bug_1893164_module_3.mjs
new file mode 100644
index 0000000000..dbb41f0e1f
--- /dev/null
+++ b/dom/base/test/jsmodules/importmaps/bug_1893164_module_3.mjs
@@ -0,0 +1 @@
+export default 3;
diff --git a/dom/base/test/jsmodules/importmaps/bug_1894631_module_1.mjs b/dom/base/test/jsmodules/importmaps/bug_1894631_module_1.mjs
new file mode 100644
index 0000000000..b0c91fe0cf
--- /dev/null
+++ b/dom/base/test/jsmodules/importmaps/bug_1894631_module_1.mjs
@@ -0,0 +1,5 @@
+/* eslint-disable import/no-named-default, import/no-unresolved, import/named */
+import { default as default_2 } from "./bug_1894631_module_2.mjs";
+import { default as default_non } from "./non_existing.mjs";
+
+module1_loaded = true;
diff --git a/dom/base/test/jsmodules/importmaps/bug_1894631_module_2.mjs b/dom/base/test/jsmodules/importmaps/bug_1894631_module_2.mjs
new file mode 100644
index 0000000000..dd17da2abc
--- /dev/null
+++ b/dom/base/test/jsmodules/importmaps/bug_1894631_module_2.mjs
@@ -0,0 +1,3 @@
+/* eslint-disable import/no-named-default */
+import { default as default_3 } from "./bug_1894631_module_3.mjs";
+import { default as default_4 } from "./bug_1894631_module_4.mjs";
diff --git a/dom/base/test/jsmodules/importmaps/bug_1894631_module_3.mjs b/dom/base/test/jsmodules/importmaps/bug_1894631_module_3.mjs
new file mode 100644
index 0000000000..dbb41f0e1f
--- /dev/null
+++ b/dom/base/test/jsmodules/importmaps/bug_1894631_module_3.mjs
@@ -0,0 +1 @@
+export default 3;
diff --git a/dom/base/test/jsmodules/importmaps/bug_1894631_module_4.mjs b/dom/base/test/jsmodules/importmaps/bug_1894631_module_4.mjs
new file mode 100644
index 0000000000..456ffaafac
--- /dev/null
+++ b/dom/base/test/jsmodules/importmaps/bug_1894631_module_4.mjs
@@ -0,0 +1 @@
+export default 4;
diff --git a/dom/base/test/jsmodules/importmaps/mochitest.toml b/dom/base/test/jsmodules/importmaps/mochitest.toml
index 1f95b155ac..b71bf21def 100644
--- a/dom/base/test/jsmodules/importmaps/mochitest.toml
+++ b/dom/base/test/jsmodules/importmaps/mochitest.toml
@@ -3,6 +3,13 @@ support-files = [
"bug_1865410_module_a.mjs",
"bug_1865410_module_b.mjs",
"bug_1873417.mjs",
+ "bug_1893164_module_1.mjs",
+ "bug_1893164_module_2.mjs",
+ "bug_1893164_module_3.mjs",
+ "bug_1894631_module_1.mjs",
+ "bug_1894631_module_2.mjs",
+ "bug_1894631_module_3.mjs",
+ "bug_1894631_module_4.mjs",
"classic_script.js",
"module_chain_1.mjs",
"module_chain_2.mjs",
@@ -34,7 +41,10 @@ support-files = [
["test_bug_1873417.html"]
+["test_bug_1893164.html"]
+
["test_importMap_with_external_script.html"]
["test_importMap_with_nonexisting_module.html"]
["test_dynamic_importMap_with_external_script.html"]
["test_dynamic_importMap_load_completes.html"]
+["test_shared_submodules_with_modulepreload.html"]
diff --git a/dom/base/test/jsmodules/importmaps/test_bug_1893164.html b/dom/base/test/jsmodules/importmaps/test_bug_1893164.html
new file mode 100644
index 0000000000..6c5306b815
--- /dev/null
+++ b/dom/base/test/jsmodules/importmaps/test_bug_1893164.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test module cancel</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ var module2_loaded = false, result;
+
+ SimpleTest.waitForExplicitFinish();
+
+ function testLoaded() {
+ ok(module2_loaded, 'module_2.mjs should be loaded');
+ ok(result == 3, "result should be 3 from module_3.mjs");
+ SimpleTest.finish();
+ }
+</script>
+
+<script src="./bug_1893164_module_1.mjs" type="module"></script>
+<script src="./bug_1893164_module_2.mjs" type="module"></script>
+
+<body onload='testLoaded()'></body>
diff --git a/dom/base/test/jsmodules/importmaps/test_shared_submodules_with_modulepreload.html b/dom/base/test/jsmodules/importmaps/test_shared_submodules_with_modulepreload.html
new file mode 100644
index 0000000000..a99582f8d3
--- /dev/null
+++ b/dom/base/test/jsmodules/importmaps/test_shared_submodules_with_modulepreload.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test module cancel won't trigger an assert</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="modulepreload" href="./bug_1894631_module_2.mjs" />
+<link rel="modulepreload" href="./bug_1894631_module_3.mjs" />
+<link rel="modulepreload" href="./non_existing.mjs" />
+
+<script src="./bug_1894631_module_1.mjs" type="module" id="module_1"></script>
+<script>
+ var module1_loaded = false;
+ var module1_error = false;
+
+ SimpleTest.waitForExplicitFinish();
+
+ const module1 = document.getElementById("module_1");
+ module1.addEventListener("error", (event) => {
+ info("error event");
+ module1_error = true;
+ });
+
+ function testLoaded() {
+ ok(module1_error, "module_1.mjs should fire an error event");
+ ok(!module1_loaded, "module_1.mjs should not be loaded");
+ SimpleTest.finish();
+ }
+</script>
+
+<body onload='testLoaded()'>
+</body>
diff --git a/dom/base/test/test_anchor_target_blank_referrer.html b/dom/base/test/test_anchor_target_blank_referrer.html
index b494c28017..643cfd6c03 100644
--- a/dom/base/test/test_anchor_target_blank_referrer.html
+++ b/dom/base/test/test_anchor_target_blank_referrer.html
@@ -35,6 +35,9 @@
const testCases = [
{ACTION: ["generate-anchor-target-blank-policy-test"],
+ PREFS: [
+ ["dom.security.https_first", false], // need to test http and https
+ ],
TESTS: [
// Referrer policy is set in meta
{NAME: 'origin-in-meta-rel-noopener',
diff --git a/dom/base/test/test_focus_radio.html b/dom/base/test/test_focus_radio.html
index 8e97012745..9e14143143 100644
--- a/dom/base/test/test_focus_radio.html
+++ b/dom/base/test/test_focus_radio.html
@@ -2,7 +2,14 @@
<title>Test for input radio focus</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
-
+<style>
+.visHidden {
+ visibility: hidden;
+}
+.dispNone {
+ display: none;
+}
+</style>
<button id="before">before</button>
<fieldset>
<legend>a</legend>
@@ -26,6 +33,27 @@
<label><input id="d3" type="radio" name="d" disabled>d3</label>
<label><input id="d4" type="radio" name="d">d4</label>
</fieldset>
+<fieldset>
+ <legend>e</legend>
+ <label><input id="e1" type="radio" name="e" hidden>e1</label>
+ <label><input id="e2" type="radio" name="e">e2</label>
+ <label><input id="e3" type="radio" name="e" hidden>e3</label>
+ <label><input id="e4" type="radio" name="e">e4</label>
+</fieldset>
+<fieldset>
+ <legend>f</legend>
+ <label><input id="f1" type="radio" name="f" class="visHidden">f1</label>
+ <label><input id="f2" type="radio" name="f">f2</label>
+ <label><input id="f3" type="radio" name="f" class="visHidden">f3</label>
+ <label><input id="f4" type="radio" name="f">f4</label>
+</fieldset>
+<fieldset>
+ <legend>g</legend>
+ <label><input id="g1" type="radio" name="g" class="dispNone">g1</label>
+ <label><input id="g2" type="radio" name="g">g2</label>
+ <label><input id="g3" type="radio" name="g" class="dispNone">g3</label>
+ <label><input id="g4" type="radio" name="g">g4</label>
+</fieldset>
<button id="after">after</button>
<script>
@@ -51,8 +79,17 @@
expectFocusAfterKey("Tab", "c1");
// d1 is disabled, so d2 should get focus.
expectFocusAfterKey("Tab", "d2");
+ // e1 is hidden, so e2 should get focus.
+ expectFocusAfterKey("Tab", "e2");
+ // f1 is hidden, so f2 should get focus.
+ expectFocusAfterKey("Tab", "f2");
+ // g1 is hidden, so g2 should get focus.
+ expectFocusAfterKey("Tab", "g2");
expectFocusAfterKey("Tab", "after");
+ expectFocusAfterKey("Shift+Tab", "g2");
+ expectFocusAfterKey("Shift+Tab", "f2");
+ expectFocusAfterKey("Shift+Tab", "e2");
expectFocusAfterKey("Shift+Tab", "d2");
expectFocusAfterKey("Shift+Tab", "c1");
expectFocusAfterKey("Shift+Tab", "b2");
@@ -85,6 +122,36 @@
// Up arrow should wrap at the top.
expectFocusAfterKey("ArrowUp", "d4");
+ expectFocusAfterKey("Tab", "e2");
+ // e3 is hidden, so down arrow should focus e4.
+ expectFocusAfterKey("ArrowDown", "e4");
+ expectFocusAfterKey("ArrowUp", "e2");
+ expectFocusAfterKey("ArrowDown", "e4");
+ // Down arrow should wrap at the bottom, skipping hidden.
+ expectFocusAfterKey("ArrowDown", "e2");
+ // Up arrow should wrap at the top.
+ expectFocusAfterKey("ArrowUp", "e4");
+
+ expectFocusAfterKey("Tab", "f2");
+ // f3 is hidden, so down arrow should focus f4.
+ expectFocusAfterKey("ArrowDown", "f4");
+ expectFocusAfterKey("ArrowUp", "f2");
+ expectFocusAfterKey("ArrowDown", "f4");
+ // Down arrow should wrap at the bottom, skipping hidden.
+ expectFocusAfterKey("ArrowDown", "f2");
+ // Up arrow should wrap at the top.
+ expectFocusAfterKey("ArrowUp", "f4");
+
+ expectFocusAfterKey("Tab", "g2");
+ // g3 is hidden, so down arrow should focus g4.
+ expectFocusAfterKey("ArrowDown", "g4");
+ expectFocusAfterKey("ArrowUp", "g2");
+ expectFocusAfterKey("ArrowDown", "g4");
+ // Down arrow should wrap at the bottom, skipping hidden.
+ expectFocusAfterKey("ArrowDown", "g2");
+ // Up arrow should wrap at the top.
+ expectFocusAfterKey("ArrowUp", "g4");
+
SimpleTest.finish();
});
</script>
diff --git a/dom/bindings/BindingUtils.cpp b/dom/bindings/BindingUtils.cpp
index 11d12dd364..198db3b5e2 100644
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -770,19 +770,31 @@ bool LegacyFactoryFunctionJSNative(JSContext* cx, unsigned argc,
->mNative(cx, argc, vp);
}
-static JSObject* CreateLegacyFactoryFunction(JSContext* cx, jsid name,
- const JSNativeHolder* nativeHolder,
- unsigned ctorNargs) {
- JSFunction* fun = js::NewFunctionByIdWithReserved(
- cx, LegacyFactoryFunctionJSNative, ctorNargs, JSFUN_CONSTRUCTOR, name);
+// This creates a JSFunction and sets its length and name properties in the
+// order that ECMAScript's CreateBuiltinFunction does.
+static JSObject* CreateBuiltinFunctionForConstructor(
+ JSContext* aCx, JSNative aNative, size_t aNativeReservedSlot,
+ void* aNativeReserved, unsigned int aNargs, jsid aName,
+ JS::Handle<JSObject*> aProto) {
+ JSFunction* fun = js::NewFunctionByIdWithReservedAndProto(
+ aCx, aNative, aProto, aNargs, JSFUN_CONSTRUCTOR, aName);
if (!fun) {
return nullptr;
}
- JSObject* constructor = JS_GetFunctionObject(fun);
- js::SetFunctionNativeReserved(
- constructor, LEGACY_FACTORY_FUNCTION_NATIVE_HOLDER_RESERVED_SLOT,
- JS::PrivateValue(const_cast<JSNativeHolder*>(nativeHolder)));
+ JS::Rooted<JSObject*> constructor(aCx, JS_GetFunctionObject(fun));
+ js::SetFunctionNativeReserved(constructor, aNativeReservedSlot,
+ JS::PrivateValue(aNativeReserved));
+
+ // Eagerly force creation of the .length and .name properties, because
+ // SpiderMonkey creates them lazily (see
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1629803).
+ bool unused;
+ if (!JS_HasProperty(aCx, constructor, "length", &unused) ||
+ !JS_HasProperty(aCx, constructor, "name", &unused)) {
+ return nullptr;
+ }
+
return constructor;
}
@@ -936,29 +948,12 @@ static JSObject* CreateInterfaceObject(
JS::Rooted<jsid> nameId(cx, JS::PropertyKey::NonIntAtom(name));
- JS::Rooted<JSObject*> constructor(cx);
- {
- JSFunction* fun = js::NewFunctionByIdWithReservedAndProto(
- cx, InterfaceObjectJSNative, interfaceProto, ctorNargs,
- JSFUN_CONSTRUCTOR, nameId);
- if (!fun) {
- return nullptr;
- }
-
- constructor = JS_GetFunctionObject(fun);
- }
-
- js::SetFunctionNativeReserved(
- constructor, INTERFACE_OBJECT_INFO_RESERVED_SLOT,
- JS::PrivateValue(const_cast<DOMInterfaceInfo*>(interfaceInfo)));
-
- // Eagerly force creation of the .length and .name properties, because they
- // need to be defined before the .prototype property (CreateBuiltinFunction
- // called from the WebIDL spec sets them, and then the .prototype property is
- // defined in the WebIDL spec itself).
- bool unused;
- if (!JS_HasProperty(cx, constructor, "length", &unused) ||
- !JS_HasProperty(cx, constructor, "name", &unused)) {
+ JS::Rooted<JSObject*> constructor(
+ cx, CreateBuiltinFunctionForConstructor(
+ cx, InterfaceObjectJSNative, INTERFACE_OBJECT_INFO_RESERVED_SLOT,
+ const_cast<DOMInterfaceInfo*>(interfaceInfo), ctorNargs, nameId,
+ interfaceProto));
+ if (!constructor) {
return nullptr;
}
@@ -1001,7 +996,11 @@ static JSObject* CreateInterfaceObject(
nameId = JS::PropertyKey::NonIntAtom(fname);
JS::Rooted<JSObject*> legacyFactoryFunction(
- cx, CreateLegacyFactoryFunction(cx, nameId, &lff.mHolder, lff.mNargs));
+ cx, CreateBuiltinFunctionForConstructor(
+ cx, LegacyFactoryFunctionJSNative,
+ LEGACY_FACTORY_FUNCTION_NATIVE_HOLDER_RESERVED_SLOT,
+ const_cast<JSNativeHolder*>(&lff.mHolder), lff.mNargs, nameId,
+ nullptr));
if (!legacyFactoryFunction ||
!JS_DefineProperty(cx, legacyFactoryFunction, "prototype", proto,
JSPROP_PERMANENT | JSPROP_READONLY) ||
diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf
index d735bcc2ee..19dd6d2e8d 100644
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -415,6 +415,10 @@ DOMInterfaces = {
'wrapperCache': False,
},
+'InspectorCSSParser': {
+ 'wrapperCache': False,
+},
+
'IntersectionObserver': {
'nativeType': 'mozilla::dom::DOMIntersectionObserver',
},
diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py
index a6a49f9b2f..de9444b42f 100644
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -8937,10 +8937,12 @@ def wrapTypeIntoCurrentCompartment(type, value, isMember=True):
return CGList(memberWraps) if len(memberWraps) != 0 else None
if type.isUnion():
- memberWraps = []
+ origValue = value
+ origType = type
if type.nullable():
type = type.inner
value = "%s.Value()" % value
+ memberWraps = []
for member in type.flatMemberTypes:
memberName = getUnionMemberName(member)
memberWrap = wrapTypeIntoCurrentCompartment(
@@ -8949,7 +8951,12 @@ def wrapTypeIntoCurrentCompartment(type, value, isMember=True):
if memberWrap:
memberWrap = CGIfWrapper(memberWrap, "%s.Is%s()" % (value, memberName))
memberWraps.append(memberWrap)
- return CGList(memberWraps, "else ") if len(memberWraps) != 0 else None
+ if len(memberWraps) == 0:
+ return None
+ wrapCode = CGList(memberWraps, "else ")
+ if origType.nullable():
+ wrapCode = CGIfWrapper(wrapCode, "!%s.IsNull()" % origValue)
+ return wrapCode
if (
type.isUndefined()
@@ -8996,7 +9003,8 @@ class CGPerSignatureCall(CGThing):
actual return value (e.g. this is an attribute setter) or an
IDLType if there's an IDL type involved (including |void|).
2) An argument list, which is allowed to be empty.
- 3) A name of a native method to call.
+ 3) A name of a native method to call. It is ignored for methods
+ annotated with the "[WebExtensionStub=...]" extended attribute.
4) Whether or not this method is static. Note that this only controls how
the method is called (|self->nativeMethodName(...)| vs
|nativeMethodName(...)|).
@@ -9370,7 +9378,7 @@ class CGPerSignatureCall(CGThing):
nativeMethodName,
argsPre,
args,
- ] = self.processWebExtensionStubAttribute(idlNode, cgThings)
+ ] = self.processWebExtensionStubAttribute(cgThings)
else:
args = self.getArguments()
@@ -9439,9 +9447,9 @@ class CGPerSignatureCall(CGThing):
def getArguments(self):
return list(zip(self.arguments, self.getArgumentNames()))
- def processWebExtensionStubAttribute(self, idlNode, cgThings):
+ def processWebExtensionStubAttribute(self, cgThings):
nativeMethodName = "CallWebExtMethod"
- stubNameSuffix = idlNode.getExtendedAttribute("WebExtensionStub")
+ stubNameSuffix = self.idlNode.getExtendedAttribute("WebExtensionStub")
if isinstance(stubNameSuffix, list):
nativeMethodName += stubNameSuffix[0]
@@ -9454,7 +9462,7 @@ class CGPerSignatureCall(CGThing):
if singleVariadicArg:
argsPre = [
"cx",
- 'u"%s"_ns' % idlNode.identifier.name,
+ 'u"%s"_ns' % self.idlNode.identifier.name,
"Constify(%s)" % "arg0",
]
args = []
@@ -9462,7 +9470,7 @@ class CGPerSignatureCall(CGThing):
argsPre = [
"cx",
- 'u"%s"_ns' % idlNode.identifier.name,
+ 'u"%s"_ns' % self.idlNode.identifier.name,
"Constify(%s)" % "args_sequence",
]
args = []
diff --git a/dom/bindings/DOMString.h b/dom/bindings/DOMString.h
index c5404f5351..e87d820777 100644
--- a/dom/bindings/DOMString.h
+++ b/dom/bindings/DOMString.h
@@ -169,8 +169,7 @@ class MOZ_STACK_CLASS DOMString {
if (MOZ_UNLIKELY(aString.IsVoid())) {
SetNull();
} else if (!aString.IsEmpty()) {
- nsStringBuffer* buf = nsStringBuffer::FromString(aString);
- if (buf) {
+ if (nsStringBuffer* buf = aString.GetStringBuffer()) {
SetKnownLiveStringBuffer(buf, aString.Length());
} else if (aString.IsLiteral()) {
SetLiteralInternal(aString.BeginReading(), aString.Length());
@@ -236,7 +235,7 @@ class MOZ_STACK_CLASS DOMString {
auto chars = static_cast<char16_t*>(buf->Data());
if (chars[len] == '\0') {
// Safe to share the buffer.
- buf->ToString(len, aString);
+ aString.Assign(buf, len);
} else {
// We need to copy, unfortunately.
aString.Assign(chars, len);
diff --git a/dom/bindings/Errors.msg b/dom/bindings/Errors.msg
index 2b0807891f..73a9364b7e 100644
--- a/dom/bindings/Errors.msg
+++ b/dom/bindings/Errors.msg
@@ -60,6 +60,7 @@ MSG_DEF(MSG_INVALID_REFERRER_URL, 2, true, JSEXN_TYPEERR, "{0}Invalid referrer U
MSG_DEF(MSG_FETCH_BODY_CONSUMED_ERROR, 1, true, JSEXN_TYPEERR, "{0}Body has already been consumed.")
MSG_DEF(MSG_RESPONSE_INVALID_STATUSTEXT_ERROR, 1, true, JSEXN_TYPEERR, "{0}Response statusText may not contain newline or carriage return.")
MSG_DEF(MSG_FETCH_FAILED, 1, true, JSEXN_TYPEERR, "{0}NetworkError when attempting to fetch resource.")
+MSG_DEF(MSG_FETCH_PARTIAL, 1, true, JSEXN_TYPEERR, "{0}Content-Length header of network response exceeds response Body.")
MSG_DEF(MSG_FETCH_BODY_WRONG_TYPE, 1, true, JSEXN_TYPEERR, "{0}Can't convert value to Uint8Array while consuming Body")
MSG_DEF(MSG_INVALID_ZOOMANDPAN_VALUE_ERROR, 1, true, JSEXN_RANGEERR, "{0}Invalid zoom and pan value.")
MSG_DEF(MSG_INVALID_TRANSFORM_ANGLE_ERROR, 1, true, JSEXN_RANGEERR, "{0}Invalid transform angle.")
diff --git a/dom/bindings/FakeString.h b/dom/bindings/FakeString.h
index f51f7890d9..e7221db868 100644
--- a/dom/bindings/FakeString.h
+++ b/dom/bindings/FakeString.h
@@ -51,7 +51,7 @@ struct FakeString {
// depend upon aString's data. aString should outlive this instance of
// FakeString.
void ShareOrDependUpon(const AString& aString) {
- RefPtr<nsStringBuffer> sharedBuffer = nsStringBuffer::FromString(aString);
+ RefPtr<nsStringBuffer> sharedBuffer = aString.GetStringBuffer();
if (!sharedBuffer) {
InitData(aString.BeginReading(), aString.Length());
if (!aString.IsTerminated()) {
diff --git a/dom/bindings/mach_commands.py b/dom/bindings/mach_commands.py
index 6f6b29bcbf..eb6865ea86 100644
--- a/dom/bindings/mach_commands.py
+++ b/dom/bindings/mach_commands.py
@@ -6,7 +6,7 @@ import os
import sys
from mach.decorators import Command, CommandArgument
-from mozbuild.util import mkdir
+from mozbuild.dirutils import mkdir
def get_test_parser():
diff --git a/dom/bindings/test/TestFunctions.cpp b/dom/bindings/test/TestFunctions.cpp
index a91121eaa7..59d4554020 100644
--- a/dom/bindings/test/TestFunctions.cpp
+++ b/dom/bindings/test/TestFunctions.cpp
@@ -66,8 +66,7 @@ void TestFunctions::GetStringDataAsDOMString(const Optional<uint32_t>& aLength,
length = mStringData.Length();
}
- nsStringBuffer* buf = nsStringBuffer::FromString(mStringData);
- if (buf) {
+ if (nsStringBuffer* buf = mStringData.GetStringBuffer()) {
aString.SetKnownLiveStringBuffer(buf, length);
return;
}
@@ -134,7 +133,7 @@ StringType TestFunctions::GetStringType(const nsAString& aString) {
return StringType::Literal;
}
- if (nsStringBuffer::FromString(aString)) {
+ if (aString.GetStringBuffer()) {
return StringType::Stringbuffer;
}
@@ -146,9 +145,8 @@ StringType TestFunctions::GetStringType(const nsAString& aString) {
}
bool TestFunctions::StringbufferMatchesStored(const nsAString& aString) {
- return nsStringBuffer::FromString(aString) &&
- nsStringBuffer::FromString(aString) ==
- nsStringBuffer::FromString(mStringData);
+ return aString.GetStringBuffer() &&
+ aString.GetStringBuffer() == mStringData.GetStringBuffer();
}
void TestFunctions::TestThrowNsresult(ErrorResult& aError) {
diff --git a/dom/broadcastchannel/BroadcastChannel.cpp b/dom/broadcastchannel/BroadcastChannel.cpp
index c89231badf..366541d911 100644
--- a/dom/broadcastchannel/BroadcastChannel.cpp
+++ b/dom/broadcastchannel/BroadcastChannel.cpp
@@ -96,8 +96,7 @@ class TeardownRunnableOnWorker final : public WorkerControlRunnable,
public:
TeardownRunnableOnWorker(WorkerPrivate* aWorkerPrivate,
BroadcastChannelChild* aActor)
- : WorkerControlRunnable(aWorkerPrivate, "TeardownRunnableOnWorker",
- WorkerThread),
+ : WorkerControlRunnable("TeardownRunnableOnWorker"),
TeardownRunnable(aActor) {}
bool WorkerRun(JSContext*, WorkerPrivate*) override {
@@ -340,7 +339,7 @@ void BroadcastChannel::Shutdown() {
RefPtr<TeardownRunnableOnWorker> runnable =
new TeardownRunnableOnWorker(workerPrivate, mActor);
- runnable->Dispatch();
+ runnable->Dispatch(workerPrivate);
}
mActor = nullptr;
diff --git a/dom/cache/CacheChild.cpp b/dom/cache/CacheChild.cpp
index c822653fea..05f25ec838 100644
--- a/dom/cache/CacheChild.cpp
+++ b/dom/cache/CacheChild.cpp
@@ -112,7 +112,7 @@ void CacheChild::NoteDeletedActor() {
// CacheOpChilds when StartDestroy was called from WorkerRef notification. If
// the last CacheOpChild is getting destructed; it's the time for us to
// SendTearDown to the other side.
- if (NumChildActors() == 1 && mDelayedDestroy && !mLocked) DestroyInternal();
+ if (NumChildActors() == 0 && mDelayedDestroy && !mLocked) DestroyInternal();
}
already_AddRefed<PCacheOpChild> CacheChild::AllocPCacheOpChild(
diff --git a/dom/cache/CacheStorageChild.cpp b/dom/cache/CacheStorageChild.cpp
index e7918bbf88..e344e2c272 100644
--- a/dom/cache/CacheStorageChild.cpp
+++ b/dom/cache/CacheStorageChild.cpp
@@ -88,7 +88,7 @@ void CacheStorageChild::NoteDeletedActor() {
// when StartDestroy was called from WorkerRef notification. If the last
// CacheOpChild is getting destructed; it's the time for us to SendTearDown to
// the other side.
- if (NumChildActors() == 1 && mDelayedDestroy) DestroyInternal();
+ if (NumChildActors() == 0 && mDelayedDestroy) DestroyInternal();
}
void CacheStorageChild::ActorDestroy(ActorDestroyReason aReason) {
diff --git a/dom/cache/CacheStreamControlParent.cpp b/dom/cache/CacheStreamControlParent.cpp
index aaab59f944..8441d5cee4 100644
--- a/dom/cache/CacheStreamControlParent.cpp
+++ b/dom/cache/CacheStreamControlParent.cpp
@@ -149,9 +149,11 @@ void CacheStreamControlParent::SetStreamList(
void CacheStreamControlParent::CloseAll() {
NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent);
- NotifyCloseAll();
QM_WARNONLY_TRY(OkIf(SendCloseAll()));
+
+ // Attention: NotifyCloseAll potentially destroys our this synchronously!
+ NotifyCloseAll();
}
void CacheStreamControlParent::Shutdown() {
diff --git a/dom/cache/CacheStreamControlParent.h b/dom/cache/CacheStreamControlParent.h
index 46fe834309..332c08487b 100644
--- a/dom/cache/CacheStreamControlParent.h
+++ b/dom/cache/CacheStreamControlParent.h
@@ -25,7 +25,12 @@ class CacheStreamControlParent final : public PCacheStreamControlParent,
CacheStreamControlParent();
void SetStreamList(SafeRefPtr<StreamList> aStreamList);
+
+ // Will close all streams. May synchronously free our this, see
+ // inherited StreamControl::CloseAllReadStreams.
void CloseAll();
+
+ // Implicitly called when the last stream goes away.
void Shutdown();
// StreamControl methods
diff --git a/dom/cache/Connection.cpp b/dom/cache/Connection.cpp
index e9c9becebc..f7afca0d7a 100644
--- a/dom/cache/Connection.cpp
+++ b/dom/cache/Connection.cpp
@@ -288,7 +288,8 @@ uint32_t Connection::DecreaseTransactionNestingLevel(
NS_IMETHODIMP
Connection::BackupToFileAsync(nsIFile* aDestinationFile,
- mozIStorageCompletionCallback* aCallback) {
+ mozIStorageCompletionCallback* aCallback,
+ uint32_t aPagesPerStep, uint32_t aStepDelayMs) {
// async methods are not supported
return NS_ERROR_NOT_IMPLEMENTED;
}
diff --git a/dom/cache/StreamList.cpp b/dom/cache/StreamList.cpp
index cbce695b60..00dd3b585f 100644
--- a/dom/cache/StreamList.cpp
+++ b/dom/cache/StreamList.cpp
@@ -136,13 +136,9 @@ void StreamList::CloseAll() {
NS_ASSERT_OWNINGTHREAD(StreamList);
if (mStreamControl && mStreamControl->CanSend()) {
- auto* streamControl = std::exchange(mStreamControl, nullptr);
-
- streamControl->CloseAll();
-
- mStreamControl = std::exchange(streamControl, nullptr);
-
- mStreamControl->Shutdown();
+ // CloseAll will kick off everything needed for shutdown.
+ // mStreamControl may go away immediately or async.
+ mStreamControl->CloseAll();
} else {
// We cannot interact with the child, let's just clear our lists of
// streams to unblock shutdown.
diff --git a/dom/cache/test/marionette/test_caches_delete_cleanup_after_shutdown.py b/dom/cache/test/marionette/test_caches_delete_cleanup_after_shutdown.py
index 4db7606b08..2923deeffe 100644
--- a/dom/cache/test/marionette/test_caches_delete_cleanup_after_shutdown.py
+++ b/dom/cache/test/marionette/test_caches_delete_cleanup_after_shutdown.py
@@ -2,6 +2,8 @@
# 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/.
+import os
+
from marionette_driver import Wait
from marionette_harness import MarionetteTestCase
@@ -13,6 +15,8 @@ from marionette_harness import MarionetteTestCase
fail if we fail to delete all of the 5,000 1k files we create on disk
as part of the test.
"""
+QM_TESTING_PREF = "dom.quotaManager.testing"
+
EXPECTED_CACHEDIR_SIZE_AFTER_CLEANUP = 128 * 1024 # 128KB
CACHE_ID = "data"
@@ -31,10 +35,12 @@ class CachesDeleteCleanupAtShutdownTestCase(MarionetteTestCase):
def setUp(self):
super(CachesDeleteCleanupAtShutdownTestCase, self).setUp()
self.marionette.restart(in_app=False, clean=True)
+ self.marionette.set_pref(QM_TESTING_PREF, True)
def tearDown(self):
self.marionette.restart(in_app=False, clean=True)
super(CachesDeleteCleanupAtShutdownTestCase, self).tearDown()
+ self.marionette.set_pref(QM_TESTING_PREF, False)
def getUsage(self):
return self.marionette.execute_script(
@@ -71,56 +77,45 @@ class CachesDeleteCleanupAtShutdownTestCase(MarionetteTestCase):
script_args=(CACHE_ID,),
)
+ def countBodies(self):
+ profile = self.marionette.instance.profile.profile
+ originDir = (
+ self.marionette.absolute_url("")[:-1].replace(":", "+").replace("/", "+")
+ )
+ morgueDir = f"{profile}/storage/default/{originDir}/cache/morgue"
+ print("morgueDir path = ", morgueDir)
+
+ bodyCount = -1
+ if os.path.exists(morgueDir):
+ bodyCount = 0
+ for elem in os.listdir(morgueDir):
+ absPathElem = os.path.join(morgueDir, elem)
+ if os.path.isdir(absPathElem):
+ bodyCount += sum(
+ 1
+ for e in os.listdir(absPathElem)
+ if os.path.isfile(os.path.join(absPathElem, e))
+ )
+ return bodyCount
+
def ensureCleanDirectory(self):
+ orphanedBodiesCount = self.countBodies()
+ return orphanedBodiesCount <= 0
+
+ def isStorageInitialized(self, temporary=False):
with self.marionette.using_context("chrome"):
- return self.marionette.execute_script(
+ return self.marionette.execute_async_script(
+ """
+ const [resolve] = arguments;
+ const req = %s;
+ req.callback = () => {
+ resolve(req.resultCode == Cr.NS_OK && req.result)
+ };
"""
- let originDir = arguments[0];
- const pathDelimiter = "/";
-
- function getRelativeFile(relativePath) {
- let file = Services.dirsvc
- .get("ProfD", Ci.nsIFile)
- .clone();
-
- relativePath.split(pathDelimiter).forEach(function(component) {
- if (component == "..") {
- file = file.parent;
- } else {
- file.append(component);
- }
- });
-
- return file;
- }
-
- function getCacheDir() {
-
- const storageDirName = "storage";
- const defaultPersistenceDirName = "default";
-
- return getRelativeFile(
- `${storageDirName}/${defaultPersistenceDirName}/${originDir}/cache`
- );
- }
-
- const cacheDir = getCacheDir();
- let morgueDir = cacheDir.clone();
-
- // morgue directory should be empty
- // or atleast directories under morgue should be empty
- morgueDir.append("morgue");
- for (let dir of morgueDir.directoryEntries) {
- for (let file of dir.directoryEntries) {
- return false;
- }
- }
- return true;
- """,
- script_args=(
- self.marionette.absolute_url("")[:-1]
- .replace(":", "+")
- .replace("/", "+"),
+ % (
+ "Services.qms.temporaryStorageInitialized()"
+ if temporary
+ else "Services.qms.storageInitialized()"
),
new_sandbox=False,
)
@@ -128,6 +123,7 @@ class CachesDeleteCleanupAtShutdownTestCase(MarionetteTestCase):
def create_and_cleanup_cache(self, ensureCleanCallback, in_app):
# create 640 cache entries
self.doCacheWork(640)
+ print("usage after doCacheWork = ", self.getUsage())
self.marionette.restart(in_app=in_app)
print("restart successful")
@@ -137,6 +133,14 @@ class CachesDeleteCleanupAtShutdownTestCase(MarionetteTestCase):
)
return ensureCleanCallback()
+ def afterCleanupClosure(self, usage):
+ print(
+ f"Storage initialized = {self.isStorageInitialized()}, temporary storage initialized = {self.isStorageInitialized(True)}"
+ )
+
+ print(f"Usage = {usage} and number of orphaned bodies = {self.countBodies()}")
+ return usage < EXPECTED_CACHEDIR_SIZE_AFTER_CLEANUP
+
def test_ensure_cache_cleanup_after_clean_restart(self):
self.marionette.navigate(
self.marionette.absolute_url("dom/cache/cacheUsage.html")
@@ -144,9 +148,8 @@ class CachesDeleteCleanupAtShutdownTestCase(MarionetteTestCase):
beforeUsage = self.getUsage()
def ensureCleanCallback():
- Wait(self.marionette, timeout=60).until(
- lambda x: (self.getUsage() - beforeUsage)
- < EXPECTED_CACHEDIR_SIZE_AFTER_CLEANUP,
+ Wait(self.marionette, interval=1, timeout=60).until(
+ lambda _: self.afterCleanupClosure(self.getUsage() - beforeUsage),
message="Cache directory is not cleaned up properly",
)
@@ -164,14 +167,19 @@ class CachesDeleteCleanupAtShutdownTestCase(MarionetteTestCase):
self.marionette.navigate(
self.marionette.absolute_url("dom/cache/cacheUsage.html")
)
+
+ print(f"profile path = {self.marionette.instance.profile.profile}")
+
beforeUsage = self.getUsage()
def ensureCleanCallback():
- self.openCache()
+ print(
+ f"ensureCleanCallback, profile path = {self.marionette.instance.profile.profile}"
+ )
- Wait(self.marionette, timeout=60).until(
- lambda x: (self.getUsage() - beforeUsage)
- < EXPECTED_CACHEDIR_SIZE_AFTER_CLEANUP,
+ self.openCache()
+ Wait(self.marionette, interval=1, timeout=60).until(
+ lambda _: self.afterCleanupClosure(self.getUsage() - beforeUsage),
message="Cache directory is not cleaned up properly",
)
diff --git a/dom/canvas/CanvasImageCache.cpp b/dom/canvas/CanvasImageCache.cpp
index 358e057265..cef9f1ee2d 100644
--- a/dom/canvas/CanvasImageCache.cpp
+++ b/dom/canvas/CanvasImageCache.cpp
@@ -9,7 +9,6 @@
#include "imgIRequest.h"
#include "mozilla/dom/Element.h"
#include "nsTHashtable.h"
-#include "mozilla/dom/HTMLCanvasElement.h"
#include "nsContentUtils.h"
#include "mozilla/Preferences.h"
#include "mozilla/UniquePtr.h"
@@ -26,11 +25,11 @@ using namespace gfx;
* due to CORS security.
*/
struct ImageCacheKey {
- ImageCacheKey(imgIContainer* aImage, HTMLCanvasElement* aCanvas,
+ ImageCacheKey(imgIContainer* aImage, CanvasRenderingContext2D* aContext,
BackendType aBackendType)
- : mImage(aImage), mCanvas(aCanvas), mBackendType(aBackendType) {}
+ : mImage(aImage), mContext(aContext), mBackendType(aBackendType) {}
nsCOMPtr<imgIContainer> mImage;
- HTMLCanvasElement* mCanvas;
+ CanvasRenderingContext2D* mContext;
BackendType mBackendType;
};
@@ -41,7 +40,7 @@ struct ImageCacheKey {
struct ImageCacheEntryData {
ImageCacheEntryData(const ImageCacheEntryData& aOther)
: mImage(aOther.mImage),
- mCanvas(aOther.mCanvas),
+ mContext(aOther.mContext),
mBackendType(aOther.mBackendType),
mSourceSurface(aOther.mSourceSurface),
mSize(aOther.mSize),
@@ -49,7 +48,7 @@ struct ImageCacheEntryData {
mCropRect(aOther.mCropRect) {}
explicit ImageCacheEntryData(const ImageCacheKey& aKey)
: mImage(aKey.mImage),
- mCanvas(aKey.mCanvas),
+ mContext(aKey.mContext),
mBackendType(aKey.mBackendType) {}
nsExpirationState* GetExpirationState() { return &mState; }
@@ -57,7 +56,7 @@ struct ImageCacheEntryData {
// Key
nsCOMPtr<imgIContainer> mImage;
- HTMLCanvasElement* mCanvas;
+ CanvasRenderingContext2D* mContext;
BackendType mBackendType;
// Value
RefPtr<SourceSurface> mSourceSurface;
@@ -79,13 +78,13 @@ class ImageCacheEntry : public PLDHashEntryHdr {
~ImageCacheEntry() = default;
bool KeyEquals(KeyTypePointer key) const {
- return mData->mImage == key->mImage && mData->mCanvas == key->mCanvas &&
+ return mData->mImage == key->mImage && mData->mContext == key->mContext &&
mData->mBackendType == key->mBackendType;
}
static KeyTypePointer KeyToPointer(KeyType& key) { return &key; }
static PLDHashNumber HashKey(KeyTypePointer key) {
- return HashGeneric(key->mImage.get(), key->mCanvas, key->mBackendType);
+ return HashGeneric(key->mImage.get(), key->mContext, key->mBackendType);
}
enum { ALLOW_MEMMOVE = true };
@@ -152,7 +151,7 @@ class ImageCache final : public nsExpirationTracker<ImageCacheEntryData, 4> {
AllCanvasImageCacheKey(aObject->mImage, aObject->mBackendType));
// Deleting the entry will delete aObject since the entry owns aObject.
- mCache.RemoveEntry(ImageCacheKey(aObject->mImage, aObject->mCanvas,
+ mCache.RemoveEntry(ImageCacheKey(aObject->mImage, aObject->mContext,
aObject->mBackendType));
}
@@ -264,11 +263,33 @@ static already_AddRefed<imgIContainer> GetImageContainer(dom::Element* aImage) {
return imgContainer.forget();
}
+void CanvasImageCache::NotifyCanvasDestroyed(
+ CanvasRenderingContext2D* aContext) {
+ MOZ_ASSERT(aContext);
+
+ if (!NS_IsMainThread() || !gImageCache) {
+ return;
+ }
+
+ for (auto i = gImageCache->mCache.Iter(); !i.Done(); i.Next()) {
+ ImageCacheEntryData* data = i.Get()->mData.get();
+ if (data->mContext == aContext) {
+ gImageCache->RemoveObject(data);
+ gImageCache->mAllCanvasCache.RemoveEntry(
+ AllCanvasImageCacheKey(data->mImage, data->mBackendType));
+ i.Remove();
+ }
+ }
+}
+
void CanvasImageCache::NotifyDrawImage(
- Element* aImage, HTMLCanvasElement* aCanvas, DrawTarget* aTarget,
+ Element* aImage, CanvasRenderingContext2D* aContext, DrawTarget* aTarget,
SourceSurface* aSource, const IntSize& aSize, const IntSize& aIntrinsicSize,
const Maybe<IntRect>& aCropRect) {
- if (!aTarget) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aContext);
+
+ if (!aTarget || !aContext) {
return;
}
@@ -285,7 +306,7 @@ void CanvasImageCache::NotifyDrawImage(
BackendType backendType = aTarget->GetBackendType();
AllCanvasImageCacheKey allCanvasCacheKey(imgContainer, backendType);
- ImageCacheKey canvasCacheKey(imgContainer, aCanvas, backendType);
+ ImageCacheKey canvasCacheKey(imgContainer, aContext, backendType);
ImageCacheEntry* entry = gImageCache->mCache.PutEntry(canvasCacheKey);
if (entry) {
@@ -311,6 +332,8 @@ void CanvasImageCache::NotifyDrawImage(
SourceSurface* CanvasImageCache::LookupAllCanvas(Element* aImage,
DrawTarget* aTarget) {
+ MOZ_ASSERT(NS_IsMainThread());
+
if (!gImageCache || !aTarget) {
return nullptr;
}
@@ -329,12 +352,13 @@ SourceSurface* CanvasImageCache::LookupAllCanvas(Element* aImage,
return entry->mSourceSurface;
}
-SourceSurface* CanvasImageCache::LookupCanvas(Element* aImage,
- HTMLCanvasElement* aCanvas,
- DrawTarget* aTarget,
- IntSize* aSizeOut,
- IntSize* aIntrinsicSizeOut,
- Maybe<IntRect>* aCropRectOut) {
+SourceSurface* CanvasImageCache::LookupCanvas(
+ Element* aImage, CanvasRenderingContext2D* aContext, DrawTarget* aTarget,
+ IntSize* aSizeOut, IntSize* aIntrinsicSizeOut,
+ Maybe<IntRect>* aCropRectOut) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aContext);
+
if (!gImageCache || !aTarget) {
return nullptr;
}
@@ -351,7 +375,7 @@ SourceSurface* CanvasImageCache::LookupCanvas(Element* aImage,
// optimized surface given to a Skia backend would cause a readback. For
// details, see bug 1794442.
ImageCacheEntry* entry = gImageCache->mCache.GetEntry(
- ImageCacheKey(imgContainer, aCanvas, aTarget->GetBackendType()));
+ ImageCacheKey(imgContainer, aContext, aTarget->GetBackendType()));
if (!entry) {
return nullptr;
}
diff --git a/dom/canvas/CanvasImageCache.h b/dom/canvas/CanvasImageCache.h
index 7aecb1fbe4..f3d21dcd7e 100644
--- a/dom/canvas/CanvasImageCache.h
+++ b/dom/canvas/CanvasImageCache.h
@@ -14,7 +14,7 @@
namespace mozilla {
namespace dom {
class Element;
-class HTMLCanvasElement;
+class CanvasRenderingContext2D;
} // namespace dom
namespace gfx {
class DrawTarget;
@@ -30,19 +30,24 @@ class CanvasImageCache {
public:
/**
- * Notify that image element aImage was drawn to aCanvas element
+ * Notify that image element aImage was drawn to aContext canvas
* using the first frame of aRequest's image. The data for the surface is
* in aSurface, and the image size is in aSize. aIntrinsicSize is the size
* the surface is intended to be rendered at.
*/
static void NotifyDrawImage(dom::Element* aImage,
- dom::HTMLCanvasElement* aCanvas,
+ dom::CanvasRenderingContext2D* aContext,
gfx::DrawTarget* aTarget, SourceSurface* aSource,
const gfx::IntSize& aSize,
const gfx::IntSize& aIntrinsicSize,
const Maybe<gfx::IntRect>& aCropRect);
/**
+ * Notify that aContext is being destroyed.
+ */
+ static void NotifyCanvasDestroyed(dom::CanvasRenderingContext2D* aContext);
+
+ /**
* Check whether aImage has recently been drawn any canvas. If we return
* a non-null surface, then the same image was recently drawn into a canvas.
*/
@@ -50,11 +55,11 @@ class CanvasImageCache {
gfx::DrawTarget* aTarget);
/**
- * Like the top above, but restricts the lookup to only aCanvas. This is
+ * Like the top above, but restricts the lookup to only aContext. This is
* required for CORS security.
*/
static SourceSurface* LookupCanvas(dom::Element* aImage,
- dom::HTMLCanvasElement* aCanvas,
+ dom::CanvasRenderingContext2D* aContext,
gfx::DrawTarget* aTarget,
gfx::IntSize* aSizeOut,
gfx::IntSize* aIntrinsicSizeOut,
diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp
index 54711aa981..3b9ca585e2 100644
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -972,6 +972,7 @@ CanvasRenderingContext2D::ContextState::ContextState(const ContextState& aOther)
textRendering(aOther.textRendering),
letterSpacing(aOther.letterSpacing),
wordSpacing(aOther.wordSpacing),
+ fontLineHeight(aOther.fontLineHeight),
letterSpacingStr(aOther.letterSpacingStr),
wordSpacingStr(aOther.wordSpacingStr),
shadowColor(aOther.shadowColor),
@@ -1089,6 +1090,7 @@ CanvasRenderingContext2D::CanvasRenderingContext2D(
}
CanvasRenderingContext2D::~CanvasRenderingContext2D() {
+ CanvasImageCache::NotifyCanvasDestroyed(this);
RemovePostRefreshObserver();
RemoveShutdownObserver();
ResetBitmap();
@@ -2886,10 +2888,16 @@ void CanvasRenderingContext2D::ParseSpacing(const nsACString& aSpacing,
class CanvasUserSpaceMetrics : public UserSpaceMetricsWithSize {
public:
CanvasUserSpaceMetrics(const gfx::IntSize& aSize, const nsFont& aFont,
+ const StyleLineHeight& aLineHeight,
+ RefPtr<nsAtom> aFontLanguage,
+ bool aFontExplicitLanguage,
const ComputedStyle* aCanvasStyle,
nsPresContext* aPresContext)
: mSize(aSize),
mFont(aFont),
+ mLineHeight(aLineHeight),
+ mFontLanguage(std::move(aFontLanguage)),
+ mFontExplicitLanguage(aFontExplicitLanguage),
mCanvasStyle(aCanvasStyle),
mPresContext(aPresContext) {}
@@ -2911,6 +2919,26 @@ class CanvasUserSpaceMetrics : public UserSpaceMetricsWithSize {
return GetCSSViewportSizeFromContext(mPresContext);
}
+ float GetLineHeight(Type aType) const override {
+ // This is used if a filter is added through `url()`, and if the SVG
+ // filter being referred to is using line-height units.
+ switch (aType) {
+ case Type::This: {
+ const auto wm = GetWritingModeForType(aType);
+ const auto lh = ReflowInput::CalcLineHeightForCanvas(
+ mLineHeight, mFont, mFontLanguage, mFontExplicitLanguage,
+ mPresContext, wm);
+ return nsPresContext::AppUnitsToFloatCSSPixels(lh);
+ }
+ case Type::Root: {
+ return SVGContentUtils::GetLineHeight(
+ mPresContext->Document()->GetRootElement());
+ }
+ }
+ MOZ_ASSERT_UNREACHABLE("Was a new value added to the enumeration?");
+ return 1.0f;
+ }
+
private:
GeckoFontMetrics GetFontMetricsForType(Type aType) const override {
switch (aType) {
@@ -2946,6 +2974,9 @@ class CanvasUserSpaceMetrics : public UserSpaceMetricsWithSize {
gfx::IntSize mSize;
const nsFont& mFont;
+ StyleLineHeight mLineHeight;
+ RefPtr<nsAtom> mFontLanguage;
+ bool mFontExplicitLanguage;
RefPtr<const ComputedStyle> mCanvasStyle;
nsPresContext* mPresContext;
};
@@ -3001,8 +3032,10 @@ void CanvasRenderingContext2D::UpdateFilter(bool aFlushIfNeeded) {
CurrentState().filter = FilterInstance::GetFilterDescription(
mCanvasElement, CurrentState().filterChain.AsSpan(),
CurrentState().autoSVGFiltersObserver, writeOnly,
- CanvasUserSpaceMetrics(GetSize(), CurrentState().fontFont, canvasStyle,
- presContext),
+ CanvasUserSpaceMetrics(
+ GetSize(), CurrentState().fontFont, CurrentState().fontLineHeight,
+ CurrentState().fontLanguage, CurrentState().fontExplicitLanguage,
+ canvasStyle, presContext),
gfxRect(0, 0, mWidth, mHeight), CurrentState().filterAdditionalImages);
CurrentState().filterSourceGraphicTainted = writeOnly;
}
@@ -4043,6 +4076,7 @@ bool CanvasRenderingContext2D::SetFontInternal(const nsACString& aFont,
CurrentState().fontFont.size = fontStyle->mSize;
CurrentState().fontLanguage = fontStyle->mLanguage;
CurrentState().fontExplicitLanguage = fontStyle->mExplicitLanguage;
+ CurrentState().fontLineHeight = data.mStyle->StyleFont()->mLineHeight;
return true;
}
@@ -4251,6 +4285,8 @@ bool CanvasRenderingContext2D::SetFontInternalDisconnected(
CurrentState().fontFont.variantCaps = fontStyle.variantCaps;
CurrentState().fontLanguage = nullptr;
CurrentState().fontExplicitLanguage = false;
+ // We don't have any computed style, assume normal height.
+ CurrentState().fontLineHeight = StyleLineHeight::Normal();
return true;
}
@@ -5392,6 +5428,10 @@ MaybeGetSurfaceDescriptorForRemoteCanvas(
if (subdescType == layers::RemoteDecoderVideoSubDescriptor::Tnull_t) {
return sd;
}
+ if (subdescType == layers::RemoteDecoderVideoSubDescriptor::
+ TSurfaceDescriptorMacIOSurface) {
+ return sd;
+ }
}
return Nothing();
@@ -5502,9 +5542,8 @@ void CanvasRenderingContext2D::DrawImage(const CanvasImageSource& aImage,
element = video;
}
- srcSurf =
- CanvasImageCache::LookupCanvas(element, mCanvasElement, mTarget,
- &imgSize, &intrinsicImgSize, &cropRect);
+ srcSurf = CanvasImageCache::LookupCanvas(element, this, mTarget, &imgSize,
+ &intrinsicImgSize, &cropRect);
}
DirectDrawInfo drawInfo;
@@ -5578,9 +5617,8 @@ void CanvasRenderingContext2D::DrawImage(const CanvasImageSource& aImage,
if (srcSurf) {
if (res.mImageRequest) {
- CanvasImageCache::NotifyDrawImage(element, mCanvasElement, mTarget,
- srcSurf, imgSize, intrinsicImgSize,
- cropRect);
+ CanvasImageCache::NotifyDrawImage(element, this, mTarget, srcSurf,
+ imgSize, intrinsicImgSize, cropRect);
}
} else {
drawInfo = res.mDrawInfo;
diff --git a/dom/canvas/CanvasRenderingContext2D.h b/dom/canvas/CanvasRenderingContext2D.h
index bfcbfccec2..3b25a1235e 100644
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -1051,6 +1051,8 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal,
gfx::Float letterSpacing = 0.0f;
gfx::Float wordSpacing = 0.0f;
+ mozilla::StyleLineHeight fontLineHeight =
+ mozilla::StyleLineHeight::Normal();
nsCString letterSpacingStr;
nsCString wordSpacingStr;
diff --git a/dom/canvas/ClientWebGLContext.cpp b/dom/canvas/ClientWebGLContext.cpp
index 236ea916d0..5c92ba003d 100644
--- a/dom/canvas/ClientWebGLContext.cpp
+++ b/dom/canvas/ClientWebGLContext.cpp
@@ -736,6 +736,21 @@ void ClientWebGLContext::GetCanvas(
}
}
+void ClientWebGLContext::SetDrawingBufferColorSpace(
+ const dom::PredefinedColorSpace val) {
+ mDrawingBufferColorSpace = val;
+
+ // Just in case, update in Options too.
+ // Why not treat our WebGLContextOptions as the source of truth? Well,
+ // mNotLost is lost on context-loss, so we'd lose any setting we had here if
+ // that happens.
+ if (mNotLost) {
+ mNotLost->info.options.colorSpace = mDrawingBufferColorSpace;
+ }
+
+ Run<RPROC(SetDrawingBufferColorSpace)>(mDrawingBufferColorSpace);
+}
+
void ClientWebGLContext::GetContextAttributes(
dom::Nullable<dom::WebGLContextAttributes>& retval) {
retval.SetNull();
@@ -1058,9 +1073,7 @@ ClientWebGLContext::SetContextOptions(JSContext* cx,
if (attributes.mAntialias.WasPassed()) {
newOpts.antialias = attributes.mAntialias.Value();
}
- newOpts.ignoreColorSpace = true;
if (attributes.mColorSpace.WasPassed()) {
- newOpts.ignoreColorSpace = false;
newOpts.colorSpace = attributes.mColorSpace.Value();
}
diff --git a/dom/canvas/ClientWebGLContext.h b/dom/canvas/ClientWebGLContext.h
index e736235361..e870d5860a 100644
--- a/dom/canvas/ClientWebGLContext.h
+++ b/dom/canvas/ClientWebGLContext.h
@@ -1052,6 +1052,20 @@ class ClientWebGLContext final : public nsICanvasRenderingContextInternal,
const FuncScope funcScope(*this, "drawingBufferHeight");
return AutoAssertCast(DrawingBufferSize().y);
}
+
+ // -
+
+ private:
+ dom::PredefinedColorSpace mDrawingBufferColorSpace =
+ dom::PredefinedColorSpace::Srgb;
+
+ public:
+ auto DrawingBufferColorSpace() const { return mDrawingBufferColorSpace; }
+
+ void SetDrawingBufferColorSpace(dom::PredefinedColorSpace);
+
+ // -
+
void GetContextAttributes(dom::Nullable<dom::WebGLContextAttributes>& retval);
private:
diff --git a/dom/canvas/HostWebGLContext.h b/dom/canvas/HostWebGLContext.h
index 23b836f9db..7d8fe29cd8 100644
--- a/dom/canvas/HostWebGLContext.h
+++ b/dom/canvas/HostWebGLContext.h
@@ -201,6 +201,9 @@ class HostWebGLContext final : public SupportsWeakPtr {
// -
+ void SetDrawingBufferColorSpace(const dom::PredefinedColorSpace val) const {
+ mContext->SetDrawingBufferColorSpace(val);
+ }
void Resize(const uvec2& size) { return mContext->Resize(size); }
uvec2 DrawingBufferSize() { return mContext->DrawingBufferSize(); }
diff --git a/dom/canvas/ImageBitmap.cpp b/dom/canvas/ImageBitmap.cpp
index 28641a1c68..6c479be978 100644
--- a/dom/canvas/ImageBitmap.cpp
+++ b/dom/canvas/ImageBitmap.cpp
@@ -61,7 +61,8 @@ static ImageBitmapShutdownObserver* sShutdownObserver = nullptr;
class SendShutdownToWorkerThread : public MainThreadWorkerControlRunnable {
public:
explicit SendShutdownToWorkerThread(ImageBitmap* aImageBitmap)
- : MainThreadWorkerControlRunnable(GetCurrentThreadWorkerPrivate()),
+ : MainThreadWorkerControlRunnable("SendShutdownToWorkerThread"),
+ mWorkerPrivate(GetCurrentThreadWorkerPrivate()),
mImageBitmap(aImageBitmap) {
MOZ_ASSERT(GetCurrentThreadWorkerPrivate());
}
@@ -74,6 +75,7 @@ class SendShutdownToWorkerThread : public MainThreadWorkerControlRunnable {
return true;
}
+ WorkerPrivate* mWorkerPrivate;
ImageBitmap* mImageBitmap;
};
@@ -146,7 +148,7 @@ ImageBitmapShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic,
for (const auto& bitmap : mBitmaps) {
const auto& runnable = bitmap->mShutdownRunnable;
if (runnable) {
- runnable->Dispatch();
+ runnable->Dispatch(runnable->mWorkerPrivate);
} else {
bitmap->OnShutdown();
}
@@ -1578,8 +1580,7 @@ class FulfillImageBitmapPromiseWorkerTask final
public:
FulfillImageBitmapPromiseWorkerTask(Promise* aPromise,
ImageBitmap* aImageBitmap)
- : WorkerSameThreadRunnable(GetCurrentThreadWorkerPrivate(),
- "FulfillImageBitmapPromiseWorkerTask"),
+ : WorkerSameThreadRunnable("FulfillImageBitmapPromiseWorkerTask"),
FulfillImageBitmapPromise(aPromise, aImageBitmap) {}
bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
@@ -1597,7 +1598,8 @@ static void AsyncFulfillImageBitmapPromise(Promise* aPromise,
} else {
RefPtr<FulfillImageBitmapPromiseWorkerTask> task =
new FulfillImageBitmapPromiseWorkerTask(aPromise, aImageBitmap);
- task->Dispatch(); // Actually, to the current worker-thread.
+ task->Dispatch(GetCurrentThreadWorkerPrivate()); // Actually, to the
+ // current worker-thread.
}
}
@@ -1700,13 +1702,13 @@ class CreateImageBitmapFromBlob final : public DiscardableRunnable,
NS_IMPL_ISUPPORTS_INHERITED(CreateImageBitmapFromBlob, DiscardableRunnable,
imgIContainerCallback, nsIInputStreamCallback)
-class CreateImageBitmapFromBlobRunnable final : public WorkerRunnable {
+class CreateImageBitmapFromBlobRunnable final : public WorkerThreadRunnable {
public:
explicit CreateImageBitmapFromBlobRunnable(WorkerPrivate* aWorkerPrivate,
CreateImageBitmapFromBlob* aTask,
layers::Image* aImage,
nsresult aStatus)
- : WorkerRunnable(aWorkerPrivate, "CreateImageBitmapFromBlobRunnable"),
+ : WorkerThreadRunnable("CreateImageBitmapFromBlobRunnable"),
mTask(aTask),
mImage(aImage),
mStatus(aStatus) {}
@@ -2287,7 +2289,7 @@ void CreateImageBitmapFromBlob::MimeTypeAndDecodeAndCropBlobCompletedMainThread(
RefPtr<CreateImageBitmapFromBlobRunnable> r =
new CreateImageBitmapFromBlobRunnable(mWorkerRef->Private(), this,
aImage, aStatus);
- r->Dispatch();
+ r->Dispatch(mWorkerRef->Private());
return;
}
diff --git a/dom/canvas/OffscreenCanvas.cpp b/dom/canvas/OffscreenCanvas.cpp
index 208d78daec..1cdccf55ff 100644
--- a/dom/canvas/OffscreenCanvas.cpp
+++ b/dom/canvas/OffscreenCanvas.cpp
@@ -358,6 +358,21 @@ UniquePtr<OffscreenCanvasCloneData> OffscreenCanvas::ToCloneData(
return nullptr;
}
+ // Check if we are using HTMLCanvasElement::captureStream. This is not
+ // defined by the spec yet, so it is better to fail now than implement
+ // something not compliant:
+ // https://github.com/w3c/mediacapture-fromelement/issues/65
+ // https://github.com/w3c/mediacapture-extensions/pull/26
+ // https://github.com/web-platform-tests/wpt/issues/21102
+ if (mDisplay && NS_WARN_IF(mDisplay->UsingElementCaptureStream())) {
+ ErrorResult rv;
+ rv.ThrowNotSupportedError(
+ "Cannot transfer OffscreenCanvas bound to element using "
+ "captureStream.");
+ MOZ_ALWAYS_TRUE(rv.MaybeSetPendingException(aCx));
+ return nullptr;
+ }
+
auto cloneData = MakeUnique<OffscreenCanvasCloneData>(
mDisplay, mWidth, mHeight, mCompositorBackendType, mTextureType,
mNeutered, mIsWriteOnly, mExpandedReader);
diff --git a/dom/canvas/OffscreenCanvasDisplayHelper.cpp b/dom/canvas/OffscreenCanvasDisplayHelper.cpp
index 9e1cbb3e75..972fa468d2 100644
--- a/dom/canvas/OffscreenCanvasDisplayHelper.cpp
+++ b/dom/canvas/OffscreenCanvasDisplayHelper.cpp
@@ -61,6 +61,22 @@ void OffscreenCanvasDisplayHelper::DestroyCanvas() {
mWorkerRef = nullptr;
}
+bool OffscreenCanvasDisplayHelper::CanElementCaptureStream() const {
+ MutexAutoLock lock(mMutex);
+ return !!mWorkerRef;
+}
+
+bool OffscreenCanvasDisplayHelper::UsingElementCaptureStream() const {
+ MutexAutoLock lock(mMutex);
+
+ if (NS_WARN_IF(!NS_IsMainThread())) {
+ MOZ_ASSERT_UNREACHABLE("Should not call off main-thread!");
+ return !!mCanvasElement;
+ }
+
+ return mCanvasElement && mCanvasElement->UsingCaptureStream();
+}
+
CanvasContextType OffscreenCanvasDisplayHelper::GetContextType() const {
MutexAutoLock lock(mMutex);
return mType;
@@ -116,11 +132,11 @@ void OffscreenCanvasDisplayHelper::FlushForDisplay() {
return;
}
- class FlushWorkerRunnable final : public WorkerRunnable {
+ class FlushWorkerRunnable final : public WorkerThreadRunnable {
public:
FlushWorkerRunnable(WorkerPrivate* aWorkerPrivate,
OffscreenCanvasDisplayHelper* aDisplayHelper)
- : WorkerRunnable(aWorkerPrivate, "FlushWorkerRunnable"),
+ : WorkerThreadRunnable("FlushWorkerRunnable"),
mDisplayHelper(aDisplayHelper) {}
bool WorkerRun(JSContext*, WorkerPrivate*) override {
@@ -148,7 +164,7 @@ void OffscreenCanvasDisplayHelper::FlushForDisplay() {
// Otherwise we are calling from the main thread during painting to a canvas
// on a worker thread.
auto task = MakeRefPtr<FlushWorkerRunnable>(mWorkerRef->Private(), this);
- task->Dispatch();
+ task->Dispatch(mWorkerRef->Private());
}
bool OffscreenCanvasDisplayHelper::CommitFrameToCompositor(
@@ -418,7 +434,7 @@ OffscreenCanvasDisplayHelper::GetSurfaceSnapshot() {
public:
SnapshotWorkerRunnable(WorkerPrivate* aWorkerPrivate,
OffscreenCanvasDisplayHelper* aDisplayHelper)
- : MainThreadWorkerRunnable(aWorkerPrivate, "SnapshotWorkerRunnable"),
+ : MainThreadWorkerRunnable("SnapshotWorkerRunnable"),
mMonitor("SnapshotWorkerRunnable::mMonitor"),
mDisplayHelper(aDisplayHelper) {}
@@ -498,7 +514,7 @@ OffscreenCanvasDisplayHelper::GetSurfaceSnapshot() {
if (mWorkerRef) {
workerRunnable =
MakeRefPtr<SnapshotWorkerRunnable>(mWorkerRef->Private(), this);
- workerRunnable->Dispatch();
+ workerRunnable->Dispatch(mWorkerRef->Private());
}
}
diff --git a/dom/canvas/OffscreenCanvasDisplayHelper.h b/dom/canvas/OffscreenCanvasDisplayHelper.h
index 502d23066a..ae6d23821c 100644
--- a/dom/canvas/OffscreenCanvasDisplayHelper.h
+++ b/dom/canvas/OffscreenCanvasDisplayHelper.h
@@ -57,6 +57,9 @@ class OffscreenCanvasDisplayHelper final {
void DestroyCanvas();
void DestroyElement();
+ bool CanElementCaptureStream() const;
+ bool UsingElementCaptureStream() const;
+
already_AddRefed<mozilla::gfx::SourceSurface> GetSurfaceSnapshot();
already_AddRefed<mozilla::layers::Image> GetAsImage();
UniquePtr<uint8_t[]> GetImageBuffer(int32_t* aOutFormat,
diff --git a/dom/canvas/WebGLContext.cpp b/dom/canvas/WebGLContext.cpp
index 4bd189c46c..669e131bd4 100644
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -894,63 +894,62 @@ void WebGLContext::BlitBackbufferToCurDriverFB(
if (mScissorTestEnabled) {
gl->fDisable(LOCAL_GL_SCISSOR_TEST);
}
-
- [&]() {
- // If a MozFramebuffer is supplied, ensure that a WebGLFramebuffer is not
- // used since it might not have completeness info, while the MozFramebuffer
- // can still supply the needed information.
- MOZ_ASSERT(!(srcAsMozFb && srcAsWebglFb));
- const auto* mozFb = srcAsMozFb ? srcAsMozFb : mDefaultFB.get();
- GLuint fbo = 0;
- gfx::IntSize size;
- if (srcAsWebglFb) {
- fbo = srcAsWebglFb->mGLName;
- const auto* info = srcAsWebglFb->GetCompletenessInfo();
- MOZ_ASSERT(info);
- size = gfx::IntSize(info->width, info->height);
- } else {
- fbo = mozFb->mFB;
- size = mozFb->mSize;
- }
-
- // If no format conversion is necessary, then attempt to directly blit
- // between framebuffers. Otherwise, if we need to convert to RGBA from
- // the source format, then we will need to use the texture blit path
- // below.
- if (!srcIsBGRA) {
- if (gl->IsSupported(gl::GLFeature::framebuffer_blit)) {
- gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fbo);
- gl->fBlitFramebuffer(0, 0, size.width, size.height, 0, 0, size.width,
- size.height, LOCAL_GL_COLOR_BUFFER_BIT,
- LOCAL_GL_NEAREST);
- return;
- }
- if (mDefaultFB->mSamples &&
- gl->IsExtensionSupported(
- gl::GLContext::APPLE_framebuffer_multisample)) {
- gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fbo);
- gl->fResolveMultisampleFramebufferAPPLE();
- return;
- }
+ const auto cleanup = MakeScopeExit([&]() {
+ if (mScissorTestEnabled) {
+ gl->fEnable(LOCAL_GL_SCISSOR_TEST);
}
+ });
- GLuint colorTex = 0;
- if (srcAsWebglFb) {
- const auto& attach = srcAsWebglFb->ColorAttachment0();
- MOZ_ASSERT(attach.Texture());
- colorTex = attach.Texture()->mGLName;
- } else {
- colorTex = mozFb->ColorTex();
+ // If a MozFramebuffer is supplied, ensure that a WebGLFramebuffer is not
+ // used since it might not have completeness info, while the MozFramebuffer
+ // can still supply the needed information.
+ MOZ_ASSERT(!(srcAsMozFb && srcAsWebglFb));
+ const auto* mozFb = srcAsMozFb ? srcAsMozFb : mDefaultFB.get();
+ GLuint fbo = 0;
+ gfx::IntSize size;
+ if (srcAsWebglFb) {
+ fbo = srcAsWebglFb->mGLName;
+ const auto* info = srcAsWebglFb->GetCompletenessInfo();
+ MOZ_ASSERT(info);
+ size = gfx::IntSize(info->width, info->height);
+ } else {
+ fbo = mozFb->mFB;
+ size = mozFb->mSize;
+ }
+
+ // If no format conversion is necessary, then attempt to directly blit
+ // between framebuffers. Otherwise, if we need to convert to RGBA from
+ // the source format, then we will need to use the texture blit path
+ // below.
+ if (!srcIsBGRA) {
+ if (gl->IsSupported(gl::GLFeature::framebuffer_blit)) {
+ gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fbo);
+ gl->fBlitFramebuffer(0, 0, size.width, size.height, 0, 0, size.width,
+ size.height, LOCAL_GL_COLOR_BUFFER_BIT,
+ LOCAL_GL_NEAREST);
+ return;
}
+ if (mDefaultFB->mSamples &&
+ gl->IsExtensionSupported(
+ gl::GLContext::APPLE_framebuffer_multisample)) {
+ gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fbo);
+ gl->fResolveMultisampleFramebufferAPPLE();
+ return;
+ }
+ }
- // DrawBlit handles ColorMask itself.
- gl->BlitHelper()->DrawBlitTextureToFramebuffer(
- colorTex, size, size, LOCAL_GL_TEXTURE_2D, srcIsBGRA);
- }();
-
- if (mScissorTestEnabled) {
- gl->fEnable(LOCAL_GL_SCISSOR_TEST);
+ GLuint colorTex = 0;
+ if (srcAsWebglFb) {
+ const auto& attach = srcAsWebglFb->ColorAttachment0();
+ MOZ_ASSERT(attach.Texture());
+ colorTex = attach.Texture()->mGLName;
+ } else {
+ colorTex = mozFb->ColorTex();
}
+
+ // DrawBlit handles ColorMask itself.
+ gl->BlitHelper()->DrawBlitTextureToFramebuffer(
+ colorTex, size, size, LOCAL_GL_TEXTURE_2D, srcIsBGRA);
}
// -
@@ -960,19 +959,34 @@ constexpr auto MakeArray(Args... args) -> std::array<T, sizeof...(Args)> {
return {{static_cast<T>(args)...}};
}
-inline gfx::ColorSpace2 ToColorSpace2(const WebGLContextOptions& options) {
- auto ret = gfx::ColorSpace2::UNKNOWN;
- if (true) {
- ret = gfx::ColorSpace2::SRGB;
- }
- if (!options.ignoreColorSpace) {
- ret = gfx::ToColorSpace2(options.colorSpace);
+inline gfx::ColorSpace2 ToColorSpace2ForOutput(
+ const std::optional<dom::PredefinedColorSpace> chosenCspace) {
+ const auto cmsMode = GfxColorManagementMode();
+ switch (cmsMode) {
+ case CMSMode::Off:
+ return gfx::ColorSpace2::Display;
+ case CMSMode::TaggedOnly:
+ if (!chosenCspace) {
+ return gfx::ColorSpace2::Display;
+ }
+ break;
+ case CMSMode::All:
+ if (!chosenCspace) {
+ return gfx::ColorSpace2::SRGB;
+ }
+ break;
}
- return ret;
+ return gfx::ToColorSpace2(*chosenCspace);
}
// -
+template <class T>
+GLuint GLNameOrZero(const T& t) {
+ if (t) return t->mGLName;
+ return 0;
+}
+
// For an overview of how WebGL compositing works, see:
// https://wiki.mozilla.org/Platform/GFX/WebGL/Compositing
bool WebGLContext::PresentInto(gl::SwapChain& swapChain) {
@@ -980,46 +994,100 @@ bool WebGLContext::PresentInto(gl::SwapChain& swapChain) {
if (!ValidateAndInitFB(nullptr)) return false;
- {
- const auto colorSpace = ToColorSpace2(mOptions);
- auto presenter = swapChain.Acquire(mDefaultFB->mSize, colorSpace);
+ const auto size = mDefaultFB->mSize;
+
+ const auto error = [&]() -> std::optional<std::string> {
+ const auto canvasCspace = ToColorSpace2ForOutput(mOptions.colorSpace);
+ auto presenter = swapChain.Acquire(size, canvasCspace);
if (!presenter) {
- GenerateWarning("Swap chain surface creation failed.");
- LoseContext();
- return false;
+ return "Swap chain surface creation failed.";
}
-
+ const auto outputCspace = presenter->BackBuffer()->mDesc.colorSpace;
const auto destFb = presenter->Fb();
- gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, destFb);
- BlitBackbufferToCurDriverFB();
+ // -
- if (!mOptions.preserveDrawingBuffer) {
- if (gl->IsSupported(gl::GLFeature::invalidate_framebuffer)) {
- gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, mDefaultFB->mFB);
- constexpr auto attachments = MakeArray<GLenum>(
- LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT);
- gl->fInvalidateFramebuffer(LOCAL_GL_READ_FRAMEBUFFER,
- attachments.size(), attachments.data());
- }
- mDefaultFB_IsInvalid = true;
+ bool colorManage = (canvasCspace != gfx::ColorSpace2::Display);
+ if (canvasCspace == outputCspace) {
+ colorManage = false;
+ }
+ if (!gl->IsSupported(gl::GLFeature::texture_3D)) {
+ NS_WARNING("Missing GLFeature::texture_3D => colorManage = false.");
+ colorManage = false;
}
-#ifdef DEBUG
- if (!mOptions.alpha) {
- gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, destFb);
- gl->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 4);
- if (IsWebGL2()) {
- gl->fPixelStorei(LOCAL_GL_PACK_ROW_LENGTH, 0);
- gl->fPixelStorei(LOCAL_GL_PACK_SKIP_PIXELS, 0);
- gl->fPixelStorei(LOCAL_GL_PACK_SKIP_ROWS, 0);
+ auto colorLut = std::shared_ptr<gl::Texture>{};
+ if (colorManage) {
+ MOZ_ASSERT(canvasCspace != gfx::ColorSpace2::Display);
+ colorLut = gl->BlitHelper()->GetColorLutTex(gl::GLBlitHelper::ColorLutKey{
+ .src = canvasCspace, .dst = outputCspace});
+ if (!colorLut) {
+ NS_WARNING("GetColorLutTex() -> nullptr => colorManage = false.");
+ colorManage = false;
}
- uint32_t pixel = 0xffbadbad;
- gl->fReadPixels(0, 0, 1, 1, LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE,
- &pixel);
- MOZ_ASSERT((pixel & 0xff000000) == 0xff000000);
}
-#endif
+
+ if (!colorManage) {
+ gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, destFb);
+ BlitBackbufferToCurDriverFB();
+ return {};
+ }
+
+ // -
+
+ const auto canvasFb = GetDefaultFBForRead({.endOfFrame = true});
+ if (!canvasFb) {
+ return "[WebGLContext::PresentInto] BindDefaultFBForRead failed.";
+ }
+
+ const auto& blitter = gl->BlitHelper()->GetDrawBlitProg({
+ .fragHeader = gl::kFragHeader_Tex2D,
+ .fragParts = {gl::kFragSample_OnePlane, gl::kFragConvert_ColorLut3d},
+ });
+
+ constexpr uint8_t texUnit_src = 0;
+ constexpr uint8_t texUnit_lut = 1;
+ gl->BindSamplerTexture(texUnit_src, SamplerLinear(), LOCAL_GL_TEXTURE_2D,
+ canvasFb->ColorTex());
+ gl->BindSamplerTexture(texUnit_lut, SamplerLinear(), LOCAL_GL_TEXTURE_3D,
+ colorLut->name);
+ const auto texCleanup = MakeScopeExit([&]() {
+ gl->BindSamplerTexture(
+ texUnit_src, GLNameOrZero(mBoundSamplers[texUnit_src]),
+ LOCAL_GL_TEXTURE_2D, GLNameOrZero(mBound2DTextures[texUnit_src]));
+ gl->BindSamplerTexture(
+ texUnit_lut, GLNameOrZero(mBoundSamplers[texUnit_lut]),
+ LOCAL_GL_TEXTURE_3D, GLNameOrZero(mBound3DTextures[texUnit_lut]));
+ gl->fActiveTexture(LOCAL_GL_TEXTURE0 + mActiveTexture);
+ });
+
+ gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, destFb);
+
+ gl->fUseProgram(blitter.mProg);
+ const auto cleanupProg = MakeScopeExit(
+ [&]() { gl->fUseProgram(GLNameOrZero(mCurrentProgram)); });
+
+ gl->fUniform1i(blitter.mLoc_uColorLut, texUnit_lut);
+
+ blitter.Draw({
+ .texMatrix0 = gl::Mat3::I(),
+ .yFlip = false,
+ .destSize = size,
+ .destRect = {},
+ });
+
+ gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, canvasFb->mFB);
+ return {};
+ }();
+ if (error) {
+ GenerateWarning("%s", error->c_str());
+ LoseContext();
+ return false;
+ }
+
+ if (!mOptions.preserveDrawingBuffer) {
+ gl->InvalidateFramebuffer(LOCAL_GL_READ_FRAMEBUFFER);
+ mDefaultFB_IsInvalid = true;
}
return true;
@@ -1029,7 +1097,7 @@ bool WebGLContext::PresentIntoXR(gl::SwapChain& swapChain,
const gl::MozFramebuffer& fb) {
OnEndOfFrame();
- const auto colorSpace = ToColorSpace2(mOptions);
+ const auto colorSpace = ToColorSpace2ForOutput(mOptions.colorSpace);
auto presenter = swapChain.Acquire(fb.mSize, colorSpace);
if (!presenter) {
GenerateWarning("Swap chain surface creation failed.");
@@ -1058,7 +1126,7 @@ bool WebGLContext::PresentIntoXR(gl::SwapChain& swapChain,
// Initialize a swap chain's surface factory given the desired surface type.
void InitSwapChain(gl::GLContext& gl, gl::SwapChain& swapChain,
- const layers::TextureType consumerType) {
+ const layers::TextureType consumerType, bool useAsync) {
if (!swapChain.mFactory) {
auto typedFactory = gl::SurfaceFactory::Create(&gl, consumerType);
if (typedFactory) {
@@ -1070,6 +1138,11 @@ void InitSwapChain(gl::GLContext& gl, gl::SwapChain& swapChain,
swapChain.mFactory = MakeUnique<gl::SurfaceFactory_Basic>(gl);
}
MOZ_ASSERT(swapChain.mFactory);
+ if (useAsync) {
+ // RemoteTextureMap will handle recycling any surfaces, so don't rely on the
+ // SwapChain's internal pooling.
+ swapChain.DisablePool();
+ }
}
void WebGLContext::Present(WebGLFramebuffer* const xrFb,
@@ -1090,7 +1163,10 @@ void WebGLContext::Present(WebGLFramebuffer* const xrFb,
mResolvedDefaultFB = nullptr;
}
- InitSwapChain(*gl, *swapChain, consumerType);
+ bool useAsync = options.remoteTextureOwnerId.IsValid() &&
+ options.remoteTextureId.IsValid();
+
+ InitSwapChain(*gl, *swapChain, consumerType, useAsync);
bool valid =
maybeFB ? PresentIntoXR(*swapChain, *maybeFB) : PresentInto(*swapChain);
@@ -1099,8 +1175,6 @@ void WebGLContext::Present(WebGLFramebuffer* const xrFb,
return;
}
- bool useAsync = options.remoteTextureOwnerId.IsValid() &&
- options.remoteTextureId.IsValid();
if (useAsync) {
PushRemoteTexture(nullptr, *swapChain, swapChain->FrontBuffer(), options);
}
@@ -1137,10 +1211,11 @@ bool WebGLContext::CopyToSwapChain(
}
gfx::IntSize size(info->width, info->height);
- InitSwapChain(*gl, srcFb->mSwapChain, consumerType);
-
bool useAsync = options.remoteTextureOwnerId.IsValid() &&
options.remoteTextureId.IsValid();
+
+ InitSwapChain(*gl, srcFb->mSwapChain, consumerType, useAsync);
+
// If we're using async present and if there is no way to serialize surfaces,
// then a readback is required to do the copy. In this case, there's no reason
// to copy into a separate shared surface for the front buffer. Just directly
@@ -1153,7 +1228,7 @@ bool WebGLContext::CopyToSwapChain(
{
// ColorSpace will need to be part of SwapChainOptions for DTWebgl.
- const auto colorSpace = ToColorSpace2(mOptions);
+ const auto colorSpace = ToColorSpace2ForOutput(mOptions.colorSpace);
auto presenter = srcFb->mSwapChain.Acquire(size, colorSpace);
if (!presenter) {
GenerateWarning("Swap chain surface creation failed.");
@@ -1697,12 +1772,12 @@ bool WebGLContext::BindCurFBForColorRead(
return true;
}
-bool WebGLContext::BindDefaultFBForRead() {
- if (!ValidateAndInitFB(nullptr)) return false;
+const gl::MozFramebuffer* WebGLContext::GetDefaultFBForRead(
+ const GetDefaultFBForReadDesc& desc) {
+ if (!ValidateAndInitFB(nullptr)) return nullptr;
if (!mDefaultFB->mSamples) {
- gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mDefaultFB->mFB);
- return true;
+ return mDefaultFB.get();
}
if (!mResolvedDefaultFB) {
@@ -1710,14 +1785,24 @@ bool WebGLContext::BindDefaultFBForRead() {
gl::MozFramebuffer::Create(gl, mDefaultFB->mSize, 0, false);
if (!mResolvedDefaultFB) {
gfxCriticalNote << FuncName() << ": Failed to create mResolvedDefaultFB.";
- return false;
+ return nullptr;
}
}
- gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mResolvedDefaultFB->mFB);
+ gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, mResolvedDefaultFB->mFB);
BlitBackbufferToCurDriverFB();
- gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mResolvedDefaultFB->mFB);
+ if (desc.endOfFrame && !mOptions.preserveDrawingBuffer) {
+ gl->InvalidateFramebuffer(LOCAL_GL_READ_FRAMEBUFFER);
+ }
+
+ return mResolvedDefaultFB.get();
+}
+
+bool WebGLContext::BindDefaultFBForRead() {
+ const auto fb = GetDefaultFBForRead();
+ if (!fb) return false;
+ gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fb->mFB);
return true;
}
@@ -2351,7 +2436,7 @@ webgl::LinkActiveInfo GetLinkActiveInfo(
ret.activeUniforms.push_back(std::move(info));
} // for i
- } // anon
+ } // anon
if (webgl2) {
// -------------------------------------
@@ -2397,7 +2482,7 @@ webgl::LinkActiveInfo GetLinkActiveInfo(
ret.activeUniformBlocks.push_back(std::move(info));
} // for i
- } // anon
+ } // anon
// -------------------------------------
// active tf varyings
@@ -2661,4 +2746,21 @@ webgl::ExplicitPixelPackingState::ForUseWith(
return {{state, metrics}};
}
+GLuint WebGLContext::SamplerLinear() const {
+ if (!mSamplerLinear) {
+ mSamplerLinear = std::make_unique<gl::Sampler>(*gl);
+ gl->fSamplerParameteri(mSamplerLinear->name, LOCAL_GL_TEXTURE_MAG_FILTER,
+ LOCAL_GL_LINEAR);
+ gl->fSamplerParameteri(mSamplerLinear->name, LOCAL_GL_TEXTURE_MIN_FILTER,
+ LOCAL_GL_LINEAR);
+ gl->fSamplerParameteri(mSamplerLinear->name, LOCAL_GL_TEXTURE_WRAP_S,
+ LOCAL_GL_CLAMP_TO_EDGE);
+ gl->fSamplerParameteri(mSamplerLinear->name, LOCAL_GL_TEXTURE_WRAP_T,
+ LOCAL_GL_CLAMP_TO_EDGE);
+ gl->fSamplerParameteri(mSamplerLinear->name, LOCAL_GL_TEXTURE_WRAP_R,
+ LOCAL_GL_CLAMP_TO_EDGE);
+ }
+ return mSamplerLinear->name;
+}
+
} // namespace mozilla
diff --git a/dom/canvas/WebGLContext.h b/dom/canvas/WebGLContext.h
index d144584f4f..69cff4e7dd 100644
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -10,6 +10,7 @@
#include <memory>
#include <stdarg.h>
+#include "Colorspaces.h"
#include "GLContextTypes.h"
#include "GLDefs.h"
#include "GLScreenBuffer.h"
@@ -96,6 +97,7 @@ namespace gl {
class GLScreenBuffer;
class MozFramebuffer;
class SharedSurface;
+class Sampler;
class Texture;
} // namespace gl
@@ -536,6 +538,12 @@ class WebGLContext : public VRefCounted, public SupportsWeakPtr {
Maybe<layers::SurfaceDescriptor> GetFrontBuffer(WebGLFramebuffer*,
const bool webvr);
+ std::optional<color::ColorProfileDesc> mDisplayProfile;
+
+ void SetDrawingBufferColorSpace(const dom::PredefinedColorSpace val) {
+ mOptions.colorSpace = val;
+ }
+
void ClearVRSwapChain();
void RunContextLossTimer();
@@ -1156,6 +1164,10 @@ class WebGLContext : public VRefCounted, public SupportsWeakPtr {
nsTArray<RefPtr<WebGLTexture>> mBound2DArrayTextures;
nsTArray<RefPtr<WebGLSampler>> mBoundSamplers;
+ mutable std::unique_ptr<gl::Sampler> mSamplerLinear;
+
+ GLuint SamplerLinear() const;
+
void ResolveTexturesForDraw() const;
RefPtr<WebGLProgram> mCurrentProgram;
@@ -1265,6 +1277,10 @@ class WebGLContext : public VRefCounted, public SupportsWeakPtr {
mutable bool mDefaultFB_IsInvalid = false;
mutable UniquePtr<gl::MozFramebuffer> mResolvedDefaultFB;
+ mutable std::unordered_map<std::tuple<gfx::ColorSpace2, gfx::ColorSpace2>,
+ std::shared_ptr<gl::Texture>>
+ mLutTexByColorMapping;
+
gl::SwapChain mSwapChain;
gl::SwapChain mWebVRSwapChain;
@@ -1303,6 +1319,15 @@ class WebGLContext : public VRefCounted, public SupportsWeakPtr {
WebGLFramebuffer* const srcAsWebglFb = nullptr,
const gl::MozFramebuffer* const srcAsMozFb = nullptr,
bool srcIsBGRA = false) const;
+
+ struct GetDefaultFBForReadDesc {
+ bool endOfFrame = false;
+ };
+ const gl::MozFramebuffer* GetDefaultFBForRead(const GetDefaultFBForReadDesc&);
+ const gl::MozFramebuffer* GetDefaultFBForRead() {
+ return GetDefaultFBForRead({});
+ }
+
bool BindDefaultFBForRead();
// --
diff --git a/dom/canvas/WebGLContextTextures.cpp b/dom/canvas/WebGLContextTextures.cpp
index e65533ec94..2bc56f4b05 100644
--- a/dom/canvas/WebGLContextTextures.cpp
+++ b/dom/canvas/WebGLContextTextures.cpp
@@ -96,7 +96,7 @@ void WebGLContext::BindTexture(GLenum rawTarget, WebGLTexture* newTex) {
return;
}
- const TexTarget texTarget(rawTarget);
+ const auto texTarget = TexTarget(rawTarget);
if (newTex) {
if (!newTex->BindTexture(texTarget)) return;
} else {
diff --git a/dom/canvas/WebGLIpdl.h b/dom/canvas/WebGLIpdl.h
index 45a5c5ad64..4c81393b70 100644
--- a/dom/canvas/WebGLIpdl.h
+++ b/dom/canvas/WebGLIpdl.h
@@ -18,6 +18,8 @@
#include "TupleUtils.h"
#include "WebGLTypes.h"
+#include <memory>
+
namespace mozilla {
namespace webgl {
@@ -652,6 +654,32 @@ struct ParamTraits<mozilla::avec3<U>> final {
}
};
+// -
+
+template <class U>
+struct ParamTraits<std::optional<U>> final {
+ using T = std::optional<U>;
+
+ static void Write(MessageWriter* const writer, const T& in) {
+ WriteParam(writer, bool{in});
+ if (in) {
+ WriteParam(writer, *in);
+ }
+ }
+
+ static bool Read(MessageReader* const reader, T* const out) {
+ bool isSome;
+ if (!ReadParam(reader, &isSome)) return false;
+
+ if (!isSome) {
+ out->reset();
+ return true;
+ }
+ out->emplace();
+ return ReadParam(reader, &**out);
+ }
+};
+
} // namespace IPC
#endif
diff --git a/dom/canvas/WebGLMethodDispatcher.h b/dom/canvas/WebGLMethodDispatcher.h
index 4bf67deb08..e55b19d0b4 100644
--- a/dom/canvas/WebGLMethodDispatcher.h
+++ b/dom/canvas/WebGLMethodDispatcher.h
@@ -93,6 +93,7 @@ DEFINE_ASYNC(HostWebGLContext::ProvokingVertex)
DEFINE_ASYNC(HostWebGLContext::Present)
DEFINE_ASYNC(HostWebGLContext::SampleCoverage)
DEFINE_ASYNC(HostWebGLContext::Scissor)
+DEFINE_ASYNC(HostWebGLContext::SetDrawingBufferColorSpace)
DEFINE_ASYNC(HostWebGLContext::ShaderSource)
DEFINE_ASYNC(HostWebGLContext::StencilFuncSeparate)
DEFINE_ASYNC(HostWebGLContext::StencilMaskSeparate)
diff --git a/dom/canvas/WebGLQueueParamTraits.h b/dom/canvas/WebGLQueueParamTraits.h
index 3c74f08750..136316433f 100644
--- a/dom/canvas/WebGLQueueParamTraits.h
+++ b/dom/canvas/WebGLQueueParamTraits.h
@@ -61,25 +61,6 @@ struct QueueParamTraits<avec3<T>> : QueueParamTraits_TiedFields<avec3<T>> {};
// ---------------------------------------------------------------------
// Enums!
-inline constexpr bool IsEnumCase(const dom::WebGLPowerPreference raw) {
- switch (raw) {
- case dom::WebGLPowerPreference::Default:
- case dom::WebGLPowerPreference::Low_power:
- case dom::WebGLPowerPreference::High_performance:
- return true;
- }
- return false;
-}
-
-inline constexpr bool IsEnumCase(const dom::PredefinedColorSpace raw) {
- switch (raw) {
- case dom::PredefinedColorSpace::Srgb:
- case dom::PredefinedColorSpace::Display_p3:
- return true;
- }
- return false;
-}
-
inline constexpr bool IsEnumCase(const webgl::AttribBaseType raw) {
switch (raw) {
case webgl::AttribBaseType::Boolean:
@@ -91,16 +72,14 @@ inline constexpr bool IsEnumCase(const webgl::AttribBaseType raw) {
return false;
}
-static_assert(IsEnumCase(dom::WebGLPowerPreference(2)));
-static_assert(!IsEnumCase(dom::WebGLPowerPreference(3)));
-static_assert(!IsEnumCase(dom::WebGLPowerPreference(5)));
+static_assert(IsEnumCase(webgl::AttribBaseType(3)));
+static_assert(!IsEnumCase(webgl::AttribBaseType(4)));
+static_assert(!IsEnumCase(webgl::AttribBaseType(5)));
#define USE_IS_ENUM_CASE(T) \
template <> \
struct QueueParamTraits<T> : QueueParamTraits_IsEnumCase<T> {};
-USE_IS_ENUM_CASE(dom::WebGLPowerPreference)
-USE_IS_ENUM_CASE(dom::PredefinedColorSpace)
USE_IS_ENUM_CASE(webgl::AttribBaseType)
USE_IS_ENUM_CASE(webgl::ProvokingVertex)
@@ -283,6 +262,20 @@ struct QueueParamTraits<gfxAlphaType>
: public ContiguousEnumSerializerInclusive<
gfxAlphaType, gfxAlphaType::Opaque, gfxAlphaType::NonPremult> {};
+// -
+
+template <class Enum>
+using WebIDLEnumQueueSerializer =
+ ContiguousEnumSerializerInclusive<Enum, ContiguousEnumValues<Enum>::min,
+ ContiguousEnumValues<Enum>::max>;
+
+template <>
+struct QueueParamTraits<dom::WebGLPowerPreference>
+ : public WebIDLEnumQueueSerializer<dom::WebGLPowerPreference> {};
+template <>
+struct QueueParamTraits<dom::PredefinedColorSpace>
+ : public WebIDLEnumQueueSerializer<dom::PredefinedColorSpace> {};
+
} // namespace webgl
} // namespace mozilla
diff --git a/dom/canvas/WebGLTypes.h b/dom/canvas/WebGLTypes.h
index f5f78e98cb..2f1d9331a7 100644
--- a/dom/canvas/WebGLTypes.h
+++ b/dom/canvas/WebGLTypes.h
@@ -362,8 +362,7 @@ struct WebGLContextOptions final {
dom::WebGLPowerPreference powerPreference =
dom::WebGLPowerPreference::Default;
- bool ignoreColorSpace = true;
- dom::PredefinedColorSpace colorSpace = dom::PredefinedColorSpace::Srgb;
+ std::optional<dom::PredefinedColorSpace> colorSpace;
bool shouldResistFingerprinting = true;
bool enableDebugRendererInfo = false;
@@ -383,7 +382,6 @@ struct WebGLContextOptions final {
powerPreference,
colorSpace,
- ignoreColorSpace,
shouldResistFingerprinting,
enableDebugRendererInfo);
diff --git a/dom/canvas/crashtests/crashtests.list b/dom/canvas/crashtests/crashtests.list
index e1920b20ee..725e7ed92b 100644
--- a/dom/canvas/crashtests/crashtests.list
+++ b/dom/canvas/crashtests/crashtests.list
@@ -16,7 +16,7 @@ skip-if(ThreadSanitizer) load 780392-1.html
skip-if(ThreadSanitizer) skip-if(gtkWidget&&isDebugBuild) skip-if(winWidget&&(!is64Bit)) load 789933-1.html # bug 1155252 for linux
skip-if(ThreadSanitizer) load 794463-1.html
skip-if(ThreadSanitizer) load 802926-1.html
-skip-if(wayland) skip-if(ThreadSanitizer) load 844280.html # wayland: bug 1856365, intermittent OOMs on Win7 debug
+skip-if(ThreadSanitizer) load 844280.html
load 896047-1.html
load 916128-1.html
load 934939-1.html
diff --git a/dom/canvas/test/reftest/colors/_generated_reftest.list b/dom/canvas/test/reftest/colors/_generated_reftest.list
index 29761e3df8..df5fa98847 100644
--- a/dom/canvas/test/reftest/colors/_generated_reftest.list
+++ b/dom/canvas/test/reftest/colors/_generated_reftest.list
@@ -17,160 +17,217 @@ defaults pref(webgl.colorspaces.prototype,true)
### Generated, do not edit. ###
# -
-skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.000)
-skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.000)
-skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.200,0.200)
-skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.200,0.200)
- ### Generated, do not edit. ###
-skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000)
-skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000)
-skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000)
-skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000)
- ### Generated, do not edit. ###
-skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200)
-skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200)
-skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.502,0.502)
-skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.502,0.502)
- ### Generated, do not edit. ###
-skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000)
-skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000)
-skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000)
-skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000)
- ### Generated, do not edit. ###
-skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502)
-skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502)
-skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(srgb,1.000,1.000,1.000)
-skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(srgb,1.000,1.000,1.000)
- ### Generated, do not edit. ###
-skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.000)
-skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.000)
-skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.200,0.200)
-skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.200,0.200)
- ### Generated, do not edit. ###
-skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.000,0.000)
-skip-if(cocoaWidget) fails == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.000,0.000)
-skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000)
-skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.200,0.000)
- ### Generated, do not edit. ###
-skip-if(cocoaWidget) fails-if(!Android) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.200,0.000)
-skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000)
-skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.200)
-skip-if(cocoaWidget) fails == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.200)
- ### Generated, do not edit. ###
-skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200)
-skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.502,0.502)
-skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.502,0.502)
-skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.000,0.000)
- ### Generated, do not edit. ###
-skip-if(cocoaWidget) fails == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.000,0.000)
-skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000)
-skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.502,0.000)
-skip-if(cocoaWidget) fails-if(!Android) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.502,0.000)
- ### Generated, do not edit. ###
-skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000)
-skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.502)
-skip-if(cocoaWidget) fails == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.502)
-skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502)
- ### Generated, do not edit. ###
-skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(display-p3,1.000,1.000,1.000)
-skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(display-p3,1.000,1.000,1.000)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.000)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.200,0.200)
- ### Generated, do not edit. ###
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.502,0.502)
- ### Generated, do not edit. ###
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(srgb,1.000,1.000,1.000)
- ### Generated, do not edit. ###
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.000)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.200,0.200)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.000,0.000)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.200,0.000)
- ### Generated, do not edit. ###
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.200)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.502,0.502)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.000,0.000)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.502,0.000)
- ### Generated, do not edit. ###
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.502)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(display-p3,1.000,1.000,1.000)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.000)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.200,0.200)
- ### Generated, do not edit. ###
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.502,0.502)
- ### Generated, do not edit. ###
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(srgb,1.000,1.000,1.000)
- ### Generated, do not edit. ###
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.000)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.200,0.200)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.000,0.000)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.200,0.000)
- ### Generated, do not edit. ###
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.200)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.502,0.502)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.000,0.000)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.502,0.000)
- ### Generated, do not edit. ###
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.502)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(display-p3,1.000,1.000,1.000)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.000)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.200,0.200)
- ### Generated, do not edit. ###
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.502,0.502)
- ### Generated, do not edit. ###
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(srgb,1.000,1.000,1.000)
- ### Generated, do not edit. ###
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.000)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.200,0.200)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.000,0.000)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.200,0.000)
- ### Generated, do not edit. ###
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.200)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.502,0.502)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.000,0.000)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.502,0.000)
- ### Generated, do not edit. ###
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.502)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(display-p3,1.000,1.000,1.000)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.000)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.200,0.200)
- ### Generated, do not edit. ###
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.502,0.502)
- ### Generated, do not edit. ###
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(srgb,1.000,1.000,1.000)
- ### Generated, do not edit. ###
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.000)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.200,0.200)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.000,0.000)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.200,0.000)
- ### Generated, do not edit. ###
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.200)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.502,0.502)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.000,0.000)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.502,0.000)
- ### Generated, do not edit. ###
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.502)
- == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(display-p3,1.000,1.000,1.000)
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.000)
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.000)
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.200,0.200)
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.200,0.200)
+ ### Generated, do not edit. ###
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000)
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000)
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000)
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000)
+ ### Generated, do not edit. ###
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200)
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200)
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.502,0.502)
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.502,0.502)
+ ### Generated, do not edit. ###
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000)
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000)
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000)
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000)
+ ### Generated, do not edit. ###
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502)
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502)
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(srgb,1.000,1.000,1.000)
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(srgb,1.000,1.000,1.000)
+ ### Generated, do not edit. ###
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.000)
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.000)
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.200,0.200)
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.200,0.200)
+ ### Generated, do not edit. ###
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.000,0.000)
+skip-if(cocoaWidget) fails == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.000,0.000)
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000)
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.200,0.000)
+ ### Generated, do not edit. ###
+skip-if(cocoaWidget) fails-if(!Android) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.200,0.000)
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000)
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.200)
+skip-if(cocoaWidget) fails == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.200)
+ ### Generated, do not edit. ###
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200)
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.502,0.502)
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.502,0.502)
+skip-if(!cocoaWidget) fuzzy(0-1,0-10000) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.000,0.000)
+ ### Generated, do not edit. ###
+skip-if(cocoaWidget) fuzzy(0-1,0-10000) fails == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.000,0.000)
+skip-if(cocoaWidget) fuzzy(0-1,0-10000) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000)
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.502,0.000)
+skip-if(cocoaWidget) fails-if(!Android) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.502,0.000)
+ ### Generated, do not edit. ###
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000)
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.502)
+skip-if(cocoaWidget) fails == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.502)
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502)
+ ### Generated, do not edit. ###
+skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(display-p3,1.000,1.000,1.000)
+skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(display-p3,1.000,1.000,1.000)
+skip-if(!cocoaWidget&&!winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.000)
+skip-if(cocoaWidget||winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.000)
+ ### Generated, do not edit. ###
+skip-if(!cocoaWidget&&!winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.200,0.200)
+skip-if(cocoaWidget||winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.200,0.200)
+skip-if(!cocoaWidget&&!winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000)
+skip-if(cocoaWidget||winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000)
+ ### Generated, do not edit. ###
+skip-if(!cocoaWidget&&!winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000)
+skip-if(cocoaWidget||winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000)
+skip-if(!cocoaWidget&&!winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200)
+skip-if(cocoaWidget||winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200)
+ ### Generated, do not edit. ###
+skip-if(!cocoaWidget&&!winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.502,0.502)
+skip-if(cocoaWidget||winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.502,0.502)
+skip-if(!cocoaWidget&&!winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000)
+skip-if(cocoaWidget||winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000)
+ ### Generated, do not edit. ###
+skip-if(!cocoaWidget&&!winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000)
+skip-if(cocoaWidget||winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000)
+skip-if(!cocoaWidget&&!winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502)
+skip-if(cocoaWidget||winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502)
+ ### Generated, do not edit. ###
+skip-if(!cocoaWidget&&!winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(srgb,1.000,1.000,1.000)
+skip-if(cocoaWidget||winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(srgb,1.000,1.000,1.000)
+skip-if(!cocoaWidget&&!winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.000)
+skip-if(cocoaWidget||winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.000)
+ ### Generated, do not edit. ###
+skip-if(!cocoaWidget&&!winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.200,0.200)
+skip-if(cocoaWidget||winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.200,0.200)
+skip-if(!cocoaWidget&&!winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.000,0.000)
+skip-if(cocoaWidget||winWidget) fails == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.000,0.000)
+ ### Generated, do not edit. ###
+skip-if(cocoaWidget||winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000)
+skip-if(!cocoaWidget&&!winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.200,0.000)
+skip-if(cocoaWidget||winWidget) fails-if(!Android) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.200,0.000)
+skip-if(cocoaWidget||winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000)
+ ### Generated, do not edit. ###
+skip-if(!cocoaWidget&&!winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.200)
+skip-if(cocoaWidget||winWidget) fails == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.200)
+skip-if(cocoaWidget||winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200)
+skip-if(!cocoaWidget&&!winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.502,0.502)
+ ### Generated, do not edit. ###
+skip-if(cocoaWidget||winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.502,0.502)
+skip-if(!cocoaWidget&&!winWidget) fuzzy(0-1,0-10000) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.000,0.000)
+skip-if(cocoaWidget||winWidget) fuzzy(0-1,0-10000) fails == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.000,0.000)
+skip-if(cocoaWidget||winWidget) fuzzy(0-1,0-10000) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000)
+ ### Generated, do not edit. ###
+skip-if(!cocoaWidget&&!winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.502,0.000)
+skip-if(cocoaWidget||winWidget) fails-if(!Android) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.502,0.000)
+skip-if(cocoaWidget||winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000)
+skip-if(!cocoaWidget&&!winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.502)
+ ### Generated, do not edit. ###
+skip-if(cocoaWidget||winWidget) fails == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.502)
+skip-if(cocoaWidget||winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502)
+skip-if(!cocoaWidget&&!winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(display-p3,1.000,1.000,1.000)
+skip-if(cocoaWidget||winWidget) == color_canvas.html?e_context=webgl2&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(display-p3,1.000,1.000,1.000)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.200,0.200)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.502,0.502)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(srgb,1.000,1.000,1.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.200,0.200)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.200,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.200)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.502,0.502)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.502,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.502)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(display-p3,1.000,1.000,1.000)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.200,0.200)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.502,0.502)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(srgb,1.000,1.000,1.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.200,0.200)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.200,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.200)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.502,0.502)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.502,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.502)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(display-p3,1.000,1.000,1.000)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.200,0.200)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.502,0.502)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(srgb,1.000,1.000,1.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.200,0.200)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.200,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.200)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.502,0.502)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.502,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.502)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(display-p3,1.000,1.000,1.000)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.200,0.200)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.502,0.502)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(srgb,1.000,1.000,1.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.200,0.200)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.200,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.200)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.502,0.502)
+ ### Generated, do not edit. ###
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.000,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.502,0.000)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.502)
+ == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(display-p3,1.000,1.000,1.000)
diff --git a/dom/canvas/test/reftest/colors/color_canvas.html b/dom/canvas/test/reftest/colors/color_canvas.html
index 7abbc86255..53314f2714 100644
--- a/dom/canvas/test/reftest/colors/color_canvas.html
+++ b/dom/canvas/test/reftest/colors/color_canvas.html
@@ -29,6 +29,7 @@
<option value=css selected>css</option>
<option value=2d>2d</option>
<option value=webgl>webgl</option>
+ <option value=webgl2>webgl2</option>
</select>
<br>
Options: <input id=e_options type=text value={}>
diff --git a/dom/canvas/test/reftest/colors/generate_color_canvas_reftests.py b/dom/canvas/test/reftest/colors/generate_color_canvas_reftests.py
index 8c1e5f3788..ad04d9a524 100644
--- a/dom/canvas/test/reftest/colors/generate_color_canvas_reftests.py
+++ b/dom/canvas/test/reftest/colors/generate_color_canvas_reftests.py
@@ -120,9 +120,8 @@ WEBGL_FORMATS = keyed_alternatives(
#'RGBA16F',
],
)
-WEBGL = cross_combine(
- [{"e_context": "webgl"}], WEBGL_FORMATS, CANVAS_CSPACES, WEBGL_COLORS
-)
+WEBGL_VERSIONS = keyed_alternatives("e_context", ["webgl", "webgl2"])
+WEBGL = cross_combine(WEBGL_VERSIONS, WEBGL_FORMATS, CANVAS_CSPACES, WEBGL_COLORS)
# -
@@ -212,7 +211,7 @@ def correct_color_from_test_config(test_config: Config) -> CssColor:
canvas_cspace = "srgb"
correct_color = parse_css_color(test_config["e_color"])
- if test_config["e_context"] == "webgl":
+ if test_config["e_context"].startswith("webgl"):
# Webgl ignores the color's cspace, because webgl has no concept of
# source colorspace for clears/draws to the backbuffer.
# This (correct) behavior is as if the color's cspace were overwritten by the
@@ -231,12 +230,12 @@ def correct_color_from_test_config(test_config: Config) -> CssColor:
def reftests_from_config(test_config: Config) -> Iterable[ColorReftest]:
correct_color = correct_color_from_test_config(test_config)
- if test_config["e_context"] == "2d":
+ if test_config["e_context"] in ["2d"]:
# Canvas2d generally has the same behavior as css, so expect all passing.
yield ColorReftest([], test_config, correct_color)
return
- assert test_config["e_context"] == "webgl", test_config["e_context"]
+ assert test_config["e_context"].startswith("webgl"), test_config["e_context"]
# -
@@ -249,6 +248,11 @@ def reftests_from_config(test_config: Config) -> Iterable[ColorReftest]:
# If we fix an error, we'll see one unexpected-pass and one unexpected-fail.
# If we get a new wrong answer, we'll see one unexpected-fail.
+ if correct_color.is_same_color(
+ parse_css_color("color(display-p3 0.502 0.000 0.000)")
+ ):
+ notes += ["fuzzy(0-1,0-10000)"]
+
if not expected_color.is_same_color(correct_color):
yield ColorReftest(notes + ["fails"], test_config, correct_color)
yield ColorReftest(notes, test_config, expected_color)
@@ -261,12 +265,26 @@ def reftests_from_config(test_config: Config) -> Iterable[ColorReftest]:
# right now. This is the same as "srgb".
expected_color_srgb = CssColor("srgb", correct_color.rgb)
- # Mac
- yield from reftests_from_expected_color(["skip-if(!cocoaWidget)"], correct_color)
- # Win, Lin, Android
- yield from reftests_from_expected_color(
- ["skip-if(cocoaWidget) "], expected_color_srgb
- )
+ if test_config["e_context"] == "webgl2":
+ # Win, Mac
+ yield from reftests_from_expected_color(
+ ["skip-if(!cocoaWidget&&!winWidget)"], correct_color
+ )
+ # Lin, Android
+ yield from reftests_from_expected_color(
+ ["skip-if(cocoaWidget||winWidget) "], expected_color_srgb
+ )
+ elif test_config["e_context"] == "webgl":
+ # Mac
+ yield from reftests_from_expected_color(
+ ["skip-if(!cocoaWidget)"], correct_color
+ )
+ # Win, Lin, Android
+ yield from reftests_from_expected_color(
+ ["skip-if(cocoaWidget) "], expected_color_srgb
+ )
+ else:
+ assert False, test_config["e_context"]
# -
@@ -279,7 +297,7 @@ def amended_notes_from_reftest(reftest: ColorReftest) -> list[str]:
is_green_only = ref_rgb_vals == (0, ref_rgb_vals[1], 0)
if (
"fails" in reftest.notes
- and reftest.test_config["e_context"] == "webgl"
+ and reftest.test_config["e_context"].startswith("webgl")
and reftest.test_config["e_cspace"] == "display-p3"
and is_green_only
):
diff --git a/dom/chrome-webidl/InspectorUtils.webidl b/dom/chrome-webidl/InspectorUtils.webidl
index b379118e8a..40c57182d3 100644
--- a/dom/chrome-webidl/InspectorUtils.webidl
+++ b/dom/chrome-webidl/InspectorUtils.webidl
@@ -227,3 +227,53 @@ interface InspectorFontFace {
readonly attribute DOMString format; // as per http://www.w3.org/TR/css3-webfonts/#referencing
readonly attribute DOMString metadata; // XML metadata from WOFF file (if any)
};
+
+dictionary InspectorCSSToken {
+ // The token type.
+ required UTF8String tokenType;
+
+ // Text associated with the token.
+ required UTF8String text;
+
+ // Value of the token. Might differ from `text`:
+ // - for `Function` tokens, text contains the opening paren, `value` does not (e.g. `var(` vs `var`)
+ // - for `AtKeyword` tokens, text contains the leading `@`, `value` does not (e.g. `@media` vs `media`)
+ // - for `Hash` and `IDHash` tokens, text contains the leading `#`, `value` does not (e.g. `#myid` vs `myid`)
+ // - for `UnquotedUrl` tokens, text contains the `url(` parts, `value` only holds the url (e.g. `url(test.jpg)` vs `test.jpg`)
+ // - for `QuotedString` tokens, text contains the wrapping quotes, `value` does not (e.g. `"hello"` vs `hello`)
+ // - for `Comment` tokens, text contains leading `/*` and trailing `*/`, `value` does not (e.g. `/* yo */` vs ` yo `)
+ required UTF8String? value;
+
+ // Unit for Dimension tokens
+ required UTF8String? unit;
+
+ // Float value for Dimension, Number and Percentage tokens
+ double? number = null;
+};
+
+/**
+ * InspectorCSSParser is an interface to the CSS lexer. It tokenizes an
+ * input stream and returns CSS tokens.
+ */
+[Func="nsContentUtils::IsCallerChromeOrFuzzingEnabled",
+ Exposed=Window]
+interface InspectorCSSParser {
+ constructor(UTF8String text);
+
+ /**
+ * The line number of the most recently returned token. Line
+ * numbers are 0-based.
+ */
+ readonly attribute unsigned long lineNumber;
+
+ /**
+ * The column number of the most recently returned token. Column
+ * numbers are 1-based.
+ */
+ readonly attribute unsigned long columnNumber;
+
+ /**
+ * Return the next token, or null at EOF.
+ */
+ InspectorCSSToken? nextToken();
+};
diff --git a/dom/chrome-webidl/UniFFI.webidl b/dom/chrome-webidl/UniFFI.webidl
index e24fc9cc5d..ea250771f8 100644
--- a/dom/chrome-webidl/UniFFI.webidl
+++ b/dom/chrome-webidl/UniFFI.webidl
@@ -43,7 +43,7 @@ interface UniFFIPointer { };
// to an int including Boolean and CallbackInterface.
// - ArrayBuffer is used for RustBuffer
// - UniFFIPointer is used for Arc pointers
-typedef (double or ArrayBuffer or UniFFIPointer) UniFFIScaffoldingType;
+typedef (double or ArrayBuffer or UniFFIPointer) UniFFIScaffoldingValue;
// The result of a call into UniFFI scaffolding call
enum UniFFIScaffoldingCallCode {
@@ -56,7 +56,7 @@ dictionary UniFFIScaffoldingCallResult {
required UniFFIScaffoldingCallCode code;
// For success, this will be the return value for non-void returns
// For error, this will be an ArrayBuffer storing the serialized error value
- UniFFIScaffoldingType data;
+ UniFFIScaffoldingValue data;
// For internal-error, this will be a utf-8 string describing the error
ByteString internalErrorMessage;
};
@@ -72,13 +72,13 @@ namespace UniFFIScaffolding {
//
// id is a unique identifier for the function, known to both the C++ and JS code
[Throws]
- Promise<UniFFIScaffoldingCallResult> callAsync(UniFFIFunctionId id, UniFFIScaffoldingType... args);
+ Promise<UniFFIScaffoldingCallResult> callAsync(UniFFIFunctionId id, UniFFIScaffoldingValue... args);
// Call a scaffolding function on the main thread
//
// id is a unique identifier for the function, known to both the C++ and JS code
[Throws]
- UniFFIScaffoldingCallResult callSync(UniFFIFunctionId id, UniFFIScaffoldingType... args);
+ UniFFIScaffoldingCallResult callSync(UniFFIFunctionId id, UniFFIScaffoldingValue... args);
// Read a UniFFIPointer from an ArrayBuffer
//
diff --git a/dom/console/Console.h b/dom/console/Console.h
index 5898c6b0f5..bce2058130 100644
--- a/dom/console/Console.h
+++ b/dom/console/Console.h
@@ -134,6 +134,7 @@ class Console final : public nsIObserver, public nsSupportsWeakReference {
MOZ_CAN_RUN_SCRIPT
static void Clear(const GlobalObject& aGlobal);
+ MOZ_CAN_RUN_SCRIPT
static already_AddRefed<ConsoleInstance> CreateInstance(
const GlobalObject& aGlobal, const ConsoleInstanceOptions& aOptions);
diff --git a/dom/console/ConsoleInstance.cpp b/dom/console/ConsoleInstance.cpp
index ade82be50f..a73b83ee4c 100644
--- a/dom/console/ConsoleInstance.cpp
+++ b/dom/console/ConsoleInstance.cpp
@@ -65,9 +65,24 @@ ConsoleInstance::ConsoleInstance(JSContext* aCx,
if (!aOptions.mMaxLogLevelPref.IsEmpty()) {
if (!NS_IsMainThread()) {
- NS_WARNING("Console.maxLogLevelPref is not supported on workers!");
// Set the log level based on what we have.
SetLogLevel();
+
+ // Flag an error to the console.
+ JS::Rooted<JS::Value> msg(aCx);
+ if (!ToJSValue(
+ aCx,
+ nsLiteralCString(
+ "Console.maxLogLevelPref is not supported within workers!"),
+ &msg)) {
+ JS_ClearPendingException(aCx);
+ return;
+ }
+
+ AutoTArray<JS::Value, 1> sequence;
+ SequenceRooter rootedSequence(aCx, &sequence);
+ sequence.AppendElement(std::move(msg));
+ this->Error(aCx, std::move(sequence));
return;
}
@@ -80,8 +95,9 @@ ConsoleInstance::ConsoleInstance(JSContext* aCx,
}
ConsoleInstance::~ConsoleInstance() {
- AssertIsOnMainThread();
- if (!mMaxLogLevelPref.IsEmpty()) {
+ // We should only ever have set `mMaxLogLevelPref` when on the main thread,
+ // but check it here to be safe.
+ if (!mMaxLogLevelPref.IsEmpty() && NS_IsMainThread()) {
Preferences::UnregisterCallback(MaxLogLevelPrefChangedCallback,
mMaxLogLevelPref, this);
}
@@ -128,7 +144,6 @@ void ConsoleInstance::SetLogLevel() {
// static
void ConsoleInstance::MaxLogLevelPrefChangedCallback(
const char* /* aPrefName */, void* aSelf) {
- AssertIsOnMainThread();
auto* instance = static_cast<ConsoleInstance*>(aSelf);
if (MOZ_UNLIKELY(!instance->mConsole)) {
// We've been unlinked already but not destroyed yet. Bail.
diff --git a/dom/console/ConsoleInstance.h b/dom/console/ConsoleInstance.h
index 5d322a867b..3d0e4ddcde 100644
--- a/dom/console/ConsoleInstance.h
+++ b/dom/console/ConsoleInstance.h
@@ -16,6 +16,7 @@ class ConsoleInstance final : public nsISupports, public nsWrapperCache {
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(ConsoleInstance)
+ MOZ_CAN_RUN_SCRIPT
explicit ConsoleInstance(JSContext* aCx,
const ConsoleInstanceOptions& aOptions);
diff --git a/dom/console/tests/xpcshell/test_worker.js b/dom/console/tests/xpcshell/test_worker.js
new file mode 100644
index 0000000000..7792dfc2da
--- /dev/null
+++ b/dom/console/tests/xpcshell/test_worker.js
@@ -0,0 +1,59 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests for console.createInstance usage in workers.
+ *
+ * Also tests that the use of `maxLogLevelPref` logs an error, and log levels
+ * fallback to the `maxLogLevel` option.
+ */
+
+"use strict";
+
+let { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+
+add_task(async function () {
+ let endConsoleListening = TestUtils.listenForConsoleMessages();
+ let workerCompleteDeferred = Promise.withResolvers();
+
+ const worker = new ChromeWorker("resource://test/worker.mjs");
+ worker.onmessage = workerCompleteDeferred.resolve;
+ worker.postMessage({});
+
+ await workerCompleteDeferred.promise;
+
+ let messages = await endConsoleListening();
+
+ // Should log that we can't use `maxLogLevelPref`, and the warning message.
+ // The info message should not be logged, as that's lower than the specified
+ // `maxLogLevel` in the worker.
+ Assert.equal(messages.length, 2, "Should have received two messages");
+
+ // First message should be the error that `maxLogLevelPref` cannot be used.
+ Assert.equal(messages[0].level, "error", "Should be an error message");
+ Assert.equal(
+ messages[0].prefix,
+ "testPrefix",
+ "Should have the correct prefix"
+ );
+ Assert.equal(
+ messages[0].arguments[0],
+ "Console.maxLogLevelPref is not supported within workers!",
+ "Should have the correct message text"
+ );
+
+ // Second message should be the warning.
+ Assert.equal(messages[1].level, "warn", "Should be a warning message");
+ Assert.equal(
+ messages[1].prefix,
+ "testPrefix",
+ "Should have the correct prefix"
+ );
+ Assert.equal(
+ messages[1].arguments[0],
+ "Test Warn",
+ "Should have the correct message text"
+ );
+});
diff --git a/dom/console/tests/xpcshell/worker.mjs b/dom/console/tests/xpcshell/worker.mjs
new file mode 100644
index 0000000000..4a1ff21b00
--- /dev/null
+++ b/dom/console/tests/xpcshell/worker.mjs
@@ -0,0 +1,15 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+onmessage = () => {
+ let logConsole = console.createInstance({
+ maxLogLevelPref: "browser.test.logLevel",
+ maxLogLevel: "Warn",
+ prefix: "testPrefix",
+ });
+
+ logConsole.warn("Test Warn");
+ logConsole.info("Test Info");
+
+ postMessage({});
+};
diff --git a/dom/console/tests/xpcshell/xpcshell.toml b/dom/console/tests/xpcshell/xpcshell.toml
index c5cc54d665..621605221d 100644
--- a/dom/console/tests/xpcshell/xpcshell.toml
+++ b/dom/console/tests/xpcshell/xpcshell.toml
@@ -11,3 +11,9 @@ support-files = ""
["test_formatting.js"]
["test_reportForServiceWorkerScope.js"]
+
+["test_worker.js"]
+skip-if = ["os == 'android'"] # Uses ChromeWorker.
+support-files = [
+ "worker.mjs",
+]
diff --git a/dom/crypto/WebCryptoTask.cpp b/dom/crypto/WebCryptoTask.cpp
index dc76867980..2ba4ef41b8 100644
--- a/dom/crypto/WebCryptoTask.cpp
+++ b/dom/crypto/WebCryptoTask.cpp
@@ -166,7 +166,7 @@ static nsresult Coerce(JSContext* aCx, T& aTarget, const OOS& aAlgorithm) {
JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*aAlgorithm.GetAsObject()));
if (!aTarget.Init(aCx, value)) {
- return NS_ERROR_DOM_SYNTAX_ERR;
+ return NS_ERROR_DOM_TYPE_MISMATCH_ERR;
}
return NS_OK;
@@ -392,9 +392,14 @@ void WebCryptoTask::FailWithError(nsresult aRv) {
MOZ_ASSERT(IsOnOriginalThread());
Telemetry::Accumulate(Telemetry::WEBCRYPTO_RESOLVED, false);
- // Blindly convert nsresult to DOMException
- // Individual tasks must ensure they pass the right values
- mResultPromise->MaybeReject(aRv);
+ if (aRv == NS_ERROR_DOM_TYPE_MISMATCH_ERR) {
+ mResultPromise->MaybeRejectWithTypeError(
+ "The operation could not be performed.");
+ } else {
+ // Blindly convert nsresult to DOMException
+ // Individual tasks must ensure they pass the right values
+ mResultPromise->MaybeReject(aRv);
+ }
// Manually release mResultPromise while we're on the main thread
mResultPromise = nullptr;
mWorkerRef = nullptr;
@@ -1112,7 +1117,7 @@ class AsymmetricSignVerifyTask : public WebCryptoTask {
mEarlyRv = GetAlgorithmName(aCx, params.mHash, hashAlgName);
if (NS_FAILED(mEarlyRv)) {
- mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
return;
}
} else {
@@ -2424,7 +2429,7 @@ class DeriveHkdfBitsTask : public ReturnArrayBufferViewTask {
// length must be greater than zero.
if (aLength == 0) {
- mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ mEarlyRv = NS_ERROR_DOM_OPERATION_ERR;
return;
}
@@ -2750,7 +2755,7 @@ class DeriveEcdhBitsTask : public ReturnArrayBufferViewTask {
RootedDictionary<EcdhKeyDeriveParams> params(aCx);
mEarlyRv = Coerce(aCx, params, aAlgorithm);
if (NS_FAILED(mEarlyRv)) {
- mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ /* The returned code is installed by Coerce function. */
return;
}
@@ -2768,7 +2773,7 @@ class DeriveEcdhBitsTask : public ReturnArrayBufferViewTask {
nsString curve2 = publicKey->Algorithm().mEc.mNamedCurve;
if (!curve1.Equals(curve2)) {
- mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+ mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
return;
}
}
diff --git a/dom/docs/ipc/process_model.rst b/dom/docs/ipc/process_model.rst
index b405566a1d..2a6e17d4d8 100644
--- a/dom/docs/ipc/process_model.rst
+++ b/dom/docs/ipc/process_model.rst
@@ -101,7 +101,7 @@ Content Process
Content processes are used to load web content, and are the only process type (other than the parent process) which can load and execute JS code. These processes are further subdivided into specific "remote types", which specify the type of content loaded within them, their sandboxing behavior, and can gate access to certain privileged IPC methods.
-The specific remote type and isolation behaviour used for a specific resource is currently controlled in 2 major places. When performing a document navigation, the final process to load the document in is selected by the logic in `ProcessIsolation.cpp <https://searchfox.org/mozilla-central/source/dom/ipc/ProcessIsolation.cpp>`_. This will combine information about the specific response, such as the site and headers, with other state to select which process and other isolating actions should be taken. When selecting which process to create the initial process for a new tab in, and when selecting processes for serviceworkers and shared workers, the logic in :searchfox:`E10SUtils.sys.mjs <toolkit/modules/E10SUtils.sys.mjs>`_ is used to select a process. The logic in ``E10SUtils.sys.mjs`` will likely be removed and replaced with ``ProcessIsolation.cpp`` in the future.
+The specific remote type and isolation behaviour used for a specific resource is currently controlled in 2 major places. When performing a document navigation, the final process to load the document in is selected by the logic in `ProcessIsolation.cpp <https://searchfox.org/mozilla-central/source/dom/ipc/ProcessIsolation.cpp>`_. This will combine information about the specific response, such as the site and headers, with other state to select which process and other isolating actions should be taken. When selecting which process to create the initial process for a new tab in, and when selecting processes for serviceworkers and shared workers, the logic in :searchfox:`E10SUtils.sys.mjs <toolkit/modules/E10SUtils.sys.mjs>` is used to select a process. The logic in ``E10SUtils.sys.mjs`` will likely be removed and replaced with ``ProcessIsolation.cpp`` in the future.
.. note::
diff --git a/dom/docs/workersAndStorage/PerformanceTesting.rst b/dom/docs/workersAndStorage/PerformanceTesting.rst
index 8e464b8116..fc196ad677 100644
--- a/dom/docs/workersAndStorage/PerformanceTesting.rst
+++ b/dom/docs/workersAndStorage/PerformanceTesting.rst
@@ -27,8 +27,8 @@ Add files to `perftest.toml
<https://searchfox.org/mozilla-central/source/dom/serviceworkers/test/performance/perftest.toml>`_
as usual for mochitests.
-Modify linux.yml, macosx.yml, and windows.yml under `taskcluster/ci/perftest
-<https://searchfox.org/mozilla-central/source/taskcluster/ci/perftest>`_.
+Modify linux.yml, macosx.yml, and windows.yml under `taskcluster/kinds/perftest
+<https://searchfox.org/mozilla-central/source/taskcluster/kinds/perftest>`_.
Currently, each test needs to be added individually to the run command (`here
<https://searchfox.org/mozilla-central/rev/91cc8848427fdbbeb324e6ca56a0d08d32d3c308/taskcluster/ci/perftest/linux.yml#121-149>`_,
for example). kind.yml can be ignored–it provides some defaults.
@@ -62,7 +62,7 @@ and `autoland
<https://treeherder.mozilla.org/jobs?repo=autoland&searchStr=perftest>`_. Look
for linux-sw, macosx-sw, and win-sw (`example
<https://treeherder.mozilla.org/perfherder/graphs?series=mozilla-central,4967140,1,15&selected=4967140,1814245176>`_).
-These symbol names are defined in the .yml files under taskcluster/ci/perftest.
+These symbol names are defined in the .yml files under taskcluster/kinds/perftest.
Contacts
========
diff --git a/dom/encoding/test/reftest/reftest.list b/dom/encoding/test/reftest/reftest.list
index 16ba0f2816..ff235544cc 100644
--- a/dom/encoding/test/reftest/reftest.list
+++ b/dom/encoding/test/reftest/reftest.list
@@ -1,5 +1,5 @@
== bug863728-1.html bug863728-1-ref.html
-fuzzy(0-128,0-281) fuzzy-if(winWidget&&fission,47-137,211-251) == bug863728-2.html bug863728-2-ref.html # fission: bug 1717838
+fuzzy(0-128,0-281) fuzzy-if(winWidget,47-137,211-251) == bug863728-2.html bug863728-2-ref.html # win/fission: bug 1717838
== bug863728-3.html bug863728-3-ref.html
== bug945215-1.html bug945215-1-ref.html
-fuzzy(0-128,0-281) fuzzy-if(winWidget&&fission,47-137,211-251) == bug945215-2.html bug945215-2-ref.html # fission: bug 1717838
+fuzzy(0-128,0-281) fuzzy-if(winWidget,47-137,211-251) == bug945215-2.html bug945215-2-ref.html # win/fission: bug 1717838
diff --git a/dom/events/Clipboard.cpp b/dom/events/Clipboard.cpp
index b797f93961..3574d08365 100644
--- a/dom/events/Clipboard.cpp
+++ b/dom/events/Clipboard.cpp
@@ -59,6 +59,19 @@ bool Clipboard::IsTestingPrefEnabledOrHasReadPermission(
nsGkAtoms::clipboardRead);
}
+// Mandatory data types defined in
+// https://w3c.github.io/clipboard-apis/#mandatory-data-types-x. The types
+// should be in the same order as kNonPlainTextExternalFormats in
+// DataTransfer.
+static const nsLiteralCString kMandatoryDataTypes[] = {
+ nsLiteralCString(kHTMLMime), nsLiteralCString(kTextMime),
+ nsLiteralCString(kPNGImageMime)};
+
+// static
+Span<const nsLiteralCString> Clipboard::MandatoryDataTypes() {
+ return Span<const nsLiteralCString>(kMandatoryDataTypes);
+}
+
namespace {
/**
@@ -87,16 +100,6 @@ class ClipboardGetCallback : public nsIAsyncClipboardGetCallback {
RefPtr<Promise> mPromise;
};
-static nsTArray<nsCString> MandatoryDataTypesAsCStrings() {
- // Mandatory data types defined in
- // https://w3c.github.io/clipboard-apis/#mandatory-data-types-x. The types
- // should be in the same order as kNonPlainTextExternalFormats in
- // DataTransfer.
- return nsTArray<nsCString>{nsLiteralCString(kHTMLMime),
- nsLiteralCString(kTextMime),
- nsLiteralCString(kPNGImageMime)};
-}
-
class ClipboardGetCallbackForRead final : public ClipboardGetCallback {
public:
explicit ClipboardGetCallbackForRead(nsIGlobalObject* aGlobal,
@@ -122,7 +125,7 @@ class ClipboardGetCallbackForRead final : public ClipboardGetCallback {
AutoTArray<RefPtr<ClipboardItem::ItemEntry>, 3> entries;
// We might reuse the request from DataTransfer created for paste event,
// which could contain more types that are not in the mandatory list.
- for (const auto& format : MandatoryDataTypesAsCStrings()) {
+ for (const auto& format : kMandatoryDataTypes) {
if (flavorList.Contains(format)) {
auto entry = MakeRefPtr<ClipboardItem::ItemEntry>(
mGlobal, NS_ConvertUTF8toUTF16(format));
@@ -287,10 +290,13 @@ void Clipboard::RequestRead(Promise* aPromise, ReadRequestType aType,
return;
}
+ AutoTArray<nsCString, ArrayLength(kMandatoryDataTypes)> types;
+ types.AppendElements(Span<const nsLiteralCString>(kMandatoryDataTypes));
+
callback = MakeRefPtr<ClipboardGetCallbackForRead>(global, std::move(p));
- rv = clipboardService->AsyncGetData(
- MandatoryDataTypesAsCStrings(), nsIClipboard::kGlobalClipboard,
- owner->GetWindowContext(), &aPrincipal, callback);
+ rv = clipboardService->AsyncGetData(types, nsIClipboard::kGlobalClipboard,
+ owner->GetWindowContext(),
+ &aPrincipal, callback);
break;
}
case ReadRequestType::eReadText: {
@@ -731,7 +737,8 @@ already_AddRefed<Promise> Clipboard::Write(
RefPtr<ClipboardWriteCallback> callback =
MakeRefPtr<ClipboardWriteCallback>(p, aData[0]);
nsresult rv = clipboard->AsyncSetData(nsIClipboard::kGlobalClipboard,
- callback, getter_AddRefs(request));
+ owner->GetWindowContext(), callback,
+ getter_AddRefs(request));
if (NS_FAILED(rv)) {
p->MaybeReject(rv);
return p.forget();
diff --git a/dom/events/Clipboard.h b/dom/events/Clipboard.h
index a9952e6052..f4d44c25db 100644
--- a/dom/events/Clipboard.h
+++ b/dom/events/Clipboard.h
@@ -51,6 +51,8 @@ class Clipboard : public DOMEventTargetHelper {
// testing purposes.
static bool ReadTextEnabled(JSContext* aCx, JSObject* aGlobal);
+ static Span<const nsLiteralCString> MandatoryDataTypes();
+
virtual JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
diff --git a/dom/events/ClipboardItem.cpp b/dom/events/ClipboardItem.cpp
index 76196be3b6..0d5df85492 100644
--- a/dom/events/ClipboardItem.cpp
+++ b/dom/events/ClipboardItem.cpp
@@ -6,6 +6,7 @@
#include "mozilla/dom/ClipboardItem.h"
+#include "mozilla/dom/Clipboard.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/Record.h"
#include "nsComponentManagerUtils.h"
@@ -290,6 +291,17 @@ already_AddRefed<ClipboardItem> ClipboardItem::Constructor(
return item.forget();
}
+// static
+bool ClipboardItem::Supports(const GlobalObject& aGlobal,
+ const nsAString& aType) {
+ for (const auto& mandatoryType : Clipboard::MandatoryDataTypes()) {
+ if (CompareUTF8toUTF16(mandatoryType, aType) == 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
void ClipboardItem::GetTypes(nsTArray<nsString>& aTypes) const {
for (const auto& item : mItems) {
aTypes.AppendElement(item->Type());
diff --git a/dom/events/ClipboardItem.h b/dom/events/ClipboardItem.h
index 9fe45bf936..53b914745c 100644
--- a/dom/events/ClipboardItem.h
+++ b/dom/events/ClipboardItem.h
@@ -107,6 +107,8 @@ class ClipboardItem final : public nsWrapperCache {
const Record<nsString, OwningNonNull<Promise>>& aItems,
const ClipboardItemOptions& aOptions, ErrorResult& aRv);
+ static bool Supports(const GlobalObject& aGlobal, const nsAString& aType);
+
dom::PresentationStyle PresentationStyle() const {
return mPresentationStyle;
};
diff --git a/dom/events/DataTransfer.cpp b/dom/events/DataTransfer.cpp
index ba56575749..b2f70dab54 100644
--- a/dom/events/DataTransfer.cpp
+++ b/dom/events/DataTransfer.cpp
@@ -909,7 +909,7 @@ already_AddRefed<nsITransferable> DataTransfer::GetTransferable(
// from another origin or from the OS.
if (mMode == Mode::ReadWrite) {
if (nsCOMPtr<nsIGlobalObject> global = GetGlobal()) {
- transferable->SetRequestingPrincipal(global->PrincipalOrNull());
+ transferable->SetDataPrincipal(global->PrincipalOrNull());
}
}
@@ -1083,7 +1083,7 @@ already_AddRefed<nsITransferable> DataTransfer::GetTransferable(
static_cast<char*>(stringBuffer->Data())[amountRead] = 0;
nsCString str;
- stringBuffer->ToString(totalCustomLength, str);
+ str.Assign(stringBuffer, totalCustomLength);
nsCOMPtr<nsISupportsCString> strSupports(
do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID));
strSupports->SetData(str);
diff --git a/dom/events/DataTransferItem.cpp b/dom/events/DataTransferItem.cpp
index 8b1aa1902c..27bce8bd8b 100644
--- a/dom/events/DataTransferItem.cpp
+++ b/dom/events/DataTransferItem.cpp
@@ -76,6 +76,7 @@ already_AddRefed<DataTransferItem> DataTransferItem::Clone(
it->mData = mData;
it->mPrincipal = mPrincipal;
it->mChromeOnly = mChromeOnly;
+ it->mDoNotAttemptToLoadData = mDoNotAttemptToLoadData;
return it.forget();
}
@@ -144,7 +145,7 @@ void DataTransferItem::SetData(nsIVariant* aData) {
}
void DataTransferItem::FillInExternalData() {
- if (mData) {
+ if (mData || mDoNotAttemptToLoadData) {
return;
}
@@ -183,6 +184,11 @@ void DataTransferItem::FillInExternalData() {
nsresult rv = clipboard->GetData(trans, mDataTransfer->ClipboardType(),
windowContext);
if (NS_WARN_IF(NS_FAILED(rv))) {
+ if (rv == NS_ERROR_CONTENT_BLOCKED) {
+ // If the load of this content was blocked by Content Analysis,
+ // do not attempt to load it again.
+ mDoNotAttemptToLoadData = true;
+ }
return;
}
} else {
diff --git a/dom/events/DataTransferItem.h b/dom/events/DataTransferItem.h
index 111f498e82..1e98ba8b92 100644
--- a/dom/events/DataTransferItem.h
+++ b/dom/events/DataTransferItem.h
@@ -42,6 +42,7 @@ class DataTransferItem final : public nsISupports, public nsWrapperCache {
mChromeOnly(false),
mKind(aKind),
mType(aType),
+ mDoNotAttemptToLoadData(false),
mDataTransfer(aDataTransfer) {
MOZ_ASSERT(mDataTransfer, "Must be associated with a DataTransfer");
}
@@ -122,6 +123,7 @@ class DataTransferItem final : public nsISupports, public nsWrapperCache {
eKind mKind;
const nsString mType;
nsCOMPtr<nsIVariant> mData;
+ bool mDoNotAttemptToLoadData;
nsCOMPtr<nsIPrincipal> mPrincipal;
RefPtr<DataTransfer> mDataTransfer;
diff --git a/dom/events/EventStateManager.cpp b/dom/events/EventStateManager.cpp
index 3c24cdb30a..23803e5447 100644
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -15,6 +15,7 @@
#include "mozilla/HTMLEditor.h"
#include "mozilla/IMEStateManager.h"
#include "mozilla/Likely.h"
+#include "mozilla/FocusModel.h"
#include "mozilla/MiscEvents.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/MouseEvents.h"
@@ -3728,14 +3729,14 @@ nsresult EventStateManager::PostHandleEvent(nsPresContext* aPresContext,
break;
}
- if (frame->IsFocusable(/* aWithMouse = */ true)) {
+ auto flags = IsFocusableFlags::WithMouse;
+ if (frame->IsFocusable(flags)) {
break;
}
if (ShadowRoot* root = newFocus->GetShadowRoot()) {
if (root->DelegatesFocus()) {
- if (Element* firstFocusable =
- root->GetFocusDelegate(/* aWithMouse */ true)) {
+ if (Element* firstFocusable = root->GetFocusDelegate(flags)) {
newFocus = firstFocusable;
break;
}
diff --git a/dom/events/IMEStateManager.cpp b/dom/events/IMEStateManager.cpp
index 966b2f62f7..1e6affb111 100644
--- a/dom/events/IMEStateManager.cpp
+++ b/dom/events/IMEStateManager.cpp
@@ -1631,7 +1631,7 @@ MOZ_CAN_RUN_SCRIPT static bool IsNextFocusableElementTextControl(
// FIXME: Should probably use nsIFrame::IsFocusable if possible, to account
// for things like visibility: hidden or so.
- if (!nextElement->IsFocusableWithoutStyle(false)) {
+ if (!nextElement->IsFocusableWithoutStyle()) {
return false;
}
diff --git a/dom/events/WheelHandlingHelper.cpp b/dom/events/WheelHandlingHelper.cpp
index 3b5653c50e..1633c68d72 100644
--- a/dom/events/WheelHandlingHelper.cpp
+++ b/dom/events/WheelHandlingHelper.cpp
@@ -725,15 +725,10 @@ ESMAutoDirWheelDeltaAdjuster::ESMAutoDirWheelDeltaAdjuster(
}
WritingMode writingMode = honouredFrame->GetWritingMode();
- WritingMode::BlockDir blockDir = writingMode.GetBlockDir();
- WritingMode::InlineDir inlineDir = writingMode.GetInlineDir();
// Get whether the honoured frame's content in the horizontal direction starts
// from right to left(E.g. it's true either if "writing-mode: vertical-rl", or
// if "writing-mode: horizontal-tb; direction: rtl;" in CSS).
- mIsHorizontalContentRightToLeft =
- (blockDir == WritingMode::BlockDir::eBlockRL ||
- (blockDir == WritingMode::BlockDir::eBlockTB &&
- inlineDir == WritingMode::InlineDir::eInlineRTL));
+ mIsHorizontalContentRightToLeft = writingMode.IsPhysicalRTL();
}
void ESMAutoDirWheelDeltaAdjuster::OnAdjusted() {
diff --git a/dom/events/test/pointerevents/mochitest.toml b/dom/events/test/pointerevents/mochitest.toml
index c22e1bdf94..8d6990d4ff 100644
--- a/dom/events/test/pointerevents/mochitest.toml
+++ b/dom/events/test/pointerevents/mochitest.toml
@@ -64,7 +64,6 @@ support-files = ["!/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js
skip-if = [
"os == 'android'", # Bug 1312791
"display == 'wayland' && os_version == '22.04'", # Bug 1856971
- "verify && os == 'win'", # Bug 1659744
]
["test_getCoalescedEvents_touch.html"]
diff --git a/dom/events/test/pointerevents/test_getCoalescedEvents.html b/dom/events/test/pointerevents/test_getCoalescedEvents.html
index 69eeac6919..9c0c0c5baa 100644
--- a/dom/events/test/pointerevents/test_getCoalescedEvents.html
+++ b/dom/events/test/pointerevents/test_getCoalescedEvents.html
@@ -18,14 +18,16 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1303957
/** Test for Bug 1303957 **/
SimpleTest.waitForExplicitFinish();
-function runTests() {
- let target0 = window.document.getElementById("target0");
- let utils = SpecialPowers.getDOMWindowUtils(window);
+SimpleTest.waitForFocus(async () => {
+ await SpecialPowers.pushPrefEnv({"set": [["dom.events.coalesce.mousemove", true]]});
+
+ const target0 = window.document.getElementById("target0");
+ const utils = SpecialPowers.getDOMWindowUtils(window);
utils.advanceTimeAndRefresh(0);
- SimpleTest.executeSoon(async () => {
- // Flush all pending mouse events before synthesizing events.
+ await new Promise(resolve => SimpleTest.executeSoon(resolve));
+ const waitForPointerMove = new Promise(resolve => {
target0.addEventListener("pointermove", (ev) => {
let length = ev.getCoalescedEvents().length;
ok(length >= 1, "Coalesced events should >= 1, got " + length);
@@ -49,11 +51,11 @@ function runTests() {
ok(coalescedEvent.offsetX >= prevOffsetX, "getCoalescedEvents()[" + i + "].offsetX = " + coalescedEvent.offsetX);
ok(coalescedEvent.offsetX == 5 || coalescedEvent.offsetX == 10 ||
- coalescedEvent.offsetX == 15 || coalescedEvent.offsetX == 20, "expected offsetX");
+ coalescedEvent.offsetX == 15 || coalescedEvent.offsetX == 20, "expected offsetX");
ok(coalescedEvent.offsetY >= prevOffsetY, "getCoalescedEvents()[" + i + "].offsetY = " + coalescedEvent.offsetY);
ok(coalescedEvent.offsetY == 5 || coalescedEvent.offsetY == 10 ||
- coalescedEvent.offsetY == 15 || coalescedEvent.offsetY == 20, "expected offsetY");
+ coalescedEvent.offsetY == 15 || coalescedEvent.offsetY == 20, "expected offsetY");
prevOffsetX = coalescedEvent.offsetX;
prevOffsetY = coalescedEvent.offsetY;
@@ -64,23 +66,24 @@ function runTests() {
ok((coalescedEvent.clientX <= x+2) && (coalescedEvent.clientX >= x-2), "getCoalescedEvents()[" + i + "].clientX");
ok((coalescedEvent.clientY <= y+2) && (coalescedEvent.clientY >= y-2), "getCoalescedEvents()[" + i + "].clientY");
}
+ resolve();
}, { once: true });
+ });
- target0.addEventListener("pointerup", (ev) => {
- utils.restoreNormalRefresh();
- SimpleTest.finish();
- }, { once: true });
+ info("Synthesizing native mouse moves....");
+ await promiseNativeMouseEvent({ type: "mousemove", target: target0, offsetX: 5, offsetY: 5 });
+ await promiseNativeMouseEvent({ type: "mousemove", target: target0, offsetX: 10, offsetY: 10 });
+ await promiseNativeMouseEvent({ type: "mousemove", target: target0, offsetX: 15, offsetY: 15 });
+ await promiseNativeMouseEvent({ type: "mousemove", target: target0, offsetX: 20, offsetY: 20 });
+ utils.restoreNormalRefresh();
+ await waitForPointerMove;
- await promiseNativeMouseEvent({ type: "mousemove", target: target0, offsetX: 5, offsetY: 5 });
- await promiseNativeMouseEvent({ type: "mousemove", target: target0, offsetX: 10, offsetY: 10 });
- await promiseNativeMouseEvent({ type: "mousemove", target: target0, offsetX: 15, offsetY: 15 });
- await promiseNativeMouseEvent({ type: "mousemove", target: target0, offsetX: 20, offsetY: 20 });
- synthesizeNativeMouseEvent({ type: "click", target: target0, offsetX: 20, offsetY: 20 });
- });
-}
+ target0.addEventListener("pointerup", (ev) => {
+ SimpleTest.finish();
+ }, { once: true });
-SimpleTest.waitForFocus(() => {
- SpecialPowers.pushPrefEnv({"set": [["dom.events.coalesce.mousemove", true]]}, runTests);
+ info("Synthesizing a native click....");
+ synthesizeNativeMouseEvent({ type: "click", target: target0, offsetX: 20, offsetY: 20 });
});
</script>
diff --git a/dom/events/test/test_dom_storage_event.html b/dom/events/test/test_dom_storage_event.html
index 88cc288d41..ce9db0dc7f 100644
--- a/dom/events/test/test_dom_storage_event.html
+++ b/dom/events/test/test_dom_storage_event.html
@@ -28,7 +28,7 @@ const kTests = [
{ createEventArg: "storageEvent",
type: "ddd", bubbles: false, cancelable: false,
- key: 'key', oldValue: 'a', newValue: 'b', url: null, storageArea: null },
+ key: 'key', oldValue: 'a', newValue: 'b', url: '', storageArea: null },
{ createEventArg: "StorageEvent",
type: "eee", bubbles: true, cancelable: true,
@@ -36,7 +36,7 @@ const kTests = [
{ createEventArg: "storageevent",
type: "fff", bubbles: false, cancelable: true,
- key: null, oldValue: null, newValue: null, url: null, storageArea: null },
+ key: null, oldValue: null, newValue: null, url: '', storageArea: null },
];
for (var i = 0; i < kTests.length; i++) {
diff --git a/dom/fetch/Fetch.cpp b/dom/fetch/Fetch.cpp
index e2261d2e88..97c4c82fca 100644
--- a/dom/fetch/Fetch.cpp
+++ b/dom/fetch/Fetch.cpp
@@ -661,6 +661,8 @@ already_AddRefed<Promise> FetchRequest(nsIGlobalObject* aGlobal,
actor->SetOriginStack(std::move(stack));
}
+ ipcArgs.isThirdPartyContext() = worker->IsThirdPartyContext();
+
actor->DoFetchOp(ipcArgs);
return p.forget();
@@ -792,7 +794,7 @@ class WorkerFetchResponseRunnable final : public MainThreadWorkerRunnable {
WorkerFetchResponseRunnable(WorkerPrivate* aWorkerPrivate,
WorkerFetchResolver* aResolver,
SafeRefPtr<InternalResponse> aResponse)
- : MainThreadWorkerRunnable(aWorkerPrivate, "WorkerFetchResponseRunnable"),
+ : MainThreadWorkerRunnable("WorkerFetchResponseRunnable"),
mResolver(aResolver),
mInternalResponse(std::move(aResponse)) {
MOZ_ASSERT(mResolver);
@@ -848,7 +850,7 @@ class WorkerDataAvailableRunnable final : public MainThreadWorkerRunnable {
public:
WorkerDataAvailableRunnable(WorkerPrivate* aWorkerPrivate,
WorkerFetchResolver* aResolver)
- : MainThreadWorkerRunnable(aWorkerPrivate, "WorkerDataAvailableRunnable"),
+ : MainThreadWorkerRunnable("WorkerDataAvailableRunnable"),
mResolver(aResolver) {}
bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
@@ -889,8 +891,7 @@ class WorkerFetchResponseEndRunnable final : public MainThreadWorkerRunnable,
WorkerFetchResponseEndRunnable(WorkerPrivate* aWorkerPrivate,
WorkerFetchResolver* aResolver,
FetchDriverObserver::EndReason aReason)
- : MainThreadWorkerRunnable(aWorkerPrivate,
- "WorkerFetchResponseEndRunnable"),
+ : MainThreadWorkerRunnable("WorkerFetchResponseEndRunnable"),
WorkerFetchResponseEndBase(aResolver),
mReason(aReason) {}
@@ -917,7 +918,8 @@ class WorkerFetchResponseEndControlRunnable final
public:
WorkerFetchResponseEndControlRunnable(WorkerPrivate* aWorkerPrivate,
WorkerFetchResolver* aResolver)
- : MainThreadWorkerControlRunnable(aWorkerPrivate),
+ : MainThreadWorkerControlRunnable(
+ "WorkerFetchResponseEndControlRunnable"),
WorkerFetchResponseEndBase(aResolver) {}
bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
@@ -940,7 +942,7 @@ void WorkerFetchResolver::OnResponseAvailableInternal(
RefPtr<WorkerFetchResponseRunnable> r = new WorkerFetchResponseRunnable(
mPromiseProxy->GetWorkerPrivate(), this, std::move(aResponse));
- if (!r->Dispatch()) {
+ if (!r->Dispatch(mPromiseProxy->GetWorkerPrivate())) {
NS_WARNING("Could not dispatch fetch response");
}
}
@@ -960,7 +962,7 @@ void WorkerFetchResolver::OnDataAvailable() {
RefPtr<WorkerDataAvailableRunnable> r =
new WorkerDataAvailableRunnable(mPromiseProxy->GetWorkerPrivate(), this);
- Unused << r->Dispatch();
+ Unused << r->Dispatch(mPromiseProxy->GetWorkerPrivate());
}
void WorkerFetchResolver::OnResponseEnd(FetchDriverObserver::EndReason aReason,
@@ -978,14 +980,14 @@ void WorkerFetchResolver::OnResponseEnd(FetchDriverObserver::EndReason aReason,
RefPtr<WorkerFetchResponseEndRunnable> r = new WorkerFetchResponseEndRunnable(
mPromiseProxy->GetWorkerPrivate(), this, aReason);
- if (!r->Dispatch()) {
+ if (!r->Dispatch(mPromiseProxy->GetWorkerPrivate())) {
RefPtr<WorkerFetchResponseEndControlRunnable> cr =
new WorkerFetchResponseEndControlRunnable(
mPromiseProxy->GetWorkerPrivate(), this);
// This can fail if the worker thread is canceled or killed causing
// the PromiseWorkerProxy to give up its WorkerRef immediately,
// allowing the worker thread to become Dead.
- if (!cr->Dispatch()) {
+ if (!cr->Dispatch(mPromiseProxy->GetWorkerPrivate())) {
NS_WARNING("Failed to dispatch WorkerFetchResponseEndControlRunnable");
}
}
diff --git a/dom/fetch/FetchDriver.cpp b/dom/fetch/FetchDriver.cpp
index 79b54f4c4a..3a4099cf0e 100644
--- a/dom/fetch/FetchDriver.cpp
+++ b/dom/fetch/FetchDriver.cpp
@@ -665,6 +665,13 @@ nsresult FetchDriver::HttpFetch(
nsCOMPtr<nsILoadInfo> loadInfo = chan->LoadInfo();
rv = loadInfo->SetWorkerAssociatedBrowsingContextID(
mAssociatedBrowsingContextID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (mIsThirdPartyWorker.isSome()) {
+ nsCOMPtr<nsILoadInfo> loadInfo = chan->LoadInfo();
+ rv = loadInfo->SetIsInThirdPartyContext(mIsThirdPartyWorker.ref());
+ NS_ENSURE_SUCCESS(rv, rv);
}
// If the fetch is created by FetchEvent.request or NavigationPreload request,
diff --git a/dom/fetch/FetchDriver.h b/dom/fetch/FetchDriver.h
index de01c2a6be..36dd5fb3dd 100644
--- a/dom/fetch/FetchDriver.h
+++ b/dom/fetch/FetchDriver.h
@@ -145,6 +145,10 @@ class FetchDriver final : public nsIChannelEventSink,
mAssociatedBrowsingContextID = aID;
}
+ void SetIsThirdPartyWorker(const Maybe<bool> aIsThirdPartyWorker) {
+ mIsThirdPartyWorker = aIsThirdPartyWorker;
+ }
+
private:
nsCOMPtr<nsIPrincipal> mPrincipal;
nsCOMPtr<nsILoadGroup> mLoadGroup;
@@ -179,6 +183,10 @@ class FetchDriver final : public nsIChannelEventSink,
bool mIsTrackingFetch;
+ // Indicates whether the fetch request is from a third-party worker. Nothing
+ // if the fetch request is not from a worker.
+ Maybe<bool> mIsThirdPartyWorker;
+
RefPtr<AlternativeDataStreamListener> mAltDataListener;
bool mOnStopRequestCalled;
diff --git a/dom/fetch/FetchParent.cpp b/dom/fetch/FetchParent.cpp
index 93e9b9e43e..2784792639 100644
--- a/dom/fetch/FetchParent.cpp
+++ b/dom/fetch/FetchParent.cpp
@@ -103,6 +103,7 @@ IPCResult FetchParent::RecvFetchOp(FetchOpArgs&& aArgs) {
mCookieJarSettings = aArgs.cookieJarSettings();
mNeedOnDataAvailable = aArgs.needOnDataAvailable();
mHasCSPEventListener = aArgs.hasCSPEventListener();
+ mIsThirdPartyContext = aArgs.isThirdPartyContext();
if (mHasCSPEventListener) {
mCSPEventListener =
@@ -173,7 +174,8 @@ IPCResult FetchParent::RecvFetchOp(FetchOpArgs&& aArgs) {
self->mWorkerScript, self->mClientInfo, self->mController,
self->mCookieJarSettings, self->mNeedOnDataAvailable,
self->mCSPEventListener, self->mAssociatedBrowsingContextID,
- self->mBackgroundEventTarget, self->mID})));
+ self->mBackgroundEventTarget, self->mID,
+ self->mIsThirdPartyContext})));
self->mResponsePromises->GetResponseEndPromise()->Then(
GetMainThreadSerialEventTarget(), __func__,
diff --git a/dom/fetch/FetchParent.h b/dom/fetch/FetchParent.h
index e373d93b73..59018e8cde 100644
--- a/dom/fetch/FetchParent.h
+++ b/dom/fetch/FetchParent.h
@@ -97,6 +97,7 @@ class FetchParent final : public PFetchParent {
bool mHasCSPEventListener{false};
bool mExtendForCSPEventListener{false};
uint64_t mAssociatedBrowsingContextID{0};
+ bool mIsThirdPartyContext{true};
Atomic<bool> mIsDone{false};
Atomic<bool> mActorDestroyed{false};
diff --git a/dom/fetch/FetchService.cpp b/dom/fetch/FetchService.cpp
index 77decbc56f..9af967d6af 100644
--- a/dom/fetch/FetchService.cpp
+++ b/dom/fetch/FetchService.cpp
@@ -65,42 +65,42 @@ FetchServicePromises::GetResponseEndPromise() {
}
void FetchServicePromises::ResolveResponseAvailablePromise(
- FetchServiceResponse&& aResponse, const char* aMethodName) {
+ FetchServiceResponse&& aResponse, StaticString aMethodName) {
if (mAvailablePromise) {
mAvailablePromise->Resolve(std::move(aResponse), aMethodName);
}
}
void FetchServicePromises::RejectResponseAvailablePromise(
- const CopyableErrorResult&& aError, const char* aMethodName) {
+ const CopyableErrorResult&& aError, StaticString aMethodName) {
if (mAvailablePromise) {
mAvailablePromise->Reject(aError, aMethodName);
}
}
void FetchServicePromises::ResolveResponseTimingPromise(
- ResponseTiming&& aTiming, const char* aMethodName) {
+ ResponseTiming&& aTiming, StaticString aMethodName) {
if (mTimingPromise) {
mTimingPromise->Resolve(std::move(aTiming), aMethodName);
}
}
void FetchServicePromises::RejectResponseTimingPromise(
- const CopyableErrorResult&& aError, const char* aMethodName) {
+ const CopyableErrorResult&& aError, StaticString aMethodName) {
if (mTimingPromise) {
mTimingPromise->Reject(aError, aMethodName);
}
}
void FetchServicePromises::ResolveResponseEndPromise(ResponseEndArgs&& aArgs,
- const char* aMethodName) {
+ StaticString aMethodName) {
if (mEndPromise) {
mEndPromise->Resolve(std::move(aArgs), aMethodName);
}
}
void FetchServicePromises::RejectResponseEndPromise(
- const CopyableErrorResult&& aError, const char* aMethodName) {
+ const CopyableErrorResult&& aError, StaticString aMethodName) {
if (mEndPromise) {
mEndPromise->Reject(aError, aMethodName);
}
@@ -229,6 +229,7 @@ RefPtr<FetchServicePromises> FetchService::FetchInstance::Fetch() {
}
mFetchDriver->SetAssociatedBrowsingContextID(
args.mAssociatedBrowsingContextID);
+ mFetchDriver->SetIsThirdPartyWorker(Some(args.mIsThirdPartyContext));
}
mFetchDriver->EnableNetworkInterceptControl();
diff --git a/dom/fetch/FetchService.h b/dom/fetch/FetchService.h
index 2b9a4d2163..36c2527ef0 100644
--- a/dom/fetch/FetchService.h
+++ b/dom/fetch/FetchService.h
@@ -48,17 +48,17 @@ class FetchServicePromises final {
RefPtr<FetchServiceResponseEndPromise> GetResponseEndPromise();
void ResolveResponseAvailablePromise(FetchServiceResponse&& aResponse,
- const char* aMethodName);
+ StaticString aMethodName);
void RejectResponseAvailablePromise(const CopyableErrorResult&& aError,
- const char* aMethodName);
+ StaticString aMethodName);
void ResolveResponseTimingPromise(ResponseTiming&& aTiming,
- const char* aMethodName);
+ StaticString aMethodName);
void RejectResponseTimingPromise(const CopyableErrorResult&& aError,
- const char* aMethodName);
+ StaticString aMethodName);
void ResolveResponseEndPromise(ResponseEndArgs&& aArgs,
- const char* aMethodName);
+ StaticString aMethodName);
void RejectResponseEndPromise(const CopyableErrorResult&& aError,
- const char* aMethodName);
+ StaticString aMethodName);
private:
~FetchServicePromises() = default;
@@ -101,6 +101,7 @@ class FetchService final : public nsIObserver {
uint64_t mAssociatedBrowsingContextID;
nsCOMPtr<nsISerialEventTarget> mEventTarget;
nsID mActorID;
+ bool mIsThirdPartyContext;
};
struct UnknownArgs {};
diff --git a/dom/fetch/PFetch.ipdl b/dom/fetch/PFetch.ipdl
index 904ef0738d..7ec1745658 100644
--- a/dom/fetch/PFetch.ipdl
+++ b/dom/fetch/PFetch.ipdl
@@ -24,6 +24,7 @@ struct FetchOpArgs{
bool needOnDataAvailable;
bool hasCSPEventListener;
uint64_t associatedBrowsingContextID;
+ bool isThirdPartyContext;
};
protocol PFetch {
diff --git a/dom/file/BaseBlobImpl.h b/dom/file/BaseBlobImpl.h
index 7265fc2104..860421c56a 100644
--- a/dom/file/BaseBlobImpl.h
+++ b/dom/file/BaseBlobImpl.h
@@ -8,6 +8,7 @@
#define mozilla_dom_BaseBlobImpl_h
#include "nsIGlobalObject.h"
+#include "mozilla/dom/Blob.h"
#include "mozilla/dom/BlobImpl.h"
#include "mozilla/ErrorResult.h"
@@ -29,8 +30,7 @@ class BaseBlobImpl : public BlobImpl {
mLength(aLength),
mSerialNumber(NextSerialNumber()),
mLastModificationDate(aLastModifiedDate) {
- // Ensure non-null mContentType by default
- mContentType.SetIsVoid(false);
+ dom::Blob::MakeValidBlobType(mContentType);
}
// Blob constructor without starting point.
@@ -41,8 +41,7 @@ class BaseBlobImpl : public BlobImpl {
mLength(aLength),
mSerialNumber(NextSerialNumber()),
mLastModificationDate(0) {
- // Ensure non-null mContentType by default
- mContentType.SetIsVoid(false);
+ dom::Blob::MakeValidBlobType(mContentType);
}
// Blob constructor with starting point.
@@ -53,8 +52,7 @@ class BaseBlobImpl : public BlobImpl {
mLength(aLength),
mSerialNumber(NextSerialNumber()),
mLastModificationDate(0) {
- // Ensure non-null mContentType by default
- mContentType.SetIsVoid(false);
+ dom::Blob::MakeValidBlobType(mContentType);
}
void GetName(nsAString& aName) const override;
diff --git a/dom/file/Blob.cpp b/dom/file/Blob.cpp
index 868e501ef9..b3a83d6f0f 100644
--- a/dom/file/Blob.cpp
+++ b/dom/file/Blob.cpp
@@ -50,6 +50,9 @@ NS_IMPL_CYCLE_COLLECTING_ADDREF(Blob)
NS_IMPL_CYCLE_COLLECTING_RELEASE(Blob)
void Blob::MakeValidBlobType(nsAString& aType) {
+ // Ensure non-null content type by default
+ aType.SetIsVoid(false);
+
char16_t* iter = aType.BeginWriting();
char16_t* end = aType.EndWriting();
diff --git a/dom/file/FileReaderSync.cpp b/dom/file/FileReaderSync.cpp
index 04e5325ea3..431d7b7b71 100644
--- a/dom/file/FileReaderSync.cpp
+++ b/dom/file/FileReaderSync.cpp
@@ -315,8 +315,7 @@ class ReadReadyRunnable final : public WorkerSyncRunnable {
public:
ReadReadyRunnable(WorkerPrivate* aWorkerPrivate,
nsIEventTarget* aSyncLoopTarget)
- : WorkerSyncRunnable(aWorkerPrivate, aSyncLoopTarget,
- "ReadReadyRunnable") {}
+ : WorkerSyncRunnable(aSyncLoopTarget, "ReadReadyRunnable") {}
bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
aWorkerPrivate->AssertIsOnWorkerThread();
diff --git a/dom/fs/parent/FileSystemAccessHandle.cpp b/dom/fs/parent/FileSystemAccessHandle.cpp
index 07430ce476..5fb2e02fc4 100644
--- a/dom/fs/parent/FileSystemAccessHandle.cpp
+++ b/dom/fs/parent/FileSystemAccessHandle.cpp
@@ -177,7 +177,7 @@ FileSystemAccessHandle::BeginInit() {
mLocked = true;
- auto CreateAndRejectInitPromise = [](const char* aFunc, nsresult aRv) {
+ auto CreateAndRejectInitPromise = [](StaticString aFunc, nsresult aRv) {
return CreateAndRejectMozPromise<InitPromise>(aFunc, aRv);
};
diff --git a/dom/html/HTMLAnchorElement.cpp b/dom/html/HTMLAnchorElement.cpp
index fffe8d9545..8c1689fe7d 100644
--- a/dom/html/HTMLAnchorElement.cpp
+++ b/dom/html/HTMLAnchorElement.cpp
@@ -14,7 +14,7 @@
#include "nsCOMPtr.h"
#include "nsContentUtils.h"
#include "nsGkAtoms.h"
-#include "nsAttrValueOrString.h"
+#include "mozilla/FocusModel.h"
#include "mozilla/dom/Document.h"
#include "nsPresContext.h"
#include "nsIURI.h"
@@ -68,10 +68,7 @@ nsresult HTMLAnchorElement::BindToTree(BindContext& aContext,
Link::BindToTree(aContext);
// Prefetch links
- if (IsInComposedDoc()) {
- TryDNSPrefetch(*this);
- }
-
+ MaybeTryDNSPrefetch();
return rv;
}
@@ -89,10 +86,10 @@ void HTMLAnchorElement::UnbindFromTree(UnbindContext& aContext) {
Link::UnbindFromTree();
}
-bool HTMLAnchorElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
+bool HTMLAnchorElement::IsHTMLFocusable(IsFocusableFlags aFlags,
+ bool* aIsFocusable,
int32_t* aTabIndex) {
- if (nsGenericHTMLElement::IsHTMLFocusable(aWithMouse, aIsFocusable,
- aTabIndex)) {
+ if (nsGenericHTMLElement::IsHTMLFocusable(aFlags, aIsFocusable, aTabIndex)) {
return true;
}
@@ -122,7 +119,7 @@ bool HTMLAnchorElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
}
}
- if ((sTabFocusModel & eTabFocus_linksMask) == 0) {
+ if (!FocusModel::IsTabFocusable(TabFocusableType::Links)) {
*aTabIndex = -1;
}
*aIsFocusable = true;
@@ -194,8 +191,8 @@ void HTMLAnchorElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName,
if (aNamespaceID == kNameSpaceID_None) {
if (aName == nsGkAtoms::href) {
Link::ResetLinkState(aNotify, !!aValue);
- if (aValue && IsInComposedDoc()) {
- TryDNSPrefetch(*this);
+ if (aValue) {
+ MaybeTryDNSPrefetch();
}
}
}
@@ -210,4 +207,22 @@ void HTMLAnchorElement::AddSizeOfExcludingThis(nsWindowSizes& aSizes,
*aNodeSize += Link::SizeOfExcludingThis(aSizes.mState);
}
+void HTMLAnchorElement::MaybeTryDNSPrefetch() {
+ if (IsInComposedDoc()) {
+ nsIURI* docURI = OwnerDoc()->GetDocumentURI();
+ if (!docURI) {
+ return;
+ }
+
+ bool docIsHttps = docURI->SchemeIs("https");
+ if ((docIsHttps &&
+ StaticPrefs::dom_prefetch_dns_for_anchor_https_document()) ||
+ (!docIsHttps &&
+ StaticPrefs::dom_prefetch_dns_for_anchor_http_document())) {
+ TryDNSPrefetch(
+ *this, HTMLDNSPrefetch::PrefetchSource::AnchorSpeculativePrefetch);
+ }
+ }
+}
+
} // namespace mozilla::dom
diff --git a/dom/html/HTMLAnchorElement.h b/dom/html/HTMLAnchorElement.h
index 88669549e3..f7f808de05 100644
--- a/dom/html/HTMLAnchorElement.h
+++ b/dom/html/HTMLAnchorElement.h
@@ -48,7 +48,7 @@ class HTMLAnchorElement final : public nsGenericHTMLElement,
nsresult BindToTree(BindContext&, nsINode& aParent) override;
void UnbindFromTree(UnbindContext&) override;
- bool IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
+ bool IsHTMLFocusable(IsFocusableFlags, bool* aIsFocusable,
int32_t* aTabIndex) override;
void GetEventTargetParent(EventChainPreVisitor& aVisitor) override;
@@ -189,6 +189,8 @@ class HTMLAnchorElement final : public nsGenericHTMLElement,
protected:
virtual ~HTMLAnchorElement();
+ void MaybeTryDNSPrefetch();
+
JSObject* WrapNode(JSContext*, JS::Handle<JSObject*> aGivenProto) override;
RefPtr<nsDOMTokenList> mRelList;
};
diff --git a/dom/html/HTMLButtonElement.cpp b/dom/html/HTMLButtonElement.cpp
index 0d9cc921ad..7d7fa7f548 100644
--- a/dom/html/HTMLButtonElement.cpp
+++ b/dom/html/HTMLButtonElement.cpp
@@ -12,13 +12,13 @@
#include "nsAttrValueInlines.h"
#include "nsIContentInlines.h"
#include "nsGkAtoms.h"
-#include "nsStyleConsts.h"
#include "nsPresContext.h"
#include "nsIFormControl.h"
#include "nsIFrame.h"
#include "nsIFormControlFrame.h"
#include "mozilla/dom/Document.h"
#include "mozilla/ContentEvents.h"
+#include "mozilla/FocusModel.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/MouseEvents.h"
@@ -113,15 +113,14 @@ void HTMLButtonElement::GetType(nsAString& aType) {
int32_t HTMLButtonElement::TabIndexDefault() { return 0; }
-bool HTMLButtonElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
+bool HTMLButtonElement::IsHTMLFocusable(IsFocusableFlags aFlags,
+ bool* aIsFocusable,
int32_t* aTabIndex) {
if (nsGenericHTMLFormControlElementWithState::IsHTMLFocusable(
- aWithMouse, aIsFocusable, aTabIndex)) {
+ aFlags, aIsFocusable, aTabIndex)) {
return true;
}
-
- *aIsFocusable = IsFormControlDefaultFocusable(aWithMouse) && !IsDisabled();
-
+ *aIsFocusable = IsFormControlDefaultFocusable(aFlags) && !IsDisabled();
return false;
}
diff --git a/dom/html/HTMLButtonElement.h b/dom/html/HTMLButtonElement.h
index b1a5c53f64..d4bfc3649d 100644
--- a/dom/html/HTMLButtonElement.h
+++ b/dom/html/HTMLButtonElement.h
@@ -87,7 +87,7 @@ class HTMLButtonElement final : public nsGenericHTMLFormControlElementWithState,
nsAttrValue& aResult) override;
// nsGenericHTMLElement
- bool IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
+ bool IsHTMLFocusable(IsFocusableFlags, bool* aIsFocusable,
int32_t* aTabIndex) override;
bool IsDisabledForEvents(WidgetEvent* aEvent) override;
diff --git a/dom/html/HTMLCanvasElement.cpp b/dom/html/HTMLCanvasElement.cpp
index 93a7bb3787..a138f2f30a 100644
--- a/dom/html/HTMLCanvasElement.cpp
+++ b/dom/html/HTMLCanvasElement.cpp
@@ -857,6 +857,20 @@ already_AddRefed<CanvasCaptureMediaStream> HTMLCanvasElement::CaptureStream(
return nullptr;
}
+ // Check if we transferred the OffscreenCanvas to a DOM worker. This is not
+ // defined by the spec yet, so it is better to fail now than implement
+ // something not compliant:
+ // https://github.com/w3c/mediacapture-fromelement/issues/65
+ // https://github.com/w3c/mediacapture-extensions/pull/26
+ // https://github.com/web-platform-tests/wpt/issues/21102
+ if (mOffscreenDisplay &&
+ NS_WARN_IF(!mOffscreenDisplay->CanElementCaptureStream())) {
+ aRv.ThrowNotSupportedError(
+ "Capture stream not supported when OffscreenCanvas transferred to "
+ "worker");
+ return nullptr;
+ }
+
auto stream = MakeRefPtr<CanvasCaptureMediaStream>(window, this);
nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
diff --git a/dom/html/HTMLCanvasElement.h b/dom/html/HTMLCanvasElement.h
index 586a43fedc..d5a4e019ff 100644
--- a/dom/html/HTMLCanvasElement.h
+++ b/dom/html/HTMLCanvasElement.h
@@ -356,6 +356,8 @@ class HTMLCanvasElement final : public nsGenericHTMLElement,
layers::ImageContainer* GetImageContainer() const { return mImageContainer; }
+ bool UsingCaptureStream() const { return !!mRequestedFrameRefreshObserver; }
+
protected:
bool mResetLayer;
bool mMaybeModified; // we fetched the context, so we may have written to the
diff --git a/dom/html/HTMLDNSPrefetch.cpp b/dom/html/HTMLDNSPrefetch.cpp
index a4043195fe..779ed23a15 100644
--- a/dom/html/HTMLDNSPrefetch.cpp
+++ b/dom/html/HTMLDNSPrefetch.cpp
@@ -105,7 +105,7 @@ class DeferredDNSPrefetches final : public nsIWebProgressListener,
void Flush();
void SubmitQueue();
- void SubmitQueueEntry(Element&, nsIDNSService::DNSFlags aFlags);
+ void SubmitQueueEntry(Element& aElement, nsIDNSService::DNSFlags aFlags);
uint16_t mHead;
uint16_t mTail;
@@ -206,8 +206,8 @@ nsIDNSService::DNSFlags HTMLDNSPrefetch::PriorityToDNSServiceFlags(
return nsIDNSService::RESOLVE_DEFAULT_FLAGS;
}
-nsresult HTMLDNSPrefetch::Prefetch(SupportsDNSPrefetch& aSupports,
- Element& aElement, Priority aPriority) {
+nsresult HTMLDNSPrefetch::DeferPrefetch(SupportsDNSPrefetch& aSupports,
+ Element& aElement, Priority aPriority) {
MOZ_ASSERT(&ToSupportsDNSPrefetch(aElement) == &aSupports);
if (!(sInitialized && sPrefetches && sDNSListener) || !EnsureDNSService()) {
return NS_ERROR_NOT_AVAILABLE;
@@ -362,10 +362,84 @@ void HTMLDNSPrefetch::ElementDestroyed(Element& aElement,
}
}
-void SupportsDNSPrefetch::TryDNSPrefetch(Element& aOwner) {
+void HTMLDNSPrefetch::SendRequest(Element& aElement,
+ nsIDNSService::DNSFlags aFlags) {
+ auto& supports = ToSupportsDNSPrefetch(aElement);
+
+ nsIURI* uri = supports.GetURIForDNSPrefetch(aElement);
+ if (!uri) {
+ return;
+ }
+
+ nsAutoCString hostName;
+ uri->GetAsciiHost(hostName);
+ if (hostName.IsEmpty()) {
+ return;
+ }
+
+ bool isLocalResource = false;
+ nsresult rv = NS_URIChainHasFlags(
+ uri, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, &isLocalResource);
+ if (NS_FAILED(rv) || isLocalResource) {
+ return;
+ }
+
+ OriginAttributes oa;
+ StoragePrincipalHelper::GetOriginAttributesForNetworkState(
+ aElement.OwnerDoc(), oa);
+
+ bool isHttps = uri->SchemeIs("https");
+
+ if (IsNeckoChild()) {
+ // during shutdown gNeckoChild might be null
+ if (gNeckoChild) {
+ gNeckoChild->SendHTMLDNSPrefetch(NS_ConvertUTF8toUTF16(hostName), isHttps,
+ oa, aFlags);
+ }
+ } else {
+ nsCOMPtr<nsICancelable> tmpOutstanding;
+
+ rv = sDNSService->AsyncResolveNative(
+ hostName, nsIDNSService::RESOLVE_TYPE_DEFAULT,
+ aFlags | nsIDNSService::RESOLVE_SPECULATE, nullptr, sDNSListener,
+ nullptr, oa, getter_AddRefs(tmpOutstanding));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ // Fetch HTTPS RR if needed.
+ if (StaticPrefs::network_dns_upgrade_with_https_rr() ||
+ StaticPrefs::network_dns_use_https_rr_as_altsvc()) {
+ sDNSService->AsyncResolveNative(
+ hostName, nsIDNSService::RESOLVE_TYPE_HTTPSSVC,
+ aFlags | nsIDNSService::RESOLVE_SPECULATE, nullptr, sDNSListener,
+ nullptr, oa, getter_AddRefs(tmpOutstanding));
+ }
+ }
+
+ // Tell element that deferred prefetch was requested.
+ supports.DNSPrefetchRequestStarted();
+}
+
+void SupportsDNSPrefetch::TryDNSPrefetch(
+ Element& aOwner, HTMLDNSPrefetch::PrefetchSource aSource) {
MOZ_ASSERT(aOwner.IsInComposedDoc());
if (HTMLDNSPrefetch::IsAllowed(aOwner.OwnerDoc())) {
- HTMLDNSPrefetch::Prefetch(*this, aOwner, HTMLDNSPrefetch::Priority::Low);
+ if (!(sInitialized && sDNSListener) || !EnsureDNSService()) {
+ return;
+ }
+
+ if (aSource == HTMLDNSPrefetch::PrefetchSource::AnchorSpeculativePrefetch) {
+ HTMLDNSPrefetch::DeferPrefetch(*this, aOwner,
+ HTMLDNSPrefetch::Priority::Low);
+ } else if (aSource == HTMLDNSPrefetch::PrefetchSource::LinkDnsPrefetch) {
+ HTMLDNSPrefetch::SendRequest(
+ aOwner, GetDNSFlagsFromElement(aOwner) |
+ HTMLDNSPrefetch::PriorityToDNSServiceFlags(
+ HTMLDNSPrefetch::Priority::High));
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unknown DNS prefetch type");
+ }
}
}
@@ -470,59 +544,7 @@ void DeferredDNSPrefetches::SubmitQueueEntry(Element& aElement,
return;
}
- nsIURI* uri = supports.GetURIForDNSPrefetch(aElement);
- if (!uri) {
- return;
- }
-
- nsAutoCString hostName;
- uri->GetAsciiHost(hostName);
- if (hostName.IsEmpty()) {
- return;
- }
-
- bool isLocalResource = false;
- nsresult rv = NS_URIChainHasFlags(
- uri, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, &isLocalResource);
- if (NS_FAILED(rv) || isLocalResource) {
- return;
- }
-
- OriginAttributes oa;
- StoragePrincipalHelper::GetOriginAttributesForNetworkState(
- aElement.OwnerDoc(), oa);
-
- bool isHttps = uri->SchemeIs("https");
-
- if (IsNeckoChild()) {
- // during shutdown gNeckoChild might be null
- if (gNeckoChild) {
- gNeckoChild->SendHTMLDNSPrefetch(NS_ConvertUTF8toUTF16(hostName), isHttps,
- oa, mEntries[mTail].mFlags);
- }
- } else {
- nsCOMPtr<nsICancelable> tmpOutstanding;
-
- rv = sDNSService->AsyncResolveNative(
- hostName, nsIDNSService::RESOLVE_TYPE_DEFAULT,
- mEntries[mTail].mFlags | nsIDNSService::RESOLVE_SPECULATE, nullptr,
- sDNSListener, nullptr, oa, getter_AddRefs(tmpOutstanding));
- if (NS_FAILED(rv)) {
- return;
- }
-
- // Fetch HTTPS RR if needed.
- if (StaticPrefs::network_dns_upgrade_with_https_rr() ||
- StaticPrefs::network_dns_use_https_rr_as_altsvc()) {
- sDNSService->AsyncResolveNative(
- hostName, nsIDNSService::RESOLVE_TYPE_HTTPSSVC,
- mEntries[mTail].mFlags | nsIDNSService::RESOLVE_SPECULATE, nullptr,
- sDNSListener, nullptr, oa, getter_AddRefs(tmpOutstanding));
- }
- }
-
- // Tell element that deferred prefetch was requested.
- supports.DNSPrefetchRequestStarted();
+ HTMLDNSPrefetch::SendRequest(aElement, aFlags);
}
void DeferredDNSPrefetches::Activate() {
diff --git a/dom/html/HTMLDNSPrefetch.h b/dom/html/HTMLDNSPrefetch.h
index 5820a6ecb2..5fd263d3f5 100644
--- a/dom/html/HTMLDNSPrefetch.h
+++ b/dom/html/HTMLDNSPrefetch.h
@@ -55,7 +55,13 @@ class HTMLDNSPrefetch {
Medium,
High,
};
- static nsresult Prefetch(SupportsDNSPrefetch&, Element&, Priority);
+ enum class PrefetchSource {
+ LinkDnsPrefetch,
+ AnchorSpeculativePrefetch,
+ };
+ static nsresult DeferPrefetch(SupportsDNSPrefetch& aSupports,
+ Element& aElement, Priority aPriority);
+ static void SendRequest(Element& aElement, nsIDNSService::DNSFlags aFlags);
static nsresult Prefetch(
const nsAString& host, bool isHttps,
const OriginAttributes& aPartitionedPrincipalOriginAttributes,
@@ -68,9 +74,9 @@ class HTMLDNSPrefetch {
nsresult aReason);
static void ElementDestroyed(Element&, SupportsDNSPrefetch&);
- private:
static nsIDNSService::DNSFlags PriorityToDNSServiceFlags(Priority);
+ private:
static nsresult Prefetch(
const nsAString& host, bool isHttps,
const OriginAttributes& aPartitionedPrincipalOriginAttributes,
@@ -114,8 +120,8 @@ class SupportsDNSPrefetch {
mDNSPrefetchDeferred(false),
mDestroyedCalled(false) {}
- void CancelDNSPrefetch(Element&);
- void TryDNSPrefetch(Element&);
+ void CancelDNSPrefetch(Element& aOwner);
+ void TryDNSPrefetch(Element& aOwner, HTMLDNSPrefetch::PrefetchSource aSource);
// This MUST be called on the destructor of the Element subclass.
// Our own destructor ensures that.
diff --git a/dom/html/HTMLDetailsElement.h b/dom/html/HTMLDetailsElement.h
index 2c7ed56d98..03df1f07a1 100644
--- a/dom/html/HTMLDetailsElement.h
+++ b/dom/html/HTMLDetailsElement.h
@@ -48,7 +48,7 @@ class HTMLDetailsElement final : public nsGenericHTMLElement {
void ToggleOpen() { SetOpen(!Open(), IgnoreErrors()); }
- virtual void AsyncEventRunning(AsyncEventDispatcher* aEvent) override;
+ void AsyncEventRunning(AsyncEventDispatcher* aEvent) override;
void HandleInvokeInternal(nsAtom* aAction, ErrorResult& aRv) override;
diff --git a/dom/html/HTMLDialogElement.cpp b/dom/html/HTMLDialogElement.cpp
index cd36201182..3e717534d2 100644
--- a/dom/html/HTMLDialogElement.cpp
+++ b/dom/html/HTMLDialogElement.cpp
@@ -157,7 +157,7 @@ void HTMLDialogElement::FocusDialog() {
RefPtr<Element> control = HasAttr(nsGkAtoms::autofocus)
? this
- : GetFocusDelegate(false /* aWithMouse */);
+ : GetFocusDelegate(IsFocusableFlags(0));
// If there isn't one of those either, then let control be subject.
if (!control) {
diff --git a/dom/html/HTMLEmbedElement.cpp b/dom/html/HTMLEmbedElement.cpp
index 39582063a7..7cd9fd916c 100644
--- a/dom/html/HTMLEmbedElement.cpp
+++ b/dom/html/HTMLEmbedElement.cpp
@@ -133,8 +133,8 @@ int32_t HTMLEmbedElement::TabIndexDefault() {
return Type() == ObjectType::Document ? 0 : -1;
}
-bool HTMLEmbedElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
- int32_t* aTabIndex) {
+bool HTMLEmbedElement::IsHTMLFocusable(IsFocusableFlags aFlags,
+ bool* aIsFocusable, int32_t* aTabIndex) {
// Has non-plugin content: let the plugin decide what to do in terms of
// internal focus from mouse clicks
if (aTabIndex) {
diff --git a/dom/html/HTMLEmbedElement.h b/dom/html/HTMLEmbedElement.h
index 8e7bcd4166..4d7f1496e8 100644
--- a/dom/html/HTMLEmbedElement.h
+++ b/dom/html/HTMLEmbedElement.h
@@ -38,7 +38,7 @@ class HTMLEmbedElement final : public nsGenericHTMLElement,
nsresult BindToTree(BindContext&, nsINode& aParent) override;
void UnbindFromTree(UnbindContext&) override;
- bool IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
+ bool IsHTMLFocusable(IsFocusableFlags, bool* aIsFocusable,
int32_t* aTabIndex) override;
int32_t TabIndexDefault() override;
diff --git a/dom/html/HTMLImageElement.cpp b/dom/html/HTMLImageElement.cpp
index 691216d152..597d7f4b22 100644
--- a/dom/html/HTMLImageElement.cpp
+++ b/dom/html/HTMLImageElement.cpp
@@ -6,6 +6,7 @@
#include "mozilla/dom/HTMLImageElement.h"
#include "mozilla/PresShell.h"
+#include "mozilla/FocusModel.h"
#include "mozilla/dom/BindContext.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/HTMLImageElementBinding.h"
@@ -488,13 +489,13 @@ nsINode* HTMLImageElement::GetScopeChainParent() const {
return nsGenericHTMLElement::GetScopeChainParent();
}
-bool HTMLImageElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
- int32_t* aTabIndex) {
+bool HTMLImageElement::IsHTMLFocusable(IsFocusableFlags aFlags,
+ bool* aIsFocusable, int32_t* aTabIndex) {
int32_t tabIndex = TabIndex();
if (IsInComposedDoc() && FindImageMap()) {
// Use tab index on individual map areas.
- *aTabIndex = (sTabFocusModel & eTabFocus_linksMask) ? 0 : -1;
+ *aTabIndex = FocusModel::IsTabFocusable(TabFocusableType::Links) ? 0 : -1;
// Image map is not focusable itself, but flag as tabbable
// so that image map areas get walked into.
*aIsFocusable = false;
@@ -502,8 +503,10 @@ bool HTMLImageElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
}
// Can be in tab order if tabindex >=0 and form controls are tabbable.
- *aTabIndex = (sTabFocusModel & eTabFocus_formElementsMask) ? tabIndex : -1;
- *aIsFocusable = IsFormControlDefaultFocusable(aWithMouse) &&
+ *aTabIndex = FocusModel::IsTabFocusable(TabFocusableType::FormElements)
+ ? tabIndex
+ : -1;
+ *aIsFocusable = IsFormControlDefaultFocusable(aFlags) &&
(tabIndex >= 0 || GetTabIndexAttrValue().isSome());
return false;
diff --git a/dom/html/HTMLImageElement.h b/dom/html/HTMLImageElement.h
index bf41c68d40..1097d1fb04 100644
--- a/dom/html/HTMLImageElement.h
+++ b/dom/html/HTMLImageElement.h
@@ -70,7 +70,7 @@ class HTMLImageElement final : public nsGenericHTMLElement,
void GetEventTargetParent(EventChainPreVisitor& aVisitor) override;
nsINode* GetScopeChainParent() const override;
- bool IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
+ bool IsHTMLFocusable(IsFocusableFlags, bool* aIsFocusable,
int32_t* aTabIndex) override;
nsresult BindToTree(BindContext&, nsINode& aParent) override;
diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp
index 958601616b..9a7f7e02bd 100644
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -1986,18 +1986,21 @@ Decimal HTMLInputElement::GetStepBase() const {
return kDefaultStepBase;
}
-nsresult HTMLInputElement::GetValueIfStepped(int32_t aStep,
- StepCallerType aCallerType,
- Decimal* aNextStep) {
+Decimal HTMLInputElement::GetValueIfStepped(int32_t aStep,
+ StepCallerType aCallerType,
+ ErrorResult& aRv) {
+ constexpr auto kNaN = Decimal::nan();
if (!DoStepDownStepUpApply()) {
- return NS_ERROR_DOM_INVALID_STATE_ERR;
+ aRv.ThrowInvalidStateError("Step doesn't apply to this input type");
+ return kNaN;
}
Decimal stepBase = GetStepBase();
Decimal step = GetStep();
if (step == kStepAny) {
- if (aCallerType != CALLED_FOR_USER_EVENT) {
- return NS_ERROR_DOM_INVALID_STATE_ERR;
+ if (aCallerType != StepCallerType::ForUserEvent) {
+ aRv.ThrowInvalidStateError("Can't step an input with step=\"any\"");
+ return kNaN;
}
// Allow the spin buttons and up/down arrow keys to do something sensible:
step = GetDefaultStep();
@@ -2015,7 +2018,7 @@ nsresult HTMLInputElement::GetValueIfStepped(int32_t aStep,
// adjustment to align maximum on a step, or else (if we adjusted
// maximum) there is no valid step between minimum and the unadjusted
// maximum.
- return NS_OK;
+ return kNaN;
}
}
}
@@ -2062,25 +2065,20 @@ nsresult HTMLInputElement::GetValueIfStepped(int32_t aStep,
(aStep < 0 && value > valueBeforeStepping))) {
// We don't want step-up to effectively step down, or step-down to
// effectively step up, so return;
- return NS_OK;
+ return kNaN;
}
- *aNextStep = value;
- return NS_OK;
+ return value;
}
-nsresult HTMLInputElement::ApplyStep(int32_t aStep) {
- Decimal nextStep = Decimal::nan(); // unchanged if value will not change
-
- nsresult rv = GetValueIfStepped(aStep, CALLED_FOR_SCRIPT, &nextStep);
-
- if (NS_SUCCEEDED(rv) && nextStep.isFinite()) {
- // We know we're not a file input, so the caller type does not matter; just
- // pass "not system" to be safe.
- SetValue(nextStep, CallerType::NonSystem);
+void HTMLInputElement::ApplyStep(int32_t aStep, ErrorResult& aRv) {
+ Decimal nextStep = GetValueIfStepped(aStep, StepCallerType::ForScript, aRv);
+ if (aRv.Failed() || !nextStep.isFinite()) {
+ return;
}
-
- return rv;
+ // We know we're not a file input, so the caller type does not matter; just
+ // pass "not system" to be safe.
+ SetValue(nextStep, CallerType::NonSystem);
}
bool HTMLInputElement::IsDateTimeInputType(FormControlType aType) {
@@ -3520,11 +3518,9 @@ void HTMLInputElement::StepNumberControlForUserEvent(int32_t aDirection) {
}
}
- Decimal newValue = Decimal::nan(); // unchanged if value will not change
-
- nsresult rv = GetValueIfStepped(aDirection, CALLED_FOR_USER_EVENT, &newValue);
-
- if (NS_FAILED(rv) || !newValue.isFinite()) {
+ Decimal newValue = GetValueIfStepped(aDirection, StepCallerType::ForUserEvent,
+ IgnoreErrors());
+ if (!newValue.isFinite()) {
return; // value should not or will not change
}
@@ -3794,6 +3790,12 @@ nsresult HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
if (mType == FormControlType::InputRadio && keyEvent->IsTrusted() &&
!keyEvent->IsAlt() && !keyEvent->IsControl() &&
!keyEvent->IsMeta()) {
+ // Radio button navigation needs to check visibility, so flush
+ // to ensure visibility is up to date.
+ if (Document* doc = GetComposedDoc()) {
+ doc->FlushPendingNotifications(
+ FlushType::EnsurePresShellInitAndFrames);
+ }
rv = MaybeHandleRadioButtonNavigation(aVisitor, keyEvent->mKeyCode);
}
@@ -6427,10 +6429,10 @@ void HTMLInputElement::RemoveFromRadioGroup() {
mRadioGroupContainer = nullptr;
}
-bool HTMLInputElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
- int32_t* aTabIndex) {
+bool HTMLInputElement::IsHTMLFocusable(IsFocusableFlags aFlags,
+ bool* aIsFocusable, int32_t* aTabIndex) {
if (nsGenericHTMLFormControlElementWithState::IsHTMLFocusable(
- aWithMouse, aIsFocusable, aTabIndex)) {
+ aFlags, aIsFocusable, aTabIndex)) {
return true;
}
@@ -6444,7 +6446,7 @@ bool HTMLInputElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
return false;
}
- const bool defaultFocusable = IsFormControlDefaultFocusable(aWithMouse);
+ const bool defaultFocusable = IsFormControlDefaultFocusable(aFlags);
if (CreatesDateTimeWidget()) {
if (aTabIndex) {
// We only want our native anonymous child to be tabable to, not ourself.
diff --git a/dom/html/HTMLInputElement.h b/dom/html/HTMLInputElement.h
index 2c90aa83fe..aa8bff8864 100644
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -162,7 +162,7 @@ class HTMLInputElement final : public TextControlElement,
void FieldSetDisabledChanged(bool aNotify) override;
// nsIContent
- bool IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
+ bool IsHTMLFocusable(IsFocusableFlags, bool* aIsFocusable,
int32_t* aTabIndex) override;
bool ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
@@ -663,9 +663,8 @@ class HTMLInputElement final : public TextControlElement,
SetUnsignedIntAttr(nsGkAtoms::width, aValue, 0, aRv);
}
- void StepUp(int32_t aN, ErrorResult& aRv) { aRv = ApplyStep(aN); }
-
- void StepDown(int32_t aN, ErrorResult& aRv) { aRv = ApplyStep(-aN); }
+ void StepUp(int32_t aN, ErrorResult& aRv) { ApplyStep(aN, aRv); }
+ void StepDown(int32_t aN, ErrorResult& aRv) { ApplyStep(-aN, aRv); }
/**
* Returns the current step value.
@@ -1311,22 +1310,17 @@ class HTMLInputElement final : public TextControlElement,
*/
Decimal GetDefaultStep() const;
- enum StepCallerType { CALLED_FOR_USER_EVENT, CALLED_FOR_SCRIPT };
+ enum class StepCallerType { ForUserEvent, ForScript };
/**
- * Sets the aValue outparam to the value that this input would take if
- * someone tries to step aStep steps and this input's value would change as
- * a result. Leaves aValue untouched if this inputs value would not change
- * (e.g. already at max, and asking for the next step up).
+ * Returns the value that this input would take if someone tries to step
+ * aStepCount steps and this input's value would change as a result, or
+ * Decimal::nan() otherwise (e.g., if this inputs value would not change due
+ * to it being already at max, and asking for the next step up).
*
* Negative aStep means step down, positive means step up.
- *
- * Returns NS_OK or else the error values that should be thrown if this call
- * was initiated by a stepUp()/stepDown() call from script under conditions
- * that such a call should throw.
*/
- nsresult GetValueIfStepped(int32_t aStepCount, StepCallerType aCallerType,
- Decimal* aNextStep);
+ Decimal GetValueIfStepped(int32_t aStepCount, StepCallerType, ErrorResult&);
/**
* Apply a step change from stepUp or stepDown by multiplying aStep by the
@@ -1334,7 +1328,7 @@ class HTMLInputElement final : public TextControlElement,
*
* @param aStep The value used to be multiplied against the step value.
*/
- nsresult ApplyStep(int32_t aStep);
+ void ApplyStep(int32_t aStep, ErrorResult&);
/**
* Returns if the current type is an experimental mobile type.
diff --git a/dom/html/HTMLLinkElement.cpp b/dom/html/HTMLLinkElement.cpp
index cb316a6f7b..30994cf964 100644
--- a/dom/html/HTMLLinkElement.cpp
+++ b/dom/html/HTMLLinkElement.cpp
@@ -555,7 +555,7 @@ void HTMLLinkElement::
}
if (linkTypes & eDNS_PREFETCH) {
- TryDNSPrefetch(*this);
+ TryDNSPrefetch(*this, HTMLDNSPrefetch::PrefetchSource::LinkDnsPrefetch);
}
}
diff --git a/dom/html/HTMLMediaElement.cpp b/dom/html/HTMLMediaElement.cpp
index 877f3ec3ba..2db729fa2f 100644
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -318,7 +318,7 @@ class HTMLMediaElement::MediaControlKeyListener final
MOZ_INIT_OUTSIDE_CTOR explicit MediaControlKeyListener(
HTMLMediaElement* aElement)
- : mElement(aElement) {
+ : mElement(aElement), mElementId(nsID::GenerateUUID()) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aElement);
}
@@ -411,6 +411,33 @@ class HTMLMediaElement::MediaControlKeyListener final
}
}
+ void NotifyMediaPositionState() {
+ if (!IsStarted()) {
+ return;
+ }
+
+ MOZ_ASSERT(mControlAgent);
+ auto* owner = Owner();
+ PositionState state(owner->Duration(), owner->PlaybackRate(),
+ owner->CurrentTime(), TimeStamp::Now());
+ MEDIACONTROL_LOG(
+ "Notify media position state (duration=%f, playbackRate=%f, "
+ "position=%f)",
+ state.mDuration, state.mPlaybackRate,
+ state.mLastReportedPlaybackPosition);
+ mControlAgent->UpdateGuessedPositionState(mOwnerBrowsingContextId,
+ mElementId, Some(state));
+ }
+
+ void Shutdown() {
+ StopIfNeeded();
+ if (!mControlAgent) {
+ return;
+ }
+ mControlAgent->UpdateGuessedPositionState(mOwnerBrowsingContextId,
+ mElementId, Nothing());
+ }
+
// This method can be called before the listener starts, which would cache
// the audible state and update after the listener starts.
void UpdateMediaAudibleState(bool aIsOwnerAudible) {
@@ -543,6 +570,10 @@ class HTMLMediaElement::MediaControlKeyListener final
MOZ_ASSERT(mState != aState, "Should not notify same state again!");
mState = aState;
mControlAgent->NotifyMediaPlaybackChanged(mOwnerBrowsingContextId, mState);
+
+ if (aState == MediaPlaybackState::ePlayed) {
+ NotifyMediaPositionState();
+ }
}
void NotifyAudibleStateChanged(MediaAudibleState aState) {
@@ -557,6 +588,7 @@ class HTMLMediaElement::MediaControlKeyListener final
bool mIsPictureInPictureEnabled = false;
bool mIsOwnerAudible = false;
MOZ_INIT_OUTSIDE_CTOR uint64_t mOwnerBrowsingContextId;
+ const nsID mElementId;
};
class HTMLMediaElement::MediaStreamTrackListener
@@ -994,7 +1026,7 @@ class HTMLMediaElement::MediaStreamRenderer {
graph->CreateSourceTrack(MediaSegment::AUDIO));
}
- void ResolveAudioDevicePromiseIfExists(const char* aMethodName) {
+ void ResolveAudioDevicePromiseIfExists(StaticString aMethodName) {
if (mSetAudioDevicePromise.IsEmpty()) {
return;
}
@@ -2065,7 +2097,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLMediaElement,
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSeekDOMPromise)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSetMediaKeysDOMPromise)
if (tmp->mMediaControlKeyListener) {
- tmp->mMediaControlKeyListener->StopIfNeeded();
+ tmp->mMediaControlKeyListener->Shutdown();
}
if (tmp->mEventBlocker) {
tmp->mEventBlocker->Shutdown();
@@ -3384,6 +3416,8 @@ void HTMLMediaElement::Seek(double aTime, SeekTarget::Type aSeekType,
// We changed whether we're seeking so we need to AddRemoveSelfReference.
AddRemoveSelfReference();
+
+ mMediaControlKeyListener->NotifyMediaPositionState();
}
double HTMLMediaElement::Duration() const {
@@ -4804,10 +4838,9 @@ void HTMLMediaElement::DoneCreatingElement() {
}
}
-bool HTMLMediaElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
- int32_t* aTabIndex) {
- if (nsGenericHTMLElement::IsHTMLFocusable(aWithMouse, aIsFocusable,
- aTabIndex)) {
+bool HTMLMediaElement::IsHTMLFocusable(IsFocusableFlags aFlags,
+ bool* aIsFocusable, int32_t* aTabIndex) {
+ if (nsGenericHTMLElement::IsHTMLFocusable(aFlags, aIsFocusable, aTabIndex)) {
return true;
}
@@ -6871,6 +6904,7 @@ void HTMLMediaElement::SetPlaybackRate(double aPlaybackRate, ErrorResult& aRv) {
mDecoder->SetPlaybackRate(ClampPlaybackRate(mPlaybackRate));
}
DispatchAsyncEvent(u"ratechange"_ns);
+ mMediaControlKeyListener->NotifyMediaPositionState();
}
void HTMLMediaElement::SetPreservesPitch(bool aPreservesPitch) {
diff --git a/dom/html/HTMLMediaElement.h b/dom/html/HTMLMediaElement.h
index 6c159a7971..2065796b08 100644
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -196,24 +196,24 @@ class HTMLMediaElement : public nsGenericHTMLElement,
void NodeInfoChanged(Document* aOldDoc) override;
- virtual bool ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
- const nsAString& aValue,
- nsIPrincipal* aMaybeScriptedPrincipal,
- nsAttrValue& aResult) override;
+ bool ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
+ const nsAString& aValue,
+ nsIPrincipal* aMaybeScriptedPrincipal,
+ nsAttrValue& aResult) override;
- virtual nsresult BindToTree(BindContext&, nsINode& aParent) override;
- virtual void UnbindFromTree(UnbindContext&) override;
- virtual void DoneCreatingElement() override;
+ nsresult BindToTree(BindContext&, nsINode& aParent) override;
+ void UnbindFromTree(UnbindContext&) override;
+ void DoneCreatingElement() override;
- virtual bool IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
- int32_t* aTabIndex) override;
- virtual int32_t TabIndexDefault() override;
+ bool IsHTMLFocusable(IsFocusableFlags, bool* aIsFocusable,
+ int32_t* aTabIndex) override;
+ int32_t TabIndexDefault() override;
// Called by the video decoder object, on the main thread,
// when it has read the metadata containing video dimensions,
// etc.
- virtual void MetadataLoaded(const MediaInfo* aInfo,
- UniquePtr<const MetadataTags> aTags) final;
+ void MetadataLoaded(const MediaInfo* aInfo,
+ UniquePtr<const MetadataTags> aTags) final;
// Called by the decoder object, on the main thread,
// when it has read the first frame of the video or audio.
diff --git a/dom/html/HTMLObjectElement.cpp b/dom/html/HTMLObjectElement.cpp
index f77a1f3ba2..d449e48407 100644
--- a/dom/html/HTMLObjectElement.cpp
+++ b/dom/html/HTMLObjectElement.cpp
@@ -135,7 +135,8 @@ void HTMLObjectElement::AfterMaybeChangeAttr(int32_t aNamespaceID,
}));
}
-bool HTMLObjectElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
+bool HTMLObjectElement::IsHTMLFocusable(IsFocusableFlags aFlags,
+ bool* aIsFocusable,
int32_t* aTabIndex) {
// TODO: this should probably be managed directly by IsHTMLFocusable.
// See bug 597242.
diff --git a/dom/html/HTMLObjectElement.h b/dom/html/HTMLObjectElement.h
index 00bcbc70d5..4e4eaf181e 100644
--- a/dom/html/HTMLObjectElement.h
+++ b/dom/html/HTMLObjectElement.h
@@ -42,7 +42,7 @@ class HTMLObjectElement final : public nsGenericHTMLFormControlElement,
nsresult BindToTree(BindContext&, nsINode& aParent) override;
void UnbindFromTree(UnbindContext&) override;
- bool IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
+ bool IsHTMLFocusable(IsFocusableFlags, bool* aIsFocusable,
int32_t* aTabIndex) override;
// Overriden nsIFormControl methods
diff --git a/dom/html/HTMLSelectElement.cpp b/dom/html/HTMLSelectElement.cpp
index 6ca4209cd9..7a1b5ccf0d 100644
--- a/dom/html/HTMLSelectElement.cpp
+++ b/dom/html/HTMLSelectElement.cpp
@@ -1026,10 +1026,11 @@ void HTMLSelectElement::SetValue(const nsAString& aValue) {
int32_t HTMLSelectElement::TabIndexDefault() { return 0; }
-bool HTMLSelectElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
+bool HTMLSelectElement::IsHTMLFocusable(IsFocusableFlags aFlags,
+ bool* aIsFocusable,
int32_t* aTabIndex) {
if (nsGenericHTMLFormControlElementWithState::IsHTMLFocusable(
- aWithMouse, aIsFocusable, aTabIndex)) {
+ aFlags, aIsFocusable, aTabIndex)) {
return true;
}
diff --git a/dom/html/HTMLSelectElement.h b/dom/html/HTMLSelectElement.h
index 1ba5dc29f6..401748b9d1 100644
--- a/dom/html/HTMLSelectElement.h
+++ b/dom/html/HTMLSelectElement.h
@@ -198,7 +198,7 @@ class HTMLSelectElement final : public nsGenericHTMLFormControlElementWithState,
// nsIContent
void GetEventTargetParent(EventChainPreVisitor& aVisitor) override;
- bool IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
+ bool IsHTMLFocusable(IsFocusableFlags, bool* aIsFocusable,
int32_t* aTabIndex) override;
void InsertChildBefore(nsIContent* aKid, nsIContent* aBeforeThis,
bool aNotify, ErrorResult& aRv) override;
diff --git a/dom/html/HTMLSharedElement.cpp b/dom/html/HTMLSharedElement.cpp
index 85849f9f79..0dd151f473 100644
--- a/dom/html/HTMLSharedElement.cpp
+++ b/dom/html/HTMLSharedElement.cpp
@@ -85,15 +85,22 @@ static void SetBaseURIUsingFirstBaseWithHref(Document* aDocument,
getter_AddRefs(newBaseURI), href, aDocument,
aDocument->GetFallbackBaseURI());
+ // Vaguely based on
+ // <https://html.spec.whatwg.org/multipage/semantics.html#set-the-frozen-base-url>
+
+ if (newBaseURI && (newBaseURI->SchemeIs("data") ||
+ newBaseURI->SchemeIs("javascript"))) {
+ newBaseURI = nullptr;
+ }
+
// Check if CSP allows this base-uri
- nsresult rv = NS_OK;
nsCOMPtr<nsIContentSecurityPolicy> csp = aDocument->GetCsp();
if (csp && newBaseURI) {
// base-uri is only enforced if explicitly defined in the
// policy - do *not* consult default-src, see:
// http://www.w3.org/TR/CSP2/#directive-default-src
bool cspPermitsBaseURI = true;
- rv = csp->Permits(
+ nsresult rv = csp->Permits(
child->AsElement(), nullptr /* nsICSPEventListener */, newBaseURI,
nsIContentSecurityPolicy::BASE_URI_DIRECTIVE, true /* aSpecific */,
true /* aSendViolationReports */, &cspPermitsBaseURI);
@@ -101,6 +108,7 @@ static void SetBaseURIUsingFirstBaseWithHref(Document* aDocument,
newBaseURI = nullptr;
}
}
+
aDocument->SetBaseURI(newBaseURI);
aDocument->SetChromeXHRDocBaseURI(nullptr);
return;
diff --git a/dom/html/HTMLSummaryElement.cpp b/dom/html/HTMLSummaryElement.cpp
index d1fcf22598..1422bb97be 100644
--- a/dom/html/HTMLSummaryElement.cpp
+++ b/dom/html/HTMLSummaryElement.cpp
@@ -68,10 +68,11 @@ nsresult HTMLSummaryElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
return rv;
}
-bool HTMLSummaryElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
+bool HTMLSummaryElement::IsHTMLFocusable(IsFocusableFlags aFlags,
+ bool* aIsFocusable,
int32_t* aTabIndex) {
- bool disallowOverridingFocusability = nsGenericHTMLElement::IsHTMLFocusable(
- aWithMouse, aIsFocusable, aTabIndex);
+ bool disallowOverridingFocusability =
+ nsGenericHTMLElement::IsHTMLFocusable(aFlags, aIsFocusable, aTabIndex);
if (disallowOverridingFocusability || !IsMainSummary()) {
return disallowOverridingFocusability;
diff --git a/dom/html/HTMLSummaryElement.h b/dom/html/HTMLSummaryElement.h
index b70e8eebbb..d61f3813aa 100644
--- a/dom/html/HTMLSummaryElement.h
+++ b/dom/html/HTMLSummaryElement.h
@@ -30,7 +30,7 @@ class HTMLSummaryElement final : public nsGenericHTMLElement {
nsresult PostHandleEvent(EventChainPostVisitor& aVisitor) override;
- bool IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
+ bool IsHTMLFocusable(IsFocusableFlags, bool* aIsFocusable,
int32_t* aTabIndex) override;
int32_t TabIndexDefault() override;
diff --git a/dom/html/HTMLTextAreaElement.cpp b/dom/html/HTMLTextAreaElement.cpp
index be4ba5a891..7624d53b85 100644
--- a/dom/html/HTMLTextAreaElement.cpp
+++ b/dom/html/HTMLTextAreaElement.cpp
@@ -133,10 +133,11 @@ HTMLTextAreaElement::SelectAll(nsPresContext* aPresContext) {
return NS_OK;
}
-bool HTMLTextAreaElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
+bool HTMLTextAreaElement::IsHTMLFocusable(IsFocusableFlags aFlags,
+ bool* aIsFocusable,
int32_t* aTabIndex) {
if (nsGenericHTMLFormControlElementWithState::IsHTMLFocusable(
- aWithMouse, aIsFocusable, aTabIndex)) {
+ aFlags, aIsFocusable, aTabIndex)) {
return true;
}
diff --git a/dom/html/HTMLTextAreaElement.h b/dom/html/HTMLTextAreaElement.h
index 0adaa48b21..d14523ae1c 100644
--- a/dom/html/HTMLTextAreaElement.h
+++ b/dom/html/HTMLTextAreaElement.h
@@ -122,7 +122,7 @@ class HTMLTextAreaElement final : public TextControlElement,
nsresult PreHandleEvent(EventChainVisitor& aVisitor) override;
nsresult PostHandleEvent(EventChainPostVisitor& aVisitor) override;
- bool IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
+ bool IsHTMLFocusable(IsFocusableFlags, bool* aIsFocusable,
int32_t* aTabIndex) override;
void DoneAddingChildren(bool aHaveNotified) override;
diff --git a/dom/html/nsGenericHTMLElement.cpp b/dom/html/nsGenericHTMLElement.cpp
index 1deaf719d3..11383863fd 100644
--- a/dom/html/nsGenericHTMLElement.cpp
+++ b/dom/html/nsGenericHTMLElement.cpp
@@ -9,6 +9,7 @@
#include "mozilla/EventListenerManager.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/HTMLEditor.h"
+#include "mozilla/FocusModel.h"
#include "mozilla/IMEContentObserver.h"
#include "mozilla/IMEStateManager.h"
#include "mozilla/MappedDeclarationsBuilder.h"
@@ -286,7 +287,7 @@ static bool IsOffsetParent(nsIFrame* aFrame) {
struct OffsetResult {
Element* mParent = nullptr;
- CSSIntRect mRect;
+ nsRect mRect;
};
static OffsetResult GetUnretargetedOffsetsFor(const Element& aElement) {
@@ -303,6 +304,7 @@ static OffsetResult GetUnretargetedOffsetsFor(const Element& aElement) {
nsIContent* offsetParent = nullptr;
Element* docElement = aElement.GetComposedDoc()->GetRootElement();
nsIContent* content = frame->GetContent();
+ const auto effectiveZoom = frame->Style()->EffectiveZoom();
if (content &&
(content->IsHTMLElement(nsGkAtoms::body) || content == docElement)) {
@@ -321,6 +323,13 @@ static OffsetResult GetUnretargetedOffsetsFor(const Element& aElement) {
break;
}
+ // WebKit-ism: offsetParent stops at zoom changes.
+ // See https://github.com/w3c/csswg-drafts/issues/10252
+ if (effectiveZoom != parent->Style()->EffectiveZoom()) {
+ offsetParent = content;
+ break;
+ }
+
// Add the parent's origin to our own to get to the
// right coordinate system.
const bool isOffsetParent = !isPositioned && IsOffsetParent(parent);
@@ -370,8 +379,7 @@ static OffsetResult GetUnretargetedOffsetsFor(const Element& aElement) {
// we only care about the size. We just have to use something non-null.
nsRect rcFrame = nsLayoutUtils::GetAllInFlowRectsUnion(frame, frame);
rcFrame.MoveTo(origin);
- return {Element::FromNodeOrNull(offsetParent),
- CSSIntRect::FromAppUnitsRounded(rcFrame)};
+ return {Element::FromNodeOrNull(offsetParent), rcFrame};
}
static bool ShouldBeRetargeted(const Element& aReferenceElement,
@@ -393,20 +401,22 @@ static bool ShouldBeRetargeted(const Element& aReferenceElement,
Element* nsGenericHTMLElement::GetOffsetRect(CSSIntRect& aRect) {
aRect = CSSIntRect();
- if (!GetPrimaryFrame(FlushType::Layout)) {
+ nsIFrame* frame = GetPrimaryFrame(FlushType::Layout);
+ if (!frame) {
return nullptr;
}
OffsetResult thisResult = GetUnretargetedOffsetsFor(*this);
- aRect = thisResult.mRect;
-
+ nsRect rect = thisResult.mRect;
Element* parent = thisResult.mParent;
while (parent && ShouldBeRetargeted(*this, *parent)) {
OffsetResult result = GetUnretargetedOffsetsFor(*parent);
- aRect += result.mRect.TopLeft();
+ rect += result.mRect.TopLeft();
parent = result.mParent;
}
+ aRect = CSSIntRect::FromAppUnitsRounded(
+ frame->Style()->EffectiveZoom().Unzoom(rect));
return parent;
}
@@ -1757,8 +1767,8 @@ bool nsGenericHTMLElement::LegacyTouchAPIEnabled(JSContext* aCx,
}
bool nsGenericHTMLElement::IsFormControlDefaultFocusable(
- bool aWithMouse) const {
- if (!aWithMouse) {
+ IsFocusableFlags aFlags) const {
+ if (!(aFlags & IsFocusableFlags::WithMouse)) {
return true;
}
switch (StaticPrefs::accessibility_mouse_focuses_formcontrol()) {
@@ -2303,7 +2313,8 @@ void nsGenericHTMLElement::Click(CallerType aCallerType) {
ClearHandlingClick();
}
-bool nsGenericHTMLElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
+bool nsGenericHTMLElement::IsHTMLFocusable(IsFocusableFlags aFlags,
+ bool* aIsFocusable,
int32_t* aTabIndex) {
MOZ_ASSERT(aIsFocusable);
MOZ_ASSERT(aTabIndex);
@@ -2589,15 +2600,15 @@ void nsGenericHTMLFormControlElement::GetAutocapitalize(
}
}
-bool nsGenericHTMLFormControlElement::IsHTMLFocusable(bool aWithMouse,
+bool nsGenericHTMLFormControlElement::IsHTMLFocusable(IsFocusableFlags aFlags,
bool* aIsFocusable,
int32_t* aTabIndex) {
- if (nsGenericHTMLFormElement::IsHTMLFocusable(aWithMouse, aIsFocusable,
+ if (nsGenericHTMLFormElement::IsHTMLFocusable(aFlags, aIsFocusable,
aTabIndex)) {
return true;
}
- *aIsFocusable = *aIsFocusable && IsFormControlDefaultFocusable(aWithMouse);
+ *aIsFocusable = *aIsFocusable && IsFormControlDefaultFocusable(aFlags);
return false;
}
@@ -3552,8 +3563,7 @@ void nsGenericHTMLElement::FocusPopover() {
RefPtr<Element> control = GetBoolAttr(nsGkAtoms::autofocus)
? this
- : GetAutofocusDelegate(false /* aWithMouse */);
-
+ : GetAutofocusDelegate(IsFocusableFlags(0));
if (!control) {
return;
}
diff --git a/dom/html/nsGenericHTMLElement.h b/dom/html/nsGenericHTMLElement.h
index 86f87e8795..f63f070207 100644
--- a/dom/html/nsGenericHTMLElement.h
+++ b/dom/html/nsGenericHTMLElement.h
@@ -198,7 +198,7 @@ class nsGenericHTMLElement : public nsGenericHTMLElementBase {
}
/** Returns whether a form control should be default-focusable. */
- bool IsFormControlDefaultFocusable(bool aWithMouse) const;
+ bool IsFormControlDefaultFocusable(mozilla::IsFocusableFlags) const;
/**
* Returns the count of descendants (inclusive of this node) in
@@ -333,16 +333,17 @@ class nsGenericHTMLElement : public nsGenericHTMLElementBase {
nsresult BindToTree(BindContext&, nsINode& aParent) override;
void UnbindFromTree(UnbindContext&) override;
- Focusable IsFocusableWithoutStyle(bool aWithMouse) override {
+ Focusable IsFocusableWithoutStyle(mozilla::IsFocusableFlags aFlags =
+ mozilla::IsFocusableFlags(0)) override {
Focusable result;
- IsHTMLFocusable(aWithMouse, &result.mFocusable, &result.mTabIndex);
+ IsHTMLFocusable(aFlags, &result.mFocusable, &result.mTabIndex);
return result;
}
/**
* Returns true if a subclass is not allowed to override the value returned
* in aIsFocusable.
*/
- virtual bool IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
+ virtual bool IsHTMLFocusable(mozilla::IsFocusableFlags, bool* aIsFocusable,
int32_t* aTabIndex);
MOZ_CAN_RUN_SCRIPT
mozilla::Result<bool, nsresult> PerformAccesskey(
@@ -1176,7 +1177,7 @@ class nsGenericHTMLFormControlElement : public nsGenericHTMLFormElement,
// nsGenericHTMLElement
// autocapitalize attribute support
void GetAutocapitalize(nsAString& aValue) const override;
- bool IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
+ bool IsHTMLFocusable(mozilla::IsFocusableFlags, bool* aIsFocusable,
int32_t* aTabIndex) override;
// EventTarget
diff --git a/dom/html/nsGenericHTMLFrameElement.cpp b/dom/html/nsGenericHTMLFrameElement.cpp
index 92591d7b72..c854012f4b 100644
--- a/dom/html/nsGenericHTMLFrameElement.cpp
+++ b/dom/html/nsGenericHTMLFrameElement.cpp
@@ -311,11 +311,10 @@ nsresult nsGenericHTMLFrameElement::CopyInnerTo(Element* aDest) {
return rv;
}
-bool nsGenericHTMLFrameElement::IsHTMLFocusable(bool aWithMouse,
+bool nsGenericHTMLFrameElement::IsHTMLFocusable(IsFocusableFlags aFlags,
bool* aIsFocusable,
int32_t* aTabIndex) {
- if (nsGenericHTMLElement::IsHTMLFocusable(aWithMouse, aIsFocusable,
- aTabIndex)) {
+ if (nsGenericHTMLElement::IsHTMLFocusable(aFlags, aIsFocusable, aTabIndex)) {
return true;
}
diff --git a/dom/html/nsGenericHTMLFrameElement.h b/dom/html/nsGenericHTMLFrameElement.h
index 587e861b88..c61fea2c02 100644
--- a/dom/html/nsGenericHTMLFrameElement.h
+++ b/dom/html/nsGenericHTMLFrameElement.h
@@ -50,15 +50,15 @@ class nsGenericHTMLFrameElement : public nsGenericHTMLElement,
NS_DECLARE_STATIC_IID_ACCESSOR(NS_GENERICHTMLFRAMEELEMENT_IID)
// nsIContent
- virtual bool IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
- int32_t* aTabIndex) override;
- virtual nsresult BindToTree(BindContext&, nsINode& aParent) override;
- virtual void UnbindFromTree(UnbindContext&) override;
- virtual void DestroyContent() override;
+ bool IsHTMLFocusable(mozilla::IsFocusableFlags, bool* aIsFocusable,
+ int32_t* aTabIndex) override;
+ nsresult BindToTree(BindContext&, nsINode& aParent) override;
+ void UnbindFromTree(UnbindContext&) override;
+ void DestroyContent() override;
nsresult CopyInnerTo(mozilla::dom::Element* aDest);
- virtual int32_t TabIndexDefault() override;
+ int32_t TabIndexDefault() override;
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsGenericHTMLFrameElement,
nsGenericHTMLElement)
diff --git a/dom/html/test/forms/mochitest.toml b/dom/html/test/forms/mochitest.toml
index 80d6d3530f..82a8639659 100644
--- a/dom/html/test/forms/mochitest.toml
+++ b/dom/html/test/forms/mochitest.toml
@@ -73,6 +73,8 @@ support-files = ["file_double_submit.html"]
["test_input_datetime_input_change_events.html"]
+["test_input_datetime_preventDefault.html"]
+
["test_input_datetime_readonly.html"]
["test_input_datetime_reset_default_value_input_change_event.html"]
diff --git a/dom/html/test/forms/test_input_datetime_preventDefault.html b/dom/html/test/forms/test_input_datetime_preventDefault.html
new file mode 100644
index 0000000000..76e5928c18
--- /dev/null
+++ b/dom/html/test/forms/test_input_datetime_preventDefault.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<title>Test for bug 1848158</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<input id="input" type="date" value="1998-01-22">
+<script>
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ let value = input.value;
+
+ input.addEventListener("keydown", function(e) {
+ e.preventDefault();
+ });
+
+ isnot(value, "", "should have a value");
+
+ input.focus();
+ synthesizeKey("KEY_Backspace");
+ is(input.value, value, "Value shouldn't change");
+ SimpleTest.finish();
+});
+</script>
diff --git a/dom/indexedDB/IDBFactory.cpp b/dom/indexedDB/IDBFactory.cpp
index c0dc5aeab2..b59efd9124 100644
--- a/dom/indexedDB/IDBFactory.cpp
+++ b/dom/indexedDB/IDBFactory.cpp
@@ -35,6 +35,8 @@
#include "nsIURI.h"
#include "nsIUUIDGenerator.h"
#include "nsIWebNavigation.h"
+#include "nsLiteralString.h"
+#include "nsStringFwd.h"
#include "nsNetUtil.h"
#include "nsSandboxFlags.h"
#include "nsServiceManagerUtils.h"
@@ -53,50 +55,7 @@ using namespace mozilla::ipc;
namespace {
-Telemetry::LABELS_IDB_CUSTOM_OPEN_WITH_OPTIONS_COUNT IdentifyPrincipalType(
- const mozilla::ipc::PrincipalInfo& aPrincipalInfo) {
- switch (aPrincipalInfo.type()) {
- case PrincipalInfo::TSystemPrincipalInfo:
- return Telemetry::LABELS_IDB_CUSTOM_OPEN_WITH_OPTIONS_COUNT::system;
- case PrincipalInfo::TContentPrincipalInfo: {
- const ContentPrincipalInfo& info =
- aPrincipalInfo.get_ContentPrincipalInfo();
-
- nsCOMPtr<nsIURI> uri;
-
- if (NS_WARN_IF(NS_FAILED(NS_NewURI(getter_AddRefs(uri), info.spec())))) {
- // This could be discriminated as an extra error value, but this is
- // extremely unlikely to fail, so we just misuse ContentOther
- return Telemetry::LABELS_IDB_CUSTOM_OPEN_WITH_OPTIONS_COUNT::
- content_other;
- }
-
- // TODO Are there constants defined for the schemes somewhere?
- if (uri->SchemeIs("file")) {
- return Telemetry::LABELS_IDB_CUSTOM_OPEN_WITH_OPTIONS_COUNT::
- content_file;
- }
- if (uri->SchemeIs("http") || uri->SchemeIs("https")) {
- return Telemetry::LABELS_IDB_CUSTOM_OPEN_WITH_OPTIONS_COUNT::
- content_http_https;
- }
- if (uri->SchemeIs("moz-extension")) {
- return Telemetry::LABELS_IDB_CUSTOM_OPEN_WITH_OPTIONS_COUNT::
- content_moz_ext;
- }
- if (uri->SchemeIs("about")) {
- return Telemetry::LABELS_IDB_CUSTOM_OPEN_WITH_OPTIONS_COUNT::
- content_about;
- }
- return Telemetry::LABELS_IDB_CUSTOM_OPEN_WITH_OPTIONS_COUNT::
- content_other;
- }
- case PrincipalInfo::TExpandedPrincipalInfo:
- return Telemetry::LABELS_IDB_CUSTOM_OPEN_WITH_OPTIONS_COUNT::expanded;
- default:
- return Telemetry::LABELS_IDB_CUSTOM_OPEN_WITH_OPTIONS_COUNT::other;
- }
-}
+constexpr nsLiteralCString kAccessError("The operation is insecure");
} // namespace
@@ -112,11 +71,12 @@ struct IDBFactory::PendingRequestInfo {
}
};
-IDBFactory::IDBFactory(const IDBFactoryGuard&)
+IDBFactory::IDBFactory(const IDBFactoryGuard&, bool aAllowed)
: mBackgroundActor(nullptr),
mInnerWindowID(0),
mActiveTransactionCount(0),
mActiveDatabaseCount(0),
+ mAllowed(aAllowed),
mBackgroundActorFailed(false),
mPrivateBrowsingMode(false) {
AssertIsOnOwningThread();
@@ -140,16 +100,21 @@ Result<RefPtr<IDBFactory>, nsresult> IDBFactory::CreateForWindow(
nsCOMPtr<nsIPrincipal> principal;
nsresult rv = AllowedForWindowInternal(aWindow, &principal);
- if (rv == NS_ERROR_DOM_NOT_SUPPORTED_ERR) {
- NS_WARNING("IndexedDB is not permitted in a third-party window.");
- return RefPtr<IDBFactory>{};
- }
-
if (NS_WARN_IF(NS_FAILED(rv))) {
+ if (rv == NS_ERROR_DOM_NOT_SUPPORTED_ERR) {
+ NS_WARNING("IndexedDB is not permitted in a third-party window.");
+ }
+
if (rv == NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR) {
IDB_REPORT_INTERNAL_ERR();
}
- return Err(rv);
+
+ auto factory =
+ MakeRefPtr<IDBFactory>(IDBFactoryGuard{}, /* aAllowed */ false);
+ factory->BindToOwner(aWindow->AsGlobal());
+ factory->mInnerWindowID = aWindow->WindowID();
+
+ return factory;
}
MOZ_ASSERT(principal);
@@ -172,7 +137,7 @@ Result<RefPtr<IDBFactory>, nsresult> IDBFactory::CreateForWindow(
nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(aWindow);
nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(webNav);
- auto factory = MakeRefPtr<IDBFactory>(IDBFactoryGuard{});
+ auto factory = MakeRefPtr<IDBFactory>(IDBFactoryGuard{}, /* aAllowed */ true);
factory->mPrincipalInfo = std::move(principalInfo);
factory->BindToOwner(aWindow->AsGlobal());
@@ -203,7 +168,11 @@ Result<RefPtr<IDBFactory>, nsresult> IDBFactory::CreateForMainThreadJS(
MOZ_ASSERT(principal);
bool isSystem;
if (!AllowedForPrincipal(principal, &isSystem)) {
- return Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+ auto factory =
+ MakeRefPtr<IDBFactory>(IDBFactoryGuard{}, /* aAllowed */ false);
+ factory->BindToOwner(aGlobal);
+
+ return factory;
}
nsresult rv = PrincipalToPrincipalInfo(principal, principalInfo.get());
@@ -220,14 +189,23 @@ Result<RefPtr<IDBFactory>, nsresult> IDBFactory::CreateForMainThreadJS(
// static
Result<RefPtr<IDBFactory>, nsresult> IDBFactory::CreateForWorker(
- nsIGlobalObject* aGlobal, const PrincipalInfo& aPrincipalInfo,
+ nsIGlobalObject* aGlobal, UniquePtr<PrincipalInfo>&& aPrincipalInfo,
uint64_t aInnerWindowID) {
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(aGlobal);
- MOZ_ASSERT(aPrincipalInfo.type() != PrincipalInfo::T__None);
- return CreateInternal(aGlobal, MakeUnique<PrincipalInfo>(aPrincipalInfo),
- aInnerWindowID);
+ if (!aPrincipalInfo) {
+ auto factory =
+ MakeRefPtr<IDBFactory>(IDBFactoryGuard{}, /* aAllowed */ false);
+ factory->BindToOwner(aGlobal);
+ factory->mInnerWindowID = aInnerWindowID;
+
+ return factory;
+ }
+
+ MOZ_ASSERT(aPrincipalInfo->type() != PrincipalInfo::T__None);
+
+ return CreateInternal(aGlobal, std::move(aPrincipalInfo), aInnerWindowID);
}
// static
@@ -264,10 +242,16 @@ Result<RefPtr<IDBFactory>, nsresult> IDBFactory::CreateInternal(
if (aPrincipalInfo->type() != PrincipalInfo::TContentPrincipalInfo &&
aPrincipalInfo->type() != PrincipalInfo::TSystemPrincipalInfo) {
NS_WARNING("IndexedDB not allowed for this principal!");
- return RefPtr<IDBFactory>{};
+
+ auto factory =
+ MakeRefPtr<IDBFactory>(IDBFactoryGuard{}, /* aAllowed */ false);
+ factory->BindToOwner(aGlobal);
+ factory->mInnerWindowID = aInnerWindowID;
+
+ return factory;
}
- auto factory = MakeRefPtr<IDBFactory>(IDBFactoryGuard{});
+ auto factory = MakeRefPtr<IDBFactory>(IDBFactoryGuard{}, /* aAllowed */ true);
factory->mPrincipalInfo = std::move(aPrincipalInfo);
factory->BindToOwner(aGlobal);
factory->mEventTarget = GetCurrentSerialEventTarget();
@@ -407,6 +391,7 @@ PersistenceType IDBFactory::GetPersistenceType(
void IDBFactory::UpdateActiveTransactionCount(int32_t aDelta) {
AssertIsOnOwningThread();
+ MOZ_ASSERT(mAllowed);
MOZ_DIAGNOSTIC_ASSERT(aDelta > 0 || (mActiveTransactionCount + aDelta) <
mActiveTransactionCount);
mActiveTransactionCount += aDelta;
@@ -414,6 +399,7 @@ void IDBFactory::UpdateActiveTransactionCount(int32_t aDelta) {
void IDBFactory::UpdateActiveDatabaseCount(int32_t aDelta) {
AssertIsOnOwningThread();
+ MOZ_ASSERT(mAllowed);
MOZ_DIAGNOSTIC_ASSERT(aDelta > 0 ||
(mActiveDatabaseCount + aDelta) < mActiveDatabaseCount);
mActiveDatabaseCount += aDelta;
@@ -424,6 +410,10 @@ void IDBFactory::UpdateActiveDatabaseCount(int32_t aDelta) {
}
bool IDBFactory::IsChrome() const {
+ if (!mAllowed) {
+ return false; // Minimal privileges
+ }
+
AssertIsOnOwningThread();
MOZ_ASSERT(mPrincipalInfo);
@@ -432,39 +422,28 @@ bool IDBFactory::IsChrome() const {
RefPtr<IDBOpenDBRequest> IDBFactory::Open(JSContext* aCx,
const nsAString& aName,
- uint64_t aVersion,
+ const Optional<uint64_t>& aVersion,
CallerType aCallerType,
ErrorResult& aRv) {
- return OpenInternal(aCx,
- /* aPrincipal */ nullptr, aName,
- Optional<uint64_t>(aVersion),
- /* aDeleting */ false, aCallerType, aRv);
-}
-
-RefPtr<IDBOpenDBRequest> IDBFactory::Open(JSContext* aCx,
- const nsAString& aName,
- const IDBOpenDBOptions& aOptions,
- CallerType aCallerType,
- ErrorResult& aRv) {
- // This overload is nonstandard, see bug 1275496.
- // Ignore calls with empty options for telemetry of usage count.
- // Unfortunately, we cannot distinguish between the use of the method with
- // only a single argument (which actually is a standard overload we don't want
- // to count) an empty dictionary passed explicitly (which is the custom
- // overload we would like to count). However, we assume that the latter is so
- // rare that it can be neglected.
- if (aOptions.IsAnyMemberPresent()) {
- Telemetry::AccumulateCategorical(IdentifyPrincipalType(*mPrincipalInfo));
+ if (!mAllowed) {
+ aRv.ThrowSecurityError(kAccessError);
+ return nullptr;
}
return OpenInternal(aCx,
- /* aPrincipal */ nullptr, aName, aOptions.mVersion,
+ /* aPrincipal */ nullptr, aName, aVersion,
/* aDeleting */ false, aCallerType, aRv);
}
-RefPtr<IDBOpenDBRequest> IDBFactory::DeleteDatabase(
- JSContext* aCx, const nsAString& aName, const IDBOpenDBOptions& aOptions,
- CallerType aCallerType, ErrorResult& aRv) {
+RefPtr<IDBOpenDBRequest> IDBFactory::DeleteDatabase(JSContext* aCx,
+ const nsAString& aName,
+ CallerType aCallerType,
+ ErrorResult& aRv) {
+ if (!mAllowed) {
+ aRv.ThrowSecurityError(kAccessError);
+ return nullptr;
+ }
+
return OpenInternal(aCx,
/* aPrincipal */ nullptr, aName, Optional<uint64_t>(),
/* aDeleting */ true, aCallerType, aRv);
@@ -478,6 +457,10 @@ already_AddRefed<Promise> IDBFactory::Databases(JSContext* const aCx,
}
RefPtr<Promise> promise = Promise::CreateInfallible(GetOwnerGlobal());
+ if (!mAllowed) {
+ promise->MaybeRejectWithSecurityError(kAccessError);
+ return promise.forget();
+ }
// Nothing can be done here if we have previously failed to create a
// background actor.
@@ -569,6 +552,11 @@ int16_t IDBFactory::Cmp(JSContext* aCx, JS::Handle<JS::Value> aFirst,
RefPtr<IDBOpenDBRequest> IDBFactory::OpenForPrincipal(
JSContext* aCx, nsIPrincipal* aPrincipal, const nsAString& aName,
uint64_t aVersion, SystemCallerGuarantee aGuarantee, ErrorResult& aRv) {
+ if (!mAllowed) {
+ aRv.ThrowSecurityError(kAccessError);
+ return nullptr;
+ }
+
MOZ_ASSERT(aPrincipal);
if (!NS_IsMainThread()) {
MOZ_CRASH(
@@ -584,6 +572,11 @@ RefPtr<IDBOpenDBRequest> IDBFactory::OpenForPrincipal(
JSContext* aCx, nsIPrincipal* aPrincipal, const nsAString& aName,
const IDBOpenDBOptions& aOptions, SystemCallerGuarantee aGuarantee,
ErrorResult& aRv) {
+ if (!mAllowed) {
+ aRv.ThrowSecurityError(kAccessError);
+ return nullptr;
+ }
+
MOZ_ASSERT(aPrincipal);
if (!NS_IsMainThread()) {
MOZ_CRASH(
@@ -599,6 +592,11 @@ RefPtr<IDBOpenDBRequest> IDBFactory::DeleteForPrincipal(
JSContext* aCx, nsIPrincipal* aPrincipal, const nsAString& aName,
const IDBOpenDBOptions& aOptions, SystemCallerGuarantee aGuarantee,
ErrorResult& aRv) {
+ if (!mAllowed) {
+ aRv.ThrowSecurityError(kAccessError);
+ return nullptr;
+ }
+
MOZ_ASSERT(aPrincipal);
if (!NS_IsMainThread()) {
MOZ_CRASH(
@@ -611,6 +609,8 @@ RefPtr<IDBOpenDBRequest> IDBFactory::DeleteForPrincipal(
}
nsresult IDBFactory::EnsureBackgroundActor() {
+ MOZ_ASSERT(mAllowed);
+
if (mBackgroundActor) {
return NS_OK;
}
@@ -671,6 +671,8 @@ RefPtr<IDBOpenDBRequest> IDBFactory::OpenInternal(
JSContext* aCx, nsIPrincipal* aPrincipal, const nsAString& aName,
const Optional<uint64_t>& aVersion, bool aDeleting, CallerType aCallerType,
ErrorResult& aRv) {
+ MOZ_ASSERT(mAllowed);
+
if (NS_WARN_IF(!GetOwnerGlobal())) {
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
return nullptr;
@@ -801,6 +803,7 @@ RefPtr<IDBOpenDBRequest> IDBFactory::OpenInternal(
nsresult IDBFactory::InitiateRequest(
const NotNull<RefPtr<IDBOpenDBRequest>>& aRequest,
const FactoryRequestParams& aParams) {
+ MOZ_ASSERT(mAllowed);
MOZ_ASSERT(mBackgroundActor);
MOZ_ASSERT(!mBackgroundActorFailed);
diff --git a/dom/indexedDB/IDBFactory.h b/dom/indexedDB/IDBFactory.h
index d64d571a05..08663eaec4 100644
--- a/dom/indexedDB/IDBFactory.h
+++ b/dom/indexedDB/IDBFactory.h
@@ -77,11 +77,16 @@ class IDBFactory final : public GlobalTeardownObserver, public nsWrapperCache {
uint32_t mActiveTransactionCount;
uint32_t mActiveDatabaseCount;
+ // When mAllowed is false we throw security errors on all operations. This is
+ // because although we make storage access decisions when we create the
+ // IDBFactory, the spec (and content) expects us to only throw if an attempt
+ // is made to use the resulting IDBFactory.
+ bool mAllowed;
bool mBackgroundActorFailed;
bool mPrivateBrowsingMode;
public:
- explicit IDBFactory(const IDBFactoryGuard&);
+ IDBFactory(const IDBFactoryGuard&, bool aAllowed);
static Result<RefPtr<IDBFactory>, nsresult> CreateForWindow(
nsPIDOMWindowInner* aWindow);
@@ -89,8 +94,9 @@ class IDBFactory final : public GlobalTeardownObserver, public nsWrapperCache {
static Result<RefPtr<IDBFactory>, nsresult> CreateForMainThreadJS(
nsIGlobalObject* aGlobal);
+ // mAllowed shall be false for null aPrincipalInfo.
static Result<RefPtr<IDBFactory>, nsresult> CreateForWorker(
- nsIGlobalObject* aGlobal, const PrincipalInfo& aPrincipalInfo,
+ nsIGlobalObject* aGlobal, UniquePtr<PrincipalInfo>&& aPrincipalInfo,
uint64_t aInnerWindowID);
static bool AllowedForWindow(nsPIDOMWindowInner* aWindow);
@@ -146,21 +152,15 @@ class IDBFactory final : public GlobalTeardownObserver, public nsWrapperCache {
bool IsChrome() const;
- [[nodiscard]] RefPtr<IDBOpenDBRequest> Open(JSContext* aCx,
- const nsAString& aName,
- uint64_t aVersion,
- CallerType aCallerType,
- ErrorResult& aRv);
-
- [[nodiscard]] RefPtr<IDBOpenDBRequest> Open(JSContext* aCx,
- const nsAString& aName,
- const IDBOpenDBOptions& aOptions,
- CallerType aCallerType,
- ErrorResult& aRv);
+ [[nodiscard]] RefPtr<IDBOpenDBRequest> Open(
+ JSContext* aCx, const nsAString& aName,
+ const Optional<uint64_t>& aVersion, CallerType aCallerType,
+ ErrorResult& aRv);
- [[nodiscard]] RefPtr<IDBOpenDBRequest> DeleteDatabase(
- JSContext* aCx, const nsAString& aName, const IDBOpenDBOptions& aOptions,
- CallerType aCallerType, ErrorResult& aRv);
+ [[nodiscard]] RefPtr<IDBOpenDBRequest> DeleteDatabase(JSContext* aCx,
+ const nsAString& aName,
+ CallerType aCallerType,
+ ErrorResult& aRv);
already_AddRefed<Promise> Databases(JSContext* aCx, ErrorResult& aRv);
diff --git a/dom/indexedDB/IDBObjectStore.cpp b/dom/indexedDB/IDBObjectStore.cpp
index 728f10b105..401c9defb2 100644
--- a/dom/indexedDB/IDBObjectStore.cpp
+++ b/dom/indexedDB/IDBObjectStore.cpp
@@ -276,12 +276,22 @@ bool CopyingStructuredCloneWriteCallback(JSContext* aCx,
aObj);
}
+void StructuredCloneErrorCallback(JSContext* aCx, uint32_t aErrorId,
+ void* aClosure, const char* aErrorMessage) {
+ // This callback is only used to prevent the default cloning TypeErrors
+ // from being thrown, as we will throw DataCloneErrors instead per spec.
+}
+
nsresult GetAddInfoCallback(JSContext* aCx, void* aClosure) {
static const JSStructuredCloneCallbacks kStructuredCloneCallbacks = {
- nullptr /* read */, StructuredCloneWriteCallback /* write */,
- nullptr /* reportError */, nullptr /* readTransfer */,
- nullptr /* writeTransfer */, nullptr /* freeTransfer */,
- nullptr /* canTransfer */, nullptr /* sabCloned */
+ nullptr /* read */,
+ StructuredCloneWriteCallback /* write */,
+ StructuredCloneErrorCallback /* reportError */,
+ nullptr /* readTransfer */,
+ nullptr /* writeTransfer */,
+ nullptr /* freeTransfer */,
+ nullptr /* canTransfer */,
+ nullptr /* sabCloned */
};
MOZ_ASSERT(aCx);
@@ -555,7 +565,7 @@ bool IDBObjectStore::DeserializeValue(
static const JSStructuredCloneCallbacks callbacks = {
StructuredCloneReadCallback<StructuredCloneReadInfoChild>,
nullptr,
- nullptr,
+ StructuredCloneErrorCallback,
nullptr,
nullptr,
nullptr,
@@ -1751,7 +1761,7 @@ bool IDBObjectStore::ValueWrapper::Clone(JSContext* aCx) {
static const JSStructuredCloneCallbacks callbacks = {
CopyingStructuredCloneReadCallback /* read */,
CopyingStructuredCloneWriteCallback /* write */,
- nullptr /* reportError */,
+ StructuredCloneErrorCallback /* reportError */,
nullptr /* readTransfer */,
nullptr /* writeTransfer */,
nullptr /* freeTransfer */,
diff --git a/dom/indexedDB/Key.cpp b/dom/indexedDB/Key.cpp
index 7e0c607617..a6223bfc95 100644
--- a/dom/indexedDB/Key.cpp
+++ b/dom/indexedDB/Key.cpp
@@ -16,6 +16,7 @@
#include "js/MemoryFunctions.h"
#include "js/Object.h" // JS::GetBuiltinClass
#include "js/PropertyAndElement.h" // JS_DefineElement, JS_GetProperty, JS_GetPropertyById, JS_HasOwnProperty, JS_HasOwnPropertyById
+#include "js/SharedArrayBuffer.h" // IsSharedArrayBufferObject
#include "js/Value.h"
#include "jsfriendapi.h"
#include "mozilla/Casting.h"
@@ -102,6 +103,84 @@ IDBResult<Ok, IDBSpecialValue::Invalid> ConvertArrayValueToKey(
aPolicy.EndSubkeyList();
return Ok();
}
+
+bool IsDetachedBuffer(JSContext* aCx, JS::Handle<JSObject*> aJsBufferSource) {
+ if (JS_IsArrayBufferViewObject(aJsBufferSource)) {
+ bool unused = false;
+ JS::Rooted<JSObject*> viewedArrayBuffer(
+ aCx, JS_GetArrayBufferViewBuffer(aCx, aJsBufferSource, &unused));
+ return JS::IsDetachedArrayBufferObject(viewedArrayBuffer);
+ }
+
+ return JS::IsDetachedArrayBufferObject(aJsBufferSource);
+}
+
+// To get a copy of the bytes held by the buffer source given a buffer source
+// type instance bufferSource:
+IDBResult<Span<const uint8_t>, IDBSpecialValue::Invalid>
+GetByteSpanFromJSBufferSource(JSContext* aCx,
+ JS::Handle<JSObject*> aJsBufferSource) {
+ // 1. Let jsBufferSource be the result of converting bufferSource to a
+ // JavaScript value.
+
+ // 2. Let jsArrayBuffer be jsBufferSource.
+ JS::Handle<JSObject*>& jsArrayBuffer = aJsBufferSource;
+
+ // 3. Let offset be 0.
+ size_t offset = 0u;
+
+ // 4. Let length be 0.
+ size_t length = 0u;
+
+ // 8. Let bytes be a new byte sequence of length equal to length.
+ uint8_t* bytes = nullptr; // Note: Copy is deferred, no allocation here
+
+ // 5. If jsBufferSource has a [[ViewedArrayBuffer]] internal slot, then:
+ if (JS_IsArrayBufferViewObject(aJsBufferSource)) {
+ // 5.1 Set jsArrayBuffer to jsBufferSource.[[ViewedArrayBuffer]].
+ // 5.2 Set offset to jsBufferSource.[[ByteOffset]].
+ // 5.3 Set length to jsBufferSource.[[ByteLength]].
+
+ // 9. For i in the range offset to offset + length − 1, inclusive, set
+ // bytes[i − offset] to GetValueFromBuffer(jsArrayBuffer, i, Uint8, true,
+ // Unordered).
+ (void)offset;
+ bool unused = false;
+ if (!JS_GetObjectAsArrayBufferView(jsArrayBuffer, &length, &unused,
+ &bytes)) {
+ return Err(IDBError(SpecialValues::Invalid));
+ }
+
+ // 6. Otherwise:
+ } else {
+ // 6.1 Assert: jsBufferSource is an ArrayBuffer or SharedArrayBuffer object.
+ MOZ_RELEASE_ASSERT(JS::IsArrayBufferObject(aJsBufferSource) ||
+ JS::IsSharedArrayBufferObject(aJsBufferSource));
+
+ // 6.2 Set length to jsBufferSource.[[ArrayBufferByteLength]].
+
+ // 9. For i in the range offset to offset + length − 1, inclusive, set
+ // bytes[i − offset] to GetValueFromBuffer(jsArrayBuffer, i, Uint8, true,
+ // Unordered).
+ (void)offset;
+ if (!JS::GetObjectAsArrayBuffer(jsArrayBuffer, &length, &bytes)) {
+ return Err(IDBError(SpecialValues::Invalid));
+ }
+ }
+
+ // 7. If IsDetachedBuffer(jsArrayBuffer) is true, then return the empty byte
+ // sequence.
+ if (IsDetachedBuffer(aCx, aJsBufferSource)) {
+ // Note: As the web platform tests assume, and as has been discussed at
+ // https://github.com/w3c/IndexedDB/issues/417 - we are better off by
+ // throwing a DataCloneError. The spec language is about to be revised.
+ return Err(IDBError(SpecialValues::Invalid));
+ }
+
+ // 10. Return bytes.
+ return Span<uint8_t>{bytes, length}.AsConst();
+}
+
} // namespace
/*
@@ -435,8 +514,19 @@ IDBResult<Ok, IDBSpecialValue::Invalid> Key::EncodeJSValInternal(
// If `input` is a buffer source type
if (JS::IsArrayBufferObject(object) || JS_IsArrayBufferViewObject(object)) {
- const bool isViewObject = JS_IsArrayBufferViewObject(object);
- return EncodeBinary(object, isViewObject, aTypeOffset);
+ // 1. Let bytes be the result of getting a copy of the bytes held by the
+ // buffer source input.
+
+ auto res = GetByteSpanFromJSBufferSource(aCx, object);
+
+ // Rethrow any exceptions.
+ if (res.isErr()) {
+ return res.propagateErr();
+ }
+
+ // 2. Return a new key with type binary and value bytes.
+ // Note: The copy takes place here.
+ return EncodeAsString(res.inspect(), eBinary + aTypeOffset);
}
// If IsArray(`input`)
@@ -518,13 +608,14 @@ nsresult Key::DecodeJSValInternal(const EncodedDataType*& aPos,
} else if (*aPos - aTypeOffset == eFloat) {
aVal.setDouble(DecodeNumber(aPos, aEnd));
} else if (*aPos - aTypeOffset == eBinary) {
- JSObject* binary = DecodeBinary(aPos, aEnd, aCx);
- if (!binary) {
+ JSObject* arrayBufferObject =
+ GetArrayBufferObjectFromDataRange(aPos, aEnd, aCx);
+ if (!arrayBufferObject) {
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
- aVal.setObject(*binary);
+ aVal.setObject(*arrayBufferObject);
} else {
MOZ_ASSERT_UNREACHABLE("Unknown key type!");
}
@@ -869,28 +960,10 @@ double Key::DecodeNumber(const EncodedDataType*& aPos,
return BitwiseCast<double>(bits);
}
-Result<Ok, nsresult> Key::EncodeBinary(JSObject* aObject, bool aIsViewObject,
- uint8_t aTypeOffset) {
- uint8_t* bufferData;
- size_t bufferLength;
-
- // We must use JS::GetObjectAsArrayBuffer()/JS_GetObjectAsArrayBufferView()
- // instead of js::GetArrayBufferLengthAndData(). The object might be wrapped,
- // the former will handle the wrapped case, the later won't.
- if (aIsViewObject) {
- bool unused;
- JS_GetObjectAsArrayBufferView(aObject, &bufferLength, &unused, &bufferData);
- } else {
- JS::GetObjectAsArrayBuffer(aObject, &bufferLength, &bufferData);
- }
-
- return EncodeAsString(Span{bufferData, bufferLength}.AsConst(),
- eBinary + aTypeOffset);
-}
-
// static
-JSObject* Key::DecodeBinary(const EncodedDataType*& aPos,
- const EncodedDataType* aEnd, JSContext* aCx) {
+JSObject* Key::GetArrayBufferObjectFromDataRange(const EncodedDataType*& aPos,
+ const EncodedDataType* aEnd,
+ JSContext* aCx) {
JS::Rooted<JSObject*> rv(aCx);
DecodeStringy<eBinary, uint8_t>(
aPos, aEnd,
diff --git a/dom/indexedDB/Key.h b/dom/indexedDB/Key.h
index 25ddd3e0b1..f4e6570a96 100644
--- a/dom/indexedDB/Key.h
+++ b/dom/indexedDB/Key.h
@@ -226,9 +226,6 @@ class Key {
Result<Ok, nsresult> EncodeNumber(double aFloat, uint8_t aType);
- Result<Ok, nsresult> EncodeBinary(JSObject* aObject, bool aIsViewObject,
- uint8_t aTypeOffset);
-
// Decoding functions. aPos points into mBuffer and is adjusted to point
// past the consumed value. (Note: this may be beyond aEnd).
static nsresult DecodeJSVal(const EncodedDataType*& aPos,
@@ -241,8 +238,9 @@ class Key {
static double DecodeNumber(const EncodedDataType*& aPos,
const EncodedDataType* aEnd);
- static JSObject* DecodeBinary(const EncodedDataType*& aPos,
- const EncodedDataType* aEnd, JSContext* aCx);
+ static JSObject* GetArrayBufferObjectFromDataRange(
+ const EncodedDataType*& aPos, const EncodedDataType* aEnd,
+ JSContext* aCx);
// Returns the size of the decoded data for stringy (string or binary),
// excluding a null terminator.
diff --git a/dom/indexedDB/crashtests/1499854-1.html b/dom/indexedDB/crashtests/1499854-1.html
index 0e10601134..7d3a86a62f 100644
--- a/dom/indexedDB/crashtests/1499854-1.html
+++ b/dom/indexedDB/crashtests/1499854-1.html
@@ -6,7 +6,7 @@
o1 = new Int32Array(51488)
o2 = new ArrayBuffer(13964)
for (let i = 0; i < 51488; i++) o1[i] = 0x41
- const dbRequest = window.indexedDB.open('', {})
+ const dbRequest = window.indexedDB.open('')
dbRequest.onupgradeneeded = function(event) {
const store = event.target.result.createObjectStore('IDBStore_0', {})
store.add({}, 'ObjectKey_0')
diff --git a/dom/indexedDB/crashtests/1857979-1.html b/dom/indexedDB/crashtests/1857979-1.html
index d97aa24562..be5a3c947c 100644
--- a/dom/indexedDB/crashtests/1857979-1.html
+++ b/dom/indexedDB/crashtests/1857979-1.html
@@ -1,10 +1,10 @@
<!DOCTYPE html>
<script>
window.addEventListener('load', async () => {
- const db1 = indexedDB.open('DB_1696052013002', {})
+ const db1 = indexedDB.open('DB_1696052013002')
db1.onsuccess = () => window.close()
const blob = new Blob(['0'], {})
- db2 = indexedDB.open('DB_1696052013003', {})
+ db2 = indexedDB.open('DB_1696052013003')
db2.onupgradeneeded = (e) => {
const store = e.target.result.createObjectStore('IDBStore_0', {
'autoIncrement': true,
diff --git a/dom/indexedDB/test/perfdocs/index.rst b/dom/indexedDB/test/perfdocs/index.rst
index ec54211038..5d7d98e3de 100644
--- a/dom/indexedDB/test/perfdocs/index.rst
+++ b/dom/indexedDB/test/perfdocs/index.rst
@@ -51,7 +51,7 @@ How to add more tests?
* Under the ``[test_name]`` section, specity the test parameters as a sequence of ``--browsertime.key=value`` arguments as a value of ``browsertime_args =``
* Under the ``[test_name]`` section, override any other values as needed
-* Add test as a subtest to run for Desktop ``taskcluster/ci/test/browsertime-desktop.yml`` (maybe also for mobile)
+* Add test as a subtest to run for Desktop ``taskcluster/kinds/test/browsertime-desktop.yml`` (maybe also for mobile)
* Add test documentation to ``testing/raptor/raptor/perfdocs/config.yml``
* Generated files:
diff --git a/dom/indexedDB/test/unit/test_invalid_version.js b/dom/indexedDB/test/unit/test_invalid_version.js
index ea5e2953d0..bf745cb93a 100644
--- a/dom/indexedDB/test/unit/test_invalid_version.js
+++ b/dom/indexedDB/test/unit/test_invalid_version.js
@@ -26,21 +26,5 @@ function* testSteps() {
is(e.name, "TypeError", "Good error name.");
}
- try {
- indexedDB.open(name, { version: 0 });
- ok(false, "Should have thrown!");
- } catch (e) {
- ok(e instanceof TypeError, "Got TypeError.");
- is(e.name, "TypeError", "Good error name.");
- }
-
- try {
- indexedDB.open(name, { version: -1 });
- ok(false, "Should have thrown!");
- } catch (e) {
- ok(e instanceof TypeError, "Got TypeError.");
- is(e.name, "TypeError", "Good error name.");
- }
-
finishTest();
}
diff --git a/dom/indexedDB/test/unit/test_metadata2Restore.js b/dom/indexedDB/test/unit/test_metadata2Restore.js
index da31eecc86..643e734e70 100644
--- a/dom/indexedDB/test/unit/test_metadata2Restore.js
+++ b/dom/indexedDB/test/unit/test_metadata2Restore.js
@@ -15,7 +15,7 @@ function* testSteps() {
attrs: { userContextId: 1 },
url: "http://localhost:81",
dbName: "dbC",
- dbOptions: { version: 1, storage: "default" },
+ dbVersion: 1,
},
// This one lives in storage/default/http+++localhost+82^userContextId=1
@@ -25,7 +25,7 @@ function* testSteps() {
attrs: { userContextId: 1 },
url: "http://localhost:82",
dbName: "dbD",
- dbOptions: { version: 1, storage: "default" },
+ dbVersion: 1,
},
// This one lives in storage/default/http+++localhost+83^userContextId=1
@@ -36,7 +36,7 @@ function* testSteps() {
attrs: { userContextId: 1 },
url: "http://localhost:83",
dbName: "dbE",
- dbOptions: { version: 1, storage: "default" },
+ dbVersion: 1,
},
// This one lives in storage/default/http+++localhost+84^userContextId=1
@@ -47,7 +47,7 @@ function* testSteps() {
attrs: { userContextId: 1 },
url: "http://localhost:84",
dbName: "dbF",
- dbOptions: { version: 1, storage: "default" },
+ dbVersion: 1,
},
// This one lives in storage/default/http+++localhost+85^userContextId=1
@@ -59,7 +59,7 @@ function* testSteps() {
attrs: { userContextId: 1 },
url: "http://localhost:85",
dbName: "dbG",
- dbOptions: { version: 1, storage: "default" },
+ dbVersion: 1,
},
// This one lives in storage/default/http+++localhost+86^userContextId=1
@@ -72,7 +72,7 @@ function* testSteps() {
attrs: { userContextId: 1 },
url: "http://localhost:86",
dbName: "dbH",
- dbOptions: { version: 1, storage: "default" },
+ dbVersion: 1,
},
// This one lives in storage/default/http+++localhost+87^userContextId=1
@@ -85,7 +85,7 @@ function* testSteps() {
attrs: { userContextId: 1 },
url: "http://localhost:87",
dbName: "dbI",
- dbOptions: { version: 1, storage: "default" },
+ dbVersion: 1,
},
// This one lives in storage/default/http+++localhost+88^userContextId=1
@@ -99,7 +99,7 @@ function* testSteps() {
attrs: { userContextId: 1 },
url: "http://localhost:88",
dbName: "dbJ",
- dbOptions: { version: 1, storage: "default" },
+ dbVersion: 1,
},
// This one lives in storage/default/http+++localhost+89^userContextId=1
@@ -113,7 +113,7 @@ function* testSteps() {
attrs: { userContextId: 1 },
url: "http://localhost:89",
dbName: "dbK",
- dbOptions: { version: 1, storage: "default" },
+ dbVersion: 1,
},
// This one lives in storage/default/http+++localhost+90^userContextId=1
@@ -128,7 +128,7 @@ function* testSteps() {
attrs: { userContextId: 1 },
url: "http://localhost:90",
dbName: "dbL",
- dbOptions: { version: 1, storage: "default" },
+ dbVersion: 1,
},
// This one lives in storage/default/http+++localhost+91^userContextId=1
@@ -144,7 +144,7 @@ function* testSteps() {
attrs: { userContextId: 1 },
url: "http://localhost:91",
dbName: "dbM",
- dbOptions: { version: 1, storage: "default" },
+ dbVersion: 1,
},
// This one lives in storage/default/http+++localhost+92^userContextId=1
@@ -160,7 +160,7 @@ function* testSteps() {
attrs: { userContextId: 1 },
url: "http://localhost:92",
dbName: "dbN",
- dbOptions: { version: 1, storage: "default" },
+ dbVersion: 1,
},
// This one lives in storage/default/http+++localhost+93^userContextId=1
@@ -177,7 +177,7 @@ function* testSteps() {
attrs: { userContextId: 1 },
url: "http://localhost:93",
dbName: "dbO",
- dbOptions: { version: 1, storage: "default" },
+ dbVersion: 1,
},
// This one lives in storage/default/http+++localhost+94^userContextId=1
@@ -195,7 +195,7 @@ function* testSteps() {
attrs: { userContextId: 1 },
url: "http://localhost:94",
dbName: "dbP",
- dbOptions: { version: 1, storage: "default" },
+ dbVersion: 1,
},
// This one lives in storage/default/http+++localhost+95^userContextId=1
@@ -213,7 +213,7 @@ function* testSteps() {
attrs: { userContextId: 1 },
url: "http://localhost:95",
dbName: "dbQ",
- dbOptions: { version: 1, storage: "default" },
+ dbVersion: 1,
},
// This one lives in storage/default/http+++localhost+96^userContextId=1
@@ -232,7 +232,7 @@ function* testSteps() {
attrs: { userContextId: 1 },
url: "http://localhost:96",
dbName: "dbR",
- dbOptions: { version: 1, storage: "default" },
+ dbVersion: 1,
},
// This one lives in storage/default/http+++localhost+97^userContextId=1
@@ -252,7 +252,7 @@ function* testSteps() {
attrs: { userContextId: 1 },
url: "http://localhost:97",
dbName: "dbS",
- dbOptions: { version: 1, storage: "default" },
+ dbVersion: 1,
},
// This one lives in storage/default/http+++localhost+98^userContextId=1
@@ -272,7 +272,7 @@ function* testSteps() {
attrs: { userContextId: 1 },
url: "http://localhost:98",
dbName: "dbT",
- dbOptions: { version: 1, storage: "default" },
+ dbVersion: 1,
},
];
@@ -287,10 +287,10 @@ function* testSteps() {
request = indexedDB.openForPrincipal(
principal,
params.dbName,
- params.dbOptions
+ params.dbVersion
);
} else {
- request = indexedDB.open(params.dbName, params.dbOptions);
+ request = indexedDB.open(params.dbName, params.dbVersion);
}
return request;
}
diff --git a/dom/indexedDB/test/unit/test_metadataRestore.js b/dom/indexedDB/test/unit/test_metadataRestore.js
index 001d4da65b..302b1614d0 100644
--- a/dom/indexedDB/test/unit/test_metadataRestore.js
+++ b/dom/indexedDB/test/unit/test_metadataRestore.js
@@ -12,70 +12,70 @@ function* testSteps() {
{
url: "http://localhost:81",
dbName: "dbC",
- dbOptions: { version: 1, storage: "default" },
+ dbVersion: 1,
},
// This one lives in storage/default/http+++localhost+82
{
url: "http://localhost:82",
dbName: "dbD",
- dbOptions: { version: 1, storage: "default" },
+ dbVersion: 1,
},
// This one lives in storage/default/http+++localhost+83
{
url: "http://localhost:83",
dbName: "dbE",
- dbOptions: { version: 1, storage: "default" },
+ dbVersion: 1,
},
// This one lives in storage/default/http+++localhost+84
{
url: "http://localhost:84",
dbName: "dbF",
- dbOptions: { version: 1, storage: "default" },
+ dbVersion: 1,
},
// This one lives in storage/default/http+++localhost+85
{
url: "http://localhost:85",
dbName: "dbG",
- dbOptions: { version: 1, storage: "default" },
+ dbVersion: 1,
},
// This one lives in storage/default/http+++localhost+86
{
url: "http://localhost:86",
dbName: "dbH",
- dbOptions: { version: 1, storage: "default" },
+ dbVersion: 1,
},
// This one lives in storage/default/http+++localhost+87
{
url: "http://localhost:87",
dbName: "dbI",
- dbOptions: { version: 1, storage: "default" },
+ dbVersion: 1,
},
// This one lives in storage/default/http+++localhost+88
{
url: "http://localhost:88",
dbName: "dbJ",
- dbOptions: { version: 1, storage: "default" },
+ dbVersion: 1,
},
// This one lives in storage/default/http+++localhost+89
{
url: "http://localhost:89",
dbName: "dbK",
- dbOptions: { version: 1, storage: "default" },
+ dbVersion: 1,
},
// This one lives in storage/default/http+++localhost+90
{
url: "http://localhost:90",
dbName: "dbL",
- dbOptions: { version: 1, storage: "default" },
+ dbVersion: 1,
},
];
@@ -90,10 +90,10 @@ function* testSteps() {
request = indexedDB.openForPrincipal(
principal,
params.dbName,
- params.dbOptions
+ params.dbVersion
);
} else {
- request = indexedDB.open(params.dbName, params.dbOptions);
+ request = indexedDB.open(params.dbName, params.dbVersion);
}
return request;
}
diff --git a/dom/interfaces/base/nsIDOMWindowUtils.idl b/dom/interfaces/base/nsIDOMWindowUtils.idl
index 27b9ff5acf..edb63f0041 100644
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -1753,14 +1753,6 @@ interface nsIDOMWindowUtils : nsISupports {
boolean isPartOfOpaqueLayer(in Element aElement);
/**
- * Count the number of different PaintedLayers that the supplied elements have
- * been assigned to in the last paint. Throws an exception if any of the
- * elements doesn't have a primary frame, or if that frame's display items are
- * assigned to any other layers than just a single PaintedLayer per element.
- */
- unsigned long numberOfAssignedPaintedLayers(in Array<Element> aElements);
-
- /**
* Get internal id of the stored blob, file or file handle.
*/
[implicit_jscontext] long long getFileId(in jsval aFile);
diff --git a/dom/interfaces/base/nsIFocusManager.idl b/dom/interfaces/base/nsIFocusManager.idl
index b27fb3e279..68a5040719 100644
--- a/dom/interfaces/base/nsIFocusManager.idl
+++ b/dom/interfaces/base/nsIFocusManager.idl
@@ -173,12 +173,12 @@ interface nsIFocusManager : nsISupports
/*
* Raise the window when switching focus
*/
- const unsigned long FLAG_RAISE = 1;
+ const unsigned long FLAG_RAISE = 1 << 0;
/**
* Do not scroll the element to focus into view.
*/
- const unsigned long FLAG_NOSCROLL = 2;
+ const unsigned long FLAG_NOSCROLL = 1 << 1;
/**
* If attempting to change focus in a window that is not focused, do not
@@ -187,7 +187,7 @@ interface nsIFocusManager : nsISupports
* effect if a child window is focused and an attempt is made to adjust the
* focus in an ancestor, as the frame must be switched in this case.
*/
- const unsigned long FLAG_NOSWITCHFRAME = 4;
+ const unsigned long FLAG_NOSWITCHFRAME = 1 << 2;
/**
* This flag is only used when passed to moveFocus. If set, focus is never
@@ -195,26 +195,26 @@ interface nsIFocusManager : nsISupports
* iterating around to the beginning of that document again. Child frames
* are navigated as normal.
*/
- const unsigned long FLAG_NOPARENTFRAME = 8;
+ const unsigned long FLAG_NOPARENTFRAME = 1 << 3;
/**
* This flag is used for window and element focus operations to signal
- * wether the caller is system or non system.
+ * whether the caller is system or non system.
*/
- const unsigned long FLAG_NONSYSTEMCALLER = 16;
+ const unsigned long FLAG_NONSYSTEMCALLER = 1 << 4;
/**
* Focus is changing due to a mouse operation, for instance the mouse was
* clicked on an element.
*/
- const unsigned long FLAG_BYMOUSE = 0x1000;
+ const unsigned long FLAG_BYMOUSE = 1 << 12;
/**
* Focus is changing due to a key operation, for instance pressing the tab
* key. This flag would normally be passed when MOVEFOCUS_FORWARD or
* MOVEFOCUS_BACKWARD is used.
*/
- const unsigned long FLAG_BYKEY = 0x2000;
+ const unsigned long FLAG_BYKEY = 1 << 13;
/**
* Focus is changing due to a call to MoveFocus. This flag will be implied
@@ -222,31 +222,31 @@ interface nsIFocusManager : nsISupports
* or key) is specified, or when the type is MOVEFOCUS_ROOT or
* MOVEFOCUS_CARET.
*/
- const unsigned long FLAG_BYMOVEFOCUS = 0x4000;
+ const unsigned long FLAG_BYMOVEFOCUS = 1 << 14;
/**
* Do not show a ring around the element to focus, if this is not a text
* control, regardless of other state.
*/
- const unsigned long FLAG_NOSHOWRING = 0x8000;
+ const unsigned long FLAG_NOSHOWRING = 1 << 15;
/**
* Always show the focus ring or other indicator of focus, regardless of
* other state. Overrides FLAG_NOSHOWRING.
*/
- const unsigned long FLAG_SHOWRING = 0x100000;
+ const unsigned long FLAG_SHOWRING = 1 << 16;
/**
* Focus is changing due to a touch operation that generated a mouse event.
* Normally used in conjunction with FLAG_BYMOUSE.
*/
- const unsigned long FLAG_BYTOUCH = 0x200000;
+ const unsigned long FLAG_BYTOUCH = 1 << 17;
/** Focus is changing due to a JS focus() call or similar operation. */
- const unsigned long FLAG_BYJS = 0x400000;
+ const unsigned long FLAG_BYJS = 1 << 18;
/** Focus is changing due to a long press operation by touch or mouse. */
- const unsigned long FLAG_BYLONGPRESS = 0x800000;
+ const unsigned long FLAG_BYLONGPRESS = 1 << 19;
/** Mask with all the focus methods. */
const unsigned long METHOD_MASK = FLAG_BYMOUSE | FLAG_BYKEY | FLAG_BYMOVEFOCUS | FLAG_BYTOUCH | FLAG_BYJS | FLAG_BYLONGPRESS;
diff --git a/dom/interfaces/notification/nsINotificationStorage.idl b/dom/interfaces/notification/nsINotificationStorage.idl
index c7a0b4490a..0052abf236 100644
--- a/dom/interfaces/notification/nsINotificationStorage.idl
+++ b/dom/interfaces/notification/nsINotificationStorage.idl
@@ -103,3 +103,7 @@ interface nsINotificationStorage : nsISupports
%{C++
#define NS_NOTIFICATION_STORAGE_CONTRACTID "@mozilla.org/notificationStorage;1"
%}
+
+%{C++
+#define NS_MEMORY_NOTIFICATION_STORAGE_CONTRACTID "@mozilla.org/memoryNotificationStorage;1"
+%}
diff --git a/dom/interfaces/security/nsIContentSecurityPolicy.idl b/dom/interfaces/security/nsIContentSecurityPolicy.idl
index 26f00a0220..c73020efdc 100644
--- a/dom/interfaces/security/nsIContentSecurityPolicy.idl
+++ b/dom/interfaces/security/nsIContentSecurityPolicy.idl
@@ -40,31 +40,33 @@ interface nsIContentSecurityPolicy : nsISerializable
* add it to the CSPStrDirectives array in nsCSPUtils.h.
*/
cenum CSPDirective : 8 {
- NO_DIRECTIVE = 0,
- DEFAULT_SRC_DIRECTIVE = 1,
- SCRIPT_SRC_DIRECTIVE = 2,
- OBJECT_SRC_DIRECTIVE = 3,
- STYLE_SRC_DIRECTIVE = 4,
- IMG_SRC_DIRECTIVE = 5,
- MEDIA_SRC_DIRECTIVE = 6,
- FRAME_SRC_DIRECTIVE = 7,
- FONT_SRC_DIRECTIVE = 8,
- CONNECT_SRC_DIRECTIVE = 9,
- REPORT_URI_DIRECTIVE = 10,
- FRAME_ANCESTORS_DIRECTIVE = 11,
- REFLECTED_XSS_DIRECTIVE = 12,
- BASE_URI_DIRECTIVE = 13,
- FORM_ACTION_DIRECTIVE = 14,
- WEB_MANIFEST_SRC_DIRECTIVE = 15,
- UPGRADE_IF_INSECURE_DIRECTIVE = 16,
- CHILD_SRC_DIRECTIVE = 17,
- BLOCK_ALL_MIXED_CONTENT = 18,
- SANDBOX_DIRECTIVE = 19,
- WORKER_SRC_DIRECTIVE = 20,
- SCRIPT_SRC_ELEM_DIRECTIVE = 21,
- SCRIPT_SRC_ATTR_DIRECTIVE = 22,
- STYLE_SRC_ELEM_DIRECTIVE = 23,
- STYLE_SRC_ATTR_DIRECTIVE = 24,
+ NO_DIRECTIVE = 0,
+ DEFAULT_SRC_DIRECTIVE = 1,
+ SCRIPT_SRC_DIRECTIVE = 2,
+ OBJECT_SRC_DIRECTIVE = 3,
+ STYLE_SRC_DIRECTIVE = 4,
+ IMG_SRC_DIRECTIVE = 5,
+ MEDIA_SRC_DIRECTIVE = 6,
+ FRAME_SRC_DIRECTIVE = 7,
+ FONT_SRC_DIRECTIVE = 8,
+ CONNECT_SRC_DIRECTIVE = 9,
+ REPORT_URI_DIRECTIVE = 10,
+ FRAME_ANCESTORS_DIRECTIVE = 11,
+ REFLECTED_XSS_DIRECTIVE = 12,
+ BASE_URI_DIRECTIVE = 13,
+ FORM_ACTION_DIRECTIVE = 14,
+ WEB_MANIFEST_SRC_DIRECTIVE = 15,
+ UPGRADE_IF_INSECURE_DIRECTIVE = 16,
+ CHILD_SRC_DIRECTIVE = 17,
+ BLOCK_ALL_MIXED_CONTENT = 18,
+ SANDBOX_DIRECTIVE = 19,
+ WORKER_SRC_DIRECTIVE = 20,
+ SCRIPT_SRC_ELEM_DIRECTIVE = 21,
+ SCRIPT_SRC_ATTR_DIRECTIVE = 22,
+ STYLE_SRC_ELEM_DIRECTIVE = 23,
+ STYLE_SRC_ATTR_DIRECTIVE = 24,
+ REQUIRE_TRUSTED_TYPES_FOR_DIRECTIVE = 25,
+ TRUSTED_TYPES_DIRECTIVE = 26,
};
/**
diff --git a/dom/ipc/BrowserChild.cpp b/dom/ipc/BrowserChild.cpp
index 3d1f399edb..ef348e0831 100644
--- a/dom/ipc/BrowserChild.cpp
+++ b/dom/ipc/BrowserChild.cpp
@@ -502,7 +502,6 @@ NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BrowserChild)
NS_INTERFACE_MAP_ENTRY(nsIWebBrowserChrome)
- NS_INTERFACE_MAP_ENTRY(nsIWebBrowserChromeFocus)
NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
NS_INTERFACE_MAP_ENTRY(nsIWindowProvider)
NS_INTERFACE_MAP_ENTRY(nsIBrowserChild)
@@ -628,18 +627,6 @@ NS_IMETHODIMP
BrowserChild::Blur() { return NS_ERROR_NOT_IMPLEMENTED; }
NS_IMETHODIMP
-BrowserChild::FocusNextElement(bool aForDocumentNavigation) {
- SendMoveFocus(true, aForDocumentNavigation);
- return NS_OK;
-}
-
-NS_IMETHODIMP
-BrowserChild::FocusPrevElement(bool aForDocumentNavigation) {
- SendMoveFocus(false, aForDocumentNavigation);
- return NS_OK;
-}
-
-NS_IMETHODIMP
BrowserChild::GetInterface(const nsIID& aIID, void** aSink) {
// XXXbz should we restrict the set of interfaces we hand out here?
// See bug 537429
@@ -2151,18 +2138,23 @@ bool BrowserChild::DeallocPDocAccessibleChild(
#endif
RefPtr<VsyncMainChild> BrowserChild::GetVsyncChild() {
- // Initializing mVsyncChild here turns on per-BrowserChild Vsync for a
+ // Initializing VsyncMainChild here turns on per-BrowserChild Vsync for a
// given platform. Note: this only makes sense if nsWindow returns a
// window-specific VsyncSource.
#if defined(MOZ_WAYLAND)
- if (IsWaylandEnabled() && !mVsyncChild) {
- mVsyncChild = MakeRefPtr<VsyncMainChild>();
- if (!SendPVsyncConstructor(mVsyncChild)) {
- mVsyncChild = nullptr;
+ if (IsWaylandEnabled()) {
+ if (auto* actor = static_cast<VsyncMainChild*>(
+ LoneManagedOrNullAsserts(ManagedPVsyncChild()))) {
+ return actor;
}
+ auto actor = MakeRefPtr<VsyncMainChild>();
+ if (!SendPVsyncConstructor(actor)) {
+ return nullptr;
+ }
+ return actor;
}
#endif
- return mVsyncChild;
+ return nullptr;
}
mozilla::ipc::IPCResult BrowserChild::RecvLoadRemoteScript(
diff --git a/dom/ipc/BrowserChild.h b/dom/ipc/BrowserChild.h
index 90cb712476..7e84b80be1 100644
--- a/dom/ipc/BrowserChild.h
+++ b/dom/ipc/BrowserChild.h
@@ -12,7 +12,6 @@
#include "nsIWebNavigation.h"
#include "nsCOMPtr.h"
#include "nsIWebBrowserChrome.h"
-#include "nsIWebBrowserChromeFocus.h"
#include "nsIInterfaceRequestor.h"
#include "nsIWindowProvider.h"
#include "nsIDocShell.h"
@@ -128,7 +127,6 @@ class BrowserChild final : public nsMessageManagerScriptExecutor,
public ipc::MessageManagerCallback,
public PBrowserChild,
public nsIWebBrowserChrome,
- public nsIWebBrowserChromeFocus,
public nsIInterfaceRequestor,
public nsIWindowProvider,
public nsSupportsWeakReference,
@@ -185,7 +183,6 @@ class BrowserChild final : public nsMessageManagerScriptExecutor,
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_NSIWEBBROWSERCHROME
- NS_DECL_NSIWEBBROWSERCHROMEFOCUS
NS_DECL_NSIINTERFACEREQUESTOR
NS_DECL_NSIWINDOWPROVIDER
NS_DECL_NSIBROWSERCHILD
@@ -761,8 +758,6 @@ class BrowserChild final : public nsMessageManagerScriptExecutor,
Maybe<bool> mLayersConnectRequested;
EffectsInfo mEffectsInfo;
- RefPtr<VsyncMainChild> mVsyncChild;
-
RefPtr<APZEventState> mAPZEventState;
// Position of client area relative to the outer window
diff --git a/dom/ipc/BrowserParent.cpp b/dom/ipc/BrowserParent.cpp
index 23c677c09c..75bfbd150b 100644
--- a/dom/ipc/BrowserParent.cpp
+++ b/dom/ipc/BrowserParent.cpp
@@ -265,7 +265,26 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BrowserParent)
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMEventListener)
NS_INTERFACE_MAP_END
-NS_IMPL_CYCLE_COLLECTION_WEAK(BrowserParent, mFrameLoader, mBrowsingContext)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(BrowserParent)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(BrowserParent)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameLoader)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mBrowsingContext)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameElement)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mBrowserDOMWindow)
+ tmp->UnlinkManager();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(BrowserParent)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameLoader)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowsingContext)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameElement)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowserDOMWindow)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(Manager())
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
NS_IMPL_CYCLE_COLLECTING_ADDREF(BrowserParent)
NS_IMPL_CYCLE_COLLECTING_RELEASE(BrowserParent)
@@ -275,7 +294,6 @@ BrowserParent::BrowserParent(ContentParent* aManager, const TabId& aTabId,
uint32_t aChromeFlags)
: TabContext(aContext),
mTabId(aTabId),
- mManager(aManager),
mBrowsingContext(aBrowsingContext),
mFrameElement(nullptr),
mBrowserDOMWindow(nullptr),
@@ -292,7 +310,6 @@ BrowserParent::BrowserParent(ContentParent* aManager, const TabId& aTabId,
mUpdatedDimensions(false),
mSizeMode(nsSizeMode_Normal),
mCreatingWindow(false),
- mVsyncParent(nullptr),
mMarkedDestroying(false),
mIsDestroyed(false),
mRemoteTargetSetsCursor(false),
@@ -307,6 +324,10 @@ BrowserParent::BrowserParent(ContentParent* aManager, const TabId& aTabId,
mShowingTooltip(false) {
MOZ_ASSERT(aManager);
+ // We access `Manager()` when updating priorities later in this constructor,
+ // so need to initialize it before IPC does.
+ SetManager(aManager);
+
RequestingAccessKeyEventData::OnBrowserParentCreated();
// When the input event queue is disabled, we don't need to handle the case
@@ -383,6 +404,10 @@ TabId BrowserParent::GetTabIdFrom(nsIDocShell* docShell) {
return TabId(0);
}
+ContentParent* BrowserParent::Manager() const {
+ return static_cast<ContentParent*>(PBrowserParent::Manager());
+}
+
void BrowserParent::AddBrowserParentToTable(layers::LayersId aLayersId,
BrowserParent* aBrowserParent) {
if (!sLayerToBrowserParentTable) {
@@ -1388,7 +1413,7 @@ IPCResult BrowserParent::RecvNewWindowGlobal(
}
}
- if (!mManager->ValidatePrincipal(aInit.principal(), validationOptions)) {
+ if (!Manager()->ValidatePrincipal(aInit.principal(), validationOptions)) {
ContentParent::LogAndAssertFailedPrincipalValidationInfo(aInit.principal(),
__func__);
}
@@ -1401,21 +1426,19 @@ IPCResult BrowserParent::RecvNewWindowGlobal(
return IPC_OK();
}
-PVsyncParent* BrowserParent::AllocPVsyncParent() {
- MOZ_ASSERT(!mVsyncParent);
- mVsyncParent = new VsyncParent();
- UpdateVsyncParentVsyncDispatcher();
- return mVsyncParent.get();
+already_AddRefed<PVsyncParent> BrowserParent::AllocPVsyncParent() {
+ return MakeAndAddRef<VsyncParent>();
}
-bool BrowserParent::DeallocPVsyncParent(PVsyncParent* aActor) {
- MOZ_ASSERT(aActor);
- mVsyncParent = nullptr;
- return true;
+IPCResult BrowserParent::RecvPVsyncConstructor(PVsyncParent* aActor) {
+ UpdateVsyncParentVsyncDispatcher();
+ return IPC_OK();
}
void BrowserParent::UpdateVsyncParentVsyncDispatcher() {
- if (!mVsyncParent) {
+ VsyncParent* actor = static_cast<VsyncParent*>(
+ LoneManagedOrNullAsserts(ManagedPVsyncParent()));
+ if (!actor) {
return;
}
@@ -1424,7 +1447,7 @@ void BrowserParent::UpdateVsyncParentVsyncDispatcher() {
if (!vsyncDispatcher) {
vsyncDispatcher = gfxPlatform::GetPlatform()->GetGlobalVsyncDispatcher();
}
- mVsyncParent->UpdateVsyncDispatcher(vsyncDispatcher);
+ actor->UpdateVsyncDispatcher(vsyncDispatcher);
}
}
@@ -2297,12 +2320,9 @@ mozilla::ipc::IPCResult BrowserParent::RecvAsyncMessage(
}
mozilla::ipc::IPCResult BrowserParent::RecvSetCursor(
- const nsCursor& aCursor, const bool& aHasCustomCursor,
- Maybe<BigBuffer>&& aCursorData, const uint32_t& aWidth,
- const uint32_t& aHeight, const float& aResolutionX,
- const float& aResolutionY, const uint32_t& aStride,
- const gfx::SurfaceFormat& aFormat, const uint32_t& aHotspotX,
- const uint32_t& aHotspotY, const bool& aForce) {
+ const nsCursor& aCursor, Maybe<IPCImage>&& aCustomCursor,
+ const float& aResolutionX, const float& aResolutionY,
+ const uint32_t& aHotspotX, const uint32_t& aHotspotY, const bool& aForce) {
nsCOMPtr<nsIWidget> widget = GetWidget();
if (!widget) {
return IPC_OK();
@@ -2312,38 +2332,21 @@ mozilla::ipc::IPCResult BrowserParent::RecvSetCursor(
widget->ClearCachedCursor();
}
- nsCOMPtr<imgIContainer> cursorImage;
- if (aHasCustomCursor) {
- const bool cursorDataValid = [&] {
- if (!aCursorData) {
- return false;
- }
- auto expectedSize = CheckedInt<uint32_t>(aHeight) * aStride;
- if (!expectedSize.isValid() ||
- expectedSize.value() != aCursorData->Size()) {
- return false;
- }
- auto minStride =
- CheckedInt<uint32_t>(aWidth) * gfx::BytesPerPixel(aFormat);
- if (!minStride.isValid() || aStride < minStride.value()) {
- return false;
- }
- return true;
- }();
- if (!cursorDataValid) {
+ nsCOMPtr<imgIContainer> customCursorImage;
+ if (aCustomCursor) {
+ RefPtr<gfx::DataSourceSurface> customCursorSurface =
+ nsContentUtils::IPCImageToSurface(*aCustomCursor);
+ if (!customCursorSurface) {
return IPC_FAIL(this, "Invalid custom cursor data");
}
- const gfx::IntSize size(aWidth, aHeight);
- RefPtr<gfx::DataSourceSurface> customCursor =
- gfx::CreateDataSourceSurfaceFromData(size, aFormat, aCursorData->Data(),
- aStride);
- RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(customCursor, size);
- cursorImage = image::ImageOps::CreateFromDrawable(drawable);
+ RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(
+ customCursorSurface, customCursorSurface->GetSize());
+ customCursorImage = image::ImageOps::CreateFromDrawable(drawable);
}
mCursor = nsIWidget::Cursor{aCursor,
- std::move(cursorImage),
+ std::move(customCursorImage),
aHotspotX,
aHotspotY,
{aResolutionX, aResolutionY}};
@@ -3962,7 +3965,7 @@ mozilla::ipc::IPCResult BrowserParent::RecvQueryVisitedState(
}
auto* gvHistory = static_cast<GeckoViewHistory*>(history.get());
- gvHistory->QueryVisitedState(widget, mManager, std::move(aURIs));
+ gvHistory->QueryVisitedState(widget, Manager(), std::move(aURIs));
return IPC_OK();
#else
return IPC_FAIL(this, "QueryVisitedState is Android-only");
diff --git a/dom/ipc/BrowserParent.h b/dom/ipc/BrowserParent.h
index 63a52c6ac4..c7fc196c25 100644
--- a/dom/ipc/BrowserParent.h
+++ b/dom/ipc/BrowserParent.h
@@ -131,7 +131,7 @@ class BrowserParent final : public PBrowserParent,
const TabId GetTabId() const { return mTabId; }
- ContentParent* Manager() const { return mManager; }
+ ContentParent* Manager() const;
CanonicalBrowsingContext* GetBrowsingContext() { return mBrowsingContext; }
@@ -383,12 +383,9 @@ class BrowserParent final : public PBrowserParent,
nsTArray<nsCString>&& aDisabledCommands);
mozilla::ipc::IPCResult RecvSetCursor(
- const nsCursor& aValue, const bool& aHasCustomCursor,
- Maybe<BigBuffer>&& aCursorData, const uint32_t& aWidth,
- const uint32_t& aHeight, const float& aResolutionX,
- const float& aResolutionY, const uint32_t& aStride,
- const gfx::SurfaceFormat& aFormat, const uint32_t& aHotspotX,
- const uint32_t& aHotspotY, const bool& aForce);
+ const nsCursor& aValue, Maybe<IPCImage>&& aCustomCursor,
+ const float& aResolutionX, const float& aResolutionY,
+ const uint32_t& aHotspotX, const uint32_t& aHotspotY, const bool& aForce);
mozilla::ipc::IPCResult RecvSetLinkStatus(const nsString& aStatus);
@@ -423,9 +420,9 @@ class BrowserParent final : public PBrowserParent,
const nsString& aTitle, const nsString& aInitialColor,
const nsTArray<nsString>& aDefaultColors);
- PVsyncParent* AllocPVsyncParent();
+ already_AddRefed<PVsyncParent> AllocPVsyncParent();
- bool DeallocPVsyncParent(PVsyncParent* aActor);
+ mozilla::ipc::IPCResult RecvPVsyncConstructor(PVsyncParent* aActor) override;
#ifdef ACCESSIBILITY
PDocAccessibleParent* AllocPDocAccessibleParent(
@@ -855,10 +852,8 @@ class BrowserParent final : public PBrowserParent,
private:
TabId mTabId;
- RefPtr<ContentParent> mManager;
// The root browsing context loaded in this BrowserParent.
RefPtr<CanonicalBrowsingContext> mBrowsingContext;
- nsCOMPtr<nsILoadContext> mLoadContext;
RefPtr<Element> mFrameElement;
nsCOMPtr<nsIBrowserDOMWindow> mBrowserDOMWindow;
// We keep a strong reference to the frameloader after we've sent the
@@ -942,8 +937,6 @@ class BrowserParent final : public PBrowserParent,
nsTArray<nsString> mVerifyDropLinks;
- RefPtr<VsyncParent> mVsyncParent;
-
#ifdef DEBUG
int32_t mActiveSupressDisplayportCount = 0;
#endif
diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp
index 2ebdd303d8..403cb3c9b8 100644
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -2781,6 +2781,20 @@ mozilla::ipc::IPCResult ContentChild::RecvNotifyProcessPriorityChanged(
moz_set_max_dirty_page_modifier(4);
} else if (mProcessPriority == hal::PROCESS_PRIORITY_BACKGROUND) {
moz_set_max_dirty_page_modifier(-2);
+
+#if defined(MOZ_MEMORY)
+ if (StaticPrefs::dom_memory_memory_pressure_on_background() == 1) {
+ jemalloc_free_dirty_pages();
+ }
+#endif
+ if (StaticPrefs::dom_memory_memory_pressure_on_background() == 2) {
+ nsCOMPtr<nsIObserverService> obsServ = services::GetObserverService();
+ obsServ->NotifyObservers(nullptr, "memory-pressure", u"heap-minimize");
+ } else if (StaticPrefs::dom_memory_memory_pressure_on_background() == 3) {
+ nsCOMPtr<nsIObserverService> obsServ = services::GetObserverService();
+ obsServ->NotifyObservers(nullptr, "memory-pressure", u"low-memory");
+ }
+
} else {
moz_set_max_dirty_page_modifier(0);
}
@@ -4446,14 +4460,6 @@ mozilla::ipc::IPCResult ContentChild::RecvDispatchBeforeUnloadToSubtree(
return IPC_OK();
}
-mozilla::ipc::IPCResult ContentChild::RecvDecoderSupportedMimeTypes(
- nsTArray<nsCString>&& aSupportedTypes) {
-#ifdef MOZ_WIDGET_ANDROID
- AndroidDecoderModule::SetSupportedMimeTypes(std::move(aSupportedTypes));
-#endif
- return IPC_OK();
-}
-
mozilla::ipc::IPCResult ContentChild::RecvInitNextGenLocalStorageEnabled(
const bool& aEnabled) {
mozilla::dom::RecvInitNextGenLocalStorageEnabled(aEnabled);
diff --git a/dom/ipc/ContentChild.h b/dom/ipc/ContentChild.h
index 12b6c3cabf..3d4b0d3e59 100644
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -779,9 +779,6 @@ class ContentChild final : public PContentChild,
const MaybeDiscarded<BrowsingContext>& aStartingAt,
DispatchBeforeUnloadToSubtreeResolver&& aResolver);
- mozilla::ipc::IPCResult RecvDecoderSupportedMimeTypes(
- nsTArray<nsCString>&& aSupportedTypes);
-
mozilla::ipc::IPCResult RecvInitNextGenLocalStorageEnabled(
const bool& aEnabled);
diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp
index 412b7e8796..2070f4ab3c 100644
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -4,10 +4,6 @@
* 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/. */
-#ifdef MOZ_WIDGET_ANDROID
-# include "AndroidDecoderModule.h"
-#endif
-
#include "mozilla/AppShutdown.h"
#include "mozilla/DebugOnly.h"
@@ -320,6 +316,11 @@
# include "mozilla/fuzzing/IPCFuzzController.h"
#endif
+#ifdef ENABLE_WEBDRIVER
+# include "nsIMarionette.h"
+# include "nsIRemoteAgent.h"
+#endif
+
// For VP9Benchmark::sBenchmarkFpsPref
#include "Benchmark.h"
@@ -724,8 +725,7 @@ uint32_t ContentParent::GetPoolSize(const nsACString& aContentProcessType) {
return *sBrowserContentParents->GetOrInsertNew(aContentProcessType);
}
-const nsDependentCSubstring RemoteTypePrefix(
- const nsACString& aContentProcessType) {
+nsDependentCSubstring RemoteTypePrefix(const nsACString& aContentProcessType) {
// The suffix after a `=` in a remoteType is dynamic, and used to control the
// process pool to use.
int32_t equalIdx = aContentProcessType.FindChar(L'=');
@@ -2021,11 +2021,6 @@ void ContentParent::MarkAsDead() {
mLifecycleState = LifecycleState::DEAD;
}
-void ContentParent::OnChannelError() {
- RefPtr<ContentParent> kungFuDeathGrip(this);
- PContentParent::OnChannelError();
-}
-
void ContentParent::ProcessingError(Result aCode, const char* aReason) {
if (MsgDropped == aCode) {
return;
@@ -2321,7 +2316,7 @@ bool ContentParent::ShouldKeepProcessAlive() {
return false;
}
- auto contentParents = sBrowserContentParents->Get(mRemoteType);
+ auto* contentParents = sBrowserContentParents->Get(mRemoteType);
if (!contentParents) {
return false;
}
@@ -2440,7 +2435,7 @@ void ContentParent::NotifyTabDestroyed(const TabId& aTabId,
MOZ_LOG(ContentParent::GetLog(), LogLevel::Verbose,
("NotifyTabDestroyed %p", this));
- MaybeBeginShutDown(/* aExpectedBrowserCount */ 1);
+ MaybeBeginShutDown();
}
TestShellParent* ContentParent::CreateTestShell() {
@@ -2645,9 +2640,7 @@ bool ContentParent::BeginSubprocessLaunch(ProcessPriority aPriority) {
#ifdef MOZ_WIDGET_GTK
// This is X11-only pending a solution for WebGL in Wayland mode.
- if (StaticPrefs::dom_ipc_avoid_gtk() &&
- StaticPrefs::widget_non_native_theme_enabled() &&
- widget::GdkIsX11Display()) {
+ if (StaticPrefs::dom_ipc_avoid_gtk() && widget::GdkIsX11Display()) {
mSubprocess->SetEnv("MOZ_HEADLESS", "1");
}
#endif
@@ -3071,14 +3064,6 @@ bool ContentParent::InitInternal(ProcessPriority aInitialPriority) {
Unused << SendUpdateMediaCodecsSupported(location, supported);
}
-#ifdef MOZ_WIDGET_ANDROID
- if (!(StaticPrefs::media_utility_process_enabled() &&
- StaticPrefs::media_utility_android_media_codec_enabled())) {
- Unused << SendDecoderSupportedMimeTypes(
- AndroidDecoderModule::GetSupportedMimeTypesPrefixed());
- }
-#endif
-
// Must send screen info before send initialData
ScreenManager& screenManager = ScreenManager::GetSingleton();
screenManager.CopyScreensToRemote(this);
@@ -3409,15 +3394,16 @@ void ContentParent::OnVarChanged(const GfxVarUpdate& aVar) {
}
mozilla::ipc::IPCResult ContentParent::RecvSetClipboard(
- const IPCTransferable& aTransferable, const int32_t& aWhichClipboard) {
+ const IPCTransferable& aTransferable, const int32_t& aWhichClipboard,
+ const MaybeDiscarded<WindowContext>& aRequestingWindowContext) {
// aRequestingPrincipal is allowed to be nullptr here.
- if (!ValidatePrincipal(aTransferable.requestingPrincipal(),
+ if (!ValidatePrincipal(aTransferable.dataPrincipal(),
{ValidatePrincipalOptions::AllowNullPtr,
ValidatePrincipalOptions::AllowExpanded,
ValidatePrincipalOptions::AllowSystem})) {
- LogAndAssertFailedPrincipalValidationInfo(
- aTransferable.requestingPrincipal(), __func__);
+ LogAndAssertFailedPrincipalValidationInfo(aTransferable.dataPrincipal(),
+ __func__);
}
nsresult rv;
@@ -3434,7 +3420,12 @@ mozilla::ipc::IPCResult ContentParent::RecvSetClipboard(
true /* aFilterUnknownFlavors */);
NS_ENSURE_SUCCESS(rv, IPC_OK());
- clipboard->SetData(trans, nullptr, aWhichClipboard);
+ // OK if this is null
+ RefPtr<WindowGlobalParent> window;
+ if (!aRequestingWindowContext.IsDiscarded()) {
+ window = aRequestingWindowContext.get_canonical();
+ }
+ clipboard->SetData(trans, nullptr, aWhichClipboard, window);
return IPC_OK();
}
@@ -3469,7 +3460,7 @@ static Result<nsCOMPtr<nsITransferable>, nsresult> CreateTransferable(
mozilla::ipc::IPCResult ContentParent::RecvGetClipboard(
nsTArray<nsCString>&& aTypes, const int32_t& aWhichClipboard,
const MaybeDiscarded<WindowContext>& aRequestingWindowContext,
- IPCTransferableData* aTransferableData) {
+ IPCTransferableDataOrError* aTransferableDataOrError) {
nsresult rv;
// We expect content processes to always pass a non-null window so Content
// Analysis can analyze it. (if Content Analysis is active)
@@ -3479,6 +3470,7 @@ mozilla::ipc::IPCResult ContentParent::RecvGetClipboard(
NS_WARNING(
"discarded window passed to RecvGetClipboard(); returning no clipboard "
"content");
+ *aTransferableDataOrError = NS_ERROR_FAILURE;
return IPC_OK();
}
if (aRequestingWindowContext.IsNull()) {
@@ -3488,21 +3480,29 @@ mozilla::ipc::IPCResult ContentParent::RecvGetClipboard(
// Retrieve clipboard
nsCOMPtr<nsIClipboard> clipboard(do_GetService(kCClipboardCID, &rv));
if (NS_FAILED(rv)) {
+ *aTransferableDataOrError = rv;
return IPC_OK();
}
// Create transferable
auto result = CreateTransferable(aTypes);
if (result.isErr()) {
+ *aTransferableDataOrError = result.unwrapErr();
return IPC_OK();
}
// Get data from clipboard
nsCOMPtr<nsITransferable> trans = result.unwrap();
- clipboard->GetData(trans, aWhichClipboard, window);
+ rv = clipboard->GetData(trans, aWhichClipboard, window);
+ if (NS_FAILED(rv)) {
+ *aTransferableDataOrError = rv;
+ return IPC_OK();
+ }
+ IPCTransferableData transferableData;
nsContentUtils::TransferableToIPCTransferableData(
- trans, aTransferableData, true /* aInSyncMessage */, this);
+ trans, &transferableData, true /* aInSyncMessage */, this);
+ *aTransferableDataOrError = std::move(transferableData);
return IPC_OK();
}
@@ -3682,10 +3682,15 @@ mozilla::ipc::IPCResult ContentParent::RecvGetClipboardDataSnapshotSync(
already_AddRefed<PClipboardWriteRequestParent>
ContentParent::AllocPClipboardWriteRequestParent(
- const int32_t& aClipboardType) {
+ const int32_t& aClipboardType,
+ const MaybeDiscarded<WindowContext>& aSettingWindowContext) {
+ WindowContext* settingWindowContext = nullptr;
+ if (!aSettingWindowContext.IsDiscarded()) {
+ settingWindowContext = aSettingWindowContext.get();
+ }
RefPtr<ClipboardWriteRequestParent> request =
MakeAndAddRef<ClipboardWriteRequestParent>(this);
- request->Init(aClipboardType);
+ request->Init(aClipboardType, settingWindowContext);
return request.forget();
}
@@ -4065,8 +4070,9 @@ ContentParent::Observe(nsISupports* aSubject, const char* aTopic,
!strcmp(aTopic, "alertdisablecallback") ||
!strcmp(aTopic, "alertsettingscallback")) {
if (!SendNotifyAlertsObserver(nsDependentCString(aTopic),
- nsDependentString(aData)))
+ nsDependentString(aData))) {
return NS_ERROR_NOT_AVAILABLE;
+ }
} else if (!strcmp(aTopic, "child-gc-request")) {
Unused << SendGarbageCollect();
} else if (!strcmp(aTopic, "child-cc-request")) {
@@ -4227,7 +4233,7 @@ bool ContentParent::CanOpenBrowser(const IPCTabContext& aContext) {
if (aContext.type() == IPCTabContext::TPopupIPCTabContext) {
const PopupIPCTabContext& popupContext = aContext.get_PopupIPCTabContext();
- auto opener = BrowserParent::GetFrom(popupContext.opener().AsParent());
+ auto* opener = BrowserParent::GetFrom(popupContext.opener().AsParent());
if (!opener) {
MOZ_CRASH_UNLESS_FUZZING(
"Got null opener from child; aborting AllocPBrowserParent.");
@@ -4338,7 +4344,7 @@ mozilla::ipc::IPCResult ContentParent::RecvUpdateRemotePrintSettings(
return IPC_OK();
}
- Unused << bp->SendUpdateRemotePrintSettings(std::move(aPrintData));
+ Unused << bp->SendUpdateRemotePrintSettings(aPrintData);
return IPC_OK();
}
@@ -4380,7 +4386,7 @@ mozilla::ipc::IPCResult ContentParent::RecvConstructPopupBrowser(
// type PopupIPCTabContext, and that the opener BrowserParent is
// reachable.
const PopupIPCTabContext& popupContext = aContext.get_PopupIPCTabContext();
- auto opener = BrowserParent::GetFrom(popupContext.opener().AsParent());
+ auto* opener = BrowserParent::GetFrom(popupContext.opener().AsParent());
openerTabId = opener->GetTabId();
openerCpId = opener->Manager()->ChildID();
@@ -5109,7 +5115,7 @@ mozilla::ipc::IPCResult ContentParent::RecvReportFrameTimingData(
"No need to bounce around if in the same process");
Unused << parent->GetContentParent()->SendReportFrameTimingData(
- loadInfoArgs, entryName, initiatorType, std::move(aData));
+ loadInfoArgs, entryName, initiatorType, aData);
return IPC_OK();
}
@@ -5117,30 +5123,30 @@ mozilla::ipc::IPCResult ContentParent::RecvScriptError(
const nsAString& aMessage, const nsAString& aSourceName,
const nsAString& aSourceLine, const uint32_t& aLineNumber,
const uint32_t& aColNumber, const uint32_t& aFlags,
- const nsACString& aCategory, const bool& aFromPrivateWindow,
- const uint64_t& aInnerWindowId, const bool& aFromChromeContext) {
+ const nsACString& aCategory, const bool& aIsFromPrivateWindow,
+ const uint64_t& aInnerWindowId, const bool& aIsFromChromeContext) {
return RecvScriptErrorInternal(aMessage, aSourceName, aSourceLine,
aLineNumber, aColNumber, aFlags, aCategory,
- aFromPrivateWindow, aFromChromeContext);
+ aIsFromPrivateWindow, aIsFromChromeContext);
}
mozilla::ipc::IPCResult ContentParent::RecvScriptErrorWithStack(
const nsAString& aMessage, const nsAString& aSourceName,
const nsAString& aSourceLine, const uint32_t& aLineNumber,
const uint32_t& aColNumber, const uint32_t& aFlags,
- const nsACString& aCategory, const bool& aFromPrivateWindow,
- const bool& aFromChromeContext, const ClonedMessageData& aFrame) {
+ const nsACString& aCategory, const bool& aIsFromPrivateWindow,
+ const bool& aIsFromChromeContext, const ClonedMessageData& aStack) {
return RecvScriptErrorInternal(
aMessage, aSourceName, aSourceLine, aLineNumber, aColNumber, aFlags,
- aCategory, aFromPrivateWindow, aFromChromeContext, &aFrame);
+ aCategory, aIsFromPrivateWindow, aIsFromChromeContext, &aStack);
}
mozilla::ipc::IPCResult ContentParent::RecvScriptErrorInternal(
const nsAString& aMessage, const nsAString& aSourceName,
const nsAString& aSourceLine, const uint32_t& aLineNumber,
const uint32_t& aColNumber, const uint32_t& aFlags,
- const nsACString& aCategory, const bool& aFromPrivateWindow,
- const bool& aFromChromeContext, const ClonedMessageData* aStack) {
+ const nsACString& aCategory, const bool& aIsFromPrivateWindow,
+ const bool& aIsFromChromeContext, const ClonedMessageData* aStack) {
nsresult rv;
nsCOMPtr<nsIConsoleService> consoleService =
do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv);
@@ -5181,7 +5187,7 @@ mozilla::ipc::IPCResult ContentParent::RecvScriptErrorInternal(
}
rv = msg->Init(aMessage, aSourceName, aSourceLine, aLineNumber, aColNumber,
- aFlags, aCategory, aFromPrivateWindow, aFromChromeContext);
+ aFlags, aCategory, aIsFromPrivateWindow, aIsFromChromeContext);
if (NS_FAILED(rv)) return IPC_OK();
msg->SetIsForwardedFromContentProcess(true);
@@ -5197,9 +5203,9 @@ bool ContentParent::DoLoadMessageManagerScript(const nsAString& aURL,
}
nsresult ContentParent::DoSendAsyncMessage(const nsAString& aMessage,
- StructuredCloneData& aHelper) {
+ StructuredCloneData& aData) {
ClonedMessageData data;
- if (!BuildClonedMessageData(aHelper, data)) {
+ if (!BuildClonedMessageData(aData, data)) {
return NS_ERROR_DOM_DATA_CLONE_ERR;
}
if (!SendAsyncMessage(aMessage, data)) {
@@ -5229,8 +5235,7 @@ mozilla::ipc::IPCResult ContentParent::RecvFindImageText(
return IPC_FAIL(this, "Text recognition not available.");
}
- RefPtr<DataSourceSurface> surf =
- nsContentUtils::IPCImageToSurface(std::move(aImage));
+ RefPtr<DataSourceSurface> surf = nsContentUtils::IPCImageToSurface(aImage);
if (!surf) {
aResolver(TextRecognitionResultOrError("Failed to read image"_ns));
return IPC_OK();
@@ -5255,14 +5260,14 @@ bool ContentParent::ShouldContinueFromReplyTimeout() {
}
mozilla::ipc::IPCResult ContentParent::RecvAddIdleObserver(
- const uint64_t& aObserver, const uint32_t& aIdleTimeInS) {
+ const uint64_t& aObserverId, const uint32_t& aIdleTimeInS) {
nsresult rv;
nsCOMPtr<nsIUserIdleService> idleService =
do_GetService("@mozilla.org/widget/useridleservice;1", &rv);
NS_ENSURE_SUCCESS(rv, IPC_FAIL(this, "Failed to get UserIdleService."));
RefPtr<ParentIdleListener> listener =
- new ParentIdleListener(this, aObserver, aIdleTimeInS);
+ new ParentIdleListener(this, aObserverId, aIdleTimeInS);
rv = idleService->AddIdleObserver(listener, aIdleTimeInS);
NS_ENSURE_SUCCESS(rv, IPC_FAIL(this, "AddIdleObserver failed."));
mIdleListeners.AppendElement(listener);
@@ -5270,11 +5275,11 @@ mozilla::ipc::IPCResult ContentParent::RecvAddIdleObserver(
}
mozilla::ipc::IPCResult ContentParent::RecvRemoveIdleObserver(
- const uint64_t& aObserver, const uint32_t& aIdleTimeInS) {
+ const uint64_t& aObserverId, const uint32_t& aIdleTimeInS) {
RefPtr<ParentIdleListener> listener;
for (int32_t i = mIdleListeners.Length() - 1; i >= 0; --i) {
listener = static_cast<ParentIdleListener*>(mIdleListeners[i].get());
- if (listener->mObserver == aObserver && listener->mTime == aIdleTimeInS) {
+ if (listener->mObserver == aObserverId && listener->mTime == aIdleTimeInS) {
nsresult rv;
nsCOMPtr<nsIUserIdleService> idleService =
do_GetService("@mozilla.org/widget/useridleservice;1", &rv);
@@ -5381,7 +5386,7 @@ mozilla::ipc::IPCResult ContentParent::RecvCreateAudioIPCConnection(
} else {
result = NS_ERROR_FAILURE;
}
- aResolver(std::move(result));
+ aResolver(result);
return IPC_OK();
}
@@ -5781,7 +5786,7 @@ mozilla::ipc::IPCResult ContentParent::RecvCreateWindow(
PBrowserParent* aThisTab, const MaybeDiscarded<BrowsingContext>& aParent,
PBrowserParent* aNewTab, const uint32_t& aChromeFlags,
const bool& aCalledFromJS, const bool& aForPrinting,
- const bool& aForPrintPreview, nsIURI* aURIToLoad,
+ const bool& aForWindowDotPrint, nsIURI* aURIToLoad,
const nsACString& aFeatures, const UserActivation::Modifiers& aModifiers,
nsIPrincipal* aTriggeringPrincipal, nsIContentSecurityPolicy* aCsp,
nsIReferrerInfo* aReferrerInfo, const OriginAttributes& aOriginAttributes,
@@ -5867,8 +5872,8 @@ mozilla::ipc::IPCResult ContentParent::RecvCreateWindow(
int32_t openLocation = nsIBrowserDOMWindow::OPEN_NEWWINDOW;
mozilla::ipc::IPCResult ipcResult = CommonCreateWindow(
aThisTab, *parent, newBCOpenerId != 0, aChromeFlags, aCalledFromJS,
- aForPrinting, aForPrintPreview, aURIToLoad, aFeatures, aModifiers, newTab,
- VoidString(), rv, newRemoteTab, &cwi.windowOpened(), openLocation,
+ aForPrinting, aForWindowDotPrint, aURIToLoad, aFeatures, aModifiers,
+ newTab, VoidString(), rv, newRemoteTab, &cwi.windowOpened(), openLocation,
aTriggeringPrincipal, aReferrerInfo, /* aLoadUri = */ false, aCsp,
aOriginAttributes);
if (!ipcResult) {
@@ -6272,9 +6277,9 @@ ContentParent::RecvUnstoreAndBroadcastBlobURLUnregistration(
}
mozilla::ipc::IPCResult ContentParent::RecvGetFilesRequest(
- const nsID& aUUID, const nsAString& aDirectoryPath,
+ const nsID& aID, const nsAString& aDirectoryPath,
const bool& aRecursiveFlag) {
- MOZ_ASSERT(!mGetFilesPendingRequests.GetWeak(aUUID));
+ MOZ_ASSERT(!mGetFilesPendingRequests.GetWeak(aID));
if (!mozilla::Preferences::GetBool("dom.filesystem.pathcheck.disabled",
false)) {
@@ -6290,30 +6295,30 @@ mozilla::ipc::IPCResult ContentParent::RecvGetFilesRequest(
ErrorResult rv;
RefPtr<GetFilesHelper> helper = GetFilesHelperParent::Create(
- aUUID, aDirectoryPath, aRecursiveFlag, this, rv);
+ aID, aDirectoryPath, aRecursiveFlag, this, rv);
if (NS_WARN_IF(rv.Failed())) {
- if (!SendGetFilesResponse(aUUID,
+ if (!SendGetFilesResponse(aID,
GetFilesResponseFailure(rv.StealNSResult()))) {
return IPC_FAIL(this, "SendGetFilesResponse failed.");
}
return IPC_OK();
}
- mGetFilesPendingRequests.InsertOrUpdate(aUUID, std::move(helper));
+ mGetFilesPendingRequests.InsertOrUpdate(aID, std::move(helper));
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvDeleteGetFilesRequest(
- const nsID& aUUID) {
- mGetFilesPendingRequests.Remove(aUUID);
+ const nsID& aID) {
+ mGetFilesPendingRequests.Remove(aID);
return IPC_OK();
}
void ContentParent::SendGetFilesResponseAndForget(
- const nsID& aUUID, const GetFilesResponseResult& aResult) {
- if (mGetFilesPendingRequests.Remove(aUUID)) {
- Unused << SendGetFilesResponse(aUUID, aResult);
+ const nsID& aID, const GetFilesResponseResult& aResult) {
+ if (mGetFilesPendingRequests.Remove(aID)) {
+ Unused << SendGetFilesResponse(aID, aResult);
}
}
@@ -6615,8 +6620,34 @@ mozilla::ipc::IPCResult ContentParent::RecvRecordDiscardedData(
return IPC_OK();
}
+static bool WebdriverRunning() {
+#ifdef ENABLE_WEBDRIVER
+ nsCOMPtr<nsIMarionette> marionette = do_GetService(NS_MARIONETTE_CONTRACTID);
+ if (marionette) {
+ bool marionetteRunning = false;
+ marionette->GetRunning(&marionetteRunning);
+ if (marionetteRunning) {
+ return true;
+ }
+ }
+
+ nsCOMPtr<nsIRemoteAgent> agent = do_GetService(NS_REMOTEAGENT_CONTRACTID);
+ if (agent) {
+ bool remoteAgentRunning = false;
+ agent->GetRunning(&remoteAgentRunning);
+ if (remoteAgentRunning) {
+ return true;
+ }
+ }
+#endif
+
+ return false;
+}
+
mozilla::ipc::IPCResult ContentParent::RecvRecordPageLoadEvent(
- const mozilla::glean::perf::PageLoadExtra& aPageLoadEventExtra) {
+ mozilla::glean::perf::PageLoadExtra&& aPageLoadEventExtra) {
+ // Check whether a webdriver is running.
+ aPageLoadEventExtra.usingWebdriver = mozilla::Some(WebdriverRunning());
mozilla::glean::perf::page_load.Record(mozilla::Some(aPageLoadEventExtra));
// Send the PageLoadPing after every 30 page loads, or on startup.
@@ -6735,7 +6766,7 @@ bool ContentParent::DeallocPSessionStorageObserverParent(
}
mozilla::ipc::IPCResult ContentParent::RecvBHRThreadHang(
- const HangDetails& aDetails) {
+ const HangDetails& aHangDetails) {
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
// Copy the HangDetails recieved over the network into a nsIHangDetails, and
@@ -6743,7 +6774,7 @@ mozilla::ipc::IPCResult ContentParent::RecvBHRThreadHang(
// XXX: We should be able to avoid this potentially expensive copy here by
// moving our deserialized argument.
nsCOMPtr<nsIHangDetails> hangDetails =
- new nsHangDetails(HangDetails(aDetails), PersistedToDisk::No);
+ new nsHangDetails(HangDetails(aHangDetails), PersistedToDisk::No);
obs->NotifyObservers(hangDetails, "bhr-thread-hang", nullptr);
}
return IPC_OK();
@@ -7151,6 +7182,19 @@ mozilla::ipc::IPCResult ContentParent::RecvNotifyPositionStateChanged(
return IPC_OK();
}
+mozilla::ipc::IPCResult ContentParent::RecvNotifyGuessedPositionStateChanged(
+ const MaybeDiscarded<BrowsingContext>& aContext, const nsID& aMediaId,
+ const Maybe<PositionState>& aState) {
+ if (aContext.IsNullOrDiscarded()) {
+ return IPC_OK();
+ }
+ if (RefPtr<IMediaInfoUpdater> updater =
+ aContext.get_canonical()->GetMediaController()) {
+ updater->UpdateGuessedPositionState(aContext.ContextId(), aMediaId, aState);
+ }
+ return IPC_OK();
+}
+
mozilla::ipc::IPCResult ContentParent::RecvAddOrRemovePageAwakeRequest(
const MaybeDiscarded<BrowsingContext>& aContext,
const bool& aShouldAddCount) {
@@ -7221,16 +7265,14 @@ mozilla::ipc::IPCResult ContentParent::RecvCreateBrowsingContext(
if (parent && parent->Group() != group) {
if (parent->Group()->Id() != aGroupId) {
return IPC_FAIL(this, "Parent has different group ID");
- } else {
- return IPC_FAIL(this, "Parent has different group object");
}
+ return IPC_FAIL(this, "Parent has different group object");
}
if (opener && opener->Group() != group) {
if (opener->Group()->Id() != aGroupId) {
return IPC_FAIL(this, "Opener has different group ID");
- } else {
- return IPC_FAIL(this, "Opener has different group object");
}
+ return IPC_FAIL(this, "Opener has different group object");
}
if (!parent && !opener && !group->Toplevels().IsEmpty()) {
return IPC_FAIL(this, "Unrelated context from child in stale group");
diff --git a/dom/ipc/ContentParent.h b/dom/ipc/ContentParent.h
index 31cfa6de88..8040f4a07b 100644
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -410,7 +410,7 @@ class ContentParent final : public PContentParent,
* WARNING: aReason appears in telemetry, so any new value passed in requires
* data review.
*/
- void KillHard(const char* aWhy);
+ void KillHard(const char* aReason);
ContentParentId ChildID() const { return mChildID; }
@@ -422,8 +422,6 @@ class ContentParent final : public PContentParent,
*/
void FriendlyName(nsAString& aName, bool aAnonymize = false);
- virtual void OnChannelError() override;
-
mozilla::ipc::IPCResult RecvInitCrashReporter(
const NativeThreadId& aThreadId);
@@ -473,12 +471,11 @@ class ContentParent final : public PContentParent,
void ForkNewProcess(bool aBlocking);
mozilla::ipc::IPCResult RecvCreateWindow(
- PBrowserParent* aThisBrowserParent,
- const MaybeDiscarded<BrowsingContext>& aParent, PBrowserParent* aNewTab,
- const uint32_t& aChromeFlags, const bool& aCalledFromJS,
- const bool& aForPrinting, const bool& aForWindowDotPrint,
- nsIURI* aURIToLoad, const nsACString& aFeatures,
- const UserActivation::Modifiers& aModifiers,
+ PBrowserParent* aThisTab, const MaybeDiscarded<BrowsingContext>& aParent,
+ PBrowserParent* aNewTab, const uint32_t& aChromeFlags,
+ const bool& aCalledFromJS, const bool& aForPrinting,
+ const bool& aForWindowDotPrint, nsIURI* aURIToLoad,
+ const nsACString& aFeatures, const UserActivation::Modifiers& aModifiers,
nsIPrincipal* aTriggeringPrincipal, nsIContentSecurityPolicy* aCsp,
nsIReferrerInfo* aReferrerInfo, const OriginAttributes& aOriginAttributes,
CreateWindowResolver&& aResolve);
@@ -879,7 +876,7 @@ class ContentParent final : public PContentParent,
const MaybeDiscarded<BrowsingContext>& aTarget, PrintData&& aPrintData);
mozilla::ipc::IPCResult RecvConstructPopupBrowser(
- ManagedEndpoint<PBrowserParent>&& actor,
+ ManagedEndpoint<PBrowserParent>&& aBrowserEp,
ManagedEndpoint<PWindowGlobalParent>&& windowEp, const TabId& tabId,
const IPCTabContext& context, const WindowGlobalInit& initialWindowInit,
const uint32_t& chromeFlags);
@@ -949,13 +946,14 @@ class ContentParent final : public PContentParent,
PBrowserParent* aBrowser,
const MaybeDiscarded<BrowsingContext>& aContext);
- mozilla::ipc::IPCResult RecvSetClipboard(const IPCTransferable& aTransferable,
- const int32_t& aWhichClipboard);
+ mozilla::ipc::IPCResult RecvSetClipboard(
+ const IPCTransferable& aTransferable, const int32_t& aWhichClipboard,
+ const MaybeDiscarded<WindowContext>& aRequestingWindowContext);
mozilla::ipc::IPCResult RecvGetClipboard(
nsTArray<nsCString>&& aTypes, const int32_t& aWhichClipboard,
const MaybeDiscarded<WindowContext>& aRequestingWindowContext,
- IPCTransferableData* aTransferableData);
+ IPCTransferableDataOrError* aTransferableDataOrError);
mozilla::ipc::IPCResult RecvEmptyClipboard(const int32_t& aWhichClipboard);
@@ -975,7 +973,9 @@ class ContentParent final : public PContentParent,
ClipboardReadRequestOrError* aRequestOrError);
already_AddRefed<PClipboardWriteRequestParent>
- AllocPClipboardWriteRequestParent(const int32_t& aClipboardType);
+ AllocPClipboardWriteRequestParent(
+ const int32_t& aClipboardType,
+ const MaybeDiscarded<WindowContext>& aSettingWindowContext);
mozilla::ipc::IPCResult RecvGetIconForExtension(const nsACString& aFileExt,
const uint32_t& aIconSize,
@@ -1085,10 +1085,10 @@ class ContentParent final : public PContentParent,
mozilla::ipc::IPCResult RecvEndDriverCrashGuard(const uint32_t& aGuardType);
- mozilla::ipc::IPCResult RecvAddIdleObserver(const uint64_t& observerId,
+ mozilla::ipc::IPCResult RecvAddIdleObserver(const uint64_t& aObserverId,
const uint32_t& aIdleTimeInS);
- mozilla::ipc::IPCResult RecvRemoveIdleObserver(const uint64_t& observerId,
+ mozilla::ipc::IPCResult RecvRemoveIdleObserver(const uint64_t& aObserverId,
const uint32_t& aIdleTimeInS);
mozilla::ipc::IPCResult RecvBackUpXResources(
@@ -1184,7 +1184,7 @@ class ContentParent final : public PContentParent,
mozilla::ipc::IPCResult RecvRecordDiscardedData(
const DiscardedData& aDiscardedData);
mozilla::ipc::IPCResult RecvRecordPageLoadEvent(
- const mozilla::glean::perf::PageLoadExtra& aPageLoadEventExtra);
+ mozilla::glean::perf::PageLoadExtra&& aPageLoadEventExtra);
mozilla::ipc::IPCResult RecvRecordOrigin(const uint32_t& aMetricId,
const nsACString& aOrigin);
mozilla::ipc::IPCResult RecvReportContentBlockingLog(
@@ -1272,6 +1272,10 @@ class ContentParent final : public PContentParent,
const MaybeDiscarded<BrowsingContext>& aContext,
const Maybe<PositionState>& aState);
+ mozilla::ipc::IPCResult RecvNotifyGuessedPositionStateChanged(
+ const MaybeDiscarded<BrowsingContext>& aContext, const nsID& aMediaId,
+ const Maybe<PositionState>& aState);
+
mozilla::ipc::IPCResult RecvAddOrRemovePageAwakeRequest(
const MaybeDiscarded<BrowsingContext>& aContext,
const bool& aShouldAddCount);
@@ -1660,8 +1664,7 @@ class ThreadsafeContentParentHandle final {
};
// This is the C++ version of remoteTypePrefix in E10SUtils.sys.mjs.
-const nsDependentCSubstring RemoteTypePrefix(
- const nsACString& aContentProcessType);
+nsDependentCSubstring RemoteTypePrefix(const nsACString& aContentProcessType);
// This is based on isWebRemoteType in E10SUtils.sys.mjs.
bool IsWebRemoteType(const nsACString& aContentProcessType);
diff --git a/dom/ipc/DOMTypes.ipdlh b/dom/ipc/DOMTypes.ipdlh
index 1be3537e68..034410eb4d 100644
--- a/dom/ipc/DOMTypes.ipdlh
+++ b/dom/ipc/DOMTypes.ipdlh
@@ -39,6 +39,7 @@ using mozilla::CSSRect from "Units.h";
using mozilla::CSSSize from "Units.h";
using mozilla::ScreenIntSize from "Units.h";
using mozilla::LayoutDeviceIntPoint from "Units.h";
+using mozilla::ImageIntSize from "Units.h";
using nsSizeMode from "nsIWidgetListener.h";
using mozilla::ScrollbarPreference from "mozilla/ScrollbarPreferences.h";
using mozilla::gfx::SurfaceFormat from "mozilla/gfx/Types.h";
@@ -326,5 +327,12 @@ struct OwnerShowInfo {
nsSizeMode sizeMode;
};
+struct IPCImage {
+ BigBuffer data;
+ uint32_t stride;
+ SurfaceFormat format;
+ ImageIntSize size;
+};
+
} // namespace dom
} // namespace mozilla
diff --git a/dom/ipc/IPCTransferable.ipdlh b/dom/ipc/IPCTransferable.ipdlh
index 1e277d05dd..9eecbf2dce 100644
--- a/dom/ipc/IPCTransferable.ipdlh
+++ b/dom/ipc/IPCTransferable.ipdlh
@@ -7,6 +7,7 @@
include "mozilla/GfxMessageUtils.h";
include "mozilla/dom/PermissionMessageUtils.h";
+include DOMTypes;
include IPCBlob;
include NeckoChannelParams;
@@ -42,11 +43,7 @@ struct IPCTransferableDataInputStream
struct IPCTransferableDataImageContainer
{
- BigBuffer data;
- uint32_t width;
- uint32_t height;
- uint32_t stride;
- SurfaceFormat format;
+ IPCImage image;
};
struct IPCTransferableDataBlob
@@ -83,7 +80,7 @@ struct IPCTransferable
{
IPCTransferableData data;
bool isPrivateData;
- nullable nsIPrincipal requestingPrincipal;
+ nullable nsIPrincipal dataPrincipal;
CookieJarSettingsArgs? cookieJarSettings;
nsContentPolicyType contentPolicyType;
nullable nsIReferrerInfo referrerInfo;
diff --git a/dom/ipc/PBrowser.ipdl b/dom/ipc/PBrowser.ipdl
index 372a81b139..e8df017ca2 100644
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -376,23 +376,12 @@ parent:
* Set the native cursor.
* @param value
* The widget cursor to set.
- * @param hasCustomCursor
- * Whether there's any custom cursor represented by cursorData and
- * company.
- * @param customCursorData
- * Serialized image data.
- * @param width
- * Width of the image.
- * @param height
- * Height of the image.
+ * @param customCursor
+ * Serialized image data for custom cursor.
* @param resolutionX
* Resolution of the image X axis in dppx units.
* @param resolutionY
* Resolution of the image Y axis in dppx units.
- * @param stride
- * Stride used in the image data.
- * @param format
- * Image format, see gfx::SurfaceFormat for possible values.
* @param hotspotX
* Horizontal hotspot of the image, as specified by the css cursor property.
* @param hotspotY
@@ -402,11 +391,8 @@ parent:
* update.
*/
async SetCursor(nsCursor value,
- bool hasCustomCursor,
- BigBuffer? customCursorData,
- uint32_t width, uint32_t height,
+ IPCImage? customCursor,
float resolutionX, float resolutionY,
- uint32_t stride, SurfaceFormat format,
uint32_t hotspotX, uint32_t hotspotY, bool force);
/**
diff --git a/dom/ipc/PContent.ipdl b/dom/ipc/PContent.ipdl
index 67c4c298e4..0aaef08b7b 100644
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -101,7 +101,6 @@ using mozilla::dom::ContentParentId from "mozilla/dom/ipc/IdType.h";
using mozilla::EventMessage from "mozilla/EventForwards.h";
using mozilla::LayoutDeviceIntPoint from "Units.h";
using mozilla::ImagePoint from "Units.h";
-using mozilla::ImageIntSize from "Units.h";
using mozilla::widget::ThemeChangeKind from "mozilla/widget/WidgetMessageUtils.h";
using class mozilla::dom::MessagePort from "mozilla/dom/MessagePort.h";
[MoveOnly=data] using class mozilla::dom::ipc::StructuredCloneData from "mozilla/dom/ipc/StructuredCloneData.h";
@@ -463,13 +462,6 @@ union TextRecognitionResultOrError {
nsCString;
};
-struct IPCImage {
- BigBuffer data;
- uint32_t stride;
- SurfaceFormat format;
- ImageIntSize size;
-};
-
struct ClipboardReadRequest {
ManagedEndpoint<PClipboardReadRequestChild> childEndpoint;
nsCString[] availableTypes;
@@ -1052,9 +1044,6 @@ child:
// Update the cached list of codec supported in the given process.
async UpdateMediaCodecsSupported(RemoteDecodeIn aLocation, MediaCodecsSupported aSupported);
- // Send the list of the supported mimetypes in the given process. GeckoView-specific
- async DecoderSupportedMimeTypes(nsCString[] supportedTypes);
-
// Used to initialize the global variable in content processes with the
// latched value in the parent process. See dom/LocalStorageCommon.h for more
// details.
@@ -1214,12 +1203,12 @@ parent:
async ConsoleMessage(nsString message);
async ScriptErrorWithStack(nsString message, nsString sourceName, nsString sourceLine,
uint32_t lineNumber, uint32_t colNumber, uint32_t flags,
- nsCString category, bool privateWindow,
- bool fromChromeContext, ClonedMessageData stack);
+ nsCString category, bool isFromPrivateWindow,
+ bool isFromChromeContext, ClonedMessageData stack);
// Places the items within dataTransfer on the clipboard.
async SetClipboard(IPCTransferable aTransferable,
- int32_t aWhichClipboard);
+ int32_t aWhichClipboard, MaybeDiscardedWindowContext aRequestingWindowContext);
// Given a list of supported types, returns the clipboard data for the
// first type that matches.
@@ -1227,7 +1216,7 @@ parent:
// which is used for content analysis.
sync GetClipboard(nsCString[] aTypes, int32_t aWhichClipboard,
MaybeDiscardedWindowContext aRequestingWindowContext)
- returns (IPCTransferableData transferableData);
+ returns (IPCTransferableDataOrError transferableDataOrError);
// Requests getting data from clipboard.
async GetClipboardAsync(nsCString[] aTypes, int32_t aWhichClipboard,
@@ -1252,8 +1241,12 @@ parent:
* and that the data will be sent over another IPC message once it is ready.
* @param aClipboardType
* The clipboard type defined in nsIClipboard.
+ * @param aSettingWindowContext
+ * The window context that is setting the clipboard, if any. This is used
+ * to possibly bypass Content Analysis if a set clipboard and get clipboard
+ * operation are done on the same page.
*/
- async PClipboardWriteRequest(int32_t aClipboardType);
+ async PClipboardWriteRequest(int32_t aClipboardType, MaybeDiscardedWindowContext aSettingWindowContext);
sync GetIconForExtension(nsCString aFileExt, uint32_t aIconSize)
returns (uint8_t[] bits);
@@ -1741,6 +1734,15 @@ parent:
PositionState? aState);
/**
+ * This method is used to update a media's position state whenever its
+ * guessed position state is being updated.
+ */
+ async NotifyGuessedPositionStateChanged(
+ MaybeDiscardedBrowsingContext aContext,
+ nsID aMediaId,
+ PositionState? aState);
+
+ /**
* This method will make canonical browsing context to update the count of
* callers which want to keep the page from being suspended even if the page
* is inactive.
@@ -1819,8 +1821,8 @@ parent:
both:
async ScriptError(nsString message, nsString sourceName, nsString sourceLine,
uint32_t lineNumber, uint32_t colNumber, uint32_t flags,
- nsCString category, bool privateWindow, uint64_t innerWindowId,
- bool fromChromeContext);
+ nsCString category, bool isFromPrivateWindow, uint64_t innerWindowId,
+ bool isFromChromeContext);
/**
* Used in fission to report timing data when the parent window is in
diff --git a/dom/ipc/PWindowGlobal.ipdl b/dom/ipc/PWindowGlobal.ipdl
index a063852f56..49141fd1ce 100644
--- a/dom/ipc/PWindowGlobal.ipdl
+++ b/dom/ipc/PWindowGlobal.ipdl
@@ -219,6 +219,10 @@ parent:
bool fromHttp,
CookieStruct[] cookies);
+ // Notify parent of storage access in the content process. This only happens
+ // once per window lifetime to avoid redundant IPC.
+ async OnInitialStorageAccess();
+
child:
async NotifyPermissionChange(nsCString type, uint32_t permission);
};
diff --git a/dom/ipc/TabContext.cpp b/dom/ipc/TabContext.cpp
index 4344ec221a..e92005603a 100644
--- a/dom/ipc/TabContext.cpp
+++ b/dom/ipc/TabContext.cpp
@@ -17,9 +17,7 @@ using namespace mozilla::layout;
namespace mozilla::dom {
TabContext::TabContext()
- : mInitialized(false),
- mChromeOuterWindowID(0),
- mMaxTouchPoints(0) {}
+ : mInitialized(false), mChromeOuterWindowID(0), mMaxTouchPoints(0) {}
uint64_t TabContext::ChromeOuterWindowID() const {
return mChromeOuterWindowID;
diff --git a/dom/ipc/WindowGlobalChild.cpp b/dom/ipc/WindowGlobalChild.cpp
index 4072828911..7fb95709ee 100644
--- a/dom/ipc/WindowGlobalChild.cpp
+++ b/dom/ipc/WindowGlobalChild.cpp
@@ -867,9 +867,26 @@ nsISupports* WindowGlobalChild::GetParentObject() {
return xpc::NativeGlobal(xpc::PrivilegedJunkScope());
}
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WEAK_PTR(WindowGlobalChild, mWindowGlobal,
- mContainerFeaturePolicy,
- mWindowContext)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(WindowGlobalChild)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WindowGlobalChild)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindowGlobal)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mContainerFeaturePolicy)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindowContext)
+ tmp->UnlinkManager();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WindowGlobalChild)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindowGlobal)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContainerFeaturePolicy)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindowContext)
+ if (!tmp->IsInProcess()) {
+ CycleCollectionNoteChild(cb, static_cast<BrowserChild*>(tmp->Manager()),
+ "Manager()");
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WindowGlobalChild)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
diff --git a/dom/ipc/WindowGlobalParent.cpp b/dom/ipc/WindowGlobalParent.cpp
index f60790a155..4379c7280c 100644
--- a/dom/ipc/WindowGlobalParent.cpp
+++ b/dom/ipc/WindowGlobalParent.cpp
@@ -10,6 +10,7 @@
#include "mozilla/AntiTrackingUtils.h"
#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/BounceTrackingStorageObserver.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/ContentBlockingAllowList.h"
#include "mozilla/dom/InProcessParent.h"
@@ -75,7 +76,7 @@
#include "mozilla/net/PCookieServiceParent.h"
#include "mozilla/net/CookieServiceParent.h"
-#include "SessionStoreFunctions.h"
+#include "nsISessionStoreFunctions.h"
#include "nsIXPConnect.h"
#include "nsImportModule.h"
#include "nsIXULRuntime.h"
@@ -1705,8 +1706,29 @@ IPCResult WindowGlobalParent::RecvSetCookies(
aCookies, GetBrowsingContext());
}
-NS_IMPL_CYCLE_COLLECTION_INHERITED(WindowGlobalParent, WindowContext,
- mPageUseCountersWindow)
+IPCResult WindowGlobalParent::RecvOnInitialStorageAccess() {
+ DebugOnly<nsresult> rv =
+ BounceTrackingStorageObserver::OnInitialStorageAccess(this);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to notify storage access");
+ return IPC_OK();
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(WindowGlobalParent)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WindowGlobalParent,
+ WindowContext)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPageUseCountersWindow)
+ tmp->UnlinkManager();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WindowGlobalParent,
+ WindowContext)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPageUseCountersWindow)
+ if (!tmp->IsInProcess()) {
+ CycleCollectionNoteChild(cb, static_cast<BrowserParent*>(tmp->Manager()),
+ "Manager()");
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(WindowGlobalParent,
WindowContext)
diff --git a/dom/ipc/WindowGlobalParent.h b/dom/ipc/WindowGlobalParent.h
index be801de0d3..3680c14596 100644
--- a/dom/ipc/WindowGlobalParent.h
+++ b/dom/ipc/WindowGlobalParent.h
@@ -329,6 +329,8 @@ class WindowGlobalParent final : public WindowContext,
const nsCString& aBaseDomain, const OriginAttributes& aOriginAttributes,
nsIURI* aHost, bool aFromHttp, const nsTArray<CookieStruct>& aCookies);
+ mozilla::ipc::IPCResult RecvOnInitialStorageAccess();
+
private:
WindowGlobalParent(CanonicalBrowsingContext* aBrowsingContext,
uint64_t aInnerWindowId, uint64_t aOuterWindowId,
diff --git a/dom/l10n/DocumentL10n.cpp b/dom/l10n/DocumentL10n.cpp
index c53b3aa0c7..9cf1d96c39 100644
--- a/dom/l10n/DocumentL10n.cpp
+++ b/dom/l10n/DocumentL10n.cpp
@@ -93,8 +93,15 @@ class L10nReadyHandler final : public PromiseNativeHandler {
* rejection" warning, which is noisy and not-actionable.
*
* So instead, we just resolve and report errors.
+ *
+ * However, in automated tests we do reject so that errors with missing
+ * messages and resources can be caught.
*/
- mPromise->MaybeResolveWithUndefined();
+ if (xpc::IsInAutomation()) {
+ mPromise->MaybeRejectWithClone(aCx, aValue);
+ } else {
+ mPromise->MaybeResolveWithUndefined();
+ }
}
private:
diff --git a/dom/l10n/tests/mochitest/document_l10n/test_docl10n_ready_rejected.html b/dom/l10n/tests/mochitest/document_l10n/test_docl10n_ready_rejected.html
index 63e18f802c..a80a4b6d94 100644
--- a/dom/l10n/tests/mochitest/document_l10n/test_docl10n_ready_rejected.html
+++ b/dom/l10n/tests/mochitest/document_l10n/test_docl10n_ready_rejected.html
@@ -12,12 +12,17 @@
document.addEventListener("DOMContentLoaded", async function() {
/**
- * Even when we fail to localize all elements, we will
- * still resolve the `ready` promise to communicate that
+ * Outside of tests, even when we fail to localize all elements,
+ * we will still resolve the `ready` promise to communicate that
* the initial translation phase is now completed.
+ *
+ * In tests, the promise will be rejected to allow errors to be caught.
*/
document.l10n.ready.then(() => {
- is(1, 1, "the ready should resolve");
+ is(1, 2, "the ready should not resolve");
+ SimpleTest.finish();
+ }, (_err) => {
+ is(1, 1, "the ready should reject");
SimpleTest.finish();
});
});
diff --git a/dom/locales/en-US/chrome/accessibility/win/accessible.properties b/dom/locales/en-US/chrome/accessibility/win/accessible.properties
index 7a0665712e..df3b8fa218 100644
--- a/dom/locales/en-US/chrome/accessibility/win/accessible.properties
+++ b/dom/locales/en-US/chrome/accessibility/win/accessible.properties
@@ -19,3 +19,12 @@ cycle = Cycle
# them to click an element when the click will be handled by a container
# (ancestor) element. This is not normally reported to users.
click ancestor = Click ancestor
+
+# These messages are reported by accessibility clients such as screen readers to
+# indicate landmarks, which are significant sections of a document to which
+# users might want to navigate quickly. See this page for more information:
+# https://www.w3.org/WAI/ARIA/apg/patterns/landmarks/examples/general-principles.html
+banner = banner
+complementary = complementary
+contentinfo = content information
+region = region
diff --git a/dom/locales/en-US/chrome/security/csp.properties b/dom/locales/en-US/chrome/security/csp.properties
index f077bc8ab0..27cb47caf7 100644
--- a/dom/locales/en-US/chrome/security/csp.properties
+++ b/dom/locales/en-US/chrome/security/csp.properties
@@ -201,6 +201,15 @@ duplicateDirective = Duplicate %1$S directives detected. All but the first inst
# LOCALIZATION NOTE (couldntParseInvalidSandboxFlag):
# %1$S is the option that could not be understood
couldntParseInvalidSandboxFlag = Couldn’t parse invalid sandbox flag ‘%1$S’
+# LOCALIZATION NOTE (invalidNumberOfTrustedTypesForDirectiveValues):
+# %1$S is the number of passed tokens.
+invalidNumberOfTrustedTypesForDirectiveValues = Received an invalid number of tokens for the ‘require-trusted-types-for‘ directive: %1$S; expected 1
+# LOCALIZATION NOTE (invalidRequireTrustedTypesForDirectiveValue):
+# %1$S is the passed token
+invalidRequireTrustedTypesForDirectiveValue = Received an invalid token for the ‘require-trusted-types-for‘ directive: %1$S; expected ‘script‘
+# LOCALIZATION NOTE (invalidTrustedTypesExpression):
+# %1$S is the passed token
+invalidTrustedTypesExpression = Received an invalid token for the ‘trusted-types‘ directive: %1$S
# LOCALIZATION NOTE (CSPMessagePrefix):
# Do not translate "Content-Security-Policy", only handle spacing for the colon.
diff --git a/dom/mathml/MathMLElement.cpp b/dom/mathml/MathMLElement.cpp
index af5e9bc22b..04e8648a3f 100644
--- a/dom/mathml/MathMLElement.cpp
+++ b/dom/mathml/MathMLElement.cpp
@@ -7,10 +7,10 @@
#include "mozilla/dom/MathMLElement.h"
#include "base/compiler_specific.h"
+#include "mozilla/FocusModel.h"
#include "mozilla/dom/BindContext.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/EventListenerManager.h"
-#include "mozilla/FontPropertyTypes.h"
#include "mozilla/StaticPrefs_mathml.h"
#include "mozilla/TextUtils.h"
#include "nsGkAtoms.h"
@@ -20,7 +20,6 @@
#include "nsStyleConsts.h"
#include "mozilla/dom/Document.h"
#include "nsPresContext.h"
-#include "mozAutoDocUpdate.h"
#include "nsIScriptError.h"
#include "nsContentUtils.h"
#include "nsIURI.h"
@@ -612,7 +611,7 @@ void MathMLElement::SetIncrementScriptLevel(bool aIncrementScriptLevel,
int32_t MathMLElement::TabIndexDefault() { return IsLink() ? 0 : -1; }
// XXX Bug 1586011: Share logic with other element classes.
-Focusable MathMLElement::IsFocusableWithoutStyle(bool aWithMouse) {
+Focusable MathMLElement::IsFocusableWithoutStyle(IsFocusableFlags) {
if (!IsInComposedDoc() || IsInDesignMode()) {
// In designMode documents we only allow focusing the document.
return {};
@@ -637,7 +636,7 @@ Focusable MathMLElement::IsFocusableWithoutStyle(bool aWithMouse) {
return {};
}
- if ((sTabFocusModel & eTabFocus_linksMask) == 0) {
+ if (!FocusModel::IsTabFocusable(TabFocusableType::Links)) {
tabIndex = -1;
}
diff --git a/dom/mathml/MathMLElement.h b/dom/mathml/MathMLElement.h
index a8c21e841a..6168701beb 100644
--- a/dom/mathml/MathMLElement.h
+++ b/dom/mathml/MathMLElement.h
@@ -74,7 +74,7 @@ class MathMLElement final : public MathMLElementBase, public Link {
int32_t TabIndexDefault() final;
- Focusable IsFocusableWithoutStyle(bool aWithMouse) override;
+ Focusable IsFocusableWithoutStyle(IsFocusableFlags) override;
already_AddRefed<nsIURI> GetHrefURI() const override;
void NodeInfoChanged(Document* aOldDoc) override {
diff --git a/dom/media/AsyncLogger.h b/dom/media/AsyncLogger.h
index adc4101382..2dedabea7c 100644
--- a/dom/media/AsyncLogger.h
+++ b/dom/media/AsyncLogger.h
@@ -17,7 +17,7 @@
#include "mozilla/Sprintf.h"
#include "mozilla/TimeStamp.h"
#include "GeckoProfiler.h"
-#include "MPSCQueue.h"
+#include "mozilla/dom/MPSCQueue.h"
#if defined(_WIN32)
# include <process.h>
diff --git a/dom/media/AudioInputSource.cpp b/dom/media/AudioInputSource.cpp
index 1ba2d81938..ef471530d6 100644
--- a/dom/media/AudioInputSource.cpp
+++ b/dom/media/AudioInputSource.cpp
@@ -55,46 +55,63 @@ AudioInputSource::AudioInputSource(RefPtr<EventListener>&& aListener,
mSandboxed(CubebUtils::SandboxEnabled()),
mAudioThreadId(ProfilerThreadId{}),
mEventListener(std::move(aListener)),
- mTaskThread(CUBEB_TASK_THREAD),
+ mTaskThread(CubebUtils::GetCubebOperationThread()),
mDriftCorrector(static_cast<uint32_t>(aSourceRate),
static_cast<uint32_t>(aTargetRate), aPrincipalHandle) {
MOZ_ASSERT(mChannelCount > 0);
MOZ_ASSERT(mEventListener);
}
-void AudioInputSource::Start() {
+void AudioInputSource::Init() {
// This is called on MediaTrackGraph's graph thread, which can be the cubeb
// stream's callback thread. Running cubeb operations within cubeb stream
// callback thread can cause the deadlock on Linux, so we dispatch those
// operations to the task thread.
MOZ_ASSERT(mTaskThread);
- LOG("AudioInputSource %p, start", this);
+ LOG("AudioInputSource %p, init", this);
MOZ_ALWAYS_SUCCEEDS(mTaskThread->Dispatch(
- NS_NewRunnableFunction(__func__, [self = RefPtr(this)]() mutable {
- self->mStream = CubebInputStream::Create(
- self->mDeviceId, self->mChannelCount,
- static_cast<uint32_t>(self->mRate), self->mIsVoice, self.get());
- if (!self->mStream) {
+ NS_NewRunnableFunction(__func__, [this, self = RefPtr(this)]() mutable {
+ mStream = CubebInputStream::Create(mDeviceId, mChannelCount,
+ static_cast<uint32_t>(mRate),
+ mIsVoice, this);
+ if (!mStream) {
LOGE("AudioInputSource %p, cannot create an audio input stream!",
self.get());
return;
}
+ })));
+}
- if (uint32_t latency = 0;
- self->mStream->Latency(&latency) == CUBEB_OK) {
- Data data(LatencyChangeData{media::TimeUnit(latency, self->mRate)});
- if (self->mSPSCQueue.Enqueue(data) == 0) {
+void AudioInputSource::Start() {
+ // This is called on MediaTrackGraph's graph thread, which can be the cubeb
+ // stream's callback thread. Running cubeb operations within cubeb stream
+ // callback thread can cause the deadlock on Linux, so we dispatch those
+ // operations to the task thread.
+ MOZ_ASSERT(mTaskThread);
+
+ LOG("AudioInputSource %p, start", this);
+ MOZ_ALWAYS_SUCCEEDS(mTaskThread->Dispatch(
+ NS_NewRunnableFunction(__func__, [this, self = RefPtr(this)]() mutable {
+ if (!mStream) {
+ LOGE("AudioInputSource %p, no audio input stream!", self.get());
+ return;
+ }
+
+ if (uint32_t latency = 0; mStream->Latency(&latency) == CUBEB_OK) {
+ Data data(LatencyChangeData{media::TimeUnit(latency, mRate)});
+ if (mSPSCQueue.Enqueue(data) == 0) {
LOGE("AudioInputSource %p, failed to enqueue latency change",
self.get());
}
}
- if (int r = self->mStream->Start(); r != CUBEB_OK) {
+ if (int r = mStream->Start(); r != CUBEB_OK) {
LOGE(
"AudioInputSource %p, cannot start its audio input stream! The "
"stream is destroyed directly!",
self.get());
- self->mStream = nullptr;
+ mStream = nullptr;
+ mConfiguredProcessingParams = CUBEB_INPUT_PROCESSING_PARAM_NONE;
}
})));
}
@@ -108,20 +125,72 @@ void AudioInputSource::Stop() {
LOG("AudioInputSource %p, stop", this);
MOZ_ALWAYS_SUCCEEDS(mTaskThread->Dispatch(
- NS_NewRunnableFunction(__func__, [self = RefPtr(this)]() mutable {
- if (!self->mStream) {
+ NS_NewRunnableFunction(__func__, [this, self = RefPtr(this)]() mutable {
+ if (!mStream) {
LOGE("AudioInputSource %p, has no audio input stream to stop!",
self.get());
return;
}
- if (int r = self->mStream->Stop(); r != CUBEB_OK) {
+ if (int r = mStream->Stop(); r != CUBEB_OK) {
LOGE(
"AudioInputSource %p, cannot stop its audio input stream! The "
"stream is going to be destroyed forcefully",
self.get());
}
- self->mStream = nullptr;
+ mStream = nullptr;
+ mConfiguredProcessingParams = CUBEB_INPUT_PROCESSING_PARAM_NONE;
+ })));
+}
+
+auto AudioInputSource::SetRequestedProcessingParams(
+ cubeb_input_processing_params aParams)
+ -> RefPtr<SetRequestedProcessingParamsPromise> {
+ // This is called on MediaTrackGraph's graph thread, which can be the cubeb
+ // stream's callback thread. Running cubeb operations within cubeb stream
+ // callback thread can cause the deadlock on Linux, so we dispatch those
+ // operations to the task thread.
+ MOZ_ASSERT(mTaskThread);
+
+ LOG("AudioInputSource %p, SetProcessingParams(%s)", this,
+ CubebUtils::ProcessingParamsToString(aParams).get());
+ using ProcessingPromise = SetRequestedProcessingParamsPromise;
+ MozPromiseHolder<ProcessingPromise> holder;
+ RefPtr<ProcessingPromise> p = holder.Ensure(__func__);
+ MOZ_ALWAYS_SUCCEEDS(mTaskThread->Dispatch(NS_NewRunnableFunction(
+ __func__, [this, self = RefPtr(this), holder = std::move(holder),
+ aParams]() mutable {
+ if (!mStream) {
+ LOGE(
+ "AudioInputSource %p, has no audio input stream to set "
+ "processing params on!",
+ this);
+ holder.Reject(CUBEB_ERROR,
+ "AudioInputSource::SetProcessingParams no stream");
+ return;
+ }
+ cubeb_input_processing_params supportedParams;
+ auto handle = CubebUtils::GetCubeb();
+ int r = cubeb_get_supported_input_processing_params(handle->Context(),
+ &supportedParams);
+ if (r != CUBEB_OK) {
+ holder.Reject(CUBEB_ERROR_NOT_SUPPORTED,
+ "AudioInputSource::SetProcessingParams");
+ return;
+ }
+ aParams &= supportedParams;
+ if (aParams == mConfiguredProcessingParams) {
+ holder.Resolve(aParams, "AudioInputSource::SetProcessingParams");
+ return;
+ }
+ mConfiguredProcessingParams = aParams;
+ r = mStream->SetProcessingParams(aParams);
+ if (r == CUBEB_OK) {
+ holder.Resolve(aParams, "AudioInputSource::SetProcessingParams");
+ return;
+ }
+ holder.Reject(r, "AudioInputSource::SetProcessingParams");
})));
+ return p;
}
AudioSegment AudioInputSource::GetAudioSegment(TrackTime aDuration,
diff --git a/dom/media/AudioInputSource.h b/dom/media/AudioInputSource.h
index b44a3ae43a..8f69165719 100644
--- a/dom/media/AudioInputSource.h
+++ b/dom/media/AudioInputSource.h
@@ -12,6 +12,7 @@
#include "CubebInputStream.h"
#include "CubebUtils.h"
#include "TimeUnits.h"
+#include "mozilla/MozPromise.h"
#include "mozilla/ProfilerUtils.h"
#include "mozilla/RefPtr.h"
#include "mozilla/SPSCQueue.h"
@@ -54,10 +55,17 @@ class AudioInputSource : public CubebInputStream::Listener {
// The following functions should always be called in the same thread: They
// are always run on MediaTrackGraph's graph thread.
+ // Sets up mStream.
+ void Init();
// Starts producing audio data.
void Start();
// Stops producing audio data.
void Stop();
+ // Set the params to be applied in the platform for this source.
+ using SetRequestedProcessingParamsPromise =
+ MozPromise<cubeb_input_processing_params, int, true>;
+ RefPtr<SetRequestedProcessingParamsPromise> SetRequestedProcessingParams(
+ cubeb_input_processing_params aParams);
// Returns the AudioSegment with aDuration of data inside.
// The graph thread can change behind the scene, e.g., cubeb stream reinit due
// to default output device changed). When this happens, we need to notify
@@ -118,6 +126,11 @@ class AudioInputSource : public CubebInputStream::Listener {
// An input-only cubeb stream operated within mTaskThread.
UniquePtr<CubebInputStream> mStream;
+ // The params configured on the cubeb stream, after filtering away unsupported
+ // params. mTaskThread only.
+ cubeb_input_processing_params mConfiguredProcessingParams =
+ CUBEB_INPUT_PROCESSING_PARAM_NONE;
+
struct Empty {};
struct LatencyChangeData {
diff --git a/dom/media/AudioRingBuffer.cpp b/dom/media/AudioRingBuffer.cpp
index 475de653b8..cbeb64a2f6 100644
--- a/dom/media/AudioRingBuffer.cpp
+++ b/dom/media/AudioRingBuffer.cpp
@@ -270,10 +270,13 @@ class RingBuffer final {
* Re-allocates memory if a larger buffer is requested than what is already
* allocated.
*/
- bool SetLengthBytes(uint32_t aLengthBytes) {
+ bool EnsureLengthBytes(uint32_t aLengthBytes) {
MOZ_ASSERT(aLengthBytes % sizeof(T) == 0,
"Length in bytes is not a whole number of samples");
+ if (mMemoryBuffer.Length() >= aLengthBytes) {
+ return true;
+ }
uint32_t lengthSamples = aLengthBytes / sizeof(T);
uint32_t oldLengthSamples = Capacity();
uint32_t availableRead = AvailableRead();
@@ -530,14 +533,17 @@ uint32_t AudioRingBuffer::Clear() {
return mPtr->mFloatRingBuffer->Clear();
}
-bool AudioRingBuffer::SetLengthBytes(uint32_t aLengthBytes) {
+bool AudioRingBuffer::EnsureLengthBytes(uint32_t aLengthBytes) {
if (mPtr->mFloatRingBuffer) {
- return mPtr->mFloatRingBuffer->SetLengthBytes(aLengthBytes);
+ return mPtr->mFloatRingBuffer->EnsureLengthBytes(aLengthBytes);
}
if (mPtr->mIntRingBuffer) {
- return mPtr->mIntRingBuffer->SetLengthBytes(aLengthBytes);
+ return mPtr->mIntRingBuffer->EnsureLengthBytes(aLengthBytes);
}
if (mPtr->mBackingBuffer) {
+ if (mPtr->mBackingBuffer->Length() >= aLengthBytes) {
+ return true;
+ }
return mPtr->mBackingBuffer->SetLength(aLengthBytes);
}
MOZ_ASSERT_UNREACHABLE("Unexpected");
diff --git a/dom/media/AudioRingBuffer.h b/dom/media/AudioRingBuffer.h
index 892a7cd408..a08211ba15 100644
--- a/dom/media/AudioRingBuffer.h
+++ b/dom/media/AudioRingBuffer.h
@@ -93,12 +93,12 @@ class AudioRingBuffer final {
uint32_t Clear();
/**
- * Set the length of the ring buffer in bytes. Must be divisible by the sample
- * size. Will not deallocate memory if the underlying buffer is large enough.
- * Returns false if setting the length requires allocating memory and the
- * allocation fails.
+ * Increase the ring buffer size if necessary to at least the specified length
+ * in bytes. Must be divisible by the sample size.
+ * Will not deallocate memory if the underlying buffer is large enough.
+ * Returns false if memory allocation is required and fails.
*/
- bool SetLengthBytes(uint32_t aLengthBytes);
+ bool EnsureLengthBytes(uint32_t aLengthBytes);
/**
* Return the number of samples this buffer can hold.
diff --git a/dom/media/AudioStream.cpp b/dom/media/AudioStream.cpp
index 7d80a3738e..bb0248d942 100644
--- a/dom/media/AudioStream.cpp
+++ b/dom/media/AudioStream.cpp
@@ -316,7 +316,8 @@ void AudioStream::SetStreamName(const nsAString& aStreamName) {
}
MonitorAutoLock mon(mMonitor);
- if (InvokeCubeb(cubeb_stream_set_name, aRawStreamName.get()) != CUBEB_OK) {
+ int r = InvokeCubeb(cubeb_stream_set_name, aRawStreamName.get());
+ if (r && r != CUBEB_ERROR_NOT_SUPPORTED) {
LOGE("Could not set cubeb stream name.");
}
}
diff --git a/dom/media/AudioStream.h b/dom/media/AudioStream.h
index 11a61b9fe7..708278314d 100644
--- a/dom/media/AudioStream.h
+++ b/dom/media/AudioStream.h
@@ -333,7 +333,7 @@ class AudioStream final {
const AudioConfig::ChannelLayout::ChannelMap mChannelMap;
// The monitor is held to protect all access to member variables below.
- Monitor mMonitor MOZ_UNANNOTATED;
+ Monitor mMonitor;
const uint32_t mOutChannels;
diff --git a/dom/media/ChannelMediaResource.cpp b/dom/media/ChannelMediaResource.cpp
index aefedb37d1..15f048d735 100644
--- a/dom/media/ChannelMediaResource.cpp
+++ b/dom/media/ChannelMediaResource.cpp
@@ -227,7 +227,7 @@ nsresult ChannelMediaResource::OnStartRequest(nsIRequest* aRequest,
// at this stage.
// For now, tell the decoder that the stream is infinite.
if (rangeTotal != -1) {
- contentLength = std::max(contentLength, rangeTotal);
+ length = std::max(contentLength, rangeTotal);
}
}
acceptsRanges = gotRangeHeader;
@@ -240,11 +240,9 @@ nsresult ChannelMediaResource::OnStartRequest(nsIRequest* aRequest,
// to assume seeking doesn't work.
acceptsRanges = false;
}
- }
- if (aRequestOffset == 0 && contentLength >= 0 &&
- (responseStatus == HTTP_OK_CODE ||
- responseStatus == HTTP_PARTIAL_RESPONSE_CODE)) {
- length = contentLength;
+ if (contentLength >= 0) {
+ length = contentLength;
+ }
}
// XXX we probably should examine the Content-Range header in case
// the server gave us a range which is not quite what we asked for
@@ -488,9 +486,9 @@ int64_t ChannelMediaResource::CalculateStreamLength() const {
bool gotRangeHeader = NS_SUCCEEDED(
ParseContentRangeHeader(hc, rangeStart, rangeEnd, rangeTotal));
if (gotRangeHeader && rangeTotal != -1) {
- contentLength = std::max(contentLength, rangeTotal);
+ return std::max(contentLength, rangeTotal);
}
- return contentLength;
+ return -1;
}
nsresult ChannelMediaResource::Open(nsIStreamListener** aStreamListener) {
diff --git a/dom/media/CubebInputStream.cpp b/dom/media/CubebInputStream.cpp
index 38b66315f9..55677d2dc1 100644
--- a/dom/media/CubebInputStream.cpp
+++ b/dom/media/CubebInputStream.cpp
@@ -129,7 +129,7 @@ CubebInputStream::CubebInputStream(
void CubebInputStream::Init() {
// cubeb_stream_register_device_changed_callback is only supported on macOS
- // platform and MockCubebfor now.
+ // platform and MockCubeb for now.
InvokeCubebWithLog(cubeb_stream_register_device_changed_callback,
CubebInputStream::DeviceChangedCallback_s);
}
@@ -138,6 +138,11 @@ int CubebInputStream::Start() { return InvokeCubebWithLog(cubeb_stream_start); }
int CubebInputStream::Stop() { return InvokeCubebWithLog(cubeb_stream_stop); }
+int CubebInputStream::SetProcessingParams(
+ cubeb_input_processing_params aParams) {
+ return InvokeCubebWithLog(cubeb_stream_set_input_processing_params, aParams);
+}
+
int CubebInputStream::Latency(uint32_t* aLatencyFrames) {
return InvokeCubebWithLog(cubeb_stream_get_input_latency, aLatencyFrames);
}
diff --git a/dom/media/CubebInputStream.h b/dom/media/CubebInputStream.h
index a35924a976..107d0f4f6e 100644
--- a/dom/media/CubebInputStream.h
+++ b/dom/media/CubebInputStream.h
@@ -51,6 +51,9 @@ class CubebInputStream final {
// Stop producing audio data.
int Stop();
+ // Apply the given processing params.
+ int SetProcessingParams(cubeb_input_processing_params aParams);
+
// Gets the approximate stream latency in frames.
int Latency(uint32_t* aLatencyFrames);
diff --git a/dom/media/CubebUtils.cpp b/dom/media/CubebUtils.cpp
index dbdab3a56a..3c93e65095 100644
--- a/dom/media/CubebUtils.cpp
+++ b/dom/media/CubebUtils.cpp
@@ -14,6 +14,7 @@
#include "mozilla/Logging.h"
#include "mozilla/Preferences.h"
#include "mozilla/Components.h"
+#include "mozilla/SharedThreadPool.h"
#include "mozilla/Sprintf.h"
#include "mozilla/StaticMutex.h"
#include "mozilla/StaticPtr.h"
@@ -186,6 +187,43 @@ static const uint32_t CUBEB_NORMAL_LATENCY_MS = 100;
static const uint32_t CUBEB_NORMAL_LATENCY_FRAMES = 1024;
namespace CubebUtils {
+nsCString ProcessingParamsToString(cubeb_input_processing_params aParams) {
+ if (aParams == CUBEB_INPUT_PROCESSING_PARAM_NONE) {
+ return "NONE"_ns;
+ }
+ nsCString str;
+ for (auto p : {CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION,
+ CUBEB_INPUT_PROCESSING_PARAM_AUTOMATIC_GAIN_CONTROL,
+ CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION,
+ CUBEB_INPUT_PROCESSING_PARAM_VOICE_ISOLATION}) {
+ if (!(aParams & p)) {
+ continue;
+ }
+ if (!str.IsEmpty()) {
+ str.Append(" | ");
+ }
+ str.Append([&p] {
+ switch (p) {
+ case CUBEB_INPUT_PROCESSING_PARAM_NONE:
+ // Handled above.
+ MOZ_CRASH(
+ "NONE is the absence of a param, thus not for logging here.");
+ case CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION:
+ return "AEC";
+ case CUBEB_INPUT_PROCESSING_PARAM_AUTOMATIC_GAIN_CONTROL:
+ return "AGC";
+ case CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION:
+ return "NS";
+ case CUBEB_INPUT_PROCESSING_PARAM_VOICE_ISOLATION:
+ return "VOICE";
+ }
+ MOZ_ASSERT_UNREACHABLE("Unexpected input processing param");
+ return "<Unknown input processing param>";
+ }());
+ }
+ return str;
+}
+
RefPtr<CubebHandle> GetCubebUnlocked();
void GetPrefAndSetString(const char* aPref, StaticAutoPtr<char>& aStorage) {
@@ -765,6 +803,13 @@ bool SandboxEnabled() {
#endif
}
+already_AddRefed<SharedThreadPool> GetCubebOperationThread() {
+ RefPtr<SharedThreadPool> pool = SharedThreadPool::Get("CubebOperation"_ns, 1);
+ const uint32_t kIdleThreadTimeoutMs = 2000;
+ pool->SetIdleThreadTimeout(PR_MillisecondsToInterval(kIdleThreadTimeoutMs));
+ return pool.forget();
+}
+
uint32_t MaxNumberOfChannels() {
RefPtr<CubebHandle> handle = GetCubeb();
uint32_t maxNumberOfChannels;
diff --git a/dom/media/CubebUtils.h b/dom/media/CubebUtils.h
index c05c8d2449..a59d72bbd6 100644
--- a/dom/media/CubebUtils.h
+++ b/dom/media/CubebUtils.h
@@ -16,10 +16,12 @@
class AudioDeviceInfo;
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(cubeb_stream_prefs)
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(cubeb_input_processing_params)
namespace mozilla {
class CallbackThreadRegistry;
+class SharedThreadPool;
namespace CubebUtils {
@@ -35,6 +37,8 @@ struct ToCubebFormat<AUDIO_FORMAT_S16> {
static const cubeb_sample_format value = CUBEB_SAMPLE_S16NE;
};
+nsCString ProcessingParamsToString(cubeb_input_processing_params aParams);
+
class CubebHandle {
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CubebHandle)
@@ -62,6 +66,11 @@ void ShutdownLibrary();
bool SandboxEnabled();
+// A thread pool containing only one thread to execute the cubeb operations. We
+// should always use this thread to init, destroy, start, or stop cubeb streams,
+// to avoid data racing or deadlock issues across platforms.
+already_AddRefed<SharedThreadPool> GetCubebOperationThread();
+
// Returns the maximum number of channels supported by the audio hardware.
uint32_t MaxNumberOfChannels();
diff --git a/dom/media/DeviceInputTrack.cpp b/dom/media/DeviceInputTrack.cpp
index 5d69f7107a..3bf2e87558 100644
--- a/dom/media/DeviceInputTrack.cpp
+++ b/dom/media/DeviceInputTrack.cpp
@@ -316,6 +316,20 @@ bool DeviceInputTrack::HasVoiceInput() const {
return false;
}
+cubeb_input_processing_params DeviceInputTrack::RequestedProcessingParams()
+ const {
+ AssertOnGraphThreadOrNotRunning();
+ Maybe<cubeb_input_processing_params> params;
+ for (const auto& listener : mListeners) {
+ if (params) {
+ *params &= listener->RequestedInputProcessingParams(mGraph);
+ } else {
+ params = Some(listener->RequestedInputProcessingParams(mGraph));
+ }
+ }
+ return params.valueOr(CUBEB_INPUT_PROCESSING_PARAM_NONE);
+}
+
void DeviceInputTrack::DeviceChanged(MediaTrackGraph* aGraph) const {
AssertOnGraphThreadOrNotRunning();
MOZ_ASSERT(aGraph == mGraph,
@@ -326,6 +340,16 @@ void DeviceInputTrack::DeviceChanged(MediaTrackGraph* aGraph) const {
}
}
+void DeviceInputTrack::NotifySetRequestedProcessingParamsResult(
+ MediaTrackGraph* aGraph, cubeb_input_processing_params aRequestedParams,
+ const Result<cubeb_input_processing_params, int>& aResult) {
+ AssertOnGraphThread();
+ for (const auto& listener : mListeners) {
+ listener->NotifySetRequestedInputProcessingParamsResult(
+ mGraph, aRequestedParams, aResult);
+ }
+}
+
void DeviceInputTrack::ReevaluateInputDevice() {
MOZ_ASSERT(NS_IsMainThread());
QueueControlMessageWithNoShutdown([self = RefPtr{this}, this] {
@@ -491,6 +515,8 @@ void NonNativeInputTrack::ProcessInput(GraphTime aFrom, GraphTime aTo,
// GraphRunner keeps the same thread.
MOZ_ASSERT(!HasGraphThreadChanged());
+ ReevaluateProcessingParams();
+
AudioSegment data = mAudioSource->GetAudioSegment(delta, consumer);
MOZ_ASSERT(data.GetDuration() == delta);
GetData<AudioSegment>()->AppendFrom(&data);
@@ -512,6 +538,8 @@ void NonNativeInputTrack::StartAudio(
mGraphThreadId = std::this_thread::get_id();
#endif
mAudioSource = std::move(aAudioInputSource);
+ mAudioSource->Init();
+ ReevaluateProcessingParams();
mAudioSource->Start();
}
@@ -571,6 +599,35 @@ AudioInputSource::Id NonNativeInputTrack::GenerateSourceId() {
return mSourceIdNumber++;
}
+void NonNativeInputTrack::ReevaluateProcessingParams() {
+ AssertOnGraphThread();
+ MOZ_ASSERT(mAudioSource);
+ auto params = RequestedProcessingParams();
+ if (mRequestedProcessingParams == params) {
+ return;
+ }
+ mRequestedProcessingParams = params;
+ using Promise = AudioInputSource::SetRequestedProcessingParamsPromise;
+ mAudioSource->SetRequestedProcessingParams(params)->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [this, self = RefPtr(this),
+ params](Promise::ResolveOrRejectValue&& aValue) {
+ if (IsDestroyed()) {
+ return;
+ }
+ auto result = ([&]() -> Result<cubeb_input_processing_params, int> {
+ if (aValue.IsResolve()) {
+ return aValue.ResolveValue();
+ }
+ return Err(aValue.RejectValue());
+ })();
+ QueueControlMessageWithNoShutdown(
+ [this, self = RefPtr(this), params, result = std::move(result)] {
+ NotifySetRequestedProcessingParamsResult(Graph(), params, result);
+ });
+ });
+}
+
#ifdef DEBUG
bool NonNativeInputTrack::HasGraphThreadChanged() {
AssertOnGraphThread();
diff --git a/dom/media/DeviceInputTrack.h b/dom/media/DeviceInputTrack.h
index 0a92ded13c..8be53f415d 100644
--- a/dom/media/DeviceInputTrack.h
+++ b/dom/media/DeviceInputTrack.h
@@ -172,11 +172,19 @@ class DeviceInputTrack : public ProcessedMediaTrack {
// Main thread API:
const nsTArray<RefPtr<DeviceInputConsumerTrack>>& GetConsumerTracks() const;
+ // Handle the result of an async operation to set processing params on a cubeb
+ // stream. If the operation failed, signal this to listeners and then disable
+ // processing. If the operation succeeded, directly signal this to listeners.
+ void NotifySetRequestedProcessingParamsResult(
+ MediaTrackGraph* aGraph, cubeb_input_processing_params aRequestedParams,
+ const Result<cubeb_input_processing_params, int>& aResult);
// Graph thread APIs:
// Query audio settings from its users.
uint32_t MaxRequestedInputChannels() const;
bool HasVoiceInput() const;
+ // Query for the aggregate processing params from all users.
+ cubeb_input_processing_params RequestedProcessingParams() const;
// Deliver notification to its users.
void DeviceChanged(MediaTrackGraph* aGraph) const;
@@ -265,6 +273,7 @@ class NonNativeInputTrack final : public DeviceInputTrack {
void NotifyDeviceChanged(AudioInputSource::Id aSourceId);
void NotifyInputStopped(AudioInputSource::Id aSourceId);
AudioInputSource::Id GenerateSourceId();
+ void ReevaluateProcessingParams();
private:
~NonNativeInputTrack() = default;
@@ -272,6 +281,8 @@ class NonNativeInputTrack final : public DeviceInputTrack {
// Graph thread only.
RefPtr<AudioInputSource> mAudioSource;
AudioInputSource::Id mSourceIdNumber;
+ cubeb_input_processing_params mRequestedProcessingParams =
+ CUBEB_INPUT_PROCESSING_PARAM_NONE;
#ifdef DEBUG
// Graph thread only.
diff --git a/dom/media/EncoderTraits.cpp b/dom/media/EncoderTraits.cpp
index ba6d43f826..7abc8a0e14 100644
--- a/dom/media/EncoderTraits.cpp
+++ b/dom/media/EncoderTraits.cpp
@@ -14,4 +14,4 @@ bool Supports(const EncoderConfig& aConfig) {
RefPtr<PEMFactory> pem = new PEMFactory();
return pem->Supports(aConfig);
}
-}
+} // namespace mozilla::EncodeTraits
diff --git a/dom/media/ExternalEngineStateMachine.h b/dom/media/ExternalEngineStateMachine.h
index 83250b0f3c..e020695bcc 100644
--- a/dom/media/ExternalEngineStateMachine.h
+++ b/dom/media/ExternalEngineStateMachine.h
@@ -154,12 +154,12 @@ class ExternalEngineStateMachine final
mSeekJob = SeekJob();
mSeekJob.mTarget = Some(aTarget);
}
- void Resolve(const char* aCallSite) {
+ void Resolve(StaticString aCallSite) {
MOZ_ASSERT(mSeekJob.Exists());
mSeekJob.Resolve(aCallSite);
mSeekJob = SeekJob();
}
- void RejectIfExists(const char* aCallSite) {
+ void RejectIfExists(StaticString aCallSite) {
mSeekJob.RejectIfExists(aCallSite);
}
bool IsSeeking() const { return mSeekRequest.Exists(); }
diff --git a/dom/media/GraphDriver.cpp b/dom/media/GraphDriver.cpp
index 744de30bb5..b37aaa010a 100644
--- a/dom/media/GraphDriver.cpp
+++ b/dom/media/GraphDriver.cpp
@@ -342,6 +342,13 @@ class AudioCallbackDriver::FallbackWrapper : public GraphInterface {
uint32_t aAlreadyBuffered) override {
MOZ_CRASH("Unexpected NotifyInputData from fallback SystemClockDriver");
}
+ void NotifySetRequestedInputProcessingParamsResult(
+ AudioCallbackDriver* aDriver,
+ cubeb_input_processing_params aRequestedParams,
+ Result<cubeb_input_processing_params, int>&& aResult) override {
+ MOZ_CRASH(
+ "Unexpected processing params result from fallback SystemClockDriver");
+ }
void DeviceChanged() override {
MOZ_CRASH("Unexpected DeviceChanged from fallback SystemClockDriver");
}
@@ -448,20 +455,17 @@ NS_IMPL_ISUPPORTS0(AudioCallbackDriver::FallbackWrapper)
/* static */
already_AddRefed<TaskQueue> AudioCallbackDriver::CreateTaskQueue() {
- RefPtr<SharedThreadPool> pool = CUBEB_TASK_THREAD;
- const uint32_t kIdleThreadTimeoutMs = 2000;
- pool->SetIdleThreadTimeout(PR_MillisecondsToInterval(kIdleThreadTimeoutMs));
-
- RefPtr<TaskQueue> queue =
- TaskQueue::Create(pool.forget(), "AudioCallbackDriver cubeb task queue");
- return queue.forget();
+ return TaskQueue::Create(CubebUtils::GetCubebOperationThread(),
+ "AudioCallbackDriver cubeb task queue")
+ .forget();
}
AudioCallbackDriver::AudioCallbackDriver(
GraphInterface* aGraphInterface, GraphDriver* aPreviousDriver,
uint32_t aSampleRate, uint32_t aOutputChannelCount,
uint32_t aInputChannelCount, CubebUtils::AudioDeviceID aOutputDeviceID,
- CubebUtils::AudioDeviceID aInputDeviceID, AudioInputType aAudioInputType)
+ CubebUtils::AudioDeviceID aInputDeviceID, AudioInputType aAudioInputType,
+ cubeb_input_processing_params aRequestedInputProcessingParams)
: GraphDriver(aGraphInterface, aPreviousDriver, aSampleRate),
mOutputChannelCount(aOutputChannelCount),
mInputChannelCount(aInputChannelCount),
@@ -469,6 +473,7 @@ AudioCallbackDriver::AudioCallbackDriver(
mInputDeviceID(aInputDeviceID),
mIterationDurationMS(MEDIA_GRAPH_TARGET_PERIOD_MS),
mCubebOperationThread(CreateTaskQueue()),
+ mRequestedInputProcessingParams(aRequestedInputProcessingParams),
mAudioThreadId(ProfilerThreadId{}),
mAudioThreadIdInCb(std::thread::id()),
mFallback("AudioCallbackDriver::mFallback"),
@@ -485,7 +490,12 @@ AudioCallbackDriver::AudioCallbackDriver(
if (aAudioInputType == AudioInputType::Voice &&
StaticPrefs::
media_getusermedia_microphone_prefer_voice_stream_with_processing_enabled()) {
- LOG(LogLevel::Debug, ("VOICE."));
+ LOG(LogLevel::Debug,
+ ("%p: AudioCallbackDriver %p ctor - using VOICE and requesting input "
+ "processing params %s.",
+ Graph(), this,
+ CubebUtils::ProcessingParamsToString(aRequestedInputProcessingParams)
+ .get()));
mInputDevicePreference = CUBEB_DEVICE_PREF_VOICE;
CubebUtils::SetInCommunication(true);
} else {
@@ -670,6 +680,10 @@ void AudioCallbackDriver::Init(const nsCString& aStreamName) {
PanOutputIfNeeded(inputWanted);
#endif
+ if (inputWanted && InputDevicePreference() == AudioInputType::Voice) {
+ SetInputProcessingParams(mRequestedInputProcessingParams);
+ }
+
cubeb_stream_register_device_changed_callback(
mAudioStream, AudioCallbackDriver::DeviceChangedCallback_s);
@@ -1256,6 +1270,11 @@ TimeDuration AudioCallbackDriver::AudioOutputLatency() {
mSampleRate);
}
+bool AudioCallbackDriver::HasFallback() const {
+ MOZ_ASSERT(InIteration());
+ return mFallbackDriverState != FallbackDriverState::None;
+}
+
bool AudioCallbackDriver::OnFallback() const {
MOZ_ASSERT(InIteration());
return mFallbackDriverState == FallbackDriverState::Running;
@@ -1309,6 +1328,9 @@ void AudioCallbackDriver::FallbackToSystemClockDriver() {
void AudioCallbackDriver::FallbackDriverStopped(GraphTime aIterationEnd,
GraphTime aStateComputedTime,
FallbackDriverState aState) {
+ LOG(LogLevel::Debug,
+ ("%p: AudioCallbackDriver %p Fallback driver has stopped.", Graph(),
+ this));
mIterationEnd = aIterationEnd;
mStateComputedTime = aStateComputedTime;
mNextReInitAttempt = TimeStamp();
@@ -1367,6 +1389,107 @@ void AudioCallbackDriver::MaybeStartAudioStream() {
Start();
}
+cubeb_input_processing_params
+AudioCallbackDriver::RequestedInputProcessingParams() const {
+ MOZ_ASSERT(InIteration());
+ return mRequestedInputProcessingParams;
+}
+
+void AudioCallbackDriver::SetRequestedInputProcessingParams(
+ cubeb_input_processing_params aParams) {
+ MOZ_ASSERT(InIteration());
+ if (mRequestedInputProcessingParams == aParams) {
+ return;
+ }
+ LOG(LogLevel::Info,
+ ("AudioCallbackDriver %p, Input processing params %s requested.", this,
+ CubebUtils::ProcessingParamsToString(aParams).get()));
+ mRequestedInputProcessingParams = aParams;
+ MOZ_ALWAYS_SUCCEEDS(mCubebOperationThread->Dispatch(
+ NS_NewRunnableFunction(__func__, [this, self = RefPtr(this), aParams] {
+ SetInputProcessingParams(aParams);
+ })));
+}
+
+void AudioCallbackDriver::SetInputProcessingParams(
+ cubeb_input_processing_params aParams) {
+ MOZ_ASSERT(OnCubebOperationThread());
+ auto requested = aParams;
+ auto result = ([&]() -> Maybe<Result<cubeb_input_processing_params, int>> {
+ // This function decides how to handle the request.
+ // Returning Nothing() does nothing, because either
+ // 1) there is no update since the previous state, or
+ // 2) handling is deferred to a later time.
+ // Returning Some() result will forward that result to
+ // AudioDataListener::OnInputProcessingParamsResult on the callback
+ // thread.
+ if (!mAudioStream) {
+ // No Init yet.
+ LOG(LogLevel::Debug, ("AudioCallbackDriver %p, has no cubeb stream to "
+ "set processing params on!",
+ this));
+ return Nothing();
+ }
+ if (mAudioStreamState == AudioStreamState::None) {
+ // Driver (and cubeb stream) was stopped.
+ return Nothing();
+ }
+ cubeb_input_processing_params supported;
+ auto handle = CubebUtils::GetCubeb();
+ int r = cubeb_get_supported_input_processing_params(handle->Context(),
+ &supported);
+ if (r != CUBEB_OK) {
+ LOG(LogLevel::Debug,
+ ("AudioCallbackDriver %p, no supported processing params", this));
+ return Some(Err(CUBEB_ERROR_NOT_SUPPORTED));
+ }
+ aParams &= supported;
+ LOG(LogLevel::Debug,
+ ("AudioCallbackDriver %p, requested processing params %s reduced to %s "
+ "by supported params %s",
+ this, CubebUtils::ProcessingParamsToString(requested).get(),
+ CubebUtils::ProcessingParamsToString(aParams).get(),
+ CubebUtils::ProcessingParamsToString(supported).get()));
+ if (aParams == mConfiguredInputProcessingParams) {
+ LOG(LogLevel::Debug,
+ ("AudioCallbackDriver %p, no change in processing params %s. Not "
+ "attempting reconfiguration.",
+ this, CubebUtils::ProcessingParamsToString(aParams).get()));
+ return Some(aParams);
+ }
+ mConfiguredInputProcessingParams = aParams;
+ r = cubeb_stream_set_input_processing_params(mAudioStream, aParams);
+ if (r == CUBEB_OK) {
+ LOG(LogLevel::Info,
+ ("AudioCallbackDriver %p, input processing params set to %s", this,
+ CubebUtils::ProcessingParamsToString(aParams).get()));
+ return Some(aParams);
+ }
+ LOG(LogLevel::Info,
+ ("AudioCallbackDriver %p, failed setting input processing params to "
+ "%s. r=%d",
+ this, CubebUtils::ProcessingParamsToString(aParams).get(), r));
+ return Some(Err(r));
+ })();
+ if (!result) {
+ return;
+ }
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(
+ NS_NewRunnableFunction(__func__, [this, self = RefPtr(this), requested,
+ result = result.extract()]() mutable {
+ LOG(LogLevel::Debug,
+ ("AudioCallbackDriver %p, Notifying of input processing params %s. "
+ "r=%d",
+ this,
+ CubebUtils::ProcessingParamsToString(
+ result.unwrapOr(CUBEB_INPUT_PROCESSING_PARAM_NONE))
+ .get(),
+ result.isErr() ? result.inspectErr() : CUBEB_OK));
+ mGraphInterface->NotifySetRequestedInputProcessingParamsResult(
+ this, requested, std::move(result));
+ })));
+}
+
} // namespace mozilla
// avoid redefined macro in unified build
diff --git a/dom/media/GraphDriver.h b/dom/media/GraphDriver.h
index 9ada03e7e6..4608913c7f 100644
--- a/dom/media/GraphDriver.h
+++ b/dom/media/GraphDriver.h
@@ -30,12 +30,6 @@ class nsAutoRefTraits<cubeb_stream> : public nsPointerRefTraits<cubeb_stream> {
};
namespace mozilla {
-
-// A thread pool containing only one thread to execute the cubeb operations. We
-// should always use this thread to init, destroy, start, or stop cubeb streams,
-// to avoid data racing or deadlock issues across platforms.
-#define CUBEB_TASK_THREAD SharedThreadPool::Get("CubebOperation"_ns, 1)
-
/**
* Assume we can run an iteration of the MediaTrackGraph loop in this much time
* or less.
@@ -185,6 +179,12 @@ struct GraphInterface : public nsISupports {
virtual void NotifyInputData(const AudioDataValue* aBuffer, size_t aFrames,
TrackRate aRate, uint32_t aChannels,
uint32_t aAlreadyBuffered) = 0;
+ /* Called on the main thread after an AudioCallbackDriver has attempted an
+ * operation to set aRequestedParams on the cubeb stream. */
+ virtual void NotifySetRequestedInputProcessingParamsResult(
+ AudioCallbackDriver* aDriver,
+ cubeb_input_processing_params aRequestedParams,
+ Result<cubeb_input_processing_params, int>&& aResult) = 0;
/* Called every time there are changes to input/output audio devices like
* plug/unplug etc. This can be called on any thread, and posts a message to
* the main thread so that it can post a message to the graph thread. */
@@ -553,12 +553,12 @@ class AudioCallbackDriver : public GraphDriver, public MixerCallbackReceiver {
AudioCallbackDriver, mCubebOperationThread, override);
/** If aInputChannelCount is zero, then this driver is output-only. */
- AudioCallbackDriver(GraphInterface* aGraphInterface,
- GraphDriver* aPreviousDriver, uint32_t aSampleRate,
- uint32_t aOutputChannelCount, uint32_t aInputChannelCount,
- CubebUtils::AudioDeviceID aOutputDeviceID,
- CubebUtils::AudioDeviceID aInputDeviceID,
- AudioInputType aAudioInputType);
+ AudioCallbackDriver(
+ GraphInterface* aGraphInterface, GraphDriver* aPreviousDriver,
+ uint32_t aSampleRate, uint32_t aOutputChannelCount,
+ uint32_t aInputChannelCount, CubebUtils::AudioDeviceID aOutputDeviceID,
+ CubebUtils::AudioDeviceID aInputDeviceID, AudioInputType aAudioInputType,
+ cubeb_input_processing_params aRequestedInputProcessingParams);
void Start() override;
MOZ_CAN_RUN_SCRIPT void Shutdown() override;
@@ -610,11 +610,16 @@ class AudioCallbackDriver : public GraphDriver, public MixerCallbackReceiver {
return AudioInputType::Unknown;
}
+ /* Get the input processing params requested from this driver, so that an
+ * external caller can decide whether it is necessary to call the setter,
+ * since it may allocate or dispatch. */
+ cubeb_input_processing_params RequestedInputProcessingParams() const;
+
+ /* Set the input processing params requested from this driver. */
+ void SetRequestedInputProcessingParams(cubeb_input_processing_params aParams);
+
std::thread::id ThreadId() const { return mAudioThreadIdInCb.load(); }
- /* Called when the thread servicing the callback has changed. This can be
- * fairly expensive */
- void OnThreadIdChanged();
/* Called at the beginning of the audio callback to check if the thread id has
* changed. */
bool CheckThreadIdChanged();
@@ -637,6 +642,9 @@ class AudioCallbackDriver : public GraphDriver, public MixerCallbackReceiver {
// Returns the output latency for the current audio output stream.
TimeDuration AudioOutputLatency();
+ /* Returns true if this driver has a fallback driver and handover to the audio
+ * callback has not been completed. */
+ bool HasFallback() const;
/* Returns true if this driver is currently driven by the fallback driver. */
bool OnFallback() const;
@@ -655,6 +663,9 @@ class AudioCallbackDriver : public GraphDriver, public MixerCallbackReceiver {
void Init(const nsCString& aStreamName);
void SetCubebStreamName(const nsCString& aStreamName);
void Stop();
+ /* After the requested input processing params has changed, this applies them
+ * on the cubeb stream. */
+ void SetInputProcessingParams(cubeb_input_processing_params aParams);
/* Calls FallbackToSystemClockDriver() if in FallbackDriverState::None.
* Returns Ok(true) if the fallback driver was started, or the old
* FallbackDriverState in an Err otherwise. */
@@ -724,6 +735,13 @@ class AudioCallbackDriver : public GraphDriver, public MixerCallbackReceiver {
* must run serially for access to mAudioStream. */
const RefPtr<TaskQueue> mCubebOperationThread;
cubeb_device_pref mInputDevicePreference;
+ /* Params that have been attempted to set on mAudioStream, after filtering by
+ * supported processing params. Cubeb operation thread only. */
+ cubeb_input_processing_params mConfiguredInputProcessingParams =
+ CUBEB_INPUT_PROCESSING_PARAM_NONE;
+ /* The input processing params requested from this audio driver. Once started,
+ * audio callback thread only. */
+ cubeb_input_processing_params mRequestedInputProcessingParams;
/* Contains the id of the audio thread, from profiler_current_thread_id. */
std::atomic<ProfilerThreadId> mAudioThreadId;
/* This allows implementing AutoInCallback. This is equal to the current
diff --git a/dom/media/ImageToI420.cpp b/dom/media/ImageConversion.cpp
index 0f7976cb63..ea8d258279 100644
--- a/dom/media/ImageToI420.cpp
+++ b/dom/media/ImageConversion.cpp
@@ -3,10 +3,11 @@
* 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/. */
-#include "ImageToI420.h"
+#include "ImageConversion.h"
#include "ImageContainer.h"
#include "libyuv/convert.h"
+#include "libyuv/convert_from_argb.h"
#include "mozilla/dom/ImageBitmapBinding.h"
#include "mozilla/dom/ImageUtils.h"
#include "mozilla/gfx/Point.h"
@@ -151,4 +152,56 @@ nsresult ConvertToI420(Image* aImage, uint8_t* aDestY, int aDestStrideY,
}
}
+nsresult ConvertToNV12(layers::Image* aImage, uint8_t* aDestY, int aDestStrideY,
+ uint8_t* aDestUV, int aDestStrideUV) {
+ if (!aImage->IsValid()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (const PlanarYCbCrData* data = GetPlanarYCbCrData(aImage)) {
+ const ImageUtils imageUtils(aImage);
+ Maybe<dom::ImageBitmapFormat> format = imageUtils.GetFormat();
+ if (format.isNothing()) {
+ MOZ_ASSERT_UNREACHABLE("YUV format conversion not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ if (format.value() != ImageBitmapFormat::YUV420P) {
+ NS_WARNING("ConvertToNV12: Convert YUV data in I420 only");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ return MapRv(libyuv::I420ToNV12(
+ data->mYChannel, data->mYStride, data->mCbChannel, data->mCbCrStride,
+ data->mCrChannel, data->mCbCrStride, aDestY, aDestStrideY, aDestUV,
+ aDestStrideUV, aImage->GetSize().width, aImage->GetSize().height));
+ }
+
+ RefPtr<SourceSurface> surf = GetSourceSurface(aImage);
+ if (!surf) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<DataSourceSurface> data = surf->GetDataSurface();
+ if (!data) {
+ return NS_ERROR_FAILURE;
+ }
+
+ DataSourceSurface::ScopedMap map(data, DataSourceSurface::READ);
+ if (!map.IsMapped()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (surf->GetFormat() != SurfaceFormat::B8G8R8A8 &&
+ surf->GetFormat() != SurfaceFormat::B8G8R8X8) {
+ NS_WARNING("ConvertToNV12: Convert SurfaceFormat in BGR* only");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ return MapRv(
+ libyuv::ARGBToNV12(static_cast<uint8_t*>(map.GetData()), map.GetStride(),
+ aDestY, aDestStrideY, aDestUV, aDestStrideUV,
+ aImage->GetSize().width, aImage->GetSize().height));
+}
+
} // namespace mozilla
diff --git a/dom/media/ImageToI420.h b/dom/media/ImageConversion.h
index 24a66ebc9f..8f1396f9e9 100644
--- a/dom/media/ImageToI420.h
+++ b/dom/media/ImageConversion.h
@@ -21,6 +21,12 @@ nsresult ConvertToI420(layers::Image* aImage, uint8_t* aDestY, int aDestStrideY,
uint8_t* aDestU, int aDestStrideU, uint8_t* aDestV,
int aDestStrideV);
+/**
+ * Converts aImage to an NV12 image and writes it to the given buffers.
+ */
+nsresult ConvertToNV12(layers::Image* aImage, uint8_t* aDestY, int aDestStrideY,
+ uint8_t* aDestUV, int aDestStrideUV);
+
} // namespace mozilla
#endif /* ImageToI420Converter_h */
diff --git a/dom/media/MediaFormatReader.cpp b/dom/media/MediaFormatReader.cpp
index 7eb8e4e5e2..0d7daaa3d8 100644
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -1770,6 +1770,13 @@ void MediaFormatReader::NotifyNewOutput(
if (aTrack == TrackInfo::kAudioTrack) {
decoder.mNumOfConsecutiveUtilityCrashes = 0;
}
+ decoder.mDecodePerfRecorder->Record(
+ sample->mTime.ToMicroseconds(),
+ [startTime = sample->mTime.ToMicroseconds(),
+ endTime =
+ sample->GetEndTime().ToMicroseconds()](PlaybackStage& aStage) {
+ aStage.SetStartTimeAndEndTime(startTime, endTime);
+ });
}
}
LOG("Done processing new %s samples", TrackTypeToStr(aTrack));
@@ -1962,6 +1969,38 @@ void MediaFormatReader::RequestDemuxSamples(TrackType aTrack) {
}
}
+void MediaFormatReader::DecoderData::StartRecordDecodingPerf(
+ const TrackType aTrack, const MediaRawData* aSample) {
+ if (!mDecodePerfRecorder) {
+ mDecodePerfRecorder.reset(new PerformanceRecorderMulti<PlaybackStage>());
+ }
+ const int32_t height = aTrack == TrackInfo::kVideoTrack
+ ? GetCurrentInfo()->GetAsVideoInfo()->mImage.height
+ : 0;
+ MediaInfoFlag flag = MediaInfoFlag::None;
+ flag |=
+ aSample->mKeyframe ? MediaInfoFlag::KeyFrame : MediaInfoFlag::NonKeyFrame;
+ if (aTrack == TrackInfo::kVideoTrack) {
+ flag |= mIsHardwareAccelerated ? MediaInfoFlag::HardwareDecoding
+ : MediaInfoFlag::SoftwareDecoding;
+ const nsCString& mimeType = GetCurrentInfo()->mMimeType;
+ if (MP4Decoder::IsH264(mimeType)) {
+ flag |= MediaInfoFlag::VIDEO_H264;
+ } else if (VPXDecoder::IsVPX(mimeType, VPXDecoder::VP8)) {
+ flag |= MediaInfoFlag::VIDEO_VP8;
+ } else if (VPXDecoder::IsVPX(mimeType, VPXDecoder::VP9)) {
+ flag |= MediaInfoFlag::VIDEO_VP9;
+ }
+#ifdef MOZ_AV1
+ else if (AOMDecoder::IsAV1(mimeType)) {
+ flag |= MediaInfoFlag::VIDEO_AV1;
+ }
+#endif
+ }
+ mDecodePerfRecorder->Start(aSample->mTime.ToMicroseconds(),
+ MediaStage::RequestDecode, height, flag);
+}
+
void MediaFormatReader::DecodeDemuxedSamples(TrackType aTrack,
MediaRawData* aSample) {
MOZ_ASSERT(OnTaskQueue());
@@ -1980,41 +2019,15 @@ void MediaFormatReader::DecodeDemuxedSamples(TrackType aTrack,
aSample->mDuration.ToMicroseconds(), aSample->mKeyframe ? " kf" : "",
aSample->mEOS ? " eos" : "");
- const int32_t height =
- aTrack == TrackInfo::kVideoTrack
- ? decoder.GetCurrentInfo()->GetAsVideoInfo()->mImage.height
- : 0;
- MediaInfoFlag flag = MediaInfoFlag::None;
- flag |=
- aSample->mKeyframe ? MediaInfoFlag::KeyFrame : MediaInfoFlag::NonKeyFrame;
- if (aTrack == TrackInfo::kVideoTrack) {
- flag |= VideoIsHardwareAccelerated() ? MediaInfoFlag::HardwareDecoding
- : MediaInfoFlag::SoftwareDecoding;
- const nsCString& mimeType = decoder.GetCurrentInfo()->mMimeType;
- if (MP4Decoder::IsH264(mimeType)) {
- flag |= MediaInfoFlag::VIDEO_H264;
- } else if (VPXDecoder::IsVPX(mimeType, VPXDecoder::VP8)) {
- flag |= MediaInfoFlag::VIDEO_VP8;
- } else if (VPXDecoder::IsVPX(mimeType, VPXDecoder::VP9)) {
- flag |= MediaInfoFlag::VIDEO_VP9;
- }
-#ifdef MOZ_AV1
- else if (AOMDecoder::IsAV1(mimeType)) {
- flag |= MediaInfoFlag::VIDEO_AV1;
- }
-#endif
- }
- PerformanceRecorder<PlaybackStage> perfRecorder(MediaStage::RequestDecode,
- height, flag);
+ decoder.StartRecordDecodingPerf(aTrack, aSample);
if (mMediaEngineId && aSample->mCrypto.IsEncrypted()) {
aSample->mShouldCopyCryptoToRemoteRawData = true;
}
decoder.mDecoder->Decode(aSample)
->Then(
mTaskQueue, __func__,
- [self, aTrack, &decoder, perfRecorder(std::move(perfRecorder))](
- MediaDataDecoder::DecodedData&& aResults) mutable {
- perfRecorder.Record();
+ [self, aTrack,
+ &decoder](MediaDataDecoder::DecodedData&& aResults) mutable {
decoder.mDecodeRequest.Complete();
self->NotifyNewOutput(aTrack, std::move(aResults));
},
diff --git a/dom/media/MediaFormatReader.h b/dom/media/MediaFormatReader.h
index 5c4e04172d..b29d25db83 100644
--- a/dom/media/MediaFormatReader.h
+++ b/dom/media/MediaFormatReader.h
@@ -430,6 +430,7 @@ class MediaFormatReader final
Maybe<TimeStamp> mWaitingForDataStartTime;
bool mWaitingForKey;
bool mReceivedNewData;
+ UniquePtr<PerformanceRecorderMulti<PlaybackStage>> mDecodePerfRecorder;
// Pending seek.
MozPromiseRequestHolder<MediaTrackDemuxer::SeekPromise> mSeekRequest;
@@ -474,6 +475,9 @@ class MediaFormatReader final
mDrainState = DrainState::DrainRequested;
}
+ void StartRecordDecodingPerf(const TrackType aTrack,
+ const MediaRawData* aSample);
+
// Track decoding error and fail when we hit the limit.
uint32_t mNumOfConsecutiveDecodingError;
uint32_t mMaxConsecutiveDecodingError;
@@ -552,7 +556,7 @@ class MediaFormatReader final
// Rejecting the promise will stop the reader from decoding ahead.
virtual bool HasPromise() const = 0;
virtual void RejectPromise(const MediaResult& aError,
- const char* aMethodName) = 0;
+ StaticString aMethodName) = 0;
// Clear track demuxer related data.
void ResetDemuxer() {
@@ -688,20 +692,20 @@ class MediaFormatReader final
bool HasPromise() const override { return mHasPromise; }
- RefPtr<DataPromise<Type>> EnsurePromise(const char* aMethodName) {
+ RefPtr<DataPromise<Type>> EnsurePromise(StaticString aMethodName) {
MOZ_ASSERT(mOwner->OnTaskQueue());
mHasPromise = true;
return mPromise.Ensure(aMethodName);
}
- void ResolvePromise(Type* aData, const char* aMethodName) {
+ void ResolvePromise(Type* aData, StaticString aMethodName) {
MOZ_ASSERT(mOwner->OnTaskQueue());
mPromise.Resolve(aData, aMethodName);
mHasPromise = false;
}
void RejectPromise(const MediaResult& aError,
- const char* aMethodName) override {
+ StaticString aMethodName) override {
MOZ_ASSERT(mOwner->OnTaskQueue());
mPromise.Reject(aError, aMethodName);
mHasPromise = false;
diff --git a/dom/media/MediaInfo.h b/dom/media/MediaInfo.h
index 7ab5df4e0a..00f322bac7 100644
--- a/dom/media/MediaInfo.h
+++ b/dom/media/MediaInfo.h
@@ -349,7 +349,32 @@ class VideoInfo : public TrackInfo {
mExtraData(new MediaByteBuffer),
mRotation(VideoRotation::kDegree_0) {}
- VideoInfo(const VideoInfo& aOther) = default;
+ VideoInfo(const VideoInfo& aOther) : TrackInfo(aOther) {
+ if (aOther.mCodecSpecificConfig) {
+ mCodecSpecificConfig = new MediaByteBuffer();
+ mCodecSpecificConfig->AppendElements(
+ reinterpret_cast<uint8_t*>(aOther.mCodecSpecificConfig->Elements()),
+ aOther.mCodecSpecificConfig->Length());
+ }
+ if (aOther.mExtraData) {
+ mExtraData = new MediaByteBuffer();
+ mExtraData->AppendElements(
+ reinterpret_cast<uint8_t*>(aOther.mExtraData->Elements()),
+ aOther.mExtraData->Length());
+ }
+ mDisplay = aOther.mDisplay;
+ mStereoMode = aOther.mStereoMode;
+ mImage = aOther.mImage;
+ mRotation = aOther.mRotation;
+ mColorDepth = aOther.mColorDepth;
+ mColorSpace = aOther.mColorSpace;
+ mColorPrimaries = aOther.mColorPrimaries;
+ mTransferFunction = aOther.mTransferFunction;
+ mColorRange = aOther.mColorRange;
+ mImageRect = aOther.mImageRect;
+ mAlphaPresent = aOther.mAlphaPresent;
+ mFrameRate = aOther.mFrameRate;
+ };
bool operator==(const VideoInfo& rhs) const;
diff --git a/dom/media/MediaManager.cpp b/dom/media/MediaManager.cpp
index fb4384c826..0eb1a0977d 100644
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -10,13 +10,16 @@
#include "AudioDeviceInfo.h"
#include "AudioStreamTrack.h"
#include "CubebDeviceEnumerator.h"
+#include "CubebInputStream.h"
#include "MediaTimer.h"
#include "MediaTrackConstraints.h"
#include "MediaTrackGraph.h"
#include "MediaTrackListener.h"
#include "VideoStreamTrack.h"
+#include "Tracing.h"
#include "VideoUtils.h"
#include "mozilla/Base64.h"
+#include "mozilla/EventTargetCapability.h"
#include "mozilla/MozPromise.h"
#include "mozilla/NullPrincipal.h"
#include "mozilla/PeerIdentity.h"
@@ -291,6 +294,72 @@ void MediaManager::CallOnSuccess(GetUserMediaSuccessCallback& aCallback,
aCallback.Call(aStream);
}
+enum class PersistentPermissionState : uint32_t {
+ Unknown = nsIPermissionManager::UNKNOWN_ACTION,
+ Allow = nsIPermissionManager::ALLOW_ACTION,
+ Deny = nsIPermissionManager::DENY_ACTION,
+ Prompt = nsIPermissionManager::PROMPT_ACTION,
+};
+
+static PersistentPermissionState CheckPermission(
+ PersistentPermissionState aPermission) {
+ switch (aPermission) {
+ case PersistentPermissionState::Unknown:
+ case PersistentPermissionState::Allow:
+ case PersistentPermissionState::Deny:
+ case PersistentPermissionState::Prompt:
+ return aPermission;
+ }
+ MOZ_CRASH("Unexpected permission value");
+}
+
+struct WindowPersistentPermissionState {
+ PersistentPermissionState mCameraPermission;
+ PersistentPermissionState mMicrophonePermission;
+};
+
+static Result<WindowPersistentPermissionState, nsresult>
+GetPersistentPermissions(uint64_t aWindowId) {
+ auto* window = nsGlobalWindowInner::GetInnerWindowWithId(aWindowId);
+ if (NS_WARN_IF(!window) || NS_WARN_IF(!window->GetPrincipal())) {
+ return Err(NS_ERROR_INVALID_ARG);
+ }
+
+ Document* doc = window->GetExtantDoc();
+ if (NS_WARN_IF(!doc)) {
+ return Err(NS_ERROR_INVALID_ARG);
+ }
+
+ nsIPrincipal* principal = window->GetPrincipal();
+ if (NS_WARN_IF(!principal)) {
+ return Err(NS_ERROR_INVALID_ARG);
+ }
+
+ nsresult rv;
+ RefPtr<PermissionDelegateHandler> permDelegate =
+ doc->GetPermissionDelegateHandler();
+ if (NS_WARN_IF(!permDelegate)) {
+ return Err(NS_ERROR_INVALID_ARG);
+ }
+
+ uint32_t audio = nsIPermissionManager::UNKNOWN_ACTION;
+ uint32_t video = nsIPermissionManager::UNKNOWN_ACTION;
+ {
+ rv = permDelegate->GetPermission("microphone"_ns, &audio, true);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return Err(rv);
+ }
+ rv = permDelegate->GetPermission("camera"_ns, &video, true);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return Err(rv);
+ }
+ }
+
+ return WindowPersistentPermissionState{
+ CheckPermission(static_cast<PersistentPermissionState>(video)),
+ CheckPermission(static_cast<PersistentPermissionState>(audio))};
+}
+
/**
* DeviceListener has threadsafe refcounting for use across the main, media and
* MTG threads. But it has a non-threadsafe SupportsWeakPtr for WeakPtr usage
@@ -1458,9 +1527,63 @@ class GetUserMediaStreamTask final : public GetUserMediaTask {
const MediaStreamConstraints& GetConstraints() { return mConstraints; }
+ void PrimeVoiceProcessing() {
+ mPrimingStream = MakeAndAddRef<PrimingCubebVoiceInputStream>();
+ mPrimingStream->Init();
+ }
+
private:
void PrepareDOMStream();
+ class PrimingCubebVoiceInputStream {
+ class Listener final : public CubebInputStream::Listener {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Listener, override);
+
+ private:
+ ~Listener() = default;
+
+ long DataCallback(const void*, long) override {
+ MOZ_CRASH("Unexpected data callback");
+ }
+ void StateCallback(cubeb_state) override {}
+ void DeviceChangedCallback() override {}
+ };
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_EVENT_TARGET(
+ PrimingCubebVoiceInputStream, mCubebThread.GetEventTarget())
+
+ public:
+ void Init() {
+ mCubebThread.GetEventTarget()->Dispatch(
+ NS_NewRunnableFunction(__func__, [this, self = RefPtr(this)] {
+ mCubebThread.AssertOnCurrentThread();
+ LOG("Priming voice processing with stream %p", this);
+ TRACE("PrimingCubebVoiceInputStream::Init");
+ const cubeb_devid default_device = nullptr;
+ const uint32_t mono = 1;
+ const uint32_t rate = CubebUtils::PreferredSampleRate(false);
+ const bool isVoice = true;
+ mCubebStream =
+ CubebInputStream::Create(default_device, mono, rate, isVoice,
+ MakeRefPtr<Listener>().get());
+ }));
+ }
+
+ private:
+ ~PrimingCubebVoiceInputStream() {
+ mCubebThread.AssertOnCurrentThread();
+ LOG("Releasing primed voice processing stream %p", this);
+ mCubebStream = nullptr;
+ }
+
+ const EventTargetCapability<nsISerialEventTarget> mCubebThread =
+ EventTargetCapability<nsISerialEventTarget>(
+ TaskQueue::Create(CubebUtils::GetCubebOperationThread(),
+ "PrimingCubebInputStream::mCubebThread")
+ .get());
+ UniquePtr<CubebInputStream> mCubebStream MOZ_GUARDED_BY(mCubebThread);
+ };
+
// Constraints derived from those passed to getUserMedia() but adjusted for
// preferences, defaults, and security
const MediaStreamConstraints mConstraints;
@@ -1473,6 +1596,7 @@ class GetUserMediaStreamTask final : public GetUserMediaTask {
// MediaDevices are set when selected and Allowed() by the UI.
RefPtr<LocalMediaDevice> mAudioDevice;
RefPtr<LocalMediaDevice> mVideoDevice;
+ RefPtr<PrimingCubebVoiceInputStream> mPrimingStream;
// Tracking id unique for a video frame source. Set when the corresponding
// device has been allocated.
Maybe<TrackingId> mVideoTrackingId;
@@ -2220,6 +2344,7 @@ MediaManager::MediaManager(already_AddRefed<TaskQueue> aMediaThread)
mPrefs.mWidth = 0; // adaptive default
mPrefs.mHeight = 0; // adaptive default
mPrefs.mFPS = MediaEnginePrefs::DEFAULT_VIDEO_FPS;
+ mPrefs.mUsePlatformProcessing = false;
mPrefs.mAecOn = false;
mPrefs.mUseAecMobile = false;
mPrefs.mAgcOn = false;
@@ -2272,14 +2397,14 @@ static void ForeachObservedPref(const Function& aFunction) {
aFunction("media.video_loopback_dev"_ns);
aFunction("media.getusermedia.fake-camera-name"_ns);
#ifdef MOZ_WEBRTC
- aFunction("media.getusermedia.aec_enabled"_ns);
- aFunction("media.getusermedia.aec"_ns);
- aFunction("media.getusermedia.agc_enabled"_ns);
- aFunction("media.getusermedia.agc"_ns);
- aFunction("media.getusermedia.hpf_enabled"_ns);
- aFunction("media.getusermedia.noise_enabled"_ns);
- aFunction("media.getusermedia.noise"_ns);
- aFunction("media.getusermedia.channels"_ns);
+ aFunction("media.getusermedia.audio.processing.aec.enabled"_ns);
+ aFunction("media.getusermedia.audio.processing.aec"_ns);
+ aFunction("media.getusermedia.audio.processing.agc.enabled"_ns);
+ aFunction("media.getusermedia.audio.processing.agc"_ns);
+ aFunction("media.getusermedia.audio.processing.hpf.enabled"_ns);
+ aFunction("media.getusermedia.audio.processing.noise.enabled"_ns);
+ aFunction("media.getusermedia.audio.processing.noise"_ns);
+ aFunction("media.getusermedia.audio.max_channels"_ns);
aFunction("media.navigator.streams.fake"_ns);
#endif
}
@@ -2392,7 +2517,7 @@ void MediaManager::Dispatch(already_AddRefed<Runnable> task) {
template <typename MozPromiseType, typename FunctionType>
/* static */
-RefPtr<MozPromiseType> MediaManager::Dispatch(const char* aName,
+RefPtr<MozPromiseType> MediaManager::Dispatch(StaticString aName,
FunctionType&& aFunction) {
MozPromiseHolder<MozPromiseType> holder;
RefPtr<MozPromiseType> promise = holder.Ensure(aName);
@@ -2851,7 +2976,7 @@ RefPtr<MediaManager::StreamPromise> MediaManager::GetUserMedia(
case MediaSourceEnum::AudioCapture:
// Only enable AudioCapture if the pref is enabled. If it's not, we can
// deny right away.
- if (!Preferences::GetBool("media.getusermedia.audiocapture.enabled")) {
+ if (!Preferences::GetBool("media.getusermedia.audio.capture.enabled")) {
return StreamPromise::CreateAndReject(
MakeRefPtr<MediaMgrError>(MediaMgrError::Name::NotAllowedError),
__func__);
@@ -3044,6 +3169,36 @@ RefPtr<MediaManager::StreamPromise> MediaManager::GetUserMedia(
std::move(audioListener), std::move(videoListener), prefs,
principalInfo, aCallerType, focusSource);
+ // It is time to ask for user permission, prime voice processing
+ // now. Use a local lambda to enable a guard pattern.
+ [&] {
+ if (!StaticPrefs::
+ media_getusermedia_microphone_voice_stream_priming_enabled() ||
+ !StaticPrefs::
+ media_getusermedia_microphone_prefer_voice_stream_with_processing_enabled()) {
+ return;
+ }
+
+ if (const auto fc = FlattenedConstraints(
+ NormalizedConstraints(GetInvariant(c.mAudio)));
+ !fc.mEchoCancellation.Get(prefs.mAecOn) &&
+ !fc.mAutoGainControl.Get(prefs.mAgcOn && prefs.mAecOn) &&
+ !fc.mNoiseSuppression.Get(prefs.mNoiseOn && prefs.mAecOn)) {
+ return;
+ }
+
+ if (GetPersistentPermissions(windowID)
+ .map([](auto&& aState) {
+ return aState.mMicrophonePermission ==
+ PersistentPermissionState::Deny;
+ })
+ .unwrapOr(true)) {
+ return;
+ }
+
+ task->PrimeVoiceProcessing();
+ }();
+
size_t taskCount =
self->AddTaskAndGetCount(windowID, callID, std::move(task));
@@ -3474,14 +3629,19 @@ void MediaManager::GetPrefs(nsIPrefBranch* aBranch, const char* aData) {
GetPref(aBranch, "media.navigator.audio.fake_frequency", aData,
&mPrefs.mFreq);
#ifdef MOZ_WEBRTC
- GetPrefBool(aBranch, "media.getusermedia.aec_enabled", aData, &mPrefs.mAecOn);
- GetPrefBool(aBranch, "media.getusermedia.agc_enabled", aData, &mPrefs.mAgcOn);
- GetPrefBool(aBranch, "media.getusermedia.hpf_enabled", aData, &mPrefs.mHPFOn);
- GetPrefBool(aBranch, "media.getusermedia.noise_enabled", aData,
- &mPrefs.mNoiseOn);
- GetPrefBool(aBranch, "media.getusermedia.transient_enabled", aData,
- &mPrefs.mTransientOn);
- GetPrefBool(aBranch, "media.getusermedia.agc2_forced", aData,
+ GetPrefBool(aBranch, "media.getusermedia.audio.processing.platform.enabled",
+ aData, &mPrefs.mUsePlatformProcessing);
+ GetPrefBool(aBranch, "media.getusermedia.audio.processing.aec.enabled", aData,
+ &mPrefs.mAecOn);
+ GetPrefBool(aBranch, "media.getusermedia.audio.processing.agc.enabled", aData,
+ &mPrefs.mAgcOn);
+ GetPrefBool(aBranch, "media.getusermedia.audio.processing.hpf.enabled", aData,
+ &mPrefs.mHPFOn);
+ GetPrefBool(aBranch, "media.getusermedia.audio.processing.noise.enabled",
+ aData, &mPrefs.mNoiseOn);
+ GetPrefBool(aBranch, "media.getusermedia.audio.processing.transient.enabled",
+ aData, &mPrefs.mTransientOn);
+ GetPrefBool(aBranch, "media.getusermedia.audio.processing.agc2.forced", aData,
&mPrefs.mAgc2Forced);
// Use 0 or 1 to force to false or true
// EchoCanceller3Config::echo_removal_control.has_clock_drift.
@@ -3489,14 +3649,19 @@ void MediaManager::GetPrefs(nsIPrefBranch* aBranch, const char* aData) {
// deemed appropriate.
GetPref(aBranch, "media.getusermedia.audio.processing.aec.expect_drift",
aData, &mPrefs.mExpectDrift);
- GetPref(aBranch, "media.getusermedia.agc", aData, &mPrefs.mAgc);
- GetPref(aBranch, "media.getusermedia.noise", aData, &mPrefs.mNoise);
- GetPref(aBranch, "media.getusermedia.channels", aData, &mPrefs.mChannels);
+ GetPref(aBranch, "media.getusermedia.audio.processing.agc", aData,
+ &mPrefs.mAgc);
+ GetPref(aBranch, "media.getusermedia.audio.processing.noise", aData,
+ &mPrefs.mNoise);
+ GetPref(aBranch, "media.getusermedia.audio.max_channels", aData,
+ &mPrefs.mChannels);
#endif
- LOG("%s: default prefs: %dx%d @%dfps, %dHz test tones, aec: %s, "
- "agc: %s, hpf: %s, noise: %s, drift: %s, agc level: %d, agc version: %s, "
- "noise level: %d, transient: %s, channels %d",
+ LOG("%s: default prefs: %dx%d @%dfps, %dHz test tones, platform processing: "
+ "%s, aec: %s, agc: %s, hpf: %s, noise: %s, drift: %s, agc level: %d, agc "
+ "version: "
+ "%s, noise level: %d, transient: %s, channels %d",
__FUNCTION__, mPrefs.mWidth, mPrefs.mHeight, mPrefs.mFPS, mPrefs.mFreq,
+ mPrefs.mUsePlatformProcessing ? "on" : "off",
mPrefs.mAecOn ? "on" : "off", mPrefs.mAgcOn ? "on" : "off",
mPrefs.mHPFOn ? "on" : "off", mPrefs.mNoiseOn ? "on" : "off",
mPrefs.mExpectDrift < 0 ? "auto"
@@ -3948,43 +4113,13 @@ bool MediaManager::IsActivelyCapturingOrHasAPermission(uint64_t aWindowId) {
// Or are persistent permissions (audio or video) granted?
- auto* window = nsGlobalWindowInner::GetInnerWindowWithId(aWindowId);
- if (NS_WARN_IF(!window) || NS_WARN_IF(!window->GetPrincipal())) {
- return false;
- }
-
- Document* doc = window->GetExtantDoc();
- if (NS_WARN_IF(!doc)) {
- return false;
- }
-
- nsIPrincipal* principal = window->GetPrincipal();
- if (NS_WARN_IF(!principal)) {
- return false;
- }
-
- // Check if this site has persistent permissions.
- nsresult rv;
- RefPtr<PermissionDelegateHandler> permDelegate =
- doc->GetPermissionDelegateHandler();
- if (NS_WARN_IF(!permDelegate)) {
- return false;
- }
-
- uint32_t audio = nsIPermissionManager::UNKNOWN_ACTION;
- uint32_t video = nsIPermissionManager::UNKNOWN_ACTION;
- {
- rv = permDelegate->GetPermission("microphone"_ns, &audio, true);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- return false;
- }
- rv = permDelegate->GetPermission("camera"_ns, &video, true);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- return false;
- }
- }
- return audio == nsIPermissionManager::ALLOW_ACTION ||
- video == nsIPermissionManager::ALLOW_ACTION;
+ return GetPersistentPermissions(aWindowId)
+ .map([](auto&& aState) {
+ return aState.mMicrophonePermission ==
+ PersistentPermissionState::Allow ||
+ aState.mCameraPermission == PersistentPermissionState::Allow;
+ })
+ .unwrapOr(false);
}
DeviceListener::DeviceListener()
diff --git a/dom/media/MediaManager.h b/dom/media/MediaManager.h
index 738ccd795d..81f23bfe05 100644
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -220,7 +220,7 @@ class MediaManager final : public nsIMediaManagerService,
* manager thread.
*/
template <typename MozPromiseType, typename FunctionType>
- static RefPtr<MozPromiseType> Dispatch(const char* aName,
+ static RefPtr<MozPromiseType> Dispatch(StaticString aName,
FunctionType&& aFunction);
#ifdef DEBUG
diff --git a/dom/media/MediaResult.h b/dom/media/MediaResult.h
index 58e3bf7671..9b4031e95c 100644
--- a/dom/media/MediaResult.h
+++ b/dom/media/MediaResult.h
@@ -9,7 +9,7 @@
#include "nsString.h" // Required before 'mozilla/ErrorNames.h'!?
#include "mozilla/ErrorNames.h"
-#include "mozilla/TimeStamp.h"
+#include "mozilla/IntegerPrintfMacros.h"
#include "nsError.h"
#include "nsPrintfCString.h"
diff --git a/dom/media/MediaTimer.cpp b/dom/media/MediaTimer.cpp
index c34eb816fa..231e0e2441 100644
--- a/dom/media/MediaTimer.cpp
+++ b/dom/media/MediaTimer.cpp
@@ -68,12 +68,12 @@ bool MediaTimer::OnMediaTimerThread() {
}
RefPtr<MediaTimerPromise> MediaTimer::WaitFor(const TimeDuration& aDuration,
- const char* aCallSite) {
+ StaticString aCallSite) {
return WaitUntil(TimeStamp::Now() + aDuration, aCallSite);
}
RefPtr<MediaTimerPromise> MediaTimer::WaitUntil(const TimeStamp& aTimeStamp,
- const char* aCallSite) {
+ StaticString aCallSite) {
MonitorAutoLock mon(mMonitor);
TIMER_LOG("MediaTimer::WaitUntil %" PRId64, RelativeMicroseconds(aTimeStamp));
Entry e(aTimeStamp, aCallSite);
diff --git a/dom/media/MediaTimer.h b/dom/media/MediaTimer.h
index 837a1591b3..2ab3f2e569 100644
--- a/dom/media/MediaTimer.h
+++ b/dom/media/MediaTimer.h
@@ -44,9 +44,9 @@ class MediaTimer {
DispatchDestroy());
RefPtr<MediaTimerPromise> WaitFor(const TimeDuration& aDuration,
- const char* aCallSite);
+ StaticString aCallSite);
RefPtr<MediaTimerPromise> WaitUntil(const TimeStamp& aTimeStamp,
- const char* aCallSite);
+ StaticString aCallSite);
void Cancel(); // Cancel and reject any unresolved promises with false.
private:
@@ -81,7 +81,7 @@ class MediaTimer {
TimeStamp mTimeStamp;
RefPtr<MediaTimerPromise::Private> mPromise;
- explicit Entry(const TimeStamp& aTimeStamp, const char* aCallSite)
+ explicit Entry(const TimeStamp& aTimeStamp, StaticString aCallSite)
: mTimeStamp(aTimeStamp),
mPromise(new MediaTimerPromise::Private(aCallSite)) {}
diff --git a/dom/media/MediaTrackGraph.cpp b/dom/media/MediaTrackGraph.cpp
index a4f81d0071..e9356548b8 100644
--- a/dom/media/MediaTrackGraph.cpp
+++ b/dom/media/MediaTrackGraph.cpp
@@ -440,11 +440,10 @@ void MediaTrackGraphImpl::CheckDriver() {
NativeInputTrack* native =
mDeviceInputTrackManagerGraphThread.GetNativeInputTrack();
CubebUtils::AudioDeviceID inputDevice = native ? native->mDeviceId : nullptr;
- uint32_t inputChannelCount =
- native ? AudioInputChannelCount(native->mDeviceId) : 0;
- AudioInputType inputPreference =
- native ? AudioInputDevicePreference(native->mDeviceId)
- : AudioInputType::Unknown;
+ uint32_t inputChannelCount = AudioInputChannelCount(inputDevice);
+ AudioInputType inputPreference = AudioInputDevicePreference(inputDevice);
+ cubeb_input_processing_params inputProcessingParams =
+ RequestedAudioInputProcessingParams(inputDevice);
uint32_t primaryOutputChannelCount = PrimaryOutputChannelCount();
if (!audioCallbackDriver) {
@@ -452,7 +451,7 @@ void MediaTrackGraphImpl::CheckDriver() {
AudioCallbackDriver* driver = new AudioCallbackDriver(
this, CurrentDriver(), mSampleRate, primaryOutputChannelCount,
inputChannelCount, PrimaryOutputDeviceID(), inputDevice,
- inputPreference);
+ inputPreference, inputProcessingParams);
SwitchAtNextIteration(driver);
}
return;
@@ -468,9 +467,14 @@ void MediaTrackGraphImpl::CheckDriver() {
AudioCallbackDriver* driver = new AudioCallbackDriver(
this, CurrentDriver(), mSampleRate, primaryOutputChannelCount,
inputChannelCount, PrimaryOutputDeviceID(), inputDevice,
- inputPreference);
+ inputPreference, inputProcessingParams);
SwitchAtNextIteration(driver);
}
+
+ if (native) {
+ audioCallbackDriver->SetRequestedInputProcessingParams(
+ inputProcessingParams);
+ }
}
void MediaTrackGraphImpl::UpdateTrackOrder() {
@@ -770,7 +774,8 @@ void MediaTrackGraphImpl::OpenAudioInputImpl(DeviceInputTrack* aTrack) {
AudioCallbackDriver* driver = new AudioCallbackDriver(
this, CurrentDriver(), mSampleRate, PrimaryOutputChannelCount(),
AudioInputChannelCount(aTrack->mDeviceId), PrimaryOutputDeviceID(),
- aTrack->mDeviceId, AudioInputDevicePreference(aTrack->mDeviceId));
+ aTrack->mDeviceId, AudioInputDevicePreference(aTrack->mDeviceId),
+ aTrack->RequestedProcessingParams());
LOG(LogLevel::Debug,
("%p OpenAudioInputImpl: starting new AudioCallbackDriver(input) %p",
this, driver));
@@ -842,7 +847,8 @@ void MediaTrackGraphImpl::CloseAudioInputImpl(DeviceInputTrack* aTrack) {
driver = new AudioCallbackDriver(
this, CurrentDriver(), mSampleRate, PrimaryOutputChannelCount(),
AudioInputChannelCount(aTrack->mDeviceId), PrimaryOutputDeviceID(),
- nullptr, AudioInputDevicePreference(aTrack->mDeviceId));
+ nullptr, AudioInputDevicePreference(aTrack->mDeviceId),
+ aTrack->RequestedProcessingParams());
SwitchAtNextIteration(driver);
} else if (CurrentDriver()->AsAudioCallbackDriver()) {
LOG(LogLevel::Debug,
@@ -937,6 +943,32 @@ void MediaTrackGraphImpl::NotifyInputData(const AudioDataValue* aBuffer,
aAlreadyBuffered);
}
+void MediaTrackGraphImpl::NotifySetRequestedInputProcessingParamsResult(
+ AudioCallbackDriver* aDriver,
+ cubeb_input_processing_params aRequestedParams,
+ Result<cubeb_input_processing_params, int>&& aResult) {
+ MOZ_ASSERT(NS_IsMainThread());
+ NativeInputTrack* native =
+ mDeviceInputTrackManagerMainThread.GetNativeInputTrack();
+ if (!native) {
+ return;
+ }
+ QueueControlMessageWithNoShutdown([this, self = RefPtr(this),
+ driver = RefPtr(aDriver), aRequestedParams,
+ result = std::move(aResult)]() mutable {
+ NativeInputTrack* native =
+ mDeviceInputTrackManagerGraphThread.GetNativeInputTrack();
+ if (!native) {
+ return;
+ }
+ if (driver != mDriver) {
+ return;
+ }
+ native->NotifySetRequestedProcessingParamsResult(this, aRequestedParams,
+ result);
+ });
+}
+
void MediaTrackGraphImpl::DeviceChangedImpl() {
MOZ_ASSERT(OnGraphThread());
NativeInputTrack* native =
@@ -1115,7 +1147,7 @@ void MediaTrackGraphImpl::ReevaluateInputDevice(CubebUtils::AudioDeviceID aID) {
AudioCallbackDriver* newDriver = new AudioCallbackDriver(
this, CurrentDriver(), mSampleRate, PrimaryOutputChannelCount(),
AudioInputChannelCount(aID), PrimaryOutputDeviceID(), aID,
- AudioInputDevicePreference(aID));
+ AudioInputDevicePreference(aID), track->RequestedProcessingParams());
SwitchAtNextIteration(newDriver);
}
}
@@ -3459,7 +3491,7 @@ void MediaTrackGraphImpl::Init(GraphDriverType aDriverRequested,
// for the input channel.
mDriver = new AudioCallbackDriver(
this, nullptr, mSampleRate, aChannelCount, 0, PrimaryOutputDeviceID(),
- nullptr, AudioInputType::Unknown);
+ nullptr, AudioInputType::Unknown, CUBEB_INPUT_PROCESSING_PARAM_NONE);
} else {
mDriver = new SystemClockDriver(this, nullptr, mSampleRate);
}
@@ -4257,6 +4289,15 @@ AudioInputType MediaTrackGraphImpl::AudioInputDevicePreference(
: AudioInputType::Unknown;
}
+cubeb_input_processing_params
+MediaTrackGraphImpl::RequestedAudioInputProcessingParams(
+ CubebUtils::AudioDeviceID aID) {
+ MOZ_ASSERT(OnGraphThreadOrNotRunning());
+ DeviceInputTrack* t =
+ mDeviceInputTrackManagerGraphThread.GetDeviceInputTrack(aID);
+ return t ? t->RequestedProcessingParams() : CUBEB_INPUT_PROCESSING_PARAM_NONE;
+}
+
void MediaTrackGraphImpl::SetNewNativeInput() {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!mDeviceInputTrackManagerMainThread.GetNativeInputTrack());
diff --git a/dom/media/MediaTrackGraph.h b/dom/media/MediaTrackGraph.h
index a754b158eb..53783aca27 100644
--- a/dom/media/MediaTrackGraph.h
+++ b/dom/media/MediaTrackGraph.h
@@ -112,12 +112,20 @@ class AudioDataListenerInterface {
/**
* Number of audio input channels.
*/
- virtual uint32_t RequestedInputChannelCount(MediaTrackGraph* aGraph) = 0;
+ virtual uint32_t RequestedInputChannelCount(
+ MediaTrackGraph* aGraph) const = 0;
+
+ /**
+ * The input processing params this listener wants the platform to apply.
+ */
+ virtual cubeb_input_processing_params RequestedInputProcessingParams(
+ MediaTrackGraph* aGraph) const = 0;
/**
* Whether the underlying audio device is used for voice input.
*/
virtual bool IsVoiceInput(MediaTrackGraph* aGraph) const = 0;
+
/**
* Called when the underlying audio device has changed.
*/
@@ -127,6 +135,14 @@ class AudioDataListenerInterface {
* Called when the underlying audio device is being closed.
*/
virtual void Disconnect(MediaTrackGraph* aGraph) = 0;
+
+ /**
+ * Called after an attempt to set the input processing params on the
+ * underlying input track.
+ */
+ virtual void NotifySetRequestedInputProcessingParamsResult(
+ MediaTrackGraph* aGraph, cubeb_input_processing_params aRequestedParams,
+ const Result<cubeb_input_processing_params, int>& aResult) = 0;
};
class AudioDataListener : public AudioDataListenerInterface {
diff --git a/dom/media/MediaTrackGraphImpl.h b/dom/media/MediaTrackGraphImpl.h
index 5daed83ef3..44c04caaa0 100644
--- a/dom/media/MediaTrackGraphImpl.h
+++ b/dom/media/MediaTrackGraphImpl.h
@@ -509,6 +509,12 @@ class MediaTrackGraphImpl : public MediaTrackGraph,
void NotifyInputData(const AudioDataValue* aBuffer, size_t aFrames,
TrackRate aRate, uint32_t aChannels,
uint32_t aAlreadyBuffered) override;
+ /* Called on the main thread after an AudioCallbackDriver has attempted an
+ * operation to set aRequestedParams on the cubeb stream. */
+ void NotifySetRequestedInputProcessingParamsResult(
+ AudioCallbackDriver* aDriver,
+ cubeb_input_processing_params aRequestedParams,
+ Result<cubeb_input_processing_params, int>&& aResult) override;
/* Called every time there are changes to input/output audio devices like
* plug/unplug etc. This can be called on any thread, and posts a message to
* the main thread so that it can post a message to the graph thread. */
@@ -586,6 +592,13 @@ class MediaTrackGraphImpl : public MediaTrackGraph,
AudioInputType AudioInputDevicePreference(CubebUtils::AudioDeviceID aID);
+ /**
+ * The input processing params requested for any processing tracks tied to the
+ * input device with id aID.
+ */
+ cubeb_input_processing_params RequestedAudioInputProcessingParams(
+ CubebUtils::AudioDeviceID aID);
+
double MediaTimeToSeconds(GraphTime aTime) const {
NS_ASSERTION(aTime > -TRACK_TIME_MAX && aTime <= TRACK_TIME_MAX,
"Bad time");
diff --git a/dom/media/SeekJob.cpp b/dom/media/SeekJob.cpp
index 93911638ce..1760cceb35 100644
--- a/dom/media/SeekJob.cpp
+++ b/dom/media/SeekJob.cpp
@@ -18,12 +18,12 @@ bool SeekJob::Exists() const {
return mTarget.isSome();
}
-void SeekJob::Resolve(const char* aCallSite) {
+void SeekJob::Resolve(StaticString aCallSite) {
mPromise.Resolve(true, aCallSite);
mTarget.reset();
}
-void SeekJob::RejectIfExists(const char* aCallSite) {
+void SeekJob::RejectIfExists(StaticString aCallSite) {
mTarget.reset();
mPromise.RejectIfExists(true, aCallSite);
}
diff --git a/dom/media/SeekJob.h b/dom/media/SeekJob.h
index 1bdd6913f3..9e1e404d91 100644
--- a/dom/media/SeekJob.h
+++ b/dom/media/SeekJob.h
@@ -20,8 +20,8 @@ struct SeekJob {
~SeekJob();
bool Exists() const;
- void Resolve(const char* aCallSite);
- void RejectIfExists(const char* aCallSite);
+ void Resolve(StaticString aCallSite);
+ void RejectIfExists(StaticString aCallSite);
Maybe<SeekTarget> mTarget;
MozPromiseHolder<MediaDecoder::SeekPromise> mPromise;
diff --git a/dom/media/VideoFrameConverter.h b/dom/media/VideoFrameConverter.h
index 31b3104955..43a4075b04 100644
--- a/dom/media/VideoFrameConverter.h
+++ b/dom/media/VideoFrameConverter.h
@@ -7,7 +7,7 @@
#define VideoFrameConverter_h
#include "ImageContainer.h"
-#include "ImageToI420.h"
+#include "ImageConversion.h"
#include "Pacer.h"
#include "PerformanceRecorder.h"
#include "VideoSegment.h"
diff --git a/dom/media/VideoUtils.cpp b/dom/media/VideoUtils.cpp
index 24b1f0dd59..31fe3242dc 100644
--- a/dom/media/VideoUtils.cpp
+++ b/dom/media/VideoUtils.cpp
@@ -10,10 +10,13 @@
#include "ImageContainer.h"
#include "MediaContainerType.h"
#include "MediaResource.h"
+#include "PDMFactory.h"
#include "TimeUnits.h"
#include "mozilla/Base64.h"
#include "mozilla/dom/ContentChild.h"
+#include "mozilla/gfx/gfxVars.h"
#include "mozilla/SchedulerGroup.h"
+#include "mozilla/ScopeExit.h"
#include "mozilla/SharedThreadPool.h"
#include "mozilla/StaticPrefs_accessibility.h"
#include "mozilla/StaticPrefs_media.h"
@@ -28,6 +31,10 @@
#include "nsServiceManagerUtils.h"
#include "nsThreadUtils.h"
+#ifdef XP_WIN
+# include "WMFDecoderModule.h"
+#endif
+
namespace mozilla {
using gfx::ColorRange;
@@ -1247,4 +1254,18 @@ void DetermineResolutionForTelemetry(const MediaInfo& aInfo,
aResolutionOut.AppendASCII(resolution);
}
+bool ContainHardwareCodecsSupported(
+ const media::MediaCodecsSupported& aSupport) {
+ return aSupport.contains(
+ mozilla::media::MediaCodecsSupport::H264HardwareDecode) ||
+ aSupport.contains(
+ mozilla::media::MediaCodecsSupport::VP8HardwareDecode) ||
+ aSupport.contains(
+ mozilla::media::MediaCodecsSupport::VP9HardwareDecode) ||
+ aSupport.contains(
+ mozilla::media::MediaCodecsSupport::AV1HardwareDecode) ||
+ aSupport.contains(
+ mozilla::media::MediaCodecsSupport::HEVCHardwareDecode);
+}
+
} // end namespace mozilla
diff --git a/dom/media/VideoUtils.h b/dom/media/VideoUtils.h
index b1dbb0cf2b..ce20226a02 100644
--- a/dom/media/VideoUtils.h
+++ b/dom/media/VideoUtils.h
@@ -9,6 +9,7 @@
#include "AudioSampleFormat.h"
#include "MediaInfo.h"
+#include "MediaCodecsSupport.h"
#include "VideoLimits.h"
#include "mozilla/AbstractThread.h"
#include "mozilla/Attributes.h"
@@ -553,6 +554,10 @@ bool IsWaveMimetype(const nsACString& aMimeType);
void DetermineResolutionForTelemetry(const MediaInfo& aInfo,
nsCString& aResolutionOut);
+// True if given MediaCodecsSupported contains any hardware decoding support.
+bool ContainHardwareCodecsSupported(
+ const media::MediaCodecsSupported& aSupport);
+
} // end namespace mozilla
#endif
diff --git a/dom/media/WavDumper.h b/dom/media/WavDumper.h
index de4195066a..971fa8a32f 100644
--- a/dom/media/WavDumper.h
+++ b/dom/media/WavDumper.h
@@ -107,13 +107,23 @@ class WavDumper {
if (!mFile) {
return;
}
- WriteDumpFileHelper(aBuffer, aSamples);
+ if (aBuffer) {
+ WriteDumpFileHelper(aBuffer, aSamples);
+ } else {
+ constexpr size_t blockSize = 128;
+ T block[blockSize] = {};
+ for (size_t remaining = aSamples; remaining;) {
+ size_t toWrite = std::min(remaining, blockSize);
+ fwrite(block, sizeof(T), toWrite, mFile);
+ remaining -= toWrite;
+ }
+ }
+ fflush(mFile);
}
private:
void WriteDumpFileHelper(const int16_t* aInput, size_t aSamples) {
mozilla::Unused << fwrite(aInput, sizeof(int16_t), aSamples, mFile);
- fflush(mFile);
}
void WriteDumpFileHelper(const float* aInput, size_t aSamples) {
@@ -127,7 +137,6 @@ class WavDumper {
MOZ_ASSERT(rv);
}
mozilla::Unused << fwrite(buf.Elements(), buf.Length(), 1, mFile);
- fflush(mFile);
}
FILE* mFile = nullptr;
diff --git a/dom/media/doctor/DDLifetime.cpp b/dom/media/doctor/DDLifetime.cpp
index 2d4c6cb966..c1d2a01a1c 100644
--- a/dom/media/doctor/DDLifetime.cpp
+++ b/dom/media/doctor/DDLifetime.cpp
@@ -5,6 +5,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "DDLifetime.h"
+#include "mozilla/IntegerPrintfMacros.h"
namespace mozilla {
diff --git a/dom/media/doctor/test/gtest/moz.build b/dom/media/doctor/test/gtest/moz.build
index 7ae9eae130..3ee3f23f4d 100644
--- a/dom/media/doctor/test/gtest/moz.build
+++ b/dom/media/doctor/test/gtest/moz.build
@@ -6,10 +6,15 @@
if CONFIG["OS_TARGET"] != "Android":
UNIFIED_SOURCES += [
- "TestMultiWriterQueue.cpp",
"TestRollingNumber.cpp",
]
+ # Bug 1894309 - Fails under TSAN
+ if not CONFIG["MOZ_TSAN"]:
+ UNIFIED_SOURCES += [
+ "TestMultiWriterQueue.cpp",
+ ]
+
include("/ipc/chromium/chromium-config.mozbuild")
LOCAL_INCLUDES += [
diff --git a/dom/media/driftcontrol/AudioDriftCorrection.cpp b/dom/media/driftcontrol/AudioDriftCorrection.cpp
index e66c435c36..1b86a99a44 100644
--- a/dom/media/driftcontrol/AudioDriftCorrection.cpp
+++ b/dom/media/driftcontrol/AudioDriftCorrection.cpp
@@ -35,8 +35,8 @@ AudioDriftCorrection::AudioDriftCorrection(
: mTargetRate(aTargetRate),
mDriftController(MakeUnique<DriftController>(aSourceRate, aTargetRate,
mDesiredBuffering)),
- mResampler(MakeUnique<AudioResampler>(
- aSourceRate, aTargetRate, mDesiredBuffering, aPrincipalHandle)) {}
+ mResampler(MakeUnique<AudioResampler>(aSourceRate, aTargetRate, 0,
+ aPrincipalHandle)) {}
AudioDriftCorrection::~AudioDriftCorrection() = default;
@@ -94,7 +94,7 @@ AudioSegment AudioDriftCorrection::RequestFrames(const AudioSegment& aInput,
mDriftController->UpdateClock(inputDuration, outputDuration,
CurrentBuffering(), BufferSize());
// Update resampler's rate if there is a new correction.
- mResampler->UpdateOutRate(mDriftController->GetCorrectedTargetRate());
+ mResampler->UpdateInRate(mDriftController->GetCorrectedSourceRate());
if (hasUnderrun) {
if (!mIsHandlingUnderrun) {
NS_WARNING("Drift-correction: Underrun");
@@ -171,7 +171,8 @@ void AudioDriftCorrection::SetDesiredBuffering(
media::TimeUnit aDesiredBuffering) {
mDesiredBuffering = aDesiredBuffering;
mDriftController->SetDesiredBuffering(mDesiredBuffering);
- mResampler->SetPreBufferDuration(mDesiredBuffering);
+ mResampler->SetInputPreBufferFrameCount(
+ mDesiredBuffering.ToTicksAtRate(mDriftController->mSourceRate));
}
} // namespace mozilla
diff --git a/dom/media/driftcontrol/AudioResampler.cpp b/dom/media/driftcontrol/AudioResampler.cpp
index ecef033a5c..1402fae39e 100644
--- a/dom/media/driftcontrol/AudioResampler.cpp
+++ b/dom/media/driftcontrol/AudioResampler.cpp
@@ -5,12 +5,14 @@
#include "AudioResampler.h"
+#include "TimeUnits.h"
+
namespace mozilla {
AudioResampler::AudioResampler(uint32_t aInRate, uint32_t aOutRate,
- media::TimeUnit aPreBufferDuration,
+ uint32_t aInputPreBufferFrameCount,
const PrincipalHandle& aPrincipalHandle)
- : mResampler(aInRate, aOutRate, aPreBufferDuration),
+ : mResampler(aInRate, aOutRate, aInputPreBufferFrameCount),
mOutputChunks(aOutRate / 10, STEREO, aPrincipalHandle) {}
void AudioResampler::AppendInput(const AudioSegment& aInSegment) {
@@ -59,11 +61,11 @@ AudioSegment AudioResampler::Resample(uint32_t aOutFrames, bool* aHasUnderrun) {
return segment;
}
- media::TimeUnit outDuration(aOutFrames, mResampler.GetOutRate());
+ media::TimeUnit outDuration(aOutFrames, mResampler.mOutRate);
mResampler.EnsurePreBuffer(outDuration);
const media::TimeUnit chunkCapacity(mOutputChunks.ChunkCapacity(),
- mResampler.GetOutRate());
+ mResampler.mOutRate);
while (!outDuration.IsZero()) {
MOZ_ASSERT(outDuration.IsPositive());
@@ -71,8 +73,7 @@ AudioSegment AudioResampler::Resample(uint32_t aOutFrames, bool* aHasUnderrun) {
const media::TimeUnit chunkDuration = std::min(outDuration, chunkCapacity);
outDuration -= chunkDuration;
- const uint32_t outFrames =
- chunkDuration.ToTicksAtRate(mResampler.GetOutRate());
+ const uint32_t outFrames = chunkDuration.ToTicksAtRate(mResampler.mOutRate);
for (uint32_t i = 0; i < chunk.ChannelCount(); ++i) {
if (chunk.mBufferFormat == AUDIO_FORMAT_FLOAT32) {
*aHasUnderrun |= mResampler.Resample(
@@ -92,8 +93,8 @@ AudioSegment AudioResampler::Resample(uint32_t aOutFrames, bool* aHasUnderrun) {
return segment;
}
-void AudioResampler::Update(uint32_t aOutRate, uint32_t aChannels) {
- mResampler.UpdateResampler(aOutRate, aChannels);
+void AudioResampler::Update(uint32_t aInRate, uint32_t aChannels) {
+ mResampler.UpdateResampler(aInRate, aChannels);
mOutputChunks.Update(aChannels);
}
diff --git a/dom/media/driftcontrol/AudioResampler.h b/dom/media/driftcontrol/AudioResampler.h
index 20e4f1051b..c5982c3a39 100644
--- a/dom/media/driftcontrol/AudioResampler.h
+++ b/dom/media/driftcontrol/AudioResampler.h
@@ -9,12 +9,11 @@
#include "AudioChunkList.h"
#include "AudioSegment.h"
#include "DynamicResampler.h"
-#include "TimeUnits.h"
namespace mozilla {
/**
- * Audio Resampler is a resampler able to change the output rate and channels
+ * Audio Resampler is a resampler able to change the input rate and channels
* count on the fly. The API is simple and it is based in AudioSegment in order
* to be used MTG. Memory allocations, for input and output buffers, will happen
* in the constructor, when channel count changes and if the amount of input
@@ -36,7 +35,7 @@ namespace mozilla {
class AudioResampler final {
public:
AudioResampler(uint32_t aInRate, uint32_t aOutRate,
- media::TimeUnit aPreBufferDuration,
+ uint32_t aInputPreBufferFrameCount,
const PrincipalHandle& aPrincipalHandle);
/**
@@ -69,24 +68,24 @@ class AudioResampler final {
AudioSegment Resample(uint32_t aOutFrames, bool* aHasUnderrun);
/*
- * Updates the output rate that will be used by the resampler.
+ * Updates the input rate that will be used by the resampler.
*/
- void UpdateOutRate(uint32_t aOutRate) {
- Update(aOutRate, mResampler.GetChannels());
+ void UpdateInRate(uint32_t aInRate) {
+ Update(aInRate, mResampler.GetChannels());
}
/**
- * Set the duration that should be used for pre-buffering.
+ * Set the number of frames that should be used for input pre-buffering.
*/
- void SetPreBufferDuration(media::TimeUnit aPreBufferDuration) {
- mResampler.SetPreBufferDuration(aPreBufferDuration);
+ void SetInputPreBufferFrameCount(uint32_t aInputPreBufferFrameCount) {
+ mResampler.SetInputPreBufferFrameCount(aInputPreBufferFrameCount);
}
private:
void UpdateChannels(uint32_t aChannels) {
- Update(mResampler.GetOutRate(), aChannels);
+ Update(mResampler.GetInRate(), aChannels);
}
- void Update(uint32_t aOutRate, uint32_t aChannels);
+ void Update(uint32_t aInRate, uint32_t aChannels);
private:
DynamicResampler mResampler;
diff --git a/dom/media/driftcontrol/DriftController.cpp b/dom/media/driftcontrol/DriftController.cpp
index b5603f72bb..791bfe9614 100644
--- a/dom/media/driftcontrol/DriftController.cpp
+++ b/dom/media/driftcontrol/DriftController.cpp
@@ -50,7 +50,7 @@ DriftController::DriftController(uint32_t aSourceRate, uint32_t aTargetRate,
mSourceRate(aSourceRate),
mTargetRate(aTargetRate),
mDesiredBuffering(aDesiredBuffering),
- mCorrectedTargetRate(static_cast<float>(aTargetRate)),
+ mCorrectedSourceRate(static_cast<float>(aSourceRate)),
mMeasuredSourceLatency(5),
mMeasuredTargetLatency(5) {
LOG_CONTROLLER(
@@ -76,8 +76,8 @@ void DriftController::ResetAfterUnderrun() {
mTargetClock = mAdjustmentInterval;
}
-uint32_t DriftController::GetCorrectedTargetRate() const {
- return std::lround(mCorrectedTargetRate);
+uint32_t DriftController::GetCorrectedSourceRate() const {
+ return std::lround(mCorrectedSourceRate);
}
void DriftController::UpdateClock(media::TimeUnit aSourceDuration,
@@ -112,14 +112,16 @@ void DriftController::CalculateCorrection(uint32_t aBufferedFrames,
static constexpr float kDerivativeGain = 0.12;
// Maximum 0.1% change per update.
- const float cap = static_cast<float>(mTargetRate) / 1000.0f;
+ const float cap = static_cast<float>(mSourceRate) / 1000.0f;
// The integral term can make us grow far outside the cap. Impose a cap on
// it individually that is roughly equivalent to the final cap.
const float integralCap = cap / kIntegralGain;
- int32_t error = CheckedInt32(mDesiredBuffering.ToTicksAtRate(mSourceRate) -
- aBufferedFrames)
+ // Use nominal (not corrected) source rate when interpreting desired
+ // buffering so that the set point is independent of the control value.
+ int32_t error = CheckedInt32(aBufferedFrames -
+ mDesiredBuffering.ToTicksAtRate(mSourceRate))
.value();
int32_t proportional = error;
// targetClockSec is the number of target clock seconds since last
@@ -135,12 +137,12 @@ void DriftController::CalculateCorrection(uint32_t aBufferedFrames,
kIntegralGain * mIntegral +
kDerivativeGain * derivative;
float correctedRate =
- std::clamp(static_cast<float>(mTargetRate) + controlSignal,
- mCorrectedTargetRate - cap, mCorrectedTargetRate + cap);
+ std::clamp(static_cast<float>(mSourceRate) + controlSignal,
+ mCorrectedSourceRate - cap, mCorrectedSourceRate + cap);
// mDesiredBuffering is divided by this to calculate the amount of
// hysteresis to apply. With a denominator of 5, an error within +/- 20% of
- // the desired buffering will not make corrections to the target sample
+ // the desired buffering will not make corrections to the source sample
// rate.
static constexpr uint32_t kHysteresisDenominator = 5; // +/- 20%
@@ -183,7 +185,7 @@ void DriftController::CalculateCorrection(uint32_t aBufferedFrames,
return correctedRate;
}
- return mCorrectedTargetRate;
+ return mCorrectedSourceRate;
}();
if (mDurationWithinHysteresis > mIntegralCapTimeLimit) {
@@ -201,10 +203,10 @@ void DriftController::CalculateCorrection(uint32_t aBufferedFrames,
LOG_CONTROLLER(
LogLevel::Verbose, this,
"Recalculating Correction: Nominal: %uHz->%uHz, Corrected: "
- "%uHz->%.2fHz (diff %.2fHz), error: %.2fms (hysteresisThreshold: "
+ "%.2fHz->%uHz (diff %.2fHz), error: %.2fms (hysteresisThreshold: "
"%.2fms), buffering: %.2fms, desired buffering: %.2fms",
- mSourceRate, mTargetRate, mSourceRate, hysteresisCorrectedRate,
- hysteresisCorrectedRate - mCorrectedTargetRate,
+ mSourceRate, mTargetRate, hysteresisCorrectedRate, mTargetRate,
+ hysteresisCorrectedRate - mCorrectedSourceRate,
media::TimeUnit(error, mSourceRate).ToSeconds() * 1000.0,
media::TimeUnit(hysteresisThreshold, mSourceRate).ToSeconds() * 1000.0,
media::TimeUnit(aBufferedFrames, mSourceRate).ToSeconds() * 1000.0,
@@ -219,13 +221,13 @@ void DriftController::CalculateCorrection(uint32_t aBufferedFrames,
kProportionalGain * proportional, kIntegralGain * mIntegral,
kDerivativeGain * derivative, controlSignal);
- if (std::lround(mCorrectedTargetRate) !=
+ if (std::lround(mCorrectedSourceRate) !=
std::lround(hysteresisCorrectedRate)) {
++mNumCorrectionChanges;
}
mPreviousError = error;
- mCorrectedTargetRate = hysteresisCorrectedRate;
+ mCorrectedSourceRate = std::max(1.f, hysteresisCorrectedRate);
// Reset the counters to prepare for the next period.
mTargetClock = media::TimeUnit::Zero();
diff --git a/dom/media/driftcontrol/DriftController.h b/dom/media/driftcontrol/DriftController.h
index 0bd745c737..e8dbc57e0e 100644
--- a/dom/media/driftcontrol/DriftController.h
+++ b/dom/media/driftcontrol/DriftController.h
@@ -22,8 +22,8 @@ namespace mozilla {
* the calculations.
*
* The DriftController looks at how the current buffering level differs from the
- * desired buffering level and sets a corrected target rate. A resampler should
- * be configured to resample from the nominal source rate to the corrected
+ * desired buffering level and sets a corrected source rate. A resampler should
+ * be configured to resample from the corrected source rate to the nominal
* target rate. It assumes that the resampler is initially configured to
* resample from the nominal source rate to the nominal target rate.
*
@@ -53,12 +53,12 @@ class DriftController final {
void ResetAfterUnderrun();
/**
- * Returns the drift-corrected target rate.
+ * Returns the drift-corrected source rate.
*/
- uint32_t GetCorrectedTargetRate() const;
+ uint32_t GetCorrectedSourceRate() const;
/**
- * The number of times mCorrectedTargetRate has been changed to adjust to
+ * The number of times mCorrectedSourceRate has been changed to adjust to
* drift.
*/
uint32_t NumCorrectionChanges() const { return mNumCorrectionChanges; }
@@ -102,9 +102,12 @@ class DriftController final {
// This implements a simple PID controller with feedback.
// Set point: SP = mDesiredBuffering.
// Process value: PV(t) = aBufferedFrames. This is the feedback.
- // Error: e(t) = mDesiredBuffering - aBufferedFrames.
- // Control value: CV(t) = the number to add to the nominal target rate, i.e.
- // the corrected target rate = CV(t) + nominal target rate.
+ // Error: e(t) = aBufferedFrames - mDesiredBuffering.
+ // Error is positive when the process value is high, which is
+ // the opposite of conventional PID controllers because this
+ // is a reverse-acting system.
+ // Control value: CV(t) = the value to add to the nominal source rate, i.e.
+ // the corrected source rate = nominal source rate + CV(t).
//
// Controller:
// Proportional part: The error, p(t) = e(t), multiplied by a gain factor, Kp.
@@ -115,13 +118,13 @@ class DriftController final {
// Control signal: The sum of the parts' output,
// u(t) = Kp*p(t) + Ki*i(t) + Kd*d(t).
//
- // Control action: Converting the control signal to a target sample rate.
+ // Control action: Converting the control signal to a source sample rate.
// Simplified, a positive control signal means the buffer is
- // lower than desired (because the error is positive), so the
- // target sample rate must be increased in order to consume
- // input data slower. We calculate the corrected target rate
- // by simply adding the control signal, u(t), to the nominal
- // target rate.
+ // higher than desired (because the error is positive),
+ // so the source sample rate must be increased in order to
+ // consume input data faster.
+ // We calculate the corrected source rate by simply adding
+ // the control signal, u(t), to the nominal source rate.
//
// Hysteresis: As long as the error is within a threshold of 20% of the set
// point (desired buffering level) (up to 10ms for >50ms desired
@@ -144,7 +147,7 @@ class DriftController final {
int32_t mPreviousError = 0;
float mIntegral = 0.0;
Maybe<float> mIntegralCenterForCap;
- float mCorrectedTargetRate;
+ float mCorrectedSourceRate;
Maybe<int32_t> mLastHysteresisBoundaryCorrection;
media::TimeUnit mDurationWithinHysteresis;
uint32_t mNumCorrectionChanges = 0;
diff --git a/dom/media/driftcontrol/DynamicResampler.cpp b/dom/media/driftcontrol/DynamicResampler.cpp
index e6f230278e..65a2ae3b57 100644
--- a/dom/media/driftcontrol/DynamicResampler.cpp
+++ b/dom/media/driftcontrol/DynamicResampler.cpp
@@ -8,14 +8,13 @@
namespace mozilla {
DynamicResampler::DynamicResampler(uint32_t aInRate, uint32_t aOutRate,
- media::TimeUnit aPreBufferDuration)
- : mInRate(aInRate),
- mPreBufferDuration(aPreBufferDuration),
- mOutRate(aOutRate) {
+ uint32_t aInputPreBufferFrameCount)
+ : mOutRate(aOutRate),
+ mInputPreBufferFrameCount(aInputPreBufferFrameCount),
+ mInRate(aInRate) {
MOZ_ASSERT(aInRate);
MOZ_ASSERT(aOutRate);
- MOZ_ASSERT(aPreBufferDuration.IsPositiveOrZero());
- UpdateResampler(mOutRate, STEREO);
+ UpdateResampler(mInRate, STEREO);
mInputStreamFile.Open("DynamicResamplerInFirstChannel", 1, mInRate);
mOutputStreamFile.Open("DynamicResamplerOutFirstChannel", 1, mOutRate);
}
@@ -35,7 +34,10 @@ void DynamicResampler::SetSampleFormat(AudioSampleFormat aFormat) {
b.SetSampleFormat(mSampleFormat);
}
- EnsureInputBufferDuration(CalculateInputBufferDuration());
+ // Pre-allocate something big.
+ // EnsureInputBufferDuration() adds 50ms for jitter to this first allocation
+ // so the 50ms argument means at least 100ms.
+ EnsureInputBufferSizeInFrames(mInRate / 20);
}
void DynamicResampler::EnsurePreBuffer(media::TimeUnit aDuration) {
@@ -43,33 +45,36 @@ void DynamicResampler::EnsurePreBuffer(media::TimeUnit aDuration) {
return;
}
- media::TimeUnit buffered(mInternalInBuffer[0].AvailableRead(), mInRate);
- if (buffered.IsZero()) {
+ uint32_t buffered = mInternalInBuffer[0].AvailableRead();
+ if (buffered == 0) {
// Wait for the first input segment before deciding how much to pre-buffer.
// If it is large it indicates high-latency, and the buffer would have to
- // handle that.
+ // handle that. This also means that the pre-buffer is not set up just
+ // before a large input segment would extend the buffering beyond the
+ // desired level.
return;
}
mIsPreBufferSet = true;
- media::TimeUnit needed = aDuration + mPreBufferDuration;
- EnsureInputBufferDuration(needed);
+ uint32_t needed =
+ aDuration.ToTicksAtRate(mInRate) + mInputPreBufferFrameCount;
+ EnsureInputBufferSizeInFrames(needed);
if (needed > buffered) {
for (auto& b : mInternalInBuffer) {
- b.PrependSilence((needed - buffered).ToTicksAtRate(mInRate));
+ b.PrependSilence(needed - buffered);
}
} else if (needed < buffered) {
for (auto& b : mInternalInBuffer) {
- b.Discard((buffered - needed).ToTicksAtRate(mInRate));
+ b.Discard(buffered - needed);
}
}
}
-void DynamicResampler::SetPreBufferDuration(media::TimeUnit aDuration) {
- MOZ_ASSERT(aDuration.IsPositive());
- mPreBufferDuration = aDuration;
+void DynamicResampler::SetInputPreBufferFrameCount(
+ uint32_t aInputPreBufferFrameCount) {
+ mInputPreBufferFrameCount = aInputPreBufferFrameCount;
}
bool DynamicResampler::Resample(float* aOutBuffer, uint32_t aOutFrames,
@@ -93,7 +98,6 @@ void DynamicResampler::ResampleInternal(const float* aInBuffer,
MOZ_ASSERT(mInRate);
MOZ_ASSERT(mOutRate);
- MOZ_ASSERT(aInBuffer);
MOZ_ASSERT(aInFrames);
MOZ_ASSERT(*aInFrames > 0);
MOZ_ASSERT(aOutBuffer);
@@ -125,7 +129,6 @@ void DynamicResampler::ResampleInternal(const int16_t* aInBuffer,
MOZ_ASSERT(mInRate);
MOZ_ASSERT(mOutRate);
- MOZ_ASSERT(aInBuffer);
MOZ_ASSERT(aInFrames);
MOZ_ASSERT(*aInFrames > 0);
MOZ_ASSERT(aOutBuffer);
@@ -147,19 +150,20 @@ void DynamicResampler::ResampleInternal(const int16_t* aInBuffer,
}
}
-void DynamicResampler::UpdateResampler(uint32_t aOutRate, uint32_t aChannels) {
- MOZ_ASSERT(aOutRate);
+void DynamicResampler::UpdateResampler(uint32_t aInRate, uint32_t aChannels) {
+ MOZ_ASSERT(aInRate);
MOZ_ASSERT(aChannels);
if (mChannels != aChannels) {
+ uint32_t bufferSizeInFrames = InFramesBufferSize();
if (mResampler) {
speex_resampler_destroy(mResampler);
}
- mResampler = speex_resampler_init(aChannels, mInRate, aOutRate,
+ mResampler = speex_resampler_init(aChannels, aInRate, mOutRate,
SPEEX_RESAMPLER_QUALITY_MIN, nullptr);
MOZ_ASSERT(mResampler);
mChannels = aChannels;
- mOutRate = aOutRate;
+ mInRate = aInRate;
// Between mono and stereo changes, keep always allocated 2 channels to
// avoid reallocations in the most common case.
if ((mChannels == STEREO || mChannels == 1) &&
@@ -192,14 +196,12 @@ void DynamicResampler::UpdateResampler(uint32_t aOutRate, uint32_t aChannels) {
b->SetSampleFormat(mSampleFormat);
}
}
- media::TimeUnit d = mSetBufferDuration;
- mSetBufferDuration = media::TimeUnit::Zero();
- EnsureInputBufferDuration(d);
+ EnsureInputBufferSizeInFrames(bufferSizeInFrames);
mInputTail.SetLength(mChannels);
return;
}
- if (mOutRate != aOutRate) {
+ if (mInRate != aInRate) {
// If the rates was the same the resampler was not being used so warm up.
if (mOutRate == mInRate) {
WarmUpResampler(true);
@@ -208,9 +210,9 @@ void DynamicResampler::UpdateResampler(uint32_t aOutRate, uint32_t aChannels) {
#ifdef DEBUG
int rv =
#endif
- speex_resampler_set_rate(mResampler, mInRate, aOutRate);
+ speex_resampler_set_rate(mResampler, aInRate, mOutRate);
MOZ_ASSERT(rv == RESAMPLER_ERR_SUCCESS);
- mOutRate = aOutRate;
+ mInRate = aInRate;
}
}
@@ -236,13 +238,9 @@ void DynamicResampler::WarmUpResampler(bool aSkipLatency) {
}
}
if (aSkipLatency) {
- int inputLatency = speex_resampler_get_input_latency(mResampler);
- MOZ_ASSERT(inputLatency > 0);
- uint32_t ratioNum, ratioDen;
- speex_resampler_get_ratio(mResampler, &ratioNum, &ratioDen);
- // Ratio at this point is one so only skip the input latency. No special
- // calculations are needed.
- speex_resampler_set_skip_frac_num(mResampler, inputLatency * ratioDen);
+ // Don't generate output frames corresponding to times before the next
+ // input sample.
+ speex_resampler_skip_zeros(mResampler);
}
mIsWarmingUp = false;
}
@@ -268,7 +266,16 @@ void DynamicResampler::AppendInputSilence(const uint32_t aInFrames) {
}
uint32_t DynamicResampler::InFramesBufferSize() const {
- return mSetBufferDuration.ToTicksAtRate(mInRate);
+ if (mSampleFormat == AUDIO_FORMAT_SILENCE) {
+ return 0;
+ }
+ // Buffers may have different capacities if a memory allocation has failed.
+ MOZ_ASSERT(!mInternalInBuffer.IsEmpty());
+ uint32_t min = std::numeric_limits<uint32_t>::max();
+ for (const auto& b : mInternalInBuffer) {
+ min = std::min(min, b.Capacity());
+ }
+ return min;
}
uint32_t DynamicResampler::InFramesBuffered(uint32_t aChannelIndex) const {
@@ -276,7 +283,7 @@ uint32_t DynamicResampler::InFramesBuffered(uint32_t aChannelIndex) const {
MOZ_ASSERT(aChannelIndex <= mChannels);
MOZ_ASSERT(aChannelIndex <= mInternalInBuffer.Length());
if (!mIsPreBufferSet) {
- return mPreBufferDuration.ToTicksAtRate(mInRate);
+ return mInputPreBufferFrameCount;
}
return mInternalInBuffer[aChannelIndex].AvailableRead();
}
diff --git a/dom/media/driftcontrol/DynamicResampler.h b/dom/media/driftcontrol/DynamicResampler.h
index c1b9000aa0..1f601c898b 100644
--- a/dom/media/driftcontrol/DynamicResampler.h
+++ b/dom/media/driftcontrol/DynamicResampler.h
@@ -27,15 +27,13 @@ const uint32_t STEREO = 2;
* to allow the requested to be resampled and returned.
*
* Input data buffering makes use of the AudioRingBuffer. The capacity of the
- * buffer is initially 100ms of float audio and it is pre-allocated at the
- * constructor. Should the input data grow beyond that, the input buffer is
- * re-allocated on the fly. In addition to that, due to special feature of
+ * buffer is initially 100ms of audio and it is pre-allocated during
+ * SetSampleFormat(). Should the input data grow beyond that, the input buffer
+ * is re-allocated on the fly. In addition to that, due to special feature of
* AudioRingBuffer, no extra copies take place when the input data is fed to the
* resampler.
*
- * The sample format must be set before using any method. If the provided sample
- * format is of type short the pre-allocated capacity of the input buffer
- * becomes 200ms of short audio.
+ * The sample format must be set before using any method.
*
* The DynamicResampler is not thread-safe, so all the methods appart from the
* constructor must be called on the same thread.
@@ -47,16 +45,15 @@ class DynamicResampler final {
* The channel count will be set to stereo. Memory allocation will take
* place. The input buffer is non-interleaved.
*/
- DynamicResampler(
- uint32_t aInRate, uint32_t aOutRate,
- media::TimeUnit aPreBufferDuration = media::TimeUnit::Zero());
+ DynamicResampler(uint32_t aInRate, uint32_t aOutRate,
+ uint32_t aInputPreBufferFrameCount = 0);
~DynamicResampler();
/**
* Set the sample format type to float or short.
*/
void SetSampleFormat(AudioSampleFormat aFormat);
- uint32_t GetOutRate() const { return mOutRate; }
+ uint32_t GetInRate() const { return mInRate; }
uint32_t GetChannels() const { return mChannels; }
/**
@@ -81,16 +78,16 @@ class DynamicResampler final {
/**
* Prepends existing input data with a silent pre-buffer if not already done.
- * Data will be prepended so that after resampling aOutFrames worth of output
- * data, the buffering level will be as close as possible to
- * mPreBufferDuration, which is the desired buffering level.
+ * Data will be prepended so that after resampling aDuration of data,
+ * the buffering level will be as close as possible to
+ * mInputPreBufferFrameCount, which is the desired buffering level.
*/
void EnsurePreBuffer(media::TimeUnit aDuration);
/**
- * Set the duration that should be used for pre-buffering.
+ * Set the number of frames that should be used for input pre-buffering.
*/
- void SetPreBufferDuration(media::TimeUnit aDuration);
+ void SetInputPreBufferFrameCount(uint32_t aInputPreBufferFrameCount);
/*
* Resample as much frames as needed from the internal input buffer to the
@@ -114,14 +111,14 @@ class DynamicResampler final {
/**
* Update the output rate or/and the channel count. If a value is not updated
- * compared to the current one nothing happens. Changing the `aOutRate`
+ * compared to the current one nothing happens. Changing the `aInRate`
* results in recalculation in the resampler. Changing `aChannels` results in
* the reallocation of the internal input buffer with the exception of
* changes between mono to stereo and vice versa where no reallocation takes
* place. A stereo internal input buffer is always maintained even if the
* sound is mono.
*/
- void UpdateResampler(uint32_t aOutRate, uint32_t aChannels);
+ void UpdateResampler(uint32_t aInRate, uint32_t aChannels);
private:
template <typename T>
@@ -174,24 +171,24 @@ class DynamicResampler final {
}
uint32_t totalOutFramesNeeded = aOutFrames;
- auto resample = [&] {
- mInternalInBuffer[aChannelIndex].ReadNoCopy(
- [&](const Span<const T>& aInBuffer) -> uint32_t {
- if (!totalOutFramesNeeded) {
- return 0;
- }
- uint32_t outFramesResampled = totalOutFramesNeeded;
- uint32_t inFrames = aInBuffer.Length();
- ResampleInternal(aInBuffer.data(), &inFrames, aOutBuffer,
- &outFramesResampled, aChannelIndex);
- aOutBuffer += outFramesResampled;
- totalOutFramesNeeded -= outFramesResampled;
- mInputTail[aChannelIndex].StoreTail<T>(aInBuffer.To(inFrames));
- return inFrames;
- });
+ auto resample = [&](const T* aInBuffer, uint32_t aInLength) -> uint32_t {
+ uint32_t outFramesResampled = totalOutFramesNeeded;
+ uint32_t inFrames = aInLength;
+ ResampleInternal(aInBuffer, &inFrames, aOutBuffer, &outFramesResampled,
+ aChannelIndex);
+ aOutBuffer += outFramesResampled;
+ totalOutFramesNeeded -= outFramesResampled;
+ mInputTail[aChannelIndex].StoreTail<T>(aInBuffer, inFrames);
+ return inFrames;
};
- resample();
+ mInternalInBuffer[aChannelIndex].ReadNoCopy(
+ [&](const Span<const T>& aInBuffer) -> uint32_t {
+ if (!totalOutFramesNeeded) {
+ return 0;
+ }
+ return resample(aInBuffer.Elements(), aInBuffer.Length());
+ });
if (totalOutFramesNeeded == 0) {
return false;
@@ -204,8 +201,7 @@ class DynamicResampler final {
((CheckedUint32(totalOutFramesNeeded) * mInRate + mOutRate - 1) /
mOutRate)
.value();
- mInternalInBuffer[aChannelIndex].WriteSilence(totalInFramesNeeded);
- resample();
+ resample(nullptr, totalInFramesNeeded);
}
mIsPreBufferSet = false;
return true;
@@ -219,33 +215,14 @@ class DynamicResampler final {
MOZ_ASSERT(mChannels);
MOZ_ASSERT(aChannelIndex < mChannels);
MOZ_ASSERT(aChannelIndex < mInternalInBuffer.Length());
- EnsureInputBufferDuration(media::TimeUnit(
- CheckedInt64(mInternalInBuffer[aChannelIndex].AvailableRead()) +
- aInFrames,
- mInRate));
+ EnsureInputBufferSizeInFrames(
+ mInternalInBuffer[aChannelIndex].AvailableRead() + aInFrames);
mInternalInBuffer[aChannelIndex].Write(Span(aInBuffer, aInFrames));
}
void WarmUpResampler(bool aSkipLatency);
- media::TimeUnit CalculateInputBufferDuration() const {
- // Pre-allocate something big, twice the pre-buffer, or at least 100ms.
- return std::max(mPreBufferDuration * 2, media::TimeUnit::FromSeconds(0.1));
- }
-
- bool EnsureInputBufferDuration(media::TimeUnit aDuration) {
- if (aDuration <= mSetBufferDuration) {
- // Buffer size is sufficient.
- return true;
- }
-
- // 5 second cap.
- const media::TimeUnit cap = media::TimeUnit::FromSeconds(5);
- if (mSetBufferDuration == cap) {
- // Already at the cap.
- return false;
- }
-
+ bool EnsureInputBufferSizeInFrames(uint32_t aSizeInFrames) {
uint32_t sampleSize = 0;
if (mSampleFormat == AUDIO_FORMAT_FLOAT32) {
sampleSize = sizeof(float);
@@ -258,53 +235,62 @@ class DynamicResampler final {
return true;
}
+ uint32_t sizeInFrames = InFramesBufferSize();
+ if (aSizeInFrames <= sizeInFrames) {
+ // Buffer size is sufficient.
+ return true; // no reallocation necessary
+ }
+
+ // 5 second cap.
+ const uint32_t cap = 5 * mInRate;
+ if (sizeInFrames >= cap) {
+ // Already at the cap.
+ return false;
+ }
+
// As a backoff strategy, at least double the previous size.
- media::TimeUnit duration = mSetBufferDuration * 2;
+ sizeInFrames *= 2;
- if (aDuration > duration) {
+ if (aSizeInFrames > sizeInFrames) {
// A larger buffer than the normal backoff strategy provides is needed, or
- // this is the first time setting the buffer size. Round up to the nearest
- // 100ms, some jitter is expected.
- duration = aDuration.ToBase<media::TimeUnit::CeilingPolicy>(10);
+ // this is the first time setting the buffer size. Add another 50ms, as
+ // some jitter is expected.
+ sizeInFrames = aSizeInFrames + mInRate / 20;
}
- duration = std::min(cap, duration);
+ // mInputPreBufferFrameCount is an indication of the desired average
+ // buffering. Provide for at least twice this.
+ sizeInFrames = std::max(sizeInFrames, mInputPreBufferFrameCount * 2);
+
+ sizeInFrames = std::min(cap, sizeInFrames);
bool success = true;
for (auto& b : mInternalInBuffer) {
- success = success &&
- b.SetLengthBytes(sampleSize * duration.ToTicksAtRate(mInRate));
+ success = success && b.EnsureLengthBytes(sampleSize * sizeInFrames);
}
if (success) {
// All buffers have the new size.
- mSetBufferDuration = duration;
return true;
}
- const uint32_t sizeInFrames =
- static_cast<uint32_t>(mSetBufferDuration.ToTicksAtRate(mInRate));
// Allocating an input buffer failed. We stick with the old buffer size.
NS_WARNING(nsPrintfCString("Failed to allocate a buffer of %u bytes (%u "
"frames). Expect glitches.",
sampleSize * sizeInFrames, sizeInFrames)
.get());
- for (auto& b : mInternalInBuffer) {
- MOZ_ALWAYS_TRUE(b.SetLengthBytes(sampleSize * sizeInFrames));
- }
return false;
}
public:
- const uint32_t mInRate;
+ const uint32_t mOutRate;
private:
bool mIsPreBufferSet = false;
bool mIsWarmingUp = false;
- media::TimeUnit mPreBufferDuration;
- media::TimeUnit mSetBufferDuration = media::TimeUnit::Zero();
+ uint32_t mInputPreBufferFrameCount;
uint32_t mChannels = 0;
- uint32_t mOutRate;
+ uint32_t mInRate;
AutoTArray<AudioRingBuffer, STEREO> mInternalInBuffer;
@@ -324,16 +310,16 @@ class DynamicResampler final {
}
template <typename T>
void StoreTail(const T* aInBuffer, uint32_t aInFrames) {
- if (aInFrames >= MAXSIZE) {
- PodCopy(Buffer<T>(), aInBuffer + aInFrames - MAXSIZE, MAXSIZE);
- mSize = MAXSIZE;
+ const T* inBuffer = aInBuffer;
+ mSize = std::min(aInFrames, MAXSIZE);
+ if (inBuffer) {
+ PodCopy(Buffer<T>(), inBuffer + aInFrames - mSize, mSize);
} else {
- PodCopy(Buffer<T>(), aInBuffer, aInFrames);
- mSize = aInFrames;
+ std::fill_n(Buffer<T>(), mSize, static_cast<T>(0));
}
}
uint32_t Length() { return mSize; }
- static const uint32_t MAXSIZE = 20;
+ static constexpr uint32_t MAXSIZE = 20;
private:
float mBuffer[MAXSIZE] = {};
diff --git a/dom/media/driftcontrol/gtest/TestAudioDriftCorrection.cpp b/dom/media/driftcontrol/gtest/TestAudioDriftCorrection.cpp
index c13f443d37..9d3f0f091a 100644
--- a/dom/media/driftcontrol/gtest/TestAudioDriftCorrection.cpp
+++ b/dom/media/driftcontrol/gtest/TestAudioDriftCorrection.cpp
@@ -260,10 +260,10 @@ TEST(TestAudioDriftCorrection, LargerTransmitterBlockSizeThanDesiredBuffering)
// Input is stable so no corrections should occur.
EXPECT_EQ(ad.NumCorrectionChanges(), 0U);
- // The drift correction buffer size had to be larger than the desired (the
- // buffer size is twice the initial buffering level), to accomodate the large
- // input block size.
- EXPECT_EQ(ad.BufferSize(), 9600U);
+ // The desired buffering and pre-buffering level was
+ // transmitterBlockSize * 11 / 10 to accomodate the large input block size.
+ // The buffer size was twice the pre-buffering level.
+ EXPECT_EQ(ad.BufferSize(), transmitterBlockSize * 11 / 10 * 2);
}
TEST(TestAudioDriftCorrection, LargerReceiverBlockSizeThanDesiredBuffering)
@@ -275,9 +275,9 @@ TEST(TestAudioDriftCorrection, LargerReceiverBlockSizeThanDesiredBuffering)
MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
AudioDriftCorrection ad(sampleRate, sampleRate, testPrincipal);
+ AudioSegment inSegment;
for (uint32_t i = 0; i < (sampleRate / 1000) * 500;
i += transmitterBlockSize) {
- AudioSegment inSegment;
AudioChunk chunk =
CreateAudioChunk<float>(transmitterBlockSize, 1, AUDIO_FORMAT_FLOAT32);
inSegment.AppendAndConsumeChunk(std::move(chunk));
@@ -285,6 +285,7 @@ TEST(TestAudioDriftCorrection, LargerReceiverBlockSizeThanDesiredBuffering)
if (i % receiverBlockSize == 0) {
AudioSegment outSegment = ad.RequestFrames(inSegment, receiverBlockSize);
EXPECT_EQ(outSegment.GetDuration(), receiverBlockSize);
+ inSegment.Clear();
}
if (i >= receiverBlockSize) {
@@ -294,11 +295,12 @@ TEST(TestAudioDriftCorrection, LargerReceiverBlockSizeThanDesiredBuffering)
// Input is stable so no corrections should occur.
EXPECT_EQ(ad.NumCorrectionChanges(), 0U);
+ EXPECT_EQ(ad.NumUnderruns(), 0U);
// The drift correction buffer size had to be larger than the desired (the
// buffer size is twice the initial buffering level), to accomodate the large
// input block size that gets buffered in the resampler only when processing
// output.
- EXPECT_EQ(ad.BufferSize(), 19200U);
+ EXPECT_EQ(ad.BufferSize(), 9600U);
}
TEST(TestAudioDriftCorrection, DynamicInputBufferSizeChanges)
@@ -329,9 +331,9 @@ TEST(TestAudioDriftCorrection, DynamicInputBufferSizeChanges)
if (((receivedFramesStart - transmittedFramesStart + i) /
aTransmitterBlockSize) > numBlocksTransmitted) {
tone.Generate(inSegment, aTransmitterBlockSize);
- MOZ_ASSERT(!inSegment.IsNull());
+ MOZ_RELEASE_ASSERT(!inSegment.IsNull());
inToneVerifier.AppendData(inSegment);
- MOZ_ASSERT(!inSegment.IsNull());
+ MOZ_RELEASE_ASSERT(!inSegment.IsNull());
++numBlocksTransmitted;
totalFramesTransmitted += aTransmitterBlockSize;
}
@@ -459,29 +461,50 @@ TEST(TestAudioDriftCorrection, DriftStepResponseUnderrunHighLatencyInput)
constexpr uint32_t iterations = 200;
const PrincipalHandle testPrincipal =
MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
- uint32_t inputRate = nominalRate * 1005 / 1000; // 0.5% drift
- uint32_t inputInterval = inputRate;
+ uint32_t inputRate1 = nominalRate * 1005 / 1000; // 0.5% drift
+ uint32_t inputInterval1 = inputRate1;
AudioGenerator<AudioDataValue> tone(1, nominalRate, 440);
AudioDriftCorrection ad(nominalRate, nominalRate, testPrincipal);
for (uint32_t i = 0; i < interval * iterations; i += interval / 100) {
AudioSegment inSegment;
if (i > 0 && i % interval == 0) {
- tone.Generate(inSegment, inputInterval);
+ tone.Generate(inSegment, inputInterval1);
}
ad.RequestFrames(inSegment, interval / 100);
}
- inputRate = nominalRate * 995 / 1000; // -0.5% drift
- inputInterval = inputRate;
+ uint32_t inputRate2 = nominalRate * 995 / 1000; // -0.5% drift
+ uint32_t inputInterval2 = inputRate2;
for (uint32_t i = 0; i < interval * iterations; i += interval / 100) {
AudioSegment inSegment;
+ // The first segment is skipped to cause an underrun.
if (i > 0 && i % interval == 0) {
- tone.Generate(inSegment, inputInterval);
+ tone.Generate(inSegment, inputInterval2);
}
ad.RequestFrames(inSegment, interval / 100);
+ if (i >= interval / 10 && i < interval) {
+ // While the DynamicResampler has not set its pre-buffer after the
+ // underrun, InFramesBuffered() reports the pre-buffer size.
+ // The initial desired buffer and pre-buffer size was
+ // inputInterval1 * 11 / 10 to accomodate the large input block size.
+ // This was doubled when the underrun occurred.
+ EXPECT_EQ(ad.CurrentBuffering(), inputInterval1 * 11 / 10 * 2)
+ << "for i=" << i;
+ } else if (i == interval) {
+ // After the pre-buffer was set and used to generate the first output
+ // block, the actual number of frames buffered almost matches the
+ // pre-buffer size, with some rounding from output to input frame count
+ // conversion.
+ EXPECT_EQ(ad.CurrentBuffering(), inputInterval1 * 11 / 10 * 2 - 1)
+ << "after first input after underrun";
+ }
}
- EXPECT_EQ(ad.BufferSize(), 220800U);
+ // The initial desired buffering and pre-buffering level was
+ // inputInterval1 * 11 / 10 to accomodate the large input block size.
+ // The buffer size was initially twice the pre-buffering level, and then
+ // doubled when the underrun occurred.
+ EXPECT_EQ(ad.BufferSize(), inputInterval1 * 11 / 10 * 2 * 2);
EXPECT_EQ(ad.NumUnderruns(), 1u);
}
@@ -511,7 +534,7 @@ TEST(TestAudioDriftCorrection, DriftStepResponseOverrun)
ad.RequestFrames(inSegment, interval / 100);
}
- // Change input callbacks to 2000ms (+0.5% drift) = 48200 frames, which will
+ // Change input callbacks to 1000ms (+0.5% drift) = 48200 frames, which will
// overrun the ring buffer.
for (uint32_t i = 0; i < interval * iterations; i += interval / 100) {
AudioSegment inSegment;
@@ -524,6 +547,9 @@ TEST(TestAudioDriftCorrection, DriftStepResponseOverrun)
ad.RequestFrames(inSegment, interval / 100);
}
- EXPECT_EQ(ad.BufferSize(), 105600U);
+ // The desired buffering and pre-buffering levels were increased to
+ // inputInterval * 11 / 10 to accomodate the large input block size.
+ // The buffer size was increased to twice the pre-buffering level.
+ EXPECT_EQ(ad.BufferSize(), inputInterval * 11 / 10 * 2);
EXPECT_EQ(ad.NumUnderruns(), 1u);
}
diff --git a/dom/media/driftcontrol/gtest/TestAudioResampler.cpp b/dom/media/driftcontrol/gtest/TestAudioResampler.cpp
index f04bc87314..7122b60a1a 100644
--- a/dom/media/driftcontrol/gtest/TestAudioResampler.cpp
+++ b/dom/media/driftcontrol/gtest/TestAudioResampler.cpp
@@ -64,8 +64,7 @@ TEST(TestAudioResampler, OutAudioSegment_Float)
uint32_t pre_buffer = 21;
- AudioResampler dr(in_rate, out_rate, media::TimeUnit(pre_buffer, in_rate),
- testPrincipal);
+ AudioResampler dr(in_rate, out_rate, pre_buffer, testPrincipal);
AudioSegment inSegment =
CreateAudioSegment<float>(in_frames, channels, AUDIO_FORMAT_FLOAT32);
@@ -91,9 +90,9 @@ TEST(TestAudioResampler, OutAudioSegment_Float)
}
}
- // Update out rate
- out_rate = 44100;
- dr.UpdateOutRate(out_rate);
+ // Update in rate
+ in_rate = 26122;
+ dr.UpdateInRate(in_rate);
out_frames = in_frames * out_rate / in_rate;
EXPECT_EQ(out_frames, 18u);
// Even if we provide no input if we have enough buffered input, we can create
@@ -121,8 +120,7 @@ TEST(TestAudioResampler, OutAudioSegment_Short)
uint32_t pre_buffer = 21;
- AudioResampler dr(in_rate, out_rate, media::TimeUnit(pre_buffer, in_rate),
- testPrincipal);
+ AudioResampler dr(in_rate, out_rate, pre_buffer, testPrincipal);
AudioSegment inSegment =
CreateAudioSegment<short>(in_frames, channels, AUDIO_FORMAT_S16);
@@ -148,9 +146,9 @@ TEST(TestAudioResampler, OutAudioSegment_Short)
}
}
- // Update out rate
- out_rate = 44100;
- dr.UpdateOutRate(out_rate);
+ // Update in rate
+ in_rate = 26122;
+ dr.UpdateInRate(out_rate);
out_frames = in_frames * out_rate / in_rate;
EXPECT_EQ(out_frames, 18u);
// Even if we provide no input if we have enough buffered input, we can create
@@ -175,8 +173,7 @@ TEST(TestAudioResampler, OutAudioSegmentLargerThanResampledInput_Float)
uint32_t pre_buffer = 5;
- AudioResampler dr(in_rate, out_rate, media::TimeUnit(pre_buffer, in_rate),
- PRINCIPAL_HANDLE_NONE);
+ AudioResampler dr(in_rate, out_rate, pre_buffer, PRINCIPAL_HANDLE_NONE);
AudioSegment inSegment =
CreateAudioSegment<float>(in_frames, channels, AUDIO_FORMAT_FLOAT32);
@@ -209,8 +206,7 @@ TEST(TestAudioResampler, InAudioSegment_Float)
uint32_t out_rate = 48000;
uint32_t pre_buffer = 10;
- AudioResampler dr(in_rate, out_rate, media::TimeUnit(pre_buffer, in_rate),
- testPrincipal);
+ AudioResampler dr(in_rate, out_rate, pre_buffer, testPrincipal);
AudioSegment inSegment;
@@ -275,8 +271,7 @@ TEST(TestAudioResampler, InAudioSegment_Short)
uint32_t out_rate = 48000;
uint32_t pre_buffer = 10;
- AudioResampler dr(in_rate, out_rate, media::TimeUnit(pre_buffer, in_rate),
- testPrincipal);
+ AudioResampler dr(in_rate, out_rate, pre_buffer, testPrincipal);
AudioSegment inSegment;
@@ -342,8 +337,7 @@ TEST(TestAudioResampler, ChannelChange_MonoToStereo)
uint32_t pre_buffer = 0;
- AudioResampler dr(in_rate, out_rate, media::TimeUnit(pre_buffer, in_rate),
- testPrincipal);
+ AudioResampler dr(in_rate, out_rate, pre_buffer, testPrincipal);
AudioChunk monoChunk =
CreateAudioChunk<float>(in_frames, 1, AUDIO_FORMAT_FLOAT32);
@@ -378,8 +372,7 @@ TEST(TestAudioResampler, ChannelChange_StereoToMono)
uint32_t pre_buffer = 0;
- AudioResampler dr(in_rate, out_rate, media::TimeUnit(pre_buffer, in_rate),
- testPrincipal);
+ AudioResampler dr(in_rate, out_rate, pre_buffer, testPrincipal);
AudioChunk monoChunk =
CreateAudioChunk<float>(in_frames, 1, AUDIO_FORMAT_FLOAT32);
@@ -414,8 +407,7 @@ TEST(TestAudioResampler, ChannelChange_StereoToQuad)
uint32_t pre_buffer = 0;
- AudioResampler dr(in_rate, out_rate, media::TimeUnit(pre_buffer, in_rate),
- testPrincipal);
+ AudioResampler dr(in_rate, out_rate, pre_buffer, testPrincipal);
AudioChunk stereoChunk =
CreateAudioChunk<float>(in_frames, 2, AUDIO_FORMAT_FLOAT32);
@@ -452,7 +444,7 @@ TEST(TestAudioResampler, ChannelChange_QuadToStereo)
uint32_t in_rate = 24000;
uint32_t out_rate = 48000;
- AudioResampler dr(in_rate, out_rate, media::TimeUnit::Zero(), testPrincipal);
+ AudioResampler dr(in_rate, out_rate, 0, testPrincipal);
AudioChunk stereoChunk =
CreateAudioChunk<float>(in_frames, 2, AUDIO_FORMAT_FLOAT32);
@@ -497,7 +489,7 @@ TEST(TestAudioResampler, ChannelChange_Discontinuity)
uint32_t in_frames = in_rate / 100;
uint32_t out_frames = out_rate / 100;
- AudioResampler dr(in_rate, out_rate, media::TimeUnit::Zero(), testPrincipal);
+ AudioResampler dr(in_rate, out_rate, 0, testPrincipal);
AudioChunk monoChunk =
CreateAudioChunk<float>(in_frames, 1, AUDIO_FORMAT_FLOAT32);
@@ -560,8 +552,7 @@ TEST(TestAudioResampler, ChannelChange_Discontinuity2)
uint32_t in_frames = in_rate / 100;
uint32_t out_frames = out_rate / 100;
- AudioResampler dr(in_rate, out_rate, media::TimeUnit(10, in_rate),
- testPrincipal);
+ AudioResampler dr(in_rate, out_rate, 10, testPrincipal);
AudioChunk monoChunk =
CreateAudioChunk<float>(in_frames / 2, 1, AUDIO_FORMAT_FLOAT32);
@@ -630,8 +621,7 @@ TEST(TestAudioResampler, ChannelChange_Discontinuity3)
uint32_t in_frames = in_rate / 100;
uint32_t out_frames = out_rate / 100;
- AudioResampler dr(in_rate, out_rate, media::TimeUnit(10, in_rate),
- testPrincipal);
+ AudioResampler dr(in_rate, out_rate, 10, testPrincipal);
AudioChunk stereoChunk =
CreateAudioChunk<float>(in_frames, 2, AUDIO_FORMAT_FLOAT32);
@@ -660,9 +650,9 @@ TEST(TestAudioResampler, ChannelChange_Discontinuity3)
// The resampler here is updated due to the rate change. This is because the
// in and out rate was the same so a pass through logic was used. By updating
- // the out rate to something different than the in rate, the resampler will
+ // the in rate to something different than the out rate, the resampler will
// start being used and discontinuity will exist.
- dr.UpdateOutRate(out_rate + 400);
+ dr.UpdateInRate(in_rate - 400);
dr.AppendInput(inSegment);
AudioSegment s2 = dr.Resample(out_frames, &hasUnderrun);
EXPECT_FALSE(hasUnderrun);
diff --git a/dom/media/driftcontrol/gtest/TestDriftController.cpp b/dom/media/driftcontrol/gtest/TestDriftController.cpp
index 33486f945f..132577e44a 100644
--- a/dom/media/driftcontrol/gtest/TestDriftController.cpp
+++ b/dom/media/driftcontrol/gtest/TestDriftController.cpp
@@ -18,50 +18,50 @@ TEST(TestDriftController, Basic)
constexpr uint32_t bufferedHigh = 7 * 480;
DriftController c(48000, 48000, media::TimeUnit::FromSeconds(0.05));
- EXPECT_EQ(c.GetCorrectedTargetRate(), 48000U);
+ EXPECT_EQ(c.GetCorrectedSourceRate(), 48000U);
// The adjustment interval is 1s.
const auto oneSec = media::TimeUnit(48000, 48000);
c.UpdateClock(oneSec, oneSec, buffered, 0);
- EXPECT_EQ(c.GetCorrectedTargetRate(), 48000u);
+ EXPECT_EQ(c.GetCorrectedSourceRate(), 48000u);
c.UpdateClock(oneSec, oneSec, bufferedLow, 0);
- EXPECT_EQ(c.GetCorrectedTargetRate(), 48048u);
+ EXPECT_EQ(c.GetCorrectedSourceRate(), 47952u);
c.UpdateClock(oneSec, oneSec, bufferedHigh, 0);
- EXPECT_EQ(c.GetCorrectedTargetRate(), 48000u);
+ EXPECT_EQ(c.GetCorrectedSourceRate(), 48000u);
c.UpdateClock(oneSec, oneSec, bufferedHigh, 0);
- EXPECT_EQ(c.GetCorrectedTargetRate(), 47952u);
+ EXPECT_EQ(c.GetCorrectedSourceRate(), 48048u);
}
TEST(TestDriftController, BasicResampler)
{
// The buffer level is the only input to the controller logic.
- constexpr uint32_t buffered = 5 * 240;
- constexpr uint32_t bufferedLow = 3 * 240;
- constexpr uint32_t bufferedHigh = 7 * 240;
+ constexpr uint32_t buffered = 5 * 480;
+ constexpr uint32_t bufferedLow = 3 * 480;
+ constexpr uint32_t bufferedHigh = 7 * 480;
- DriftController c(24000, 48000, media::TimeUnit::FromSeconds(0.05));
+ DriftController c(48000, 24000, media::TimeUnit::FromSeconds(0.05));
// The adjustment interval is 1s.
const auto oneSec = media::TimeUnit(48000, 48000);
c.UpdateClock(oneSec, oneSec, buffered, 0);
- EXPECT_EQ(c.GetCorrectedTargetRate(), 48000u);
+ EXPECT_EQ(c.GetCorrectedSourceRate(), 48000u);
// low
c.UpdateClock(oneSec, oneSec, bufferedLow, 0);
- EXPECT_EQ(c.GetCorrectedTargetRate(), 48048u);
+ EXPECT_EQ(c.GetCorrectedSourceRate(), 47952u);
// high
c.UpdateClock(oneSec, oneSec, bufferedHigh, 0);
- EXPECT_EQ(c.GetCorrectedTargetRate(), 48000u);
+ EXPECT_EQ(c.GetCorrectedSourceRate(), 48000u);
// high
c.UpdateClock(oneSec, oneSec, bufferedHigh, 0);
- EXPECT_EQ(c.GetCorrectedTargetRate(), 47964u);
+ EXPECT_EQ(c.GetCorrectedSourceRate(), 48048u);
}
TEST(TestDriftController, BufferedInput)
@@ -72,56 +72,56 @@ TEST(TestDriftController, BufferedInput)
constexpr uint32_t bufferedHigh = 7 * 480;
DriftController c(48000, 48000, media::TimeUnit::FromSeconds(0.05));
- EXPECT_EQ(c.GetCorrectedTargetRate(), 48000u);
+ EXPECT_EQ(c.GetCorrectedSourceRate(), 48000u);
// The adjustment interval is 1s.
const auto oneSec = media::TimeUnit(48000, 48000);
c.UpdateClock(oneSec, oneSec, buffered, 0);
- EXPECT_EQ(c.GetCorrectedTargetRate(), 48000u);
+ EXPECT_EQ(c.GetCorrectedSourceRate(), 48000u);
// 0 buffered when updating correction
c.UpdateClock(oneSec, oneSec, 0, 0);
- EXPECT_EQ(c.GetCorrectedTargetRate(), 48048u);
+ EXPECT_EQ(c.GetCorrectedSourceRate(), 47952u);
c.UpdateClock(oneSec, oneSec, bufferedLow, 0);
- EXPECT_EQ(c.GetCorrectedTargetRate(), 48000u);
+ EXPECT_EQ(c.GetCorrectedSourceRate(), 48000u);
c.UpdateClock(oneSec, oneSec, buffered, 0);
- EXPECT_EQ(c.GetCorrectedTargetRate(), 48000u);
+ EXPECT_EQ(c.GetCorrectedSourceRate(), 48000u);
c.UpdateClock(oneSec, oneSec, bufferedHigh, 0);
- EXPECT_EQ(c.GetCorrectedTargetRate(), 47952u);
+ EXPECT_EQ(c.GetCorrectedSourceRate(), 48048u);
}
TEST(TestDriftController, BufferedInputWithResampling)
{
// The buffer level is the only input to the controller logic.
- constexpr uint32_t buffered = 5 * 240;
- constexpr uint32_t bufferedLow = 3 * 240;
- constexpr uint32_t bufferedHigh = 7 * 240;
+ constexpr uint32_t buffered = 5 * 480;
+ constexpr uint32_t bufferedLow = 3 * 480;
+ constexpr uint32_t bufferedHigh = 7 * 480;
- DriftController c(24000, 48000, media::TimeUnit::FromSeconds(0.05));
- EXPECT_EQ(c.GetCorrectedTargetRate(), 48000u);
+ DriftController c(48000, 24000, media::TimeUnit::FromSeconds(0.05));
+ EXPECT_EQ(c.GetCorrectedSourceRate(), 48000u);
// The adjustment interval is 1s.
const auto oneSec = media::TimeUnit(24000, 24000);
c.UpdateClock(oneSec, oneSec, buffered, 0);
- EXPECT_EQ(c.GetCorrectedTargetRate(), 48000u);
+ EXPECT_EQ(c.GetCorrectedSourceRate(), 48000u);
// 0 buffered when updating correction
c.UpdateClock(oneSec, oneSec, 0, 0);
- EXPECT_EQ(c.GetCorrectedTargetRate(), 48048u);
+ EXPECT_EQ(c.GetCorrectedSourceRate(), 47952u);
c.UpdateClock(oneSec, oneSec, bufferedLow, 0);
- EXPECT_EQ(c.GetCorrectedTargetRate(), 48000u);
+ EXPECT_EQ(c.GetCorrectedSourceRate(), 48000u);
c.UpdateClock(oneSec, oneSec, buffered, 0);
- EXPECT_EQ(c.GetCorrectedTargetRate(), 48000u);
+ EXPECT_EQ(c.GetCorrectedSourceRate(), 48000u);
c.UpdateClock(oneSec, oneSec, bufferedHigh, 0);
- EXPECT_EQ(c.GetCorrectedTargetRate(), 47952u);
+ EXPECT_EQ(c.GetCorrectedSourceRate(), 48048u);
}
TEST(TestDriftController, SmallError)
@@ -132,21 +132,21 @@ TEST(TestDriftController, SmallError)
constexpr uint32_t bufferedHigh = buffered + 48;
DriftController c(48000, 48000, media::TimeUnit::FromSeconds(0.05));
- EXPECT_EQ(c.GetCorrectedTargetRate(), 48000u);
+ EXPECT_EQ(c.GetCorrectedSourceRate(), 48000u);
// The adjustment interval is 1s.
const auto oneSec = media::TimeUnit(48000, 48000);
c.UpdateClock(oneSec, oneSec, buffered, 0);
- EXPECT_EQ(c.GetCorrectedTargetRate(), 48000u);
+ EXPECT_EQ(c.GetCorrectedSourceRate(), 48000u);
c.UpdateClock(oneSec, oneSec, bufferedLow, 0);
- EXPECT_EQ(c.GetCorrectedTargetRate(), 48000u);
+ EXPECT_EQ(c.GetCorrectedSourceRate(), 48000u);
c.UpdateClock(oneSec, oneSec, bufferedHigh, 0);
- EXPECT_EQ(c.GetCorrectedTargetRate(), 48000u);
+ EXPECT_EQ(c.GetCorrectedSourceRate(), 48000u);
c.UpdateClock(oneSec, oneSec, bufferedHigh, 0);
- EXPECT_EQ(c.GetCorrectedTargetRate(), 48000u);
+ EXPECT_EQ(c.GetCorrectedSourceRate(), 48000u);
}
TEST(TestDriftController, SmallBufferedFrames)
@@ -158,11 +158,34 @@ TEST(TestDriftController, SmallBufferedFrames)
media::TimeUnit oneSec = media::TimeUnit::FromSeconds(1);
media::TimeUnit hundredMillis = oneSec / 10;
- EXPECT_EQ(c.GetCorrectedTargetRate(), 48000U);
+ EXPECT_EQ(c.GetCorrectedSourceRate(), 48000U);
for (uint32_t i = 0; i < 9; ++i) {
c.UpdateClock(hundredMillis, hundredMillis, bufferedLow, 0);
}
- EXPECT_EQ(c.GetCorrectedTargetRate(), 48000U);
+ EXPECT_EQ(c.GetCorrectedSourceRate(), 48000U);
c.UpdateClock(hundredMillis, hundredMillis, bufferedLow, 0);
- EXPECT_EQ(c.GetCorrectedTargetRate(), 48048U);
+ EXPECT_EQ(c.GetCorrectedSourceRate(), 47952U);
+}
+
+TEST(TestDriftController, VerySmallBufferedFrames)
+{
+ // The buffer level is the only input to the controller logic.
+ uint32_t bufferedLow = 1;
+ uint32_t nominalRate = 48000;
+
+ DriftController c(nominalRate, nominalRate, media::TimeUnit::FromSeconds(1));
+ media::TimeUnit oneSec = media::TimeUnit::FromSeconds(1);
+ media::TimeUnit sourceDuration(1, nominalRate);
+
+ EXPECT_EQ(c.GetCorrectedSourceRate(), nominalRate);
+ uint32_t previousCorrected = nominalRate;
+ // Steps are limited to nominalRate/1000.
+ // Perform 1001 steps to check the corrected rate does not underflow zero.
+ for (uint32_t i = 0; i < 1001; ++i) {
+ c.UpdateClock(sourceDuration, oneSec, bufferedLow, 0);
+ uint32_t correctedRate = c.GetCorrectedSourceRate();
+ EXPECT_LE(correctedRate, previousCorrected) << "for i=" << i;
+ EXPECT_GT(correctedRate, 0u) << "for i=" << i;
+ previousCorrected = correctedRate;
+ }
}
diff --git a/dom/media/driftcontrol/gtest/TestDynamicResampler.cpp b/dom/media/driftcontrol/gtest/TestDynamicResampler.cpp
index fb8ac52ae4..539dfbfbea 100644
--- a/dom/media/driftcontrol/gtest/TestDynamicResampler.cpp
+++ b/dom/media/driftcontrol/gtest/TestDynamicResampler.cpp
@@ -19,7 +19,7 @@ TEST(TestDynamicResampler, SameRates_Float1)
DynamicResampler dr(in_rate, out_rate);
dr.SetSampleFormat(AUDIO_FORMAT_FLOAT32);
- EXPECT_EQ(dr.GetOutRate(), out_rate);
+ EXPECT_EQ(dr.GetInRate(), in_rate);
EXPECT_EQ(dr.GetChannels(), channels);
// float in_ch1[] = {.1, .2, .3, .4, .5, .6, .7, .8, .9, 1.0};
@@ -76,7 +76,7 @@ TEST(TestDynamicResampler, SameRates_Short1)
DynamicResampler dr(in_rate, out_rate);
dr.SetSampleFormat(AUDIO_FORMAT_S16);
- EXPECT_EQ(dr.GetOutRate(), out_rate);
+ EXPECT_EQ(dr.GetInRate(), in_rate);
EXPECT_EQ(dr.GetChannels(), channels);
short in_ch1[] = {1, 2, 3};
@@ -298,9 +298,9 @@ TEST(TestDynamicResampler, UpdateOutRate_Float)
uint32_t pre_buffer = 20;
- DynamicResampler dr(in_rate, out_rate, media::TimeUnit(pre_buffer, in_rate));
+ DynamicResampler dr(in_rate, out_rate);
dr.SetSampleFormat(AUDIO_FORMAT_FLOAT32);
- EXPECT_EQ(dr.GetOutRate(), out_rate);
+ EXPECT_EQ(dr.GetInRate(), in_rate);
EXPECT_EQ(dr.GetChannels(), channels);
float in_ch1[10] = {};
@@ -329,10 +329,10 @@ TEST(TestDynamicResampler, UpdateOutRate_Float)
EXPECT_FLOAT_EQ(out_ch2[i], 0.0);
}
- // Update out rate
- out_rate = 44100;
- dr.UpdateResampler(out_rate, channels);
- EXPECT_EQ(dr.GetOutRate(), out_rate);
+ // Update in rate
+ in_rate = 26122;
+ dr.UpdateResampler(in_rate, channels);
+ EXPECT_EQ(dr.GetInRate(), in_rate);
EXPECT_EQ(dr.GetChannels(), channels);
out_frames = in_frames * out_rate / in_rate;
EXPECT_EQ(out_frames, 18u);
@@ -354,9 +354,9 @@ TEST(TestDynamicResampler, UpdateOutRate_Short)
uint32_t pre_buffer = 20;
- DynamicResampler dr(in_rate, out_rate, media::TimeUnit(pre_buffer, in_rate));
+ DynamicResampler dr(in_rate, out_rate);
dr.SetSampleFormat(AUDIO_FORMAT_S16);
- EXPECT_EQ(dr.GetOutRate(), out_rate);
+ EXPECT_EQ(dr.GetInRate(), in_rate);
EXPECT_EQ(dr.GetChannels(), channels);
short in_ch1[10] = {};
@@ -385,10 +385,10 @@ TEST(TestDynamicResampler, UpdateOutRate_Short)
EXPECT_EQ(out_ch2[i], 0.0);
}
- // Update out rate
- out_rate = 44100;
- dr.UpdateResampler(out_rate, channels);
- EXPECT_EQ(dr.GetOutRate(), out_rate);
+ // Update in rate
+ in_rate = 26122;
+ dr.UpdateResampler(in_rate, channels);
+ EXPECT_EQ(dr.GetInRate(), in_rate);
EXPECT_EQ(dr.GetChannels(), channels);
out_frames = in_frames * out_rate / in_rate;
EXPECT_EQ(out_frames, 18u);
@@ -400,16 +400,15 @@ TEST(TestDynamicResampler, UpdateOutRate_Short)
EXPECT_FALSE(hasUnderrun);
}
-TEST(TestDynamicResampler, BigRangeOutRates_Float)
+TEST(TestDynamicResampler, BigRangeInRates_Float)
{
uint32_t in_frames = 10;
uint32_t out_frames = 10;
uint32_t channels = 2;
uint32_t in_rate = 44100;
uint32_t out_rate = 44100;
- uint32_t pre_buffer = 20;
- DynamicResampler dr(in_rate, out_rate, media::TimeUnit(pre_buffer, in_rate));
+ DynamicResampler dr(in_rate, out_rate);
dr.SetSampleFormat(AUDIO_FORMAT_FLOAT32);
const uint32_t in_capacity = 40;
@@ -427,10 +426,14 @@ TEST(TestDynamicResampler, BigRangeOutRates_Float)
float out_ch1[out_capacity] = {};
float out_ch2[out_capacity] = {};
- for (uint32_t rate = 10000; rate < 90000; ++rate) {
- out_rate = rate;
- dr.UpdateResampler(out_rate, channels);
- EXPECT_EQ(dr.GetOutRate(), out_rate);
+ // Downsampling at a high enough ratio happens to have enough excess
+ // in_frames from rounding in the out_frames calculation to cover the
+ // skipped input latency when switching from zero-latency 44100->44100 to a
+ // non-1:1 ratio.
+ for (uint32_t rate = 100000; rate >= 10000; rate -= 2) {
+ in_rate = rate;
+ dr.UpdateResampler(in_rate, channels);
+ EXPECT_EQ(dr.GetInRate(), in_rate);
EXPECT_EQ(dr.GetChannels(), channels);
in_frames = 20; // more than we need
out_frames = in_frames * out_rate / in_rate;
@@ -444,16 +447,15 @@ TEST(TestDynamicResampler, BigRangeOutRates_Float)
}
}
-TEST(TestDynamicResampler, BigRangeOutRates_Short)
+TEST(TestDynamicResampler, BigRangeInRates_Short)
{
uint32_t in_frames = 10;
uint32_t out_frames = 10;
uint32_t channels = 2;
uint32_t in_rate = 44100;
uint32_t out_rate = 44100;
- uint32_t pre_buffer = 20;
- DynamicResampler dr(in_rate, out_rate, media::TimeUnit(pre_buffer, in_rate));
+ DynamicResampler dr(in_rate, out_rate);
dr.SetSampleFormat(AUDIO_FORMAT_S16);
const uint32_t in_capacity = 40;
@@ -471,9 +473,9 @@ TEST(TestDynamicResampler, BigRangeOutRates_Short)
short out_ch1[out_capacity] = {};
short out_ch2[out_capacity] = {};
- for (uint32_t rate = 10000; rate < 90000; ++rate) {
- out_rate = rate;
- dr.UpdateResampler(out_rate, channels);
+ for (uint32_t rate = 100000; rate >= 10000; rate -= 2) {
+ in_rate = rate;
+ dr.UpdateResampler(in_rate, channels);
in_frames = 20; // more than we need
out_frames = in_frames * out_rate / in_rate;
for (uint32_t y = 0; y < 2; ++y) {
@@ -517,8 +519,8 @@ TEST(TestDynamicResampler, UpdateChannels_Float)
EXPECT_FALSE(hasUnderrun);
// Add 3rd channel
- dr.UpdateResampler(out_rate, 3);
- EXPECT_EQ(dr.GetOutRate(), out_rate);
+ dr.UpdateResampler(in_rate, 3);
+ EXPECT_EQ(dr.GetInRate(), in_rate);
EXPECT_EQ(dr.GetChannels(), 3u);
float in_ch3[10] = {};
@@ -546,8 +548,8 @@ TEST(TestDynamicResampler, UpdateChannels_Float)
in_buffer[3] = in_ch4;
float out_ch4[10] = {};
- dr.UpdateResampler(out_rate, 4);
- EXPECT_EQ(dr.GetOutRate(), out_rate);
+ dr.UpdateResampler(in_rate, 4);
+ EXPECT_EQ(dr.GetInRate(), in_rate);
EXPECT_EQ(dr.GetChannels(), 4u);
dr.AppendInput(in_buffer, in_frames);
@@ -592,8 +594,8 @@ TEST(TestDynamicResampler, UpdateChannels_Short)
EXPECT_FALSE(hasUnderrun);
// Add 3rd channel
- dr.UpdateResampler(out_rate, 3);
- EXPECT_EQ(dr.GetOutRate(), out_rate);
+ dr.UpdateResampler(in_rate, 3);
+ EXPECT_EQ(dr.GetInRate(), in_rate);
EXPECT_EQ(dr.GetChannels(), 3u);
short in_ch3[10] = {};
@@ -622,8 +624,8 @@ TEST(TestDynamicResampler, UpdateChannels_Short)
in_buffer[3] = in_ch4;
short out_ch4[10] = {};
- dr.UpdateResampler(out_rate, 4);
- EXPECT_EQ(dr.GetOutRate(), out_rate);
+ dr.UpdateResampler(in_rate, 4);
+ EXPECT_EQ(dr.GetInRate(), in_rate);
EXPECT_EQ(dr.GetChannels(), 4u);
dr.AppendInput(in_buffer, in_frames);
@@ -647,7 +649,7 @@ TEST(TestDynamicResampler, Underrun)
DynamicResampler dr(in_rate, out_rate);
dr.SetSampleFormat(AUDIO_FORMAT_FLOAT32);
- EXPECT_EQ(dr.GetOutRate(), out_rate);
+ EXPECT_EQ(dr.GetInRate(), in_rate);
EXPECT_EQ(dr.GetChannels(), channels);
float in_ch1[in_frames] = {};
@@ -689,7 +691,7 @@ TEST(TestDynamicResampler, Underrun)
}
// Now try with resampling.
- dr.UpdateResampler(out_rate / 2, channels);
+ dr.UpdateResampler(in_rate * 2, channels);
dr.AppendInput(in_buffer, in_frames);
hasUnderrun = dr.Resample(out_ch1, out_frames, 0);
EXPECT_TRUE(hasUnderrun);
diff --git a/dom/media/driftcontrol/plot.py b/dom/media/driftcontrol/plot.py
index d55c0f7de0..c3685ead7c 100755
--- a/dom/media/driftcontrol/plot.py
+++ b/dom/media/driftcontrol/plot.py
@@ -86,23 +86,23 @@ MOZ_LOG_FILE=/tmp/driftcontrol.csv \
[d + h for (d, h) in zip(desired, hysteresisthreshold)],
alpha=0.2,
color="goldenrod",
- legend_label="Hysteresis Threshold (won't correct out rate within area)",
+ legend_label="Hysteresis Threshold (won't correct in rate within area)",
)
fig2 = figure(x_range=fig1.x_range)
fig2.line(t, inrate, color="hotpink", legend_label="Nominal in sample rate")
fig2.line(t, outrate, color="firebrick", legend_label="Nominal out sample rate")
fig2.line(
- t, corrected, color="dodgerblue", legend_label="Corrected out sample rate"
+ t, corrected, color="dodgerblue", legend_label="Corrected in sample rate"
)
fig2.line(
t,
hysteresiscorrected,
color="seagreen",
- legend_label="Hysteresis-corrected out sample rate",
+ legend_label="Hysteresis-corrected in sample rate",
)
fig2.line(
- t, configured, color="goldenrod", legend_label="Configured out sample rate"
+ t, configured, color="goldenrod", legend_label="Configured in sample rate"
)
fig3 = figure(x_range=fig1.x_range)
diff --git a/dom/media/encoder/VP8TrackEncoder.cpp b/dom/media/encoder/VP8TrackEncoder.cpp
index 0c7f3de1f4..36680b6552 100644
--- a/dom/media/encoder/VP8TrackEncoder.cpp
+++ b/dom/media/encoder/VP8TrackEncoder.cpp
@@ -9,7 +9,7 @@
#include <vpx/vpx_encoder.h>
#include "DriftCompensation.h"
-#include "ImageToI420.h"
+#include "ImageConversion.h"
#include "mozilla/gfx/2D.h"
#include "prsystem.h"
#include "VideoSegment.h"
diff --git a/dom/media/gmp/CDMStorageIdProvider.cpp b/dom/media/gmp/CDMStorageIdProvider.cpp
index 52255879b3..9af4580d9e 100644
--- a/dom/media/gmp/CDMStorageIdProvider.cpp
+++ b/dom/media/gmp/CDMStorageIdProvider.cpp
@@ -6,7 +6,7 @@
#include "CDMStorageIdProvider.h"
#include "GMPLog.h"
#include "nsCOMPtr.h"
-#include "nsComponentManagerUtils.h"
+#include "mozilla/IntegerPrintfMacros.h"
#include "nsICryptoHash.h"
#ifdef SUPPORT_STORAGE_ID
diff --git a/dom/media/gmp/ChromiumCDMChild.h b/dom/media/gmp/ChromiumCDMChild.h
index 1bf153a5d5..fae54bbb5c 100644
--- a/dom/media/gmp/ChromiumCDMChild.h
+++ b/dom/media/gmp/ChromiumCDMChild.h
@@ -125,7 +125,7 @@ class ChromiumCDMChild : public PChromiumCDMChild, public cdm::Host_10 {
GMPContentChild* mPlugin = nullptr;
cdm::ContentDecryptionModule_10* mCDM = nullptr;
- typedef SimpleMap<uint64_t> DurationMap;
+ typedef SimpleMap<int64_t, uint64_t, ThreadSafePolicy> DurationMap;
DurationMap mFrameDurations;
nsTArray<uint32_t> mLoadSessionPromiseIds;
diff --git a/dom/media/gmp/GMPVideoDecoderChild.cpp b/dom/media/gmp/GMPVideoDecoderChild.cpp
index 0f605cca9b..f60952ee51 100644
--- a/dom/media/gmp/GMPVideoDecoderChild.cpp
+++ b/dom/media/gmp/GMPVideoDecoderChild.cpp
@@ -36,12 +36,17 @@ void GMPVideoDecoderChild::Init(GMPVideoDecoder* aDecoder) {
GMPVideoHostImpl& GMPVideoDecoderChild::Host() { return mVideoHost; }
void GMPVideoDecoderChild::Decoded(GMPVideoi420Frame* aDecodedFrame) {
- MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
-
if (!aDecodedFrame) {
MOZ_CRASH("Not given a decoded frame!");
}
+ if (NS_WARN_IF(!mPlugin)) {
+ aDecodedFrame->Destroy();
+ return;
+ }
+
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
auto df = static_cast<GMPVideoi420FrameImpl*>(aDecodedFrame);
GMPVideoi420FrameData frameData;
@@ -53,36 +58,60 @@ void GMPVideoDecoderChild::Decoded(GMPVideoi420Frame* aDecodedFrame) {
void GMPVideoDecoderChild::ReceivedDecodedReferenceFrame(
const uint64_t aPictureId) {
+ if (NS_WARN_IF(!mPlugin)) {
+ return;
+ }
+
MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
SendReceivedDecodedReferenceFrame(aPictureId);
}
void GMPVideoDecoderChild::ReceivedDecodedFrame(const uint64_t aPictureId) {
+ if (NS_WARN_IF(!mPlugin)) {
+ return;
+ }
+
MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
SendReceivedDecodedFrame(aPictureId);
}
void GMPVideoDecoderChild::InputDataExhausted() {
+ if (NS_WARN_IF(!mPlugin)) {
+ return;
+ }
+
MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
SendInputDataExhausted();
}
void GMPVideoDecoderChild::DrainComplete() {
+ if (NS_WARN_IF(!mPlugin)) {
+ return;
+ }
+
MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
SendDrainComplete();
}
void GMPVideoDecoderChild::ResetComplete() {
+ if (NS_WARN_IF(!mPlugin)) {
+ return;
+ }
+
MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
SendResetComplete();
}
void GMPVideoDecoderChild::Error(GMPErr aError) {
+ if (NS_WARN_IF(!mPlugin)) {
+ return;
+ }
+
MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
SendError(aError);
@@ -121,9 +150,9 @@ mozilla::ipc::IPCResult GMPVideoDecoderChild::RecvDecode(
mozilla::ipc::IPCResult GMPVideoDecoderChild::RecvChildShmemForPool(
Shmem&& aFrameBuffer) {
- if (aFrameBuffer.IsWritable()) {
- mVideoHost.SharedMemMgr()->MgrDeallocShmem(GMPSharedMem::kGMPFrameData,
- aFrameBuffer);
+ GMPSharedMemManager* memMgr = mVideoHost.SharedMemMgr();
+ if (memMgr && aFrameBuffer.IsWritable()) {
+ memMgr->MgrDeallocShmem(GMPSharedMem::kGMPFrameData, aFrameBuffer);
}
return IPC_OK();
}
@@ -153,6 +182,7 @@ mozilla::ipc::IPCResult GMPVideoDecoderChild::RecvDrain() {
}
mozilla::ipc::IPCResult GMPVideoDecoderChild::RecvDecodingComplete() {
+ MOZ_ASSERT(mPlugin);
MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
if (mNeedShmemIntrCount) {
@@ -163,6 +193,13 @@ mozilla::ipc::IPCResult GMPVideoDecoderChild::RecvDecodingComplete() {
mPendingDecodeComplete = true;
return IPC_OK();
}
+
+ // This will call ActorDestroy.
+ Unused << Send__delete__(this);
+ return IPC_OK();
+}
+
+void GMPVideoDecoderChild::ActorDestroy(ActorDestroyReason why) {
if (mVideoDecoder) {
// Ignore any return code. It is OK for this to fail without killing the
// process.
@@ -173,13 +210,13 @@ mozilla::ipc::IPCResult GMPVideoDecoderChild::RecvDecodingComplete() {
mVideoHost.DoneWithAPI();
mPlugin = nullptr;
-
- Unused << Send__delete__(this);
-
- return IPC_OK();
}
bool GMPVideoDecoderChild::Alloc(size_t aSize, Shmem* aMem) {
+ if (NS_WARN_IF(!mPlugin)) {
+ return false;
+ }
+
MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
bool rv;
diff --git a/dom/media/gmp/GMPVideoDecoderChild.h b/dom/media/gmp/GMPVideoDecoderChild.h
index 3c74e5f02c..527d6cad44 100644
--- a/dom/media/gmp/GMPVideoDecoderChild.h
+++ b/dom/media/gmp/GMPVideoDecoderChild.h
@@ -59,6 +59,7 @@ class GMPVideoDecoderChild : public PGMPVideoDecoderChild,
mozilla::ipc::IPCResult RecvReset();
mozilla::ipc::IPCResult RecvDrain();
mozilla::ipc::IPCResult RecvDecodingComplete();
+ void ActorDestroy(ActorDestroyReason why) override;
GMPContentChild* mPlugin;
GMPVideoDecoder* mVideoDecoder;
diff --git a/dom/media/gmp/GMPVideoEncoderChild.cpp b/dom/media/gmp/GMPVideoEncoderChild.cpp
index 19a96b5efe..01a913f920 100644
--- a/dom/media/gmp/GMPVideoEncoderChild.cpp
+++ b/dom/media/gmp/GMPVideoEncoderChild.cpp
@@ -38,6 +38,11 @@ GMPVideoHostImpl& GMPVideoEncoderChild::Host() { return mVideoHost; }
void GMPVideoEncoderChild::Encoded(GMPVideoEncodedFrame* aEncodedFrame,
const uint8_t* aCodecSpecificInfo,
uint32_t aCodecSpecificInfoLength) {
+ if (NS_WARN_IF(!mPlugin)) {
+ aEncodedFrame->Destroy();
+ return;
+ }
+
MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
auto ef = static_cast<GMPVideoEncodedFrameImpl*>(aEncodedFrame);
@@ -53,6 +58,10 @@ void GMPVideoEncoderChild::Encoded(GMPVideoEncodedFrame* aEncodedFrame,
}
void GMPVideoEncoderChild::Error(GMPErr aError) {
+ if (NS_WARN_IF(!mPlugin)) {
+ return;
+ }
+
MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
SendError(aError);
@@ -95,9 +104,9 @@ mozilla::ipc::IPCResult GMPVideoEncoderChild::RecvEncode(
mozilla::ipc::IPCResult GMPVideoEncoderChild::RecvChildShmemForPool(
Shmem&& aEncodedBuffer) {
- if (aEncodedBuffer.IsWritable()) {
- mVideoHost.SharedMemMgr()->MgrDeallocShmem(GMPSharedMem::kGMPEncodedData,
- aEncodedBuffer);
+ GMPSharedMemManager* memMgr = mVideoHost.SharedMemMgr();
+ if (memMgr && aEncodedBuffer.IsWritable()) {
+ memMgr->MgrDeallocShmem(GMPSharedMem::kGMPEncodedData, aEncodedBuffer);
}
return IPC_OK();
}
@@ -142,6 +151,7 @@ mozilla::ipc::IPCResult GMPVideoEncoderChild::RecvSetPeriodicKeyFrames(
}
mozilla::ipc::IPCResult GMPVideoEncoderChild::RecvEncodingComplete() {
+ MOZ_ASSERT(mPlugin);
MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
if (mNeedShmemIntrCount) {
@@ -153,26 +163,29 @@ mozilla::ipc::IPCResult GMPVideoEncoderChild::RecvEncodingComplete() {
return IPC_OK();
}
- if (!mVideoEncoder) {
- // There is not much to clean up anymore.
- Unused << Send__delete__(this);
- return IPC_OK();
- }
+ // This will call ActorDestroy.
+ Unused << Send__delete__(this);
+ return IPC_OK();
+}
- // Ignore any return code. It is OK for this to fail without killing the
- // process.
- mVideoEncoder->EncodingComplete();
+void GMPVideoEncoderChild::ActorDestroy(ActorDestroyReason why) {
+ if (mVideoEncoder) {
+ // Ignore any return code. It is OK for this to fail without killing the
+ // process.
+ mVideoEncoder->EncodingComplete();
+ mVideoEncoder = nullptr;
+ }
mVideoHost.DoneWithAPI();
mPlugin = nullptr;
-
- Unused << Send__delete__(this);
-
- return IPC_OK();
}
bool GMPVideoEncoderChild::Alloc(size_t aSize, Shmem* aMem) {
+ if (NS_WARN_IF(!mPlugin)) {
+ return false;
+ }
+
MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
bool rv;
diff --git a/dom/media/gmp/GMPVideoEncoderChild.h b/dom/media/gmp/GMPVideoEncoderChild.h
index dd3c0fdf37..344a55b388 100644
--- a/dom/media/gmp/GMPVideoEncoderChild.h
+++ b/dom/media/gmp/GMPVideoEncoderChild.h
@@ -59,6 +59,7 @@ class GMPVideoEncoderChild : public PGMPVideoEncoderChild,
const uint32_t& aFrameRate);
mozilla::ipc::IPCResult RecvSetPeriodicKeyFrames(const bool& aEnable);
mozilla::ipc::IPCResult RecvEncodingComplete();
+ void ActorDestroy(ActorDestroyReason why) override;
GMPContentChild* mPlugin;
GMPVideoEncoder* mVideoEncoder;
diff --git a/dom/media/gmp/mozIGeckoMediaPluginService.idl b/dom/media/gmp/mozIGeckoMediaPluginService.idl
index 000cfef2f5..a4e3253cba 100644
--- a/dom/media/gmp/mozIGeckoMediaPluginService.idl
+++ b/dom/media/gmp/mozIGeckoMediaPluginService.idl
@@ -57,7 +57,7 @@ native GetGMPVideoEncoderCallback(mozilla::UniquePtr<GetGMPVideoEncoderCallback>
native GetNodeIdCallback(mozilla::UniquePtr<GetNodeIdCallback>&&);
native GMPCrashHelperPtr(mozilla::GMPCrashHelper*);
-[scriptable, uuid(44d362ae-937a-4803-bee6-f2512a0149d1)]
+[scriptable, builtinclass, uuid(44d362ae-937a-4803-bee6-f2512a0149d1)]
interface mozIGeckoMediaPluginService : nsISupports
{
diff --git a/dom/media/gtest/AudioVerifier.h b/dom/media/gtest/AudioVerifier.h
index ba67f6e489..2ff8ed9269 100644
--- a/dom/media/gtest/AudioVerifier.h
+++ b/dom/media/gtest/AudioVerifier.h
@@ -99,7 +99,7 @@ class AudioVerifier {
void CountZeroCrossing(Sample aCurrentSample) {
if (mPrevious > 0 && aCurrentSample <= 0) {
if (mZeroCrossCount++) {
- MOZ_ASSERT(mZeroCrossCount > 1);
+ MOZ_RELEASE_ASSERT(mZeroCrossCount > 1);
mSumPeriodInSamples += mTotalFramesSoFar - mLastZeroCrossPosition;
}
mLastZeroCrossPosition = mTotalFramesSoFar;
@@ -120,7 +120,7 @@ class AudioVerifier {
return;
}
- MOZ_ASSERT(mCurrentDiscontinuityFrameCount == 0);
+ MOZ_RELEASE_ASSERT(mCurrentDiscontinuityFrameCount == 0);
if (!discontinuity) {
return;
}
diff --git a/dom/media/gtest/GMPTestMonitor.h b/dom/media/gtest/GMPTestMonitor.h
index 27477b6a42..9f4e8f0a84 100644
--- a/dom/media/gtest/GMPTestMonitor.h
+++ b/dom/media/gtest/GMPTestMonitor.h
@@ -15,7 +15,7 @@ class GMPTestMonitor {
GMPTestMonitor() : mFinished(false) {}
void AwaitFinished() {
- MOZ_ASSERT(NS_IsMainThread());
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
mozilla::SpinEventLoopUntil("GMPTestMonitor::AwaitFinished"_ns,
[&]() { return mFinished; });
mFinished = false;
@@ -23,7 +23,7 @@ class GMPTestMonitor {
private:
void MarkFinished() {
- MOZ_ASSERT(NS_IsMainThread());
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
mFinished = true;
}
diff --git a/dom/media/gtest/MockCubeb.cpp b/dom/media/gtest/MockCubeb.cpp
index ae3a676ac8..9d61ccf4b5 100644
--- a/dom/media/gtest/MockCubeb.cpp
+++ b/dom/media/gtest/MockCubeb.cpp
@@ -168,79 +168,93 @@ MockCubebStream::MockCubebStream(
mAudioVerifier(aInputStreamParams ? aInputStreamParams->rate
: aOutputStreamParams->rate,
100 /* aFrequency */) {
- MOZ_ASSERT(mAudioGenerator.ChannelCount() <= MAX_INPUT_CHANNELS,
- "mInputBuffer has no enough space to hold generated data");
- MOZ_ASSERT_IF(mFrozenStart, mRunningMode == RunningMode::Automatic);
+ MOZ_RELEASE_ASSERT(mAudioGenerator.ChannelCount() <= MAX_INPUT_CHANNELS,
+ "mInputBuffer has no enough space to hold generated data");
+ if (mFrozenStart) {
+ MOZ_RELEASE_ASSERT(mRunningMode == RunningMode::Automatic);
+ }
if (aInputStreamParams) {
mInputParams = *aInputStreamParams;
}
if (aOutputStreamParams) {
mOutputParams = *aOutputStreamParams;
- MOZ_ASSERT(SampleRate() == mOutputParams.rate);
+ MOZ_RELEASE_ASSERT(SampleRate() == mOutputParams.rate);
}
}
MockCubebStream::~MockCubebStream() = default;
int MockCubebStream::Start() {
- NotifyState(CUBEB_STATE_STARTED);
- mStreamStop = false;
- if (mFrozenStart) {
- // We need to grab mFrozenStartMonitor before returning to avoid races in
- // the calling code -- it controls when to mFrozenStartMonitor.Notify().
- // TempData helps facilitate this by holding what's needed to block the
- // calling thread until the background thread has grabbed the lock.
- struct TempData {
- NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TempData)
- static_assert(HasThreadSafeRefCnt::value,
- "Silence a -Wunused-local-typedef warning");
- Monitor mMonitor{"MockCubebStream::Start::TempData::mMonitor"};
- bool mFinished = false;
-
- private:
- ~TempData() = default;
- };
- auto temp = MakeRefPtr<TempData>();
- MonitorAutoLock lock(temp->mMonitor);
- NS_DispatchBackgroundTask(NS_NewRunnableFunction(
- "MockCubebStream::WaitForThawBeforeStart",
- [temp, this, self = RefPtr<SmartMockCubebStream>(mSelf)]() mutable {
- MonitorAutoLock lock(mFrozenStartMonitor);
- {
- // Unblock MockCubebStream::Start now that we have locked the frozen
- // start monitor.
- MonitorAutoLock tempLock(temp->mMonitor);
- temp->mFinished = true;
- temp->mMonitor.Notify();
- temp = nullptr;
- }
- while (mFrozenStart) {
- mFrozenStartMonitor.Wait();
- }
- if (!mStreamStop) {
+ {
+ MutexAutoLock l(mMutex);
+ NotifyState(CUBEB_STATE_STARTED);
+ }
+ {
+ MonitorAutoLock lock(mFrozenStartMonitor);
+ if (mFrozenStart) {
+ // We need to grab mFrozenStartMonitor before returning to avoid races in
+ // the calling code -- it controls when to mFrozenStartMonitor.Notify().
+ // TempData helps facilitate this by holding what's needed to block the
+ // calling thread until the background thread has grabbed the lock.
+ struct TempData {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TempData)
+ static_assert(HasThreadSafeRefCnt::value,
+ "Silence a -Wunused-local-typedef warning");
+ Monitor mMonitor{"MockCubebStream::Start::TempData::mMonitor"};
+ bool mFinished = false;
+
+ private:
+ ~TempData() = default;
+ };
+ auto temp = MakeRefPtr<TempData>();
+ MonitorAutoLock lock(temp->mMonitor);
+ NS_DispatchBackgroundTask(NS_NewRunnableFunction(
+ "MockCubebStream::WaitForThawBeforeStart",
+ [temp, this, self = RefPtr<SmartMockCubebStream>(mSelf)]() mutable {
+ {
+ // Unblock MockCubebStream::Start now that we have locked the
+ // frozen start monitor.
+ MonitorAutoLock tempLock(temp->mMonitor);
+ temp->mFinished = true;
+ temp->mMonitor.Notify();
+ temp = nullptr;
+ }
+ {
+ MonitorAutoLock lock(mFrozenStartMonitor);
+ while (mFrozenStart) {
+ mFrozenStartMonitor.Wait();
+ }
+ }
+ if (MutexAutoLock l(mMutex);
+ !mState || *mState != CUBEB_STATE_STARTED) {
+ return;
+ }
MockCubeb::AsMock(context)->StartStream(mSelf);
- }
- }));
- while (!temp->mFinished) {
- temp->mMonitor.Wait();
+ }));
+ while (!temp->mFinished) {
+ temp->mMonitor.Wait();
+ }
+ return CUBEB_OK;
}
- return CUBEB_OK;
}
MockCubeb::AsMock(context)->StartStream(this);
return CUBEB_OK;
}
int MockCubebStream::Stop() {
+ MockCubeb::AsMock(context)->StopStream(this);
+ MutexAutoLock l(mMutex);
mOutputVerificationEvent.Notify(std::make_tuple(
mAudioVerifier.PreSilenceSamples(), mAudioVerifier.EstimatedFreq(),
mAudioVerifier.CountDiscontinuities()));
- MockCubeb::AsMock(context)->StopStream(this);
- mStreamStop = true;
NotifyState(CUBEB_STATE_STOPPED);
return CUBEB_OK;
}
-uint64_t MockCubebStream::Position() { return mPosition; }
+uint64_t MockCubebStream::Position() {
+ MutexAutoLock l(mMutex);
+ return mPosition;
+}
void MockCubebStream::Destroy() {
// Stop() even if cubeb_stream_stop() has already been called, as with
@@ -248,11 +262,15 @@ void MockCubebStream::Destroy() {
// This provides an extra STOPPED state callback as with audioipc.
// It also ensures that this stream is removed from MockCubeb::mLiveStreams.
Stop();
- mDestroyed = true;
+ {
+ MutexAutoLock l(mMutex);
+ mDestroyed = true;
+ }
MockCubeb::AsMock(context)->StreamDestroy(this);
}
int MockCubebStream::SetName(char const* aName) {
+ MutexAutoLock l(mMutex);
mName = aName;
mNameSetEvent.Notify(mName);
return CUBEB_OK;
@@ -260,6 +278,7 @@ int MockCubebStream::SetName(char const* aName) {
int MockCubebStream::RegisterDeviceChangedCallback(
cubeb_device_changed_callback aDeviceChangedCallback) {
+ MutexAutoLock l(mMutex);
if (mDeviceChangedCallback && aDeviceChangedCallback) {
return CUBEB_ERROR_INVALID_PARAMETER;
}
@@ -267,14 +286,41 @@ int MockCubebStream::RegisterDeviceChangedCallback(
return CUBEB_OK;
}
+int MockCubebStream::SetInputProcessingParams(
+ cubeb_input_processing_params aParams) {
+ MockCubeb* mock = MockCubeb::AsMock(context);
+ auto res = mock->SupportedInputProcessingParams();
+ if (res.isErr()) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+ cubeb_input_processing_params supported = res.unwrap();
+ if ((supported & aParams) != aParams) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+ return mock->InputProcessingApplyRv();
+}
+
cubeb_stream* MockCubebStream::AsCubebStream() {
- MOZ_ASSERT(!mDestroyed);
+ MutexAutoLock l(mMutex);
+ return AsCubebStreamLocked();
+}
+
+cubeb_stream* MockCubebStream::AsCubebStreamLocked() {
+ MOZ_RELEASE_ASSERT(!mDestroyed);
+ mMutex.AssertCurrentThreadOwns();
return reinterpret_cast<cubeb_stream*>(this);
}
MockCubebStream* MockCubebStream::AsMock(cubeb_stream* aStream) {
auto* mockStream = reinterpret_cast<MockCubebStream*>(aStream);
- MOZ_ASSERT(!mockStream->mDestroyed);
+ MutexAutoLock l(mockStream->mMutex);
+ return AsMockLocked(aStream);
+}
+
+MockCubebStream* MockCubebStream::AsMockLocked(cubeb_stream* aStream) {
+ auto* mockStream = reinterpret_cast<MockCubebStream*>(aStream);
+ mockStream->mMutex.AssertCurrentThreadOwns();
+ MOZ_RELEASE_ASSERT(!mockStream->mDestroyed);
return mockStream;
}
@@ -285,58 +331,96 @@ cubeb_devid MockCubebStream::GetOutputDeviceID() const {
}
uint32_t MockCubebStream::InputChannels() const {
+ MutexAutoLock l(mMutex);
+ return InputChannelsLocked();
+}
+
+uint32_t MockCubebStream::InputChannelsLocked() const {
+ mMutex.AssertCurrentThreadOwns();
return mAudioGenerator.ChannelCount();
}
uint32_t MockCubebStream::OutputChannels() const {
+ MutexAutoLock l(mMutex);
+ return OutputChannelsLocked();
+}
+
+uint32_t MockCubebStream::OutputChannelsLocked() const {
+ mMutex.AssertCurrentThreadOwns();
return mOutputParams.channels;
}
uint32_t MockCubebStream::SampleRate() const {
+ MutexAutoLock l(mMutex);
+ return SampleRateLocked();
+}
+
+uint32_t MockCubebStream::SampleRateLocked() const {
+ mMutex.AssertCurrentThreadOwns();
return mAudioGenerator.mSampleRate;
}
uint32_t MockCubebStream::InputFrequency() const {
+ MutexAutoLock l(mMutex);
+ return InputFrequencyLocked();
+}
+
+uint32_t MockCubebStream::InputFrequencyLocked() const {
+ mMutex.AssertCurrentThreadOwns();
return mAudioGenerator.mFrequency;
}
+Maybe<cubeb_state> MockCubebStream::State() const {
+ MutexAutoLock l(mMutex);
+ return mState;
+}
+
nsTArray<AudioDataValue>&& MockCubebStream::TakeRecordedOutput() {
+ MutexAutoLock l(mMutex);
return std::move(mRecordedOutput);
}
nsTArray<AudioDataValue>&& MockCubebStream::TakeRecordedInput() {
+ MutexAutoLock l(mMutex);
return std::move(mRecordedInput);
}
void MockCubebStream::SetDriftFactor(float aDriftFactor) {
- MOZ_ASSERT(mRunningMode == MockCubeb::RunningMode::Automatic);
+ MOZ_RELEASE_ASSERT(mRunningMode == MockCubeb::RunningMode::Automatic);
+ MutexAutoLock l(mMutex);
mDriftFactor = aDriftFactor;
}
-void MockCubebStream::ForceError() { mForceErrorState = true; }
+void MockCubebStream::ForceError() {
+ MutexAutoLock l(mMutex);
+ mForceErrorState = true;
+}
void MockCubebStream::ForceDeviceChanged() {
- MOZ_ASSERT(mRunningMode == RunningMode::Automatic);
+ MOZ_RELEASE_ASSERT(mRunningMode == RunningMode::Automatic);
+ MutexAutoLock l(mMutex);
mForceDeviceChanged = true;
};
void MockCubebStream::NotifyDeviceChangedNow() {
- MOZ_ASSERT(mRunningMode == RunningMode::Manual);
+ MOZ_RELEASE_ASSERT(mRunningMode == RunningMode::Manual);
NotifyDeviceChanged();
}
void MockCubebStream::Thaw() {
- MOZ_ASSERT(mRunningMode == RunningMode::Automatic);
+ MOZ_RELEASE_ASSERT(mRunningMode == RunningMode::Automatic);
MonitorAutoLock l(mFrozenStartMonitor);
mFrozenStart = false;
mFrozenStartMonitor.Notify();
}
void MockCubebStream::SetOutputRecordingEnabled(bool aEnabled) {
+ MutexAutoLock l(mMutex);
mOutputRecordingEnabled = aEnabled;
}
void MockCubebStream::SetInputRecordingEnabled(bool aEnabled) {
+ MutexAutoLock l(mMutex);
mInputRecordingEnabled = aEnabled;
}
@@ -370,33 +454,43 @@ MediaEventSource<void>& MockCubebStream::DeviceChangeForcedEvent() {
}
KeepProcessing MockCubebStream::ManualDataCallback(long aNrFrames) {
- MOZ_ASSERT(mRunningMode == RunningMode::Manual);
- MOZ_ASSERT(aNrFrames <= kMaxNrFrames);
+ MOZ_RELEASE_ASSERT(mRunningMode == RunningMode::Manual);
+ MOZ_RELEASE_ASSERT(aNrFrames <= kMaxNrFrames);
+ MutexAutoLock l(mMutex);
return Process(aNrFrames);
}
KeepProcessing MockCubebStream::Process(long aNrFrames) {
+ mMutex.AssertCurrentThreadOwns();
+ if (!mState || *mState != CUBEB_STATE_STARTED) {
+ return KeepProcessing::InvalidState;
+ }
if (mInputParams.rate) {
mAudioGenerator.GenerateInterleaved(mInputBuffer, aNrFrames);
}
- cubeb_stream* stream = AsCubebStream();
+ cubeb_stream* stream = AsCubebStreamLocked();
const long outframes =
mDataCallback(stream, mUserPtr, mHasInput ? mInputBuffer : nullptr,
mHasOutput ? mOutputBuffer : nullptr, aNrFrames);
- if (mInputRecordingEnabled && mHasInput) {
- mRecordedInput.AppendElements(mInputBuffer, outframes * InputChannels());
- }
- if (mOutputRecordingEnabled && mHasOutput) {
- mRecordedOutput.AppendElements(mOutputBuffer, outframes * OutputChannels());
- }
- mAudioVerifier.AppendDataInterleaved(mOutputBuffer, outframes,
- MAX_OUTPUT_CHANNELS);
- mPosition += outframes;
+ if (outframes > 0) {
+ if (mInputRecordingEnabled && mHasInput) {
+ mRecordedInput.AppendElements(mInputBuffer,
+ outframes * InputChannelsLocked());
+ }
+ if (mOutputRecordingEnabled && mHasOutput) {
+ mRecordedOutput.AppendElements(mOutputBuffer,
+
+ outframes * OutputChannelsLocked());
+ }
+ mAudioVerifier.AppendDataInterleaved(mOutputBuffer, outframes,
+ MAX_OUTPUT_CHANNELS);
+ mPosition += outframes;
- mFramesProcessedEvent.Notify(outframes);
- if (mAudioVerifier.PreSilenceEnded()) {
- mFramesVerifiedEvent.Notify(outframes);
+ mFramesProcessedEvent.Notify(outframes);
+ if (mAudioVerifier.PreSilenceEnded()) {
+ mFramesVerifiedEvent.Notify(outframes);
+ }
}
if (outframes < aNrFrames) {
@@ -422,8 +516,9 @@ KeepProcessing MockCubebStream::Process(long aNrFrames) {
}
KeepProcessing MockCubebStream::Process10Ms() {
- MOZ_ASSERT(mRunningMode == RunningMode::Automatic);
- uint32_t rate = SampleRate();
+ MOZ_RELEASE_ASSERT(mRunningMode == RunningMode::Automatic);
+ MutexAutoLock l(mMutex);
+ uint32_t rate = SampleRateLocked();
const long nrFrames =
static_cast<long>(static_cast<float>(rate * 10) * mDriftFactor) /
PR_MSEC_PER_SEC;
@@ -431,11 +526,14 @@ KeepProcessing MockCubebStream::Process10Ms() {
}
void MockCubebStream::NotifyState(cubeb_state aState) {
- mStateCallback(AsCubebStream(), mUserPtr, aState);
+ mMutex.AssertCurrentThreadOwns();
+ mState = Some(aState);
+ mStateCallback(AsCubebStreamLocked(), mUserPtr, aState);
mStateEvent.Notify(aState);
}
void MockCubebStream::NotifyDeviceChanged() {
+ MutexAutoLock l(mMutex);
mDeviceChangedCallback(this->mUserPtr);
mDeviceChangedForcedEvent.Notify();
}
@@ -445,20 +543,20 @@ MockCubeb::MockCubeb() : MockCubeb(MockCubeb::RunningMode::Automatic) {}
MockCubeb::MockCubeb(RunningMode aRunningMode)
: ops(&mock_ops), mRunningMode(aRunningMode) {}
-MockCubeb::~MockCubeb() { MOZ_ASSERT(!mFakeAudioThread); };
+MockCubeb::~MockCubeb() { MOZ_RELEASE_ASSERT(!mFakeAudioThread); };
void MockCubeb::Destroy() {
- MOZ_ASSERT(mHasCubebContext);
+ MOZ_RELEASE_ASSERT(mHasCubebContext);
{
auto streams = mLiveStreams.Lock();
- MOZ_ASSERT(streams->IsEmpty());
+ MOZ_RELEASE_ASSERT(streams->IsEmpty());
}
mDestroyed = true;
Release();
}
cubeb* MockCubeb::AsCubebContext() {
- MOZ_ASSERT(!mDestroyed);
+ MOZ_RELEASE_ASSERT(!mDestroyed);
if (mHasCubebContext.compareExchange(false, true)) {
AddRef();
}
@@ -467,7 +565,7 @@ cubeb* MockCubeb::AsCubebContext() {
MockCubeb* MockCubeb::AsMock(cubeb* aContext) {
auto* mockCubeb = reinterpret_cast<MockCubeb*>(aContext);
- MOZ_ASSERT(!mockCubeb->mDestroyed);
+ MOZ_RELEASE_ASSERT(!mockCubeb->mDestroyed);
return mockCubeb;
}
@@ -528,6 +626,28 @@ int MockCubeb::RegisterDeviceCollectionChangeCallback(
return CUBEB_OK;
}
+Result<cubeb_input_processing_params, int>
+MockCubeb::SupportedInputProcessingParams() const {
+ const auto& [params, rv] = mSupportedInputProcessingParams;
+ if (rv != CUBEB_OK) {
+ return Err(rv);
+ }
+ return params;
+}
+
+void MockCubeb::SetSupportedInputProcessingParams(
+ cubeb_input_processing_params aParams, int aRv) {
+ mSupportedInputProcessingParams = std::make_pair(aParams, aRv);
+}
+
+void MockCubeb::SetInputProcessingApplyRv(int aRv) {
+ mInputProcessingParamsApplyRv = aRv;
+}
+
+int MockCubeb::InputProcessingApplyRv() const {
+ return mInputProcessingParamsApplyRv;
+}
+
void MockCubeb::AddDevice(cubeb_device_info aDevice) {
if (aDevice.type == CUBEB_DEVICE_TYPE_INPUT) {
mInputDevices.AppendElement(aDevice);
@@ -613,12 +733,12 @@ void MockCubeb::SetSupportDeviceChangeCallback(bool aSupports) {
void MockCubeb::ForceStreamInitError() { mStreamInitErrorState = true; }
void MockCubeb::SetStreamStartFreezeEnabled(bool aEnabled) {
- MOZ_ASSERT(mRunningMode == RunningMode::Automatic);
+ MOZ_RELEASE_ASSERT(mRunningMode == RunningMode::Automatic);
mStreamStartFreezeEnabled = aEnabled;
}
auto MockCubeb::ForceAudioThread() -> RefPtr<ForcedAudioThreadPromise> {
- MOZ_ASSERT(mRunningMode == RunningMode::Automatic);
+ MOZ_RELEASE_ASSERT(mRunningMode == RunningMode::Automatic);
RefPtr<ForcedAudioThreadPromise> p =
mForcedAudioThreadPromise.Ensure(__func__);
mForcedAudioThread = true;
@@ -627,7 +747,7 @@ auto MockCubeb::ForceAudioThread() -> RefPtr<ForcedAudioThreadPromise> {
}
void MockCubeb::UnforceAudioThread() {
- MOZ_ASSERT(mRunningMode == RunningMode::Automatic);
+ MOZ_RELEASE_ASSERT(mRunningMode == RunningMode::Automatic);
mForcedAudioThread = false;
}
@@ -660,12 +780,12 @@ void MockCubeb::StreamDestroy(MockCubebStream* aStream) {
}
void MockCubeb::GoFaster() {
- MOZ_ASSERT(mRunningMode == RunningMode::Automatic);
+ MOZ_RELEASE_ASSERT(mRunningMode == RunningMode::Automatic);
mFastMode = true;
}
void MockCubeb::DontGoFaster() {
- MOZ_ASSERT(mRunningMode == RunningMode::Automatic);
+ MOZ_RELEASE_ASSERT(mRunningMode == RunningMode::Automatic);
mFastMode = false;
}
@@ -679,13 +799,17 @@ MockCubeb::StreamDestroyEvent() {
}
void MockCubeb::StartStream(MockCubebStream* aStream) {
+ if (aStream) {
+ aStream->mMutex.AssertNotCurrentThreadOwns();
+ }
auto streams = mLiveStreams.Lock();
- MOZ_ASSERT_IF(!aStream, mForcedAudioThread);
- // Forcing an audio thread must happen before starting streams
- MOZ_ASSERT_IF(!aStream, streams->IsEmpty());
if (aStream) {
- MOZ_ASSERT(!streams->Contains(aStream->mSelf));
+ MOZ_RELEASE_ASSERT(!streams->Contains(aStream->mSelf));
streams->AppendElement(aStream->mSelf);
+ } else {
+ MOZ_RELEASE_ASSERT(mForcedAudioThread);
+ // Forcing an audio thread must happen before starting streams
+ MOZ_RELEASE_ASSERT(streams->IsEmpty());
}
if (!mFakeAudioThread && mRunningMode == RunningMode::Automatic) {
AddRef(); // released when the thread exits
@@ -694,6 +818,7 @@ void MockCubeb::StartStream(MockCubebStream* aStream) {
}
void MockCubeb::StopStream(MockCubebStream* aStream) {
+ aStream->mMutex.AssertNotCurrentThreadOwns();
{
auto streams = mLiveStreams.Lock();
if (!streams->Contains(aStream->mSelf)) {
@@ -719,7 +844,7 @@ void MockCubeb::ThreadFunction() {
}
}
streams->RemoveElementsBy([](const auto& stream) { return !stream; });
- MOZ_ASSERT(mFakeAudioThread);
+ MOZ_RELEASE_ASSERT(mFakeAudioThread);
if (streams->IsEmpty() && !mForcedAudioThread) {
// This leaks the std::thread if Gecko's main thread has already been
// shut down.
diff --git a/dom/media/gtest/MockCubeb.h b/dom/media/gtest/MockCubeb.h
index ed6342a779..15689a4a4e 100644
--- a/dom/media/gtest/MockCubeb.h
+++ b/dom/media/gtest/MockCubeb.h
@@ -11,12 +11,15 @@
#include "MediaEventSource.h"
#include "mozilla/DataMutex.h"
#include "mozilla/MozPromise.h"
+#include "mozilla/Result.h"
+#include "mozilla/ResultVariant.h"
#include "mozilla/ThreadSafeWeakPtr.h"
#include "nsTArray.h"
#include <thread>
#include <atomic>
#include <chrono>
+#include <utility>
namespace mozilla {
const uint32_t MAX_OUTPUT_CHANNELS = 2;
@@ -68,6 +71,8 @@ struct cubeb_ops {
// Keep those and the struct definition in sync with cubeb.h and
// cubeb-internal.h
void cubeb_mock_destroy(cubeb* context);
+static int cubeb_mock_get_supported_input_processing_params(
+ cubeb* context, cubeb_input_processing_params* params);
static int cubeb_mock_enumerate_devices(cubeb* context, cubeb_device_type type,
cubeb_device_collection* out);
@@ -101,6 +106,9 @@ static int cubeb_mock_stream_set_volume(cubeb_stream* stream, float volume);
static int cubeb_mock_stream_set_name(cubeb_stream* stream,
char const* stream_name);
+static int cubeb_mock_stream_set_input_processing_params(
+ cubeb_stream* stream, cubeb_input_processing_params);
+
static int cubeb_mock_stream_register_device_changed_callback(
cubeb_stream* stream,
cubeb_device_changed_callback device_changed_callback);
@@ -122,7 +130,7 @@ cubeb_ops const mock_ops = {
/*.get_min_latency =*/cubeb_mock_get_min_latency,
/*.get_preferred_sample_rate =*/cubeb_mock_get_preferred_sample_rate,
/*.get_supported_input_processing_params =*/
- NULL,
+ cubeb_mock_get_supported_input_processing_params,
/*.enumerate_devices =*/cubeb_mock_enumerate_devices,
/*.device_collection_destroy =*/cubeb_mock_device_collection_destroy,
/*.destroy =*/cubeb_mock_destroy,
@@ -139,7 +147,7 @@ cubeb_ops const mock_ops = {
/*.stream_set_input_mute =*/NULL,
/*.stream_set_input_processing_params =*/
- NULL,
+ cubeb_mock_stream_set_input_processing_params,
/*.stream_device_destroy =*/NULL,
/*.stream_register_device_changed_callback =*/
cubeb_mock_stream_register_device_changed_callback,
@@ -160,7 +168,7 @@ class MockCubebStream {
void* mUserPtr;
public:
- enum class KeepProcessing { No, Yes };
+ enum class KeepProcessing { No, Yes, InvalidState };
enum class RunningMode { Automatic, Manual };
MockCubebStream(cubeb* aContext, char const* aStreamName,
@@ -175,50 +183,56 @@ class MockCubebStream {
~MockCubebStream();
- int Start();
- int Stop();
- uint64_t Position();
- void Destroy();
- int SetName(char const* aName);
+ int Start() MOZ_EXCLUDES(mMutex);
+ int Stop() MOZ_EXCLUDES(mMutex);
+ uint64_t Position() MOZ_EXCLUDES(mMutex);
+ void Destroy() MOZ_EXCLUDES(mMutex);
+ int SetName(char const* aName) MOZ_EXCLUDES(mMutex);
int RegisterDeviceChangedCallback(
- cubeb_device_changed_callback aDeviceChangedCallback);
+ cubeb_device_changed_callback aDeviceChangedCallback)
+ MOZ_EXCLUDES(mMutex);
+ int SetInputProcessingParams(cubeb_input_processing_params aParams);
- cubeb_stream* AsCubebStream();
+ cubeb_stream* AsCubebStream() MOZ_EXCLUDES(mMutex);
static MockCubebStream* AsMock(cubeb_stream* aStream);
- char const* StreamName() const { return mName.get(); }
+ char const* StreamName() const MOZ_EXCLUDES(mMutex) {
+ MutexAutoLock l(mMutex);
+ return mName.get();
+ }
cubeb_devid GetInputDeviceID() const;
cubeb_devid GetOutputDeviceID() const;
- uint32_t InputChannels() const;
- uint32_t OutputChannels() const;
- uint32_t SampleRate() const;
- uint32_t InputFrequency() const;
+ uint32_t InputChannels() const MOZ_EXCLUDES(mMutex);
+ uint32_t OutputChannels() const MOZ_EXCLUDES(mMutex);
+ uint32_t SampleRate() const MOZ_EXCLUDES(mMutex);
+ uint32_t InputFrequency() const MOZ_EXCLUDES(mMutex);
+ Maybe<cubeb_state> State() const MOZ_EXCLUDES(mMutex);
- void SetDriftFactor(float aDriftFactor);
- void ForceError();
- void ForceDeviceChanged();
- void Thaw();
+ void SetDriftFactor(float aDriftFactor) MOZ_EXCLUDES(mMutex);
+ void ForceError() MOZ_EXCLUDES(mMutex);
+ void ForceDeviceChanged() MOZ_EXCLUDES(mMutex);
+ void Thaw() MOZ_EXCLUDES(mMutex);
// For RunningMode::Manual, drive this MockCubebStream forward.
- KeepProcessing ManualDataCallback(long aNrFrames);
+ KeepProcessing ManualDataCallback(long aNrFrames) MOZ_EXCLUDES(mMutex);
// For RunningMode::Manual, notify the client of a DeviceChanged event
// synchronously.
- void NotifyDeviceChangedNow();
+ void NotifyDeviceChangedNow() MOZ_EXCLUDES(mMutex);
// Enable input recording for this driver. This is best called before
// the thread is running, but is safe to call whenever.
- void SetOutputRecordingEnabled(bool aEnabled);
+ void SetOutputRecordingEnabled(bool aEnabled) MOZ_EXCLUDES(mMutex);
// Enable input recording for this driver. This is best called before
// the thread is running, but is safe to call whenever.
- void SetInputRecordingEnabled(bool aEnabled);
+ void SetInputRecordingEnabled(bool aEnabled) MOZ_EXCLUDES(mMutex);
// Get the recorded output from this stream. This doesn't copy, and therefore
// only works once.
- nsTArray<AudioDataValue>&& TakeRecordedOutput();
+ nsTArray<AudioDataValue>&& TakeRecordedOutput() MOZ_EXCLUDES(mMutex);
// Get the recorded input from this stream. This doesn't copy, and therefore
// only works once.
- nsTArray<AudioDataValue>&& TakeRecordedInput();
+ nsTArray<AudioDataValue>&& TakeRecordedInput() MOZ_EXCLUDES(mMutex);
MediaEventSource<nsCString>& NameSetEvent();
MediaEventSource<cubeb_state>& StateEvent();
@@ -232,7 +246,13 @@ class MockCubebStream {
MediaEventSource<void>& DeviceChangeForcedEvent();
private:
- KeepProcessing Process(long aNrFrames);
+ cubeb_stream* AsCubebStreamLocked() MOZ_REQUIRES(mMutex);
+ static MockCubebStream* AsMockLocked(cubeb_stream* aStream);
+ uint32_t InputChannelsLocked() const MOZ_REQUIRES(mMutex);
+ uint32_t OutputChannelsLocked() const MOZ_REQUIRES(mMutex);
+ uint32_t SampleRateLocked() const MOZ_REQUIRES(mMutex);
+ uint32_t InputFrequencyLocked() const MOZ_REQUIRES(mMutex);
+ KeepProcessing Process(long aNrFrames) MOZ_REQUIRES(mMutex);
KeepProcessing Process10Ms();
public:
@@ -242,52 +262,58 @@ class MockCubebStream {
SmartMockCubebStream* const mSelf;
private:
- void NotifyState(cubeb_state aState);
- void NotifyDeviceChanged();
+ void NotifyState(cubeb_state aState) MOZ_REQUIRES(mMutex);
+ void NotifyDeviceChanged() MOZ_EXCLUDES(mMutex);
static constexpr long kMaxNrFrames = 1920;
+ // Mutex guarding most members to ensure state is in sync.
+ mutable Mutex mMutex{"MockCubebStream::mMutex"};
// Monitor used to block start until mFrozenStart is false.
- Monitor mFrozenStartMonitor MOZ_UNANNOTATED;
+ Monitor mFrozenStartMonitor MOZ_ACQUIRED_BEFORE(mMutex);
// Whether this stream should wait for an explicit start request before
- // starting. Protected by FrozenStartMonitor.
- bool mFrozenStart;
+ // starting.
+ bool mFrozenStart MOZ_GUARDED_BY(mFrozenStartMonitor);
+ // The stream's most recently issued state change, if any has occurred.
// Used to abort a frozen start if cubeb_stream_start() is called currently
// with a blocked cubeb_stream_start() call.
- std::atomic_bool mStreamStop{true};
+ Maybe<cubeb_state> mState MOZ_GUARDED_BY(mMutex);
// Whether or not the output-side of this stream (what is written from the
// callback output buffer) is recorded in an internal buffer. The data is then
// available via `GetRecordedOutput`.
- std::atomic_bool mOutputRecordingEnabled{false};
+ bool mOutputRecordingEnabled MOZ_GUARDED_BY(mMutex) = false;
// Whether or not the input-side of this stream (what is written from the
// callback input buffer) is recorded in an internal buffer. The data is then
// available via `TakeRecordedInput`.
- std::atomic_bool mInputRecordingEnabled{false};
+ bool mInputRecordingEnabled MOZ_GUARDED_BY(mMutex) = false;
// The audio buffer used on data callback.
- AudioDataValue mOutputBuffer[MAX_OUTPUT_CHANNELS * kMaxNrFrames] = {};
- AudioDataValue mInputBuffer[MAX_INPUT_CHANNELS * kMaxNrFrames] = {};
+ AudioDataValue mOutputBuffer[MAX_OUTPUT_CHANNELS *
+ kMaxNrFrames] MOZ_GUARDED_BY(mMutex) = {};
+ AudioDataValue mInputBuffer[MAX_INPUT_CHANNELS * kMaxNrFrames] MOZ_GUARDED_BY(
+ mMutex) = {};
// The audio callback
- cubeb_data_callback mDataCallback = nullptr;
+ const cubeb_data_callback mDataCallback = nullptr;
// The stream state callback
- cubeb_state_callback mStateCallback = nullptr;
+ const cubeb_state_callback mStateCallback = nullptr;
// The device changed callback
- cubeb_device_changed_callback mDeviceChangedCallback = nullptr;
+ cubeb_device_changed_callback mDeviceChangedCallback MOZ_GUARDED_BY(mMutex) =
+ nullptr;
// A name for this stream
- nsCString mName;
+ nsCString mName MOZ_GUARDED_BY(mMutex);
// The stream params
- cubeb_stream_params mOutputParams = {};
- cubeb_stream_params mInputParams = {};
+ cubeb_stream_params mOutputParams MOZ_GUARDED_BY(mMutex) = {};
+ cubeb_stream_params mInputParams MOZ_GUARDED_BY(mMutex) = {};
/* Device IDs */
- cubeb_devid mInputDeviceID;
- cubeb_devid mOutputDeviceID;
-
- std::atomic<float> mDriftFactor{1.0};
- std::atomic_bool mFastMode{false};
- std::atomic_bool mForceErrorState{false};
- std::atomic_bool mForceDeviceChanged{false};
- std::atomic_bool mDestroyed{false};
- std::atomic<uint64_t> mPosition{0};
- AudioGenerator<AudioDataValue> mAudioGenerator;
- AudioVerifier<AudioDataValue> mAudioVerifier;
+ const cubeb_devid mInputDeviceID;
+ const cubeb_devid mOutputDeviceID;
+
+ float mDriftFactor MOZ_GUARDED_BY(mMutex) = 1.0;
+ bool mFastMode MOZ_GUARDED_BY(mMutex) = false;
+ bool mForceErrorState MOZ_GUARDED_BY(mMutex) = false;
+ bool mForceDeviceChanged MOZ_GUARDED_BY(mMutex) = false;
+ bool mDestroyed MOZ_GUARDED_BY(mMutex) = false;
+ uint64_t mPosition MOZ_GUARDED_BY(mMutex) = 0;
+ AudioGenerator<AudioDataValue> mAudioGenerator MOZ_GUARDED_BY(mMutex);
+ AudioVerifier<AudioDataValue> mAudioVerifier MOZ_GUARDED_BY(mMutex);
MediaEventProducer<nsCString> mNameSetEvent;
MediaEventProducer<cubeb_state> mStateEvent;
@@ -299,12 +325,29 @@ class MockCubebStream {
MediaEventProducer<void> mDeviceChangedForcedEvent;
// The recorded data, copied from the output_buffer of the callback.
// Interleaved.
- nsTArray<AudioDataValue> mRecordedOutput;
+ nsTArray<AudioDataValue> mRecordedOutput MOZ_GUARDED_BY(mMutex);
// The recorded data, copied from the input buffer of the callback.
// Interleaved.
- nsTArray<AudioDataValue> mRecordedInput;
+ nsTArray<AudioDataValue> mRecordedInput MOZ_GUARDED_BY(mMutex);
};
+inline std::ostream& operator<<(std::ostream& aStream,
+ const MockCubebStream::KeepProcessing& aVal) {
+ switch (aVal) {
+ case MockCubebStream::KeepProcessing::Yes:
+ aStream << "KeepProcessing::Yes";
+ return aStream;
+ case MockCubebStream::KeepProcessing::No:
+ aStream << "KeepProcessing::No";
+ return aStream;
+ case MockCubebStream::KeepProcessing::InvalidState:
+ aStream << "KeepProcessing::InvalidState";
+ return aStream;
+ }
+ aStream << "KeepProcessing(invalid " << static_cast<uint32_t>(aVal) << ")";
+ return aStream;
+}
+
class SmartMockCubebStream
: public MockCubebStream,
public SupportsThreadSafeWeakPtr<SmartMockCubebStream> {
@@ -362,6 +405,16 @@ class MockCubeb {
cubeb_device_type aDevType,
cubeb_device_collection_changed_callback aCallback, void* aUserPtr);
+ Result<cubeb_input_processing_params, int> SupportedInputProcessingParams()
+ const;
+ void SetSupportedInputProcessingParams(cubeb_input_processing_params aParams,
+ int aRv);
+ // Set the rv to be returned when SetInputProcessingParams for any stream of
+ // this context would apply the params to the stream, i.e. after passing the
+ // supported-params check.
+ void SetInputProcessingApplyRv(int aRv);
+ int InputProcessingApplyRv() const;
+
// Control API
// Add an input or output device to this backend. This calls the device
@@ -455,6 +508,10 @@ class MockCubeb {
// notification via a system callback. If not, Gecko is expected to re-query
// the list every time.
bool mSupportsDeviceCollectionChangedCallback = true;
+ std::pair<cubeb_input_processing_params, int>
+ mSupportedInputProcessingParams = std::make_pair(
+ CUBEB_INPUT_PROCESSING_PARAM_NONE, CUBEB_ERROR_NOT_SUPPORTED);
+ int mInputProcessingParamsApplyRv = CUBEB_OK;
const RunningMode mRunningMode;
Atomic<bool> mStreamInitErrorState;
// Whether new MockCubebStreams should be frozen on start.
@@ -502,6 +559,18 @@ int cubeb_mock_register_device_collection_changed(
devtype, callback, user_ptr);
}
+int cubeb_mock_get_supported_input_processing_params(
+ cubeb* context, cubeb_input_processing_params* params) {
+ Result<cubeb_input_processing_params, int> res =
+ MockCubeb::AsMock(context)->SupportedInputProcessingParams();
+ if (res.isErr()) {
+ *params = CUBEB_INPUT_PROCESSING_PARAM_NONE;
+ return res.unwrapErr();
+ }
+ *params = res.unwrap();
+ return CUBEB_OK;
+}
+
int cubeb_mock_stream_init(
cubeb* context, cubeb_stream** stream, char const* stream_name,
cubeb_devid input_device, cubeb_stream_params* input_stream_params,
@@ -562,6 +631,11 @@ int cubeb_mock_stream_register_device_changed_callback(
device_changed_callback);
}
+static int cubeb_mock_stream_set_input_processing_params(
+ cubeb_stream* stream, cubeb_input_processing_params params) {
+ return MockCubebStream::AsMock(stream)->SetInputProcessingParams(params);
+}
+
int cubeb_mock_get_min_latency(cubeb* context, cubeb_stream_params params,
uint32_t* latency_ms) {
*latency_ms = 10;
diff --git a/dom/media/gtest/TestAudioCallbackDriver.cpp b/dom/media/gtest/TestAudioCallbackDriver.cpp
index 050395fa44..9c8c9bd107 100644
--- a/dom/media/gtest/TestAudioCallbackDriver.cpp
+++ b/dom/media/gtest/TestAudioCallbackDriver.cpp
@@ -9,34 +9,39 @@
#include "GraphDriver.h"
#include "gmock/gmock.h"
-#include "gtest/gtest-printers.h"
#include "gtest/gtest.h"
#include "MediaTrackGraphImpl.h"
#include "mozilla/gtest/WaitFor.h"
#include "mozilla/Attributes.h"
#include "mozilla/SyncRunnable.h"
-#include "mozilla/UniquePtr.h"
#include "nsTArray.h"
#include "MockCubeb.h"
-using namespace mozilla;
+namespace mozilla {
+
using IterationResult = GraphInterface::IterationResult;
using ::testing::_;
using ::testing::AnyNumber;
+using ::testing::AtMost;
+using ::testing::Eq;
+using ::testing::InSequence;
using ::testing::NiceMock;
class MockGraphInterface : public GraphInterface {
NS_DECL_THREADSAFE_ISUPPORTS
explicit MockGraphInterface(TrackRate aSampleRate)
: mSampleRate(aSampleRate) {}
- MOCK_METHOD0(NotifyInputStopped, void());
- MOCK_METHOD5(NotifyInputData, void(const AudioDataValue*, size_t, TrackRate,
- uint32_t, uint32_t));
- MOCK_METHOD0(DeviceChanged, void());
+ MOCK_METHOD(void, NotifyInputStopped, ());
+ MOCK_METHOD(void, NotifyInputData,
+ (const AudioDataValue*, size_t, TrackRate, uint32_t, uint32_t));
+ MOCK_METHOD(void, NotifySetRequestedInputProcessingParamsResult,
+ (AudioCallbackDriver*, cubeb_input_processing_params,
+ (Result<cubeb_input_processing_params, int>&&)));
+ MOCK_METHOD(void, DeviceChanged, ());
#ifdef DEBUG
- MOCK_CONST_METHOD1(InDriverIteration, bool(const GraphDriver*));
+ MOCK_METHOD(bool, InDriverIteration, (const GraphDriver*), (const));
#endif
/* OneIteration cannot be mocked because IterationResult is non-memmovable and
* cannot be passed as a parameter, which GMock does internally. */
@@ -81,7 +86,7 @@ class MockGraphInterface : public GraphInterface {
RefPtr<Runnable> aSwitchedRunnable = NS_NewRunnableFunction(
"DefaultNoopSwitchedRunnable", [] {})) {
auto guard = mNextDriver.Lock();
- MOZ_ASSERT(guard->isNothing());
+ MOZ_RELEASE_ASSERT(guard->isNothing());
*guard =
Some(std::make_tuple(std::move(aDriver), std::move(aSwitchedRunnable)));
}
@@ -108,7 +113,7 @@ class MockGraphInterface : public GraphInterface {
NS_IMPL_ISUPPORTS0(MockGraphInterface)
TEST(TestAudioCallbackDriver, StartStop)
-MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+MOZ_CAN_RUN_SCRIPT_BOUNDARY {
const TrackRate rate = 44100;
MockCubeb* cubeb = new MockCubeb();
CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
@@ -118,7 +123,8 @@ MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
EXPECT_CALL(*graph, NotifyInputStopped).Times(0);
driver = MakeRefPtr<AudioCallbackDriver>(graph, nullptr, rate, 2, 0, nullptr,
- nullptr, AudioInputType::Unknown);
+ nullptr, AudioInputType::Unknown,
+ CUBEB_INPUT_PROCESSING_PARAM_NONE);
EXPECT_FALSE(driver->ThreadRunning()) << "Verify thread is not running";
EXPECT_FALSE(driver->IsStarted()) << "Verify thread is not started";
@@ -135,7 +141,7 @@ MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
EXPECT_FALSE(driver->IsStarted()) << "Verify thread is not started";
}
-void TestSlowStart(const TrackRate aRate) MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+void TestSlowStart(const TrackRate aRate) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
std::cerr << "TestSlowStart with rate " << aRate << std::endl;
MockCubeb* cubeb = new MockCubeb();
@@ -177,15 +183,17 @@ void TestSlowStart(const TrackRate aRate) MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
});
driver = MakeRefPtr<AudioCallbackDriver>(graph, nullptr, aRate, 2, 2, nullptr,
- (void*)1, AudioInputType::Voice);
+ (void*)1, AudioInputType::Voice,
+ CUBEB_INPUT_PROCESSING_PARAM_NONE);
EXPECT_FALSE(driver->ThreadRunning()) << "Verify thread is not running";
EXPECT_FALSE(driver->IsStarted()) << "Verify thread is not started";
graph->SetCurrentDriver(driver);
graph->SetEnsureNextIteration(true);
+ auto initPromise = TakeN(cubeb->StreamInitEvent(), 1);
driver->Start();
- RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
+ auto [stream] = WaitFor(initPromise).unwrap()[0];
cubeb->SetStreamStartFreezeEnabled(false);
const size_t fallbackIterations = 3;
@@ -234,7 +242,7 @@ void TestSlowStart(const TrackRate aRate) MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
}
TEST(TestAudioCallbackDriver, SlowStart)
-MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+{
TestSlowStart(1000); // 10ms = 10 <<< 128 samples
TestSlowStart(8000); // 10ms = 80 < 128 samples
TestSlowStart(44100); // 10ms = 441 > 128 samples
@@ -252,7 +260,7 @@ class MOZ_STACK_CLASS AutoSetter {
: mVal(aVal), mNew(aNew), mOld(mVal.exchange(aNew)) {}
~AutoSetter() {
DebugOnly<T> oldNew = mVal.exchange(mOld);
- MOZ_ASSERT(oldNew == mNew);
+ MOZ_RELEASE_ASSERT(oldNew == mNew);
}
};
#endif
@@ -265,12 +273,13 @@ MOZ_CAN_RUN_SCRIPT_BOUNDARY {
auto graph = MakeRefPtr<MockGraphInterface>(rate);
auto driver = MakeRefPtr<AudioCallbackDriver>(
- graph, nullptr, rate, 2, 1, nullptr, (void*)1, AudioInputType::Voice);
+ graph, nullptr, rate, 2, 1, nullptr, (void*)1, AudioInputType::Voice,
+ CUBEB_INPUT_PROCESSING_PARAM_NONE);
EXPECT_FALSE(driver->ThreadRunning()) << "Verify thread is not running";
EXPECT_FALSE(driver->IsStarted()) << "Verify thread is not started";
#ifdef DEBUG
- std::atomic<std::thread::id> threadInDriverIteration((std::thread::id()));
+ std::atomic<std::thread::id> threadInDriverIteration{std::thread::id()};
EXPECT_CALL(*graph, InDriverIteration(driver.get())).WillRepeatedly([&] {
return std::this_thread::get_id() == threadInDriverIteration;
});
@@ -279,17 +288,24 @@ MOZ_CAN_RUN_SCRIPT_BOUNDARY {
EXPECT_CALL(*graph, NotifyInputData(_, 0, rate, 1, _)).Times(AnyNumber());
EXPECT_CALL(*graph, NotifyInputData(_, ignoredFrameCount, _, _, _)).Times(0);
EXPECT_CALL(*graph, DeviceChanged);
+ Result<cubeb_input_processing_params, int> expected =
+ Err(CUBEB_ERROR_NOT_SUPPORTED);
+ EXPECT_CALL(*graph, NotifySetRequestedInputProcessingParamsResult(
+ driver.get(), CUBEB_INPUT_PROCESSING_PARAM_NONE,
+ Eq(std::ref(expected))));
graph->SetCurrentDriver(driver);
graph->SetEnsureNextIteration(true);
// This starts the fallback driver.
+ auto initPromise = TakeN(cubeb->StreamInitEvent(), 1);
driver->Start();
- RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
+ auto [stream] = WaitFor(initPromise).unwrap()[0];
// Wait for the audio driver to have started the stream before running data
// callbacks. driver->Start() does a dispatch to the cubeb operation thread
// and starts the stream there.
- nsCOMPtr<nsIEventTarget> cubebOpThread = CUBEB_TASK_THREAD;
+ nsCOMPtr<nsIEventTarget> cubebOpThread =
+ CubebUtils::GetCubebOperationThread();
MOZ_ALWAYS_SUCCEEDS(SyncRunnable::DispatchToThread(
cubebOpThread, NS_NewRunnableFunction(__func__, [] {})));
@@ -393,85 +409,412 @@ MOZ_CAN_RUN_SCRIPT_BOUNDARY {
auto graph = MakeRefPtr<MockGraphInterface>(rate);
auto driver = MakeRefPtr<AudioCallbackDriver>(
- graph, nullptr, rate, 2, 1, nullptr, (void*)1, AudioInputType::Voice);
+ graph, nullptr, rate, 2, 1, nullptr, (void*)1, AudioInputType::Voice,
+ CUBEB_INPUT_PROCESSING_PARAM_NONE);
EXPECT_FALSE(driver->ThreadRunning()) << "Verify thread is not running";
EXPECT_FALSE(driver->IsStarted()) << "Verify thread is not started";
auto newDriver = MakeRefPtr<AudioCallbackDriver>(
- graph, nullptr, rate, 2, 1, nullptr, (void*)1, AudioInputType::Voice);
+ graph, nullptr, rate, 2, 1, nullptr, (void*)1, AudioInputType::Voice,
+ CUBEB_INPUT_PROCESSING_PARAM_NONE);
EXPECT_FALSE(newDriver->ThreadRunning()) << "Verify thread is not running";
EXPECT_FALSE(newDriver->IsStarted()) << "Verify thread is not started";
#ifdef DEBUG
- std::atomic<std::thread::id> threadInDriverIteration(
- (std::this_thread::get_id()));
+ std::atomic<std::thread::id> threadInDriverIteration{
+ std::this_thread::get_id()};
EXPECT_CALL(*graph, InDriverIteration(_)).WillRepeatedly([&] {
return std::this_thread::get_id() == threadInDriverIteration;
});
#endif
EXPECT_CALL(*graph, NotifyInputData(_, 0, rate, 1, _)).Times(AnyNumber());
- EXPECT_CALL(*graph, DeviceChanged);
+ // This only happens if the first fallback driver is stopped by the audio
+ // driver handover rather than the driver switch. It happens when the
+ // subsequent audio callback performs the switch.
+ EXPECT_CALL(*graph, NotifyInputStopped()).Times(AtMost(1));
+ Result<cubeb_input_processing_params, int> expected =
+ Err(CUBEB_ERROR_NOT_SUPPORTED);
+ EXPECT_CALL(*graph, NotifySetRequestedInputProcessingParamsResult(
+ driver.get(), CUBEB_INPUT_PROCESSING_PARAM_NONE,
+ Eq(std::ref(expected))));
+ EXPECT_CALL(*graph, NotifySetRequestedInputProcessingParamsResult(
+ newDriver.get(), CUBEB_INPUT_PROCESSING_PARAM_NONE,
+ Eq(std::ref(expected))));
graph->SetCurrentDriver(driver);
graph->SetEnsureNextIteration(true);
+ auto initPromise = TakeN(cubeb->StreamInitEvent(), 1);
// This starts the fallback driver.
driver->Start();
- RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
+ RefPtr<SmartMockCubebStream> stream;
+ std::tie(stream) = WaitFor(initPromise).unwrap()[0];
// Wait for the audio driver to have started or the DeviceChanged event will
// be ignored. driver->Start() does a dispatch to the cubeb operation thread
// and starts the stream there.
- nsCOMPtr<nsIEventTarget> cubebOpThread = CUBEB_TASK_THREAD;
+ nsCOMPtr<nsIEventTarget> cubebOpThread =
+ CubebUtils::GetCubebOperationThread();
MOZ_ALWAYS_SUCCEEDS(SyncRunnable::DispatchToThread(
cubebOpThread, NS_NewRunnableFunction(__func__, [] {})));
-#ifdef DEBUG
- AutoSetter as(threadInDriverIteration, std::this_thread::get_id());
-#endif
+ initPromise = TakeN(cubeb->StreamInitEvent(), 1);
+ Monitor mon(__func__);
+ bool canContinueToStartNextDriver = false;
+ bool continued = false;
// This marks the audio driver as running.
EXPECT_EQ(stream->ManualDataCallback(0),
MockCubebStream::KeepProcessing::Yes);
- // If a fallback driver callback happens between the audio callback above, and
- // the SwitchTo below, the audio driver will perform the switch instead of the
- // fallback since the fallback will have stopped. This test may therefore
- // intermittently take different code paths.
-
- // Stop the fallback driver by switching audio driver in the graph.
- {
- Monitor mon(__func__);
- MonitorAutoLock lock(mon);
- bool switched = false;
- graph->SwitchTo(newDriver, NS_NewRunnableFunction(__func__, [&] {
- MonitorAutoLock lock(mon);
- switched = true;
- lock.Notify();
- }));
- while (!switched) {
- lock.Wait();
- }
+ // To satisfy TSAN's lock-order-inversion checking we avoid locking stream's
+ // mMutex (by calling ManualDataCallback) under mon. The SwitchTo runnable
+ // below already locks mon under stream's mMutex.
+ MonitorAutoLock lock(mon);
+
+ // If a fallback driver callback happens between the audio callback
+ // above, and the SwitchTo below, the driver will enter
+ // `FallbackDriverState::None`, relying on the audio driver to
+ // iterate the graph, including performing the driver switch. This
+ // test may therefore intermittently take different code paths.
+ // Note however that the fallback driver runs every ~10ms while the
+ // time from the manual callback above to telling the mock graph to
+ // switch drivers below is much much shorter. The vast majority of
+ // test runs will exercise the intended code path.
+
+ // Make the fallback driver enter FallbackDriverState::Stopped by
+ // switching audio driver in the graph.
+ graph->SwitchTo(newDriver, NS_NewRunnableFunction(__func__, [&] {
+ MonitorAutoLock lock(mon);
+ // Block the fallback driver on its thread until
+ // the test on main thread has finished testing
+ // what it needs.
+ while (!canContinueToStartNextDriver) {
+ lock.Wait();
+ }
+ // Notify the test that it can take these
+ // variables off the stack now.
+ continued = true;
+ lock.Notify();
+ }));
+
+ // Wait for the fallback driver to stop running.
+ while (driver->OnFallback()) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
- {
+ if (driver->HasFallback()) {
+ // Driver entered FallbackDriverState::Stopped as desired.
+ // Proceed with a DeviceChangedCallback.
+
+ EXPECT_CALL(*graph, DeviceChanged);
+
+ {
#ifdef DEBUG
- AutoSetter as(threadInDriverIteration, std::thread::id());
+ AutoSetter as(threadInDriverIteration, std::thread::id());
#endif
- // After stopping the fallback driver, but before newDriver has stopped the
- // old audio driver, fire a DeviceChanged event to ensure it is handled
- // properly.
- AudioCallbackDriver::DeviceChangedCallback_s(driver);
+ // After stopping the fallback driver, but before newDriver has
+ // stopped the old audio driver, fire a DeviceChanged event to
+ // ensure it is handled properly.
+ AudioCallbackDriver::DeviceChangedCallback_s(driver);
+ }
+
+ EXPECT_FALSE(driver->OnFallback())
+ << "DeviceChangedCallback after stopping must not start the "
+ "fallback driver again";
}
+ // Iterate the audio driver on a background thread in case the fallback
+ // driver completed the handover to the audio driver before the switch
+ // above. Doing the switch would deadlock as the switch runnable waits on
+ // mon.
+ NS_DispatchBackgroundTask(NS_NewRunnableFunction(
+ "DeviceChangeAfterStop::postSwitchManualAudioCallback", [stream] {
+ // An audio callback after switching must tell the stream to stop.
+ EXPECT_EQ(stream->ManualDataCallback(0),
+ MockCubebStream::KeepProcessing::No);
+ }));
+
+ // Unblock the fallback driver.
+ canContinueToStartNextDriver = true;
+ lock.Notify();
+
+ // Wait for the fallback driver to continue, so we can clear the
+ // stack.
+ while (!continued) {
+ lock.Wait();
+ }
+
+ // Wait for newDriver's cubeb stream to init.
+ std::tie(stream) = WaitFor(initPromise).unwrap()[0];
+
graph->StopIterating();
newDriver->EnsureNextIteration();
while (newDriver->OnFallback()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
- // This will block until all events have been queued.
- MOZ_KnownLive(driver)->Shutdown();
- MOZ_KnownLive(newDriver)->Shutdown();
+ {
+#ifdef DEBUG
+ AutoSetter as(threadInDriverIteration, std::thread::id());
+#endif
+ EXPECT_EQ(stream->ManualDataCallback(0),
+ MockCubebStream::KeepProcessing::No);
+ }
+
// Drain the event queue.
NS_ProcessPendingEvents(nullptr);
}
+
+void TestInputProcessingOnStart(
+ MockCubeb* aCubeb, cubeb_input_processing_params aRequested,
+ const Result<cubeb_input_processing_params, int>& aExpected)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY {
+ const TrackRate rate = 44100;
+
+ auto graph = MakeRefPtr<NiceMock<MockGraphInterface>>(rate);
+ auto driver = MakeRefPtr<AudioCallbackDriver>(
+ graph, nullptr, rate, 2, 1, nullptr, nullptr, AudioInputType::Voice,
+ aRequested);
+ EXPECT_FALSE(driver->ThreadRunning()) << "Verify thread is not running";
+ EXPECT_FALSE(driver->IsStarted()) << "Verify thread is not started";
+
+#ifdef DEBUG
+ std::atomic_bool inGraphIteration{false};
+ ON_CALL(*graph, InDriverIteration(_)).WillByDefault([&] {
+ return inGraphIteration.load() && NS_IsMainThread();
+ });
+#endif
+ bool notified = false;
+ EXPECT_CALL(*graph, NotifyInputStopped).Times(0);
+ EXPECT_CALL(*graph, NotifySetRequestedInputProcessingParamsResult(
+ driver.get(), aRequested, Eq(std::ref(aExpected))))
+ .WillOnce([&] { notified = true; });
+
+ graph->SetCurrentDriver(driver);
+ auto initPromise = TakeN(aCubeb->StreamInitEvent(), 1);
+ driver->Start();
+ auto [stream] = WaitFor(initPromise).unwrap()[0];
+
+ // Wait for the audio driver to have started the stream before running data
+ // callbacks. driver->Start() does a dispatch to the cubeb operation thread
+ // and starts the stream there.
+ nsCOMPtr<nsIEventTarget> cubebOpThread =
+ CubebUtils::GetCubebOperationThread();
+ MOZ_ALWAYS_SUCCEEDS(SyncRunnable::DispatchToThread(
+ cubebOpThread, NS_NewRunnableFunction(__func__, [] {})));
+
+ // This makes the fallback driver stop on its next callback.
+ {
+#ifdef DEBUG
+ AutoSetter as(inGraphIteration, true);
+#endif
+ while (driver->OnFallback()) {
+ stream->ManualDataCallback(0);
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
+ }
+
+ while (!notified) {
+ NS_ProcessNextEvent();
+ }
+
+ // This will block untill all events have been executed.
+ MOZ_KnownLive(driver)->Shutdown();
+ EXPECT_FALSE(driver->ThreadRunning()) << "Verify thread is not running";
+ EXPECT_FALSE(driver->IsStarted()) << "Verify thread is not started";
+}
+
+TEST(TestAudioCallbackDriver, InputProcessingOnStart)
+{
+ constexpr cubeb_input_processing_params allParams =
+ CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION |
+ CUBEB_INPUT_PROCESSING_PARAM_AUTOMATIC_GAIN_CONTROL |
+ CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION |
+ CUBEB_INPUT_PROCESSING_PARAM_VOICE_ISOLATION;
+
+ MockCubeb* cubeb = new MockCubeb(MockCubeb::RunningMode::Manual);
+ CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
+
+ // Not supported by backend.
+ cubeb->SetSupportedInputProcessingParams(CUBEB_INPUT_PROCESSING_PARAM_NONE,
+ CUBEB_ERROR_NOT_SUPPORTED);
+ TestInputProcessingOnStart(cubeb,
+ CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION,
+ Err(CUBEB_ERROR_NOT_SUPPORTED));
+
+ // Not supported by params.
+ cubeb->SetSupportedInputProcessingParams(CUBEB_INPUT_PROCESSING_PARAM_NONE,
+ CUBEB_OK);
+ TestInputProcessingOnStart(cubeb,
+ CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION,
+ CUBEB_INPUT_PROCESSING_PARAM_NONE);
+
+ // Successful all.
+ cubeb->SetSupportedInputProcessingParams(allParams, CUBEB_OK);
+ TestInputProcessingOnStart(cubeb, allParams, allParams);
+
+ // Successful partial.
+ TestInputProcessingOnStart(cubeb,
+ CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION,
+ CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION);
+
+ // Not supported by stream.
+ cubeb->SetInputProcessingApplyRv(CUBEB_ERROR);
+ TestInputProcessingOnStart(
+ cubeb, CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION, Err(CUBEB_ERROR));
+}
+
+TEST(TestAudioCallbackDriver, InputProcessingWhileRunning)
+MOZ_CAN_RUN_SCRIPT_BOUNDARY {
+ constexpr TrackRate rate = 44100;
+ constexpr cubeb_input_processing_params allParams =
+ CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION |
+ CUBEB_INPUT_PROCESSING_PARAM_AUTOMATIC_GAIN_CONTROL |
+ CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION |
+ CUBEB_INPUT_PROCESSING_PARAM_VOICE_ISOLATION;
+ constexpr int applyError = 99;
+
+ int numNotifications = 0;
+ const auto signal = [&]() mutable {
+ MOZ_ASSERT(NS_IsMainThread());
+ ++numNotifications;
+ };
+ const auto waitForSignal = [&](int aNotification) {
+ while (numNotifications < aNotification) {
+ NS_ProcessNextEvent();
+ }
+ };
+ MockCubeb* cubeb = new MockCubeb(MockCubeb::RunningMode::Manual);
+ CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
+
+ auto graph = MakeRefPtr<NiceMock<MockGraphInterface>>(rate);
+ auto driver = MakeRefPtr<AudioCallbackDriver>(
+ graph, nullptr, rate, 2, 1, nullptr, nullptr, AudioInputType::Voice,
+ CUBEB_INPUT_PROCESSING_PARAM_NONE);
+ EXPECT_FALSE(driver->ThreadRunning()) << "Verify thread is not running";
+ EXPECT_FALSE(driver->IsStarted()) << "Verify thread is not started";
+
+ EXPECT_CALL(*graph, NotifyInputStopped).Times(0);
+ // Expectations
+ const Result<cubeb_input_processing_params, int> noneResult =
+ CUBEB_INPUT_PROCESSING_PARAM_NONE;
+ const Result<cubeb_input_processing_params, int> aecResult =
+ CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION;
+ const Result<cubeb_input_processing_params, int> allResult = allParams;
+ const Result<cubeb_input_processing_params, int> notSupportedResult =
+ Err(CUBEB_ERROR_NOT_SUPPORTED);
+ const Result<cubeb_input_processing_params, int> applyErrorResult =
+ Err(applyError);
+ {
+ InSequence s;
+
+ // Notified on start.
+ EXPECT_CALL(*graph, NotifySetRequestedInputProcessingParamsResult(
+ driver.get(), CUBEB_INPUT_PROCESSING_PARAM_NONE,
+ Eq(std::ref(notSupportedResult))))
+ .WillOnce(signal);
+ // Not supported by backend.
+ EXPECT_CALL(*graph, NotifySetRequestedInputProcessingParamsResult(
+ driver.get(),
+ CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION,
+ Eq(std::ref(notSupportedResult))))
+ .WillOnce(signal);
+ // Not supported by params.
+ EXPECT_CALL(*graph, NotifySetRequestedInputProcessingParamsResult(
+ driver.get(),
+ CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION,
+ Eq(std::ref(noneResult))))
+ .WillOnce(signal);
+ // Successful all.
+ EXPECT_CALL(*graph, NotifySetRequestedInputProcessingParamsResult(
+ driver.get(), allParams, Eq(std::ref(allResult))))
+ .WillOnce(signal);
+ // Successful partial.
+ EXPECT_CALL(*graph, NotifySetRequestedInputProcessingParamsResult(
+ driver.get(),
+ CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION,
+ Eq(std::ref(aecResult))))
+ .WillOnce(signal);
+ // Not supported by stream.
+ EXPECT_CALL(*graph, NotifySetRequestedInputProcessingParamsResult(
+ driver.get(),
+ CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION,
+ Eq(std::ref(applyErrorResult))))
+ .WillOnce(signal);
+ }
+
+#ifdef DEBUG
+ std::atomic_bool inGraphIteration{false};
+ ON_CALL(*graph, InDriverIteration(_)).WillByDefault([&] {
+ return inGraphIteration.load() && NS_IsMainThread();
+ });
+#endif
+
+ const auto setParams = [&](cubeb_input_processing_params aParams) {
+ {
+#ifdef DEBUG
+ AutoSetter as(inGraphIteration, true);
+#endif
+ driver->SetRequestedInputProcessingParams(aParams);
+ }
+ };
+
+ graph->SetCurrentDriver(driver);
+ auto initPromise = TakeN(cubeb->StreamInitEvent(), 1);
+ driver->Start();
+ auto [stream] = WaitFor(initPromise).unwrap()[0];
+
+ // Wait for the audio driver to have started the stream before running data
+ // callbacks. driver->Start() does a dispatch to the cubeb operation thread
+ // and starts the stream there.
+ nsCOMPtr<nsIEventTarget> cubebOpThread =
+ CubebUtils::GetCubebOperationThread();
+ MOZ_ALWAYS_SUCCEEDS(SyncRunnable::DispatchToThread(
+ cubebOpThread, NS_NewRunnableFunction(__func__, [] {})));
+
+ // This makes the fallback driver stop on its next callback.
+
+ {
+#ifdef DEBUG
+ AutoSetter as(inGraphIteration, true);
+#endif
+ while (driver->OnFallback()) {
+ stream->ManualDataCallback(0);
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
+ }
+ waitForSignal(1);
+
+ // Not supported by backend.
+ cubeb->SetSupportedInputProcessingParams(CUBEB_INPUT_PROCESSING_PARAM_NONE,
+ CUBEB_ERROR_NOT_SUPPORTED);
+ setParams(CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION);
+ waitForSignal(2);
+
+ // Not supported by params.
+ cubeb->SetSupportedInputProcessingParams(CUBEB_INPUT_PROCESSING_PARAM_NONE,
+ CUBEB_OK);
+ setParams(CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION);
+ waitForSignal(3);
+
+ // Successful all.
+ cubeb->SetSupportedInputProcessingParams(allParams, CUBEB_OK);
+ setParams(allParams);
+ waitForSignal(4);
+
+ // Successful partial.
+ setParams(CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION);
+ waitForSignal(5);
+
+ // Not supported by stream.
+ cubeb->SetInputProcessingApplyRv(applyError);
+ setParams(CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION);
+ waitForSignal(6);
+
+ // This will block untill all events have been executed.
+ MOZ_KnownLive(driver)->Shutdown();
+ EXPECT_FALSE(driver->ThreadRunning()) << "Verify thread is not running";
+ EXPECT_FALSE(driver->IsStarted()) << "Verify thread is not started";
+}
+
+} // namespace mozilla
diff --git a/dom/media/gtest/TestAudioInputProcessing.cpp b/dom/media/gtest/TestAudioInputProcessing.cpp
index d21c37a900..e357839768 100644
--- a/dom/media/gtest/TestAudioInputProcessing.cpp
+++ b/dom/media/gtest/TestAudioInputProcessing.cpp
@@ -428,3 +428,178 @@ TEST(TestAudioInputProcessing, Downmixing)
aip->Stop(graph);
track->Destroy();
}
+
+TEST(TestAudioInputProcessing, DisabledPlatformProcessing)
+{
+ const TrackRate rate = 44100;
+ const uint32_t channels = 1;
+ auto graph = MakeRefPtr<NiceMock<MockGraph>>(rate);
+ graph->Init(channels);
+
+ auto aip = MakeRefPtr<AudioInputProcessing>(channels);
+
+ MediaEnginePrefs settings;
+ settings.mUsePlatformProcessing = false;
+ settings.mAecOn = true;
+ aip->ApplySettings(graph, nullptr, settings);
+ aip->Start(graph);
+
+ EXPECT_EQ(aip->RequestedInputProcessingParams(graph),
+ CUBEB_INPUT_PROCESSING_PARAM_NONE);
+
+ aip->Stop(graph);
+ graph->Destroy();
+}
+
+TEST(TestAudioInputProcessing, EnabledPlatformProcessing)
+{
+ const TrackRate rate = 44100;
+ const uint32_t channels = 1;
+ auto graph = MakeRefPtr<NiceMock<MockGraph>>(rate);
+ graph->Init(channels);
+
+ auto aip = MakeRefPtr<AudioInputProcessing>(channels);
+
+ MediaEnginePrefs settings;
+ settings.mUsePlatformProcessing = true;
+ settings.mAecOn = true;
+ aip->ApplySettings(graph, nullptr, settings);
+ aip->Start(graph);
+
+ EXPECT_EQ(aip->RequestedInputProcessingParams(graph),
+ CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION);
+
+ aip->Stop(graph);
+ graph->Destroy();
+}
+
+namespace webrtc {
+bool operator==(const AudioProcessing::Config& aLhs,
+ const AudioProcessing::Config& aRhs) {
+ return aLhs.echo_canceller.enabled == aRhs.echo_canceller.enabled &&
+ (aLhs.gain_controller1.enabled == aRhs.gain_controller1.enabled ||
+ aLhs.gain_controller2.enabled == aRhs.gain_controller2.enabled) &&
+ aLhs.noise_suppression.enabled == aRhs.noise_suppression.enabled;
+}
+
+static std::ostream& operator<<(
+ std::ostream& aStream, const webrtc::AudioProcessing::Config& aConfig) {
+ aStream << "webrtc::AudioProcessing::Config[";
+ bool hadPrior = false;
+ if (aConfig.echo_canceller.enabled) {
+ aStream << "AEC";
+ hadPrior = true;
+ }
+ if (aConfig.gain_controller1.enabled || aConfig.gain_controller2.enabled) {
+ if (hadPrior) {
+ aStream << ", ";
+ }
+ aStream << "AGC";
+ }
+ if (aConfig.noise_suppression.enabled) {
+ if (hadPrior) {
+ aStream << ", ";
+ }
+ aStream << "NS";
+ }
+ aStream << "]";
+ return aStream;
+}
+} // namespace webrtc
+
+TEST(TestAudioInputProcessing, PlatformProcessing)
+{
+ const TrackRate rate = 44100;
+ const uint32_t channels = 1;
+ auto graph = MakeRefPtr<NiceMock<MockGraph>>(rate);
+ graph->Init(channels);
+
+ auto aip = MakeRefPtr<AudioInputProcessing>(channels);
+
+ MediaEnginePrefs settings;
+ settings.mUsePlatformProcessing = true;
+ settings.mAecOn = true;
+ aip->ApplySettings(graph, nullptr, settings);
+ aip->Start(graph);
+
+ webrtc::AudioProcessing::Config echoOnlyConfig;
+ echoOnlyConfig.echo_canceller.enabled = true;
+ webrtc::AudioProcessing::Config echoNoiseConfig = echoOnlyConfig;
+ echoNoiseConfig.noise_suppression.enabled = true;
+
+ // Config is applied, and platform processing requested.
+ EXPECT_EQ(aip->RequestedInputProcessingParams(graph),
+ CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION);
+ EXPECT_EQ(aip->AppliedConfig(graph), echoOnlyConfig);
+ EXPECT_FALSE(aip->IsPassThrough(graph));
+
+ // Platform processing params successfully applied.
+ aip->NotifySetRequestedInputProcessingParamsResult(
+ graph, CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION,
+ CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION);
+ // Turns off the equivalent APM config.
+ EXPECT_EQ(aip->AppliedConfig(graph), webrtc::AudioProcessing::Config());
+ EXPECT_TRUE(aip->IsPassThrough(graph));
+
+ // Simulate an error after a driver switch.
+ aip->NotifySetRequestedInputProcessingParamsResult(
+ graph, CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION, Err(CUBEB_ERROR));
+ // The APM config is turned back on, and platform processing is requested to
+ // be turned off.
+ EXPECT_EQ(aip->RequestedInputProcessingParams(graph),
+ CUBEB_INPUT_PROCESSING_PARAM_NONE);
+ EXPECT_EQ(aip->AppliedConfig(graph), echoOnlyConfig);
+ EXPECT_FALSE(aip->IsPassThrough(graph));
+
+ // Pretend there was a response for an old request.
+ aip->NotifySetRequestedInputProcessingParamsResult(
+ graph, CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION,
+ CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION);
+ // It does nothing since we are requesting NONE now.
+ EXPECT_EQ(aip->RequestedInputProcessingParams(graph),
+ CUBEB_INPUT_PROCESSING_PARAM_NONE);
+ EXPECT_EQ(aip->AppliedConfig(graph), echoOnlyConfig);
+ EXPECT_FALSE(aip->IsPassThrough(graph));
+
+ // Turn it off as requested.
+ aip->NotifySetRequestedInputProcessingParamsResult(
+ graph, CUBEB_INPUT_PROCESSING_PARAM_NONE,
+ CUBEB_INPUT_PROCESSING_PARAM_NONE);
+ EXPECT_EQ(aip->RequestedInputProcessingParams(graph),
+ CUBEB_INPUT_PROCESSING_PARAM_NONE);
+ EXPECT_EQ(aip->AppliedConfig(graph), echoOnlyConfig);
+ EXPECT_FALSE(aip->IsPassThrough(graph));
+
+ // Test partial support for the requested params.
+ settings.mNoiseOn = true;
+ aip->ApplySettings(graph, nullptr, settings);
+ EXPECT_EQ(aip->RequestedInputProcessingParams(graph),
+ CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION |
+ CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION);
+ EXPECT_EQ(aip->AppliedConfig(graph), echoNoiseConfig);
+ EXPECT_FALSE(aip->IsPassThrough(graph));
+ // Only noise suppression was supported in the platform.
+ aip->NotifySetRequestedInputProcessingParamsResult(
+ graph,
+ CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION |
+ CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION,
+ CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION);
+ // In the APM only echo cancellation is applied.
+ EXPECT_EQ(aip->AppliedConfig(graph), echoOnlyConfig);
+ EXPECT_FALSE(aip->IsPassThrough(graph));
+
+ // Test error for partial support.
+ aip->NotifySetRequestedInputProcessingParamsResult(
+ graph,
+ CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION |
+ CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION,
+ Err(CUBEB_ERROR));
+ // The full config is applied in the APM, and NONE is requested.
+ EXPECT_EQ(aip->RequestedInputProcessingParams(graph),
+ CUBEB_INPUT_PROCESSING_PARAM_NONE);
+ EXPECT_EQ(aip->AppliedConfig(graph), echoNoiseConfig);
+ EXPECT_FALSE(aip->IsPassThrough(graph));
+
+ aip->Stop(graph);
+ graph->Destroy();
+}
diff --git a/dom/media/gtest/TestAudioInputSource.cpp b/dom/media/gtest/TestAudioInputSource.cpp
index f3f18b26a9..5defd1d053 100644
--- a/dom/media/gtest/TestAudioInputSource.cpp
+++ b/dom/media/gtest/TestAudioInputSource.cpp
@@ -10,16 +10,22 @@
#include "gtest/gtest.h"
#include "MockCubeb.h"
+#include "mozilla/Result.h"
#include "mozilla/gtest/WaitFor.h"
#include "nsContentUtils.h"
using namespace mozilla;
using testing::ContainerEq;
-namespace {
+// Short-hand for DispatchToCurrentThread with a function.
#define DispatchFunction(f) \
NS_DispatchToCurrentThread(NS_NewRunnableFunction(__func__, f))
-} // namespace
+
+// Short-hand for draining the current threads event queue, i.e. processing
+// those runnables dispatched per above.
+#define ProcessEventQueue() \
+ while (NS_ProcessNextEvent(nullptr, false)) { \
+ }
class MockEventListener : public AudioInputSource::EventListener {
public:
@@ -63,7 +69,10 @@ TEST(TestAudioInputSource, StartAndStop)
// Make sure start and stop works.
{
- DispatchFunction([&] { ais->Start(); });
+ DispatchFunction([&] {
+ ais->Init();
+ ais->Start();
+ });
RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
EXPECT_TRUE(stream->mHasInput);
EXPECT_FALSE(stream->mHasOutput);
@@ -79,7 +88,10 @@ TEST(TestAudioInputSource, StartAndStop)
// Make sure restart is ok.
{
- DispatchFunction([&] { ais->Start(); });
+ DispatchFunction([&] {
+ ais->Init();
+ ais->Start();
+ });
RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
EXPECT_TRUE(stream->mHasInput);
EXPECT_FALSE(stream->mHasOutput);
@@ -133,7 +145,10 @@ TEST(TestAudioInputSource, DataOutputBeforeStartAndAfterStop)
EXPECT_TRUE(data.IsNull());
}
- DispatchFunction([&] { ais->Start(); });
+ DispatchFunction([&] {
+ ais->Init();
+ ais->Start();
+ });
RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
EXPECT_TRUE(stream->mHasInput);
EXPECT_FALSE(stream->mHasOutput);
@@ -206,7 +221,10 @@ TEST(TestAudioInputSource, ErrorCallback)
sourceRate, targetRate);
ASSERT_TRUE(ais);
- DispatchFunction([&] { ais->Start(); });
+ DispatchFunction([&] {
+ ais->Init();
+ ais->Start();
+ });
RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
EXPECT_TRUE(stream->mHasInput);
EXPECT_FALSE(stream->mHasOutput);
@@ -251,7 +269,10 @@ TEST(TestAudioInputSource, DeviceChangedCallback)
sourceRate, targetRate);
ASSERT_TRUE(ais);
- DispatchFunction([&] { ais->Start(); });
+ DispatchFunction([&] {
+ ais->Init();
+ ais->Start();
+ });
RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
EXPECT_TRUE(stream->mHasInput);
EXPECT_FALSE(stream->mHasOutput);
@@ -267,3 +288,82 @@ TEST(TestAudioInputSource, DeviceChangedCallback)
ais = nullptr; // Drop the SharedThreadPool here.
}
+
+TEST(TestAudioInputSource, InputProcessing)
+{
+ MockCubeb* cubeb = new MockCubeb();
+ CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
+
+ const AudioInputSource::Id sourceId = 1;
+ const CubebUtils::AudioDeviceID deviceId = (CubebUtils::AudioDeviceID)1;
+ const uint32_t channels = 2;
+ const PrincipalHandle testPrincipal =
+ MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
+ const TrackRate sourceRate = 44100;
+ const TrackRate targetRate = 48000;
+ using ProcessingPromise =
+ AudioInputSource::SetRequestedProcessingParamsPromise;
+
+ auto listener = MakeRefPtr<MockEventListener>();
+ EXPECT_CALL(*listener,
+ AudioStateCallback(
+ sourceId, AudioInputSource::EventListener::State::Started))
+ .Times(0);
+ EXPECT_CALL(*listener,
+ AudioStateCallback(
+ sourceId, AudioInputSource::EventListener::State::Stopped))
+ .Times(10);
+
+ RefPtr<AudioInputSource> ais = MakeRefPtr<AudioInputSource>(
+ std::move(listener), sourceId, deviceId, channels, true, testPrincipal,
+ sourceRate, targetRate);
+
+ const auto test =
+ [&](cubeb_input_processing_params aRequested,
+ const Result<cubeb_input_processing_params, int>& aExpected) {
+ RefPtr<ProcessingPromise> p;
+ DispatchFunction([&] {
+ ais->Init();
+ p = ais->SetRequestedProcessingParams(aRequested);
+ });
+ ProcessEventQueue();
+ EXPECT_EQ(WaitFor(p), aExpected);
+
+ DispatchFunction([&] { ais->Stop(); });
+ Unused << WaitFor(cubeb->StreamDestroyEvent());
+ };
+
+ // Not supported by backend.
+ cubeb->SetSupportedInputProcessingParams(CUBEB_INPUT_PROCESSING_PARAM_NONE,
+ CUBEB_ERROR_NOT_SUPPORTED);
+ test(CUBEB_INPUT_PROCESSING_PARAM_NONE, Err(CUBEB_ERROR_NOT_SUPPORTED));
+
+ // Not supported by params.
+ cubeb->SetSupportedInputProcessingParams(CUBEB_INPUT_PROCESSING_PARAM_NONE,
+ CUBEB_OK);
+ test(CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION,
+ CUBEB_INPUT_PROCESSING_PARAM_NONE);
+
+ constexpr cubeb_input_processing_params allParams =
+ CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION |
+ CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION |
+ CUBEB_INPUT_PROCESSING_PARAM_AUTOMATIC_GAIN_CONTROL |
+ CUBEB_INPUT_PROCESSING_PARAM_VOICE_ISOLATION;
+
+ // Successful all.
+ cubeb->SetSupportedInputProcessingParams(allParams, CUBEB_OK);
+ test(allParams, allParams);
+
+ // Successful partial.
+ test(CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION,
+ CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION);
+
+ // Not supported by stream.
+ // Note this also tests that AudioInputSource resets its configured params
+ // state from the previous successful test.
+ constexpr int propagatedError = 99;
+ cubeb->SetInputProcessingApplyRv(propagatedError);
+ test(CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION, Err(propagatedError));
+
+ ais = nullptr; // Drop the SharedThreadPool here.
+}
diff --git a/dom/media/gtest/TestAudioRingBuffer.cpp b/dom/media/gtest/TestAudioRingBuffer.cpp
index 082323efd1..3da39780f1 100644
--- a/dom/media/gtest/TestAudioRingBuffer.cpp
+++ b/dom/media/gtest/TestAudioRingBuffer.cpp
@@ -1094,7 +1094,7 @@ TEST(TestAudioRingBuffer, PrependSilenceNoWrapShort)
EXPECT_THAT(out, ElementsAre(2, 3, 4, 5, 0, 0, 6, 7));
}
-TEST(TestAudioRingBuffer, SetLengthBytesNoWrapFloat)
+TEST(TestAudioRingBuffer, EnsureLengthBytesNoWrapFloat)
{
AudioRingBuffer rb(6 * sizeof(float));
rb.SetSampleFormat(AUDIO_FORMAT_FLOAT32);
@@ -1106,7 +1106,7 @@ TEST(TestAudioRingBuffer, SetLengthBytesNoWrapFloat)
EXPECT_EQ(rb.AvailableWrite(), 0u);
EXPECT_EQ(rb.Capacity(), 6u);
- EXPECT_TRUE(rb.SetLengthBytes(11 * sizeof(float)));
+ EXPECT_TRUE(rb.EnsureLengthBytes(11 * sizeof(float)));
float out[10] = {};
rv = rb.Read(Span(out, 10));
EXPECT_EQ(rv, 5u);
@@ -1116,7 +1116,7 @@ TEST(TestAudioRingBuffer, SetLengthBytesNoWrapFloat)
EXPECT_THAT(out, ElementsAre(.1, .2, .3, .4, .5, 0, 0, 0, 0, 0));
}
-TEST(TestAudioRingBuffer, SetLengthBytesNoWrapShort)
+TEST(TestAudioRingBuffer, EnsureLengthBytesNoWrapShort)
{
AudioRingBuffer rb(6 * sizeof(short));
rb.SetSampleFormat(AUDIO_FORMAT_S16);
@@ -1128,7 +1128,7 @@ TEST(TestAudioRingBuffer, SetLengthBytesNoWrapShort)
EXPECT_EQ(rb.AvailableWrite(), 0u);
EXPECT_EQ(rb.Capacity(), 6u);
- EXPECT_TRUE(rb.SetLengthBytes(11 * sizeof(short)));
+ EXPECT_TRUE(rb.EnsureLengthBytes(11 * sizeof(short)));
short out[10] = {};
rv = rb.Read(Span(out, 10));
EXPECT_EQ(rv, 5u);
@@ -1138,7 +1138,7 @@ TEST(TestAudioRingBuffer, SetLengthBytesNoWrapShort)
EXPECT_THAT(out, ElementsAre(1, 2, 3, 4, 5, 0, 0, 0, 0, 0));
}
-TEST(TestAudioRingBuffer, SetLengthBytesWrap1PartFloat)
+TEST(TestAudioRingBuffer, EnsureLengthBytesWrap1PartFloat)
{
AudioRingBuffer rb(6 * sizeof(float));
rb.SetSampleFormat(AUDIO_FORMAT_FLOAT32);
@@ -1158,7 +1158,7 @@ TEST(TestAudioRingBuffer, SetLengthBytesWrap1PartFloat)
EXPECT_EQ(rb.AvailableRead(), 5u);
EXPECT_EQ(rb.AvailableWrite(), 0u);
- EXPECT_TRUE(rb.SetLengthBytes(11 * sizeof(float)));
+ EXPECT_TRUE(rb.EnsureLengthBytes(11 * sizeof(float)));
EXPECT_EQ(rb.AvailableRead(), 5u);
EXPECT_EQ(rb.AvailableWrite(), 5u);
@@ -1175,7 +1175,7 @@ TEST(TestAudioRingBuffer, SetLengthBytesWrap1PartFloat)
EXPECT_THAT(out, ElementsAre(.1, .2, .3, .4, .5, .6, .7, 0, 0, 0));
}
-TEST(TestAudioRingBuffer, SetLengthBytesWrap1PartShort)
+TEST(TestAudioRingBuffer, EnsureLengthBytesWrap1PartShort)
{
AudioRingBuffer rb(6 * sizeof(short));
rb.SetSampleFormat(AUDIO_FORMAT_S16);
@@ -1195,7 +1195,7 @@ TEST(TestAudioRingBuffer, SetLengthBytesWrap1PartShort)
EXPECT_EQ(rb.AvailableRead(), 5u);
EXPECT_EQ(rb.AvailableWrite(), 0u);
- EXPECT_TRUE(rb.SetLengthBytes(11 * sizeof(short)));
+ EXPECT_TRUE(rb.EnsureLengthBytes(11 * sizeof(short)));
EXPECT_EQ(rb.AvailableRead(), 5u);
EXPECT_EQ(rb.AvailableWrite(), 5u);
@@ -1212,7 +1212,7 @@ TEST(TestAudioRingBuffer, SetLengthBytesWrap1PartShort)
EXPECT_THAT(out, ElementsAre(1, 2, 3, 4, 5, 6, 7, 0, 0, 0));
}
-TEST(TestAudioRingBuffer, SetLengthBytesWrap2PartsFloat)
+TEST(TestAudioRingBuffer, EnsureLengthBytesWrap2PartsFloat)
{
AudioRingBuffer rb(6 * sizeof(float));
rb.SetSampleFormat(AUDIO_FORMAT_FLOAT32);
@@ -1232,7 +1232,7 @@ TEST(TestAudioRingBuffer, SetLengthBytesWrap2PartsFloat)
EXPECT_EQ(rb.AvailableRead(), 5u);
EXPECT_EQ(rb.AvailableWrite(), 0u);
- EXPECT_TRUE(rb.SetLengthBytes(8 * sizeof(float)));
+ EXPECT_TRUE(rb.EnsureLengthBytes(8 * sizeof(float)));
EXPECT_EQ(rb.AvailableRead(), 5u);
EXPECT_EQ(rb.AvailableWrite(), 2u);
@@ -1249,7 +1249,7 @@ TEST(TestAudioRingBuffer, SetLengthBytesWrap2PartsFloat)
EXPECT_THAT(out, ElementsAre(.1, .2, .3, .4, .5, .6, .7, 0));
}
-TEST(TestAudioRingBuffer, SetLengthBytesWrap2PartsShort)
+TEST(TestAudioRingBuffer, EnsureLengthBytesWrap2PartsShort)
{
AudioRingBuffer rb(6 * sizeof(short));
rb.SetSampleFormat(AUDIO_FORMAT_S16);
@@ -1269,7 +1269,7 @@ TEST(TestAudioRingBuffer, SetLengthBytesWrap2PartsShort)
EXPECT_EQ(rb.AvailableRead(), 5u);
EXPECT_EQ(rb.AvailableWrite(), 0u);
- EXPECT_TRUE(rb.SetLengthBytes(8 * sizeof(short)));
+ EXPECT_TRUE(rb.EnsureLengthBytes(8 * sizeof(short)));
EXPECT_EQ(rb.AvailableRead(), 5u);
EXPECT_EQ(rb.AvailableWrite(), 2u);
@@ -1285,3 +1285,29 @@ TEST(TestAudioRingBuffer, SetLengthBytesWrap2PartsShort)
EXPECT_EQ(rb.Capacity(), 8u);
EXPECT_THAT(out, ElementsAre(1, 2, 3, 4, 5, 6, 7, 0));
}
+
+TEST(TestAudioRingBuffer, EnsureLengthShorter)
+{
+ AudioRingBuffer rb(5 * sizeof(float));
+ rb.SetSampleFormat(AUDIO_FORMAT_FLOAT32);
+
+ float in[5] = {.1, .2, .3, .4, .5};
+ EXPECT_EQ(rb.Write(Span(in, 5)), 4u);
+ EXPECT_EQ(rb.AvailableRead(), 4u);
+ EXPECT_EQ(rb.AvailableWrite(), 0u);
+ EXPECT_EQ(rb.Capacity(), 5u);
+
+ float out[5] = {};
+ EXPECT_EQ(rb.Read(Span(out, 3)), 3u);
+ EXPECT_THAT(out, ElementsAre(.1, .2, .3, 0, 0));
+ EXPECT_EQ(rb.AvailableRead(), 1u);
+ EXPECT_EQ(rb.AvailableWrite(), 3u);
+
+ EXPECT_TRUE(rb.EnsureLengthBytes(3 * sizeof(float)));
+ EXPECT_EQ(rb.AvailableRead(), 1u);
+ EXPECT_EQ(rb.AvailableWrite(), 3u);
+ EXPECT_EQ(rb.Capacity(), 5u);
+ EXPECT_EQ(rb.Write(Span(in, 5)), 3u);
+ EXPECT_EQ(rb.Read(Span(out, 5)), 4u);
+ EXPECT_THAT(out, ElementsAre(.4, .1, .2, .3, 0));
+}
diff --git a/dom/media/gtest/TestAudioTrackGraph.cpp b/dom/media/gtest/TestAudioTrackGraph.cpp
index 7be1224ab9..b1202277ce 100644
--- a/dom/media/gtest/TestAudioTrackGraph.cpp
+++ b/dom/media/gtest/TestAudioTrackGraph.cpp
@@ -21,9 +21,13 @@
#include "WavDumper.h"
using namespace mozilla;
+using testing::Eq;
+using testing::InSequence;
+using testing::Return;
+using testing::StrictMock;
// Short-hand for InvokeAsync on the current thread.
-#define Invoke(f) InvokeAsync(GetCurrentSerialEventTarget(), __func__, f)
+#define InvokeAsync(f) InvokeAsync(GetCurrentSerialEventTarget(), __func__, f)
// Short-hand for DispatchToCurrentThread with a function.
#define DispatchFunction(f) \
@@ -33,6 +37,12 @@ using namespace mozilla;
#define DispatchMethod(t, m, args...) \
NS_DispatchToCurrentThread(NewRunnableMethod(__func__, t, m, ##args))
+// Short-hand for draining the current threads event queue, i.e. processing
+// those runnables dispatched per above.
+#define ProcessEventQueue() \
+ while (NS_ProcessNextEvent(nullptr, false)) { \
+ }
+
namespace {
#ifdef MOZ_WEBRTC
/*
@@ -111,6 +121,44 @@ struct StopNonNativeInput : public ControlMessage {
void Run() override { mInputTrack->StopAudio(); }
};
+// Helper for detecting when fallback driver has been switched away, for use
+// with RunningMode::Manual.
+class OnFallbackListener : public MediaTrackListener {
+ const RefPtr<MediaTrack> mTrack;
+ Atomic<bool> mOnFallback{true};
+
+ public:
+ explicit OnFallbackListener(MediaTrack* aTrack) : mTrack(aTrack) {}
+
+ void Reset() { mOnFallback = true; }
+ bool OnFallback() { return mOnFallback; }
+
+ void NotifyOutput(MediaTrackGraph*, TrackTime) override {
+ if (auto* ad =
+ mTrack->GraphImpl()->CurrentDriver()->AsAudioCallbackDriver()) {
+ mOnFallback = ad->OnFallback();
+ }
+ }
+};
+
+class MockAudioDataListener : public AudioDataListener {
+ protected:
+ ~MockAudioDataListener() = default;
+
+ public:
+ MockAudioDataListener() = default;
+
+ MOCK_METHOD(uint32_t, RequestedInputChannelCount, (MediaTrackGraph*),
+ (const));
+ MOCK_METHOD(cubeb_input_processing_params, RequestedInputProcessingParams,
+ (MediaTrackGraph*), (const));
+ MOCK_METHOD(bool, IsVoiceInput, (MediaTrackGraph*), (const));
+ MOCK_METHOD(void, DeviceChanged, (MediaTrackGraph*));
+ MOCK_METHOD(void, Disconnect, (MediaTrackGraph*));
+ MOCK_METHOD(void, NotifySetRequestedInputProcessingParamsResult,
+ (MediaTrackGraph*, cubeb_input_processing_params,
+ (const Result<cubeb_input_processing_params, int>&)));
+};
} // namespace
/*
@@ -164,7 +212,7 @@ TEST(TestAudioTrackGraph, DifferentDeviceIDs)
// graph from the global hash table and let it shutdown.
using SourceTrackPromise = MozPromise<SourceMediaTrack*, nsresult, true>;
- auto p = Invoke([g] {
+ auto p = InvokeAsync([g] {
return SourceTrackPromise::CreateAndResolve(
g->CreateSourceTrack(MediaSegment::AUDIO), __func__);
});
@@ -256,7 +304,7 @@ TEST(TestAudioTrackGraph, NotifyDeviceStarted)
nullptr, GetMainThreadSerialEventTarget());
RefPtr<SourceMediaTrack> dummySource;
- Unused << WaitFor(Invoke([&] {
+ Unused << WaitFor(InvokeAsync([&] {
// Dummy track to make graph rolling. Add it and remove it to remove the
// graph from the global hash table and let it shutdown.
dummySource = graph->CreateSourceTrack(MediaSegment::AUDIO);
@@ -522,7 +570,7 @@ TEST(TestAudioTrackGraph, NonNativeInputTrackErrorCallback)
class TestDeviceInputConsumerTrack : public DeviceInputConsumerTrack {
public:
static TestDeviceInputConsumerTrack* Create(MediaTrackGraph* aGraph) {
- MOZ_ASSERT(NS_IsMainThread());
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
TestDeviceInputConsumerTrack* track =
new TestDeviceInputConsumerTrack(aGraph->GraphRate());
aGraph->AddTrack(track);
@@ -530,7 +578,7 @@ class TestDeviceInputConsumerTrack : public DeviceInputConsumerTrack {
}
void Destroy() {
- MOZ_ASSERT(NS_IsMainThread());
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
DisconnectDeviceInput();
DeviceInputConsumerTrack::Destroy();
}
@@ -543,7 +591,7 @@ class TestDeviceInputConsumerTrack : public DeviceInputConsumerTrack {
if (mInputs.IsEmpty()) {
GetData<AudioSegment>()->AppendNullData(aTo - aFrom);
} else {
- MOZ_ASSERT(mInputs.Length() == 1);
+ MOZ_RELEASE_ASSERT(mInputs.Length() == 1);
AudioSegment data;
DeviceInputConsumerTrack::GetInputSourceData(data, aFrom, aTo);
GetData<AudioSegment>()->AppendFrom(&data);
@@ -555,7 +603,7 @@ class TestDeviceInputConsumerTrack : public DeviceInputConsumerTrack {
return 0;
}
DeviceInputTrack* t = mInputs[0]->GetSource()->AsDeviceInputTrack();
- MOZ_ASSERT(t);
+ MOZ_RELEASE_ASSERT(t);
return t->NumberOfChannels();
}
@@ -574,30 +622,23 @@ TEST(TestAudioTrackGraph, DeviceChangedCallback)
CubebUtils::PreferredSampleRate(/* aShouldResistFingerprinting */ false),
nullptr, GetMainThreadSerialEventTarget());
- class TestAudioDataListener : public AudioDataListener {
+ class TestAudioDataListener : public StrictMock<MockAudioDataListener> {
public:
- TestAudioDataListener(uint32_t aChannelCount, bool aIsVoice)
- : mChannelCount(aChannelCount),
- mIsVoice(aIsVoice),
- mDeviceChangedCount(0) {}
-
- uint32_t RequestedInputChannelCount(MediaTrackGraph* aGraph) override {
- return mChannelCount;
- }
- bool IsVoiceInput(MediaTrackGraph* aGraph) const override {
- return mIsVoice;
- };
- void DeviceChanged(MediaTrackGraph* aGraph) override {
- ++mDeviceChangedCount;
+ TestAudioDataListener(uint32_t aChannelCount, bool aIsVoice) {
+ EXPECT_CALL(*this, RequestedInputChannelCount)
+ .WillRepeatedly(Return(aChannelCount));
+ EXPECT_CALL(*this, RequestedInputProcessingParams)
+ .WillRepeatedly(Return(CUBEB_INPUT_PROCESSING_PARAM_NONE));
+ EXPECT_CALL(*this, IsVoiceInput).WillRepeatedly(Return(aIsVoice));
+ {
+ InSequence s;
+ EXPECT_CALL(*this, DeviceChanged);
+ EXPECT_CALL(*this, Disconnect);
+ }
}
- void Disconnect(MediaTrackGraph* aGraph) override{/* Ignored */};
- uint32_t DeviceChangedCount() { return mDeviceChangedCount; }
private:
~TestAudioDataListener() = default;
- const uint32_t mChannelCount;
- const bool mIsVoice;
- std::atomic<uint32_t> mDeviceChangedCount;
};
// Create a full-duplex AudioCallbackDriver by creating a NativeInputTrack.
@@ -610,7 +651,7 @@ TEST(TestAudioTrackGraph, DeviceChangedCallback)
EXPECT_TRUE(track1->ConnectedToNativeDevice());
EXPECT_FALSE(track1->ConnectedToNonNativeDevice());
auto started =
- Invoke([&] { return graphImpl->NotifyWhenDeviceStarted(nullptr); });
+ InvokeAsync([&] { return graphImpl->NotifyWhenDeviceStarted(nullptr); });
RefPtr<SmartMockCubebStream> stream1 = WaitFor(cubeb->StreamInitEvent());
EXPECT_TRUE(stream1->mHasInput);
EXPECT_TRUE(stream1->mHasOutput);
@@ -648,9 +689,6 @@ TEST(TestAudioTrackGraph, DeviceChangedCallback)
WaitFor(cubeb->StreamDestroyEvent());
EXPECT_EQ(destroyedStream.get(), stream2.get());
- // Make sure we only have one device-changed event for the NativeInputTrack.
- EXPECT_EQ(listener2->DeviceChangedCount(), 1U);
-
// Destroy the NativeInputTrack.
DispatchFunction([&] {
track1->DisconnectDeviceInput();
@@ -658,9 +696,6 @@ TEST(TestAudioTrackGraph, DeviceChangedCallback)
});
destroyedStream = WaitFor(cubeb->StreamDestroyEvent());
EXPECT_EQ(destroyedStream.get(), stream1.get());
-
- // Make sure we only have one device-changed event for the NativeInputTrack.
- EXPECT_EQ(listener1->DeviceChangedCount(), 1U);
}
// The native audio stream (a.k.a. GraphDriver) and the non-native audio stream
@@ -692,57 +727,32 @@ TEST(TestAudioTrackGraph, RestartAudioIfMaxChannelCountChanged)
// A test-only AudioDataListener that simulates AudioInputProcessing's setter
// and getter for the input channel count.
- class TestAudioDataListener : public AudioDataListener {
+ class TestAudioDataListener : public StrictMock<MockAudioDataListener> {
public:
- TestAudioDataListener(uint32_t aChannelCount, bool aIsVoice)
- : mChannelCount(aChannelCount), mIsVoice(aIsVoice) {}
+ TestAudioDataListener(uint32_t aChannelCount, bool aIsVoice) {
+ EXPECT_CALL(*this, RequestedInputChannelCount)
+ .WillRepeatedly(Return(aChannelCount));
+ EXPECT_CALL(*this, RequestedInputProcessingParams)
+ .WillRepeatedly(Return(CUBEB_INPUT_PROCESSING_PARAM_NONE));
+ EXPECT_CALL(*this, IsVoiceInput).WillRepeatedly(Return(aIsVoice));
+ EXPECT_CALL(*this, Disconnect);
+ }
// Main thread API
void SetInputChannelCount(MediaTrackGraph* aGraph,
CubebUtils::AudioDeviceID aDevice,
uint32_t aChannelCount) {
- MOZ_ASSERT(NS_IsMainThread());
-
- struct Message : public ControlMessage {
- MediaTrackGraph* mGraph;
- TestAudioDataListener* mListener;
- CubebUtils::AudioDeviceID mDevice;
- uint32_t mChannelCount;
-
- Message(MediaTrackGraph* aGraph, TestAudioDataListener* aListener,
- CubebUtils::AudioDeviceID aDevice, uint32_t aChannelCount)
- : ControlMessage(nullptr),
- mGraph(aGraph),
- mListener(aListener),
- mDevice(aDevice),
- mChannelCount(aChannelCount) {}
- void Run() override {
- mListener->mChannelCount = mChannelCount;
- mGraph->ReevaluateInputDevice(mDevice);
- }
- };
-
- static_cast<MediaTrackGraphImpl*>(aGraph)->AppendMessage(
- MakeUnique<Message>(aGraph, this, aDevice, aChannelCount));
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ static_cast<MediaTrackGraphImpl*>(aGraph)
+ ->QueueControlMessageWithNoShutdown(
+ [this, self = RefPtr(this), aGraph, aDevice, aChannelCount] {
+ EXPECT_CALL(*this, RequestedInputChannelCount)
+ .WillRepeatedly(Return(aChannelCount));
+ aGraph->ReevaluateInputDevice(aDevice);
+ });
}
- // Graph thread APIs: AudioDataListenerInterface implementations.
- uint32_t RequestedInputChannelCount(MediaTrackGraph* aGraph) override {
- aGraph->AssertOnGraphThread();
- return mChannelCount;
- }
- bool IsVoiceInput(MediaTrackGraph* aGraph) const override {
- return mIsVoice;
- };
- void DeviceChanged(MediaTrackGraph* aGraph) override { /* Ignored */
- }
- void Disconnect(MediaTrackGraph* aGraph) override{/* Ignored */};
private:
~TestAudioDataListener() = default;
-
- // Graph thread-only.
- uint32_t mChannelCount;
- // Any thread.
- const bool mIsVoice;
};
// Request a new input channel count and expect to have a new stream.
@@ -841,8 +851,8 @@ TEST(TestAudioTrackGraph, RestartAudioIfMaxChannelCountChanged)
EXPECT_TRUE(track1->ConnectedToNativeDevice());
EXPECT_FALSE(track1->ConnectedToNonNativeDevice());
- auto started =
- Invoke([&] { return graphImpl->NotifyWhenDeviceStarted(nullptr); });
+ auto started = InvokeAsync(
+ [&] { return graphImpl->NotifyWhenDeviceStarted(nullptr); });
nativeStream = WaitFor(cubeb->StreamInitEvent());
EXPECT_TRUE(nativeStream->mHasInput);
EXPECT_TRUE(nativeStream->mHasOutput);
@@ -947,30 +957,18 @@ TEST(TestAudioTrackGraph, RestartAudioIfMaxChannelCountChanged)
// AudioDataListener. However, it only tests when MOZ_WEBRTC is defined.
TEST(TestAudioTrackGraph, SwitchNativeInputDevice)
{
- class TestAudioDataListener : public AudioDataListener {
+ class TestAudioDataListener : public StrictMock<MockAudioDataListener> {
public:
- TestAudioDataListener(uint32_t aChannelCount, bool aIsVoice)
- : mChannelCount(aChannelCount),
- mIsVoice(aIsVoice),
- mDeviceChangedCount(0) {}
-
- uint32_t RequestedInputChannelCount(MediaTrackGraph* aGraph) override {
- return mChannelCount;
- }
- bool IsVoiceInput(MediaTrackGraph* aGraph) const override {
- return mIsVoice;
- };
- void DeviceChanged(MediaTrackGraph* aGraph) override {
- ++mDeviceChangedCount;
+ TestAudioDataListener(uint32_t aChannelCount, bool aIsVoice) {
+ EXPECT_CALL(*this, RequestedInputChannelCount)
+ .WillRepeatedly(Return(aChannelCount));
+ EXPECT_CALL(*this, RequestedInputProcessingParams)
+ .WillRepeatedly(Return(CUBEB_INPUT_PROCESSING_PARAM_NONE));
+ EXPECT_CALL(*this, IsVoiceInput).WillRepeatedly(Return(aIsVoice));
}
- void Disconnect(MediaTrackGraph* aGraph) override{/* Ignored */};
- uint32_t DeviceChangedCount() { return mDeviceChangedCount; }
private:
~TestAudioDataListener() = default;
- const uint32_t mChannelCount;
- const bool mIsVoice;
- std::atomic<uint32_t> mDeviceChangedCount;
};
MockCubeb* cubeb = new MockCubeb();
@@ -1049,11 +1047,12 @@ TEST(TestAudioTrackGraph, SwitchNativeInputDevice)
RefPtr<TestDeviceInputConsumerTrack> track1 =
TestDeviceInputConsumerTrack::Create(graph);
RefPtr<TestAudioDataListener> listener1 = new TestAudioDataListener(1, false);
+ EXPECT_CALL(*listener1, Disconnect);
track1->ConnectDeviceInput(device1, listener1, PRINCIPAL_HANDLE_NONE);
EXPECT_EQ(track1->DeviceId().value(), device1);
auto started =
- Invoke([&] { return graph->NotifyWhenDeviceStarted(nullptr); });
+ InvokeAsync([&] { return graph->NotifyWhenDeviceStarted(nullptr); });
RefPtr<SmartMockCubebStream> stream1 = WaitFor(cubeb->StreamInitEvent());
EXPECT_TRUE(stream1->mHasInput);
@@ -1069,6 +1068,7 @@ TEST(TestAudioTrackGraph, SwitchNativeInputDevice)
RefPtr<TestDeviceInputConsumerTrack> track2 =
TestDeviceInputConsumerTrack::Create(graph);
RefPtr<TestAudioDataListener> listener2 = new TestAudioDataListener(2, false);
+ EXPECT_CALL(*listener2, Disconnect).Times(2);
track2->ConnectDeviceInput(device2, listener2, PRINCIPAL_HANDLE_NONE);
EXPECT_EQ(track2->DeviceId().value(), device2);
@@ -1085,6 +1085,7 @@ TEST(TestAudioTrackGraph, SwitchNativeInputDevice)
RefPtr<TestDeviceInputConsumerTrack> track3 =
TestDeviceInputConsumerTrack::Create(graph);
RefPtr<TestAudioDataListener> listener3 = new TestAudioDataListener(1, false);
+ EXPECT_CALL(*listener3, Disconnect).Times(2);
track3->ConnectDeviceInput(device3, listener3, PRINCIPAL_HANDLE_NONE);
EXPECT_EQ(track3->DeviceId().value(), device3);
@@ -1160,7 +1161,7 @@ TEST(TestAudioTrackGraph, ErrorCallback)
// output from the graph.
RefPtr<AudioProcessingTrack> processingTrack;
RefPtr<AudioInputProcessing> listener;
- auto started = Invoke([&] {
+ auto started = InvokeAsync([&] {
processingTrack = AudioProcessingTrack::Create(graph);
listener = new AudioInputProcessing(2);
QueueExpectIsPassThrough(processingTrack, listener);
@@ -1225,7 +1226,7 @@ TEST(TestAudioTrackGraph, AudioProcessingTrack)
RefPtr<ProcessedMediaTrack> outputTrack;
RefPtr<MediaInputPort> port;
RefPtr<AudioInputProcessing> listener;
- auto p = Invoke([&] {
+ auto p = InvokeAsync([&] {
processingTrack = AudioProcessingTrack::Create(graph);
outputTrack = graph->CreateForwardedInputTrack(MediaSegment::AUDIO);
outputTrack->QueueSetAutoend(false);
@@ -1282,7 +1283,7 @@ TEST(TestAudioTrackGraph, AudioProcessingTrack)
EXPECT_EQ(estimatedFreq, inputFrequency);
std::cerr << "PreSilence: " << preSilenceSamples << std::endl;
- // We buffer 128 frames. See DeviceInputTrack::ProcessInput.
+ // We buffer 128 frames. See NativeInputTrack::NotifyInputData.
EXPECT_GE(preSilenceSamples, 128U);
// If the fallback system clock driver is doing a graph iteration before the
// first audio driver iteration comes in, that iteration is ignored and
@@ -1297,13 +1298,17 @@ TEST(TestAudioTrackGraph, AudioProcessingTrack)
TEST(TestAudioTrackGraph, ReConnectDeviceInput)
{
- MockCubeb* cubeb = new MockCubeb();
+ MockCubeb* cubeb = new MockCubeb(MockCubeb::RunningMode::Manual);
CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
// 48k is a native processing rate, and avoids a resampling pass compared
// to 44.1k. The resampler may add take a few frames to stabilize, which show
// as unexected discontinuities in the test.
const TrackRate rate = 48000;
+ // Use a drift factor so that we don't dont produce perfect 10ms-chunks.
+ // This will exercise whatever buffers are in the audio processing pipeline,
+ // and the bookkeeping surrounding them.
+ const long step = 10 * rate * 1111 / 1000 / PR_MSEC_PER_SEC;
MediaTrackGraph* graph = MediaTrackGraphImpl::GetInstance(
MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*Window ID*/ 1, rate, nullptr,
@@ -1315,7 +1320,8 @@ TEST(TestAudioTrackGraph, ReConnectDeviceInput)
RefPtr<ProcessedMediaTrack> outputTrack;
RefPtr<MediaInputPort> port;
RefPtr<AudioInputProcessing> listener;
- auto p = Invoke([&] {
+ RefPtr<OnFallbackListener> fallbackListener;
+ DispatchFunction([&] {
processingTrack = AudioProcessingTrack::Create(graph);
outputTrack = graph->CreateForwardedInputTrack(MediaSegment::AUDIO);
outputTrack->QueueSetAutoend(false);
@@ -1337,54 +1343,65 @@ TEST(TestAudioTrackGraph, ReConnectDeviceInput)
settings.mAgc2Forced = true;
QueueApplySettings(processingTrack, listener, settings);
- return graph->NotifyWhenDeviceStarted(nullptr);
+ fallbackListener = new OnFallbackListener(processingTrack);
+ processingTrack->AddListener(fallbackListener);
});
RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
EXPECT_TRUE(stream->mHasInput);
- Unused << WaitFor(p);
- // Set a drift factor so that we don't dont produce perfect 10ms-chunks. This
- // will exercise whatever buffers are in the audio processing pipeline, and
- // the bookkeeping surrounding them.
- stream->SetDriftFactor(1.111);
+ while (
+ stream->State()
+ .map([](cubeb_state aState) { return aState != CUBEB_STATE_STARTED; })
+ .valueOr(true)) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
- // Wait for a second worth of audio data. GoFaster is dispatched through a
- // ControlMessage so that it is called in the first audio driver iteration.
- // Otherwise the audio driver might be going very fast while the fallback
- // system clock driver is still in an iteration.
- DispatchFunction([&] {
- processingTrack->GraphImpl()->AppendMessage(MakeUnique<GoFaster>(cubeb));
- });
- {
- uint32_t totalFrames = 0;
- WaitUntil(stream->FramesProcessedEvent(), [&](uint32_t aFrames) {
- totalFrames += aFrames;
- return totalFrames > static_cast<uint32_t>(graph->GraphRate());
- });
+ // Wait for the AudioCallbackDriver to come into effect.
+ while (fallbackListener->OnFallback()) {
+ EXPECT_EQ(stream->ManualDataCallback(0),
+ MockCubebStream::KeepProcessing::Yes);
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
+
+ // Iterate for a second worth of audio data.
+ for (long frames = 0; frames < graph->GraphRate(); frames += step) {
+ stream->ManualDataCallback(step);
}
- cubeb->DontGoFaster();
// Close the input to see that no asserts go off due to bad state.
DispatchFunction([&] { processingTrack->DisconnectDeviceInput(); });
- stream = WaitFor(cubeb->StreamInitEvent());
+ // Dispatch the disconnect message.
+ ProcessEventQueue();
+ // Run the disconnect message.
+ EXPECT_EQ(stream->ManualDataCallback(0),
+ MockCubebStream::KeepProcessing::Yes);
+ // Switch driver.
+ auto initPromise = TakeN(cubeb->StreamInitEvent(), 1);
+ EXPECT_EQ(stream->ManualDataCallback(0), MockCubebStream::KeepProcessing::No);
+ std::tie(stream) = WaitFor(initPromise).unwrap()[0];
EXPECT_FALSE(stream->mHasInput);
- Unused << WaitFor(
- Invoke([&] { return graph->NotifyWhenDeviceStarted(nullptr); }));
- // Output-only. Wait for another second before unmuting.
- DispatchFunction([&] {
- processingTrack->GraphImpl()->AppendMessage(MakeUnique<GoFaster>(cubeb));
- });
- {
- uint32_t totalFrames = 0;
- WaitUntil(stream->FramesProcessedEvent(), [&](uint32_t aFrames) {
- totalFrames += aFrames;
- return totalFrames > static_cast<uint32_t>(graph->GraphRate());
- });
+ while (
+ stream->State()
+ .map([](cubeb_state aState) { return aState != CUBEB_STATE_STARTED; })
+ .valueOr(true)) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
+
+ // Wait for the new AudioCallbackDriver to come into effect.
+ fallbackListener->Reset();
+ while (fallbackListener->OnFallback()) {
+ EXPECT_EQ(stream->ManualDataCallback(0),
+ MockCubebStream::KeepProcessing::Yes);
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
+
+ // Output-only. Iterate for another second before unmuting.
+ for (long frames = 0; frames < graph->GraphRate(); frames += step) {
+ stream->ManualDataCallback(step);
}
- cubeb->DontGoFaster();
// Re-open the input to again see that no asserts go off due to bad state.
DispatchFunction([&] {
@@ -1392,27 +1409,40 @@ TEST(TestAudioTrackGraph, ReConnectDeviceInput)
processingTrack->ConnectDeviceInput(deviceId, listener,
PRINCIPAL_HANDLE_NONE);
});
-
- stream = WaitFor(cubeb->StreamInitEvent());
+ // Dispatch the connect message.
+ ProcessEventQueue();
+ // Run the connect message.
+ EXPECT_EQ(stream->ManualDataCallback(0),
+ MockCubebStream::KeepProcessing::Yes);
+ // Switch driver.
+ initPromise = TakeN(cubeb->StreamInitEvent(), 1);
+ EXPECT_EQ(stream->ManualDataCallback(0), MockCubebStream::KeepProcessing::No);
+ std::tie(stream) = WaitFor(initPromise).unwrap()[0];
EXPECT_TRUE(stream->mHasInput);
- Unused << WaitFor(
- Invoke([&] { return graph->NotifyWhenDeviceStarted(nullptr); }));
- // Full-duplex. Wait for another second before finishing.
- DispatchFunction([&] {
- processingTrack->GraphImpl()->AppendMessage(MakeUnique<GoFaster>(cubeb));
- });
- {
- uint32_t totalFrames = 0;
- WaitUntil(stream->FramesProcessedEvent(), [&](uint32_t aFrames) {
- totalFrames += aFrames;
- return totalFrames > static_cast<uint32_t>(graph->GraphRate());
- });
+ while (
+ stream->State()
+ .map([](cubeb_state aState) { return aState != CUBEB_STATE_STARTED; })
+ .valueOr(true)) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
+
+ // Wait for the new AudioCallbackDriver to come into effect.
+ fallbackListener->Reset();
+ while (fallbackListener->OnFallback()) {
+ EXPECT_EQ(stream->ManualDataCallback(0),
+ MockCubebStream::KeepProcessing::Yes);
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
+
+ // Full-duplex. Iterate for another second before finishing.
+ for (long frames = 0; frames < graph->GraphRate(); frames += step) {
+ stream->ManualDataCallback(step);
}
- cubeb->DontGoFaster();
// Clean up.
DispatchFunction([&] {
+ processingTrack->RemoveListener(fallbackListener);
outputTrack->RemoveAudioOutput((void*)1);
outputTrack->Destroy();
port->Destroy();
@@ -1422,7 +1452,14 @@ TEST(TestAudioTrackGraph, ReConnectDeviceInput)
processingTrack->Destroy();
});
- uint32_t inputRate = stream->SampleRate();
+ // Dispatch the clean-up messages.
+ ProcessEventQueue();
+ // Run the clean-up messages.
+ EXPECT_EQ(stream->ManualDataCallback(0),
+ MockCubebStream::KeepProcessing::Yes);
+ // Shut down driver.
+ EXPECT_EQ(stream->ManualDataCallback(0), MockCubebStream::KeepProcessing::No);
+
uint32_t inputFrequency = stream->InputFrequency();
uint64_t preSilenceSamples;
uint32_t estimatedFreq;
@@ -1431,17 +1468,12 @@ TEST(TestAudioTrackGraph, ReConnectDeviceInput)
WaitFor(stream->OutputVerificationEvent());
EXPECT_EQ(estimatedFreq, inputFrequency);
- std::cerr << "PreSilence: " << preSilenceSamples << std::endl;
- // We buffer 10ms worth of frames in non-passthrough mode, plus up to 128
- // frames as we round up to the nearest block. See
- // AudioInputProcessing::Process and DeviceInputTrack::PrcoessInput.
- EXPECT_GE(preSilenceSamples, 128U + inputRate / 100);
- // If the fallback system clock driver is doing a graph iteration before the
- // first audio driver iteration comes in, that iteration is ignored and
- // results in zeros. It takes one fallback driver iteration *after* the audio
- // driver has started to complete the switch, *usually* resulting two
- // 10ms-iterations of silence; sometimes only one.
- EXPECT_LE(preSilenceSamples, 128U + 3 * inputRate / 100 /* 3*10ms */);
+ std::cerr << "PreSilence: " << preSilenceSamples << "\n";
+ // We buffer 128 frames. See NativeInputTrack::NotifyInputData.
+ // When not in passthrough the AudioInputProcessing packetizer also buffers
+ // 10ms of silence, pulled in from NativeInputTrack when being run by the
+ // fallback SystemClockDriver.
+ EXPECT_EQ(preSilenceSamples, WEBAUDIO_BLOCK_SIZE + rate / 100);
// The waveform from AudioGenerator starts at 0, but we don't control its
// ending, so we expect a discontinuity there. Note that this check is only
// for the waveform on the stream *after* re-opening the input.
@@ -1467,7 +1499,7 @@ float rmsf32(AudioDataValue* aSamples, uint32_t aChannels, uint32_t aFrames) {
TEST(TestAudioTrackGraph, AudioProcessingTrackDisabling)
{
- MockCubeb* cubeb = new MockCubeb();
+ MockCubeb* cubeb = new MockCubeb(MockCubeb::RunningMode::Manual);
CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
MediaTrackGraph* graph = MediaTrackGraphImpl::GetInstance(
@@ -1481,7 +1513,8 @@ TEST(TestAudioTrackGraph, AudioProcessingTrackDisabling)
RefPtr<ProcessedMediaTrack> outputTrack;
RefPtr<MediaInputPort> port;
RefPtr<AudioInputProcessing> listener;
- auto p = Invoke([&] {
+ RefPtr<OnFallbackListener> fallbackListener;
+ DispatchFunction([&] {
processingTrack = AudioProcessingTrack::Create(graph);
outputTrack = graph->CreateForwardedInputTrack(MediaSegment::AUDIO);
outputTrack->QueueSetAutoend(false);
@@ -1495,32 +1528,36 @@ TEST(TestAudioTrackGraph, AudioProcessingTrackDisabling)
PRINCIPAL_HANDLE_NONE);
processingTrack->GraphImpl()->AppendMessage(
MakeUnique<StartInputProcessing>(processingTrack, listener));
- return graph->NotifyWhenDeviceStarted(nullptr);
+ fallbackListener = new OnFallbackListener(processingTrack);
+ processingTrack->AddListener(fallbackListener);
});
+ ProcessEventQueue();
+
RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
EXPECT_TRUE(stream->mHasInput);
- Unused << WaitFor(p);
+
+ while (
+ stream->State()
+ .map([](cubeb_state aState) { return aState != CUBEB_STATE_STARTED; })
+ .valueOr(true)) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
+
+ // Wait for the AudioCallbackDriver to come into effect.
+ while (fallbackListener->OnFallback()) {
+ EXPECT_EQ(stream->ManualDataCallback(0),
+ MockCubebStream::KeepProcessing::Yes);
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
stream->SetOutputRecordingEnabled(true);
// Wait for a second worth of audio data.
- uint64_t targetPosition = graph->GraphRate();
- auto AdvanceToTargetPosition = [&] {
- DispatchFunction([&] {
- processingTrack->GraphImpl()->AppendMessage(MakeUnique<GoFaster>(cubeb));
- });
- WaitUntil(stream->FramesProcessedEvent(), [&](uint32_t aFrames) {
- // Position() gives a more up-to-date indication than summing aFrames if
- // multiple events are queued.
- if (stream->Position() < targetPosition) {
- return false;
- }
- cubeb->DontGoFaster();
- return true;
- });
- };
- AdvanceToTargetPosition();
+ const long step = graph->GraphRate() / 100; // 10ms
+ for (long frames = 0; frames < graph->GraphRate(); frames += step) {
+ stream->ManualDataCallback(step);
+ }
const uint32_t ITERATION_COUNT = 5;
uint32_t iterations = ITERATION_COUNT;
@@ -1537,8 +1574,11 @@ TEST(TestAudioTrackGraph, AudioProcessingTrackDisabling)
}
});
- targetPosition += graph->GraphRate();
- AdvanceToTargetPosition();
+ ProcessEventQueue();
+
+ for (long frames = 0; frames < graph->GraphRate(); frames += step) {
+ stream->ManualDataCallback(step);
+ }
}
// Clean up.
@@ -1548,10 +1588,18 @@ TEST(TestAudioTrackGraph, AudioProcessingTrackDisabling)
port->Destroy();
processingTrack->GraphImpl()->AppendMessage(
MakeUnique<StopInputProcessing>(processingTrack, listener));
+ processingTrack->RemoveListener(fallbackListener);
processingTrack->DisconnectDeviceInput();
processingTrack->Destroy();
});
+ ProcessEventQueue();
+
+ // Close the input and switch driver.
+ while (stream->ManualDataCallback(0) != MockCubebStream::KeepProcessing::No) {
+ std::cerr << "Waiting for switch...\n";
+ }
+
uint64_t preSilenceSamples;
uint32_t estimatedFreq;
uint32_t nrDiscontinuities;
@@ -1604,7 +1652,7 @@ TEST(TestAudioTrackGraph, SetRequestedInputChannelCount)
EXPECT_EQ(track1->DeviceId().value(), device1);
auto started =
- Invoke([&] { return graph->NotifyWhenDeviceStarted(nullptr); });
+ InvokeAsync([&] { return graph->NotifyWhenDeviceStarted(nullptr); });
RefPtr<SmartMockCubebStream> stream1 = WaitFor(cubeb->StreamInitEvent());
EXPECT_TRUE(stream1->mHasInput);
@@ -1829,7 +1877,7 @@ TEST(TestAudioTrackGraph, RestartAudioIfProcessingMaxChannelCountChanged)
EXPECT_EQ(track1->DeviceId().value(), nativeDevice);
auto started =
- Invoke([&] { return graph->NotifyWhenDeviceStarted(nullptr); });
+ InvokeAsync([&] { return graph->NotifyWhenDeviceStarted(nullptr); });
nativeStream = WaitFor(cubeb->StreamInitEvent());
EXPECT_TRUE(nativeStream->mHasInput);
@@ -1961,44 +2009,23 @@ TEST(TestAudioTrackGraph, SetInputChannelCountBeforeAudioCallbackDriver)
const CubebUtils::AudioDeviceID deviceId = (CubebUtils::AudioDeviceID)1;
RefPtr<AudioProcessingTrack> track;
RefPtr<AudioInputProcessing> listener;
- {
- MozPromiseHolder<GenericPromise> h;
- RefPtr<GenericPromise> p = h.Ensure(__func__);
-
- struct GuardMessage : public ControlMessage {
- MozPromiseHolder<GenericPromise> mHolder;
-
- GuardMessage(MediaTrack* aTrack,
- MozPromiseHolder<GenericPromise>&& aHolder)
- : ControlMessage(aTrack), mHolder(std::move(aHolder)) {}
- void Run() override {
- mTrack->GraphImpl()->Dispatch(NS_NewRunnableFunction(
- "TestAudioTrackGraph::SetInputChannel::Message::Resolver",
- [holder = std::move(mHolder)]() mutable {
- holder.Resolve(true, __func__);
- }));
- }
- };
-
- DispatchFunction([&] {
- track = AudioProcessingTrack::Create(graph);
- listener = new AudioInputProcessing(2);
- QueueExpectIsPassThrough(track, listener);
- track->SetInputProcessing(listener);
-
- MediaEnginePrefs settings;
- settings.mChannels = 1;
- QueueApplySettings(track, listener, settings);
+ DispatchFunction([&] {
+ track = AudioProcessingTrack::Create(graph);
+ listener = new AudioInputProcessing(2);
+ QueueExpectIsPassThrough(track, listener);
+ track->SetInputProcessing(listener);
- track->GraphImpl()->AppendMessage(
- MakeUnique<GuardMessage>(track, std::move(h)));
- });
+ MediaEnginePrefs settings;
+ settings.mChannels = 1;
+ QueueApplySettings(track, listener, settings);
+ });
- Unused << WaitFor(p);
- }
+ // Wait for AudioCallbackDriver to init output-only stream.
+ RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
+ EXPECT_FALSE(stream->mHasInput);
+ EXPECT_TRUE(stream->mHasOutput);
// Open a full-duplex AudioCallbackDriver.
-
RefPtr<MediaInputPort> port;
DispatchFunction([&] {
track->GraphImpl()->AppendMessage(
@@ -2006,22 +2033,13 @@ TEST(TestAudioTrackGraph, SetInputChannelCountBeforeAudioCallbackDriver)
track->ConnectDeviceInput(deviceId, listener, PRINCIPAL_HANDLE_NONE);
});
- // MediaTrackGraph will create a output-only AudioCallbackDriver in
- // CheckDriver before we open an audio input above, since AudioProcessingTrack
- // is a audio-type MediaTrack, so we need to wait here until the duplex
- // AudioCallbackDriver is created.
- RefPtr<SmartMockCubebStream> stream;
- SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>(
- "TEST(TestAudioTrackGraph, SetInputChannelCountBeforeAudioCallbackDriver)"_ns,
- [&] {
- stream = WaitFor(cubeb->StreamInitEvent());
- EXPECT_TRUE(stream->mHasOutput);
- return stream->mHasInput;
- });
+ stream = WaitFor(cubeb->StreamInitEvent());
+ EXPECT_TRUE(stream->mHasInput);
+ EXPECT_TRUE(stream->mHasOutput);
EXPECT_EQ(stream->InputChannels(), 1U);
Unused << WaitFor(
- Invoke([&] { return graph->NotifyWhenDeviceStarted(nullptr); }));
+ InvokeAsync([&] { return graph->NotifyWhenDeviceStarted(nullptr); }));
// Clean up.
DispatchFunction([&] {
@@ -2257,7 +2275,7 @@ TEST(TestAudioTrackGraph, SwitchNativeAudioProcessingTrack)
EXPECT_EQ(track1->DeviceId().value(), device1);
auto started =
- Invoke([&] { return graph->NotifyWhenDeviceStarted(nullptr); });
+ InvokeAsync([&] { return graph->NotifyWhenDeviceStarted(nullptr); });
RefPtr<SmartMockCubebStream> stream1 = WaitFor(cubeb->StreamInitEvent());
EXPECT_TRUE(stream1->mHasInput);
@@ -2350,23 +2368,6 @@ TEST(TestAudioTrackGraph, SwitchNativeAudioProcessingTrack)
std::cerr << "No native input now" << std::endl;
}
-class OnFallbackListener : public MediaTrackListener {
- const RefPtr<MediaTrack> mTrack;
- Atomic<bool> mOnFallback{true};
-
- public:
- explicit OnFallbackListener(MediaTrack* aTrack) : mTrack(aTrack) {}
-
- bool OnFallback() { return mOnFallback; }
-
- void NotifyOutput(MediaTrackGraph*, TrackTime) override {
- if (auto* ad =
- mTrack->GraphImpl()->CurrentDriver()->AsAudioCallbackDriver()) {
- mOnFallback = ad->OnFallback();
- }
- }
-};
-
void TestCrossGraphPort(uint32_t aInputRate, uint32_t aOutputRate,
float aDriftFactor, uint32_t aRunTimeSeconds = 10,
uint32_t aNumExpectedUnderruns = 0) {
@@ -2409,6 +2410,13 @@ void TestCrossGraphPort(uint32_t aInputRate, uint32_t aOutputRate,
RefPtr<SmartMockCubebStream> inputStream = WaitFor(cubeb->StreamInitEvent());
+ while (
+ inputStream->State()
+ .map([](cubeb_state aState) { return aState != CUBEB_STATE_STARTED; })
+ .valueOr(true)) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
+
// Wait for the primary AudioCallbackDriver to come into effect.
while (primaryFallbackListener->OnFallback()) {
EXPECT_EQ(inputStream->ManualDataCallback(0),
@@ -2441,6 +2449,13 @@ void TestCrossGraphPort(uint32_t aInputRate, uint32_t aOutputRate,
RefPtr<SmartMockCubebStream> partnerStream =
WaitFor(cubeb->StreamInitEvent());
+ while (
+ partnerStream->State()
+ .map([](cubeb_state aState) { return aState != CUBEB_STATE_STARTED; })
+ .valueOr(true)) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
+
// Process the CrossGraphTransmitter on the primary graph.
EXPECT_EQ(inputStream->ManualDataCallback(0),
MockCubebStream::KeepProcessing::Yes);
@@ -2453,8 +2468,7 @@ void TestCrossGraphPort(uint32_t aInputRate, uint32_t aOutputRate,
}
DispatchFunction([&] { receiver->RemoveListener(partnerFallbackListener); });
- while (NS_ProcessNextEvent(nullptr, false)) {
- }
+ ProcessEventQueue();
nsIThread* currentThread = NS_GetCurrentThread();
cubeb_state inputState = CUBEB_STATE_STARTED;
@@ -2499,8 +2513,7 @@ void TestCrossGraphPort(uint32_t aInputRate, uint32_t aOutputRate,
processingTrack->Destroy();
});
- while (NS_ProcessNextEvent(nullptr, false)) {
- }
+ ProcessEventQueue();
EXPECT_EQ(inputStream->ManualDataCallback(0),
MockCubebStream::KeepProcessing::Yes);
@@ -2816,6 +2829,221 @@ TEST(TestAudioInputProcessing, ClockDriftExpectation)
}
#endif // MOZ_WEBRTC
+TEST(TestAudioTrackGraph, PlatformProcessing)
+{
+ constexpr cubeb_input_processing_params allParams =
+ CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION |
+ CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION |
+ CUBEB_INPUT_PROCESSING_PARAM_AUTOMATIC_GAIN_CONTROL |
+ CUBEB_INPUT_PROCESSING_PARAM_VOICE_ISOLATION;
+ MockCubeb* cubeb = new MockCubeb(MockCubeb::RunningMode::Manual);
+ cubeb->SetSupportedInputProcessingParams(allParams, CUBEB_OK);
+ CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
+
+ MediaTrackGraph* graph = MediaTrackGraphImpl::GetInstance(
+ MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*Window ID*/ 1,
+ CubebUtils::PreferredSampleRate(/* aShouldResistFingerprinting */ false),
+ nullptr, GetMainThreadSerialEventTarget());
+
+ const CubebUtils::AudioDeviceID device = (CubebUtils::AudioDeviceID)1;
+
+ // Set up mock listener.
+ RefPtr<MockAudioDataListener> listener = MakeRefPtr<MockAudioDataListener>();
+ EXPECT_CALL(*listener, IsVoiceInput).WillRepeatedly(Return(true));
+ EXPECT_CALL(*listener, RequestedInputChannelCount).WillRepeatedly(Return(1));
+ EXPECT_CALL(*listener, RequestedInputProcessingParams)
+ .WillRepeatedly(Return(CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION));
+ EXPECT_CALL(*listener, Disconnect);
+
+ // Expectations.
+ const Result<cubeb_input_processing_params, int> notSupportedResult(
+ Err(CUBEB_ERROR_NOT_SUPPORTED));
+ const Result<cubeb_input_processing_params, int> errorResult(
+ Err(CUBEB_ERROR));
+ const Result<cubeb_input_processing_params, int> noneResult(
+ CUBEB_INPUT_PROCESSING_PARAM_NONE);
+ const Result<cubeb_input_processing_params, int> echoResult(
+ CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION);
+ const Result<cubeb_input_processing_params, int> noiseResult(
+ CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION);
+ Atomic<int> numProcessingParamsResults(0);
+ {
+ InSequence s;
+ // On first driver start.
+ EXPECT_CALL(*listener,
+ NotifySetRequestedInputProcessingParamsResult(
+ graph, CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION,
+ Eq(std::ref(echoResult))))
+ .WillOnce([&] { ++numProcessingParamsResults; });
+ // After requesting something else.
+ EXPECT_CALL(*listener,
+ NotifySetRequestedInputProcessingParamsResult(
+ graph, CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION,
+ Eq(std::ref(noiseResult))))
+ .WillOnce([&] { ++numProcessingParamsResults; });
+ // After error request.
+ EXPECT_CALL(*listener,
+ NotifySetRequestedInputProcessingParamsResult(
+ graph, CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION,
+ Eq(std::ref(errorResult))))
+ .WillOnce([&] { ++numProcessingParamsResults; });
+ // After requesting None.
+ EXPECT_CALL(*listener, NotifySetRequestedInputProcessingParamsResult(
+ graph, CUBEB_INPUT_PROCESSING_PARAM_NONE,
+ Eq(std::ref(noneResult))))
+ .WillOnce([&] { ++numProcessingParamsResults; });
+ // After driver switch.
+ EXPECT_CALL(*listener, NotifySetRequestedInputProcessingParamsResult(
+ graph, CUBEB_INPUT_PROCESSING_PARAM_NONE,
+ Eq(std::ref(noneResult))))
+ .WillOnce([&] { ++numProcessingParamsResults; });
+ // After requesting something not supported.
+ EXPECT_CALL(*listener,
+ NotifySetRequestedInputProcessingParamsResult(
+ graph, CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION,
+ Eq(std::ref(noneResult))))
+ .WillOnce([&] { ++numProcessingParamsResults; });
+ // After requesting something with backend not supporting processing params.
+ EXPECT_CALL(*listener,
+ NotifySetRequestedInputProcessingParamsResult(
+ graph, CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION,
+ Eq(std::ref(notSupportedResult))))
+ .WillOnce([&] { ++numProcessingParamsResults; });
+ }
+
+ // Open a device.
+ RefPtr<TestDeviceInputConsumerTrack> track;
+ RefPtr<OnFallbackListener> fallbackListener;
+ DispatchFunction([&] {
+ track = TestDeviceInputConsumerTrack::Create(graph);
+ track->ConnectDeviceInput(device, listener, PRINCIPAL_HANDLE_NONE);
+ fallbackListener = new OnFallbackListener(track);
+ track->AddListener(fallbackListener);
+ });
+
+ RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
+ EXPECT_TRUE(stream->mHasInput);
+ EXPECT_TRUE(stream->mHasOutput);
+ EXPECT_EQ(stream->InputChannels(), 1U);
+ EXPECT_EQ(stream->GetInputDeviceID(), device);
+
+ while (
+ stream->State()
+ .map([](cubeb_state aState) { return aState != CUBEB_STATE_STARTED; })
+ .valueOr(true)) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
+
+ const auto waitForResult = [&](int aNumResult) {
+ while (numProcessingParamsResults < aNumResult) {
+ EXPECT_EQ(stream->ManualDataCallback(0),
+ MockCubebStream::KeepProcessing::Yes);
+ NS_ProcessNextEvent();
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
+ };
+
+ // Wait for the AudioCallbackDriver to come into effect.
+ while (fallbackListener->OnFallback()) {
+ EXPECT_EQ(stream->ManualDataCallback(0),
+ MockCubebStream::KeepProcessing::Yes);
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
+
+ // Wait for the first result after driver creation.
+ waitForResult(1);
+
+ // Request new processing params.
+ EXPECT_CALL(*listener, RequestedInputProcessingParams)
+ .WillRepeatedly(Return(CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION));
+ waitForResult(2);
+
+ // Test with returning error on new request.
+ cubeb->SetInputProcessingApplyRv(CUBEB_ERROR);
+ EXPECT_CALL(*listener, RequestedInputProcessingParams)
+ .WillRepeatedly(Return(CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION));
+ waitForResult(3);
+
+ // Test unsetting all params.
+ cubeb->SetInputProcessingApplyRv(CUBEB_OK);
+ EXPECT_CALL(*listener, RequestedInputProcessingParams)
+ .WillRepeatedly(Return(CUBEB_INPUT_PROCESSING_PARAM_NONE));
+ waitForResult(4);
+
+ // Switch driver.
+ EXPECT_CALL(*listener, RequestedInputChannelCount).WillRepeatedly(Return(2));
+ DispatchFunction([&] {
+ track->QueueControlMessageWithNoShutdown(
+ [&] { graph->ReevaluateInputDevice(device); });
+ });
+ ProcessEventQueue();
+ // Process the reevaluation message.
+ EXPECT_EQ(stream->ManualDataCallback(0),
+ MockCubebStream::KeepProcessing::Yes);
+ // Perform the switch.
+ auto initPromise = TakeN(cubeb->StreamInitEvent(), 1);
+ EXPECT_EQ(stream->ManualDataCallback(0), MockCubebStream::KeepProcessing::No);
+ std::tie(stream) = WaitFor(initPromise).unwrap()[0];
+ EXPECT_TRUE(stream->mHasInput);
+ EXPECT_TRUE(stream->mHasOutput);
+ EXPECT_EQ(stream->InputChannels(), 2U);
+ EXPECT_EQ(stream->GetInputDeviceID(), device);
+
+ while (
+ stream->State()
+ .map([](cubeb_state aState) { return aState != CUBEB_STATE_STARTED; })
+ .valueOr(true)) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
+
+ // Wait for the new AudioCallbackDriver to come into effect.
+ fallbackListener->Reset();
+ while (fallbackListener->OnFallback()) {
+ EXPECT_EQ(stream->ManualDataCallback(0),
+ MockCubebStream::KeepProcessing::Yes);
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
+
+ // Wait for the first result after driver creation.
+ waitForResult(5);
+
+ // Test requesting something not supported.
+ cubeb->SetSupportedInputProcessingParams(
+ CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION |
+ CUBEB_INPUT_PROCESSING_PARAM_AUTOMATIC_GAIN_CONTROL |
+ CUBEB_INPUT_PROCESSING_PARAM_VOICE_ISOLATION,
+ CUBEB_OK);
+ EXPECT_CALL(*listener, RequestedInputProcessingParams)
+ .WillRepeatedly(Return(CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION));
+ waitForResult(6);
+
+ // Test requesting something when unsupported by backend.
+ cubeb->SetSupportedInputProcessingParams(CUBEB_INPUT_PROCESSING_PARAM_NONE,
+ CUBEB_ERROR_NOT_SUPPORTED);
+ EXPECT_CALL(*listener, RequestedInputProcessingParams)
+ .WillRepeatedly(Return(CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION));
+ waitForResult(7);
+
+ // Clean up.
+ DispatchFunction([&] {
+ track->RemoveListener(fallbackListener);
+ track->Destroy();
+ });
+ ProcessEventQueue();
+ // Process the destroy message.
+ EXPECT_EQ(stream->ManualDataCallback(0),
+ MockCubebStream::KeepProcessing::Yes);
+ // Shut down.
+ EXPECT_EQ(stream->ManualDataCallback(0), MockCubebStream::KeepProcessing::No);
+ RefPtr<SmartMockCubebStream> destroyedStream =
+ WaitFor(cubeb->StreamDestroyEvent());
+ EXPECT_EQ(destroyedStream.get(), stream.get());
+ {
+ NativeInputTrack* native = graph->GetNativeInputTrackMainThread();
+ ASSERT_TRUE(!native);
+ }
+}
+
#undef Invoke
#undef DispatchFunction
#undef DispatchMethod
diff --git a/dom/media/gtest/TestCDMStorage.cpp b/dom/media/gtest/TestCDMStorage.cpp
index d6249cc95f..57bbbd9298 100644
--- a/dom/media/gtest/TestCDMStorage.cpp
+++ b/dom/media/gtest/TestCDMStorage.cpp
@@ -58,7 +58,7 @@ template <typename T>
static nsresult EnumerateCDMStorageDir(const nsACString& aDir, T&& aDirIter) {
RefPtr<GeckoMediaPluginServiceParent> service =
GeckoMediaPluginServiceParent::GetSingleton();
- MOZ_ASSERT(service);
+ MOZ_RELEASE_ASSERT(service);
// $profileDir/gmp/$platform/
nsCOMPtr<nsIFile> path;
@@ -94,7 +94,7 @@ class GMPShutdownObserver : public nsIRunnable, public nsIObserver {
NS_DECL_THREADSAFE_ISUPPORTS
NS_IMETHOD Run() override {
- MOZ_ASSERT(NS_IsMainThread());
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
EXPECT_TRUE(observerService);
@@ -133,7 +133,7 @@ class NotifyObserversTask : public Runnable {
explicit NotifyObserversTask(const char* aTopic)
: mozilla::Runnable("NotifyObserversTask"), mTopic(aTopic) {}
NS_IMETHOD Run() override {
- MOZ_ASSERT(NS_IsMainThread());
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
if (observerService) {
@@ -153,7 +153,7 @@ class ClearCDMStorageTask : public nsIRunnable, public nsIObserver {
NS_DECL_THREADSAFE_ISUPPORTS
NS_IMETHOD Run() override {
- MOZ_ASSERT(NS_IsMainThread());
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
EXPECT_TRUE(observerService);
@@ -272,7 +272,7 @@ static nsCString GetNodeId(const nsAString& aOrigin,
static bool IsCDMStorageIsEmpty() {
RefPtr<GeckoMediaPluginServiceParent> service =
GeckoMediaPluginServiceParent::GetSingleton();
- MOZ_ASSERT(service);
+ MOZ_RELEASE_ASSERT(service);
nsCOMPtr<nsIFile> storage;
nsresult rv = service->GetStorageDir(getter_AddRefs(storage));
EXPECT_NS_SUCCEEDED(rv);
@@ -286,14 +286,14 @@ static bool IsCDMStorageIsEmpty() {
static void AssertIsOnGMPThread() {
RefPtr<GeckoMediaPluginService> service =
GeckoMediaPluginService::GetGeckoMediaPluginService();
- MOZ_ASSERT(service);
+ MOZ_RELEASE_ASSERT(service);
nsCOMPtr<nsIThread> thread;
service->GetThread(getter_AddRefs(thread));
- MOZ_ASSERT(thread);
+ MOZ_RELEASE_ASSERT(thread);
nsCOMPtr<nsIThread> currentThread;
- DebugOnly<nsresult> rv = NS_GetCurrentThread(getter_AddRefs(currentThread));
- MOZ_ASSERT(NS_SUCCEEDED(rv));
- MOZ_ASSERT(currentThread == thread);
+ nsresult rv = NS_GetCurrentThread(getter_AddRefs(currentThread));
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+ MOZ_RELEASE_ASSERT(currentThread == thread);
}
class CDMStorageTest {
@@ -1049,8 +1049,8 @@ class CDMStorageTest {
constexpr auto data = "Just_some_arbitrary_data."_ns;
- MOZ_ASSERT(longRecordName.Length() < GMP_MAX_RECORD_NAME_SIZE);
- MOZ_ASSERT(longRecordName.Length() > 260); // Windows MAX_PATH
+ MOZ_RELEASE_ASSERT(longRecordName.Length() < GMP_MAX_RECORD_NAME_SIZE);
+ MOZ_RELEASE_ASSERT(longRecordName.Length() > 260); // Windows MAX_PATH
nsCString response("stored ");
response.Append(longRecordName);
diff --git a/dom/media/gtest/TestDeviceInputTrack.cpp b/dom/media/gtest/TestDeviceInputTrack.cpp
index 14b5227f9d..fca06d5e4a 100644
--- a/dom/media/gtest/TestDeviceInputTrack.cpp
+++ b/dom/media/gtest/TestDeviceInputTrack.cpp
@@ -87,7 +87,7 @@ TEST_F(TestDeviceInputTrack, DeviceInputConsumerTrack) {
class TestDeviceInputConsumerTrack : public DeviceInputConsumerTrack {
public:
static TestDeviceInputConsumerTrack* Create(MediaTrackGraph* aGraph) {
- MOZ_ASSERT(NS_IsMainThread());
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
TestDeviceInputConsumerTrack* track =
new TestDeviceInputConsumerTrack(aGraph->GraphRate());
aGraph->AddTrack(track);
@@ -95,7 +95,7 @@ TEST_F(TestDeviceInputTrack, DeviceInputConsumerTrack) {
}
void Destroy() {
- MOZ_ASSERT(NS_IsMainThread());
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
DisconnectDeviceInput();
DeviceInputConsumerTrack::Destroy();
}
@@ -108,7 +108,7 @@ TEST_F(TestDeviceInputTrack, DeviceInputConsumerTrack) {
return 0;
}
DeviceInputTrack* t = mInputs[0]->GetSource()->AsDeviceInputTrack();
- MOZ_ASSERT(t);
+ MOZ_RELEASE_ASSERT(t);
return t->NumberOfChannels();
}
@@ -122,16 +122,26 @@ TEST_F(TestDeviceInputTrack, DeviceInputConsumerTrack) {
TestAudioDataListener(uint32_t aChannelCount, bool aIsVoice)
: mChannelCount(aChannelCount), mIsVoice(aIsVoice) {}
// Graph thread APIs: AudioDataListenerInterface implementations.
- uint32_t RequestedInputChannelCount(MediaTrackGraph* aGraph) override {
+ uint32_t RequestedInputChannelCount(
+ MediaTrackGraph* aGraph) const override {
aGraph->AssertOnGraphThread();
return mChannelCount;
}
+ cubeb_input_processing_params RequestedInputProcessingParams(
+ MediaTrackGraph*) const override {
+ return CUBEB_INPUT_PROCESSING_PARAM_NONE;
+ }
bool IsVoiceInput(MediaTrackGraph* aGraph) const override {
return mIsVoice;
};
void DeviceChanged(MediaTrackGraph* aGraph) override { /* Ignored */
}
void Disconnect(MediaTrackGraph* aGraph) override{/* Ignored */};
+ void NotifySetRequestedInputProcessingParamsResult(
+ MediaTrackGraph* aGraph, cubeb_input_processing_params aRequestedParams,
+ const Result<cubeb_input_processing_params, int>& aResult) override {
+ /* Ignored */
+ }
private:
~TestAudioDataListener() = default;
diff --git a/dom/media/gtest/TestMP4Demuxer.cpp b/dom/media/gtest/TestMP4Demuxer.cpp
index 43dfdf19a4..1a1bde8035 100644
--- a/dom/media/gtest/TestMP4Demuxer.cpp
+++ b/dom/media/gtest/TestMP4Demuxer.cpp
@@ -56,7 +56,7 @@ class MP4DemuxerBinding {
}
RefPtr<GenericPromise> CheckTrackKeyFrame(MediaTrackDemuxer* aTrackDemuxer) {
- MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+ MOZ_RELEASE_ASSERT(mTaskQueue->IsCurrentThreadIn());
RefPtr<MediaTrackDemuxer> track = aTrackDemuxer;
RefPtr<MP4DemuxerBinding> binding = this;
@@ -97,7 +97,7 @@ class MP4DemuxerBinding {
}
RefPtr<GenericPromise> CheckTrackSamples(MediaTrackDemuxer* aTrackDemuxer) {
- MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+ MOZ_RELEASE_ASSERT(mTaskQueue->IsCurrentThreadIn());
RefPtr<MediaTrackDemuxer> track = aTrackDemuxer;
RefPtr<MP4DemuxerBinding> binding = this;
diff --git a/dom/media/gtest/TestMediaDataEncoder.cpp b/dom/media/gtest/TestMediaDataEncoder.cpp
index 39c92fb19c..fefedcce43 100644
--- a/dom/media/gtest/TestMediaDataEncoder.cpp
+++ b/dom/media/gtest/TestMediaDataEncoder.cpp
@@ -193,7 +193,7 @@ static already_AddRefed<MediaDataEncoder> CreateH264Encoder(
}
void WaitForShutdown(const RefPtr<MediaDataEncoder>& aEncoder) {
- MOZ_ASSERT(aEncoder);
+ MOZ_RELEASE_ASSERT(aEncoder);
Maybe<bool> result;
// media::Await() supports exclusive promises only, but ShutdownPromise is
diff --git a/dom/media/gtest/TestWebMWriter.cpp b/dom/media/gtest/TestWebMWriter.cpp
index 837ee6a2c6..5384fd7c99 100644
--- a/dom/media/gtest/TestWebMWriter.cpp
+++ b/dom/media/gtest/TestWebMWriter.cpp
@@ -223,7 +223,7 @@ struct WebMioData {
};
static int webm_read(void* aBuffer, size_t aLength, void* aUserData) {
- NS_ASSERTION(aUserData, "aUserData must point to a valid WebMioData");
+ MOZ_RELEASE_ASSERT(aUserData, "aUserData must point to a valid WebMioData");
WebMioData* ioData = static_cast<WebMioData*>(aUserData);
// Check the read length.
@@ -247,7 +247,7 @@ static int webm_read(void* aBuffer, size_t aLength, void* aUserData) {
}
static int webm_seek(int64_t aOffset, int aWhence, void* aUserData) {
- NS_ASSERTION(aUserData, "aUserData must point to a valid WebMioData");
+ MOZ_RELEASE_ASSERT(aUserData, "aUserData must point to a valid WebMioData");
WebMioData* ioData = static_cast<WebMioData*>(aUserData);
if (Abs(aOffset) > ioData->data.Length()) {
@@ -281,7 +281,7 @@ static int webm_seek(int64_t aOffset, int aWhence, void* aUserData) {
}
static int64_t webm_tell(void* aUserData) {
- NS_ASSERTION(aUserData, "aUserData must point to a valid WebMioData");
+ MOZ_RELEASE_ASSERT(aUserData, "aUserData must point to a valid WebMioData");
WebMioData* ioData = static_cast<WebMioData*>(aUserData);
return ioData->offset.isValid() ? ioData->offset.value() : -1;
}
diff --git a/dom/media/ipc/MFCDMChild.cpp b/dom/media/ipc/MFCDMChild.cpp
index 2ba2bdaf4e..9df86b82f4 100644
--- a/dom/media/ipc/MFCDMChild.cpp
+++ b/dom/media/ipc/MFCDMChild.cpp
@@ -7,6 +7,7 @@
#include "mozilla/EMEUtils.h"
#include "mozilla/KeySystemConfig.h"
#include "mozilla/RefPtr.h"
+#include "mozilla/StaticString.h"
#include "mozilla/WMFCDMProxyCallback.h"
#include "nsString.h"
#include "RemoteDecoderManagerChild.h"
@@ -44,7 +45,7 @@ namespace mozilla {
#define INVOKE_ASYNC(method, promiseId, param1) \
do { \
- auto callsite = __func__; \
+ StaticString callsite = __func__; \
using ParamType = std::remove_reference<decltype(param1)>::type; \
mManagerThread->Dispatch(NS_NewRunnableFunction( \
callsite, [self = RefPtr{this}, callsite, promiseId, \
@@ -56,7 +57,7 @@ namespace mozilla {
#define INVOKE_ASYNC2(method, promiseId, param1, param2) \
do { \
- auto callsite = __func__; \
+ StaticString callsite = __func__; \
using ParamType1 = std::remove_reference<decltype(param1)>::type; \
using ParamType2 = std::remove_reference<decltype(param2)>::type; \
mManagerThread->Dispatch(NS_NewRunnableFunction( \
@@ -188,7 +189,7 @@ void MFCDMChild::AssertSendable() {
template <typename PromiseType>
already_AddRefed<PromiseType> MFCDMChild::InvokeAsync(
- std::function<void()>&& aCall, const char* aCallerName,
+ std::function<void()>&& aCall, StaticString aCallerName,
MozPromiseHolder<PromiseType>& aPromise) {
AssertSendable();
diff --git a/dom/media/ipc/MFCDMChild.h b/dom/media/ipc/MFCDMChild.h
index 3396b0c790..ec766cab24 100644
--- a/dom/media/ipc/MFCDMChild.h
+++ b/dom/media/ipc/MFCDMChild.h
@@ -30,7 +30,7 @@ class MFCDMChild final : public PMFCDMChild {
template <typename PromiseType>
already_AddRefed<PromiseType> InvokeAsync(
- std::function<void()>&& aCall, const char* aCallerName,
+ std::function<void()>&& aCall, StaticString aCallerName,
MozPromiseHolder<PromiseType>& aPromise);
using InitPromise = MozPromise<MFCDMInitIPDL, nsresult, true>;
diff --git a/dom/media/ipc/RDDChild.cpp b/dom/media/ipc/RDDChild.cpp
index fb2e14bb4f..6180ec7391 100644
--- a/dom/media/ipc/RDDChild.cpp
+++ b/dom/media/ipc/RDDChild.cpp
@@ -5,6 +5,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "RDDChild.h"
+#include "TelemetryProbesReporter.h"
+#include "VideoUtils.h"
#include "mozilla/FOGIPC.h"
#include "mozilla/RDDProcessManager.h"
#include "mozilla/dom/ContentParent.h"
@@ -142,6 +144,13 @@ mozilla::ipc::IPCResult RDDChild::RecvGetModulesTrust(
mozilla::ipc::IPCResult RDDChild::RecvUpdateMediaCodecsSupported(
const media::MediaCodecsSupported& aSupported) {
+#if defined(XP_MACOSX) || defined(XP_LINUX)
+ // We report this on GPUChild on Windows and Android
+ if (ContainHardwareCodecsSupported(aSupported)) {
+ mozilla::TelemetryProbesReporter::ReportDeviceMediaCodecSupported(
+ aSupported);
+ }
+#endif
dom::ContentParent::BroadcastMediaCodecsSupportedUpdate(
RemoteDecodeIn::RddProcess, aSupported);
return IPC_OK();
diff --git a/dom/media/ipc/RDDParent.cpp b/dom/media/ipc/RDDParent.cpp
index 8892e8fbbe..4b6c1372ce 100644
--- a/dom/media/ipc/RDDParent.cpp
+++ b/dom/media/ipc/RDDParent.cpp
@@ -52,6 +52,10 @@
# include "mozilla/SandboxTestingChild.h"
#endif
+#if defined(XP_MACOSX) || defined(XP_LINUX)
+# include "VideoUtils.h"
+#endif
+
namespace mozilla {
using namespace ipc;
@@ -159,18 +163,6 @@ mozilla::ipc::IPCResult RDDParent::RecvInit(
}
IPCResult RDDParent::RecvUpdateVar(const GfxVarUpdate& aUpdate) {
-#if defined(XP_WIN)
- auto scopeExit = MakeScopeExit(
- [couldUseHWDecoder = gfx::gfxVars::CanUseHardwareVideoDecoding()] {
- if (couldUseHWDecoder != gfx::gfxVars::CanUseHardwareVideoDecoding()) {
- // The capabilities of the system may have changed, force a refresh by
- // re-initializing the WMF PDM.
- WMFDecoderModule::Init();
- Unused << RDDParent::GetSingleton()->SendUpdateMediaCodecsSupported(
- PDMFactory::Supported(true /* force refresh */));
- }
- });
-#endif
gfxVars::ApplyUpdate(aUpdate);
return IPC_OK();
}
diff --git a/dom/media/ipc/RemoteImageHolder.h b/dom/media/ipc/RemoteImageHolder.h
index 981e24d150..36deab1ef1 100644
--- a/dom/media/ipc/RemoteImageHolder.h
+++ b/dom/media/ipc/RemoteImageHolder.h
@@ -58,12 +58,12 @@ class RemoteImageHolder final {
gfx::ColorRange mColorRange = {};
};
- template <>
- struct ipc::IPDLParamTraits<RemoteImageHolder> {
- static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
- RemoteImageHolder&& aParam);
- static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
- RemoteImageHolder* aResult);
+template <>
+struct ipc::IPDLParamTraits<RemoteImageHolder> {
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ RemoteImageHolder&& aParam);
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ RemoteImageHolder* aResult);
};
} // namespace mozilla
diff --git a/dom/media/mediacapabilities/KeyValueStorage.cpp b/dom/media/mediacapabilities/KeyValueStorage.cpp
index f0ac0aad7d..a2e007c598 100644
--- a/dom/media/mediacapabilities/KeyValueStorage.cpp
+++ b/dom/media/mediacapabilities/KeyValueStorage.cpp
@@ -94,7 +94,7 @@ class VoidCallback final : public nsIKeyValueVoidCallback {
mResultPromise.Reject(NS_ERROR_FAILURE, __func__);
return NS_OK;
}
- RefPtr<GenericPromise> Ensure(const char* aMethodName) {
+ RefPtr<GenericPromise> Ensure(StaticString aMethodName) {
return mResultPromise.Ensure(aMethodName);
}
diff --git a/dom/media/mediacontrol/ContentMediaController.cpp b/dom/media/mediacontrol/ContentMediaController.cpp
index e1fe574d9b..0c3bbbecdc 100644
--- a/dom/media/mediacontrol/ContentMediaController.cpp
+++ b/dom/media/mediacontrol/ContentMediaController.cpp
@@ -304,6 +304,37 @@ void ContentMediaAgent::UpdatePositionState(
}
}
+void ContentMediaAgent::UpdateGuessedPositionState(
+ uint64_t aBrowsingContextId, const nsID& aMediaId,
+ const Maybe<PositionState>& aState) {
+ RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId);
+ if (!bc || bc->IsDiscarded()) {
+ return;
+ }
+
+ if (aState) {
+ LOG("Update guessed position state for BC %" PRId64
+ " media id %s (duration=%f, playbackRate=%f, position=%f)",
+ bc->Id(), aMediaId.ToString().get(), aState->mDuration,
+ aState->mPlaybackRate, aState->mLastReportedPlaybackPosition);
+ } else {
+ LOG("Clear guessed position state for BC %" PRId64 " media id %s", bc->Id(),
+ aMediaId.ToString().get());
+ }
+
+ if (XRE_IsContentProcess()) {
+ ContentChild* contentChild = ContentChild::GetSingleton();
+ Unused << contentChild->SendNotifyGuessedPositionStateChanged(bc, aMediaId,
+ aState);
+ return;
+ }
+ // This would only happen when we disable e10s.
+ if (RefPtr<IMediaInfoUpdater> updater =
+ bc->Canonical()->GetMediaController()) {
+ updater->UpdateGuessedPositionState(bc->Id(), aMediaId, aState);
+ }
+}
+
ContentMediaController::ContentMediaController(uint64_t aId) {
LOG("Create content media controller for BC %" PRId64, aId);
}
diff --git a/dom/media/mediacontrol/ContentMediaController.h b/dom/media/mediacontrol/ContentMediaController.h
index a58be24b9d..236b3b254d 100644
--- a/dom/media/mediacontrol/ContentMediaController.h
+++ b/dom/media/mediacontrol/ContentMediaController.h
@@ -67,6 +67,9 @@ class ContentMediaAgent : public IMediaInfoUpdater {
bool aIsInFullScreen) override;
void UpdatePositionState(uint64_t aBrowsingContextId,
const Maybe<PositionState>& aState) override;
+ void UpdateGuessedPositionState(uint64_t aBrowsingContextId,
+ const nsID& aMediaId,
+ const Maybe<PositionState>& aState) override;
// Use these methods to register/unregister `ContentMediaControlKeyReceiver`
// in order to listen to media control key events.
diff --git a/dom/media/mediacontrol/MediaControlKeyManager.cpp b/dom/media/mediacontrol/MediaControlKeyManager.cpp
index b40d3af91e..92e2679bdd 100644
--- a/dom/media/mediacontrol/MediaControlKeyManager.cpp
+++ b/dom/media/mediacontrol/MediaControlKeyManager.cpp
@@ -107,6 +107,7 @@ void MediaControlKeyManager::StopMonitoringControlKeys() {
nullptr);
obs->NotifyObservers(nullptr, "media-displayed-metadata-changed",
nullptr);
+ obs->NotifyObservers(nullptr, "media-position-state-changed", nullptr);
}
}
}
@@ -197,6 +198,12 @@ void MediaControlKeyManager::SetPositionState(
if (mEventSource && mEventSource->IsOpened()) {
mEventSource->SetPositionState(aState);
}
+
+ if (StaticPrefs::media_mediacontrol_testingevents_enabled()) {
+ if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) {
+ obs->NotifyObservers(nullptr, "media-position-state-changed", nullptr);
+ }
+ }
}
void MediaControlKeyManager::OnPreferenceChange() {
diff --git a/dom/media/mediacontrol/MediaControlService.cpp b/dom/media/mediacontrol/MediaControlService.cpp
index f45ab4253d..c64749f556 100644
--- a/dom/media/mediacontrol/MediaControlService.cpp
+++ b/dom/media/mediacontrol/MediaControlService.cpp
@@ -482,6 +482,7 @@ void MediaControlService::ControllerManager::UpdateMainControllerInternal(
mSource->SetPlaybackState(mMainController->PlaybackState());
mSource->SetMediaMetadata(mMainController->GetCurrentMediaMetadata());
mSource->SetSupportedMediaKeys(mMainController->GetSupportedMediaKeys());
+ mSource->SetPositionState(mMainController->GetCurrentPositionState());
ConnectMainControllerEvents();
}
diff --git a/dom/media/mediacontrol/MediaPlaybackStatus.cpp b/dom/media/mediacontrol/MediaPlaybackStatus.cpp
index 80dedf8599..434d6dbd7e 100644
--- a/dom/media/mediacontrol/MediaPlaybackStatus.cpp
+++ b/dom/media/mediacontrol/MediaPlaybackStatus.cpp
@@ -71,6 +71,23 @@ void MediaPlaybackStatus::UpdateMediaAudibleState(uint64_t aContextId,
}
}
+void MediaPlaybackStatus::UpdateGuessedPositionState(
+ uint64_t aContextId, const nsID& aElementId,
+ const Maybe<PositionState>& aState) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (aState) {
+ LOG("Update guessed position state for context %" PRIu64
+ " element %s (duration=%f, playbackRate=%f, position=%f)",
+ aContextId, aElementId.ToString().get(), aState->mDuration,
+ aState->mPlaybackRate, aState->mLastReportedPlaybackPosition);
+ } else {
+ LOG("Clear guessed position state for context %" PRIu64 " element %s",
+ aContextId, aElementId.ToString().get());
+ }
+ ContextMediaInfo& info = GetNotNullContextInfo(aContextId);
+ info.UpdateGuessedPositionState(aElementId, aState);
+}
+
bool MediaPlaybackStatus::IsPlaying() const {
MOZ_ASSERT(NS_IsMainThread());
return std::any_of(mContextInfoMap.Values().cbegin(),
@@ -92,6 +109,35 @@ bool MediaPlaybackStatus::IsAnyMediaBeingControlled() const {
[](const auto& info) { return info->IsAnyMediaBeingControlled(); });
}
+Maybe<PositionState> MediaPlaybackStatus::GuessedMediaPositionState(
+ Maybe<uint64_t> aPreferredContextId) const {
+ auto contextId = aPreferredContextId;
+ if (!contextId) {
+ contextId = mOwningAudioFocusContextId;
+ }
+
+ // either the preferred or focused context
+ if (contextId) {
+ auto entry = mContextInfoMap.Lookup(*contextId);
+ if (!entry) {
+ return Nothing();
+ }
+ LOG("Using guessed position state from preferred/focused BC %" PRId64,
+ *contextId);
+ return entry.Data()->GuessedPositionState();
+ }
+
+ // look for the first position state
+ for (const auto& context : mContextInfoMap.Values()) {
+ auto state = context->GuessedPositionState();
+ if (state) {
+ LOG("Using guessed position state from BC %" PRId64, context->Id());
+ return state;
+ }
+ }
+ return Nothing();
+}
+
MediaPlaybackStatus::ContextMediaInfo&
MediaPlaybackStatus::GetNotNullContextInfo(uint64_t aContextId) {
MOZ_ASSERT(NS_IsMainThread());
@@ -139,4 +185,22 @@ bool MediaPlaybackStatus::IsContextOwningAudioFocus(uint64_t aContextId) const {
: false;
}
+Maybe<PositionState>
+MediaPlaybackStatus::ContextMediaInfo::GuessedPositionState() const {
+ if (mGuessedPositionStateMap.Count() != 1) {
+ LOG("Count is %d", mGuessedPositionStateMap.Count());
+ return Nothing();
+ }
+ return Some(mGuessedPositionStateMap.begin()->GetData());
+}
+
+void MediaPlaybackStatus::ContextMediaInfo::UpdateGuessedPositionState(
+ const nsID& aElementId, const Maybe<PositionState>& aState) {
+ if (aState) {
+ mGuessedPositionStateMap.InsertOrUpdate(aElementId, *aState);
+ } else {
+ mGuessedPositionStateMap.Remove(aElementId);
+ }
+}
+
} // namespace mozilla::dom
diff --git a/dom/media/mediacontrol/MediaPlaybackStatus.h b/dom/media/mediacontrol/MediaPlaybackStatus.h
index da597e4dfa..f9ac25f73d 100644
--- a/dom/media/mediacontrol/MediaPlaybackStatus.h
+++ b/dom/media/mediacontrol/MediaPlaybackStatus.h
@@ -7,9 +7,11 @@
#include "mozilla/Maybe.h"
#include "mozilla/RefPtr.h"
+#include "mozilla/dom/MediaSession.h"
#include "nsISupportsImpl.h"
#include "nsTArray.h"
#include "nsTHashMap.h"
+#include "nsID.h"
namespace mozilla::dom {
@@ -63,10 +65,14 @@ class MediaPlaybackStatus final {
public:
void UpdateMediaPlaybackState(uint64_t aContextId, MediaPlaybackState aState);
void UpdateMediaAudibleState(uint64_t aContextId, MediaAudibleState aState);
+ void UpdateGuessedPositionState(uint64_t aContextId, const nsID& aElementId,
+ const Maybe<PositionState>& aState);
bool IsPlaying() const;
bool IsAudible() const;
bool IsAnyMediaBeingControlled() const;
+ Maybe<PositionState> GuessedMediaPositionState(
+ Maybe<uint64_t> aPreferredContextId) const;
Maybe<uint64_t> GetAudioFocusOwnerContextId() const;
@@ -121,6 +127,10 @@ class MediaPlaybackStatus final {
bool IsAnyMediaBeingControlled() const { return mControlledMediaNum > 0; }
uint64_t Id() const { return mContextId; }
+ Maybe<PositionState> GuessedPositionState() const;
+ void UpdateGuessedPositionState(const nsID& aElementId,
+ const Maybe<PositionState>& aState);
+
private:
/**
* The possible value for those three numbers should follow this rule,
@@ -130,6 +140,12 @@ class MediaPlaybackStatus final {
uint32_t mAudibleMediaNum = 0;
uint32_t mPlayingMediaNum = 0;
uint64_t mContextId = 0;
+
+ /**
+ * Contains the guessed position state of all media elements in this
+ * browsing context identified by their ID.
+ */
+ nsTHashMap<nsID, PositionState> mGuessedPositionStateMap;
};
ContextMediaInfo& GetNotNullContextInfo(uint64_t aContextId);
diff --git a/dom/media/mediacontrol/MediaStatusManager.cpp b/dom/media/mediacontrol/MediaStatusManager.cpp
index 633ae19a44..6e86dbf2eb 100644
--- a/dom/media/mediacontrol/MediaStatusManager.cpp
+++ b/dom/media/mediacontrol/MediaStatusManager.cpp
@@ -380,6 +380,29 @@ void MediaStatusManager::UpdatePositionState(
mPositionStateChangedEvent.Notify(aState);
}
+void MediaStatusManager::UpdateGuessedPositionState(
+ uint64_t aBrowsingContextId, const nsID& aMediaId,
+ const Maybe<PositionState>& aGuessedState) {
+ mPlaybackStatusDelegate.UpdateGuessedPositionState(aBrowsingContextId,
+ aMediaId, aGuessedState);
+
+ // The position state comes from a non-active media session and
+ // there is another one active (with some metadata).
+ if (mActiveMediaSessionContextId &&
+ *mActiveMediaSessionContextId != aBrowsingContextId) {
+ return;
+ }
+
+ // media session is declared for the updated session, but there's no active
+ // session - it will get emitted once the session becomes active
+ if (mMediaSessionInfoMap.Contains(aBrowsingContextId) &&
+ !mActiveMediaSessionContextId) {
+ return;
+ }
+
+ mPositionStateChangedEvent.Notify(GetCurrentPositionState());
+}
+
void MediaStatusManager::NotifySupportedKeysChangedIfNeeded(
uint64_t aBrowsingContextId) {
// Only the active media session's supported actions would be shown in virtual
@@ -431,11 +454,13 @@ MediaMetadataBase MediaStatusManager::GetCurrentMediaMetadata() const {
Maybe<PositionState> MediaStatusManager::GetCurrentPositionState() const {
if (mActiveMediaSessionContextId) {
auto info = mMediaSessionInfoMap.Lookup(*mActiveMediaSessionContextId);
- if (info) {
+ if (info && info->mPositionState) {
return info->mPositionState;
}
}
- return Nothing();
+
+ return mPlaybackStatusDelegate.GuessedMediaPositionState(
+ mActiveMediaSessionContextId);
}
void MediaStatusManager::FillMissingTitleAndArtworkIfNeeded(
diff --git a/dom/media/mediacontrol/MediaStatusManager.h b/dom/media/mediacontrol/MediaStatusManager.h
index a4216c8453..45f3ccccc5 100644
--- a/dom/media/mediacontrol/MediaStatusManager.h
+++ b/dom/media/mediacontrol/MediaStatusManager.h
@@ -120,6 +120,12 @@ class IMediaInfoUpdater {
// Use this method when media session update its position state.
virtual void UpdatePositionState(uint64_t aBrowsingContextId,
const Maybe<PositionState>& aState) = 0;
+
+ // Use this method to update controlled media's position state and the
+ // browsing context where controlled media exists.
+ virtual void UpdateGuessedPositionState(
+ uint64_t aBrowsingContextId, const nsID& aMediaId,
+ const Maybe<PositionState>& aGuessedState) = 0;
};
/**
@@ -165,12 +171,19 @@ class MediaStatusManager : public IMediaInfoUpdater {
MediaSessionAction aAction) override;
void UpdatePositionState(uint64_t aBrowsingContextId,
const Maybe<PositionState>& aState) override;
+ void UpdateGuessedPositionState(
+ uint64_t aBrowsingContextId, const nsID& aMediaId,
+ const Maybe<PositionState>& aGuessedState) override;
// Return active media session's metadata if active media session exists and
// it has already set its metadata. Otherwise, return default media metadata
// which is based on website's title and favicon.
MediaMetadataBase GetCurrentMediaMetadata() const;
+ // Return the active media session's position state. If the active media
+ // session doesn't exist or doesn't have any state, Nothing is returned.
+ Maybe<PositionState> GetCurrentPositionState() const;
+
bool IsMediaAudible() const;
bool IsMediaPlaying() const;
bool IsAnyMediaBeingControlled() const;
@@ -247,10 +260,6 @@ class MediaStatusManager : public IMediaInfoUpdater {
// media session doesn't exist, return 'None' instead.
MediaSessionPlaybackState GetCurrentDeclaredPlaybackState() const;
- // Return the active media session's position state. If the active media
- // session doesn't exist or doesn't have any state, Nothing is returned.
- Maybe<PositionState> GetCurrentPositionState() const;
-
// This state can match to the `guessed playback state` in the spec [1], it
// indicates if we have any media element playing within the tab which this
// controller belongs to. But currently we only take media elements into
diff --git a/dom/media/mediacontrol/tests/browser/browser_media_control_position_state.js b/dom/media/mediacontrol/tests/browser/browser_media_control_position_state.js
index 6074e2ee16..75f65eb34b 100644
--- a/dom/media/mediacontrol/tests/browser/browser_media_control_position_state.js
+++ b/dom/media/mediacontrol/tests/browser/browser_media_control_position_state.js
@@ -4,6 +4,7 @@ const IFRAME_URL =
"https://example.com/browser/dom/media/mediacontrol/tests/browser/file_iframe_media.html";
const testVideoId = "video";
+const videoDuration = 5.589333;
add_task(async function setupTestingPref() {
await SpecialPowers.pushPrefEnv({
@@ -18,9 +19,15 @@ add_task(async function setupTestingPref() {
add_task(async function testSetPositionState() {
info(`open media page`);
const tab = await createLoadedTabWrapper(PAGE_URL);
+ logPositionStateChangeEvents(tab);
+
+ info(`apply initial position state`);
+ await applyPositionState(tab, { duration: 10 });
info(`start media`);
+ const initialPositionState = isNextPositionState(tab, { duration: 10 });
await playMedia(tab, testVideoId);
+ await initialPositionState;
info(`set duration only`);
await setPositionState(tab, {
@@ -47,9 +54,15 @@ add_task(async function testSetPositionState() {
add_task(async function testSetPositionStateFromInactiveMediaSession() {
info(`open media page`);
const tab = await createLoadedTabWrapper(PAGE_URL);
+ logPositionStateChangeEvents(tab);
+
+ info(`apply initial position state`);
+ await applyPositionState(tab, { duration: 10 });
info(`start media`);
+ const initialPositionState = isNextPositionState(tab, { duration: 10 });
await playMedia(tab, testVideoId);
+ await initialPositionState;
info(
`add an event listener to measure how many times the position state changes`
@@ -82,48 +95,193 @@ add_task(async function testSetPositionStateFromInactiveMediaSession() {
});
/**
- * The following are helper functions.
+ *
+ * @param {boolean} withMetadata
+ * Specifies if the tab should set metadata for the playing video
*/
-async function setPositionState(tab, positionState) {
+async function testGuessedPositionState(withMetadata) {
+ info(`open media page`);
+ const tab = await createLoadedTabWrapper(PAGE_URL);
+ logPositionStateChangeEvents(tab);
+
+ if (withMetadata) {
+ info(`set media metadata`);
+ await setMediaMetadata(tab, { title: "A Video" });
+ }
+
+ info(`start media`);
+ await emitsPositionState(() => playMedia(tab, testVideoId), tab, {
+ duration: videoDuration,
+ position: 0,
+ playbackRate: 1.0,
+ });
+
+ info(`set playback rate to 2x`);
+ await emitsPositionState(() => setPlaybackRate(tab, testVideoId, 2.0), tab, {
+ duration: videoDuration,
+ position: null, // ignored,
+ playbackRate: 2.0,
+ });
+
+ info(`seek to 1s`);
+ await emitsPositionState(() => setCurrentTime(tab, testVideoId, 1.0), tab, {
+ duration: videoDuration,
+ position: 1.0,
+ playbackRate: 2.0,
+ });
+
+ let positionChangedNum = 0;
const controller = tab.linkedBrowser.browsingContext.mediaController;
- const positionStateChanged = new Promise(r => {
- controller.addEventListener(
- "positionstatechange",
- event => {
- const { duration, playbackRate, position } = positionState;
- // duration is mandatory.
- is(
- event.duration,
- duration,
- `expected duration ${event.duration} is equal to ${duration}`
- );
-
- // Playback rate is optional, if it's not present, default should be 1.0
- if (playbackRate) {
- is(
- event.playbackRate,
- playbackRate,
- `expected playbackRate ${event.playbackRate} is equal to ${playbackRate}`
- );
- } else {
- is(event.playbackRate, 1.0, `expected default playbackRate is 1.0`);
- }
-
- // Position state is optional, if it's not present, default should be 0.0
- if (position) {
- is(
- event.position,
- position,
- `expected position ${event.position} is equal to ${position}`
- );
- } else {
- is(event.position, 0.0, `expected default position is 0.0`);
- }
- r();
- },
- { once: true }
- );
+ controller.onpositionstatechange = () => positionChangedNum++;
+
+ info(`pause media`);
+ // shouldn't generate an event
+ await pauseMedia(tab, testVideoId);
+
+ info(`seek to 2s`);
+ await emitsPositionState(() => setCurrentTime(tab, testVideoId, 2.0), tab, {
+ duration: videoDuration,
+ position: 2.0,
+ playbackRate: 2.0,
+ });
+
+ info(`start media`);
+ await emitsPositionState(() => playMedia(tab, testVideoId), tab, {
+ duration: videoDuration,
+ position: 2.0,
+ playbackRate: 2.0,
});
+
+ is(
+ positionChangedNum,
+ 2,
+ `We should only receive two of position changes, because pausing is effectless`
+ );
+
+ info(`remove tab`);
+ await tab.close();
+}
+
+add_task(async function testGuessedPositionStateWithMetadata() {
+ testGuessedPositionState(true);
+});
+
+add_task(async function testGuessedPositionStateWithoutMetadata() {
+ testGuessedPositionState(false);
+});
+
+/**
+ * @typedef {{
+ * duration: number,
+ * playbackRate?: number | null,
+ * position?: number | null,
+ * }} ExpectedPositionState
+ */
+
+/**
+ * Checks if the next received position state matches the expected one.
+ *
+ * @param {tab} tab
+ * The tab that contains the media
+ * @param {ExpectedPositionState} positionState
+ * The expected position state. `duration` is mandatory. `playbackRate`
+ * and `position` are optional. If they're `null`, they're ignored,
+ * otherwise if they're not present or undefined, they're expected to
+ * be the default value.
+ * @returns {Promise}
+ * Resolves when the event has been received
+ */
+async function isNextPositionState(tab, positionState) {
+ const got = await nextPositionState(tab);
+ isPositionState(got, positionState);
+}
+
+/**
+ * Waits for the next position state and returns it
+ *
+ * @param {tab} tab The tab to receive position state from
+ * @returns {Promise<MediaPositionState>} The emitted position state
+ */
+function nextPositionState(tab) {
+ const controller = tab.linkedBrowser.browsingContext.mediaController;
+ return new Promise(r => {
+ controller.addEventListener("positionstatechange", r, { once: true });
+ });
+}
+
+/**
+ * @param {MediaPositionState} got
+ * The received position state
+ * @param {ExpectedPositionState} expected
+ * The expected position state. `duration` is mandatory. `playbackRate`
+ * and `position` are optional. If they're `null`, they're ignored,
+ * otherwise if they're not present or undefined, they're expected to
+ * be the default value.
+ */
+function isPositionState(got, expected) {
+ const { duration, playbackRate, position } = expected;
+ // duration is mandatory.
+ isFuzzyEq(got.duration, duration, "duration");
+
+ // Playback rate is optional, if it's not present, default should be 1.0
+ if (typeof playbackRate === "number") {
+ isFuzzyEq(got.playbackRate, playbackRate, "playbackRate");
+ } else if (playbackRate !== null) {
+ is(got.playbackRate, 1.0, `expected default playbackRate is 1.0`);
+ }
+
+ // Position is optional, if it's not present, default should be 0.0
+ if (typeof position === "number") {
+ isFuzzyEq(got.position, position, "position");
+ } else if (position !== null) {
+ is(got.position, 0.0, `expected default position is 0.0`);
+ }
+}
+
+/**
+ * Checks if two numbers are equal within one significant digit
+ *
+ * @param {number} got
+ * The value received while testing
+ * @param {number} expected
+ * The expected value
+ * @param {string} role
+ * The role of the check (used for formatting)
+ */
+function isFuzzyEq(got, expected, role) {
+ expected = expected.toFixed(1);
+ got = got.toFixed(1);
+ is(got, expected, `expected ${role} ${got} to equal ${expected}`);
+}
+
+/**
+ * Test if `cb` emits a position state event.
+ *
+ * @param {() => (void | Promise<void>)} cb
+ * A callback that is expected to generate a position state event
+ * @param {tab} tab
+ * The tab that contains the media
+ * @param {ExpectedPositionState} positionState
+ * The expected position state to be generated.
+ */
+async function emitsPositionState(cb, tab, positionState) {
+ const positionStateChanged = isNextPositionState(tab, positionState);
+ await cb();
+ await positionStateChanged;
+}
+
+/**
+ * The following are helper functions.
+ */
+async function setPositionState(tab, positionState) {
+ await emitsPositionState(
+ () => applyPositionState(tab, positionState),
+ tab,
+ positionState
+ );
+}
+
+async function applyPositionState(tab, positionState) {
await SpecialPowers.spawn(
tab.linkedBrowser,
[positionState],
@@ -131,7 +289,12 @@ async function setPositionState(tab, positionState) {
content.navigator.mediaSession.setPositionState(positionState);
}
);
- await positionStateChanged;
+}
+
+async function setMediaMetadata(tab, metadata) {
+ await SpecialPowers.spawn(tab.linkedBrowser, [metadata], data => {
+ content.navigator.mediaSession.metadata = new content.MediaMetadata(data);
+ });
}
async function setPositionStateOnInactiveMediaSession(tab) {
diff --git a/dom/media/mediacontrol/tests/browser/head.js b/dom/media/mediacontrol/tests/browser/head.js
index cac96c0bff..7c6a1e37e4 100644
--- a/dom/media/mediacontrol/tests/browser/head.js
+++ b/dom/media/mediacontrol/tests/browser/head.js
@@ -195,6 +195,58 @@ function checkOrWaitUntilMediaStartedPlaying(tab, elementId) {
}
/**
+ * Set the playback rate on a media element.
+ *
+ * @param {tab} tab
+ * The tab that contains the media which we would check
+ * @param {string} elementId
+ * The element Id of the media which we would check
+ * @param {number} rate
+ * The playback rate to set
+ * @return {Promise}
+ * Resolve when the playback rate has been set
+ */
+function setPlaybackRate(tab, elementId, rate) {
+ return SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [elementId, rate],
+ (Id, rate) => {
+ const video = content.document.getElementById(Id);
+ if (!video) {
+ ok(false, `can't get the media element!`);
+ }
+ video.playbackRate = rate;
+ }
+ );
+}
+
+/**
+ * Set the time on a media element.
+ *
+ * @param {tab} tab
+ * The tab that contains the media which we would check
+ * @param {string} elementId
+ * The element Id of the media which we would check
+ * @param {number} currentTime
+ * The time to set
+ * @return {Promise}
+ * Resolve when the time has been set
+ */
+function setCurrentTime(tab, elementId, currentTime) {
+ return SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [elementId, currentTime],
+ (Id, currentTime) => {
+ const video = content.document.getElementById(Id);
+ if (!video) {
+ ok(false, `can't get the media element!`);
+ }
+ video.currentTime = currentTime;
+ }
+ );
+}
+
+/**
* Returns a promise that resolves when the specific media stops playing.
*
* @param {tab} tab
@@ -390,6 +442,18 @@ function waitUntilMediaControllerAmountChanged() {
}
/**
+ * Wait until the position state that would be displayed on the virtual control
+ * interface changes. we would observe that by listening for
+ * `media-position-state-changed` notification.
+ *
+ * @return {Promise}
+ * Resolve when observing `media-position-state-changed`
+ */
+function waitUntilPositionStateChanged() {
+ return BrowserUtils.promiseObserved("media-position-state-changed");
+}
+
+/**
* check if the media controll from given tab is active. If not, return a
* promise and resolve it when controller become active.
*/
@@ -400,3 +464,20 @@ async function checkOrWaitUntilControllerBecomeActive(tab) {
}
await new Promise(r => (controller.onactivated = r));
}
+
+/**
+ * Logs all `positionstatechange` events in a tab.
+ */
+function logPositionStateChangeEvents(tab) {
+ tab.linkedBrowser.browsingContext.mediaController.addEventListener(
+ "positionstatechange",
+ event =>
+ info(
+ `got position state: ${JSON.stringify({
+ duration: event.duration,
+ playbackRate: event.playbackRate,
+ position: event.position,
+ })}`
+ )
+ );
+}
diff --git a/dom/media/mediasource/MediaSource.cpp b/dom/media/mediasource/MediaSource.cpp
index 94e9904262..8f4cd0c514 100644
--- a/dom/media/mediasource/MediaSource.cpp
+++ b/dom/media/mediasource/MediaSource.cpp
@@ -191,8 +191,7 @@ void MediaSource::IsTypeSupported(const nsAString& aType,
return;
}
if (mimeType == MEDIAMIMETYPE("audio/webm")) {
- if (!(StaticPrefs::media_mediasource_webm_enabled() ||
- StaticPrefs::media_mediasource_webm_audio_enabled())) {
+ if (!StaticPrefs::media_mediasource_webm_enabled()) {
// Don't leak information about the fact that it's pref-disabled; just act
// like we can't play it. Or should this throw "Unknown type"?
return aRv.ThrowNotSupportedError("Can't play type");
diff --git a/dom/media/mediasource/MediaSourceDemuxer.cpp b/dom/media/mediasource/MediaSourceDemuxer.cpp
index 6df15cb2d4..b846beb403 100644
--- a/dom/media/mediasource/MediaSourceDemuxer.cpp
+++ b/dom/media/mediasource/MediaSourceDemuxer.cpp
@@ -271,7 +271,7 @@ MediaSourceTrackDemuxer::MediaSourceTrackDemuxer(MediaSourceDemuxer* aParent,
: mParent(aParent),
mTaskQueue(mParent->GetTaskQueue()),
mType(aType),
- mMutex("MediaSourceTrackDemuxer"),
+ mMutex("MediaSourceTrackDemuxer", this),
mManager(aManager),
mReset(true),
mPreRoll(TimeUnit::FromMicroseconds(
@@ -316,6 +316,7 @@ void MediaSourceTrackDemuxer::Reset() {
RefPtr<MediaSourceTrackDemuxer> self = this;
nsCOMPtr<nsIRunnable> task =
NS_NewRunnableFunction("MediaSourceTrackDemuxer::Reset", [self]() {
+ self->mMutex.AssertOnWritingThread();
self->mNextSample.reset();
self->mReset = true;
if (!self->mManager) {
@@ -324,7 +325,7 @@ void MediaSourceTrackDemuxer::Reset() {
MOZ_ASSERT(self->OnTaskQueue());
self->mManager->Seek(self->mType, TimeUnit::Zero(), TimeUnit::Zero());
{
- MutexAutoLock mon(self->mMutex);
+ MutexSingleWriterAutoLockOnThread(lock, self->mMutex);
self->mNextRandomAccessPoint =
self->mManager->GetNextRandomAccessPoint(
self->mType, MediaSourceDemuxer::EOS_FUZZ);
@@ -336,7 +337,7 @@ void MediaSourceTrackDemuxer::Reset() {
}
nsresult MediaSourceTrackDemuxer::GetNextRandomAccessPoint(TimeUnit* aTime) {
- MutexAutoLock mon(mMutex);
+ MutexSingleWriterAutoLock mon(mMutex);
*aTime = mNextRandomAccessPoint;
return NS_OK;
}
@@ -350,7 +351,7 @@ MediaSourceTrackDemuxer::SkipToNextRandomAccessPoint(
}
media::TimeIntervals MediaSourceTrackDemuxer::GetBuffered() {
- MutexAutoLock mon(mMutex);
+ MutexSingleWriterAutoLock mon(mMutex);
if (!mManager) {
return media::TimeIntervals();
}
@@ -371,6 +372,7 @@ void MediaSourceTrackDemuxer::BreakCycles() {
RefPtr<MediaSourceTrackDemuxer::SeekPromise> MediaSourceTrackDemuxer::DoSeek(
const TimeUnit& aTime) {
+ mMutex.AssertOnWritingThread();
if (!mManager) {
return SeekPromise::CreateAndReject(
MediaResult(NS_ERROR_DOM_MEDIA_CANCELED,
@@ -426,7 +428,7 @@ RefPtr<MediaSourceTrackDemuxer::SeekPromise> MediaSourceTrackDemuxer::DoSeek(
}
mReset = false;
{
- MutexAutoLock mon(mMutex);
+ MutexSingleWriterAutoLockOnThread(lock, mMutex);
mNextRandomAccessPoint =
mManager->GetNextRandomAccessPoint(mType, MediaSourceDemuxer::EOS_FUZZ);
}
@@ -435,6 +437,7 @@ RefPtr<MediaSourceTrackDemuxer::SeekPromise> MediaSourceTrackDemuxer::DoSeek(
RefPtr<MediaSourceTrackDemuxer::SamplesPromise>
MediaSourceTrackDemuxer::DoGetSamples(int32_t aNumSamples) {
+ mMutex.AssertOnWritingThread();
if (!mManager) {
return SamplesPromise::CreateAndReject(
MediaResult(NS_ERROR_DOM_MEDIA_CANCELED,
@@ -487,7 +490,7 @@ MediaSourceTrackDemuxer::DoGetSamples(int32_t aNumSamples) {
RefPtr<SamplesHolder> samples = new SamplesHolder;
samples->AppendSample(sample);
{
- MutexAutoLock mon(mMutex); // spurious warning will be given
+ MutexSingleWriterAutoLockOnThread(lock, mMutex);
// Diagnostic asserts for bug 1810396
MOZ_DIAGNOSTIC_ASSERT(sample, "Invalid sample pointer found!");
MOZ_DIAGNOSTIC_ASSERT(sample->HasValidTime(), "Invalid sample time found!");
@@ -505,6 +508,7 @@ MediaSourceTrackDemuxer::DoGetSamples(int32_t aNumSamples) {
RefPtr<MediaSourceTrackDemuxer::SkipAccessPointPromise>
MediaSourceTrackDemuxer::DoSkipToNextRandomAccessPoint(
const TimeUnit& aTimeThreadshold) {
+ mMutex.AssertOnWritingThread();
if (!mManager) {
return SkipAccessPointPromise::CreateAndReject(
SkipFailureHolder(MediaResult(NS_ERROR_DOM_MEDIA_CANCELED,
@@ -534,13 +538,13 @@ MediaSourceTrackDemuxer::DoSkipToNextRandomAccessPoint(
}
bool MediaSourceTrackDemuxer::HasManager(TrackBuffersManager* aManager) const {
- MOZ_ASSERT(OnTaskQueue());
+ mMutex.AssertOnWritingThread();
return mManager == aManager;
}
void MediaSourceTrackDemuxer::DetachManager() {
MOZ_ASSERT(OnTaskQueue());
- MutexAutoLock mon(mMutex);
+ MutexSingleWriterAutoLock mon(mMutex);
mManager = nullptr;
}
diff --git a/dom/media/mediasource/MediaSourceDemuxer.h b/dom/media/mediasource/MediaSourceDemuxer.h
index 177aae769b..fa25878af9 100644
--- a/dom/media/mediasource/MediaSourceDemuxer.h
+++ b/dom/media/mediasource/MediaSourceDemuxer.h
@@ -101,13 +101,16 @@ class MediaSourceDemuxer : public MediaDataDemuxer,
class MediaSourceTrackDemuxer
: public MediaTrackDemuxer,
- public DecoderDoctorLifeLogger<MediaSourceTrackDemuxer> {
+ public DecoderDoctorLifeLogger<MediaSourceTrackDemuxer>,
+ public SingleWriterLockOwner {
public:
MediaSourceTrackDemuxer(MediaSourceDemuxer* aParent,
TrackInfo::TrackType aType,
TrackBuffersManager* aManager)
MOZ_REQUIRES(aParent->mMutex);
+ bool OnWritingThread() const override { return OnTaskQueue(); }
+
UniquePtr<TrackInfo> GetInfo() const override;
RefPtr<SeekPromise> Seek(const media::TimeUnit& aTime) override;
@@ -146,12 +149,12 @@ class MediaSourceTrackDemuxer
TrackInfo::TrackType mType;
// Mutex protecting members below accessed from multiple threads.
- Mutex mMutex MOZ_UNANNOTATED;
- media::TimeUnit mNextRandomAccessPoint;
+ MutexSingleWriter mMutex;
+ media::TimeUnit mNextRandomAccessPoint MOZ_GUARDED_BY(mMutex);
// Would be accessed in MFR's demuxer proxy task queue and TaskQueue, and
// only be set on the TaskQueue. It can be accessed while on TaskQueue without
// the need for the lock.
- RefPtr<TrackBuffersManager> mManager;
+ RefPtr<TrackBuffersManager> mManager MOZ_GUARDED_BY(mMutex);
// Only accessed on TaskQueue
Maybe<RefPtr<MediaRawData>> mNextSample;
diff --git a/dom/media/metrics.yaml b/dom/media/metrics.yaml
index 58e525174b..3f735b0273 100644
--- a/dom/media/metrics.yaml
+++ b/dom/media/metrics.yaml
@@ -166,3 +166,27 @@ media.playback:
description: True if the first frame is decoded by a hardware decoder.
type: boolean
expires: never
+ device_hardware_decoder_support:
+ type: labeled_boolean
+ description:
+ The results of hardware decoder support for different video codecs. True
+ means that codec can be decoded by hardware on user's device.
+ metadata:
+ tags:
+ - 'Core :: Audio/Video: Playback'
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1892516
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1892516#c4
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - media-alerts@mozilla.com
+ expires: never
+ labels:
+ - h264
+ - vp8
+ - vp9
+ - av1
+ - hevc
+ telemetry_mirror: MEDIA_DEVICE_HARDWARE_DECODING_SUPPORT
diff --git a/dom/media/moz.build b/dom/media/moz.build
index ac62e9b67e..7f256387c7 100644
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -157,7 +157,7 @@ EXPORTS += [
"FileBlockCache.h",
"ForwardedInputTrack.h",
"FrameStatistics.h",
- "ImageToI420.h",
+ "ImageConversion.h",
"Intervals.h",
"MediaCache.h",
"MediaContainerType.h",
@@ -190,7 +190,6 @@ EXPORTS += [
"MediaTrackList.h",
"MediaTrackListener.h",
"MemoryBlockCache.h",
- "MPSCQueue.h",
"nsIDocumentActivity.h",
"PrincipalChangeObserver.h",
"PrincipalHandle.h",
@@ -237,6 +236,7 @@ EXPORTS.mozilla.dom += [
"MediaDevices.h",
"MediaStreamError.h",
"MediaStreamTrack.h",
+ "MPSCQueue.h",
"VideoPlaybackQuality.h",
"VideoStreamTrack.h",
"VideoTrack.h",
@@ -282,7 +282,7 @@ UNIFIED_SOURCES += [
"GetUserMediaRequest.cpp",
"GraphDriver.cpp",
"GraphRunner.cpp",
- "ImageToI420.cpp",
+ "ImageConversion.cpp",
"MediaCache.cpp",
"MediaContainerType.cpp",
"MediaDecoder.cpp",
diff --git a/dom/media/ogg/OggDemuxer.cpp b/dom/media/ogg/OggDemuxer.cpp
index 3f14887617..db9477cf1c 100644
--- a/dom/media/ogg/OggDemuxer.cpp
+++ b/dom/media/ogg/OggDemuxer.cpp
@@ -2050,11 +2050,7 @@ nsresult OggDemuxer::SeekBisection(TrackInfo::TrackType aType,
interval = 0;
break;
}
-
backsteps = std::min(backsteps + 1, maxBackStep);
- // We reset mustBackoff. If we still need to backoff further, it will
- // be set to true again.
- mustBackoff = false;
} else {
backsteps = 0;
}
diff --git a/dom/media/platforms/EncoderConfig.cpp b/dom/media/platforms/EncoderConfig.cpp
index ed780b947c..2c32e4c2ff 100644
--- a/dom/media/platforms/EncoderConfig.cpp
+++ b/dom/media/platforms/EncoderConfig.cpp
@@ -7,6 +7,7 @@
#include "EncoderConfig.h"
#include "MP4Decoder.h"
#include "VPXDecoder.h"
+#include "mozilla/dom/BindingUtils.h"
namespace mozilla {
@@ -24,4 +25,45 @@ CodecType EncoderConfig::CodecTypeForMime(const nsACString& aMimeType) {
return CodecType::Unknown;
}
+const char* CodecTypeStrings[] = {
+ "BeginVideo", "H264", "VP8", "VP9", "EndVideo", "Opus", "Vorbis",
+ "Flac", "AAC", "PCM", "G722", "EndAudio", "Unknown"};
+
+nsCString EncoderConfig::ToString() const {
+ nsCString rv;
+ rv.Append(CodecTypeStrings[UnderlyingValue(mCodec)]);
+ rv.AppendLiteral(mBitrateMode == BitrateMode::Constant ? " (CBR)" : " (VBR)");
+ rv.AppendPrintf("%" PRIu32 "bps", mBitrate);
+ if (mUsage == Usage::Realtime) {
+ rv.AppendLiteral(", realtime");
+ } else {
+ rv.AppendLiteral(", record");
+ }
+ if (mCodec > CodecType::_BeginVideo_ && mCodec < CodecType::_EndVideo_) {
+ rv.AppendPrintf(" [%dx%d]", mSize.Width(), mSize.Height());
+ if (mHardwarePreference == HardwarePreference::RequireHardware) {
+ rv.AppendLiteral(", hw required");
+ } else if (mHardwarePreference == HardwarePreference::RequireSoftware) {
+ rv.AppendLiteral(", sw required");
+ } else {
+ rv.AppendLiteral(", hw: no preference");
+ }
+ rv.AppendPrintf(" format: %s", GetEnumString(mPixelFormat).get());
+ rv.AppendPrintf(" format (source): %s",
+ GetEnumString(mSourcePixelFormat).get());
+ if (mScalabilityMode == ScalabilityMode::L1T2) {
+ rv.AppendLiteral(" (L1T2)");
+ } else if (mScalabilityMode == ScalabilityMode::L1T3) {
+ rv.AppendLiteral(" (L1T2)");
+ }
+ rv.AppendPrintf(", fps: %" PRIu8, mFramerate);
+ rv.AppendPrintf(", kf interval: %zu", mKeyframeInterval);
+ } else {
+ rv.AppendPrintf(", ch: %" PRIu32 ", %" PRIu32 "Hz", mNumberOfChannels,
+ mSampleRate);
+ }
+ rv.AppendPrintf("(w/%s codec specific)", mCodecSpecific ? "" : "o");
+ return rv;
+};
+
} // namespace mozilla
diff --git a/dom/media/platforms/EncoderConfig.h b/dom/media/platforms/EncoderConfig.h
index e0da1709d6..15241b71c1 100644
--- a/dom/media/platforms/EncoderConfig.h
+++ b/dom/media/platforms/EncoderConfig.h
@@ -159,6 +159,8 @@ class EncoderConfig final {
static CodecType CodecTypeForMime(const nsACString& aMimeType);
+ nsCString ToString() const;
+
bool IsVideo() const {
return mCodec > CodecType::_BeginVideo_ && mCodec < CodecType::_EndVideo_;
}
diff --git a/dom/media/platforms/MediaCodecsSupport.cpp b/dom/media/platforms/MediaCodecsSupport.cpp
index 13c10ab389..1386e87a6c 100644
--- a/dom/media/platforms/MediaCodecsSupport.cpp
+++ b/dom/media/platforms/MediaCodecsSupport.cpp
@@ -195,15 +195,12 @@ CodecDefinition MCSInfo::GetCodecDefinition(const MediaCodec& aCodec) {
}
MediaCodecsSupport MCSInfo::GetMediaCodecsSupportEnum(
- const MediaCodec& aCodec, const DecodeSupportSet& aSupport) {
- if (aSupport.isEmpty()) {
- return MediaCodecsSupport{};
- }
+ const MediaCodec& aCodec, const DecodeSupport& aSupport) {
const CodecDefinition cd = GetCodecDefinition(aCodec);
- if (aSupport.contains(DecodeSupport::SoftwareDecode)) {
+ if (aSupport == DecodeSupport::SoftwareDecode) {
return cd.swDecodeSupport;
}
- if (aSupport.contains(DecodeSupport::HardwareDecode)) {
+ if (aSupport == DecodeSupport::HardwareDecode) {
return cd.hwDecodeSupport;
}
return MediaCodecsSupport::SENTINEL;
diff --git a/dom/media/platforms/MediaCodecsSupport.h b/dom/media/platforms/MediaCodecsSupport.h
index ead4426259..5176b4ecb2 100644
--- a/dom/media/platforms/MediaCodecsSupport.h
+++ b/dom/media/platforms/MediaCodecsSupport.h
@@ -183,7 +183,7 @@ class MCSInfo final {
// Returns a MediaCodecsSupport enum corresponding to the provided
// codec type and decode support level requested.
static MediaCodecsSupport GetMediaCodecsSupportEnum(
- const MediaCodec& aCodec, const DecodeSupportSet& aSupport);
+ const MediaCodec& aCodec, const DecodeSupport& aSupport);
// Returns true if SW/HW decode enum for a given codec is present in the args.
static bool SupportsSoftwareDecode(
diff --git a/dom/media/platforms/PDMFactory.cpp b/dom/media/platforms/PDMFactory.cpp
index 00f46385e2..f640ee4506 100644
--- a/dom/media/platforms/PDMFactory.cpp
+++ b/dom/media/platforms/PDMFactory.cpp
@@ -27,6 +27,7 @@
#include "mozilla/RemoteDecoderManagerChild.h"
#include "mozilla/RemoteDecoderModule.h"
#include "mozilla/SharedThreadPool.h"
+#include "mozilla/StaticMutex.h"
#include "mozilla/StaticPrefs_media.h"
#include "mozilla/SyncRunnable.h"
#include "mozilla/TaskQueue.h"
@@ -474,7 +475,7 @@ DecodeSupportSet PDMFactory::Supports(
void PDMFactory::CreatePDMs() {
if (StaticPrefs::media_use_blank_decoder()) {
- CreateAndStartupPDM<BlankDecoderModule>();
+ StartupPDM(BlankDecoderModule::Create());
// The Blank PDM SupportsMimeType reports true for all codecs; the creation
// of its decoder is infallible. As such it will be used for all media, we
// can stop creating more PDM from this point.
@@ -500,7 +501,7 @@ void PDMFactory::CreatePDMs() {
void PDMFactory::CreateGpuPDMs() {
#ifdef XP_WIN
if (StaticPrefs::media_wmf_enabled()) {
- CreateAndStartupPDM<WMFDecoderModule>();
+ StartupPDM(WMFDecoderModule::Create());
}
#endif
}
@@ -529,12 +530,12 @@ void PDMFactory::CreateRddPDMs() {
#ifdef XP_WIN
if (StaticPrefs::media_wmf_enabled() &&
StaticPrefs::media_rdd_wmf_enabled()) {
- CreateAndStartupPDM<WMFDecoderModule>();
+ StartupPDM(WMFDecoderModule::Create());
}
#endif
#ifdef MOZ_APPLEMEDIA
if (StaticPrefs::media_rdd_applemedia_enabled()) {
- CreateAndStartupPDM<AppleDecoderModule>();
+ StartupPDM(AppleDecoderModule::Create());
}
#endif
StartupPDM(FFVPXRuntimeLinker::CreateDecoder());
@@ -546,7 +547,8 @@ void PDMFactory::CreateRddPDMs() {
FFmpegRuntimeLinker::LinkStatusCode());
}
#endif
- CreateAndStartupPDM<AgnosticDecoderModule>();
+ StartupPDM(AgnosticDecoderModule::Create(),
+ StaticPrefs::media_prefer_non_ffvpx());
}
void PDMFactory::CreateUtilityPDMs() {
@@ -555,13 +557,13 @@ void PDMFactory::CreateUtilityPDMs() {
if (StaticPrefs::media_wmf_enabled() &&
StaticPrefs::media_utility_wmf_enabled() &&
aKind == ipc::SandboxingKind::UTILITY_AUDIO_DECODING_WMF) {
- CreateAndStartupPDM<WMFDecoderModule>();
+ StartupPDM(WMFDecoderModule::Create());
}
#endif
#ifdef MOZ_APPLEMEDIA
if (StaticPrefs::media_utility_applemedia_enabled() &&
aKind == ipc::SandboxingKind::UTILITY_AUDIO_DECODING_APPLE_MEDIA) {
- CreateAndStartupPDM<AppleDecoderModule>();
+ StartupPDM(AppleDecoderModule::Create());
}
#endif
if (aKind == ipc::SandboxingKind::GENERIC_UTILITY) {
@@ -582,12 +584,13 @@ void PDMFactory::CreateUtilityPDMs() {
StaticPrefs::media_android_media_codec_preferred());
}
#endif
- CreateAndStartupPDM<AgnosticDecoderModule>();
+ StartupPDM(AgnosticDecoderModule::Create(),
+ StaticPrefs::media_prefer_non_ffvpx());
}
#ifdef MOZ_WMF_MEDIA_ENGINE
if (aKind == ipc::SandboxingKind::MF_MEDIA_ENGINE_CDM) {
if (StaticPrefs::media_wmf_media_engine_enabled()) {
- CreateAndStartupPDM<MFMediaEngineDecoderModule>();
+ StartupPDM(MFMediaEngineDecoderModule::Create());
}
}
#endif
@@ -595,31 +598,30 @@ void PDMFactory::CreateUtilityPDMs() {
void PDMFactory::CreateContentPDMs() {
if (StaticPrefs::media_gpu_process_decoder()) {
- CreateAndStartupPDM<RemoteDecoderModule>(RemoteDecodeIn::GpuProcess);
+ StartupPDM(RemoteDecoderModule::Create(RemoteDecodeIn::GpuProcess));
}
if (StaticPrefs::media_rdd_process_enabled()) {
- CreateAndStartupPDM<RemoteDecoderModule>(RemoteDecodeIn::RddProcess);
+ StartupPDM(RemoteDecoderModule::Create(RemoteDecodeIn::RddProcess));
}
if (StaticPrefs::media_utility_process_enabled()) {
#ifdef MOZ_APPLEMEDIA
- CreateAndStartupPDM<RemoteDecoderModule>(
- RemoteDecodeIn::UtilityProcess_AppleMedia);
+ StartupPDM(
+ RemoteDecoderModule::Create(RemoteDecodeIn::UtilityProcess_AppleMedia));
#endif
#ifdef XP_WIN
- CreateAndStartupPDM<RemoteDecoderModule>(
- RemoteDecodeIn::UtilityProcess_WMF);
+ StartupPDM(RemoteDecoderModule::Create(RemoteDecodeIn::UtilityProcess_WMF));
#endif
// WMF and AppleMedia should be created before Generic because the order
// affects what decoder module would be chose first.
- CreateAndStartupPDM<RemoteDecoderModule>(
- RemoteDecodeIn::UtilityProcess_Generic);
+ StartupPDM(
+ RemoteDecoderModule::Create(RemoteDecodeIn::UtilityProcess_Generic));
}
#ifdef MOZ_WMF_MEDIA_ENGINE
if (StaticPrefs::media_wmf_media_engine_enabled()) {
- CreateAndStartupPDM<RemoteDecoderModule>(
- RemoteDecodeIn::UtilityProcess_MFMediaEngineCDM);
+ StartupPDM(RemoteDecoderModule::Create(
+ RemoteDecodeIn::UtilityProcess_MFMediaEngineCDM));
}
#endif
@@ -631,7 +633,7 @@ void PDMFactory::CreateContentPDMs() {
# ifdef MOZ_WMF
if (!StaticPrefs::media_rdd_process_enabled() ||
!StaticPrefs::media_rdd_wmf_enabled()) {
- if (!CreateAndStartupPDM<WMFDecoderModule>()) {
+ if (!StartupPDM(WMFDecoderModule::Create())) {
mFailureFlags += DecoderDoctorDiagnostics::Flags::WMFFailedToLoad;
}
}
@@ -642,11 +644,11 @@ void PDMFactory::CreateContentPDMs() {
#endif
#ifdef MOZ_APPLEMEDIA
- CreateAndStartupPDM<AppleDecoderModule>();
+ StartupPDM(AppleDecoderModule::Create());
#endif
#ifdef MOZ_OMX
if (StaticPrefs::media_omx_enabled()) {
- CreateAndStartupPDM<OmxDecoderModule>();
+ StartupPDM(OmxDecoderModule::Create());
}
#endif
StartupPDM(FFVPXRuntimeLinker::CreateDecoder());
@@ -658,7 +660,8 @@ void PDMFactory::CreateContentPDMs() {
}
#endif
- CreateAndStartupPDM<AgnosticDecoderModule>();
+ StartupPDM(AgnosticDecoderModule::Create(),
+ StaticPrefs::media_prefer_non_ffvpx());
#if !defined(MOZ_WIDGET_ANDROID) // Still required for video?
}
#endif // !defined(MOZ_WIDGET_ANDROID)
@@ -681,7 +684,7 @@ void PDMFactory::CreateContentPDMs() {
void PDMFactory::CreateDefaultPDMs() {
#ifdef XP_WIN
if (StaticPrefs::media_wmf_enabled()) {
- if (!CreateAndStartupPDM<WMFDecoderModule>()) {
+ if (!StartupPDM(WMFDecoderModule::Create())) {
mFailureFlags += DecoderDoctorDiagnostics::Flags::WMFFailedToLoad;
}
} else if (StaticPrefs::media_decoder_doctor_wmf_disabled_is_failure()) {
@@ -690,11 +693,11 @@ void PDMFactory::CreateDefaultPDMs() {
#endif
#ifdef MOZ_APPLEMEDIA
- CreateAndStartupPDM<AppleDecoderModule>();
+ StartupPDM(AppleDecoderModule::Create());
#endif
#ifdef MOZ_OMX
if (StaticPrefs::media_omx_enabled()) {
- CreateAndStartupPDM<OmxDecoderModule>();
+ StartupPDM(OmxDecoderModule::Create());
}
#endif
StartupPDM(FFVPXRuntimeLinker::CreateDecoder());
@@ -712,7 +715,8 @@ void PDMFactory::CreateDefaultPDMs() {
}
#endif
- CreateAndStartupPDM<AgnosticDecoderModule>();
+ StartupPDM(AgnosticDecoderModule::Create(),
+ StaticPrefs::media_prefer_non_ffvpx());
if (StaticPrefs::media_gmp_decoder_enabled() &&
!StartupPDM(GMPDecoderModule::Create(),
@@ -783,9 +787,11 @@ void PDMFactory::SetCDMProxy(CDMProxy* aProxy) {
mEMEPDM = MakeRefPtr<EMEDecoderModule>(aProxy, m);
}
+StaticMutex sSupportedMutex;
+
/* static */
media::MediaCodecsSupported PDMFactory::Supported(bool aForceRefresh) {
- MOZ_ASSERT(NS_IsMainThread());
+ StaticMutexAutoLock lock(sSupportedMutex);
static auto calculate = []() {
auto pdm = MakeRefPtr<PDMFactory>();
diff --git a/dom/media/platforms/PDMFactory.h b/dom/media/platforms/PDMFactory.h
index c56c11c506..9a4d4ff6b9 100644
--- a/dom/media/platforms/PDMFactory.h
+++ b/dom/media/platforms/PDMFactory.h
@@ -79,11 +79,6 @@ class PDMFactory final {
void CreateContentPDMs();
void CreateDefaultPDMs();
- template <typename DECODER_MODULE, typename... ARGS>
- bool CreateAndStartupPDM(ARGS&&... aArgs) {
- return StartupPDM(DECODER_MODULE::Create(std::forward<ARGS>(aArgs)...));
- }
-
// Startup the provided PDM and add it to our list if successful.
bool StartupPDM(already_AddRefed<PlatformDecoderModule> aPDM,
bool aInsertAtBeginning = false);
diff --git a/dom/media/platforms/SimpleMap.h b/dom/media/platforms/SimpleMap.h
index c26bff1e9a..635ba6f085 100644
--- a/dom/media/platforms/SimpleMap.h
+++ b/dom/media/platforms/SimpleMap.h
@@ -5,49 +5,101 @@
#ifndef mozilla_SimpleMap_h
#define mozilla_SimpleMap_h
+#include <utility>
+
+#include "mozilla/Maybe.h"
#include "mozilla/Mutex.h"
#include "nsTArray.h"
-#include <utility>
-
namespace mozilla {
-template <typename T>
+struct ThreadSafePolicy {
+ struct PolicyLock {
+ explicit PolicyLock(const char* aName) : mMutex(aName) {}
+ Mutex mMutex MOZ_UNANNOTATED;
+ };
+ PolicyLock& mPolicyLock;
+ explicit ThreadSafePolicy(PolicyLock& aPolicyLock)
+ : mPolicyLock(aPolicyLock) {
+ mPolicyLock.mMutex.Lock();
+ }
+ ~ThreadSafePolicy() { mPolicyLock.mMutex.Unlock(); }
+};
+
+struct NoOpPolicy {
+ struct PolicyLock {
+ explicit PolicyLock(const char*) {}
+ };
+ explicit NoOpPolicy(PolicyLock&) {}
+ ~NoOpPolicy() = default;
+};
+
+// An map employing an array instead of a hash table to optimize performance,
+// particularly beneficial when the number of expected items in the map is
+// small.
+template <typename K, typename V, typename Policy = NoOpPolicy>
class SimpleMap {
- public:
- typedef std::pair<int64_t, T> Element;
+ using ElementType = std::pair<K, V>;
+ using MapType = AutoTArray<ElementType, 16>;
- SimpleMap() : mMutex("SimpleMap") {}
+ public:
+ SimpleMap() : mLock("SimpleMap"){};
+ // Check if aKey is in the map.
+ bool Contains(const K& aKey) {
+ struct Comparator {
+ bool Equals(const ElementType& aElement, const K& aKey) const {
+ return aElement.first == aKey;
+ }
+ };
+ Policy guard(mLock);
+ return mMap.Contains(aKey, Comparator());
+ }
// Insert Key and Value pair at the end of our map.
- void Insert(int64_t aKey, const T& aValue) {
- MutexAutoLock lock(mMutex);
+ void Insert(const K& aKey, const V& aValue) {
+ Policy guard(mLock);
mMap.AppendElement(std::make_pair(aKey, aValue));
}
// Sets aValue matching aKey and remove it from the map if found.
// The element returned is the first one found.
// Returns true if found, false otherwise.
- bool Find(int64_t aKey, T& aValue) {
- MutexAutoLock lock(mMutex);
+ bool Find(const K& aKey, V& aValue) {
+ if (Maybe<V> v = Take(aKey)) {
+ aValue = v.extract();
+ return true;
+ }
+ return false;
+ }
+ // Take the value matching aKey and remove it from the map if found.
+ Maybe<V> Take(const K& aKey) {
+ Policy guard(mLock);
for (uint32_t i = 0; i < mMap.Length(); i++) {
- Element& element = mMap[i];
+ ElementType& element = mMap[i];
if (element.first == aKey) {
- aValue = element.second;
+ Maybe<V> value = Some(element.second);
mMap.RemoveElementAt(i);
- return true;
+ return value;
}
}
- return false;
+ return Nothing();
}
// Remove all elements of the map.
void Clear() {
- MutexAutoLock lock(mMutex);
+ Policy guard(mLock);
mMap.Clear();
}
+ // Iterate through all elements of the map and call the function F.
+ template <typename F>
+ void ForEach(F&& aCallback) {
+ Policy guard(mLock);
+ for (const auto& element : mMap) {
+ aCallback(element.first, element.second);
+ }
+ }
private:
- Mutex mMutex MOZ_UNANNOTATED; // To protect mMap.
- AutoTArray<Element, 16> mMap;
+ typename Policy::PolicyLock mLock;
+ MapType mMap;
};
} // namespace mozilla
diff --git a/dom/media/platforms/agnostic/AOMDecoder.cpp b/dom/media/platforms/agnostic/AOMDecoder.cpp
index cb7f784848..284c209d51 100644
--- a/dom/media/platforms/agnostic/AOMDecoder.cpp
+++ b/dom/media/platforms/agnostic/AOMDecoder.cpp
@@ -284,6 +284,8 @@ RefPtr<MediaDataDecoder::DecodePromise> AOMDecoder::ProcessDecode(
aStage.SetYUVColorSpace(b.mYUVColorSpace);
aStage.SetColorRange(b.mColorRange);
aStage.SetColorDepth(b.mColorDepth);
+ aStage.SetStartTimeAndEndTime(v->mTime.ToMicroseconds(),
+ v->GetEndTime().ToMicroseconds());
});
results.AppendElement(std::move(v));
}
diff --git a/dom/media/platforms/agnostic/DAV1DDecoder.cpp b/dom/media/platforms/agnostic/DAV1DDecoder.cpp
index e93ceb27a1..e7339dd8a9 100644
--- a/dom/media/platforms/agnostic/DAV1DDecoder.cpp
+++ b/dom/media/platforms/agnostic/DAV1DDecoder.cpp
@@ -352,6 +352,8 @@ Result<already_AddRefed<VideoData>, MediaResult> DAV1DDecoder::ConstructImage(
aStage.SetYUVColorSpace(b.mYUVColorSpace);
aStage.SetColorRange(b.mColorRange);
aStage.SetColorDepth(b.mColorDepth);
+ aStage.SetStartTimeAndEndTime(aPicture.m.timestamp,
+ aPicture.m.timestamp + aPicture.m.duration);
});
return VideoData::CreateAndCopyData(
diff --git a/dom/media/platforms/agnostic/TheoraDecoder.cpp b/dom/media/platforms/agnostic/TheoraDecoder.cpp
index d60093a204..30dde14697 100644
--- a/dom/media/platforms/agnostic/TheoraDecoder.cpp
+++ b/dom/media/platforms/agnostic/TheoraDecoder.cpp
@@ -236,6 +236,8 @@ RefPtr<MediaDataDecoder::DecodePromise> TheoraDecoder::ProcessDecode(
aStage.SetYUVColorSpace(b.mYUVColorSpace);
aStage.SetColorRange(b.mColorRange);
aStage.SetColorDepth(b.mColorDepth);
+ aStage.SetStartTimeAndEndTime(v->mTime.ToMicroseconds(),
+ v->GetEndTime().ToMicroseconds());
});
});
diff --git a/dom/media/platforms/agnostic/VPXDecoder.cpp b/dom/media/platforms/agnostic/VPXDecoder.cpp
index 1b07606bd5..637c6a6452 100644
--- a/dom/media/platforms/agnostic/VPXDecoder.cpp
+++ b/dom/media/platforms/agnostic/VPXDecoder.cpp
@@ -272,6 +272,8 @@ RefPtr<MediaDataDecoder::DecodePromise> VPXDecoder::ProcessDecode(
aStage.SetYUVColorSpace(b.mYUVColorSpace);
aStage.SetColorRange(b.mColorRange);
aStage.SetColorDepth(b.mColorDepth);
+ aStage.SetStartTimeAndEndTime(v->mTime.ToMicroseconds(),
+ v->GetEndTime().ToMicroseconds());
});
});
diff --git a/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp
index b964036a4a..06b5f7c476 100644
--- a/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp
+++ b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp
@@ -114,15 +114,17 @@ void GMPVideoDecoder::Decoded(GMPVideoi420Frame* aDecodedFrame) {
RefPtr<VideoData> v = r.unwrap();
MOZ_ASSERT(v);
- mPerformanceRecorder.Record(static_cast<int64_t>(decodedFrame->Timestamp()),
- [&](DecodeStage& aStage) {
- aStage.SetImageFormat(DecodeStage::YUV420P);
- aStage.SetResolution(decodedFrame->Width(),
- decodedFrame->Height());
- aStage.SetYUVColorSpace(b.mYUVColorSpace);
- aStage.SetColorDepth(b.mColorDepth);
- aStage.SetColorRange(b.mColorRange);
- });
+ mPerformanceRecorder.Record(
+ static_cast<int64_t>(decodedFrame->Timestamp()),
+ [&](DecodeStage& aStage) {
+ aStage.SetImageFormat(DecodeStage::YUV420P);
+ aStage.SetResolution(decodedFrame->Width(), decodedFrame->Height());
+ aStage.SetYUVColorSpace(b.mYUVColorSpace);
+ aStage.SetColorDepth(b.mColorDepth);
+ aStage.SetColorRange(b.mColorRange);
+ aStage.SetStartTimeAndEndTime(v->mTime.ToMicroseconds(),
+ v->GetEndTime().ToMicroseconds());
+ });
if (mReorderFrames) {
mReorderQueue.Push(std::move(v));
@@ -130,11 +132,11 @@ void GMPVideoDecoder::Decoded(GMPVideoi420Frame* aDecodedFrame) {
mUnorderedData.AppendElement(std::move(v));
}
- if (mSamples.IsEmpty()) {
- // If we have no remaining samples in the table, then we have processed
- // all outstanding decode requests.
- ProcessReorderQueue(mDecodePromise, __func__);
- }
+ if (mSamples.IsEmpty()) {
+ // If we have no remaining samples in the table, then we have processed
+ // all outstanding decode requests.
+ ProcessReorderQueue(mDecodePromise, __func__);
+ }
}
void GMPVideoDecoder::ReceivedDecodedReferenceFrame(const uint64_t aPictureId) {
@@ -201,7 +203,7 @@ void GMPVideoDecoder::Terminated() {
}
void GMPVideoDecoder::ProcessReorderQueue(
- MozPromiseHolder<DecodePromise>& aPromise, const char* aMethodName) {
+ MozPromiseHolder<DecodePromise>& aPromise, StaticString aMethodName) {
if (aPromise.IsEmpty()) {
return;
}
diff --git a/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h
index 1f0f59c685..6f831e5fbc 100644
--- a/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h
+++ b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h
@@ -72,7 +72,7 @@ class GMPVideoDecoder final : public MediaDataDecoder,
virtual GMPUniquePtr<GMPVideoEncodedFrame> CreateFrame(MediaRawData* aSample);
virtual const VideoInfo& GetConfig() const;
void ProcessReorderQueue(MozPromiseHolder<DecodePromise>& aPromise,
- const char* aMethodName);
+ StaticString aMethodName);
private:
~GMPVideoDecoder() = default;
diff --git a/dom/media/platforms/android/AndroidDecoderModule.cpp b/dom/media/platforms/android/AndroidDecoderModule.cpp
index fff8669a74..21d0ede270 100644
--- a/dom/media/platforms/android/AndroidDecoderModule.cpp
+++ b/dom/media/platforms/android/AndroidDecoderModule.cpp
@@ -64,21 +64,28 @@ AndroidDecoderModule::AndroidDecoderModule(CDMProxy* aProxy) {
mProxy = static_cast<MediaDrmCDMProxy*>(aProxy);
}
-StaticAutoPtr<nsTArray<nsCString>> AndroidDecoderModule::sSupportedSwMimeTypes;
-StaticAutoPtr<nsTArray<nsCString>> AndroidDecoderModule::sSupportedHwMimeTypes;
-StaticAutoPtr<MediaCodecsSupported> AndroidDecoderModule::sSupportedCodecs;
+/* static */ bool AndroidDecoderModule::AreSupportedMimeTypesReady() {
+ StaticMutexAutoLock lock(sMutex);
+ return sSupportedSwMimeTypes && sSupportedHwMimeTypes;
+}
+
+/* static */ bool AndroidDecoderModule::IsSupportedCodecsReady() {
+ StaticMutexAutoLock lock(sMutex);
+ return sSupportedCodecs;
+}
/* static */
media::MediaCodecsSupported AndroidDecoderModule::GetSupportedCodecs() {
- if (!sSupportedSwMimeTypes || !sSupportedHwMimeTypes || !sSupportedCodecs) {
+ if (!AreSupportedMimeTypesReady() || !IsSupportedCodecsReady()) {
SetSupportedMimeTypes();
}
+ StaticMutexAutoLock lock(sMutex);
return *sSupportedCodecs;
}
DecodeSupportSet AndroidDecoderModule::SupportsMimeType(
const nsACString& aMimeType) {
- if (!sSupportedSwMimeTypes) {
+ if (!AreSupportedMimeTypesReady()) {
SetSupportedMimeTypes();
}
@@ -135,13 +142,16 @@ DecodeSupportSet AndroidDecoderModule::SupportsMimeType(
// If a codec has no special handling or can't be determined from the
// MIME type string, check if the MIME type string itself is supported.
- if (sSupportedHwMimeTypes &&
- sSupportedHwMimeTypes->Contains(TranslateMimeType(aMimeType))) {
- return DecodeSupport::HardwareDecode;
- }
- if (sSupportedSwMimeTypes &&
- sSupportedSwMimeTypes->Contains(TranslateMimeType(aMimeType))) {
- return DecodeSupport::SoftwareDecode;
+ {
+ StaticMutexAutoLock lock(sMutex);
+ if (sSupportedHwMimeTypes &&
+ sSupportedHwMimeTypes->Contains(TranslateMimeType(aMimeType))) {
+ return DecodeSupport::HardwareDecode;
+ }
+ if (sSupportedSwMimeTypes &&
+ sSupportedSwMimeTypes->Contains(TranslateMimeType(aMimeType))) {
+ return DecodeSupport::SoftwareDecode;
+ }
}
return media::DecodeSupportSet{};
}
@@ -179,24 +189,45 @@ void AndroidDecoderModule::SetSupportedMimeTypes() {
// Inbound MIME types prefixed with SW/HW need to be processed
void AndroidDecoderModule::SetSupportedMimeTypes(
nsTArray<nsCString>&& aSupportedTypes) {
+ StaticMutexAutoLock lock(sMutex);
// Return if support is already cached
if (sSupportedSwMimeTypes && sSupportedHwMimeTypes && sSupportedCodecs) {
return;
}
if (!sSupportedSwMimeTypes) {
sSupportedSwMimeTypes = new nsTArray<nsCString>;
- ClearOnShutdown(&sSupportedSwMimeTypes);
+ if (NS_IsMainThread()) {
+ ClearOnShutdown(&sSupportedSwMimeTypes);
+ } else {
+ Unused << NS_DispatchToMainThread(NS_NewRunnableFunction(__func__, []() {
+ StaticMutexAutoLock lock(sMutex);
+ ClearOnShutdown(&sSupportedSwMimeTypes);
+ }));
+ }
}
if (!sSupportedHwMimeTypes) {
sSupportedHwMimeTypes = new nsTArray<nsCString>;
- ClearOnShutdown(&sSupportedHwMimeTypes);
+ if (NS_IsMainThread()) {
+ ClearOnShutdown(&sSupportedHwMimeTypes);
+ } else {
+ Unused << NS_DispatchToMainThread(NS_NewRunnableFunction(__func__, []() {
+ StaticMutexAutoLock lock(sMutex);
+ ClearOnShutdown(&sSupportedHwMimeTypes);
+ }));
+ }
}
if (!sSupportedCodecs) {
sSupportedCodecs = new MediaCodecsSupported();
- ClearOnShutdown(&sSupportedCodecs);
+ if (NS_IsMainThread()) {
+ ClearOnShutdown(&sSupportedCodecs);
+ } else {
+ Unused << NS_DispatchToMainThread(NS_NewRunnableFunction(__func__, []() {
+ StaticMutexAutoLock lock(sMutex);
+ ClearOnShutdown(&sSupportedCodecs);
+ }));
+ }
}
- DecodeSupportSet support;
// Process each MIME type string
for (const auto& s : aSupportedTypes) {
// Verify MIME type string present
@@ -212,12 +243,13 @@ void AndroidDecoderModule::SetSupportedMimeTypes(
// Extract SW/HW support prefix
const auto caps = Substring(s, 0, 2);
+ DecodeSupport support{};
if (caps == "SW"_ns) {
sSupportedSwMimeTypes->AppendElement(mimeType);
- support += DecodeSupport::SoftwareDecode;
+ support = DecodeSupport::SoftwareDecode;
} else if (caps == "HW"_ns) {
sSupportedHwMimeTypes->AppendElement(mimeType);
- support += DecodeSupport::HardwareDecode;
+ support = DecodeSupport::HardwareDecode;
} else {
SLOG("Error parsing acceleration info from JNI codec string %s",
s.Data());
diff --git a/dom/media/platforms/android/AndroidDecoderModule.h b/dom/media/platforms/android/AndroidDecoderModule.h
index 37a0f08588..5550e123f3 100644
--- a/dom/media/platforms/android/AndroidDecoderModule.h
+++ b/dom/media/platforms/android/AndroidDecoderModule.h
@@ -54,16 +54,25 @@ class AndroidDecoderModule : public PlatformDecoderModule {
private:
explicit AndroidDecoderModule(CDMProxy* aProxy = nullptr);
virtual ~AndroidDecoderModule() = default;
+
+ static bool AreSupportedMimeTypesReady();
+ static bool IsSupportedCodecsReady();
+
RefPtr<MediaDrmCDMProxy> mProxy;
// SW compatible MIME type strings
- static StaticAutoPtr<nsTArray<nsCString>> sSupportedSwMimeTypes;
+ static inline StaticAutoPtr<nsTArray<nsCString>> sSupportedSwMimeTypes
+ MOZ_GUARDED_BY(sMutex);
// HW compatible MIME type strings
- static StaticAutoPtr<nsTArray<nsCString>> sSupportedHwMimeTypes;
+ static inline StaticAutoPtr<nsTArray<nsCString>> sSupportedHwMimeTypes
+ MOZ_GUARDED_BY(sMutex);
// EnumSet containing SW/HW codec support information parsed from
// MIME type strings. If a specific codec could not be determined
// it will not be included in this EnumSet. All supported MIME type strings
// are still stored in sSupportedSwMimeTypes and sSupportedHwMimeTypes.
- static StaticAutoPtr<media::MediaCodecsSupported> sSupportedCodecs;
+ static inline StaticAutoPtr<media::MediaCodecsSupported> sSupportedCodecs
+ MOZ_GUARDED_BY(sMutex);
+
+ static inline StaticMutex sMutex;
};
extern LazyLogModule sAndroidDecoderModuleLog;
diff --git a/dom/media/platforms/android/AndroidEncoderModule.cpp b/dom/media/platforms/android/AndroidEncoderModule.cpp
index 15b23330e2..23c76cba5f 100644
--- a/dom/media/platforms/android/AndroidEncoderModule.cpp
+++ b/dom/media/platforms/android/AndroidEncoderModule.cpp
@@ -29,6 +29,9 @@ bool AndroidEncoderModule::Supports(const EncoderConfig& aConfig) const {
if (!CanLikelyEncode(aConfig)) {
return false;
}
+ if (aConfig.mScalabilityMode != ScalabilityMode::None) {
+ return false;
+ }
return SupportsCodec(aConfig.mCodec);
}
diff --git a/dom/media/platforms/android/RemoteDataDecoder.cpp b/dom/media/platforms/android/RemoteDataDecoder.cpp
index f0fbc7a77c..260b70abdb 100644
--- a/dom/media/platforms/android/RemoteDataDecoder.cpp
+++ b/dom/media/platforms/android/RemoteDataDecoder.cpp
@@ -527,6 +527,8 @@ class RemoteVideoDecoder final : public RemoteDataDecoder {
});
aStage.SetResolution(v->mImage->GetSize().Width(),
v->mImage->GetSize().Height());
+ aStage.SetStartTimeAndEndTime(v->mTime.ToMicroseconds(),
+ v->GetEndTime().ToMicroseconds());
});
RemoteDataDecoder::UpdateOutputStatus(std::move(v));
@@ -574,7 +576,7 @@ class RemoteVideoDecoder final : public RemoteDataDecoder {
bool mIsHardwareAccelerated = false;
// Accessed on mThread and reader's thread. SimpleMap however is
// thread-safe, so it's okay to do so.
- SimpleMap<InputInfo> mInputInfos;
+ SimpleMap<int64_t, InputInfo, ThreadSafePolicy> mInputInfos;
// Only accessed on mThread.
Maybe<TimeUnit> mSeekTarget;
Maybe<TimeUnit> mLatestOutputTime;
diff --git a/dom/media/platforms/apple/AppleEncoderModule.cpp b/dom/media/platforms/apple/AppleEncoderModule.cpp
index e18d9a05c4..6fa4a53ab5 100644
--- a/dom/media/platforms/apple/AppleEncoderModule.cpp
+++ b/dom/media/platforms/apple/AppleEncoderModule.cpp
@@ -27,6 +27,9 @@ bool AppleEncoderModule::Supports(const EncoderConfig& aConfig) const {
if (!CanLikelyEncode(aConfig)) {
return false;
}
+ if (aConfig.mScalabilityMode != ScalabilityMode::None) {
+ return false;
+ }
return aConfig.mCodec == CodecType::H264;
}
diff --git a/dom/media/platforms/apple/AppleVTDecoder.cpp b/dom/media/platforms/apple/AppleVTDecoder.cpp
index 6a70ed19d5..17d462c8c9 100644
--- a/dom/media/platforms/apple/AppleVTDecoder.cpp
+++ b/dom/media/platforms/apple/AppleVTDecoder.cpp
@@ -575,6 +575,8 @@ void AppleVTDecoder::OutputFrame(CVPixelBufferRef aImage,
aStage.SetColorDepth(mColorDepth);
aStage.SetYUVColorSpace(mColorSpace);
aStage.SetColorRange(mColorRange);
+ aStage.SetStartTimeAndEndTime(data->mTime.ToMicroseconds(),
+ data->GetEndTime().ToMicroseconds());
});
// Frames come out in DTS order but we need to output them
diff --git a/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.cpp b/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.cpp
index 1e8e488e25..381cbf71a8 100644
--- a/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.cpp
@@ -5,6 +5,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "FFmpegAudioDecoder.h"
+#include "FFmpegUtils.h"
#include "AudioSampleFormat.h"
#include "FFmpegLog.h"
#include "TimeUnits.h"
@@ -250,7 +251,7 @@ MediaResult FFmpegAudioDecoder<LIBAV_VER>::PostProcessOutput(
aSample->mDuration.ToString().get(),
mLib->av_get_sample_fmt_name(mFrame->format));
- uint32_t numChannels = mCodecContext->channels;
+ uint32_t numChannels = ChannelCount(mCodecContext);
uint32_t samplingRate = mCodecContext->sample_rate;
if (!numChannels) {
numChannels = mAudioInfo.mChannels;
@@ -284,7 +285,7 @@ MediaResult FFmpegAudioDecoder<LIBAV_VER>::PostProcessOutput(
RefPtr<AudioData> data =
new AudioData(aSample->mOffset, pts, std::move(audio), numChannels,
- samplingRate, mCodecContext->channel_layout);
+ samplingRate, numChannels);
MOZ_ASSERT(duration == data->mDuration, "must be equal");
aResults.AppendElement(std::move(data));
@@ -395,16 +396,23 @@ MediaResult FFmpegAudioDecoder<LIBAV_VER>::DoDecode(MediaRawData* aSample,
DecodedData& aResults) {
MOZ_ASSERT(mTaskQueue->IsOnCurrentThread());
PROCESS_DECODE_LOG(aSample);
- AVPacket packet;
- mLib->av_init_packet(&packet);
+ AVPacket* packet;
+#if LIBAVCODEC_VERSION_MAJOR >= 61
+ packet = mLib->av_packet_alloc();
+ auto freePacket = MakeScopeExit([&] { mLib->av_packet_free(&packet); });
+#else
+ AVPacket packet_mem;
+ packet = &packet_mem;
+ mLib->av_init_packet(packet);
+#endif
FFMPEG_LOG("FFmpegAudioDecoder::DoDecode: %d bytes, [%s,%s] (Duration: %s)",
aSize, aSample->mTime.ToString().get(),
aSample->GetEndTime().ToString().get(),
aSample->mDuration.ToString().get());
- packet.data = const_cast<uint8_t*>(aData);
- packet.size = aSize;
+ packet->data = const_cast<uint8_t*>(aData);
+ packet->size = aSize;
if (aGotFrame) {
*aGotFrame = false;
@@ -418,8 +426,9 @@ MediaResult FFmpegAudioDecoder<LIBAV_VER>::DoDecode(MediaRawData* aSample,
}
bool decoded = false;
- auto rv = DecodeUsingFFmpeg(&packet, decoded, aSample, aResults, aGotFrame);
+ auto rv = DecodeUsingFFmpeg(packet, decoded, aSample, aResults, aGotFrame);
NS_ENSURE_SUCCESS(rv, rv);
+
return NS_OK;
}
diff --git a/dom/media/platforms/ffmpeg/FFmpegAudioEncoder.cpp b/dom/media/platforms/ffmpeg/FFmpegAudioEncoder.cpp
index 28db667732..284d1067a9 100644
--- a/dom/media/platforms/ffmpeg/FFmpegAudioEncoder.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegAudioEncoder.cpp
@@ -101,12 +101,13 @@ nsresult FFmpegAudioEncoder<LIBAV_VER>::InitSpecific() {
// And now the audio-specific part
mCodecContext->sample_rate = AssertedCast<int>(mConfig.mSampleRate);
- mCodecContext->channels = AssertedCast<int>(mConfig.mNumberOfChannels);
#if LIBAVCODEC_VERSION_MAJOR >= 60
// Gecko's ordering intentionnally matches ffmepg's ordering
mLib->av_channel_layout_default(&mCodecContext->ch_layout,
- AssertedCast<int>(mCodecContext->channels));
+ AssertedCast<int>(mConfig.mNumberOfChannels));
+#else
+ mCodecContext->channels = AssertedCast<int>(mConfig.mNumberOfChannels);
#endif
switch (mConfig.mCodec) {
@@ -206,7 +207,7 @@ FFmpegAudioEncoder<LIBAV_VER>::EncodeOnePacket(Span<float> aSamples,
// packets smaller than the packet size are allowed when draining.
MOZ_ASSERT(AssertedCast<int>(frameCount) <= mCodecContext->frame_size);
- mFrame->channels = AssertedCast<int>(mConfig.mNumberOfChannels);
+ ChannelCount(mFrame) = AssertedCast<int>(mConfig.mNumberOfChannels);
# if LIBAVCODEC_VERSION_MAJOR >= 60
int rv = mLib->av_channel_layout_copy(&mFrame->ch_layout,
@@ -229,10 +230,10 @@ FFmpegAudioEncoder<LIBAV_VER>::EncodeOnePacket(Span<float> aSamples,
AVRational{.num = 1, .den = static_cast<int>(mConfig.mSampleRate)};
# endif
mFrame->pts = aPts.ToTicksAtRate(mConfig.mSampleRate);
- mFrame->pkt_duration = frameCount;
# if LIBAVCODEC_VERSION_MAJOR >= 60
mFrame->duration = frameCount;
# else
+ mFrame->pkt_duration = frameCount;
// Save duration in the time_base unit.
mDurationMap.Insert(mFrame->pts, mFrame->pkt_duration);
# endif
@@ -258,7 +259,7 @@ FFmpegAudioEncoder<LIBAV_VER>::EncodeOnePacket(Span<float> aSamples,
MOZ_ASSERT(mCodecContext->sample_fmt == AV_SAMPLE_FMT_FLTP);
for (uint32_t i = 0; i < mConfig.mNumberOfChannels; i++) {
DeinterleaveAndConvertBuffer(aSamples.data(), mFrame->nb_samples,
- mFrame->channels, mFrame->data);
+ mConfig.mNumberOfChannels, mFrame->data);
}
}
diff --git a/dom/media/platforms/ffmpeg/FFmpegDataDecoder.cpp b/dom/media/platforms/ffmpeg/FFmpegDataDecoder.cpp
index 30422987cf..e86ff63dba 100644
--- a/dom/media/platforms/ffmpeg/FFmpegDataDecoder.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegDataDecoder.cpp
@@ -231,11 +231,22 @@ FFmpegDataDecoder<LIBAV_VER>::ProcessDrain() {
empty->mTimecode = mLastInputDts;
bool gotFrame = false;
DecodedData results;
- // When draining the FFmpeg decoder will return either a single frame at a
- // time until gotFrame is set to false; or return a block of frames with
- // NS_ERROR_DOM_MEDIA_END_OF_STREAM
- while (NS_SUCCEEDED(DoDecode(empty, &gotFrame, results)) && gotFrame) {
- }
+ // When draining the underlying FFmpeg decoder without encountering any
+ // problems, DoDecode will either return a single frame at a time until
+ // gotFrame is set to false, or it will return a block of frames with
+ // NS_ERROR_DOM_MEDIA_END_OF_STREAM (EOS). However, if any issue arises, such
+ // as pending data in the pipeline being corrupt or invalid, non-EOS errors
+ // like NS_ERROR_DOM_MEDIA_DECODE_ERR will be returned and must be handled
+ // accordingly.
+ do {
+ MediaResult r = DoDecode(empty, &gotFrame, results);
+ if (NS_FAILED(r)) {
+ if (r.Code() == NS_ERROR_DOM_MEDIA_END_OF_STREAM) {
+ break;
+ }
+ return DecodePromise::CreateAndReject(r, __func__);
+ }
+ } while (gotFrame);
return DecodePromise::CreateAndResolve(std::move(results), __func__);
}
diff --git a/dom/media/platforms/ffmpeg/FFmpegDataEncoder.h b/dom/media/platforms/ffmpeg/FFmpegDataEncoder.h
index de80ed36ca..c9a4585913 100644
--- a/dom/media/platforms/ffmpeg/FFmpegDataEncoder.h
+++ b/dom/media/platforms/ffmpeg/FFmpegDataEncoder.h
@@ -28,7 +28,7 @@ class FFmpegDataEncoder : public MediaDataEncoder {};
template <>
class FFmpegDataEncoder<LIBAV_VER> : public MediaDataEncoder {
- using DurationMap = SimpleMap<int64_t>;
+ using DurationMap = SimpleMap<int64_t, int64_t, ThreadSafePolicy>;
public:
FFmpegDataEncoder(const FFmpegLibWrapper* aLib, AVCodecID aCodecID,
diff --git a/dom/media/platforms/ffmpeg/FFmpegEncoderModule.cpp b/dom/media/platforms/ffmpeg/FFmpegEncoderModule.cpp
index b6e734268d..cb507a0810 100644
--- a/dom/media/platforms/ffmpeg/FFmpegEncoderModule.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegEncoderModule.cpp
@@ -20,6 +20,13 @@ bool FFmpegEncoderModule<V>::Supports(const EncoderConfig& aConfig) const {
if (!CanLikelyEncode(aConfig)) {
return false;
}
+ // We only support L1T2 and L1T3 ScalabilityMode in VP8 and VP9 encoders via
+ // libvpx for now.
+ if ((aConfig.mScalabilityMode != ScalabilityMode::None)) {
+ if (aConfig.mCodec != CodecType::VP8 && aConfig.mCodec != CodecType::VP9) {
+ return false;
+ }
+ }
return SupportsCodec(aConfig.mCodec) != AV_CODEC_ID_NONE;
}
diff --git a/dom/media/platforms/ffmpeg/FFmpegLibWrapper.cpp b/dom/media/platforms/ffmpeg/FFmpegLibWrapper.cpp
index 5fd6102a34..8557a1eb19 100644
--- a/dom/media/platforms/ffmpeg/FFmpegLibWrapper.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegLibWrapper.cpp
@@ -69,6 +69,7 @@ FFmpegLibWrapper::LinkResult FFmpegLibWrapper::Link() {
AV_FUNC_58 = 1 << 5,
AV_FUNC_59 = 1 << 6,
AV_FUNC_60 = 1 << 7,
+ AV_FUNC_61 = 1 << 7,
AV_FUNC_AVUTIL_53 = AV_FUNC_53 | AV_FUNC_AVUTIL_MASK,
AV_FUNC_AVUTIL_54 = AV_FUNC_54 | AV_FUNC_AVUTIL_MASK,
AV_FUNC_AVUTIL_55 = AV_FUNC_55 | AV_FUNC_AVUTIL_MASK,
@@ -77,8 +78,10 @@ FFmpegLibWrapper::LinkResult FFmpegLibWrapper::Link() {
AV_FUNC_AVUTIL_58 = AV_FUNC_58 | AV_FUNC_AVUTIL_MASK,
AV_FUNC_AVUTIL_59 = AV_FUNC_59 | AV_FUNC_AVUTIL_MASK,
AV_FUNC_AVUTIL_60 = AV_FUNC_60 | AV_FUNC_AVUTIL_MASK,
+ AV_FUNC_AVUTIL_61 = AV_FUNC_61 | AV_FUNC_AVUTIL_MASK,
AV_FUNC_AVCODEC_ALL = AV_FUNC_53 | AV_FUNC_54 | AV_FUNC_55 | AV_FUNC_56 |
- AV_FUNC_57 | AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60,
+ AV_FUNC_57 | AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60 |
+ AV_FUNC_61,
AV_FUNC_AVUTIL_ALL = AV_FUNC_AVCODEC_ALL | AV_FUNC_AVUTIL_MASK
};
@@ -107,6 +110,9 @@ FFmpegLibWrapper::LinkResult FFmpegLibWrapper::Link() {
case 60:
version = AV_FUNC_60;
break;
+ case 61:
+ version = AV_FUNC_61;
+ break;
default:
FFMPEGV_LOG("Unknown avcodec version: %d", macro);
Unlink();
@@ -153,14 +159,17 @@ FFmpegLibWrapper::LinkResult FFmpegLibWrapper::Link() {
AV_FUNC(avcodec_decode_video2, AV_FUNC_53 | AV_FUNC_54 | AV_FUNC_55 |
AV_FUNC_56 | AV_FUNC_57 | AV_FUNC_58)
AV_FUNC(avcodec_find_decoder, AV_FUNC_AVCODEC_ALL)
- AV_FUNC(avcodec_find_decoder_by_name, AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60)
+ AV_FUNC(avcodec_find_decoder_by_name,
+ AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60 | AV_FUNC_61)
AV_FUNC(avcodec_find_encoder, AV_FUNC_AVCODEC_ALL)
- AV_FUNC(avcodec_find_encoder_by_name, AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60)
+ AV_FUNC(avcodec_find_encoder_by_name,
+ AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60 | AV_FUNC_61)
AV_FUNC(avcodec_flush_buffers, AV_FUNC_AVCODEC_ALL)
AV_FUNC(avcodec_open2, AV_FUNC_AVCODEC_ALL)
AV_FUNC(avcodec_register_all, AV_FUNC_53 | AV_FUNC_54 | AV_FUNC_55 |
AV_FUNC_56 | AV_FUNC_57 | AV_FUNC_58)
- AV_FUNC(av_init_packet, AV_FUNC_AVCODEC_ALL)
+ AV_FUNC(av_init_packet, (AV_FUNC_55 | AV_FUNC_56 | AV_FUNC_57 | AV_FUNC_58 |
+ AV_FUNC_59 | AV_FUNC_60))
AV_FUNC(av_parser_init, AV_FUNC_AVCODEC_ALL)
AV_FUNC(av_parser_close, AV_FUNC_AVCODEC_ALL)
AV_FUNC(av_parser_parse2, AV_FUNC_AVCODEC_ALL)
@@ -168,53 +177,68 @@ FFmpegLibWrapper::LinkResult FFmpegLibWrapper::Link() {
AV_FUNC(avcodec_alloc_frame, (AV_FUNC_53 | AV_FUNC_54))
AV_FUNC(avcodec_get_frame_defaults, (AV_FUNC_53 | AV_FUNC_54))
AV_FUNC(avcodec_free_frame, AV_FUNC_54)
- AV_FUNC(avcodec_send_packet, AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60)
- AV_FUNC(avcodec_receive_packet, AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60)
- AV_FUNC(avcodec_send_frame, AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60)
- AV_FUNC(avcodec_receive_frame, AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60)
- AV_FUNC(avcodec_default_get_buffer2, (AV_FUNC_55 | AV_FUNC_56 | AV_FUNC_57 |
- AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60))
- AV_FUNC(av_packet_alloc, (AV_FUNC_57 | AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60))
- AV_FUNC(av_packet_unref, (AV_FUNC_57 | AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60))
- AV_FUNC(av_packet_free, (AV_FUNC_57 | AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60))
+ AV_FUNC(avcodec_send_packet,
+ AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60 | AV_FUNC_61)
+ AV_FUNC(avcodec_receive_packet,
+ AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60 | AV_FUNC_61)
+ AV_FUNC(avcodec_send_frame, AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60 | AV_FUNC_61)
+ AV_FUNC(avcodec_receive_frame,
+ AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60 | AV_FUNC_61)
+ AV_FUNC(avcodec_default_get_buffer2,
+ (AV_FUNC_55 | AV_FUNC_56 | AV_FUNC_57 | AV_FUNC_58 | AV_FUNC_59 |
+ AV_FUNC_60 | AV_FUNC_61))
+ AV_FUNC(av_packet_alloc,
+ (AV_FUNC_57 | AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60 | AV_FUNC_61))
+ AV_FUNC(av_packet_unref,
+ (AV_FUNC_57 | AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60 | AV_FUNC_61))
+ AV_FUNC(av_packet_free,
+ (AV_FUNC_57 | AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60 | AV_FUNC_61))
AV_FUNC(avcodec_descriptor_get, AV_FUNC_AVCODEC_ALL)
AV_FUNC(av_log_set_level, AV_FUNC_AVUTIL_ALL)
AV_FUNC(av_malloc, AV_FUNC_AVUTIL_ALL)
AV_FUNC(av_freep, AV_FUNC_AVUTIL_ALL)
AV_FUNC(av_frame_alloc,
(AV_FUNC_AVUTIL_55 | AV_FUNC_AVUTIL_56 | AV_FUNC_AVUTIL_57 |
- AV_FUNC_AVUTIL_58 | AV_FUNC_AVUTIL_59 | AV_FUNC_AVUTIL_60))
+ AV_FUNC_AVUTIL_58 | AV_FUNC_AVUTIL_59 | AV_FUNC_AVUTIL_60 |
+ AV_FUNC_AVUTIL_61))
AV_FUNC(av_frame_free,
(AV_FUNC_AVUTIL_55 | AV_FUNC_AVUTIL_56 | AV_FUNC_AVUTIL_57 |
- AV_FUNC_AVUTIL_58 | AV_FUNC_AVUTIL_59 | AV_FUNC_AVUTIL_60))
+ AV_FUNC_AVUTIL_58 | AV_FUNC_AVUTIL_59 | AV_FUNC_AVUTIL_60 |
+ AV_FUNC_AVUTIL_61))
AV_FUNC(av_frame_unref,
(AV_FUNC_AVUTIL_55 | AV_FUNC_AVUTIL_56 | AV_FUNC_AVUTIL_57 |
- AV_FUNC_AVUTIL_58 | AV_FUNC_AVUTIL_59 | AV_FUNC_AVUTIL_60))
+ AV_FUNC_AVUTIL_58 | AV_FUNC_AVUTIL_59 | AV_FUNC_AVUTIL_60 |
+ AV_FUNC_AVUTIL_61))
AV_FUNC(av_frame_get_buffer,
(AV_FUNC_AVUTIL_55 | AV_FUNC_AVUTIL_56 | AV_FUNC_AVUTIL_57 |
- AV_FUNC_AVUTIL_58 | AV_FUNC_AVUTIL_59 | AV_FUNC_AVUTIL_60))
+ AV_FUNC_AVUTIL_58 | AV_FUNC_AVUTIL_59 | AV_FUNC_AVUTIL_60 |
+ AV_FUNC_AVUTIL_61))
AV_FUNC(av_frame_make_writable,
(AV_FUNC_AVUTIL_55 | AV_FUNC_AVUTIL_56 | AV_FUNC_AVUTIL_57 |
- AV_FUNC_AVUTIL_58 | AV_FUNC_AVUTIL_59 | AV_FUNC_AVUTIL_60))
+ AV_FUNC_AVUTIL_58 | AV_FUNC_AVUTIL_59 | AV_FUNC_AVUTIL_60 |
+ AV_FUNC_AVUTIL_61))
AV_FUNC(av_image_check_size, AV_FUNC_AVUTIL_ALL)
AV_FUNC(av_image_get_buffer_size, AV_FUNC_AVUTIL_ALL)
- AV_FUNC_OPTION(av_channel_layout_default, AV_FUNC_AVUTIL_60)
- AV_FUNC_OPTION(av_channel_layout_from_mask, AV_FUNC_AVUTIL_60)
- AV_FUNC_OPTION(av_channel_layout_copy, AV_FUNC_AVUTIL_60)
+ AV_FUNC_OPTION(av_channel_layout_default,
+ AV_FUNC_AVUTIL_60 | AV_FUNC_AVUTIL_61)
+ AV_FUNC_OPTION(av_channel_layout_from_mask,
+ AV_FUNC_AVUTIL_60 | AV_FUNC_AVUTIL_61)
+ AV_FUNC_OPTION(av_channel_layout_copy, AV_FUNC_AVUTIL_60 | AV_FUNC_AVUTIL_61)
AV_FUNC_OPTION(av_buffer_get_opaque,
(AV_FUNC_AVUTIL_56 | AV_FUNC_AVUTIL_57 | AV_FUNC_AVUTIL_58 |
- AV_FUNC_AVUTIL_59 | AV_FUNC_AVUTIL_60))
- AV_FUNC(av_buffer_create,
- (AV_FUNC_AVUTIL_55 | AV_FUNC_AVUTIL_56 | AV_FUNC_AVUTIL_57 |
- AV_FUNC_AVUTIL_58 | AV_FUNC_AVUTIL_59 | AV_FUNC_AVUTIL_60))
+ AV_FUNC_AVUTIL_59 | AV_FUNC_AVUTIL_60 | AV_FUNC_AVUTIL_61))
+ AV_FUNC(
+ av_buffer_create,
+ (AV_FUNC_AVUTIL_55 | AV_FUNC_AVUTIL_56 | AV_FUNC_AVUTIL_57 |
+ AV_FUNC_AVUTIL_58 | AV_FUNC_AVUTIL_59 | AV_FUNC_AVUTIL_60 | AV_FUNC_61))
AV_FUNC_OPTION(av_frame_get_colorspace,
AV_FUNC_AVUTIL_55 | AV_FUNC_AVUTIL_56 | AV_FUNC_AVUTIL_57 |
AV_FUNC_AVUTIL_58)
AV_FUNC_OPTION(av_frame_get_color_range,
AV_FUNC_AVUTIL_55 | AV_FUNC_AVUTIL_56 | AV_FUNC_AVUTIL_57 |
AV_FUNC_AVUTIL_58)
- AV_FUNC(av_strerror,
- AV_FUNC_AVUTIL_58 | AV_FUNC_AVUTIL_59 | AV_FUNC_AVUTIL_60)
+ AV_FUNC(av_strerror, AV_FUNC_AVUTIL_58 | AV_FUNC_AVUTIL_59 |
+ AV_FUNC_AVUTIL_60 | AV_FUNC_AVUTIL_61)
AV_FUNC(av_get_sample_fmt_name, AV_FUNC_AVUTIL_ALL)
AV_FUNC(av_dict_set, AV_FUNC_AVUTIL_ALL)
AV_FUNC(av_dict_free, AV_FUNC_AVUTIL_ALL)
@@ -224,35 +248,38 @@ FFmpegLibWrapper::LinkResult FFmpegLibWrapper::Link() {
#ifdef MOZ_WIDGET_GTK
AV_FUNC_OPTION_SILENT(avcodec_get_hw_config,
- AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60)
- AV_FUNC_OPTION_SILENT(av_codec_iterate, AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60)
+ AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60 | AV_FUNC_61)
+ AV_FUNC_OPTION_SILENT(av_codec_iterate,
+ AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60 | AV_FUNC_61)
AV_FUNC_OPTION_SILENT(av_codec_is_decoder,
- AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60)
+ AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60 | AV_FUNC_61)
AV_FUNC_OPTION_SILENT(av_hwdevice_ctx_init,
- AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60)
+ AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60 | AV_FUNC_61)
AV_FUNC_OPTION_SILENT(av_hwdevice_ctx_alloc,
- AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60)
+ AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60 | AV_FUNC_61)
AV_FUNC_OPTION_SILENT(av_hwdevice_hwconfig_alloc,
- AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60)
+ AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60 | AV_FUNC_61)
AV_FUNC_OPTION_SILENT(av_hwdevice_get_hwframe_constraints,
- AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60)
+ AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60 | AV_FUNC_61)
AV_FUNC_OPTION_SILENT(av_hwframe_constraints_free,
- AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60)
- AV_FUNC_OPTION_SILENT(av_buffer_ref,
- AV_FUNC_AVUTIL_58 | AV_FUNC_59 | AV_FUNC_60)
- AV_FUNC_OPTION_SILENT(av_buffer_unref,
- AV_FUNC_AVUTIL_58 | AV_FUNC_59 | AV_FUNC_60)
+ AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60 | AV_FUNC_61)
+ AV_FUNC_OPTION_SILENT(av_buffer_ref, AV_FUNC_AVUTIL_58 | AV_FUNC_AVUTIL_59 |
+ AV_FUNC_AVUTIL_60 |
+ AV_FUNC_AVUTIL_61)
+ AV_FUNC_OPTION_SILENT(av_buffer_unref, AV_FUNC_AVUTIL_58 | AV_FUNC_AVUTIL_59 |
+ AV_FUNC_AVUTIL_60 |
+ AV_FUNC_AVUTIL_61)
AV_FUNC_OPTION_SILENT(av_hwframe_transfer_get_formats,
- AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60)
+ AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60 | AV_FUNC_61)
AV_FUNC_OPTION_SILENT(av_hwdevice_ctx_create_derived,
- AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60)
+ AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60 | AV_FUNC_61)
AV_FUNC_OPTION_SILENT(av_hwframe_ctx_alloc,
- AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60)
- AV_FUNC_OPTION_SILENT(avcodec_get_name,
- AV_FUNC_57 | AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60)
- AV_FUNC_OPTION_SILENT(av_get_pix_fmt_string, AV_FUNC_AVUTIL_58 |
- AV_FUNC_AVUTIL_59 |
- AV_FUNC_AVUTIL_60)
+ AV_FUNC_58 | AV_FUNC_59 | AV_FUNC_60 | AV_FUNC_61)
+ AV_FUNC_OPTION_SILENT(avcodec_get_name, AV_FUNC_57 | AV_FUNC_58 | AV_FUNC_59 |
+ AV_FUNC_60 | AV_FUNC_61)
+ AV_FUNC_OPTION_SILENT(av_get_pix_fmt_string,
+ AV_FUNC_AVUTIL_58 | AV_FUNC_AVUTIL_59 |
+ AV_FUNC_AVUTIL_60 | AV_FUNC_AVUTIL_61)
#endif
AV_FUNC_OPTION(av_tx_init, AV_FUNC_AVUTIL_ALL)
diff --git a/dom/media/platforms/ffmpeg/FFmpegLibWrapper.h b/dom/media/platforms/ffmpeg/FFmpegLibWrapper.h
index 226b4fc8cb..d3b1be90f3 100644
--- a/dom/media/platforms/ffmpeg/FFmpegLibWrapper.h
+++ b/dom/media/platforms/ffmpeg/FFmpegLibWrapper.h
@@ -138,10 +138,12 @@ struct MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS FFmpegLibWrapper {
int flags);
// libavcodec >= v57
- AVPacket* (*av_packet_alloc)(void);
void (*av_packet_unref)(AVPacket* pkt);
void (*av_packet_free)(AVPacket** pkt);
+ // libavcodec >= 61
+ AVPacket* (*av_packet_alloc)();
+
// libavcodec v58 and later only
int (*avcodec_send_packet)(AVCodecContext* avctx, const AVPacket* avpkt);
int (*avcodec_receive_packet)(AVCodecContext* avctx, AVPacket* avpkt);
diff --git a/dom/media/platforms/ffmpeg/FFmpegRuntimeLinker.cpp b/dom/media/platforms/ffmpeg/FFmpegRuntimeLinker.cpp
index 2019a859e4..81eb2c0441 100644
--- a/dom/media/platforms/ffmpeg/FFmpegRuntimeLinker.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegRuntimeLinker.cpp
@@ -33,6 +33,7 @@ static FFmpegLibWrapper sLibAV;
static const char* sLibs[] = {
// clang-format off
#if defined(XP_DARWIN)
+ "libavcodec.61.dylib",
"libavcodec.60.dylib",
"libavcodec.59.dylib",
"libavcodec.58.dylib",
@@ -45,6 +46,7 @@ static const char* sLibs[] = {
"libavcodec.so", // OpenBSD hardly controls the major/minor library version
// of ffmpeg and update it regulary on ABI/API changes
#else
+ "libavcodec.so.61",
"libavcodec.so.60",
"libavcodec.so.59",
"libavcodec.so.58",
@@ -174,6 +176,9 @@ already_AddRefed<PlatformDecoderModule> FFmpegRuntimeLinker::CreateDecoder() {
case 60:
module = FFmpegDecoderModule<60>::Create(&sLibAV);
break;
+ case 61:
+ module = FFmpegDecoderModule<61>::Create(&sLibAV);
+ break;
default:
module = nullptr;
}
@@ -209,6 +214,9 @@ already_AddRefed<PlatformEncoderModule> FFmpegRuntimeLinker::CreateEncoder() {
case 60:
module = FFmpegEncoderModule<60>::Create(&sLibAV);
break;
+ case 61:
+ module = FFmpegEncoderModule<61>::Create(&sLibAV);
+ break;
default:
module = nullptr;
}
diff --git a/dom/media/platforms/ffmpeg/FFmpegUtils.h b/dom/media/platforms/ffmpeg/FFmpegUtils.h
index fe588ed14c..bdbb184cf2 100644
--- a/dom/media/platforms/ffmpeg/FFmpegUtils.h
+++ b/dom/media/platforms/ffmpeg/FFmpegUtils.h
@@ -51,6 +51,36 @@ inline bool IsVideoCodec(AVCodecID aCodecID) {
}
}
+// Access the correct location for the channel count, based on ffmpeg version.
+template <typename T>
+inline int& ChannelCount(T* aObject) {
+#if LIBAVCODEC_VERSION_MAJOR <= 59
+ return aObject->channels;
+#else
+ return aObject->ch_layout.nb_channels;
+#endif
+}
+
+// Access the correct location for the duration, based on ffmpeg version.
+template <typename T>
+inline int64_t& Duration(T* aObject) {
+#if LIBAVCODEC_VERSION_MAJOR < 61
+ return aObject->pkt_duration;
+#else
+ return aObject->duration;
+#endif
+}
+
+// Access the correct location for the duration, based on ffmpeg version.
+template <typename T>
+inline const int64_t& Duration(const T* aObject) {
+#if LIBAVCODEC_VERSION_MAJOR < 61
+ return aObject->pkt_duration;
+#else
+ return aObject->duration;
+#endif
+}
+
} // namespace mozilla
#endif // DOM_MEDIA_PLATFORMS_FFMPEG_FFMPEGUTILS_H_
diff --git a/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp b/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp
index 3fe46938fd..e116ca594f 100644
--- a/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp
@@ -7,6 +7,7 @@
#include "FFmpegVideoDecoder.h"
#include "FFmpegLog.h"
+#include "FFmpegUtils.h"
#include "ImageContainer.h"
#include "MP4Decoder.h"
#include "MediaInfo.h"
@@ -45,6 +46,7 @@
# define AV_PIX_FMT_YUV444P PIX_FMT_YUV444P
# define AV_PIX_FMT_YUV444P10LE PIX_FMT_YUV444P10LE
# define AV_PIX_FMT_GBRP PIX_FMT_GBRP
+# define AV_PIX_FMT_GBRP10LE PIX_FMT_GBRP10LE
# define AV_PIX_FMT_NONE PIX_FMT_NONE
# define AV_PIX_FMT_VAAPI_VLD PIX_FMT_VAAPI_VLD
#endif
@@ -136,6 +138,9 @@ static AVPixelFormat ChoosePixelFormat(AVCodecContext* aCodecContext,
case AV_PIX_FMT_GBRP:
FFMPEGV_LOG("Requesting pixel format GBRP.");
return AV_PIX_FMT_GBRP;
+ case AV_PIX_FMT_GBRP10LE:
+ FFMPEGV_LOG("Requesting pixel format GBRP10LE.");
+ return AV_PIX_FMT_GBRP10LE;
default:
break;
}
@@ -209,7 +214,7 @@ template <>
class VAAPIDisplayHolder<LIBAV_VER> {
public:
VAAPIDisplayHolder(FFmpegLibWrapper* aLib, VADisplay aDisplay, int aDRMFd)
- : mLib(aLib), mDisplay(aDisplay), mDRMFd(aDRMFd){};
+ : mLib(aLib), mDisplay(aDisplay), mDRMFd(aDRMFd) {};
~VAAPIDisplayHolder() {
mLib->vaTerminate(mDisplay);
close(mDRMFd);
@@ -612,6 +617,7 @@ static gfx::ColorDepth GetColorDepth(const AVPixelFormat& aFormat) {
case AV_PIX_FMT_YUV420P10LE:
case AV_PIX_FMT_YUV422P10LE:
case AV_PIX_FMT_YUV444P10LE:
+ case AV_PIX_FMT_GBRP10LE:
return gfx::ColorDepth::COLOR_10;
#if LIBAVCODEC_VERSION_MAJOR >= 57
case AV_PIX_FMT_YUV420P12LE:
@@ -629,7 +635,7 @@ static gfx::ColorDepth GetColorDepth(const AVPixelFormat& aFormat) {
}
static bool IsYUVFormat(const AVPixelFormat& aFormat) {
- return aFormat != AV_PIX_FMT_GBRP;
+ return aFormat != AV_PIX_FMT_GBRP && aFormat != AV_PIX_FMT_GBRP10LE;
}
static gfx::YUVColorSpace TransferAVColorSpaceToColorSpace(
@@ -871,7 +877,9 @@ int FFmpegVideoDecoder<LIBAV_VER>::GetVideoBuffer(
aFrame->height = aCodecContext->coded_height;
aFrame->format = aCodecContext->pix_fmt;
aFrame->extended_data = aFrame->data;
+# if LIBAVCODEC_VERSION_MAJOR < 61
aFrame->reordered_opaque = aCodecContext->reordered_opaque;
+# endif
MOZ_ASSERT(aFrame->data[0] && aFrame->data[1] && aFrame->data[2]);
// This will hold a reference to image, and the reference would be dropped
@@ -991,12 +999,7 @@ void FFmpegVideoDecoder<LIBAV_VER>::DecodeStats::UpdateDecodeTimes(
float decodeTime = (now - mDecodeStart).ToMilliseconds();
mDecodeStart = now;
- if (aFrame->pkt_duration <= 0) {
- FFMPEGV_LOG("Incorrect frame duration, skipping decode stats.");
- return;
- }
-
- float frameDuration = aFrame->pkt_duration / 1000.0f;
+ const float frameDuration = Duration(aFrame) / 1000.0f;
mDecodedFrames++;
mAverageFrameDuration =
@@ -1044,19 +1047,27 @@ MediaResult FFmpegVideoDecoder<LIBAV_VER>::DoDecode(
MediaRawData* aSample, uint8_t* aData, int aSize, bool* aGotFrame,
MediaDataDecoder::DecodedData& aResults) {
MOZ_ASSERT(mTaskQueue->IsOnCurrentThread());
- AVPacket packet;
- mLib->av_init_packet(&packet);
+ AVPacket* packet;
+
+#if LIBAVCODEC_VERSION_MAJOR >= 61
+ packet = mLib->av_packet_alloc();
+ auto raii = MakeScopeExit([&]() { mLib->av_packet_free(&packet); });
+#else
+ AVPacket packet_mem;
+ packet = &packet_mem;
+ mLib->av_init_packet(packet);
+#endif
#if LIBAVCODEC_VERSION_MAJOR >= 58
mDecodeStats.DecodeStart();
#endif
- packet.data = aData;
- packet.size = aSize;
- packet.dts = aSample->mTimecode.ToMicroseconds();
- packet.pts = aSample->mTime.ToMicroseconds();
- packet.flags = aSample->mKeyframe ? AV_PKT_FLAG_KEY : 0;
- packet.pos = aSample->mOffset;
+ packet->data = aData;
+ packet->size = aSize;
+ packet->dts = aSample->mTimecode.ToMicroseconds();
+ packet->pts = aSample->mTime.ToMicroseconds();
+ packet->flags = aSample->mKeyframe ? AV_PKT_FLAG_KEY : 0;
+ packet->pos = aSample->mOffset;
mTrackingId.apply([&](const auto& aId) {
MediaInfoFlag flag = MediaInfoFlag::None;
@@ -1087,14 +1098,14 @@ MediaResult FFmpegVideoDecoder<LIBAV_VER>::DoDecode(
break;
}
mPerformanceRecorder.Start(
- packet.dts,
+ packet->dts,
nsPrintfCString("FFmpegVideoDecoder(%d)", LIBAVCODEC_VERSION_MAJOR),
aId, flag);
});
#if LIBAVCODEC_VERSION_MAJOR >= 58
- packet.duration = aSample->mDuration.ToMicroseconds();
- int res = mLib->avcodec_send_packet(mCodecContext, &packet);
+ packet->duration = aSample->mDuration.ToMicroseconds();
+ int res = mLib->avcodec_send_packet(mCodecContext, packet);
if (res < 0) {
// In theory, avcodec_send_packet could sent -EAGAIN should its internal
// buffers be full. In practice this can't happen as we only feed one frame
@@ -1102,7 +1113,9 @@ MediaResult FFmpegVideoDecoder<LIBAV_VER>::DoDecode(
char errStr[AV_ERROR_MAX_STRING_SIZE];
mLib->av_strerror(res, errStr, AV_ERROR_MAX_STRING_SIZE);
FFMPEG_LOG("avcodec_send_packet error: %s", errStr);
- return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
+ return MediaResult(res == int(AVERROR_EOF)
+ ? NS_ERROR_DOM_MEDIA_END_OF_STREAM
+ : NS_ERROR_DOM_MEDIA_DECODE_ERR,
RESULT_DETAIL("avcodec_send_packet error: %s", errStr));
}
if (aGotFrame) {
@@ -1154,10 +1167,10 @@ MediaResult FFmpegVideoDecoder<LIBAV_VER>::DoDecode(
}
if (mUsingV4L2) {
rv = CreateImageV4L2(mFrame->pkt_pos, GetFramePts(mFrame),
- mFrame->pkt_duration, aResults);
+ Duration(mFrame), aResults);
} else {
rv = CreateImageVAAPI(mFrame->pkt_pos, GetFramePts(mFrame),
- mFrame->pkt_duration, aResults);
+ Duration(mFrame), aResults);
}
// If VA-API/V4L2 playback failed, just quit. Decoder is going to be
@@ -1171,8 +1184,8 @@ MediaResult FFmpegVideoDecoder<LIBAV_VER>::DoDecode(
} else
# endif
{
- rv = CreateImage(mFrame->pkt_pos, GetFramePts(mFrame),
- mFrame->pkt_duration, aResults);
+ rv = CreateImage(mFrame->pkt_pos, GetFramePts(mFrame), Duration(mFrame),
+ aResults);
}
if (NS_FAILED(rv)) {
return rv;
@@ -1202,6 +1215,7 @@ MediaResult FFmpegVideoDecoder<LIBAV_VER>::DoDecode(
# endif
return Some(DecodeStage::YUV444P);
case AV_PIX_FMT_GBRP:
+ case AV_PIX_FMT_GBRP10LE:
return Some(DecodeStage::GBRP);
case AV_PIX_FMT_VAAPI_VLD:
return Some(DecodeStage::VAAPI_SURFACE);
@@ -1213,6 +1227,8 @@ MediaResult FFmpegVideoDecoder<LIBAV_VER>::DoDecode(
aStage.SetColorDepth(GetColorDepth(mCodecContext->pix_fmt));
aStage.SetYUVColorSpace(GetFrameColorSpace());
aStage.SetColorRange(GetFrameColorRange());
+ aStage.SetStartTimeAndEndTime(aSample->mTime.ToMicroseconds(),
+ aSample->GetEndTime().ToMicroseconds());
});
if (aGotFrame) {
*aGotFrame = true;
@@ -1237,14 +1253,14 @@ MediaResult FFmpegVideoDecoder<LIBAV_VER>::DoDecode(
int decoded;
int bytesConsumed =
- mLib->avcodec_decode_video2(mCodecContext, mFrame, &decoded, &packet);
+ mLib->avcodec_decode_video2(mCodecContext, mFrame, &decoded, packet);
FFMPEG_LOG(
"DoDecodeFrame:decode_video: rv=%d decoded=%d "
"(Input: pts(%" PRId64 ") dts(%" PRId64 ") Output: pts(%" PRId64
") "
"opaque(%" PRId64 ") pts(%" PRId64 ") pkt_dts(%" PRId64 "))",
- bytesConsumed, decoded, packet.pts, packet.dts, mFrame->pts,
+ bytesConsumed, decoded, packet->pts, packet->dts, mFrame->pts,
mFrame->reordered_opaque, mFrame->pts, mFrame->pkt_dts);
if (bytesConsumed < 0) {
@@ -1306,6 +1322,7 @@ MediaResult FFmpegVideoDecoder<LIBAV_VER>::DoDecode(
# endif
return Some(DecodeStage::YUV444P);
case AV_PIX_FMT_GBRP:
+ case AV_PIX_FMT_GBRP10LE:
return Some(DecodeStage::GBRP);
default:
return Nothing();
@@ -1315,6 +1332,8 @@ MediaResult FFmpegVideoDecoder<LIBAV_VER>::DoDecode(
aStage.SetColorDepth(GetColorDepth(mCodecContext->pix_fmt));
aStage.SetYUVColorSpace(GetFrameColorSpace());
aStage.SetColorRange(GetFrameColorRange());
+ aStage.SetStartTimeAndEndTime(aSample->mTime.ToMicroseconds(),
+ aSample->GetEndTime().ToMicroseconds());
});
});
@@ -1372,8 +1391,8 @@ MediaResult FFmpegVideoDecoder<LIBAV_VER>::CreateImage(
int64_t aOffset, int64_t aPts, int64_t aDuration,
MediaDataDecoder::DecodedData& aResults) const {
FFMPEG_LOG("Got one frame output with pts=%" PRId64 " dts=%" PRId64
- " duration=%" PRId64 " opaque=%" PRId64,
- aPts, mFrame->pkt_dts, aDuration, mCodecContext->reordered_opaque);
+ " duration=%" PRId64,
+ aPts, mFrame->pkt_dts, aDuration);
VideoData::YCbCrBuffer b;
b.mPlanes[0].mData = mFrame->data[0];
@@ -1392,14 +1411,16 @@ MediaResult FFmpegVideoDecoder<LIBAV_VER>::CreateImage(
b.mPlanes[0].mHeight = mFrame->height;
if (mCodecContext->pix_fmt == AV_PIX_FMT_YUV444P ||
mCodecContext->pix_fmt == AV_PIX_FMT_YUV444P10LE ||
- mCodecContext->pix_fmt == AV_PIX_FMT_GBRP
+ mCodecContext->pix_fmt == AV_PIX_FMT_GBRP ||
+ mCodecContext->pix_fmt == AV_PIX_FMT_GBRP10LE
#if LIBAVCODEC_VERSION_MAJOR >= 57
|| mCodecContext->pix_fmt == AV_PIX_FMT_YUV444P12LE
#endif
) {
b.mPlanes[1].mWidth = b.mPlanes[2].mWidth = mFrame->width;
b.mPlanes[1].mHeight = b.mPlanes[2].mHeight = mFrame->height;
- if (mCodecContext->pix_fmt == AV_PIX_FMT_YUV444P10LE) {
+ if (mCodecContext->pix_fmt == AV_PIX_FMT_YUV444P10LE ||
+ mCodecContext->pix_fmt == AV_PIX_FMT_GBRP10LE) {
b.mColorDepth = gfx::ColorDepth::COLOR_10;
}
#if LIBAVCODEC_VERSION_MAJOR >= 57
@@ -1501,8 +1522,8 @@ MediaResult FFmpegVideoDecoder<LIBAV_VER>::CreateImageVAAPI(
int64_t aOffset, int64_t aPts, int64_t aDuration,
MediaDataDecoder::DecodedData& aResults) {
FFMPEG_LOG("VA-API Got one frame output with pts=%" PRId64 " dts=%" PRId64
- " duration=%" PRId64 " opaque=%" PRId64,
- aPts, mFrame->pkt_dts, aDuration, mCodecContext->reordered_opaque);
+ " duration=%" PRId64,
+ aPts, mFrame->pkt_dts, aDuration);
VADRMPRIMESurfaceDescriptor vaDesc;
if (!GetVAAPISurfaceDescriptor(&vaDesc)) {
@@ -1547,8 +1568,8 @@ MediaResult FFmpegVideoDecoder<LIBAV_VER>::CreateImageV4L2(
int64_t aOffset, int64_t aPts, int64_t aDuration,
MediaDataDecoder::DecodedData& aResults) {
FFMPEG_LOG("V4L2 Got one frame output with pts=%" PRId64 " dts=%" PRId64
- " duration=%" PRId64 " opaque=%" PRId64,
- aPts, mFrame->pkt_dts, aDuration, mCodecContext->reordered_opaque);
+ " duration=%" PRId64,
+ aPts, mFrame->pkt_dts, aDuration);
AVDRMFrameDescriptor* desc = (AVDRMFrameDescriptor*)mFrame->data[0];
if (!desc) {
@@ -1673,8 +1694,7 @@ static const struct {
VAProfile va_profile;
char name[100];
} vaapi_profile_map[] = {
-# define MAP(c, v, n) \
- { AV_CODEC_ID_##c, VAProfile##v, n }
+# define MAP(c, v, n) {AV_CODEC_ID_##c, VAProfile##v, n}
MAP(H264, H264ConstrainedBaseline, "H264ConstrainedBaseline"),
MAP(H264, H264Main, "H264Main"),
MAP(H264, H264High, "H264High"),
diff --git a/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.h b/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.h
index fda38069ba..2d6771a7b1 100644
--- a/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.h
+++ b/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.h
@@ -43,7 +43,7 @@ class FFmpegVideoDecoder<LIBAV_VER>
typedef mozilla::layers::Image Image;
typedef mozilla::layers::ImageContainer ImageContainer;
typedef mozilla::layers::KnowsCompositor KnowsCompositor;
- typedef SimpleMap<int64_t> DurationMap;
+ typedef SimpleMap<int64_t, int64_t, ThreadSafePolicy> DurationMap;
public:
FFmpegVideoDecoder(FFmpegLibWrapper* aLib, const VideoInfo& aConfig,
@@ -205,6 +205,7 @@ class FFmpegVideoDecoder<LIBAV_VER>
const bool mLowLatency;
const Maybe<TrackingId> mTrackingId;
PerformanceRecorderMulti<DecodeStage> mPerformanceRecorder;
+ PerformanceRecorderMulti<DecodeStage> mPerformanceRecorder2;
// True if we're allocating shmem for ffmpeg decode buffer.
Maybe<Atomic<bool>> mIsUsingShmemBufferForDecode;
diff --git a/dom/media/platforms/ffmpeg/FFmpegVideoEncoder.cpp b/dom/media/platforms/ffmpeg/FFmpegVideoEncoder.cpp
index 9d1dbcf80f..83b0f98c5b 100644
--- a/dom/media/platforms/ffmpeg/FFmpegVideoEncoder.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegVideoEncoder.cpp
@@ -15,7 +15,7 @@
#include "libavutil/pixfmt.h"
#include "mozilla/dom/ImageUtils.h"
#include "nsPrintfCString.h"
-#include "ImageToI420.h"
+#include "ImageConversion.h"
#include "libyuv.h"
#include "FFmpegRuntimeLinker.h"
@@ -510,7 +510,7 @@ Result<MediaDataEncoder::EncodedData, nsresult> FFmpegVideoEncoder<
// Save duration in the time_base unit.
mDurationMap.Insert(mFrame->pts, aSample->mDuration.ToMicroseconds());
# endif
- mFrame->pkt_duration = aSample->mDuration.ToMicroseconds();
+ Duration(mFrame) = aSample->mDuration.ToMicroseconds();
// Now send the AVFrame to ffmpeg for encoding, same code for audio and video.
return FFmpegDataEncoder<LIBAV_VER>::EncodeWithModernAPIs();
diff --git a/dom/media/platforms/ffmpeg/FFmpegVideoEncoder.h b/dom/media/platforms/ffmpeg/FFmpegVideoEncoder.h
index 0ee5f52aec..2c4b20c441 100644
--- a/dom/media/platforms/ffmpeg/FFmpegVideoEncoder.h
+++ b/dom/media/platforms/ffmpeg/FFmpegVideoEncoder.h
@@ -22,7 +22,7 @@ class FFmpegVideoEncoder : public MediaDataEncoder {};
template <>
class FFmpegVideoEncoder<LIBAV_VER> : public FFmpegDataEncoder<LIBAV_VER> {
- using DurationMap = SimpleMap<int64_t>;
+ using DurationMap = SimpleMap<int64_t, int64_t, ThreadSafePolicy>;
public:
FFmpegVideoEncoder(const FFmpegLibWrapper* aLib, AVCodecID aCodecID,
diff --git a/dom/media/platforms/ffmpeg/ffmpeg61/include/COPYING.LGPLv2.1 b/dom/media/platforms/ffmpeg/ffmpeg61/include/COPYING.LGPLv2.1
new file mode 100644
index 0000000000..00b4fedfe7
--- /dev/null
+++ b/dom/media/platforms/ffmpeg/ffmpeg61/include/COPYING.LGPLv2.1
@@ -0,0 +1,504 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/avcodec.h b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/avcodec.h
new file mode 100644
index 0000000000..5216bff1f8
--- /dev/null
+++ b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/avcodec.h
@@ -0,0 +1,3121 @@
+/*
+ * copyright (c) 2001 Fabrice Bellard
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVCODEC_AVCODEC_H
+#define AVCODEC_AVCODEC_H
+
+/**
+ * @file
+ * @ingroup libavc
+ * Libavcodec external API header
+ */
+
+#include "libavutil/samplefmt.h"
+#include "libavutil/attributes.h"
+#include "libavutil/avutil.h"
+#include "libavutil/buffer.h"
+#include "libavutil/channel_layout.h"
+#include "libavutil/dict.h"
+#include "libavutil/frame.h"
+#include "libavutil/log.h"
+#include "libavutil/pixfmt.h"
+#include "libavutil/rational.h"
+
+#include "codec.h"
+#include "codec_id.h"
+#include "defs.h"
+#include "packet.h"
+#include "version_major.h"
+#ifndef HAVE_AV_CONFIG_H
+/* When included as part of the ffmpeg build, only include the major version
+ * to avoid unnecessary rebuilds. When included externally, keep including
+ * the full version information. */
+# include "version.h"
+
+# include "codec_desc.h"
+# include "codec_par.h"
+#endif
+
+struct AVCodecParameters;
+
+/**
+ * @defgroup libavc libavcodec
+ * Encoding/Decoding Library
+ *
+ * @{
+ *
+ * @defgroup lavc_decoding Decoding
+ * @{
+ * @}
+ *
+ * @defgroup lavc_encoding Encoding
+ * @{
+ * @}
+ *
+ * @defgroup lavc_codec Codecs
+ * @{
+ * @defgroup lavc_codec_native Native Codecs
+ * @{
+ * @}
+ * @defgroup lavc_codec_wrappers External library wrappers
+ * @{
+ * @}
+ * @defgroup lavc_codec_hwaccel Hardware Accelerators bridge
+ * @{
+ * @}
+ * @}
+ * @defgroup lavc_internal Internal
+ * @{
+ * @}
+ * @}
+ */
+
+/**
+ * @ingroup libavc
+ * @defgroup lavc_encdec send/receive encoding and decoding API overview
+ * @{
+ *
+ * The avcodec_send_packet()/avcodec_receive_frame()/avcodec_send_frame()/
+ * avcodec_receive_packet() functions provide an encode/decode API, which
+ * decouples input and output.
+ *
+ * The API is very similar for encoding/decoding and audio/video, and works as
+ * follows:
+ * - Set up and open the AVCodecContext as usual.
+ * - Send valid input:
+ * - For decoding, call avcodec_send_packet() to give the decoder raw
+ * compressed data in an AVPacket.
+ * - For encoding, call avcodec_send_frame() to give the encoder an AVFrame
+ * containing uncompressed audio or video.
+ *
+ * In both cases, it is recommended that AVPackets and AVFrames are
+ * refcounted, or libavcodec might have to copy the input data. (libavformat
+ * always returns refcounted AVPackets, and av_frame_get_buffer() allocates
+ * refcounted AVFrames.)
+ * - Receive output in a loop. Periodically call one of the avcodec_receive_*()
+ * functions and process their output:
+ * - For decoding, call avcodec_receive_frame(). On success, it will return
+ * an AVFrame containing uncompressed audio or video data.
+ * - For encoding, call avcodec_receive_packet(). On success, it will return
+ * an AVPacket with a compressed frame.
+ *
+ * Repeat this call until it returns AVERROR(EAGAIN) or an error. The
+ * AVERROR(EAGAIN) return value means that new input data is required to
+ * return new output. In this case, continue with sending input. For each
+ * input frame/packet, the codec will typically return 1 output frame/packet,
+ * but it can also be 0 or more than 1.
+ *
+ * At the beginning of decoding or encoding, the codec might accept multiple
+ * input frames/packets without returning a frame, until its internal buffers
+ * are filled. This situation is handled transparently if you follow the steps
+ * outlined above.
+ *
+ * In theory, sending input can result in EAGAIN - this should happen only if
+ * not all output was received. You can use this to structure alternative decode
+ * or encode loops other than the one suggested above. For example, you could
+ * try sending new input on each iteration, and try to receive output if that
+ * returns EAGAIN.
+ *
+ * End of stream situations. These require "flushing" (aka draining) the codec,
+ * as the codec might buffer multiple frames or packets internally for
+ * performance or out of necessity (consider B-frames).
+ * This is handled as follows:
+ * - Instead of valid input, send NULL to the avcodec_send_packet() (decoding)
+ * or avcodec_send_frame() (encoding) functions. This will enter draining
+ * mode.
+ * - Call avcodec_receive_frame() (decoding) or avcodec_receive_packet()
+ * (encoding) in a loop until AVERROR_EOF is returned. The functions will
+ * not return AVERROR(EAGAIN), unless you forgot to enter draining mode.
+ * - Before decoding can be resumed again, the codec has to be reset with
+ * avcodec_flush_buffers().
+ *
+ * Using the API as outlined above is highly recommended. But it is also
+ * possible to call functions outside of this rigid schema. For example, you can
+ * call avcodec_send_packet() repeatedly without calling
+ * avcodec_receive_frame(). In this case, avcodec_send_packet() will succeed
+ * until the codec's internal buffer has been filled up (which is typically of
+ * size 1 per output frame, after initial input), and then reject input with
+ * AVERROR(EAGAIN). Once it starts rejecting input, you have no choice but to
+ * read at least some output.
+ *
+ * Not all codecs will follow a rigid and predictable dataflow; the only
+ * guarantee is that an AVERROR(EAGAIN) return value on a send/receive call on
+ * one end implies that a receive/send call on the other end will succeed, or
+ * at least will not fail with AVERROR(EAGAIN). In general, no codec will
+ * permit unlimited buffering of input or output.
+ *
+ * A codec is not allowed to return AVERROR(EAGAIN) for both sending and
+ * receiving. This would be an invalid state, which could put the codec user
+ * into an endless loop. The API has no concept of time either: it cannot happen
+ * that trying to do avcodec_send_packet() results in AVERROR(EAGAIN), but a
+ * repeated call 1 second later accepts the packet (with no other receive/flush
+ * API calls involved). The API is a strict state machine, and the passage of
+ * time is not supposed to influence it. Some timing-dependent behavior might
+ * still be deemed acceptable in certain cases. But it must never result in both
+ * send/receive returning EAGAIN at the same time at any point. It must also
+ * absolutely be avoided that the current state is "unstable" and can
+ * "flip-flop" between the send/receive APIs allowing progress. For example,
+ * it's not allowed that the codec randomly decides that it actually wants to
+ * consume a packet now instead of returning a frame, after it just returned
+ * AVERROR(EAGAIN) on an avcodec_send_packet() call.
+ * @}
+ */
+
+/**
+ * @defgroup lavc_core Core functions/structures.
+ * @ingroup libavc
+ *
+ * Basic definitions, functions for querying libavcodec capabilities,
+ * allocating core structures, etc.
+ * @{
+ */
+
+#if FF_API_BUFFER_MIN_SIZE
+/**
+ * @ingroup lavc_encoding
+ * minimum encoding buffer size
+ * Used to avoid some checks during header writing.
+ * @deprecated Unused: avcodec_receive_packet() does not work
+ * with preallocated packet buffers.
+ */
+# define AV_INPUT_BUFFER_MIN_SIZE 16384
+#endif
+
+/**
+ * @ingroup lavc_encoding
+ */
+typedef struct RcOverride {
+ int start_frame;
+ int end_frame;
+ int qscale; // If this is 0 then quality_factor will be used instead.
+ float quality_factor;
+} RcOverride;
+
+/* encoding support
+ These flags can be passed in AVCodecContext.flags before initialization.
+ Note: Not everything is supported yet.
+*/
+
+/**
+ * Allow decoders to produce frames with data planes that are not aligned
+ * to CPU requirements (e.g. due to cropping).
+ */
+#define AV_CODEC_FLAG_UNALIGNED (1 << 0)
+/**
+ * Use fixed qscale.
+ */
+#define AV_CODEC_FLAG_QSCALE (1 << 1)
+/**
+ * 4 MV per MB allowed / advanced prediction for H.263.
+ */
+#define AV_CODEC_FLAG_4MV (1 << 2)
+/**
+ * Output even those frames that might be corrupted.
+ */
+#define AV_CODEC_FLAG_OUTPUT_CORRUPT (1 << 3)
+/**
+ * Use qpel MC.
+ */
+#define AV_CODEC_FLAG_QPEL (1 << 4)
+#if FF_API_DROPCHANGED
+/**
+ * Don't output frames whose parameters differ from first
+ * decoded frame in stream.
+ *
+ * @deprecated callers should implement this functionality in their own code
+ */
+# define AV_CODEC_FLAG_DROPCHANGED (1 << 5)
+#endif
+/**
+ * Request the encoder to output reconstructed frames, i.e.\ frames that would
+ * be produced by decoding the encoded bistream. These frames may be retrieved
+ * by calling avcodec_receive_frame() immediately after a successful call to
+ * avcodec_receive_packet().
+ *
+ * Should only be used with encoders flagged with the
+ * @ref AV_CODEC_CAP_ENCODER_RECON_FRAME capability.
+ *
+ * @note
+ * Each reconstructed frame returned by the encoder corresponds to the last
+ * encoded packet, i.e. the frames are returned in coded order rather than
+ * presentation order.
+ *
+ * @note
+ * Frame parameters (like pixel format or dimensions) do not have to match the
+ * AVCodecContext values. Make sure to use the values from the returned frame.
+ */
+#define AV_CODEC_FLAG_RECON_FRAME (1 << 6)
+/**
+ * @par decoding
+ * Request the decoder to propagate each packet's AVPacket.opaque and
+ * AVPacket.opaque_ref to its corresponding output AVFrame.
+ *
+ * @par encoding:
+ * Request the encoder to propagate each frame's AVFrame.opaque and
+ * AVFrame.opaque_ref values to its corresponding output AVPacket.
+ *
+ * @par
+ * May only be set on encoders that have the
+ * @ref AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE capability flag.
+ *
+ * @note
+ * While in typical cases one input frame produces exactly one output packet
+ * (perhaps after a delay), in general the mapping of frames to packets is
+ * M-to-N, so
+ * - Any number of input frames may be associated with any given output packet.
+ * This includes zero - e.g. some encoders may output packets that carry only
+ * metadata about the whole stream.
+ * - A given input frame may be associated with any number of output packets.
+ * Again this includes zero - e.g. some encoders may drop frames under certain
+ * conditions.
+ * .
+ * This implies that when using this flag, the caller must NOT assume that
+ * - a given input frame's opaques will necessarily appear on some output
+ * packet;
+ * - every output packet will have some non-NULL opaque value.
+ * .
+ * When an output packet contains multiple frames, the opaque values will be
+ * taken from the first of those.
+ *
+ * @note
+ * The converse holds for decoders, with frames and packets switched.
+ */
+#define AV_CODEC_FLAG_COPY_OPAQUE (1 << 7)
+/**
+ * Signal to the encoder that the values of AVFrame.duration are valid and
+ * should be used (typically for transferring them to output packets).
+ *
+ * If this flag is not set, frame durations are ignored.
+ */
+#define AV_CODEC_FLAG_FRAME_DURATION (1 << 8)
+/**
+ * Use internal 2pass ratecontrol in first pass mode.
+ */
+#define AV_CODEC_FLAG_PASS1 (1 << 9)
+/**
+ * Use internal 2pass ratecontrol in second pass mode.
+ */
+#define AV_CODEC_FLAG_PASS2 (1 << 10)
+/**
+ * loop filter.
+ */
+#define AV_CODEC_FLAG_LOOP_FILTER (1 << 11)
+/**
+ * Only decode/encode grayscale.
+ */
+#define AV_CODEC_FLAG_GRAY (1 << 13)
+/**
+ * error[?] variables will be set during encoding.
+ */
+#define AV_CODEC_FLAG_PSNR (1 << 15)
+/**
+ * Use interlaced DCT.
+ */
+#define AV_CODEC_FLAG_INTERLACED_DCT (1 << 18)
+/**
+ * Force low delay.
+ */
+#define AV_CODEC_FLAG_LOW_DELAY (1 << 19)
+/**
+ * Place global headers in extradata instead of every keyframe.
+ */
+#define AV_CODEC_FLAG_GLOBAL_HEADER (1 << 22)
+/**
+ * Use only bitexact stuff (except (I)DCT).
+ */
+#define AV_CODEC_FLAG_BITEXACT (1 << 23)
+/* Fx : Flag for H.263+ extra options */
+/**
+ * H.263 advanced intra coding / MPEG-4 AC prediction
+ */
+#define AV_CODEC_FLAG_AC_PRED (1 << 24)
+/**
+ * interlaced motion estimation
+ */
+#define AV_CODEC_FLAG_INTERLACED_ME (1 << 29)
+#define AV_CODEC_FLAG_CLOSED_GOP (1U << 31)
+
+/**
+ * Allow non spec compliant speedup tricks.
+ */
+#define AV_CODEC_FLAG2_FAST (1 << 0)
+/**
+ * Skip bitstream encoding.
+ */
+#define AV_CODEC_FLAG2_NO_OUTPUT (1 << 2)
+/**
+ * Place global headers at every keyframe instead of in extradata.
+ */
+#define AV_CODEC_FLAG2_LOCAL_HEADER (1 << 3)
+
+/**
+ * Input bitstream might be truncated at a packet boundaries
+ * instead of only at frame boundaries.
+ */
+#define AV_CODEC_FLAG2_CHUNKS (1 << 15)
+/**
+ * Discard cropping information from SPS.
+ */
+#define AV_CODEC_FLAG2_IGNORE_CROP (1 << 16)
+
+/**
+ * Show all frames before the first keyframe
+ */
+#define AV_CODEC_FLAG2_SHOW_ALL (1 << 22)
+/**
+ * Export motion vectors through frame side data
+ */
+#define AV_CODEC_FLAG2_EXPORT_MVS (1 << 28)
+/**
+ * Do not skip samples and export skip information as frame side data
+ */
+#define AV_CODEC_FLAG2_SKIP_MANUAL (1 << 29)
+/**
+ * Do not reset ASS ReadOrder field on flush (subtitles decoding)
+ */
+#define AV_CODEC_FLAG2_RO_FLUSH_NOOP (1 << 30)
+/**
+ * Generate/parse ICC profiles on encode/decode, as appropriate for the type of
+ * file. No effect on codecs which cannot contain embedded ICC profiles, or
+ * when compiled without support for lcms2.
+ */
+#define AV_CODEC_FLAG2_ICC_PROFILES (1U << 31)
+
+/* Exported side data.
+ These flags can be passed in AVCodecContext.export_side_data before
+ initialization.
+*/
+/**
+ * Export motion vectors through frame side data
+ */
+#define AV_CODEC_EXPORT_DATA_MVS (1 << 0)
+/**
+ * Export encoder Producer Reference Time through packet side data
+ */
+#define AV_CODEC_EXPORT_DATA_PRFT (1 << 1)
+/**
+ * Decoding only.
+ * Export the AVVideoEncParams structure through frame side data.
+ */
+#define AV_CODEC_EXPORT_DATA_VIDEO_ENC_PARAMS (1 << 2)
+/**
+ * Decoding only.
+ * Do not apply film grain, export it instead.
+ */
+#define AV_CODEC_EXPORT_DATA_FILM_GRAIN (1 << 3)
+
+/**
+ * The decoder will keep a reference to the frame and may reuse it later.
+ */
+#define AV_GET_BUFFER_FLAG_REF (1 << 0)
+
+/**
+ * The encoder will keep a reference to the packet and may reuse it later.
+ */
+#define AV_GET_ENCODE_BUFFER_FLAG_REF (1 << 0)
+
+/**
+ * main external API structure.
+ * New fields can be added to the end with minor version bumps.
+ * Removal, reordering and changes to existing fields require a major
+ * version bump.
+ * You can use AVOptions (av_opt* / av_set/get*()) to access these fields from
+ * user applications. The name string for AVOptions options matches the
+ * associated command line parameter name and can be found in
+ * libavcodec/options_table.h The AVOption/command line parameter names differ
+ * in some cases from the C structure field names for historic reasons or
+ * brevity. sizeof(AVCodecContext) must not be used outside libav*.
+ */
+typedef struct AVCodecContext {
+ /**
+ * information on struct for av_log
+ * - set by avcodec_alloc_context3
+ */
+ const AVClass* av_class;
+ int log_level_offset;
+
+ enum AVMediaType codec_type; /* see AVMEDIA_TYPE_xxx */
+ const struct AVCodec* codec;
+ enum AVCodecID codec_id; /* see AV_CODEC_ID_xxx */
+
+ /**
+ * fourcc (LSB first, so "ABCD" -> ('D'<<24) + ('C'<<16) + ('B'<<8) + 'A').
+ * This is used to work around some encoder bugs.
+ * A demuxer should set this to what is stored in the field used to identify
+ * the codec. If there are multiple such fields in a container then the
+ * demuxer should choose the one which maximizes the information about the
+ * used codec. If the codec tag field in a container is larger than 32 bits
+ * then the demuxer should remap the longer ID to 32 bits with a table or
+ * other structure. Alternatively a new extra_codec_tag + size could be added
+ * but for this a clear advantage must be demonstrated first.
+ * - encoding: Set by user, if not then the default based on codec_id will be
+ * used.
+ * - decoding: Set by user, will be converted to uppercase by libavcodec
+ * during init.
+ */
+ unsigned int codec_tag;
+
+ void* priv_data;
+
+ /**
+ * Private context used for internal data.
+ *
+ * Unlike priv_data, this is not codec-specific. It is used in general
+ * libavcodec functions.
+ */
+ struct AVCodecInternal* internal;
+
+ /**
+ * Private data of the user, can be used to carry app specific stuff.
+ * - encoding: Set by user.
+ * - decoding: Set by user.
+ */
+ void* opaque;
+
+ /**
+ * the average bitrate
+ * - encoding: Set by user; unused for constant quantizer encoding.
+ * - decoding: Set by user, may be overwritten by libavcodec
+ * if this info is available in the stream
+ */
+ int64_t bit_rate;
+
+ /**
+ * AV_CODEC_FLAG_*.
+ * - encoding: Set by user.
+ * - decoding: Set by user.
+ */
+ int flags;
+
+ /**
+ * AV_CODEC_FLAG2_*
+ * - encoding: Set by user.
+ * - decoding: Set by user.
+ */
+ int flags2;
+
+ /**
+ * some codecs need / can use extradata like Huffman tables.
+ * MJPEG: Huffman tables
+ * rv10: additional flags
+ * MPEG-4: global headers (they can be in the bitstream or here)
+ * The allocated memory should be AV_INPUT_BUFFER_PADDING_SIZE bytes larger
+ * than extradata_size to avoid problems if it is read with the bitstream
+ * reader. The bytewise contents of extradata must not depend on the
+ * architecture or CPU endianness. Must be allocated with the av_malloc()
+ * family of functions.
+ * - encoding: Set/allocated/freed by libavcodec.
+ * - decoding: Set/allocated/freed by user.
+ */
+ uint8_t* extradata;
+ int extradata_size;
+
+ /**
+ * This is the fundamental unit of time (in seconds) in terms
+ * of which frame timestamps are represented. For fixed-fps content,
+ * timebase should be 1/framerate and timestamp increments should be
+ * identically 1.
+ * This often, but not always is the inverse of the frame rate or field rate
+ * for video. 1/time_base is not the average frame rate if the frame rate is
+ * not constant.
+ *
+ * Like containers, elementary streams also can store timestamps, 1/time_base
+ * is the unit in which these timestamps are specified.
+ * As example of such codec time base see ISO/IEC 14496-2:2001(E)
+ * vop_time_increment_resolution and fixed_vop_rate
+ * (fixed_vop_rate == 0 implies that it is different from the framerate)
+ *
+ * - encoding: MUST be set by user.
+ * - decoding: unused.
+ */
+ AVRational time_base;
+
+ /**
+ * Timebase in which pkt_dts/pts and AVPacket.dts/pts are expressed.
+ * - encoding: unused.
+ * - decoding: set by user.
+ */
+ AVRational pkt_timebase;
+
+ /**
+ * - decoding: For codecs that store a framerate value in the compressed
+ * bitstream, the decoder may export it here. { 0, 1} when
+ * unknown.
+ * - encoding: May be used to signal the framerate of CFR content to an
+ * encoder.
+ */
+ AVRational framerate;
+
+#if FF_API_TICKS_PER_FRAME
+ /**
+ * For some codecs, the time base is closer to the field rate than the frame
+ * rate. Most notably, H.264 and MPEG-2 specify time_base as half of frame
+ * duration if no telecine is used ...
+ *
+ * Set to time_base ticks per frame. Default 1, e.g., H.264/MPEG-2 set it
+ * to 2.
+ *
+ * @deprecated
+ * - decoding: Use AVCodecDescriptor.props & AV_CODEC_PROP_FIELDS
+ * - encoding: Set AVCodecContext.framerate instead
+ *
+ */
+ attribute_deprecated int ticks_per_frame;
+#endif
+
+ /**
+ * Codec delay.
+ *
+ * Encoding: Number of frames delay there will be from the encoder input to
+ * the decoder output. (we assume the decoder matches the spec)
+ * Decoding: Number of frames delay in addition to what a standard decoder
+ * as specified in the spec would produce.
+ *
+ * Video:
+ * Number of frames the decoded output will be delayed relative to the
+ * encoded input.
+ *
+ * Audio:
+ * For encoding, this field is unused (see initial_padding).
+ *
+ * For decoding, this is the number of samples the decoder needs to
+ * output before the decoder's output is valid. When seeking, you should
+ * start decoding this many samples prior to your desired seek point.
+ *
+ * - encoding: Set by libavcodec.
+ * - decoding: Set by libavcodec.
+ */
+ int delay;
+
+ /* video only */
+ /**
+ * picture width / height.
+ *
+ * @note Those fields may not match the values of the last
+ * AVFrame output by avcodec_receive_frame() due frame
+ * reordering.
+ *
+ * - encoding: MUST be set by user.
+ * - decoding: May be set by the user before opening the decoder if known e.g.
+ * from the container. Some decoders will require the dimensions
+ * to be set by the caller. During decoding, the decoder may
+ * overwrite those values as required while parsing the data.
+ */
+ int width, height;
+
+ /**
+ * Bitstream width / height, may be different from width/height e.g. when
+ * the decoded frame is cropped before being output or lowres is enabled.
+ *
+ * @note Those field may not match the value of the last
+ * AVFrame output by avcodec_receive_frame() due frame
+ * reordering.
+ *
+ * - encoding: unused
+ * - decoding: May be set by the user before opening the decoder if known
+ * e.g. from the container. During decoding, the decoder may
+ * overwrite those values as required while parsing the data.
+ */
+ int coded_width, coded_height;
+
+ /**
+ * sample aspect ratio (0 if unknown)
+ * That is the width of a pixel divided by the height of the pixel.
+ * Numerator and denominator must be relatively prime and smaller than 256 for
+ * some video standards.
+ * - encoding: Set by user.
+ * - decoding: Set by libavcodec.
+ */
+ AVRational sample_aspect_ratio;
+
+ /**
+ * Pixel format, see AV_PIX_FMT_xxx.
+ * May be set by the demuxer if known from headers.
+ * May be overridden by the decoder if it knows better.
+ *
+ * @note This field may not match the value of the last
+ * AVFrame output by avcodec_receive_frame() due frame
+ * reordering.
+ *
+ * - encoding: Set by user.
+ * - decoding: Set by user if known, overridden by libavcodec while
+ * parsing the data.
+ */
+ enum AVPixelFormat pix_fmt;
+
+ /**
+ * Nominal unaccelerated pixel format, see AV_PIX_FMT_xxx.
+ * - encoding: unused.
+ * - decoding: Set by libavcodec before calling get_format()
+ */
+ enum AVPixelFormat sw_pix_fmt;
+
+ /**
+ * Chromaticity coordinates of the source primaries.
+ * - encoding: Set by user
+ * - decoding: Set by libavcodec
+ */
+ enum AVColorPrimaries color_primaries;
+
+ /**
+ * Color Transfer Characteristic.
+ * - encoding: Set by user
+ * - decoding: Set by libavcodec
+ */
+ enum AVColorTransferCharacteristic color_trc;
+
+ /**
+ * YUV colorspace type.
+ * - encoding: Set by user
+ * - decoding: Set by libavcodec
+ */
+ enum AVColorSpace colorspace;
+
+ /**
+ * MPEG vs JPEG YUV range.
+ * - encoding: Set by user to override the default output color range value,
+ * If not specified, libavcodec sets the color range depending on the
+ * output format.
+ * - decoding: Set by libavcodec, can be set by the user to propagate the
+ * color range to components reading from the decoder context.
+ */
+ enum AVColorRange color_range;
+
+ /**
+ * This defines the location of chroma samples.
+ * - encoding: Set by user
+ * - decoding: Set by libavcodec
+ */
+ enum AVChromaLocation chroma_sample_location;
+
+ /** Field order
+ * - encoding: set by libavcodec
+ * - decoding: Set by user.
+ */
+ enum AVFieldOrder field_order;
+
+ /**
+ * number of reference frames
+ * - encoding: Set by user.
+ * - decoding: Set by lavc.
+ */
+ int refs;
+
+ /**
+ * Size of the frame reordering buffer in the decoder.
+ * For MPEG-2 it is 1 IPB or 0 low delay IP.
+ * - encoding: Set by libavcodec.
+ * - decoding: Set by libavcodec.
+ */
+ int has_b_frames;
+
+ /**
+ * slice flags
+ * - encoding: unused
+ * - decoding: Set by user.
+ */
+ int slice_flags;
+#define SLICE_FLAG_CODED_ORDER \
+ 0x0001 ///< draw_horiz_band() is called in coded order instead of display
+#define SLICE_FLAG_ALLOW_FIELD \
+ 0x0002 ///< allow draw_horiz_band() with field slices (MPEG-2 field pics)
+#define SLICE_FLAG_ALLOW_PLANE \
+ 0x0004 ///< allow draw_horiz_band() with 1 component at a time (SVQ1)
+
+ /**
+ * If non NULL, 'draw_horiz_band' is called by the libavcodec
+ * decoder to draw a horizontal band. It improves cache usage. Not
+ * all codecs can do that. You must check the codec capabilities
+ * beforehand.
+ * When multithreading is used, it may be called from multiple threads
+ * at the same time; threads might draw different parts of the same AVFrame,
+ * or multiple AVFrames, and there is no guarantee that slices will be drawn
+ * in order.
+ * The function is also used by hardware acceleration APIs.
+ * It is called at least once during frame decoding to pass
+ * the data needed for hardware render.
+ * In that mode instead of pixel data, AVFrame points to
+ * a structure specific to the acceleration API. The application
+ * reads the structure and can change some fields to indicate progress
+ * or mark state.
+ * - encoding: unused
+ * - decoding: Set by user.
+ * @param height the height of the slice
+ * @param y the y position of the slice
+ * @param type 1->top field, 2->bottom field, 3->frame
+ * @param offset offset into the AVFrame.data from which the slice should be
+ * read
+ */
+ void (*draw_horiz_band)(struct AVCodecContext* s, const AVFrame* src,
+ int offset[AV_NUM_DATA_POINTERS], int y, int type,
+ int height);
+
+ /**
+ * Callback to negotiate the pixel format. Decoding only, may be set by the
+ * caller before avcodec_open2().
+ *
+ * Called by some decoders to select the pixel format that will be used for
+ * the output frames. This is mainly used to set up hardware acceleration,
+ * then the provided format list contains the corresponding hwaccel pixel
+ * formats alongside the "software" one. The software pixel format may also
+ * be retrieved from \ref sw_pix_fmt.
+ *
+ * This callback will be called when the coded frame properties (such as
+ * resolution, pixel format, etc.) change and more than one output format is
+ * supported for those new properties. If a hardware pixel format is chosen
+ * and initialization for it fails, the callback may be called again
+ * immediately.
+ *
+ * This callback may be called from different threads if the decoder is
+ * multi-threaded, but not from more than one thread simultaneously.
+ *
+ * @param fmt list of formats which may be used in the current
+ * configuration, terminated by AV_PIX_FMT_NONE.
+ * @warning Behavior is undefined if the callback returns a value other
+ * than one of the formats in fmt or AV_PIX_FMT_NONE.
+ * @return the chosen format or AV_PIX_FMT_NONE
+ */
+ enum AVPixelFormat (*get_format)(struct AVCodecContext* s,
+ const enum AVPixelFormat* fmt);
+
+ /**
+ * maximum number of B-frames between non-B-frames
+ * Note: The output will be delayed by max_b_frames+1 relative to the input.
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ int max_b_frames;
+
+ /**
+ * qscale factor between IP and B-frames
+ * If > 0 then the last P-frame quantizer will be used (q=
+ * lastp_q*factor+offset). If < 0 then normal ratecontrol will be done (q=
+ * -normal_q*factor+offset).
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ float b_quant_factor;
+
+ /**
+ * qscale offset between IP and B-frames
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ float b_quant_offset;
+
+ /**
+ * qscale factor between P- and I-frames
+ * If > 0 then the last P-frame quantizer will be used (q = lastp_q * factor +
+ * offset). If < 0 then normal ratecontrol will be done (q=
+ * -normal_q*factor+offset).
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ float i_quant_factor;
+
+ /**
+ * qscale offset between P and I-frames
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ float i_quant_offset;
+
+ /**
+ * luminance masking (0-> disabled)
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ float lumi_masking;
+
+ /**
+ * temporary complexity masking (0-> disabled)
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ float temporal_cplx_masking;
+
+ /**
+ * spatial complexity masking (0-> disabled)
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ float spatial_cplx_masking;
+
+ /**
+ * p block masking (0-> disabled)
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ float p_masking;
+
+ /**
+ * darkness masking (0-> disabled)
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ float dark_masking;
+
+ /**
+ * noise vs. sse weight for the nsse comparison function
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ int nsse_weight;
+
+ /**
+ * motion estimation comparison function
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ int me_cmp;
+ /**
+ * subpixel motion estimation comparison function
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ int me_sub_cmp;
+ /**
+ * macroblock comparison function (not supported yet)
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ int mb_cmp;
+ /**
+ * interlaced DCT comparison function
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ int ildct_cmp;
+#define FF_CMP_SAD 0
+#define FF_CMP_SSE 1
+#define FF_CMP_SATD 2
+#define FF_CMP_DCT 3
+#define FF_CMP_PSNR 4
+#define FF_CMP_BIT 5
+#define FF_CMP_RD 6
+#define FF_CMP_ZERO 7
+#define FF_CMP_VSAD 8
+#define FF_CMP_VSSE 9
+#define FF_CMP_NSSE 10
+#define FF_CMP_W53 11
+#define FF_CMP_W97 12
+#define FF_CMP_DCTMAX 13
+#define FF_CMP_DCT264 14
+#define FF_CMP_MEDIAN_SAD 15
+#define FF_CMP_CHROMA 256
+
+ /**
+ * ME diamond size & shape
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ int dia_size;
+
+ /**
+ * amount of previous MV predictors (2a+1 x 2a+1 square)
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ int last_predictor_count;
+
+ /**
+ * motion estimation prepass comparison function
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ int me_pre_cmp;
+
+ /**
+ * ME prepass diamond size & shape
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ int pre_dia_size;
+
+ /**
+ * subpel ME quality
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ int me_subpel_quality;
+
+ /**
+ * maximum motion estimation search range in subpel units
+ * If 0 then no limit.
+ *
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ int me_range;
+
+ /**
+ * macroblock decision mode
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ int mb_decision;
+#define FF_MB_DECISION_SIMPLE 0 ///< uses mb_cmp
+#define FF_MB_DECISION_BITS 1 ///< chooses the one which needs the fewest bits
+#define FF_MB_DECISION_RD 2 ///< rate distortion
+
+ /**
+ * custom intra quantization matrix
+ * Must be allocated with the av_malloc() family of functions, and will be
+ * freed in avcodec_free_context().
+ * - encoding: Set/allocated by user, freed by libavcodec. Can be NULL.
+ * - decoding: Set/allocated/freed by libavcodec.
+ */
+ uint16_t* intra_matrix;
+
+ /**
+ * custom inter quantization matrix
+ * Must be allocated with the av_malloc() family of functions, and will be
+ * freed in avcodec_free_context().
+ * - encoding: Set/allocated by user, freed by libavcodec. Can be NULL.
+ * - decoding: Set/allocated/freed by libavcodec.
+ */
+ uint16_t* inter_matrix;
+
+ /**
+ * custom intra quantization matrix
+ * - encoding: Set by user, can be NULL.
+ * - decoding: unused.
+ */
+ uint16_t* chroma_intra_matrix;
+
+ /**
+ * precision of the intra DC coefficient - 8
+ * - encoding: Set by user.
+ * - decoding: Set by libavcodec
+ */
+ int intra_dc_precision;
+
+ /**
+ * minimum MB Lagrange multiplier
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ int mb_lmin;
+
+ /**
+ * maximum MB Lagrange multiplier
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ int mb_lmax;
+
+ /**
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ int bidir_refine;
+
+ /**
+ * minimum GOP size
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ int keyint_min;
+
+ /**
+ * the number of pictures in a group of pictures, or 0 for intra_only
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ int gop_size;
+
+ /**
+ * Note: Value depends upon the compare function used for fullpel ME.
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ int mv0_threshold;
+
+ /**
+ * Number of slices.
+ * Indicates number of picture subdivisions. Used for parallelized
+ * decoding.
+ * - encoding: Set by user
+ * - decoding: unused
+ */
+ int slices;
+
+ /* audio only */
+ int sample_rate; ///< samples per second
+
+ /**
+ * audio sample format
+ * - encoding: Set by user.
+ * - decoding: Set by libavcodec.
+ */
+ enum AVSampleFormat sample_fmt; ///< sample format
+
+ /**
+ * Audio channel layout.
+ * - encoding: must be set by the caller, to one of AVCodec.ch_layouts.
+ * - decoding: may be set by the caller if known e.g. from the container.
+ * The decoder can then override during decoding as needed.
+ */
+ AVChannelLayout ch_layout;
+
+ /* The following data should not be initialized. */
+ /**
+ * Number of samples per channel in an audio frame.
+ *
+ * - encoding: set by libavcodec in avcodec_open2(). Each submitted frame
+ * except the last must contain exactly frame_size samples per channel.
+ * May be 0 when the codec has AV_CODEC_CAP_VARIABLE_FRAME_SIZE set, then
+ * the frame size is not restricted.
+ * - decoding: may be set by some decoders to indicate constant frame size
+ */
+ int frame_size;
+
+ /**
+ * number of bytes per packet if constant and known or 0
+ * Used by some WAV based audio codecs.
+ */
+ int block_align;
+
+ /**
+ * Audio cutoff bandwidth (0 means "automatic")
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ int cutoff;
+
+ /**
+ * Type of service that the audio stream conveys.
+ * - encoding: Set by user.
+ * - decoding: Set by libavcodec.
+ */
+ enum AVAudioServiceType audio_service_type;
+
+ /**
+ * desired sample format
+ * - encoding: Not used.
+ * - decoding: Set by user.
+ * Decoder will decode to this format if it can.
+ */
+ enum AVSampleFormat request_sample_fmt;
+
+ /**
+ * Audio only. The number of "priming" samples (padding) inserted by the
+ * encoder at the beginning of the audio. I.e. this number of leading
+ * decoded samples must be discarded by the caller to get the original audio
+ * without leading padding.
+ *
+ * - decoding: unused
+ * - encoding: Set by libavcodec. The timestamps on the output packets are
+ * adjusted by the encoder so that they always refer to the
+ * first sample of the data actually contained in the packet,
+ * including any added padding. E.g. if the timebase is
+ * 1/samplerate and the timestamp of the first input sample is
+ * 0, the timestamp of the first output packet will be
+ * -initial_padding.
+ */
+ int initial_padding;
+
+ /**
+ * Audio only. The amount of padding (in samples) appended by the encoder to
+ * the end of the audio. I.e. this number of decoded samples must be
+ * discarded by the caller from the end of the stream to get the original
+ * audio without any trailing padding.
+ *
+ * - decoding: unused
+ * - encoding: unused
+ */
+ int trailing_padding;
+
+ /**
+ * Number of samples to skip after a discontinuity
+ * - decoding: unused
+ * - encoding: set by libavcodec
+ */
+ int seek_preroll;
+
+ /**
+ * This callback is called at the beginning of each frame to get data
+ * buffer(s) for it. There may be one contiguous buffer for all the data or
+ * there may be a buffer per each data plane or anything in between. What
+ * this means is, you may set however many entries in buf[] you feel
+ * necessary. Each buffer must be reference-counted using the AVBuffer API
+ * (see description of buf[] below).
+ *
+ * The following fields will be set in the frame before this callback is
+ * called:
+ * - format
+ * - width, height (video only)
+ * - sample_rate, channel_layout, nb_samples (audio only)
+ * Their values may differ from the corresponding values in
+ * AVCodecContext. This callback must use the frame values, not the codec
+ * context values, to calculate the required buffer size.
+ *
+ * This callback must fill the following fields in the frame:
+ * - data[]
+ * - linesize[]
+ * - extended_data:
+ * * if the data is planar audio with more than 8 channels, then this
+ * callback must allocate and fill extended_data to contain all pointers
+ * to all data planes. data[] must hold as many pointers as it can.
+ * extended_data must be allocated with av_malloc() and will be freed in
+ * av_frame_unref().
+ * * otherwise extended_data must point to data
+ * - buf[] must contain one or more pointers to AVBufferRef structures. Each
+ * of the frame's data and extended_data pointers must be contained in these.
+ * That is, one AVBufferRef for each allocated chunk of memory, not
+ * necessarily one AVBufferRef per data[] entry. See: av_buffer_create(),
+ * av_buffer_alloc(), and av_buffer_ref().
+ * - extended_buf and nb_extended_buf must be allocated with av_malloc() by
+ * this callback and filled with the extra buffers if there are more
+ * buffers than buf[] can hold. extended_buf will be freed in
+ * av_frame_unref().
+ *
+ * If AV_CODEC_CAP_DR1 is not set then get_buffer2() must call
+ * avcodec_default_get_buffer2() instead of providing buffers allocated by
+ * some other means.
+ *
+ * Each data plane must be aligned to the maximum required by the target
+ * CPU.
+ *
+ * @see avcodec_default_get_buffer2()
+ *
+ * Video:
+ *
+ * If AV_GET_BUFFER_FLAG_REF is set in flags then the frame may be reused
+ * (read and/or written to if it is writable) later by libavcodec.
+ *
+ * avcodec_align_dimensions2() should be used to find the required width and
+ * height, as they normally need to be rounded up to the next multiple of 16.
+ *
+ * Some decoders do not support linesizes changing between frames.
+ *
+ * If frame multithreading is used, this callback may be called from a
+ * different thread, but not from more than one at once. Does not need to be
+ * reentrant.
+ *
+ * @see avcodec_align_dimensions2()
+ *
+ * Audio:
+ *
+ * Decoders request a buffer of a particular size by setting
+ * AVFrame.nb_samples prior to calling get_buffer2(). The decoder may,
+ * however, utilize only part of the buffer by setting AVFrame.nb_samples
+ * to a smaller value in the output frame.
+ *
+ * As a convenience, av_samples_get_buffer_size() and
+ * av_samples_fill_arrays() in libavutil may be used by custom get_buffer2()
+ * functions to find the required data size and to fill data pointers and
+ * linesize. In AVFrame.linesize, only linesize[0] may be set for audio
+ * since all planes must be the same size.
+ *
+ * @see av_samples_get_buffer_size(), av_samples_fill_arrays()
+ *
+ * - encoding: unused
+ * - decoding: Set by libavcodec, user can override.
+ */
+ int (*get_buffer2)(struct AVCodecContext* s, AVFrame* frame, int flags);
+
+ /* - encoding parameters */
+ /**
+ * number of bits the bitstream is allowed to diverge from the reference.
+ * the reference can be CBR (for CBR pass1) or VBR (for pass2)
+ * - encoding: Set by user; unused for constant quantizer encoding.
+ * - decoding: unused
+ */
+ int bit_rate_tolerance;
+
+ /**
+ * Global quality for codecs which cannot change it per frame.
+ * This should be proportional to MPEG-1/2/4 qscale.
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ int global_quality;
+
+ /**
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ int compression_level;
+#define FF_COMPRESSION_DEFAULT -1
+
+ float qcompress; ///< amount of qscale change between easy & hard scenes
+ ///< (0.0-1.0)
+ float qblur; ///< amount of qscale smoothing over time (0.0-1.0)
+
+ /**
+ * minimum quantizer
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ int qmin;
+
+ /**
+ * maximum quantizer
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ int qmax;
+
+ /**
+ * maximum quantizer difference between frames
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ int max_qdiff;
+
+ /**
+ * decoder bitstream buffer size
+ * - encoding: Set by user.
+ * - decoding: May be set by libavcodec.
+ */
+ int rc_buffer_size;
+
+ /**
+ * ratecontrol override, see RcOverride
+ * - encoding: Allocated/set/freed by user.
+ * - decoding: unused
+ */
+ int rc_override_count;
+ RcOverride* rc_override;
+
+ /**
+ * maximum bitrate
+ * - encoding: Set by user.
+ * - decoding: Set by user, may be overwritten by libavcodec.
+ */
+ int64_t rc_max_rate;
+
+ /**
+ * minimum bitrate
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ int64_t rc_min_rate;
+
+ /**
+ * Ratecontrol attempt to use, at maximum, <value> of what can be used without
+ * an underflow.
+ * - encoding: Set by user.
+ * - decoding: unused.
+ */
+ float rc_max_available_vbv_use;
+
+ /**
+ * Ratecontrol attempt to use, at least, <value> times the amount needed to
+ * prevent a vbv overflow.
+ * - encoding: Set by user.
+ * - decoding: unused.
+ */
+ float rc_min_vbv_overflow_use;
+
+ /**
+ * Number of bits which should be loaded into the rc buffer before decoding
+ * starts.
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ int rc_initial_buffer_occupancy;
+
+ /**
+ * trellis RD quantization
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ int trellis;
+
+ /**
+ * pass1 encoding statistics output buffer
+ * - encoding: Set by libavcodec.
+ * - decoding: unused
+ */
+ char* stats_out;
+
+ /**
+ * pass2 encoding statistics input buffer
+ * Concatenated stuff from stats_out of pass1 should be placed here.
+ * - encoding: Allocated/set/freed by user.
+ * - decoding: unused
+ */
+ char* stats_in;
+
+ /**
+ * Work around bugs in encoders which sometimes cannot be detected
+ * automatically.
+ * - encoding: Set by user
+ * - decoding: Set by user
+ */
+ int workaround_bugs;
+#define FF_BUG_AUTODETECT 1 ///< autodetection
+#define FF_BUG_XVID_ILACE 4
+#define FF_BUG_UMP4 8
+#define FF_BUG_NO_PADDING 16
+#define FF_BUG_AMV 32
+#define FF_BUG_QPEL_CHROMA 64
+#define FF_BUG_STD_QPEL 128
+#define FF_BUG_QPEL_CHROMA2 256
+#define FF_BUG_DIRECT_BLOCKSIZE 512
+#define FF_BUG_EDGE 1024
+#define FF_BUG_HPEL_CHROMA 2048
+#define FF_BUG_DC_CLIP 4096
+#define FF_BUG_MS \
+ 8192 ///< Work around various bugs in Microsoft's broken decoders.
+#define FF_BUG_TRUNCATED 16384
+#define FF_BUG_IEDGE 32768
+
+ /**
+ * strictly follow the standard (MPEG-4, ...).
+ * - encoding: Set by user.
+ * - decoding: Set by user.
+ * Setting this to STRICT or higher means the encoder and decoder will
+ * generally do stupid things, whereas setting it to unofficial or lower
+ * will mean the encoder might produce output that is not supported by all
+ * spec-compliant decoders. Decoders don't differentiate between normal,
+ * unofficial and experimental (that is, they always try to decode things
+ * when they can) unless they are explicitly asked to behave stupidly
+ * (=strictly conform to the specs)
+ * This may only be set to one of the FF_COMPLIANCE_* values in defs.h.
+ */
+ int strict_std_compliance;
+
+ /**
+ * error concealment flags
+ * - encoding: unused
+ * - decoding: Set by user.
+ */
+ int error_concealment;
+#define FF_EC_GUESS_MVS 1
+#define FF_EC_DEBLOCK 2
+#define FF_EC_FAVOR_INTER 256
+
+ /**
+ * debug
+ * - encoding: Set by user.
+ * - decoding: Set by user.
+ */
+ int debug;
+#define FF_DEBUG_PICT_INFO 1
+#define FF_DEBUG_RC 2
+#define FF_DEBUG_BITSTREAM 4
+#define FF_DEBUG_MB_TYPE 8
+#define FF_DEBUG_QP 16
+#define FF_DEBUG_DCT_COEFF 0x00000040
+#define FF_DEBUG_SKIP 0x00000080
+#define FF_DEBUG_STARTCODE 0x00000100
+#define FF_DEBUG_ER 0x00000400
+#define FF_DEBUG_MMCO 0x00000800
+#define FF_DEBUG_BUGS 0x00001000
+#define FF_DEBUG_BUFFERS 0x00008000
+#define FF_DEBUG_THREADS 0x00010000
+#define FF_DEBUG_GREEN_MD 0x00800000
+#define FF_DEBUG_NOMC 0x01000000
+
+ /**
+ * Error recognition; may misdetect some more or less valid parts as errors.
+ * This is a bitfield of the AV_EF_* values defined in defs.h.
+ *
+ * - encoding: Set by user.
+ * - decoding: Set by user.
+ */
+ int err_recognition;
+
+ /**
+ * Hardware accelerator in use
+ * - encoding: unused.
+ * - decoding: Set by libavcodec
+ */
+ const struct AVHWAccel* hwaccel;
+
+ /**
+ * Legacy hardware accelerator context.
+ *
+ * For some hardware acceleration methods, the caller may use this field to
+ * signal hwaccel-specific data to the codec. The struct pointed to by this
+ * pointer is hwaccel-dependent and defined in the respective header. Please
+ * refer to the FFmpeg HW accelerator documentation to know how to fill
+ * this.
+ *
+ * In most cases this field is optional - the necessary information may also
+ * be provided to libavcodec through @ref hw_frames_ctx or @ref
+ * hw_device_ctx (see avcodec_get_hw_config()). However, in some cases it
+ * may be the only method of signalling some (optional) information.
+ *
+ * The struct and its contents are owned by the caller.
+ *
+ * - encoding: May be set by the caller before avcodec_open2(). Must remain
+ * valid until avcodec_free_context().
+ * - decoding: May be set by the caller in the get_format() callback.
+ * Must remain valid until the next get_format() call,
+ * or avcodec_free_context() (whichever comes first).
+ */
+ void* hwaccel_context;
+
+ /**
+ * A reference to the AVHWFramesContext describing the input (for encoding)
+ * or output (decoding) frames. The reference is set by the caller and
+ * afterwards owned (and freed) by libavcodec - it should never be read by
+ * the caller after being set.
+ *
+ * - decoding: This field should be set by the caller from the get_format()
+ * callback. The previous reference (if any) will always be
+ * unreffed by libavcodec before the get_format() call.
+ *
+ * If the default get_buffer2() is used with a hwaccel pixel
+ * format, then this AVHWFramesContext will be used for
+ * allocating the frame buffers.
+ *
+ * - encoding: For hardware encoders configured to use a hwaccel pixel
+ * format, this field should be set by the caller to a reference
+ * to the AVHWFramesContext describing input frames.
+ * AVHWFramesContext.format must be equal to
+ * AVCodecContext.pix_fmt.
+ *
+ * This field should be set before avcodec_open2() is called.
+ */
+ AVBufferRef* hw_frames_ctx;
+
+ /**
+ * A reference to the AVHWDeviceContext describing the device which will
+ * be used by a hardware encoder/decoder. The reference is set by the
+ * caller and afterwards owned (and freed) by libavcodec.
+ *
+ * This should be used if either the codec device does not require
+ * hardware frames or any that are used are to be allocated internally by
+ * libavcodec. If the user wishes to supply any of the frames used as
+ * encoder input or decoder output then hw_frames_ctx should be used
+ * instead. When hw_frames_ctx is set in get_format() for a decoder, this
+ * field will be ignored while decoding the associated stream segment, but
+ * may again be used on a following one after another get_format() call.
+ *
+ * For both encoders and decoders this field should be set before
+ * avcodec_open2() is called and must not be written to thereafter.
+ *
+ * Note that some decoders may require this field to be set initially in
+ * order to support hw_frames_ctx at all - in that case, all frames
+ * contexts used must be created on the same device.
+ */
+ AVBufferRef* hw_device_ctx;
+
+ /**
+ * Bit set of AV_HWACCEL_FLAG_* flags, which affect hardware accelerated
+ * decoding (if active).
+ * - encoding: unused
+ * - decoding: Set by user (either before avcodec_open2(), or in the
+ * AVCodecContext.get_format callback)
+ */
+ int hwaccel_flags;
+
+ /**
+ * Video decoding only. Sets the number of extra hardware frames which
+ * the decoder will allocate for use by the caller. This must be set
+ * before avcodec_open2() is called.
+ *
+ * Some hardware decoders require all frames that they will use for
+ * output to be defined in advance before decoding starts. For such
+ * decoders, the hardware frame pool must therefore be of a fixed size.
+ * The extra frames set here are on top of any number that the decoder
+ * needs internally in order to operate normally (for example, frames
+ * used as reference pictures).
+ */
+ int extra_hw_frames;
+
+ /**
+ * error
+ * - encoding: Set by libavcodec if flags & AV_CODEC_FLAG_PSNR.
+ * - decoding: unused
+ */
+ uint64_t error[AV_NUM_DATA_POINTERS];
+
+ /**
+ * DCT algorithm, see FF_DCT_* below
+ * - encoding: Set by user.
+ * - decoding: unused
+ */
+ int dct_algo;
+#define FF_DCT_AUTO 0
+#define FF_DCT_FASTINT 1
+#define FF_DCT_INT 2
+#define FF_DCT_MMX 3
+#define FF_DCT_ALTIVEC 5
+#define FF_DCT_FAAN 6
+
+ /**
+ * IDCT algorithm, see FF_IDCT_* below.
+ * - encoding: Set by user.
+ * - decoding: Set by user.
+ */
+ int idct_algo;
+#define FF_IDCT_AUTO 0
+#define FF_IDCT_INT 1
+#define FF_IDCT_SIMPLE 2
+#define FF_IDCT_SIMPLEMMX 3
+#define FF_IDCT_ARM 7
+#define FF_IDCT_ALTIVEC 8
+#define FF_IDCT_SIMPLEARM 10
+#define FF_IDCT_XVID 14
+#define FF_IDCT_SIMPLEARMV5TE 16
+#define FF_IDCT_SIMPLEARMV6 17
+#define FF_IDCT_FAAN 20
+#define FF_IDCT_SIMPLENEON 22
+#define FF_IDCT_SIMPLEAUTO 128
+
+ /**
+ * bits per sample/pixel from the demuxer (needed for huffyuv).
+ * - encoding: Set by libavcodec.
+ * - decoding: Set by user.
+ */
+ int bits_per_coded_sample;
+
+ /**
+ * Bits per sample/pixel of internal libavcodec pixel/sample format.
+ * - encoding: set by user.
+ * - decoding: set by libavcodec.
+ */
+ int bits_per_raw_sample;
+
+ /**
+ * thread count
+ * is used to decide how many independent tasks should be passed to execute()
+ * - encoding: Set by user.
+ * - decoding: Set by user.
+ */
+ int thread_count;
+
+ /**
+ * Which multithreading methods to use.
+ * Use of FF_THREAD_FRAME will increase decoding delay by one frame per
+ * thread, so clients which cannot provide future frames should not use it.
+ *
+ * - encoding: Set by user, otherwise the default is used.
+ * - decoding: Set by user, otherwise the default is used.
+ */
+ int thread_type;
+#define FF_THREAD_FRAME 1 ///< Decode more than one frame at once
+#define FF_THREAD_SLICE \
+ 2 ///< Decode more than one part of a single frame at once
+
+ /**
+ * Which multithreading methods are in use by the codec.
+ * - encoding: Set by libavcodec.
+ * - decoding: Set by libavcodec.
+ */
+ int active_thread_type;
+
+ /**
+ * The codec may call this to execute several independent things.
+ * It will return only after finishing all tasks.
+ * The user may replace this with some multithreaded implementation,
+ * the default implementation will execute the parts serially.
+ * @param count the number of things to execute
+ * - encoding: Set by libavcodec, user can override.
+ * - decoding: Set by libavcodec, user can override.
+ */
+ int (*execute)(struct AVCodecContext* c,
+ int (*func)(struct AVCodecContext* c2, void* arg), void* arg2,
+ int* ret, int count, int size);
+
+ /**
+ * The codec may call this to execute several independent things.
+ * It will return only after finishing all tasks.
+ * The user may replace this with some multithreaded implementation,
+ * the default implementation will execute the parts serially.
+ * @param c context passed also to func
+ * @param count the number of things to execute
+ * @param arg2 argument passed unchanged to func
+ * @param ret return values of executed functions, must have space for "count"
+ * values. May be NULL.
+ * @param func function that will be called count times, with jobnr from 0 to
+ * count-1. threadnr will be in the range 0 to c->thread_count-1 < MAX_THREADS
+ * and so that no two instances of func executing at the same time will have
+ * the same threadnr.
+ * @return always 0 currently, but code should handle a future improvement
+ * where when any call to func returns < 0 no further calls to func may be
+ * done and < 0 is returned.
+ * - encoding: Set by libavcodec, user can override.
+ * - decoding: Set by libavcodec, user can override.
+ */
+ int (*execute2)(struct AVCodecContext* c,
+ int (*func)(struct AVCodecContext* c2, void* arg, int jobnr,
+ int threadnr),
+ void* arg2, int* ret, int count);
+
+ /**
+ * profile
+ * - encoding: Set by user.
+ * - decoding: Set by libavcodec.
+ * See the AV_PROFILE_* defines in defs.h.
+ */
+ int profile;
+#if FF_API_FF_PROFILE_LEVEL
+ /** @deprecated The following defines are deprecated; use AV_PROFILE_*
+ * in defs.h instead. */
+# define FF_PROFILE_UNKNOWN -99
+# define FF_PROFILE_RESERVED -100
+
+# define FF_PROFILE_AAC_MAIN 0
+# define FF_PROFILE_AAC_LOW 1
+# define FF_PROFILE_AAC_SSR 2
+# define FF_PROFILE_AAC_LTP 3
+# define FF_PROFILE_AAC_HE 4
+# define FF_PROFILE_AAC_HE_V2 28
+# define FF_PROFILE_AAC_LD 22
+# define FF_PROFILE_AAC_ELD 38
+# define FF_PROFILE_MPEG2_AAC_LOW 128
+# define FF_PROFILE_MPEG2_AAC_HE 131
+
+# define FF_PROFILE_DNXHD 0
+# define FF_PROFILE_DNXHR_LB 1
+# define FF_PROFILE_DNXHR_SQ 2
+# define FF_PROFILE_DNXHR_HQ 3
+# define FF_PROFILE_DNXHR_HQX 4
+# define FF_PROFILE_DNXHR_444 5
+
+# define FF_PROFILE_DTS 20
+# define FF_PROFILE_DTS_ES 30
+# define FF_PROFILE_DTS_96_24 40
+# define FF_PROFILE_DTS_HD_HRA 50
+# define FF_PROFILE_DTS_HD_MA 60
+# define FF_PROFILE_DTS_EXPRESS 70
+# define FF_PROFILE_DTS_HD_MA_X 61
+# define FF_PROFILE_DTS_HD_MA_X_IMAX 62
+
+# define FF_PROFILE_EAC3_DDP_ATMOS 30
+
+# define FF_PROFILE_TRUEHD_ATMOS 30
+
+# define FF_PROFILE_MPEG2_422 0
+# define FF_PROFILE_MPEG2_HIGH 1
+# define FF_PROFILE_MPEG2_SS 2
+# define FF_PROFILE_MPEG2_SNR_SCALABLE 3
+# define FF_PROFILE_MPEG2_MAIN 4
+# define FF_PROFILE_MPEG2_SIMPLE 5
+
+# define FF_PROFILE_H264_CONSTRAINED (1 << 9) // 8+1; constraint_set1_flag
+# define FF_PROFILE_H264_INTRA (1 << 11) // 8+3; constraint_set3_flag
+
+# define FF_PROFILE_H264_BASELINE 66
+# define FF_PROFILE_H264_CONSTRAINED_BASELINE \
+ (66 | FF_PROFILE_H264_CONSTRAINED)
+# define FF_PROFILE_H264_MAIN 77
+# define FF_PROFILE_H264_EXTENDED 88
+# define FF_PROFILE_H264_HIGH 100
+# define FF_PROFILE_H264_HIGH_10 110
+# define FF_PROFILE_H264_HIGH_10_INTRA (110 | FF_PROFILE_H264_INTRA)
+# define FF_PROFILE_H264_MULTIVIEW_HIGH 118
+# define FF_PROFILE_H264_HIGH_422 122
+# define FF_PROFILE_H264_HIGH_422_INTRA (122 | FF_PROFILE_H264_INTRA)
+# define FF_PROFILE_H264_STEREO_HIGH 128
+# define FF_PROFILE_H264_HIGH_444 144
+# define FF_PROFILE_H264_HIGH_444_PREDICTIVE 244
+# define FF_PROFILE_H264_HIGH_444_INTRA (244 | FF_PROFILE_H264_INTRA)
+# define FF_PROFILE_H264_CAVLC_444 44
+
+# define FF_PROFILE_VC1_SIMPLE 0
+# define FF_PROFILE_VC1_MAIN 1
+# define FF_PROFILE_VC1_COMPLEX 2
+# define FF_PROFILE_VC1_ADVANCED 3
+
+# define FF_PROFILE_MPEG4_SIMPLE 0
+# define FF_PROFILE_MPEG4_SIMPLE_SCALABLE 1
+# define FF_PROFILE_MPEG4_CORE 2
+# define FF_PROFILE_MPEG4_MAIN 3
+# define FF_PROFILE_MPEG4_N_BIT 4
+# define FF_PROFILE_MPEG4_SCALABLE_TEXTURE 5
+# define FF_PROFILE_MPEG4_SIMPLE_FACE_ANIMATION 6
+# define FF_PROFILE_MPEG4_BASIC_ANIMATED_TEXTURE 7
+# define FF_PROFILE_MPEG4_HYBRID 8
+# define FF_PROFILE_MPEG4_ADVANCED_REAL_TIME 9
+# define FF_PROFILE_MPEG4_CORE_SCALABLE 10
+# define FF_PROFILE_MPEG4_ADVANCED_CODING 11
+# define FF_PROFILE_MPEG4_ADVANCED_CORE 12
+# define FF_PROFILE_MPEG4_ADVANCED_SCALABLE_TEXTURE 13
+# define FF_PROFILE_MPEG4_SIMPLE_STUDIO 14
+# define FF_PROFILE_MPEG4_ADVANCED_SIMPLE 15
+
+# define FF_PROFILE_JPEG2000_CSTREAM_RESTRICTION_0 1
+# define FF_PROFILE_JPEG2000_CSTREAM_RESTRICTION_1 2
+# define FF_PROFILE_JPEG2000_CSTREAM_NO_RESTRICTION 32768
+# define FF_PROFILE_JPEG2000_DCINEMA_2K 3
+# define FF_PROFILE_JPEG2000_DCINEMA_4K 4
+
+# define FF_PROFILE_VP9_0 0
+# define FF_PROFILE_VP9_1 1
+# define FF_PROFILE_VP9_2 2
+# define FF_PROFILE_VP9_3 3
+
+# define FF_PROFILE_HEVC_MAIN 1
+# define FF_PROFILE_HEVC_MAIN_10 2
+# define FF_PROFILE_HEVC_MAIN_STILL_PICTURE 3
+# define FF_PROFILE_HEVC_REXT 4
+# define FF_PROFILE_HEVC_SCC 9
+
+# define FF_PROFILE_VVC_MAIN_10 1
+# define FF_PROFILE_VVC_MAIN_10_444 33
+
+# define FF_PROFILE_AV1_MAIN 0
+# define FF_PROFILE_AV1_HIGH 1
+# define FF_PROFILE_AV1_PROFESSIONAL 2
+
+# define FF_PROFILE_MJPEG_HUFFMAN_BASELINE_DCT 0xc0
+# define FF_PROFILE_MJPEG_HUFFMAN_EXTENDED_SEQUENTIAL_DCT 0xc1
+# define FF_PROFILE_MJPEG_HUFFMAN_PROGRESSIVE_DCT 0xc2
+# define FF_PROFILE_MJPEG_HUFFMAN_LOSSLESS 0xc3
+# define FF_PROFILE_MJPEG_JPEG_LS 0xf7
+
+# define FF_PROFILE_SBC_MSBC 1
+
+# define FF_PROFILE_PRORES_PROXY 0
+# define FF_PROFILE_PRORES_LT 1
+# define FF_PROFILE_PRORES_STANDARD 2
+# define FF_PROFILE_PRORES_HQ 3
+# define FF_PROFILE_PRORES_4444 4
+# define FF_PROFILE_PRORES_XQ 5
+
+# define FF_PROFILE_ARIB_PROFILE_A 0
+# define FF_PROFILE_ARIB_PROFILE_C 1
+
+# define FF_PROFILE_KLVA_SYNC 0
+# define FF_PROFILE_KLVA_ASYNC 1
+
+# define FF_PROFILE_EVC_BASELINE 0
+# define FF_PROFILE_EVC_MAIN 1
+#endif
+
+ /**
+ * Encoding level descriptor.
+ * - encoding: Set by user, corresponds to a specific level defined by the
+ * codec, usually corresponding to the profile level, if not specified it
+ * is set to FF_LEVEL_UNKNOWN.
+ * - decoding: Set by libavcodec.
+ * See AV_LEVEL_* in defs.h.
+ */
+ int level;
+#if FF_API_FF_PROFILE_LEVEL
+ /** @deprecated The following define is deprecated; use AV_LEVEL_UNKOWN
+ * in defs.h instead. */
+# define FF_LEVEL_UNKNOWN -99
+#endif
+
+ /**
+ * Properties of the stream that gets decoded
+ * - encoding: unused
+ * - decoding: set by libavcodec
+ */
+ unsigned properties;
+#define FF_CODEC_PROPERTY_LOSSLESS 0x00000001
+#define FF_CODEC_PROPERTY_CLOSED_CAPTIONS 0x00000002
+#define FF_CODEC_PROPERTY_FILM_GRAIN 0x00000004
+
+ /**
+ * Skip loop filtering for selected frames.
+ * - encoding: unused
+ * - decoding: Set by user.
+ */
+ enum AVDiscard skip_loop_filter;
+
+ /**
+ * Skip IDCT/dequantization for selected frames.
+ * - encoding: unused
+ * - decoding: Set by user.
+ */
+ enum AVDiscard skip_idct;
+
+ /**
+ * Skip decoding for selected frames.
+ * - encoding: unused
+ * - decoding: Set by user.
+ */
+ enum AVDiscard skip_frame;
+
+ /**
+ * Skip processing alpha if supported by codec.
+ * Note that if the format uses pre-multiplied alpha (common with VP6,
+ * and recommended due to better video quality/compression)
+ * the image will look as if alpha-blended onto a black background.
+ * However for formats that do not use pre-multiplied alpha
+ * there might be serious artefacts (though e.g. libswscale currently
+ * assumes pre-multiplied alpha anyway).
+ *
+ * - decoding: set by user
+ * - encoding: unused
+ */
+ int skip_alpha;
+
+ /**
+ * Number of macroblock rows at the top which are skipped.
+ * - encoding: unused
+ * - decoding: Set by user.
+ */
+ int skip_top;
+
+ /**
+ * Number of macroblock rows at the bottom which are skipped.
+ * - encoding: unused
+ * - decoding: Set by user.
+ */
+ int skip_bottom;
+
+ /**
+ * low resolution decoding, 1-> 1/2 size, 2->1/4 size
+ * - encoding: unused
+ * - decoding: Set by user.
+ */
+ int lowres;
+
+ /**
+ * AVCodecDescriptor
+ * - encoding: unused.
+ * - decoding: set by libavcodec.
+ */
+ const struct AVCodecDescriptor* codec_descriptor;
+
+ /**
+ * Character encoding of the input subtitles file.
+ * - decoding: set by user
+ * - encoding: unused
+ */
+ char* sub_charenc;
+
+ /**
+ * Subtitles character encoding mode. Formats or codecs might be adjusting
+ * this setting (if they are doing the conversion themselves for instance).
+ * - decoding: set by libavcodec
+ * - encoding: unused
+ */
+ int sub_charenc_mode;
+#define FF_SUB_CHARENC_MODE_DO_NOTHING \
+ -1 ///< do nothing (demuxer outputs a stream supposed to be already in UTF-8,
+ ///< or the codec is bitmap for instance)
+#define FF_SUB_CHARENC_MODE_AUTOMATIC \
+ 0 ///< libavcodec will select the mode itself
+#define FF_SUB_CHARENC_MODE_PRE_DECODER \
+ 1 ///< the AVPacket data needs to be recoded to UTF-8 before being fed to the
+ ///< decoder, requires iconv
+#define FF_SUB_CHARENC_MODE_IGNORE \
+ 2 ///< neither convert the subtitles, nor check them for valid UTF-8
+
+ /**
+ * Header containing style information for text subtitles.
+ * For SUBTITLE_ASS subtitle type, it should contain the whole ASS
+ * [Script Info] and [V4+ Styles] section, plus the [Events] line and
+ * the Format line following. It shouldn't include any Dialogue line.
+ * - encoding: Set/allocated/freed by user (before avcodec_open2())
+ * - decoding: Set/allocated/freed by libavcodec (by avcodec_open2())
+ */
+ int subtitle_header_size;
+ uint8_t* subtitle_header;
+
+ /**
+ * dump format separator.
+ * can be ", " or "\n " or anything else
+ * - encoding: Set by user.
+ * - decoding: Set by user.
+ */
+ uint8_t* dump_separator;
+
+ /**
+ * ',' separated list of allowed decoders.
+ * If NULL then all are allowed
+ * - encoding: unused
+ * - decoding: set by user
+ */
+ char* codec_whitelist;
+
+ /**
+ * Additional data associated with the entire coded stream.
+ *
+ * - decoding: may be set by user before calling avcodec_open2().
+ * - encoding: may be set by libavcodec after avcodec_open2().
+ */
+ AVPacketSideData* coded_side_data;
+ int nb_coded_side_data;
+
+ /**
+ * Bit set of AV_CODEC_EXPORT_DATA_* flags, which affects the kind of
+ * metadata exported in frame, packet, or coded stream side data by
+ * decoders and encoders.
+ *
+ * - decoding: set by user
+ * - encoding: set by user
+ */
+ int export_side_data;
+
+ /**
+ * The number of pixels per image to maximally accept.
+ *
+ * - decoding: set by user
+ * - encoding: set by user
+ */
+ int64_t max_pixels;
+
+ /**
+ * Video decoding only. Certain video codecs support cropping, meaning that
+ * only a sub-rectangle of the decoded frame is intended for display. This
+ * option controls how cropping is handled by libavcodec.
+ *
+ * When set to 1 (the default), libavcodec will apply cropping internally.
+ * I.e. it will modify the output frame width/height fields and offset the
+ * data pointers (only by as much as possible while preserving alignment, or
+ * by the full amount if the AV_CODEC_FLAG_UNALIGNED flag is set) so that
+ * the frames output by the decoder refer only to the cropped area. The
+ * crop_* fields of the output frames will be zero.
+ *
+ * When set to 0, the width/height fields of the output frames will be set
+ * to the coded dimensions and the crop_* fields will describe the cropping
+ * rectangle. Applying the cropping is left to the caller.
+ *
+ * @warning When hardware acceleration with opaque output frames is used,
+ * libavcodec is unable to apply cropping from the top/left border.
+ *
+ * @note when this option is set to zero, the width/height fields of the
+ * AVCodecContext and output AVFrames have different meanings. The codec
+ * context fields store display dimensions (with the coded dimensions in
+ * coded_width/height), while the frame fields store the coded dimensions
+ * (with the display dimensions being determined by the crop_* fields).
+ */
+ int apply_cropping;
+
+ /**
+ * The percentage of damaged samples to discard a frame.
+ *
+ * - decoding: set by user
+ * - encoding: unused
+ */
+ int discard_damaged_percentage;
+
+ /**
+ * The number of samples per frame to maximally accept.
+ *
+ * - decoding: set by user
+ * - encoding: set by user
+ */
+ int64_t max_samples;
+
+ /**
+ * This callback is called at the beginning of each packet to get a data
+ * buffer for it.
+ *
+ * The following field will be set in the packet before this callback is
+ * called:
+ * - size
+ * This callback must use the above value to calculate the required buffer
+ * size, which must padded by at least AV_INPUT_BUFFER_PADDING_SIZE bytes.
+ *
+ * In some specific cases, the encoder may not use the entire buffer allocated
+ * by this callback. This will be reflected in the size value in the packet
+ * once returned by avcodec_receive_packet().
+ *
+ * This callback must fill the following fields in the packet:
+ * - data: alignment requirements for AVPacket apply, if any. Some
+ * architectures and encoders may benefit from having aligned data.
+ * - buf: must contain a pointer to an AVBufferRef structure. The packet's
+ * data pointer must be contained in it. See: av_buffer_create(),
+ * av_buffer_alloc(), and av_buffer_ref().
+ *
+ * If AV_CODEC_CAP_DR1 is not set then get_encode_buffer() must call
+ * avcodec_default_get_encode_buffer() instead of providing a buffer allocated
+ * by some other means.
+ *
+ * The flags field may contain a combination of AV_GET_ENCODE_BUFFER_FLAG_
+ * flags. They may be used for example to hint what use the buffer may get
+ * after being created. Implementations of this callback may ignore flags they
+ * don't understand. If AV_GET_ENCODE_BUFFER_FLAG_REF is set in flags then the
+ * packet may be reused (read and/or written to if it is writable) later by
+ * libavcodec.
+ *
+ * This callback must be thread-safe, as when frame threading is used, it may
+ * be called from multiple threads simultaneously.
+ *
+ * @see avcodec_default_get_encode_buffer()
+ *
+ * - encoding: Set by libavcodec, user can override.
+ * - decoding: unused
+ */
+ int (*get_encode_buffer)(struct AVCodecContext* s, AVPacket* pkt, int flags);
+
+ /**
+ * Frame counter, set by libavcodec.
+ *
+ * - decoding: total number of frames returned from the decoder so far.
+ * - encoding: total number of frames passed to the encoder so far.
+ *
+ * @note the counter is not incremented if encoding/decoding resulted in
+ * an error.
+ */
+ int64_t frame_num;
+
+ /**
+ * Decoding only. May be set by the caller before avcodec_open2() to an
+ * av_malloc()'ed array (or via AVOptions). Owned and freed by the decoder
+ * afterwards.
+ *
+ * Side data attached to decoded frames may come from several sources:
+ * 1. coded_side_data, which the decoder will for certain types translate
+ * from packet-type to frame-type and attach to frames;
+ * 2. side data attached to an AVPacket sent for decoding (same
+ * considerations as above);
+ * 3. extracted from the coded bytestream.
+ * The first two cases are supplied by the caller and typically come from a
+ * container.
+ *
+ * This array configures decoder behaviour in cases when side data of the
+ * same type is present both in the coded bytestream and in the
+ * user-supplied side data (items 1. and 2. above). In all cases, at most
+ * one instance of each side data type will be attached to output frames. By
+ * default it will be the bytestream side data. Adding an
+ * AVPacketSideDataType value to this array will flip the preference for
+ * this type, thus making the decoder prefer user-supplied side data over
+ * bytestream. In case side data of the same type is present both in
+ * coded_data and attacked to a packet, the packet instance always has
+ * priority.
+ *
+ * The array may also contain a single -1, in which case the preference is
+ * switched for all side data types.
+ */
+ int* side_data_prefer_packet;
+ /**
+ * Number of entries in side_data_prefer_packet.
+ */
+ unsigned nb_side_data_prefer_packet;
+
+ /**
+ * Array containing static side data, such as HDR10 CLL / MDCV structures.
+ * Side data entries should be allocated by usage of helpers defined in
+ * libavutil/frame.h.
+ *
+ * - encoding: may be set by user before calling avcodec_open2() for
+ * encoder configuration. Afterwards owned and freed by the
+ * encoder.
+ * - decoding: unused
+ */
+ AVFrameSideData** decoded_side_data;
+ int nb_decoded_side_data;
+} AVCodecContext;
+
+/**
+ * @defgroup lavc_hwaccel AVHWAccel
+ *
+ * @note Nothing in this structure should be accessed by the user. At some
+ * point in future it will not be externally visible at all.
+ *
+ * @{
+ */
+typedef struct AVHWAccel {
+ /**
+ * Name of the hardware accelerated codec.
+ * The name is globally unique among encoders and among decoders (but an
+ * encoder and a decoder can share the same name).
+ */
+ const char* name;
+
+ /**
+ * Type of codec implemented by the hardware accelerator.
+ *
+ * See AVMEDIA_TYPE_xxx
+ */
+ enum AVMediaType type;
+
+ /**
+ * Codec implemented by the hardware accelerator.
+ *
+ * See AV_CODEC_ID_xxx
+ */
+ enum AVCodecID id;
+
+ /**
+ * Supported pixel format.
+ *
+ * Only hardware accelerated formats are supported here.
+ */
+ enum AVPixelFormat pix_fmt;
+
+ /**
+ * Hardware accelerated codec capabilities.
+ * see AV_HWACCEL_CODEC_CAP_*
+ */
+ int capabilities;
+} AVHWAccel;
+
+/**
+ * HWAccel is experimental and is thus avoided in favor of non experimental
+ * codecs
+ */
+#define AV_HWACCEL_CODEC_CAP_EXPERIMENTAL 0x0200
+
+/**
+ * Hardware acceleration should be used for decoding even if the codec level
+ * used is unknown or higher than the maximum supported level reported by the
+ * hardware driver.
+ *
+ * It's generally a good idea to pass this flag unless you have a specific
+ * reason not to, as hardware tends to under-report supported levels.
+ */
+#define AV_HWACCEL_FLAG_IGNORE_LEVEL (1 << 0)
+
+/**
+ * Hardware acceleration can output YUV pixel formats with a different chroma
+ * sampling than 4:2:0 and/or other than 8 bits per component.
+ */
+#define AV_HWACCEL_FLAG_ALLOW_HIGH_DEPTH (1 << 1)
+
+/**
+ * Hardware acceleration should still be attempted for decoding when the
+ * codec profile does not match the reported capabilities of the hardware.
+ *
+ * For example, this can be used to try to decode baseline profile H.264
+ * streams in hardware - it will often succeed, because many streams marked
+ * as baseline profile actually conform to constrained baseline profile.
+ *
+ * @warning If the stream is actually not supported then the behaviour is
+ * undefined, and may include returning entirely incorrect output
+ * while indicating success.
+ */
+#define AV_HWACCEL_FLAG_ALLOW_PROFILE_MISMATCH (1 << 2)
+
+/**
+ * Some hardware decoders (namely nvdec) can either output direct decoder
+ * surfaces, or make an on-device copy and return said copy.
+ * There is a hard limit on how many decoder surfaces there can be, and it
+ * cannot be accurately guessed ahead of time.
+ * For some processing chains, this can be okay, but others will run into the
+ * limit and in turn produce very confusing errors that require fine tuning of
+ * more or less obscure options by the user, or in extreme cases cannot be
+ * resolved at all without inserting an avfilter that forces a copy.
+ *
+ * Thus, the hwaccel will by default make a copy for safety and resilience.
+ * If a users really wants to minimize the amount of copies, they can set this
+ * flag and ensure their processing chain does not exhaust the surface pool.
+ */
+#define AV_HWACCEL_FLAG_UNSAFE_OUTPUT (1 << 3)
+
+/**
+ * @}
+ */
+
+enum AVSubtitleType {
+ SUBTITLE_NONE,
+
+ SUBTITLE_BITMAP, ///< A bitmap, pict will be set
+
+ /**
+ * Plain text, the text field must be set by the decoder and is
+ * authoritative. ass and pict fields may contain approximations.
+ */
+ SUBTITLE_TEXT,
+
+ /**
+ * Formatted text, the ass field must be set by the decoder and is
+ * authoritative. pict and text fields may contain approximations.
+ */
+ SUBTITLE_ASS,
+};
+
+#define AV_SUBTITLE_FLAG_FORCED 0x00000001
+
+typedef struct AVSubtitleRect {
+ int x; ///< top left corner of pict, undefined when pict is not set
+ int y; ///< top left corner of pict, undefined when pict is not set
+ int w; ///< width of pict, undefined when pict is not set
+ int h; ///< height of pict, undefined when pict is not set
+ int nb_colors; ///< number of colors in pict, undefined when pict is not set
+
+ /**
+ * data+linesize for the bitmap of this subtitle.
+ * Can be set for text/ass as well once they are rendered.
+ */
+ uint8_t* data[4];
+ int linesize[4];
+
+ int flags;
+ enum AVSubtitleType type;
+
+ char* text; ///< 0 terminated plain UTF-8 text
+
+ /**
+ * 0 terminated ASS/SSA compatible event line.
+ * The presentation of this is unaffected by the other values in this
+ * struct.
+ */
+ char* ass;
+} AVSubtitleRect;
+
+typedef struct AVSubtitle {
+ uint16_t format; /* 0 = graphics */
+ uint32_t start_display_time; /* relative to packet pts, in ms */
+ uint32_t end_display_time; /* relative to packet pts, in ms */
+ unsigned num_rects;
+ AVSubtitleRect** rects;
+ int64_t pts; ///< Same as packet pts, in AV_TIME_BASE
+} AVSubtitle;
+
+/**
+ * Return the LIBAVCODEC_VERSION_INT constant.
+ */
+unsigned avcodec_version(void);
+
+/**
+ * Return the libavcodec build-time configuration.
+ */
+const char* avcodec_configuration(void);
+
+/**
+ * Return the libavcodec license.
+ */
+const char* avcodec_license(void);
+
+/**
+ * Allocate an AVCodecContext and set its fields to default values. The
+ * resulting struct should be freed with avcodec_free_context().
+ *
+ * @param codec if non-NULL, allocate private data and initialize defaults
+ * for the given codec. It is illegal to then call avcodec_open2()
+ * with a different codec.
+ * If NULL, then the codec-specific defaults won't be initialized,
+ * which may result in suboptimal default settings (this is
+ * important mainly for encoders, e.g. libx264).
+ *
+ * @return An AVCodecContext filled with default values or NULL on failure.
+ */
+AVCodecContext* avcodec_alloc_context3(const AVCodec* codec);
+
+/**
+ * Free the codec context and everything associated with it and write NULL to
+ * the provided pointer.
+ */
+void avcodec_free_context(AVCodecContext** avctx);
+
+/**
+ * Get the AVClass for AVCodecContext. It can be used in combination with
+ * AV_OPT_SEARCH_FAKE_OBJ for examining options.
+ *
+ * @see av_opt_find().
+ */
+const AVClass* avcodec_get_class(void);
+
+/**
+ * Get the AVClass for AVSubtitleRect. It can be used in combination with
+ * AV_OPT_SEARCH_FAKE_OBJ for examining options.
+ *
+ * @see av_opt_find().
+ */
+const AVClass* avcodec_get_subtitle_rect_class(void);
+
+/**
+ * Fill the parameters struct based on the values from the supplied codec
+ * context. Any allocated fields in par are freed and replaced with duplicates
+ * of the corresponding fields in codec.
+ *
+ * @return >= 0 on success, a negative AVERROR code on failure
+ */
+int avcodec_parameters_from_context(struct AVCodecParameters* par,
+ const AVCodecContext* codec);
+
+/**
+ * Fill the codec context based on the values from the supplied codec
+ * parameters. Any allocated fields in codec that have a corresponding field in
+ * par are freed and replaced with duplicates of the corresponding field in par.
+ * Fields in codec that do not have a counterpart in par are not touched.
+ *
+ * @return >= 0 on success, a negative AVERROR code on failure.
+ */
+int avcodec_parameters_to_context(AVCodecContext* codec,
+ const struct AVCodecParameters* par);
+
+/**
+ * Initialize the AVCodecContext to use the given AVCodec. Prior to using this
+ * function the context has to be allocated with avcodec_alloc_context3().
+ *
+ * The functions avcodec_find_decoder_by_name(), avcodec_find_encoder_by_name(),
+ * avcodec_find_decoder() and avcodec_find_encoder() provide an easy way for
+ * retrieving a codec.
+ *
+ * Depending on the codec, you might need to set options in the codec context
+ * also for decoding (e.g. width, height, or the pixel or audio sample format in
+ * the case the information is not available in the bitstream, as when decoding
+ * raw audio or video).
+ *
+ * Options in the codec context can be set either by setting them in the options
+ * AVDictionary, or by setting the values in the context itself, directly or by
+ * using the av_opt_set() API before calling this function.
+ *
+ * Example:
+ * @code
+ * av_dict_set(&opts, "b", "2.5M", 0);
+ * codec = avcodec_find_decoder(AV_CODEC_ID_H264);
+ * if (!codec)
+ * exit(1);
+ *
+ * context = avcodec_alloc_context3(codec);
+ *
+ * if (avcodec_open2(context, codec, opts) < 0)
+ * exit(1);
+ * @endcode
+ *
+ * In the case AVCodecParameters are available (e.g. when demuxing a stream
+ * using libavformat, and accessing the AVStream contained in the demuxer), the
+ * codec parameters can be copied to the codec context using
+ * avcodec_parameters_to_context(), as in the following example:
+ *
+ * @code
+ * AVStream *stream = ...;
+ * context = avcodec_alloc_context3(codec);
+ * if (avcodec_parameters_to_context(context, stream->codecpar) < 0)
+ * exit(1);
+ * if (avcodec_open2(context, codec, NULL) < 0)
+ * exit(1);
+ * @endcode
+ *
+ * @note Always call this function before using decoding routines (such as
+ * @ref avcodec_receive_frame()).
+ *
+ * @param avctx The context to initialize.
+ * @param codec The codec to open this context for. If a non-NULL codec has been
+ * previously passed to avcodec_alloc_context3() or
+ * for this context, then this parameter MUST be either NULL or
+ * equal to the previously passed codec.
+ * @param options A dictionary filled with AVCodecContext and codec-private
+ * options, which are set on top of the options already set in
+ * avctx, can be NULL. On return this object will be filled with
+ * options that were not found in the avctx codec context.
+ *
+ * @return zero on success, a negative value on error
+ * @see avcodec_alloc_context3(), avcodec_find_decoder(),
+ * avcodec_find_encoder(), av_dict_set(), av_opt_set(), av_opt_find(),
+ * avcodec_parameters_to_context()
+ */
+int avcodec_open2(AVCodecContext* avctx, const AVCodec* codec,
+ AVDictionary** options);
+
+#if FF_API_AVCODEC_CLOSE
+/**
+ * Close a given AVCodecContext and free all the data associated with it
+ * (but not the AVCodecContext itself).
+ *
+ * Calling this function on an AVCodecContext that hasn't been opened will free
+ * the codec-specific data allocated in avcodec_alloc_context3() with a non-NULL
+ * codec. Subsequent calls will do nothing.
+ *
+ * @deprecated Do not use this function. Use avcodec_free_context() to destroy a
+ * codec context (either open or closed). Opening and closing a codec context
+ * multiple times is not supported anymore -- use multiple codec contexts
+ * instead.
+ */
+attribute_deprecated int avcodec_close(AVCodecContext* avctx);
+#endif
+
+/**
+ * Free all allocated data in the given subtitle struct.
+ *
+ * @param sub AVSubtitle to free.
+ */
+void avsubtitle_free(AVSubtitle* sub);
+
+/**
+ * @}
+ */
+
+/**
+ * @addtogroup lavc_decoding
+ * @{
+ */
+
+/**
+ * The default callback for AVCodecContext.get_buffer2(). It is made public so
+ * it can be called by custom get_buffer2() implementations for decoders without
+ * AV_CODEC_CAP_DR1 set.
+ */
+int avcodec_default_get_buffer2(AVCodecContext* s, AVFrame* frame, int flags);
+
+/**
+ * The default callback for AVCodecContext.get_encode_buffer(). It is made
+ * public so it can be called by custom get_encode_buffer() implementations for
+ * encoders without AV_CODEC_CAP_DR1 set.
+ */
+int avcodec_default_get_encode_buffer(AVCodecContext* s, AVPacket* pkt,
+ int flags);
+
+/**
+ * Modify width and height values so that they will result in a memory
+ * buffer that is acceptable for the codec if you do not use any horizontal
+ * padding.
+ *
+ * May only be used if a codec with AV_CODEC_CAP_DR1 has been opened.
+ */
+void avcodec_align_dimensions(AVCodecContext* s, int* width, int* height);
+
+/**
+ * Modify width and height values so that they will result in a memory
+ * buffer that is acceptable for the codec if you also ensure that all
+ * line sizes are a multiple of the respective linesize_align[i].
+ *
+ * May only be used if a codec with AV_CODEC_CAP_DR1 has been opened.
+ */
+void avcodec_align_dimensions2(AVCodecContext* s, int* width, int* height,
+ int linesize_align[AV_NUM_DATA_POINTERS]);
+
+/**
+ * Decode a subtitle message.
+ * Return a negative value on error, otherwise return the number of bytes used.
+ * If no subtitle could be decompressed, got_sub_ptr is zero.
+ * Otherwise, the subtitle is stored in *sub.
+ * Note that AV_CODEC_CAP_DR1 is not available for subtitle codecs. This is for
+ * simplicity, because the performance difference is expected to be negligible
+ * and reusing a get_buffer written for video codecs would probably perform
+ * badly due to a potentially very different allocation pattern.
+ *
+ * Some decoders (those marked with AV_CODEC_CAP_DELAY) have a delay between
+ * input and output. This means that for some packets they will not immediately
+ * produce decoded output and need to be flushed at the end of decoding to get
+ * all the decoded data. Flushing is done by calling this function with packets
+ * with avpkt->data set to NULL and avpkt->size set to 0 until it stops
+ * returning subtitles. It is safe to flush even those decoders that are not
+ * marked with AV_CODEC_CAP_DELAY, then no subtitles will be returned.
+ *
+ * @note The AVCodecContext MUST have been opened with @ref avcodec_open2()
+ * before packets may be fed to the decoder.
+ *
+ * @param avctx the codec context
+ * @param[out] sub The preallocated AVSubtitle in which the decoded subtitle
+ * will be stored, must be freed with avsubtitle_free if *got_sub_ptr is set.
+ * @param[in,out] got_sub_ptr Zero if no subtitle could be decompressed,
+ * otherwise, it is nonzero.
+ * @param[in] avpkt The input AVPacket containing the input buffer.
+ */
+int avcodec_decode_subtitle2(AVCodecContext* avctx, AVSubtitle* sub,
+ int* got_sub_ptr, const AVPacket* avpkt);
+
+/**
+ * Supply raw packet data as input to a decoder.
+ *
+ * Internally, this call will copy relevant AVCodecContext fields, which can
+ * influence decoding per-packet, and apply them when the packet is actually
+ * decoded. (For example AVCodecContext.skip_frame, which might direct the
+ * decoder to drop the frame contained by the packet sent with this function.)
+ *
+ * @warning The input buffer, avpkt->data must be AV_INPUT_BUFFER_PADDING_SIZE
+ * larger than the actual read bytes because some optimized bitstream
+ * readers read 32 or 64 bits at once and could read over the end.
+ *
+ * @note The AVCodecContext MUST have been opened with @ref avcodec_open2()
+ * before packets may be fed to the decoder.
+ *
+ * @param avctx codec context
+ * @param[in] avpkt The input AVPacket. Usually, this will be a single video
+ * frame, or several complete audio frames.
+ * Ownership of the packet remains with the caller, and the
+ * decoder will not write to the packet. The decoder may create
+ * a reference to the packet data (or copy it if the packet is
+ * not reference-counted).
+ * Unlike with older APIs, the packet is always fully consumed,
+ * and if it contains multiple frames (e.g. some audio codecs),
+ * will require you to call avcodec_receive_frame() multiple
+ * times afterwards before you can send a new packet.
+ * It can be NULL (or an AVPacket with data set to NULL and
+ * size set to 0); in this case, it is considered a flush
+ * packet, which signals the end of the stream. Sending the
+ * first flush packet will return success. Subsequent ones are
+ * unnecessary and will return AVERROR_EOF. If the decoder
+ * still has frames buffered, it will return them after sending
+ * a flush packet.
+ *
+ * @retval 0 success
+ * @retval AVERROR(EAGAIN) input is not accepted in the current state - user
+ * must read output with avcodec_receive_frame() (once
+ * all output is read, the packet should be resent,
+ * and the call will not fail with EAGAIN).
+ * @retval AVERROR_EOF the decoder has been flushed, and no new packets
+ * can be sent to it (also returned if more than 1 flush packet is sent)
+ * @retval AVERROR(EINVAL) codec not opened, it is an encoder, or requires
+ * flush
+ * @retval AVERROR(ENOMEM) failed to add packet to internal queue, or similar
+ * @retval "another negative error code" legitimate decoding errors
+ */
+int avcodec_send_packet(AVCodecContext* avctx, const AVPacket* avpkt);
+
+/**
+ * Return decoded output data from a decoder or encoder (when the
+ * @ref AV_CODEC_FLAG_RECON_FRAME flag is used).
+ *
+ * @param avctx codec context
+ * @param frame This will be set to a reference-counted video or audio
+ * frame (depending on the decoder type) allocated by the
+ * codec. Note that the function will always call
+ * av_frame_unref(frame) before doing anything else.
+ *
+ * @retval 0 success, a frame was returned
+ * @retval AVERROR(EAGAIN) output is not available in this state - user must
+ * try to send new input
+ * @retval AVERROR_EOF the codec has been fully flushed, and there will be
+ * no more output frames
+ * @retval AVERROR(EINVAL) codec not opened, or it is an encoder without the
+ * @ref AV_CODEC_FLAG_RECON_FRAME flag enabled
+ * @retval "other negative error code" legitimate decoding errors
+ */
+int avcodec_receive_frame(AVCodecContext* avctx, AVFrame* frame);
+
+/**
+ * Supply a raw video or audio frame to the encoder. Use
+ * avcodec_receive_packet() to retrieve buffered output packets.
+ *
+ * @param avctx codec context
+ * @param[in] frame AVFrame containing the raw audio or video frame to be
+ * encoded. Ownership of the frame remains with the caller, and the encoder will
+ * not write to the frame. The encoder may create a reference to the frame data
+ * (or copy it if the frame is not reference-counted). It can be NULL, in which
+ * case it is considered a flush packet. This signals the end of the stream. If
+ * the encoder still has packets buffered, it will return them after this call.
+ * Once flushing mode has been entered, additional flush packets are ignored,
+ * and sending frames will return AVERROR_EOF.
+ *
+ * For audio:
+ * If AV_CODEC_CAP_VARIABLE_FRAME_SIZE is set, then each frame
+ * can have any number of samples.
+ * If it is not set, frame->nb_samples must be equal to
+ * avctx->frame_size for all frames except the last.
+ * The final frame may be smaller than avctx->frame_size.
+ * @retval 0 success
+ * @retval AVERROR(EAGAIN) input is not accepted in the current state - user
+ * must read output with avcodec_receive_packet() (once all output is read, the
+ * packet should be resent, and the call will not fail with EAGAIN).
+ * @retval AVERROR_EOF the encoder has been flushed, and no new frames can
+ * be sent to it
+ * @retval AVERROR(EINVAL) codec not opened, it is a decoder, or requires
+ * flush
+ * @retval AVERROR(ENOMEM) failed to add packet to internal queue, or similar
+ * @retval "another negative error code" legitimate encoding errors
+ */
+int avcodec_send_frame(AVCodecContext* avctx, const AVFrame* frame);
+
+/**
+ * Read encoded data from the encoder.
+ *
+ * @param avctx codec context
+ * @param avpkt This will be set to a reference-counted packet allocated by the
+ * encoder. Note that the function will always call
+ * av_packet_unref(avpkt) before doing anything else.
+ * @retval 0 success
+ * @retval AVERROR(EAGAIN) output is not available in the current state - user
+ * must try to send input
+ * @retval AVERROR_EOF the encoder has been fully flushed, and there will be
+ * no more output packets
+ * @retval AVERROR(EINVAL) codec not opened, or it is a decoder
+ * @retval "another negative error code" legitimate encoding errors
+ */
+int avcodec_receive_packet(AVCodecContext* avctx, AVPacket* avpkt);
+
+/**
+ * Create and return a AVHWFramesContext with values adequate for hardware
+ * decoding. This is meant to get called from the get_format callback, and is
+ * a helper for preparing a AVHWFramesContext for AVCodecContext.hw_frames_ctx.
+ * This API is for decoding with certain hardware acceleration modes/APIs only.
+ *
+ * The returned AVHWFramesContext is not initialized. The caller must do this
+ * with av_hwframe_ctx_init().
+ *
+ * Calling this function is not a requirement, but makes it simpler to avoid
+ * codec or hardware API specific details when manually allocating frames.
+ *
+ * Alternatively to this, an API user can set AVCodecContext.hw_device_ctx,
+ * which sets up AVCodecContext.hw_frames_ctx fully automatically, and makes
+ * it unnecessary to call this function or having to care about
+ * AVHWFramesContext initialization at all.
+ *
+ * There are a number of requirements for calling this function:
+ *
+ * - It must be called from get_format with the same avctx parameter that was
+ * passed to get_format. Calling it outside of get_format is not allowed, and
+ * can trigger undefined behavior.
+ * - The function is not always supported (see description of return values).
+ * Even if this function returns successfully, hwaccel initialization could
+ * fail later. (The degree to which implementations check whether the stream
+ * is actually supported varies. Some do this check only after the user's
+ * get_format callback returns.)
+ * - The hw_pix_fmt must be one of the choices suggested by get_format. If the
+ * user decides to use a AVHWFramesContext prepared with this API function,
+ * the user must return the same hw_pix_fmt from get_format.
+ * - The device_ref passed to this function must support the given hw_pix_fmt.
+ * - After calling this API function, it is the user's responsibility to
+ * initialize the AVHWFramesContext (returned by the out_frames_ref
+ * parameter), and to set AVCodecContext.hw_frames_ctx to it. If done, this must
+ * be done before returning from get_format (this is implied by the normal
+ * AVCodecContext.hw_frames_ctx API rules).
+ * - The AVHWFramesContext parameters may change every time time get_format is
+ * called. Also, AVCodecContext.hw_frames_ctx is reset before get_format. So
+ * you are inherently required to go through this process again on every
+ * get_format call.
+ * - It is perfectly possible to call this function without actually using
+ * the resulting AVHWFramesContext. One use-case might be trying to reuse a
+ * previously initialized AVHWFramesContext, and calling this API function
+ * only to test whether the required frame parameters have changed.
+ * - Fields that use dynamically allocated values of any kind must not be set
+ * by the user unless setting them is explicitly allowed by the documentation.
+ * If the user sets AVHWFramesContext.free and AVHWFramesContext.user_opaque,
+ * the new free callback must call the potentially set previous free callback.
+ * This API call may set any dynamically allocated fields, including the free
+ * callback.
+ *
+ * The function will set at least the following fields on AVHWFramesContext
+ * (potentially more, depending on hwaccel API):
+ *
+ * - All fields set by av_hwframe_ctx_alloc().
+ * - Set the format field to hw_pix_fmt.
+ * - Set the sw_format field to the most suited and most versatile format. (An
+ * implication is that this will prefer generic formats over opaque formats
+ * with arbitrary restrictions, if possible.)
+ * - Set the width/height fields to the coded frame size, rounded up to the
+ * API-specific minimum alignment.
+ * - Only _if_ the hwaccel requires a pre-allocated pool: set the
+ * initial_pool_size field to the number of maximum reference surfaces possible
+ * with the codec, plus 1 surface for the user to work (meaning the user can
+ * safely reference at most 1 decoded surface at a time), plus additional
+ * buffering introduced by frame threading. If the hwaccel does not require
+ * pre-allocation, the field is left to 0, and the decoder will allocate new
+ * surfaces on demand during decoding.
+ * - Possibly AVHWFramesContext.hwctx fields, depending on the underlying
+ * hardware API.
+ *
+ * Essentially, out_frames_ref returns the same as av_hwframe_ctx_alloc(), but
+ * with basic frame parameters set.
+ *
+ * The function is stateless, and does not change the AVCodecContext or the
+ * device_ref AVHWDeviceContext.
+ *
+ * @param avctx The context which is currently calling get_format, and which
+ * implicitly contains all state needed for filling the returned
+ * AVHWFramesContext properly.
+ * @param device_ref A reference to the AVHWDeviceContext describing the device
+ * which will be used by the hardware decoder.
+ * @param hw_pix_fmt The hwaccel format you are going to return from get_format.
+ * @param out_frames_ref On success, set to a reference to an _uninitialized_
+ * AVHWFramesContext, created from the given device_ref.
+ * Fields will be set to values required for decoding.
+ * Not changed if an error is returned.
+ * @return zero on success, a negative value on error. The following error codes
+ * have special semantics:
+ * AVERROR(ENOENT): the decoder does not support this functionality. Setup
+ * is always manual, or it is a decoder which does not
+ * support setting AVCodecContext.hw_frames_ctx at all,
+ * or it is a software format.
+ * AVERROR(EINVAL): it is known that hardware decoding is not supported for
+ * this configuration, or the device_ref is not supported
+ * for the hwaccel referenced by hw_pix_fmt.
+ */
+int avcodec_get_hw_frames_parameters(AVCodecContext* avctx,
+ AVBufferRef* device_ref,
+ enum AVPixelFormat hw_pix_fmt,
+ AVBufferRef** out_frames_ref);
+
+/**
+ * @defgroup lavc_parsing Frame parsing
+ * @{
+ */
+
+enum AVPictureStructure {
+ AV_PICTURE_STRUCTURE_UNKNOWN, ///< unknown
+ AV_PICTURE_STRUCTURE_TOP_FIELD, ///< coded as top field
+ AV_PICTURE_STRUCTURE_BOTTOM_FIELD, ///< coded as bottom field
+ AV_PICTURE_STRUCTURE_FRAME, ///< coded as frame
+};
+
+typedef struct AVCodecParserContext {
+ void* priv_data;
+ const struct AVCodecParser* parser;
+ int64_t frame_offset; /* offset of the current frame */
+ int64_t cur_offset; /* current offset
+ (incremented by each av_parser_parse()) */
+ int64_t next_frame_offset; /* offset of the next frame */
+ /* video info */
+ int pict_type; /* XXX: Put it back in AVCodecContext. */
+ /**
+ * This field is used for proper frame duration computation in lavf.
+ * It signals, how much longer the frame duration of the current frame
+ * is compared to normal frame duration.
+ *
+ * frame_duration = (1 + repeat_pict) * time_base
+ *
+ * It is used by codecs like H.264 to display telecined material.
+ */
+ int repeat_pict; /* XXX: Put it back in AVCodecContext. */
+ int64_t pts; /* pts of the current frame */
+ int64_t dts; /* dts of the current frame */
+
+ /* private data */
+ int64_t last_pts;
+ int64_t last_dts;
+ int fetch_timestamp;
+
+#define AV_PARSER_PTS_NB 4
+ int cur_frame_start_index;
+ int64_t cur_frame_offset[AV_PARSER_PTS_NB];
+ int64_t cur_frame_pts[AV_PARSER_PTS_NB];
+ int64_t cur_frame_dts[AV_PARSER_PTS_NB];
+
+ int flags;
+#define PARSER_FLAG_COMPLETE_FRAMES 0x0001
+#define PARSER_FLAG_ONCE 0x0002
+/// Set if the parser has a valid file offset
+#define PARSER_FLAG_FETCHED_OFFSET 0x0004
+#define PARSER_FLAG_USE_CODEC_TS 0x1000
+
+ int64_t offset; ///< byte offset from starting packet start
+ int64_t cur_frame_end[AV_PARSER_PTS_NB];
+
+ /**
+ * Set by parser to 1 for key frames and 0 for non-key frames.
+ * It is initialized to -1, so if the parser doesn't set this flag,
+ * old-style fallback using AV_PICTURE_TYPE_I picture type as key frames
+ * will be used.
+ */
+ int key_frame;
+
+ // Timestamp generation support:
+ /**
+ * Synchronization point for start of timestamp generation.
+ *
+ * Set to >0 for sync point, 0 for no sync point and <0 for undefined
+ * (default).
+ *
+ * For example, this corresponds to presence of H.264 buffering period
+ * SEI message.
+ */
+ int dts_sync_point;
+
+ /**
+ * Offset of the current timestamp against last timestamp sync point in
+ * units of AVCodecContext.time_base.
+ *
+ * Set to INT_MIN when dts_sync_point unused. Otherwise, it must
+ * contain a valid timestamp offset.
+ *
+ * Note that the timestamp of sync point has usually a nonzero
+ * dts_ref_dts_delta, which refers to the previous sync point. Offset of
+ * the next frame after timestamp sync point will be usually 1.
+ *
+ * For example, this corresponds to H.264 cpb_removal_delay.
+ */
+ int dts_ref_dts_delta;
+
+ /**
+ * Presentation delay of current frame in units of AVCodecContext.time_base.
+ *
+ * Set to INT_MIN when dts_sync_point unused. Otherwise, it must
+ * contain valid non-negative timestamp delta (presentation time of a frame
+ * must not lie in the past).
+ *
+ * This delay represents the difference between decoding and presentation
+ * time of the frame.
+ *
+ * For example, this corresponds to H.264 dpb_output_delay.
+ */
+ int pts_dts_delta;
+
+ /**
+ * Position of the packet in file.
+ *
+ * Analogous to cur_frame_pts/dts
+ */
+ int64_t cur_frame_pos[AV_PARSER_PTS_NB];
+
+ /**
+ * Byte position of currently parsed frame in stream.
+ */
+ int64_t pos;
+
+ /**
+ * Previous frame byte position.
+ */
+ int64_t last_pos;
+
+ /**
+ * Duration of the current frame.
+ * For audio, this is in units of 1 / AVCodecContext.sample_rate.
+ * For all other types, this is in units of AVCodecContext.time_base.
+ */
+ int duration;
+
+ enum AVFieldOrder field_order;
+
+ /**
+ * Indicate whether a picture is coded as a frame, top field or bottom field.
+ *
+ * For example, H.264 field_pic_flag equal to 0 corresponds to
+ * AV_PICTURE_STRUCTURE_FRAME. An H.264 picture with field_pic_flag
+ * equal to 1 and bottom_field_flag equal to 0 corresponds to
+ * AV_PICTURE_STRUCTURE_TOP_FIELD.
+ */
+ enum AVPictureStructure picture_structure;
+
+ /**
+ * Picture number incremented in presentation or output order.
+ * This field may be reinitialized at the first picture of a new sequence.
+ *
+ * For example, this corresponds to H.264 PicOrderCnt.
+ */
+ int output_picture_number;
+
+ /**
+ * Dimensions of the decoded video intended for presentation.
+ */
+ int width;
+ int height;
+
+ /**
+ * Dimensions of the coded video.
+ */
+ int coded_width;
+ int coded_height;
+
+ /**
+ * The format of the coded data, corresponds to enum AVPixelFormat for video
+ * and for enum AVSampleFormat for audio.
+ *
+ * Note that a decoder can have considerable freedom in how exactly it
+ * decodes the data, so the format reported here might be different from the
+ * one returned by a decoder.
+ */
+ int format;
+} AVCodecParserContext;
+
+typedef struct AVCodecParser {
+ int codec_ids[7]; /* several codec IDs are permitted */
+ int priv_data_size;
+ int (*parser_init)(AVCodecParserContext* s);
+ /* This callback never returns an error, a negative value means that
+ * the frame start was in a previous packet. */
+ int (*parser_parse)(AVCodecParserContext* s, AVCodecContext* avctx,
+ const uint8_t** poutbuf, int* poutbuf_size,
+ const uint8_t* buf, int buf_size);
+ void (*parser_close)(AVCodecParserContext* s);
+ int (*split)(AVCodecContext* avctx, const uint8_t* buf, int buf_size);
+} AVCodecParser;
+
+/**
+ * Iterate over all registered codec parsers.
+ *
+ * @param opaque a pointer where libavcodec will store the iteration state. Must
+ * point to NULL to start the iteration.
+ *
+ * @return the next registered codec parser or NULL when the iteration is
+ * finished
+ */
+const AVCodecParser* av_parser_iterate(void** opaque);
+
+AVCodecParserContext* av_parser_init(int codec_id);
+
+/**
+ * Parse a packet.
+ *
+ * @param s parser context.
+ * @param avctx codec context.
+ * @param poutbuf set to pointer to parsed buffer or NULL if not yet
+ finished.
+ * @param poutbuf_size set to size of parsed buffer or zero if not yet
+ finished.
+ * @param buf input buffer.
+ * @param buf_size buffer size in bytes without the padding. I.e. the full
+ buffer size is assumed to be buf_size + AV_INPUT_BUFFER_PADDING_SIZE. To signal
+ EOF, this should be 0 (so that the last frame can be output).
+ * @param pts input presentation timestamp.
+ * @param dts input decoding timestamp.
+ * @param pos input byte position in stream.
+ * @return the number of bytes of the input bitstream used.
+ *
+ * Example:
+ * @code
+ * while(in_len){
+ * len = av_parser_parse2(myparser, AVCodecContext, &data, &size,
+ * in_data, in_len,
+ * pts, dts, pos);
+ * in_data += len;
+ * in_len -= len;
+ *
+ * if(size)
+ * decode_frame(data, size);
+ * }
+ * @endcode
+ */
+int av_parser_parse2(AVCodecParserContext* s, AVCodecContext* avctx,
+ uint8_t** poutbuf, int* poutbuf_size, const uint8_t* buf,
+ int buf_size, int64_t pts, int64_t dts, int64_t pos);
+
+void av_parser_close(AVCodecParserContext* s);
+
+/**
+ * @}
+ * @}
+ */
+
+/**
+ * @addtogroup lavc_encoding
+ * @{
+ */
+
+int avcodec_encode_subtitle(AVCodecContext* avctx, uint8_t* buf, int buf_size,
+ const AVSubtitle* sub);
+
+/**
+ * @}
+ */
+
+/**
+ * @defgroup lavc_misc Utility functions
+ * @ingroup libavc
+ *
+ * Miscellaneous utility functions related to both encoding and decoding
+ * (or neither).
+ * @{
+ */
+
+/**
+ * @defgroup lavc_misc_pixfmt Pixel formats
+ *
+ * Functions for working with pixel formats.
+ * @{
+ */
+
+/**
+ * Return a value representing the fourCC code associated to the
+ * pixel format pix_fmt, or 0 if no associated fourCC code can be
+ * found.
+ */
+unsigned int avcodec_pix_fmt_to_codec_tag(enum AVPixelFormat pix_fmt);
+
+/**
+ * Find the best pixel format to convert to given a certain source pixel
+ * format. When converting from one pixel format to another, information loss
+ * may occur. For example, when converting from RGB24 to GRAY, the color
+ * information will be lost. Similarly, other losses occur when converting from
+ * some formats to other formats. avcodec_find_best_pix_fmt_of_2() searches
+ * which of the given pixel formats should be used to suffer the least amount of
+ * loss. The pixel formats from which it chooses one, are determined by the
+ * pix_fmt_list parameter.
+ *
+ *
+ * @param[in] pix_fmt_list AV_PIX_FMT_NONE terminated array of pixel formats to
+ * choose from
+ * @param[in] src_pix_fmt source pixel format
+ * @param[in] has_alpha Whether the source pixel format alpha channel is used.
+ * @param[out] loss_ptr Combination of flags informing you what kind of losses
+ * will occur.
+ * @return The best pixel format to convert to or -1 if none was found.
+ */
+enum AVPixelFormat avcodec_find_best_pix_fmt_of_list(
+ const enum AVPixelFormat* pix_fmt_list, enum AVPixelFormat src_pix_fmt,
+ int has_alpha, int* loss_ptr);
+
+enum AVPixelFormat avcodec_default_get_format(struct AVCodecContext* s,
+ const enum AVPixelFormat* fmt);
+
+/**
+ * @}
+ */
+
+void avcodec_string(char* buf, int buf_size, AVCodecContext* enc, int encode);
+
+int avcodec_default_execute(AVCodecContext* c,
+ int (*func)(AVCodecContext* c2, void* arg2),
+ void* arg, int* ret, int count, int size);
+int avcodec_default_execute2(AVCodecContext* c,
+ int (*func)(AVCodecContext* c2, void* arg2, int,
+ int),
+ void* arg, int* ret, int count);
+// FIXME func typedef
+
+/**
+ * Fill AVFrame audio data and linesize pointers.
+ *
+ * The buffer buf must be a preallocated buffer with a size big enough
+ * to contain the specified samples amount. The filled AVFrame data
+ * pointers will point to this buffer.
+ *
+ * AVFrame extended_data channel pointers are allocated if necessary for
+ * planar audio.
+ *
+ * @param frame the AVFrame
+ * frame->nb_samples must be set prior to calling the
+ * function. This function fills in frame->data,
+ * frame->extended_data, frame->linesize[0].
+ * @param nb_channels channel count
+ * @param sample_fmt sample format
+ * @param buf buffer to use for frame data
+ * @param buf_size size of buffer
+ * @param align plane size sample alignment (0 = default)
+ * @return >=0 on success, negative error code on failure
+ * @todo return the size in bytes required to store the samples in
+ * case of success, at the next libavutil bump
+ */
+int avcodec_fill_audio_frame(AVFrame* frame, int nb_channels,
+ enum AVSampleFormat sample_fmt, const uint8_t* buf,
+ int buf_size, int align);
+
+/**
+ * Reset the internal codec state / flush internal buffers. Should be called
+ * e.g. when seeking or when switching to a different stream.
+ *
+ * @note for decoders, this function just releases any references the decoder
+ * might keep internally, but the caller's references remain valid.
+ *
+ * @note for encoders, this function will only do something if the encoder
+ * declares support for AV_CODEC_CAP_ENCODER_FLUSH. When called, the encoder
+ * will drain any remaining packets, and can then be re-used for a different
+ * stream (as opposed to sending a null frame which will leave the encoder
+ * in a permanent EOF state after draining). This can be desirable if the
+ * cost of tearing down and replacing the encoder instance is high.
+ */
+void avcodec_flush_buffers(AVCodecContext* avctx);
+
+/**
+ * Return audio frame duration.
+ *
+ * @param avctx codec context
+ * @param frame_bytes size of the frame, or 0 if unknown
+ * @return frame duration, in samples, if known. 0 if not able to
+ * determine.
+ */
+int av_get_audio_frame_duration(AVCodecContext* avctx, int frame_bytes);
+
+/* memory */
+
+/**
+ * Same behaviour av_fast_malloc but the buffer has additional
+ * AV_INPUT_BUFFER_PADDING_SIZE at the end which will always be 0.
+ *
+ * In addition the whole buffer will initially and after resizes
+ * be 0-initialized so that no uninitialized data will ever appear.
+ */
+void av_fast_padded_malloc(void* ptr, unsigned int* size, size_t min_size);
+
+/**
+ * Same behaviour av_fast_padded_malloc except that buffer will always
+ * be 0-initialized after call.
+ */
+void av_fast_padded_mallocz(void* ptr, unsigned int* size, size_t min_size);
+
+/**
+ * @return a positive value if s is open (i.e. avcodec_open2() was called on it
+ * with no corresponding avcodec_close()), 0 otherwise.
+ */
+int avcodec_is_open(AVCodecContext* s);
+
+/**
+ * @}
+ */
+
+#endif /* AVCODEC_AVCODEC_H */
diff --git a/dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/avdct.h b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/avdct.h
new file mode 100644
index 0000000000..9edf4c187e
--- /dev/null
+++ b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/avdct.h
@@ -0,0 +1,85 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVCODEC_AVDCT_H
+#define AVCODEC_AVDCT_H
+
+#include "libavutil/opt.h"
+
+/**
+ * AVDCT context.
+ * @note function pointers can be NULL if the specific features have been
+ * disabled at build time.
+ */
+typedef struct AVDCT {
+ const AVClass* av_class;
+
+ void (*idct)(int16_t* block /* align 16 */);
+
+ /**
+ * IDCT input permutation.
+ * Several optimized IDCTs need a permutated input (relative to the
+ * normal order of the reference IDCT).
+ * This permutation must be performed before the idct_put/add.
+ * Note, normally this can be merged with the zigzag/alternate scan<br>
+ * An example to avoid confusion:
+ * - (->decode coeffs -> zigzag reorder -> dequant -> reference IDCT -> ...)
+ * - (x -> reference DCT -> reference IDCT -> x)
+ * - (x -> reference DCT -> simple_mmx_perm = idct_permutation
+ * -> simple_idct_mmx -> x)
+ * - (-> decode coeffs -> zigzag reorder -> simple_mmx_perm -> dequant
+ * -> simple_idct_mmx -> ...)
+ */
+ uint8_t idct_permutation[64];
+
+ void (*fdct)(int16_t* block /* align 16 */);
+
+ /**
+ * DCT algorithm.
+ * must use AVOptions to set this field.
+ */
+ int dct_algo;
+
+ /**
+ * IDCT algorithm.
+ * must use AVOptions to set this field.
+ */
+ int idct_algo;
+
+ void (*get_pixels)(int16_t* block /* align 16 */,
+ const uint8_t* pixels /* align 8 */, ptrdiff_t line_size);
+
+ int bits_per_sample;
+
+ void (*get_pixels_unaligned)(int16_t* block /* align 16 */,
+ const uint8_t* pixels, ptrdiff_t line_size);
+} AVDCT;
+
+/**
+ * Allocates a AVDCT context.
+ * This needs to be initialized with avcodec_dct_init() after optionally
+ * configuring it with AVOptions.
+ *
+ * To free it use av_free()
+ */
+AVDCT* avcodec_dct_alloc(void);
+int avcodec_dct_init(AVDCT*);
+
+const AVClass* avcodec_dct_get_class(void);
+
+#endif /* AVCODEC_AVDCT_H */
diff --git a/dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/bsf.h b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/bsf.h
new file mode 100644
index 0000000000..044a0597bf
--- /dev/null
+++ b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/bsf.h
@@ -0,0 +1,335 @@
+/*
+ * Bitstream filters public API
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVCODEC_BSF_H
+#define AVCODEC_BSF_H
+
+#include "libavutil/dict.h"
+#include "libavutil/log.h"
+#include "libavutil/rational.h"
+
+#include "codec_id.h"
+#include "codec_par.h"
+#include "packet.h"
+
+/**
+ * @defgroup lavc_bsf Bitstream filters
+ * @ingroup libavc
+ *
+ * Bitstream filters transform encoded media data without decoding it. This
+ * allows e.g. manipulating various header values. Bitstream filters operate on
+ * @ref AVPacket "AVPackets".
+ *
+ * The bitstream filtering API is centered around two structures:
+ * AVBitStreamFilter and AVBSFContext. The former represents a bitstream filter
+ * in abstract, the latter a specific filtering process. Obtain an
+ * AVBitStreamFilter using av_bsf_get_by_name() or av_bsf_iterate(), then pass
+ * it to av_bsf_alloc() to create an AVBSFContext. Fill in the user-settable
+ * AVBSFContext fields, as described in its documentation, then call
+ * av_bsf_init() to prepare the filter context for use.
+ *
+ * Submit packets for filtering using av_bsf_send_packet(), obtain filtered
+ * results with av_bsf_receive_packet(). When no more input packets will be
+ * sent, submit a NULL AVPacket to signal the end of the stream to the filter.
+ * av_bsf_receive_packet() will then return trailing packets, if any are
+ * produced by the filter.
+ *
+ * Finally, free the filter context with av_bsf_free().
+ * @{
+ */
+
+/**
+ * The bitstream filter state.
+ *
+ * This struct must be allocated with av_bsf_alloc() and freed with
+ * av_bsf_free().
+ *
+ * The fields in the struct will only be changed (by the caller or by the
+ * filter) as described in their documentation, and are to be considered
+ * immutable otherwise.
+ */
+typedef struct AVBSFContext {
+ /**
+ * A class for logging and AVOptions
+ */
+ const AVClass* av_class;
+
+ /**
+ * The bitstream filter this context is an instance of.
+ */
+ const struct AVBitStreamFilter* filter;
+
+ /**
+ * Opaque filter-specific private data. If filter->priv_class is non-NULL,
+ * this is an AVOptions-enabled struct.
+ */
+ void* priv_data;
+
+ /**
+ * Parameters of the input stream. This field is allocated in
+ * av_bsf_alloc(), it needs to be filled by the caller before
+ * av_bsf_init().
+ */
+ AVCodecParameters* par_in;
+
+ /**
+ * Parameters of the output stream. This field is allocated in
+ * av_bsf_alloc(), it is set by the filter in av_bsf_init().
+ */
+ AVCodecParameters* par_out;
+
+ /**
+ * The timebase used for the timestamps of the input packets. Set by the
+ * caller before av_bsf_init().
+ */
+ AVRational time_base_in;
+
+ /**
+ * The timebase used for the timestamps of the output packets. Set by the
+ * filter in av_bsf_init().
+ */
+ AVRational time_base_out;
+} AVBSFContext;
+
+typedef struct AVBitStreamFilter {
+ const char* name;
+
+ /**
+ * A list of codec ids supported by the filter, terminated by
+ * AV_CODEC_ID_NONE.
+ * May be NULL, in that case the bitstream filter works with any codec id.
+ */
+ const enum AVCodecID* codec_ids;
+
+ /**
+ * A class for the private data, used to declare bitstream filter private
+ * AVOptions. This field is NULL for bitstream filters that do not declare
+ * any options.
+ *
+ * If this field is non-NULL, the first member of the filter private data
+ * must be a pointer to AVClass, which will be set by libavcodec generic
+ * code to this class.
+ */
+ const AVClass* priv_class;
+} AVBitStreamFilter;
+
+/**
+ * @return a bitstream filter with the specified name or NULL if no such
+ * bitstream filter exists.
+ */
+const AVBitStreamFilter* av_bsf_get_by_name(const char* name);
+
+/**
+ * Iterate over all registered bitstream filters.
+ *
+ * @param opaque a pointer where libavcodec will store the iteration state. Must
+ * point to NULL to start the iteration.
+ *
+ * @return the next registered bitstream filter or NULL when the iteration is
+ * finished
+ */
+const AVBitStreamFilter* av_bsf_iterate(void** opaque);
+
+/**
+ * Allocate a context for a given bitstream filter. The caller must fill in the
+ * context parameters as described in the documentation and then call
+ * av_bsf_init() before sending any data to the filter.
+ *
+ * @param filter the filter for which to allocate an instance.
+ * @param[out] ctx a pointer into which the pointer to the newly-allocated
+ * context will be written. It must be freed with av_bsf_free() after the
+ * filtering is done.
+ *
+ * @return 0 on success, a negative AVERROR code on failure
+ */
+int av_bsf_alloc(const AVBitStreamFilter* filter, AVBSFContext** ctx);
+
+/**
+ * Prepare the filter for use, after all the parameters and options have been
+ * set.
+ *
+ * @param ctx a AVBSFContext previously allocated with av_bsf_alloc()
+ */
+int av_bsf_init(AVBSFContext* ctx);
+
+/**
+ * Submit a packet for filtering.
+ *
+ * After sending each packet, the filter must be completely drained by calling
+ * av_bsf_receive_packet() repeatedly until it returns AVERROR(EAGAIN) or
+ * AVERROR_EOF.
+ *
+ * @param ctx an initialized AVBSFContext
+ * @param pkt the packet to filter. The bitstream filter will take ownership of
+ * the packet and reset the contents of pkt. pkt is not touched if an error
+ * occurs. If pkt is empty (i.e. NULL, or pkt->data is NULL and
+ * pkt->side_data_elems zero), it signals the end of the stream (i.e. no more
+ * non-empty packets will be sent; sending more empty packets does nothing) and
+ * will cause the filter to output any packets it may have buffered internally.
+ *
+ * @return
+ * - 0 on success.
+ * - AVERROR(EAGAIN) if packets need to be retrieved from the filter (using
+ * av_bsf_receive_packet()) before new input can be consumed.
+ * - Another negative AVERROR value if an error occurs.
+ */
+int av_bsf_send_packet(AVBSFContext* ctx, AVPacket* pkt);
+
+/**
+ * Retrieve a filtered packet.
+ *
+ * @param ctx an initialized AVBSFContext
+ * @param[out] pkt this struct will be filled with the contents of the filtered
+ * packet. It is owned by the caller and must be freed using
+ * av_packet_unref() when it is no longer needed.
+ * This parameter should be "clean" (i.e. freshly allocated
+ * with av_packet_alloc() or unreffed with av_packet_unref())
+ * when this function is called. If this function returns
+ * successfully, the contents of pkt will be completely
+ * overwritten by the returned data. On failure, pkt is not
+ * touched.
+ *
+ * @return
+ * - 0 on success.
+ * - AVERROR(EAGAIN) if more packets need to be sent to the filter (using
+ * av_bsf_send_packet()) to get more output.
+ * - AVERROR_EOF if there will be no further output from the filter.
+ * - Another negative AVERROR value if an error occurs.
+ *
+ * @note one input packet may result in several output packets, so after sending
+ * a packet with av_bsf_send_packet(), this function needs to be called
+ * repeatedly until it stops returning 0. It is also possible for a filter to
+ * output fewer packets than were sent to it, so this function may return
+ * AVERROR(EAGAIN) immediately after a successful av_bsf_send_packet() call.
+ */
+int av_bsf_receive_packet(AVBSFContext* ctx, AVPacket* pkt);
+
+/**
+ * Reset the internal bitstream filter state. Should be called e.g. when
+ * seeking.
+ */
+void av_bsf_flush(AVBSFContext* ctx);
+
+/**
+ * Free a bitstream filter context and everything associated with it; write NULL
+ * into the supplied pointer.
+ */
+void av_bsf_free(AVBSFContext** ctx);
+
+/**
+ * Get the AVClass for AVBSFContext. It can be used in combination with
+ * AV_OPT_SEARCH_FAKE_OBJ for examining options.
+ *
+ * @see av_opt_find().
+ */
+const AVClass* av_bsf_get_class(void);
+
+/**
+ * Structure for chain/list of bitstream filters.
+ * Empty list can be allocated by av_bsf_list_alloc().
+ */
+typedef struct AVBSFList AVBSFList;
+
+/**
+ * Allocate empty list of bitstream filters.
+ * The list must be later freed by av_bsf_list_free()
+ * or finalized by av_bsf_list_finalize().
+ *
+ * @return Pointer to @ref AVBSFList on success, NULL in case of failure
+ */
+AVBSFList* av_bsf_list_alloc(void);
+
+/**
+ * Free list of bitstream filters.
+ *
+ * @param lst Pointer to pointer returned by av_bsf_list_alloc()
+ */
+void av_bsf_list_free(AVBSFList** lst);
+
+/**
+ * Append bitstream filter to the list of bitstream filters.
+ *
+ * @param lst List to append to
+ * @param bsf Filter context to be appended
+ *
+ * @return >=0 on success, negative AVERROR in case of failure
+ */
+int av_bsf_list_append(AVBSFList* lst, AVBSFContext* bsf);
+
+/**
+ * Construct new bitstream filter context given it's name and options
+ * and append it to the list of bitstream filters.
+ *
+ * @param lst List to append to
+ * @param bsf_name Name of the bitstream filter
+ * @param options Options for the bitstream filter, can be set to NULL
+ *
+ * @return >=0 on success, negative AVERROR in case of failure
+ */
+int av_bsf_list_append2(AVBSFList* lst, const char* bsf_name,
+ AVDictionary** options);
+/**
+ * Finalize list of bitstream filters.
+ *
+ * This function will transform @ref AVBSFList to single @ref AVBSFContext,
+ * so the whole chain of bitstream filters can be treated as single filter
+ * freshly allocated by av_bsf_alloc().
+ * If the call is successful, @ref AVBSFList structure is freed and lst
+ * will be set to NULL. In case of failure, caller is responsible for
+ * freeing the structure by av_bsf_list_free()
+ *
+ * @param lst Filter list structure to be transformed
+ * @param[out] bsf Pointer to be set to newly created @ref AVBSFContext
+ * structure representing the chain of bitstream filters
+ *
+ * @return >=0 on success, negative AVERROR in case of failure
+ */
+int av_bsf_list_finalize(AVBSFList** lst, AVBSFContext** bsf);
+
+/**
+ * Parse string describing list of bitstream filters and create single
+ * @ref AVBSFContext describing the whole chain of bitstream filters.
+ * Resulting @ref AVBSFContext can be treated as any other @ref AVBSFContext
+ * freshly allocated by av_bsf_alloc().
+ *
+ * @param str String describing chain of bitstream filters in format
+ * `bsf1[=opt1=val1:opt2=val2][,bsf2]`
+ * @param[out] bsf Pointer to be set to newly created @ref AVBSFContext
+ * structure representing the chain of bitstream filters
+ *
+ * @return >=0 on success, negative AVERROR in case of failure
+ */
+int av_bsf_list_parse_str(const char* str, AVBSFContext** bsf);
+
+/**
+ * Get null/pass-through bitstream filter.
+ *
+ * @param[out] bsf Pointer to be set to new instance of pass-through bitstream
+ * filter
+ *
+ * @return
+ */
+int av_bsf_get_null_filter(AVBSFContext** bsf);
+
+/**
+ * @}
+ */
+
+#endif // AVCODEC_BSF_H
diff --git a/dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/codec.h b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/codec.h
new file mode 100644
index 0000000000..7163d91d96
--- /dev/null
+++ b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/codec.h
@@ -0,0 +1,382 @@
+/*
+ * AVCodec public API
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVCODEC_CODEC_H
+#define AVCODEC_CODEC_H
+
+#include <stdint.h>
+
+#include "libavutil/avutil.h"
+#include "libavutil/hwcontext.h"
+#include "libavutil/log.h"
+#include "libavutil/pixfmt.h"
+#include "libavutil/rational.h"
+#include "libavutil/samplefmt.h"
+
+#include "libavcodec/codec_id.h"
+#include "libavcodec/version_major.h"
+
+/**
+ * @addtogroup lavc_core
+ * @{
+ */
+
+/**
+ * Decoder can use draw_horiz_band callback.
+ */
+#define AV_CODEC_CAP_DRAW_HORIZ_BAND (1 << 0)
+/**
+ * Codec uses get_buffer() or get_encode_buffer() for allocating buffers and
+ * supports custom allocators.
+ * If not set, it might not use get_buffer() or get_encode_buffer() at all, or
+ * use operations that assume the buffer was allocated by
+ * avcodec_default_get_buffer2 or avcodec_default_get_encode_buffer.
+ */
+#define AV_CODEC_CAP_DR1 (1 << 1)
+/**
+ * Encoder or decoder requires flushing with NULL input at the end in order to
+ * give the complete and correct output.
+ *
+ * NOTE: If this flag is not set, the codec is guaranteed to never be fed with
+ * with NULL data. The user can still send NULL data to the public encode
+ * or decode function, but libavcodec will not pass it along to the codec
+ * unless this flag is set.
+ *
+ * Decoders:
+ * The decoder has a non-zero delay and needs to be fed with avpkt->data=NULL,
+ * avpkt->size=0 at the end to get the delayed data until the decoder no longer
+ * returns frames.
+ *
+ * Encoders:
+ * The encoder needs to be fed with NULL data at the end of encoding until the
+ * encoder no longer returns data.
+ *
+ * NOTE: For encoders implementing the AVCodec.encode2() function, setting this
+ * flag also means that the encoder must set the pts and duration for
+ * each output packet. If this flag is not set, the pts and duration will
+ * be determined by libavcodec from the input frame.
+ */
+#define AV_CODEC_CAP_DELAY (1 << 5)
+/**
+ * Codec can be fed a final frame with a smaller size.
+ * This can be used to prevent truncation of the last audio samples.
+ */
+#define AV_CODEC_CAP_SMALL_LAST_FRAME (1 << 6)
+
+#if FF_API_SUBFRAMES
+/**
+ * Codec can output multiple frames per AVPacket
+ * Normally demuxers return one frame at a time, demuxers which do not do
+ * are connected to a parser to split what they return into proper frames.
+ * This flag is reserved to the very rare category of codecs which have a
+ * bitstream that cannot be split into frames without timeconsuming
+ * operations like full decoding. Demuxers carrying such bitstreams thus
+ * may return multiple frames in a packet. This has many disadvantages like
+ * prohibiting stream copy in many cases thus it should only be considered
+ * as a last resort.
+ */
+# define AV_CODEC_CAP_SUBFRAMES (1 << 8)
+#endif
+
+/**
+ * Codec is experimental and is thus avoided in favor of non experimental
+ * encoders
+ */
+#define AV_CODEC_CAP_EXPERIMENTAL (1 << 9)
+/**
+ * Codec should fill in channel configuration and samplerate instead of
+ * container
+ */
+#define AV_CODEC_CAP_CHANNEL_CONF (1 << 10)
+/**
+ * Codec supports frame-level multithreading.
+ */
+#define AV_CODEC_CAP_FRAME_THREADS (1 << 12)
+/**
+ * Codec supports slice-based (or partition-based) multithreading.
+ */
+#define AV_CODEC_CAP_SLICE_THREADS (1 << 13)
+/**
+ * Codec supports changed parameters at any point.
+ */
+#define AV_CODEC_CAP_PARAM_CHANGE (1 << 14)
+/**
+ * Codec supports multithreading through a method other than slice- or
+ * frame-level multithreading. Typically this marks wrappers around
+ * multithreading-capable external libraries.
+ */
+#define AV_CODEC_CAP_OTHER_THREADS (1 << 15)
+/**
+ * Audio encoder supports receiving a different number of samples in each call.
+ */
+#define AV_CODEC_CAP_VARIABLE_FRAME_SIZE (1 << 16)
+/**
+ * Decoder is not a preferred choice for probing.
+ * This indicates that the decoder is not a good choice for probing.
+ * It could for example be an expensive to spin up hardware decoder,
+ * or it could simply not provide a lot of useful information about
+ * the stream.
+ * A decoder marked with this flag should only be used as last resort
+ * choice for probing.
+ */
+#define AV_CODEC_CAP_AVOID_PROBING (1 << 17)
+
+/**
+ * Codec is backed by a hardware implementation. Typically used to
+ * identify a non-hwaccel hardware decoder. For information about hwaccels, use
+ * avcodec_get_hw_config() instead.
+ */
+#define AV_CODEC_CAP_HARDWARE (1 << 18)
+
+/**
+ * Codec is potentially backed by a hardware implementation, but not
+ * necessarily. This is used instead of AV_CODEC_CAP_HARDWARE, if the
+ * implementation provides some sort of internal fallback.
+ */
+#define AV_CODEC_CAP_HYBRID (1 << 19)
+
+/**
+ * This encoder can reorder user opaque values from input AVFrames and return
+ * them with corresponding output packets.
+ * @see AV_CODEC_FLAG_COPY_OPAQUE
+ */
+#define AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE (1 << 20)
+
+/**
+ * This encoder can be flushed using avcodec_flush_buffers(). If this flag is
+ * not set, the encoder must be closed and reopened to ensure that no frames
+ * remain pending.
+ */
+#define AV_CODEC_CAP_ENCODER_FLUSH (1 << 21)
+
+/**
+ * The encoder is able to output reconstructed frame data, i.e. raw frames that
+ * would be produced by decoding the encoded bitstream.
+ *
+ * Reconstructed frame output is enabled by the AV_CODEC_FLAG_RECON_FRAME flag.
+ */
+#define AV_CODEC_CAP_ENCODER_RECON_FRAME (1 << 22)
+
+/**
+ * AVProfile.
+ */
+typedef struct AVProfile {
+ int profile;
+ const char* name; ///< short name for the profile
+} AVProfile;
+
+/**
+ * AVCodec.
+ */
+typedef struct AVCodec {
+ /**
+ * Name of the codec implementation.
+ * The name is globally unique among encoders and among decoders (but an
+ * encoder and a decoder can share the same name).
+ * This is the primary way to find a codec from the user perspective.
+ */
+ const char* name;
+ /**
+ * Descriptive name for the codec, meant to be more human readable than name.
+ * You should use the NULL_IF_CONFIG_SMALL() macro to define it.
+ */
+ const char* long_name;
+ enum AVMediaType type;
+ enum AVCodecID id;
+ /**
+ * Codec capabilities.
+ * see AV_CODEC_CAP_*
+ */
+ int capabilities;
+ uint8_t max_lowres; ///< maximum value for lowres supported by the decoder
+ const AVRational*
+ supported_framerates; ///< array of supported framerates, or NULL if any,
+ ///< array is terminated by {0,0}
+ const enum AVPixelFormat*
+ pix_fmts; ///< array of supported pixel formats, or NULL if unknown,
+ ///< array is terminated by -1
+ const int*
+ supported_samplerates; ///< array of supported audio samplerates, or NULL
+ ///< if unknown, array is terminated by 0
+ const enum AVSampleFormat*
+ sample_fmts; ///< array of supported sample formats, or NULL if unknown,
+ ///< array is terminated by -1
+ const AVClass* priv_class; ///< AVClass for the private context
+ const AVProfile*
+ profiles; ///< array of recognized profiles, or NULL if unknown, array is
+ ///< terminated by {AV_PROFILE_UNKNOWN}
+
+ /**
+ * Group name of the codec implementation.
+ * This is a short symbolic name of the wrapper backing this codec. A
+ * wrapper uses some kind of external implementation for the codec, such
+ * as an external library, or a codec implementation provided by the OS or
+ * the hardware.
+ * If this field is NULL, this is a builtin, libavcodec native codec.
+ * If non-NULL, this will be the suffix in AVCodec.name in most cases
+ * (usually AVCodec.name will be of the form "<codec_name>_<wrapper_name>").
+ */
+ const char* wrapper_name;
+
+ /**
+ * Array of supported channel layouts, terminated with a zeroed layout.
+ */
+ const AVChannelLayout* ch_layouts;
+} AVCodec;
+
+/**
+ * Iterate over all registered codecs.
+ *
+ * @param opaque a pointer where libavcodec will store the iteration state. Must
+ * point to NULL to start the iteration.
+ *
+ * @return the next registered codec or NULL when the iteration is
+ * finished
+ */
+const AVCodec* av_codec_iterate(void** opaque);
+
+/**
+ * Find a registered decoder with a matching codec ID.
+ *
+ * @param id AVCodecID of the requested decoder
+ * @return A decoder if one was found, NULL otherwise.
+ */
+const AVCodec* avcodec_find_decoder(enum AVCodecID id);
+
+/**
+ * Find a registered decoder with the specified name.
+ *
+ * @param name name of the requested decoder
+ * @return A decoder if one was found, NULL otherwise.
+ */
+const AVCodec* avcodec_find_decoder_by_name(const char* name);
+
+/**
+ * Find a registered encoder with a matching codec ID.
+ *
+ * @param id AVCodecID of the requested encoder
+ * @return An encoder if one was found, NULL otherwise.
+ */
+const AVCodec* avcodec_find_encoder(enum AVCodecID id);
+
+/**
+ * Find a registered encoder with the specified name.
+ *
+ * @param name name of the requested encoder
+ * @return An encoder if one was found, NULL otherwise.
+ */
+const AVCodec* avcodec_find_encoder_by_name(const char* name);
+/**
+ * @return a non-zero number if codec is an encoder, zero otherwise
+ */
+int av_codec_is_encoder(const AVCodec* codec);
+
+/**
+ * @return a non-zero number if codec is a decoder, zero otherwise
+ */
+int av_codec_is_decoder(const AVCodec* codec);
+
+/**
+ * Return a name for the specified profile, if available.
+ *
+ * @param codec the codec that is searched for the given profile
+ * @param profile the profile value for which a name is requested
+ * @return A name for the profile if found, NULL otherwise.
+ */
+const char* av_get_profile_name(const AVCodec* codec, int profile);
+
+enum {
+ /**
+ * The codec supports this format via the hw_device_ctx interface.
+ *
+ * When selecting this format, AVCodecContext.hw_device_ctx should
+ * have been set to a device of the specified type before calling
+ * avcodec_open2().
+ */
+ AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX = 0x01,
+ /**
+ * The codec supports this format via the hw_frames_ctx interface.
+ *
+ * When selecting this format for a decoder,
+ * AVCodecContext.hw_frames_ctx should be set to a suitable frames
+ * context inside the get_format() callback. The frames context
+ * must have been created on a device of the specified type.
+ *
+ * When selecting this format for an encoder,
+ * AVCodecContext.hw_frames_ctx should be set to the context which
+ * will be used for the input frames before calling avcodec_open2().
+ */
+ AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX = 0x02,
+ /**
+ * The codec supports this format by some internal method.
+ *
+ * This format can be selected without any additional configuration -
+ * no device or frames context is required.
+ */
+ AV_CODEC_HW_CONFIG_METHOD_INTERNAL = 0x04,
+ /**
+ * The codec supports this format by some ad-hoc method.
+ *
+ * Additional settings and/or function calls are required. See the
+ * codec-specific documentation for details. (Methods requiring
+ * this sort of configuration are deprecated and others should be
+ * used in preference.)
+ */
+ AV_CODEC_HW_CONFIG_METHOD_AD_HOC = 0x08,
+};
+
+typedef struct AVCodecHWConfig {
+ /**
+ * For decoders, a hardware pixel format which that decoder may be
+ * able to decode to if suitable hardware is available.
+ *
+ * For encoders, a pixel format which the encoder may be able to
+ * accept. If set to AV_PIX_FMT_NONE, this applies to all pixel
+ * formats supported by the codec.
+ */
+ enum AVPixelFormat pix_fmt;
+ /**
+ * Bit set of AV_CODEC_HW_CONFIG_METHOD_* flags, describing the possible
+ * setup methods which can be used with this configuration.
+ */
+ int methods;
+ /**
+ * The device type associated with the configuration.
+ *
+ * Must be set for AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX and
+ * AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX, otherwise unused.
+ */
+ enum AVHWDeviceType device_type;
+} AVCodecHWConfig;
+
+/**
+ * Retrieve supported hardware configurations for a codec.
+ *
+ * Values of index from zero to some maximum return the indexed configuration
+ * descriptor; all other values return NULL. If the codec does not support
+ * any hardware configurations then it will always return NULL.
+ */
+const AVCodecHWConfig* avcodec_get_hw_config(const AVCodec* codec, int index);
+
+/**
+ * @}
+ */
+
+#endif /* AVCODEC_CODEC_H */
diff --git a/dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/codec_desc.h b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/codec_desc.h
new file mode 100644
index 0000000000..a8d424ea7d
--- /dev/null
+++ b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/codec_desc.h
@@ -0,0 +1,134 @@
+/*
+ * Codec descriptors public API
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVCODEC_CODEC_DESC_H
+#define AVCODEC_CODEC_DESC_H
+
+#include "libavutil/avutil.h"
+
+#include "codec_id.h"
+
+/**
+ * @addtogroup lavc_core
+ * @{
+ */
+
+/**
+ * This struct describes the properties of a single codec described by an
+ * AVCodecID.
+ * @see avcodec_descriptor_get()
+ */
+typedef struct AVCodecDescriptor {
+ enum AVCodecID id;
+ enum AVMediaType type;
+ /**
+ * Name of the codec described by this descriptor. It is non-empty and
+ * unique for each codec descriptor. It should contain alphanumeric
+ * characters and '_' only.
+ */
+ const char* name;
+ /**
+ * A more descriptive name for this codec. May be NULL.
+ */
+ const char* long_name;
+ /**
+ * Codec properties, a combination of AV_CODEC_PROP_* flags.
+ */
+ int props;
+ /**
+ * MIME type(s) associated with the codec.
+ * May be NULL; if not, a NULL-terminated array of MIME types.
+ * The first item is always non-NULL and is the preferred MIME type.
+ */
+ const char* const* mime_types;
+ /**
+ * If non-NULL, an array of profiles recognized for this codec.
+ * Terminated with AV_PROFILE_UNKNOWN.
+ */
+ const struct AVProfile* profiles;
+} AVCodecDescriptor;
+
+/**
+ * Codec uses only intra compression.
+ * Video and audio codecs only.
+ */
+#define AV_CODEC_PROP_INTRA_ONLY (1 << 0)
+/**
+ * Codec supports lossy compression. Audio and video codecs only.
+ * @note a codec may support both lossy and lossless
+ * compression modes
+ */
+#define AV_CODEC_PROP_LOSSY (1 << 1)
+/**
+ * Codec supports lossless compression. Audio and video codecs only.
+ */
+#define AV_CODEC_PROP_LOSSLESS (1 << 2)
+/**
+ * Codec supports frame reordering. That is, the coded order (the order in which
+ * the encoded packets are output by the encoders / stored / input to the
+ * decoders) may be different from the presentation order of the corresponding
+ * frames.
+ *
+ * For codecs that do not have this property set, PTS and DTS should always be
+ * equal.
+ */
+#define AV_CODEC_PROP_REORDER (1 << 3)
+
+/**
+ * Video codec supports separate coding of fields in interlaced frames.
+ */
+#define AV_CODEC_PROP_FIELDS (1 << 4)
+
+/**
+ * Subtitle codec is bitmap based
+ * Decoded AVSubtitle data can be read from the AVSubtitleRect->pict field.
+ */
+#define AV_CODEC_PROP_BITMAP_SUB (1 << 16)
+/**
+ * Subtitle codec is text based.
+ * Decoded AVSubtitle data can be read from the AVSubtitleRect->ass field.
+ */
+#define AV_CODEC_PROP_TEXT_SUB (1 << 17)
+
+/**
+ * @return descriptor for given codec ID or NULL if no descriptor exists.
+ */
+const AVCodecDescriptor* avcodec_descriptor_get(enum AVCodecID id);
+
+/**
+ * Iterate over all codec descriptors known to libavcodec.
+ *
+ * @param prev previous descriptor. NULL to get the first descriptor.
+ *
+ * @return next descriptor or NULL after the last descriptor
+ */
+const AVCodecDescriptor* avcodec_descriptor_next(const AVCodecDescriptor* prev);
+
+/**
+ * @return codec descriptor with the given name or NULL if no such descriptor
+ * exists.
+ */
+const AVCodecDescriptor* avcodec_descriptor_get_by_name(const char* name);
+
+/**
+ * @}
+ */
+
+#endif // AVCODEC_CODEC_DESC_H
diff --git a/dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/codec_id.h b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/codec_id.h
new file mode 100644
index 0000000000..edeb281ff4
--- /dev/null
+++ b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/codec_id.h
@@ -0,0 +1,676 @@
+/*
+ * Codec IDs
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVCODEC_CODEC_ID_H
+#define AVCODEC_CODEC_ID_H
+
+#include "libavutil/avutil.h"
+#include "libavutil/samplefmt.h"
+
+#include "version_major.h"
+
+/**
+ * @addtogroup lavc_core
+ * @{
+ */
+
+/**
+ * Identify the syntax and semantics of the bitstream.
+ * The principle is roughly:
+ * Two decoders with the same ID can decode the same streams.
+ * Two encoders with the same ID can encode compatible streams.
+ * There may be slight deviations from the principle due to implementation
+ * details.
+ *
+ * If you add a codec ID to this list, add it so that
+ * 1. no value of an existing codec ID changes (that would break ABI),
+ * 2. it is as close as possible to similar codecs
+ *
+ * After adding new codec IDs, do not forget to add an entry to the codec
+ * descriptor list and bump libavcodec minor version.
+ */
+enum AVCodecID {
+ AV_CODEC_ID_NONE,
+
+ /* video codecs */
+ AV_CODEC_ID_MPEG1VIDEO,
+ AV_CODEC_ID_MPEG2VIDEO, ///< preferred ID for MPEG-1/2 video decoding
+ AV_CODEC_ID_H261,
+ AV_CODEC_ID_H263,
+ AV_CODEC_ID_RV10,
+ AV_CODEC_ID_RV20,
+ AV_CODEC_ID_MJPEG,
+ AV_CODEC_ID_MJPEGB,
+ AV_CODEC_ID_LJPEG,
+ AV_CODEC_ID_SP5X,
+ AV_CODEC_ID_JPEGLS,
+ AV_CODEC_ID_MPEG4,
+ AV_CODEC_ID_RAWVIDEO,
+ AV_CODEC_ID_MSMPEG4V1,
+ AV_CODEC_ID_MSMPEG4V2,
+ AV_CODEC_ID_MSMPEG4V3,
+ AV_CODEC_ID_WMV1,
+ AV_CODEC_ID_WMV2,
+ AV_CODEC_ID_H263P,
+ AV_CODEC_ID_H263I,
+ AV_CODEC_ID_FLV1,
+ AV_CODEC_ID_SVQ1,
+ AV_CODEC_ID_SVQ3,
+ AV_CODEC_ID_DVVIDEO,
+ AV_CODEC_ID_HUFFYUV,
+ AV_CODEC_ID_CYUV,
+ AV_CODEC_ID_H264,
+ AV_CODEC_ID_INDEO3,
+ AV_CODEC_ID_VP3,
+ AV_CODEC_ID_THEORA,
+ AV_CODEC_ID_ASV1,
+ AV_CODEC_ID_ASV2,
+ AV_CODEC_ID_FFV1,
+ AV_CODEC_ID_4XM,
+ AV_CODEC_ID_VCR1,
+ AV_CODEC_ID_CLJR,
+ AV_CODEC_ID_MDEC,
+ AV_CODEC_ID_ROQ,
+ AV_CODEC_ID_INTERPLAY_VIDEO,
+ AV_CODEC_ID_XAN_WC3,
+ AV_CODEC_ID_XAN_WC4,
+ AV_CODEC_ID_RPZA,
+ AV_CODEC_ID_CINEPAK,
+ AV_CODEC_ID_WS_VQA,
+ AV_CODEC_ID_MSRLE,
+ AV_CODEC_ID_MSVIDEO1,
+ AV_CODEC_ID_IDCIN,
+ AV_CODEC_ID_8BPS,
+ AV_CODEC_ID_SMC,
+ AV_CODEC_ID_FLIC,
+ AV_CODEC_ID_TRUEMOTION1,
+ AV_CODEC_ID_VMDVIDEO,
+ AV_CODEC_ID_MSZH,
+ AV_CODEC_ID_ZLIB,
+ AV_CODEC_ID_QTRLE,
+ AV_CODEC_ID_TSCC,
+ AV_CODEC_ID_ULTI,
+ AV_CODEC_ID_QDRAW,
+ AV_CODEC_ID_VIXL,
+ AV_CODEC_ID_QPEG,
+ AV_CODEC_ID_PNG,
+ AV_CODEC_ID_PPM,
+ AV_CODEC_ID_PBM,
+ AV_CODEC_ID_PGM,
+ AV_CODEC_ID_PGMYUV,
+ AV_CODEC_ID_PAM,
+ AV_CODEC_ID_FFVHUFF,
+ AV_CODEC_ID_RV30,
+ AV_CODEC_ID_RV40,
+ AV_CODEC_ID_VC1,
+ AV_CODEC_ID_WMV3,
+ AV_CODEC_ID_LOCO,
+ AV_CODEC_ID_WNV1,
+ AV_CODEC_ID_AASC,
+ AV_CODEC_ID_INDEO2,
+ AV_CODEC_ID_FRAPS,
+ AV_CODEC_ID_TRUEMOTION2,
+ AV_CODEC_ID_BMP,
+ AV_CODEC_ID_CSCD,
+ AV_CODEC_ID_MMVIDEO,
+ AV_CODEC_ID_ZMBV,
+ AV_CODEC_ID_AVS,
+ AV_CODEC_ID_SMACKVIDEO,
+ AV_CODEC_ID_NUV,
+ AV_CODEC_ID_KMVC,
+ AV_CODEC_ID_FLASHSV,
+ AV_CODEC_ID_CAVS,
+ AV_CODEC_ID_JPEG2000,
+ AV_CODEC_ID_VMNC,
+ AV_CODEC_ID_VP5,
+ AV_CODEC_ID_VP6,
+ AV_CODEC_ID_VP6F,
+ AV_CODEC_ID_TARGA,
+ AV_CODEC_ID_DSICINVIDEO,
+ AV_CODEC_ID_TIERTEXSEQVIDEO,
+ AV_CODEC_ID_TIFF,
+ AV_CODEC_ID_GIF,
+ AV_CODEC_ID_DXA,
+ AV_CODEC_ID_DNXHD,
+ AV_CODEC_ID_THP,
+ AV_CODEC_ID_SGI,
+ AV_CODEC_ID_C93,
+ AV_CODEC_ID_BETHSOFTVID,
+ AV_CODEC_ID_PTX,
+ AV_CODEC_ID_TXD,
+ AV_CODEC_ID_VP6A,
+ AV_CODEC_ID_AMV,
+ AV_CODEC_ID_VB,
+ AV_CODEC_ID_PCX,
+ AV_CODEC_ID_SUNRAST,
+ AV_CODEC_ID_INDEO4,
+ AV_CODEC_ID_INDEO5,
+ AV_CODEC_ID_MIMIC,
+ AV_CODEC_ID_RL2,
+ AV_CODEC_ID_ESCAPE124,
+ AV_CODEC_ID_DIRAC,
+ AV_CODEC_ID_BFI,
+ AV_CODEC_ID_CMV,
+ AV_CODEC_ID_MOTIONPIXELS,
+ AV_CODEC_ID_TGV,
+ AV_CODEC_ID_TGQ,
+ AV_CODEC_ID_TQI,
+ AV_CODEC_ID_AURA,
+ AV_CODEC_ID_AURA2,
+ AV_CODEC_ID_V210X,
+ AV_CODEC_ID_TMV,
+ AV_CODEC_ID_V210,
+ AV_CODEC_ID_DPX,
+ AV_CODEC_ID_MAD,
+ AV_CODEC_ID_FRWU,
+ AV_CODEC_ID_FLASHSV2,
+ AV_CODEC_ID_CDGRAPHICS,
+ AV_CODEC_ID_R210,
+ AV_CODEC_ID_ANM,
+ AV_CODEC_ID_BINKVIDEO,
+ AV_CODEC_ID_IFF_ILBM,
+#define AV_CODEC_ID_IFF_BYTERUN1 AV_CODEC_ID_IFF_ILBM
+ AV_CODEC_ID_KGV1,
+ AV_CODEC_ID_YOP,
+ AV_CODEC_ID_VP8,
+ AV_CODEC_ID_PICTOR,
+ AV_CODEC_ID_ANSI,
+ AV_CODEC_ID_A64_MULTI,
+ AV_CODEC_ID_A64_MULTI5,
+ AV_CODEC_ID_R10K,
+ AV_CODEC_ID_MXPEG,
+ AV_CODEC_ID_LAGARITH,
+ AV_CODEC_ID_PRORES,
+ AV_CODEC_ID_JV,
+ AV_CODEC_ID_DFA,
+ AV_CODEC_ID_WMV3IMAGE,
+ AV_CODEC_ID_VC1IMAGE,
+ AV_CODEC_ID_UTVIDEO,
+ AV_CODEC_ID_BMV_VIDEO,
+ AV_CODEC_ID_VBLE,
+ AV_CODEC_ID_DXTORY,
+ AV_CODEC_ID_V410,
+ AV_CODEC_ID_XWD,
+ AV_CODEC_ID_CDXL,
+ AV_CODEC_ID_XBM,
+ AV_CODEC_ID_ZEROCODEC,
+ AV_CODEC_ID_MSS1,
+ AV_CODEC_ID_MSA1,
+ AV_CODEC_ID_TSCC2,
+ AV_CODEC_ID_MTS2,
+ AV_CODEC_ID_CLLC,
+ AV_CODEC_ID_MSS2,
+ AV_CODEC_ID_VP9,
+ AV_CODEC_ID_AIC,
+ AV_CODEC_ID_ESCAPE130,
+ AV_CODEC_ID_G2M,
+ AV_CODEC_ID_WEBP,
+ AV_CODEC_ID_HNM4_VIDEO,
+ AV_CODEC_ID_HEVC,
+#define AV_CODEC_ID_H265 AV_CODEC_ID_HEVC
+ AV_CODEC_ID_FIC,
+ AV_CODEC_ID_ALIAS_PIX,
+ AV_CODEC_ID_BRENDER_PIX,
+ AV_CODEC_ID_PAF_VIDEO,
+ AV_CODEC_ID_EXR,
+ AV_CODEC_ID_VP7,
+ AV_CODEC_ID_SANM,
+ AV_CODEC_ID_SGIRLE,
+ AV_CODEC_ID_MVC1,
+ AV_CODEC_ID_MVC2,
+ AV_CODEC_ID_HQX,
+ AV_CODEC_ID_TDSC,
+ AV_CODEC_ID_HQ_HQA,
+ AV_CODEC_ID_HAP,
+ AV_CODEC_ID_DDS,
+ AV_CODEC_ID_DXV,
+ AV_CODEC_ID_SCREENPRESSO,
+ AV_CODEC_ID_RSCC,
+ AV_CODEC_ID_AVS2,
+ AV_CODEC_ID_PGX,
+ AV_CODEC_ID_AVS3,
+ AV_CODEC_ID_MSP2,
+ AV_CODEC_ID_VVC,
+#define AV_CODEC_ID_H266 AV_CODEC_ID_VVC
+ AV_CODEC_ID_Y41P,
+ AV_CODEC_ID_AVRP,
+ AV_CODEC_ID_012V,
+ AV_CODEC_ID_AVUI,
+ AV_CODEC_ID_TARGA_Y216,
+ AV_CODEC_ID_V308,
+ AV_CODEC_ID_V408,
+ AV_CODEC_ID_YUV4,
+ AV_CODEC_ID_AVRN,
+ AV_CODEC_ID_CPIA,
+ AV_CODEC_ID_XFACE,
+ AV_CODEC_ID_SNOW,
+ AV_CODEC_ID_SMVJPEG,
+ AV_CODEC_ID_APNG,
+ AV_CODEC_ID_DAALA,
+ AV_CODEC_ID_CFHD,
+ AV_CODEC_ID_TRUEMOTION2RT,
+ AV_CODEC_ID_M101,
+ AV_CODEC_ID_MAGICYUV,
+ AV_CODEC_ID_SHEERVIDEO,
+ AV_CODEC_ID_YLC,
+ AV_CODEC_ID_PSD,
+ AV_CODEC_ID_PIXLET,
+ AV_CODEC_ID_SPEEDHQ,
+ AV_CODEC_ID_FMVC,
+ AV_CODEC_ID_SCPR,
+ AV_CODEC_ID_CLEARVIDEO,
+ AV_CODEC_ID_XPM,
+ AV_CODEC_ID_AV1,
+ AV_CODEC_ID_BITPACKED,
+ AV_CODEC_ID_MSCC,
+ AV_CODEC_ID_SRGC,
+ AV_CODEC_ID_SVG,
+ AV_CODEC_ID_GDV,
+ AV_CODEC_ID_FITS,
+ AV_CODEC_ID_IMM4,
+ AV_CODEC_ID_PROSUMER,
+ AV_CODEC_ID_MWSC,
+ AV_CODEC_ID_WCMV,
+ AV_CODEC_ID_RASC,
+ AV_CODEC_ID_HYMT,
+ AV_CODEC_ID_ARBC,
+ AV_CODEC_ID_AGM,
+ AV_CODEC_ID_LSCR,
+ AV_CODEC_ID_VP4,
+ AV_CODEC_ID_IMM5,
+ AV_CODEC_ID_MVDV,
+ AV_CODEC_ID_MVHA,
+ AV_CODEC_ID_CDTOONS,
+ AV_CODEC_ID_MV30,
+ AV_CODEC_ID_NOTCHLC,
+ AV_CODEC_ID_PFM,
+ AV_CODEC_ID_MOBICLIP,
+ AV_CODEC_ID_PHOTOCD,
+ AV_CODEC_ID_IPU,
+ AV_CODEC_ID_ARGO,
+ AV_CODEC_ID_CRI,
+ AV_CODEC_ID_SIMBIOSIS_IMX,
+ AV_CODEC_ID_SGA_VIDEO,
+ AV_CODEC_ID_GEM,
+ AV_CODEC_ID_VBN,
+ AV_CODEC_ID_JPEGXL,
+ AV_CODEC_ID_QOI,
+ AV_CODEC_ID_PHM,
+ AV_CODEC_ID_RADIANCE_HDR,
+ AV_CODEC_ID_WBMP,
+ AV_CODEC_ID_MEDIA100,
+ AV_CODEC_ID_VQC,
+ AV_CODEC_ID_PDV,
+ AV_CODEC_ID_EVC,
+ AV_CODEC_ID_RTV1,
+ AV_CODEC_ID_VMIX,
+ AV_CODEC_ID_LEAD,
+
+ /* various PCM "codecs" */
+ AV_CODEC_ID_FIRST_AUDIO =
+ 0x10000, ///< A dummy id pointing at the start of audio codecs
+ AV_CODEC_ID_PCM_S16LE = 0x10000,
+ AV_CODEC_ID_PCM_S16BE,
+ AV_CODEC_ID_PCM_U16LE,
+ AV_CODEC_ID_PCM_U16BE,
+ AV_CODEC_ID_PCM_S8,
+ AV_CODEC_ID_PCM_U8,
+ AV_CODEC_ID_PCM_MULAW,
+ AV_CODEC_ID_PCM_ALAW,
+ AV_CODEC_ID_PCM_S32LE,
+ AV_CODEC_ID_PCM_S32BE,
+ AV_CODEC_ID_PCM_U32LE,
+ AV_CODEC_ID_PCM_U32BE,
+ AV_CODEC_ID_PCM_S24LE,
+ AV_CODEC_ID_PCM_S24BE,
+ AV_CODEC_ID_PCM_U24LE,
+ AV_CODEC_ID_PCM_U24BE,
+ AV_CODEC_ID_PCM_S24DAUD,
+ AV_CODEC_ID_PCM_ZORK,
+ AV_CODEC_ID_PCM_S16LE_PLANAR,
+ AV_CODEC_ID_PCM_DVD,
+ AV_CODEC_ID_PCM_F32BE,
+ AV_CODEC_ID_PCM_F32LE,
+ AV_CODEC_ID_PCM_F64BE,
+ AV_CODEC_ID_PCM_F64LE,
+ AV_CODEC_ID_PCM_BLURAY,
+ AV_CODEC_ID_PCM_LXF,
+ AV_CODEC_ID_S302M,
+ AV_CODEC_ID_PCM_S8_PLANAR,
+ AV_CODEC_ID_PCM_S24LE_PLANAR,
+ AV_CODEC_ID_PCM_S32LE_PLANAR,
+ AV_CODEC_ID_PCM_S16BE_PLANAR,
+ AV_CODEC_ID_PCM_S64LE,
+ AV_CODEC_ID_PCM_S64BE,
+ AV_CODEC_ID_PCM_F16LE,
+ AV_CODEC_ID_PCM_F24LE,
+ AV_CODEC_ID_PCM_VIDC,
+ AV_CODEC_ID_PCM_SGA,
+
+ /* various ADPCM codecs */
+ AV_CODEC_ID_ADPCM_IMA_QT = 0x11000,
+ AV_CODEC_ID_ADPCM_IMA_WAV,
+ AV_CODEC_ID_ADPCM_IMA_DK3,
+ AV_CODEC_ID_ADPCM_IMA_DK4,
+ AV_CODEC_ID_ADPCM_IMA_WS,
+ AV_CODEC_ID_ADPCM_IMA_SMJPEG,
+ AV_CODEC_ID_ADPCM_MS,
+ AV_CODEC_ID_ADPCM_4XM,
+ AV_CODEC_ID_ADPCM_XA,
+ AV_CODEC_ID_ADPCM_ADX,
+ AV_CODEC_ID_ADPCM_EA,
+ AV_CODEC_ID_ADPCM_G726,
+ AV_CODEC_ID_ADPCM_CT,
+ AV_CODEC_ID_ADPCM_SWF,
+ AV_CODEC_ID_ADPCM_YAMAHA,
+ AV_CODEC_ID_ADPCM_SBPRO_4,
+ AV_CODEC_ID_ADPCM_SBPRO_3,
+ AV_CODEC_ID_ADPCM_SBPRO_2,
+ AV_CODEC_ID_ADPCM_THP,
+ AV_CODEC_ID_ADPCM_IMA_AMV,
+ AV_CODEC_ID_ADPCM_EA_R1,
+ AV_CODEC_ID_ADPCM_EA_R3,
+ AV_CODEC_ID_ADPCM_EA_R2,
+ AV_CODEC_ID_ADPCM_IMA_EA_SEAD,
+ AV_CODEC_ID_ADPCM_IMA_EA_EACS,
+ AV_CODEC_ID_ADPCM_EA_XAS,
+ AV_CODEC_ID_ADPCM_EA_MAXIS_XA,
+ AV_CODEC_ID_ADPCM_IMA_ISS,
+ AV_CODEC_ID_ADPCM_G722,
+ AV_CODEC_ID_ADPCM_IMA_APC,
+ AV_CODEC_ID_ADPCM_VIMA,
+ AV_CODEC_ID_ADPCM_AFC,
+ AV_CODEC_ID_ADPCM_IMA_OKI,
+ AV_CODEC_ID_ADPCM_DTK,
+ AV_CODEC_ID_ADPCM_IMA_RAD,
+ AV_CODEC_ID_ADPCM_G726LE,
+ AV_CODEC_ID_ADPCM_THP_LE,
+ AV_CODEC_ID_ADPCM_PSX,
+ AV_CODEC_ID_ADPCM_AICA,
+ AV_CODEC_ID_ADPCM_IMA_DAT4,
+ AV_CODEC_ID_ADPCM_MTAF,
+ AV_CODEC_ID_ADPCM_AGM,
+ AV_CODEC_ID_ADPCM_ARGO,
+ AV_CODEC_ID_ADPCM_IMA_SSI,
+ AV_CODEC_ID_ADPCM_ZORK,
+ AV_CODEC_ID_ADPCM_IMA_APM,
+ AV_CODEC_ID_ADPCM_IMA_ALP,
+ AV_CODEC_ID_ADPCM_IMA_MTF,
+ AV_CODEC_ID_ADPCM_IMA_CUNNING,
+ AV_CODEC_ID_ADPCM_IMA_MOFLEX,
+ AV_CODEC_ID_ADPCM_IMA_ACORN,
+ AV_CODEC_ID_ADPCM_XMD,
+
+ /* AMR */
+ AV_CODEC_ID_AMR_NB = 0x12000,
+ AV_CODEC_ID_AMR_WB,
+
+ /* RealAudio codecs*/
+ AV_CODEC_ID_RA_144 = 0x13000,
+ AV_CODEC_ID_RA_288,
+
+ /* various DPCM codecs */
+ AV_CODEC_ID_ROQ_DPCM = 0x14000,
+ AV_CODEC_ID_INTERPLAY_DPCM,
+ AV_CODEC_ID_XAN_DPCM,
+ AV_CODEC_ID_SOL_DPCM,
+ AV_CODEC_ID_SDX2_DPCM,
+ AV_CODEC_ID_GREMLIN_DPCM,
+ AV_CODEC_ID_DERF_DPCM,
+ AV_CODEC_ID_WADY_DPCM,
+ AV_CODEC_ID_CBD2_DPCM,
+
+ /* audio codecs */
+ AV_CODEC_ID_MP2 = 0x15000,
+ AV_CODEC_ID_MP3, ///< preferred ID for decoding MPEG audio layer 1, 2 or 3
+ AV_CODEC_ID_AAC,
+ AV_CODEC_ID_AC3,
+ AV_CODEC_ID_DTS,
+ AV_CODEC_ID_VORBIS,
+ AV_CODEC_ID_DVAUDIO,
+ AV_CODEC_ID_WMAV1,
+ AV_CODEC_ID_WMAV2,
+ AV_CODEC_ID_MACE3,
+ AV_CODEC_ID_MACE6,
+ AV_CODEC_ID_VMDAUDIO,
+ AV_CODEC_ID_FLAC,
+ AV_CODEC_ID_MP3ADU,
+ AV_CODEC_ID_MP3ON4,
+ AV_CODEC_ID_SHORTEN,
+ AV_CODEC_ID_ALAC,
+ AV_CODEC_ID_WESTWOOD_SND1,
+ AV_CODEC_ID_GSM, ///< as in Berlin toast format
+ AV_CODEC_ID_QDM2,
+ AV_CODEC_ID_COOK,
+ AV_CODEC_ID_TRUESPEECH,
+ AV_CODEC_ID_TTA,
+ AV_CODEC_ID_SMACKAUDIO,
+ AV_CODEC_ID_QCELP,
+ AV_CODEC_ID_WAVPACK,
+ AV_CODEC_ID_DSICINAUDIO,
+ AV_CODEC_ID_IMC,
+ AV_CODEC_ID_MUSEPACK7,
+ AV_CODEC_ID_MLP,
+ AV_CODEC_ID_GSM_MS, /* as found in WAV */
+ AV_CODEC_ID_ATRAC3,
+ AV_CODEC_ID_APE,
+ AV_CODEC_ID_NELLYMOSER,
+ AV_CODEC_ID_MUSEPACK8,
+ AV_CODEC_ID_SPEEX,
+ AV_CODEC_ID_WMAVOICE,
+ AV_CODEC_ID_WMAPRO,
+ AV_CODEC_ID_WMALOSSLESS,
+ AV_CODEC_ID_ATRAC3P,
+ AV_CODEC_ID_EAC3,
+ AV_CODEC_ID_SIPR,
+ AV_CODEC_ID_MP1,
+ AV_CODEC_ID_TWINVQ,
+ AV_CODEC_ID_TRUEHD,
+ AV_CODEC_ID_MP4ALS,
+ AV_CODEC_ID_ATRAC1,
+ AV_CODEC_ID_BINKAUDIO_RDFT,
+ AV_CODEC_ID_BINKAUDIO_DCT,
+ AV_CODEC_ID_AAC_LATM,
+ AV_CODEC_ID_QDMC,
+ AV_CODEC_ID_CELT,
+ AV_CODEC_ID_G723_1,
+ AV_CODEC_ID_G729,
+ AV_CODEC_ID_8SVX_EXP,
+ AV_CODEC_ID_8SVX_FIB,
+ AV_CODEC_ID_BMV_AUDIO,
+ AV_CODEC_ID_RALF,
+ AV_CODEC_ID_IAC,
+ AV_CODEC_ID_ILBC,
+ AV_CODEC_ID_OPUS,
+ AV_CODEC_ID_COMFORT_NOISE,
+ AV_CODEC_ID_TAK,
+ AV_CODEC_ID_METASOUND,
+ AV_CODEC_ID_PAF_AUDIO,
+ AV_CODEC_ID_ON2AVC,
+ AV_CODEC_ID_DSS_SP,
+ AV_CODEC_ID_CODEC2,
+ AV_CODEC_ID_FFWAVESYNTH,
+ AV_CODEC_ID_SONIC,
+ AV_CODEC_ID_SONIC_LS,
+ AV_CODEC_ID_EVRC,
+ AV_CODEC_ID_SMV,
+ AV_CODEC_ID_DSD_LSBF,
+ AV_CODEC_ID_DSD_MSBF,
+ AV_CODEC_ID_DSD_LSBF_PLANAR,
+ AV_CODEC_ID_DSD_MSBF_PLANAR,
+ AV_CODEC_ID_4GV,
+ AV_CODEC_ID_INTERPLAY_ACM,
+ AV_CODEC_ID_XMA1,
+ AV_CODEC_ID_XMA2,
+ AV_CODEC_ID_DST,
+ AV_CODEC_ID_ATRAC3AL,
+ AV_CODEC_ID_ATRAC3PAL,
+ AV_CODEC_ID_DOLBY_E,
+ AV_CODEC_ID_APTX,
+ AV_CODEC_ID_APTX_HD,
+ AV_CODEC_ID_SBC,
+ AV_CODEC_ID_ATRAC9,
+ AV_CODEC_ID_HCOM,
+ AV_CODEC_ID_ACELP_KELVIN,
+ AV_CODEC_ID_MPEGH_3D_AUDIO,
+ AV_CODEC_ID_SIREN,
+ AV_CODEC_ID_HCA,
+ AV_CODEC_ID_FASTAUDIO,
+ AV_CODEC_ID_MSNSIREN,
+ AV_CODEC_ID_DFPWM,
+ AV_CODEC_ID_BONK,
+ AV_CODEC_ID_MISC4,
+ AV_CODEC_ID_APAC,
+ AV_CODEC_ID_FTR,
+ AV_CODEC_ID_WAVARC,
+ AV_CODEC_ID_RKA,
+ AV_CODEC_ID_AC4,
+ AV_CODEC_ID_OSQ,
+ AV_CODEC_ID_QOA,
+ AV_CODEC_ID_LC3,
+
+ /* subtitle codecs */
+ AV_CODEC_ID_FIRST_SUBTITLE =
+ 0x17000, ///< A dummy ID pointing at the start of subtitle codecs.
+ AV_CODEC_ID_DVD_SUBTITLE = 0x17000,
+ AV_CODEC_ID_DVB_SUBTITLE,
+ AV_CODEC_ID_TEXT, ///< raw UTF-8 text
+ AV_CODEC_ID_XSUB,
+ AV_CODEC_ID_SSA,
+ AV_CODEC_ID_MOV_TEXT,
+ AV_CODEC_ID_HDMV_PGS_SUBTITLE,
+ AV_CODEC_ID_DVB_TELETEXT,
+ AV_CODEC_ID_SRT,
+ AV_CODEC_ID_MICRODVD,
+ AV_CODEC_ID_EIA_608,
+ AV_CODEC_ID_JACOSUB,
+ AV_CODEC_ID_SAMI,
+ AV_CODEC_ID_REALTEXT,
+ AV_CODEC_ID_STL,
+ AV_CODEC_ID_SUBVIEWER1,
+ AV_CODEC_ID_SUBVIEWER,
+ AV_CODEC_ID_SUBRIP,
+ AV_CODEC_ID_WEBVTT,
+ AV_CODEC_ID_MPL2,
+ AV_CODEC_ID_VPLAYER,
+ AV_CODEC_ID_PJS,
+ AV_CODEC_ID_ASS,
+ AV_CODEC_ID_HDMV_TEXT_SUBTITLE,
+ AV_CODEC_ID_TTML,
+ AV_CODEC_ID_ARIB_CAPTION,
+
+ /* other specific kind of codecs (generally used for attachments) */
+ AV_CODEC_ID_FIRST_UNKNOWN =
+ 0x18000, ///< A dummy ID pointing at the start of various fake codecs.
+ AV_CODEC_ID_TTF = 0x18000,
+
+ AV_CODEC_ID_SCTE_35, ///< Contain timestamp estimated through PCR of program
+ ///< stream.
+ AV_CODEC_ID_EPG,
+ AV_CODEC_ID_BINTEXT,
+ AV_CODEC_ID_XBIN,
+ AV_CODEC_ID_IDF,
+ AV_CODEC_ID_OTF,
+ AV_CODEC_ID_SMPTE_KLV,
+ AV_CODEC_ID_DVD_NAV,
+ AV_CODEC_ID_TIMED_ID3,
+ AV_CODEC_ID_BIN_DATA,
+ AV_CODEC_ID_SMPTE_2038,
+
+ AV_CODEC_ID_PROBE =
+ 0x19000, ///< codec_id is not known (like AV_CODEC_ID_NONE) but lavf
+ ///< should attempt to identify it
+
+ AV_CODEC_ID_MPEG2TS = 0x20000, /**< _FAKE_ codec to indicate a raw MPEG-2 TS
+ * stream (only used by libavformat) */
+ AV_CODEC_ID_MPEG4SYSTEMS =
+ 0x20001, /**< _FAKE_ codec to indicate a MPEG-4 Systems
+ * stream (only used by libavformat) */
+ AV_CODEC_ID_FFMETADATA = 0x21000, ///< Dummy codec for streams containing
+ ///< only metadata information.
+ AV_CODEC_ID_WRAPPED_AVFRAME =
+ 0x21001, ///< Passthrough codec, AVFrames wrapped in AVPacket
+ /**
+ * Dummy null video codec, useful mainly for development and debugging.
+ * Null encoder/decoder discard all input and never return any output.
+ */
+ AV_CODEC_ID_VNULL,
+ /**
+ * Dummy null audio codec, useful mainly for development and debugging.
+ * Null encoder/decoder discard all input and never return any output.
+ */
+ AV_CODEC_ID_ANULL,
+};
+
+/**
+ * Get the type of the given codec.
+ */
+enum AVMediaType avcodec_get_type(enum AVCodecID codec_id);
+
+/**
+ * Get the name of a codec.
+ * @return a static string identifying the codec; never NULL
+ */
+const char* avcodec_get_name(enum AVCodecID id);
+
+/**
+ * Return codec bits per sample.
+ *
+ * @param[in] codec_id the codec
+ * @return Number of bits per sample or zero if unknown for the given codec.
+ */
+int av_get_bits_per_sample(enum AVCodecID codec_id);
+
+/**
+ * Return codec bits per sample.
+ * Only return non-zero if the bits per sample is exactly correct, not an
+ * approximation.
+ *
+ * @param[in] codec_id the codec
+ * @return Number of bits per sample or zero if unknown for the given codec.
+ */
+int av_get_exact_bits_per_sample(enum AVCodecID codec_id);
+
+/**
+ * Return a name for the specified profile, if available.
+ *
+ * @param codec_id the ID of the codec to which the requested profile belongs
+ * @param profile the profile value for which a name is requested
+ * @return A name for the profile if found, NULL otherwise.
+ *
+ * @note unlike av_get_profile_name(), which searches a list of profiles
+ * supported by a specific decoder or encoder implementation, this
+ * function searches the list of profiles from the AVCodecDescriptor
+ */
+const char* avcodec_profile_name(enum AVCodecID codec_id, int profile);
+
+/**
+ * Return the PCM codec associated with a sample format.
+ * @param be endianness, 0 for little, 1 for big,
+ * -1 (or anything else) for native
+ * @return AV_CODEC_ID_PCM_* or AV_CODEC_ID_NONE
+ */
+enum AVCodecID av_get_pcm_codec(enum AVSampleFormat fmt, int be);
+
+/**
+ * @}
+ */
+
+#endif // AVCODEC_CODEC_ID_H
diff --git a/dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/codec_par.h b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/codec_par.h
new file mode 100644
index 0000000000..a99b976bcb
--- /dev/null
+++ b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/codec_par.h
@@ -0,0 +1,250 @@
+/*
+ * Codec parameters public API
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVCODEC_CODEC_PAR_H
+#define AVCODEC_CODEC_PAR_H
+
+#include <stdint.h>
+
+#include "libavutil/avutil.h"
+#include "libavutil/channel_layout.h"
+#include "libavutil/rational.h"
+#include "libavutil/pixfmt.h"
+
+#include "codec_id.h"
+#include "defs.h"
+#include "packet.h"
+
+/**
+ * @addtogroup lavc_core
+ * @{
+ */
+
+/**
+ * This struct describes the properties of an encoded stream.
+ *
+ * sizeof(AVCodecParameters) is not a part of the public ABI, this struct must
+ * be allocated with avcodec_parameters_alloc() and freed with
+ * avcodec_parameters_free().
+ */
+typedef struct AVCodecParameters {
+ /**
+ * General type of the encoded data.
+ */
+ enum AVMediaType codec_type;
+ /**
+ * Specific type of the encoded data (the codec used).
+ */
+ enum AVCodecID codec_id;
+ /**
+ * Additional information about the codec (corresponds to the AVI FOURCC).
+ */
+ uint32_t codec_tag;
+
+ /**
+ * Extra binary data needed for initializing the decoder, codec-dependent.
+ *
+ * Must be allocated with av_malloc() and will be freed by
+ * avcodec_parameters_free(). The allocated size of extradata must be at
+ * least extradata_size + AV_INPUT_BUFFER_PADDING_SIZE, with the padding
+ * bytes zeroed.
+ */
+ uint8_t* extradata;
+ /**
+ * Size of the extradata content in bytes.
+ */
+ int extradata_size;
+
+ /**
+ * Additional data associated with the entire stream.
+ *
+ * Should be allocated with av_packet_side_data_new() or
+ * av_packet_side_data_add(), and will be freed by avcodec_parameters_free().
+ */
+ AVPacketSideData* coded_side_data;
+
+ /**
+ * Amount of entries in @ref coded_side_data.
+ */
+ int nb_coded_side_data;
+
+ /**
+ * - video: the pixel format, the value corresponds to enum AVPixelFormat.
+ * - audio: the sample format, the value corresponds to enum AVSampleFormat.
+ */
+ int format;
+
+ /**
+ * The average bitrate of the encoded data (in bits per second).
+ */
+ int64_t bit_rate;
+
+ /**
+ * The number of bits per sample in the codedwords.
+ *
+ * This is basically the bitrate per sample. It is mandatory for a bunch of
+ * formats to actually decode them. It's the number of bits for one sample in
+ * the actual coded bitstream.
+ *
+ * This could be for example 4 for ADPCM
+ * For PCM formats this matches bits_per_raw_sample
+ * Can be 0
+ */
+ int bits_per_coded_sample;
+
+ /**
+ * This is the number of valid bits in each output sample. If the
+ * sample format has more bits, the least significant bits are additional
+ * padding bits, which are always 0. Use right shifts to reduce the sample
+ * to its actual size. For example, audio formats with 24 bit samples will
+ * have bits_per_raw_sample set to 24, and format set to AV_SAMPLE_FMT_S32.
+ * To get the original sample use "(int32_t)sample >> 8"."
+ *
+ * For ADPCM this might be 12 or 16 or similar
+ * Can be 0
+ */
+ int bits_per_raw_sample;
+
+ /**
+ * Codec-specific bitstream restrictions that the stream conforms to.
+ */
+ int profile;
+ int level;
+
+ /**
+ * Video only. The dimensions of the video frame in pixels.
+ */
+ int width;
+ int height;
+
+ /**
+ * Video only. The aspect ratio (width / height) which a single pixel
+ * should have when displayed.
+ *
+ * When the aspect ratio is unknown / undefined, the numerator should be
+ * set to 0 (the denominator may have any value).
+ */
+ AVRational sample_aspect_ratio;
+
+ /**
+ * Video only. Number of frames per second, for streams with constant frame
+ * durations. Should be set to { 0, 1 } when some frames have differing
+ * durations or if the value is not known.
+ *
+ * @note This field correponds to values that are stored in codec-level
+ * headers and is typically overridden by container/transport-layer
+ * timestamps, when available. It should thus be used only as a last resort,
+ * when no higher-level timing information is available.
+ */
+ AVRational framerate;
+
+ /**
+ * Video only. The order of the fields in interlaced video.
+ */
+ enum AVFieldOrder field_order;
+
+ /**
+ * Video only. Additional colorspace characteristics.
+ */
+ enum AVColorRange color_range;
+ enum AVColorPrimaries color_primaries;
+ enum AVColorTransferCharacteristic color_trc;
+ enum AVColorSpace color_space;
+ enum AVChromaLocation chroma_location;
+
+ /**
+ * Video only. Number of delayed frames.
+ */
+ int video_delay;
+
+ /**
+ * Audio only. The channel layout and number of channels.
+ */
+ AVChannelLayout ch_layout;
+ /**
+ * Audio only. The number of audio samples per second.
+ */
+ int sample_rate;
+ /**
+ * Audio only. The number of bytes per coded audio frame, required by some
+ * formats.
+ *
+ * Corresponds to nBlockAlign in WAVEFORMATEX.
+ */
+ int block_align;
+ /**
+ * Audio only. Audio frame size, if known. Required by some formats to be
+ * static.
+ */
+ int frame_size;
+
+ /**
+ * Audio only. The amount of padding (in samples) inserted by the encoder at
+ * the beginning of the audio. I.e. this number of leading decoded samples
+ * must be discarded by the caller to get the original audio without leading
+ * padding.
+ */
+ int initial_padding;
+ /**
+ * Audio only. The amount of padding (in samples) appended by the encoder to
+ * the end of the audio. I.e. this number of decoded samples must be
+ * discarded by the caller from the end of the stream to get the original
+ * audio without any trailing padding.
+ */
+ int trailing_padding;
+ /**
+ * Audio only. Number of samples to skip after a discontinuity.
+ */
+ int seek_preroll;
+} AVCodecParameters;
+
+/**
+ * Allocate a new AVCodecParameters and set its fields to default values
+ * (unknown/invalid/0). The returned struct must be freed with
+ * avcodec_parameters_free().
+ */
+AVCodecParameters* avcodec_parameters_alloc(void);
+
+/**
+ * Free an AVCodecParameters instance and everything associated with it and
+ * write NULL to the supplied pointer.
+ */
+void avcodec_parameters_free(AVCodecParameters** par);
+
+/**
+ * Copy the contents of src to dst. Any allocated fields in dst are freed and
+ * replaced with newly allocated duplicates of the corresponding fields in src.
+ *
+ * @return >= 0 on success, a negative AVERROR code on failure.
+ */
+int avcodec_parameters_copy(AVCodecParameters* dst,
+ const AVCodecParameters* src);
+
+/**
+ * This function is the same as av_get_audio_frame_duration(), except it works
+ * with AVCodecParameters instead of an AVCodecContext.
+ */
+int av_get_audio_frame_duration2(AVCodecParameters* par, int frame_bytes);
+
+/**
+ * @}
+ */
+
+#endif // AVCODEC_CODEC_PAR_H
diff --git a/dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/defs.h b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/defs.h
new file mode 100644
index 0000000000..63df946e16
--- /dev/null
+++ b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/defs.h
@@ -0,0 +1,344 @@
+/*
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVCODEC_DEFS_H
+#define AVCODEC_DEFS_H
+
+/**
+ * @file
+ * @ingroup libavc
+ * Misc types and constants that do not belong anywhere else.
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+
+/**
+ * @ingroup lavc_decoding
+ * Required number of additionally allocated bytes at the end of the input
+ * bitstream for decoding. This is mainly needed because some optimized
+ * bitstream readers read 32 or 64 bit at once and could read over the end.<br>
+ * Note: If the first 23 bits of the additional bytes are not 0, then damaged
+ * MPEG bitstreams could cause overread and segfault.
+ */
+#define AV_INPUT_BUFFER_PADDING_SIZE 64
+
+/**
+ * Verify checksums embedded in the bitstream (could be of either encoded or
+ * decoded data, depending on the format) and print an error message on
+ * mismatch. If AV_EF_EXPLODE is also set, a mismatching checksum will result in
+ * the decoder/demuxer returning an error.
+ */
+#define AV_EF_CRCCHECK (1 << 0)
+#define AV_EF_BITSTREAM (1 << 1) ///< detect bitstream specification deviations
+#define AV_EF_BUFFER (1 << 2) ///< detect improper bitstream length
+#define AV_EF_EXPLODE (1 << 3) ///< abort decoding on minor error detection
+
+#define AV_EF_IGNORE_ERR (1 << 15) ///< ignore errors and continue
+#define AV_EF_CAREFUL \
+ (1 << 16) ///< consider things that violate the spec, are fast to calculate
+ ///< and have not been seen in the wild as errors
+#define AV_EF_COMPLIANT \
+ (1 << 17) ///< consider all spec non compliances as errors
+#define AV_EF_AGGRESSIVE \
+ (1 << 18) ///< consider things that a sane encoder/muxer should not do as an
+ ///< error
+
+#define FF_COMPLIANCE_VERY_STRICT \
+ 2 ///< Strictly conform to an older more strict version of the spec or
+ ///< reference software.
+#define FF_COMPLIANCE_STRICT \
+ 1 ///< Strictly conform to all the things in the spec no matter what
+ ///< consequences.
+#define FF_COMPLIANCE_NORMAL 0
+#define FF_COMPLIANCE_UNOFFICIAL -1 ///< Allow unofficial extensions
+#define FF_COMPLIANCE_EXPERIMENTAL \
+ -2 ///< Allow nonstandardized experimental things.
+
+#define AV_PROFILE_UNKNOWN -99
+#define AV_PROFILE_RESERVED -100
+
+#define AV_PROFILE_AAC_MAIN 0
+#define AV_PROFILE_AAC_LOW 1
+#define AV_PROFILE_AAC_SSR 2
+#define AV_PROFILE_AAC_LTP 3
+#define AV_PROFILE_AAC_HE 4
+#define AV_PROFILE_AAC_HE_V2 28
+#define AV_PROFILE_AAC_LD 22
+#define AV_PROFILE_AAC_ELD 38
+#define AV_PROFILE_MPEG2_AAC_LOW 128
+#define AV_PROFILE_MPEG2_AAC_HE 131
+
+#define AV_PROFILE_DNXHD 0
+#define AV_PROFILE_DNXHR_LB 1
+#define AV_PROFILE_DNXHR_SQ 2
+#define AV_PROFILE_DNXHR_HQ 3
+#define AV_PROFILE_DNXHR_HQX 4
+#define AV_PROFILE_DNXHR_444 5
+
+#define AV_PROFILE_DTS 20
+#define AV_PROFILE_DTS_ES 30
+#define AV_PROFILE_DTS_96_24 40
+#define AV_PROFILE_DTS_HD_HRA 50
+#define AV_PROFILE_DTS_HD_MA 60
+#define AV_PROFILE_DTS_EXPRESS 70
+#define AV_PROFILE_DTS_HD_MA_X 61
+#define AV_PROFILE_DTS_HD_MA_X_IMAX 62
+
+#define AV_PROFILE_EAC3_DDP_ATMOS 30
+
+#define AV_PROFILE_TRUEHD_ATMOS 30
+
+#define AV_PROFILE_MPEG2_422 0
+#define AV_PROFILE_MPEG2_HIGH 1
+#define AV_PROFILE_MPEG2_SS 2
+#define AV_PROFILE_MPEG2_SNR_SCALABLE 3
+#define AV_PROFILE_MPEG2_MAIN 4
+#define AV_PROFILE_MPEG2_SIMPLE 5
+
+#define AV_PROFILE_H264_CONSTRAINED (1 << 9) // 8+1; constraint_set1_flag
+#define AV_PROFILE_H264_INTRA (1 << 11) // 8+3; constraint_set3_flag
+
+#define AV_PROFILE_H264_BASELINE 66
+#define AV_PROFILE_H264_CONSTRAINED_BASELINE (66 | AV_PROFILE_H264_CONSTRAINED)
+#define AV_PROFILE_H264_MAIN 77
+#define AV_PROFILE_H264_EXTENDED 88
+#define AV_PROFILE_H264_HIGH 100
+#define AV_PROFILE_H264_HIGH_10 110
+#define AV_PROFILE_H264_HIGH_10_INTRA (110 | AV_PROFILE_H264_INTRA)
+#define AV_PROFILE_H264_MULTIVIEW_HIGH 118
+#define AV_PROFILE_H264_HIGH_422 122
+#define AV_PROFILE_H264_HIGH_422_INTRA (122 | AV_PROFILE_H264_INTRA)
+#define AV_PROFILE_H264_STEREO_HIGH 128
+#define AV_PROFILE_H264_HIGH_444 144
+#define AV_PROFILE_H264_HIGH_444_PREDICTIVE 244
+#define AV_PROFILE_H264_HIGH_444_INTRA (244 | AV_PROFILE_H264_INTRA)
+#define AV_PROFILE_H264_CAVLC_444 44
+
+#define AV_PROFILE_VC1_SIMPLE 0
+#define AV_PROFILE_VC1_MAIN 1
+#define AV_PROFILE_VC1_COMPLEX 2
+#define AV_PROFILE_VC1_ADVANCED 3
+
+#define AV_PROFILE_MPEG4_SIMPLE 0
+#define AV_PROFILE_MPEG4_SIMPLE_SCALABLE 1
+#define AV_PROFILE_MPEG4_CORE 2
+#define AV_PROFILE_MPEG4_MAIN 3
+#define AV_PROFILE_MPEG4_N_BIT 4
+#define AV_PROFILE_MPEG4_SCALABLE_TEXTURE 5
+#define AV_PROFILE_MPEG4_SIMPLE_FACE_ANIMATION 6
+#define AV_PROFILE_MPEG4_BASIC_ANIMATED_TEXTURE 7
+#define AV_PROFILE_MPEG4_HYBRID 8
+#define AV_PROFILE_MPEG4_ADVANCED_REAL_TIME 9
+#define AV_PROFILE_MPEG4_CORE_SCALABLE 10
+#define AV_PROFILE_MPEG4_ADVANCED_CODING 11
+#define AV_PROFILE_MPEG4_ADVANCED_CORE 12
+#define AV_PROFILE_MPEG4_ADVANCED_SCALABLE_TEXTURE 13
+#define AV_PROFILE_MPEG4_SIMPLE_STUDIO 14
+#define AV_PROFILE_MPEG4_ADVANCED_SIMPLE 15
+
+#define AV_PROFILE_JPEG2000_CSTREAM_RESTRICTION_0 1
+#define AV_PROFILE_JPEG2000_CSTREAM_RESTRICTION_1 2
+#define AV_PROFILE_JPEG2000_CSTREAM_NO_RESTRICTION 32768
+#define AV_PROFILE_JPEG2000_DCINEMA_2K 3
+#define AV_PROFILE_JPEG2000_DCINEMA_4K 4
+
+#define AV_PROFILE_VP9_0 0
+#define AV_PROFILE_VP9_1 1
+#define AV_PROFILE_VP9_2 2
+#define AV_PROFILE_VP9_3 3
+
+#define AV_PROFILE_HEVC_MAIN 1
+#define AV_PROFILE_HEVC_MAIN_10 2
+#define AV_PROFILE_HEVC_MAIN_STILL_PICTURE 3
+#define AV_PROFILE_HEVC_REXT 4
+#define AV_PROFILE_HEVC_SCC 9
+
+#define AV_PROFILE_VVC_MAIN_10 1
+#define AV_PROFILE_VVC_MAIN_10_444 33
+
+#define AV_PROFILE_AV1_MAIN 0
+#define AV_PROFILE_AV1_HIGH 1
+#define AV_PROFILE_AV1_PROFESSIONAL 2
+
+#define AV_PROFILE_MJPEG_HUFFMAN_BASELINE_DCT 0xc0
+#define AV_PROFILE_MJPEG_HUFFMAN_EXTENDED_SEQUENTIAL_DCT 0xc1
+#define AV_PROFILE_MJPEG_HUFFMAN_PROGRESSIVE_DCT 0xc2
+#define AV_PROFILE_MJPEG_HUFFMAN_LOSSLESS 0xc3
+#define AV_PROFILE_MJPEG_JPEG_LS 0xf7
+
+#define AV_PROFILE_SBC_MSBC 1
+
+#define AV_PROFILE_PRORES_PROXY 0
+#define AV_PROFILE_PRORES_LT 1
+#define AV_PROFILE_PRORES_STANDARD 2
+#define AV_PROFILE_PRORES_HQ 3
+#define AV_PROFILE_PRORES_4444 4
+#define AV_PROFILE_PRORES_XQ 5
+
+#define AV_PROFILE_ARIB_PROFILE_A 0
+#define AV_PROFILE_ARIB_PROFILE_C 1
+
+#define AV_PROFILE_KLVA_SYNC 0
+#define AV_PROFILE_KLVA_ASYNC 1
+
+#define AV_PROFILE_EVC_BASELINE 0
+#define AV_PROFILE_EVC_MAIN 1
+
+#define AV_LEVEL_UNKNOWN -99
+
+enum AVFieldOrder {
+ AV_FIELD_UNKNOWN,
+ AV_FIELD_PROGRESSIVE,
+ AV_FIELD_TT, ///< Top coded_first, top displayed first
+ AV_FIELD_BB, ///< Bottom coded first, bottom displayed first
+ AV_FIELD_TB, ///< Top coded first, bottom displayed first
+ AV_FIELD_BT, ///< Bottom coded first, top displayed first
+};
+
+/**
+ * @ingroup lavc_decoding
+ */
+enum AVDiscard {
+ /* We leave some space between them for extensions (drop some
+ * keyframes for intra-only or drop just some bidir frames). */
+ AVDISCARD_NONE = -16, ///< discard nothing
+ AVDISCARD_DEFAULT =
+ 0, ///< discard useless packets like 0 size packets in avi
+ AVDISCARD_NONREF = 8, ///< discard all non reference
+ AVDISCARD_BIDIR = 16, ///< discard all bidirectional frames
+ AVDISCARD_NONINTRA = 24, ///< discard all non intra frames
+ AVDISCARD_NONKEY = 32, ///< discard all frames except keyframes
+ AVDISCARD_ALL = 48, ///< discard all
+};
+
+enum AVAudioServiceType {
+ AV_AUDIO_SERVICE_TYPE_MAIN = 0,
+ AV_AUDIO_SERVICE_TYPE_EFFECTS = 1,
+ AV_AUDIO_SERVICE_TYPE_VISUALLY_IMPAIRED = 2,
+ AV_AUDIO_SERVICE_TYPE_HEARING_IMPAIRED = 3,
+ AV_AUDIO_SERVICE_TYPE_DIALOGUE = 4,
+ AV_AUDIO_SERVICE_TYPE_COMMENTARY = 5,
+ AV_AUDIO_SERVICE_TYPE_EMERGENCY = 6,
+ AV_AUDIO_SERVICE_TYPE_VOICE_OVER = 7,
+ AV_AUDIO_SERVICE_TYPE_KARAOKE = 8,
+ AV_AUDIO_SERVICE_TYPE_NB, ///< Not part of ABI
+};
+
+/**
+ * Pan Scan area.
+ * This specifies the area which should be displayed.
+ * Note there may be multiple such areas for one frame.
+ */
+typedef struct AVPanScan {
+ /**
+ * id
+ * - encoding: Set by user.
+ * - decoding: Set by libavcodec.
+ */
+ int id;
+
+ /**
+ * width and height in 1/16 pel
+ * - encoding: Set by user.
+ * - decoding: Set by libavcodec.
+ */
+ int width;
+ int height;
+
+ /**
+ * position of the top left corner in 1/16 pel for up to 3 fields/frames
+ * - encoding: Set by user.
+ * - decoding: Set by libavcodec.
+ */
+ int16_t position[3][2];
+} AVPanScan;
+
+/**
+ * This structure describes the bitrate properties of an encoded bitstream. It
+ * roughly corresponds to a subset the VBV parameters for MPEG-2 or HRD
+ * parameters for H.264/HEVC.
+ */
+typedef struct AVCPBProperties {
+ /**
+ * Maximum bitrate of the stream, in bits per second.
+ * Zero if unknown or unspecified.
+ */
+ int64_t max_bitrate;
+ /**
+ * Minimum bitrate of the stream, in bits per second.
+ * Zero if unknown or unspecified.
+ */
+ int64_t min_bitrate;
+ /**
+ * Average bitrate of the stream, in bits per second.
+ * Zero if unknown or unspecified.
+ */
+ int64_t avg_bitrate;
+
+ /**
+ * The size of the buffer to which the ratecontrol is applied, in bits.
+ * Zero if unknown or unspecified.
+ */
+ int64_t buffer_size;
+
+ /**
+ * The delay between the time the packet this structure is associated with
+ * is received and the time when it should be decoded, in periods of a 27MHz
+ * clock.
+ *
+ * UINT64_MAX when unknown or unspecified.
+ */
+ uint64_t vbv_delay;
+} AVCPBProperties;
+
+/**
+ * Allocate a CPB properties structure and initialize its fields to default
+ * values.
+ *
+ * @param size if non-NULL, the size of the allocated struct will be written
+ * here. This is useful for embedding it in side data.
+ *
+ * @return the newly allocated struct or NULL on failure
+ */
+AVCPBProperties* av_cpb_properties_alloc(size_t* size);
+
+/**
+ * This structure supplies correlation between a packet timestamp and a wall
+ * clock production time. The definition follows the Producer Reference Time
+ * ('prft') as defined in ISO/IEC 14496-12
+ */
+typedef struct AVProducerReferenceTime {
+ /**
+ * A UTC timestamp, in microseconds, since Unix epoch (e.g, av_gettime()).
+ */
+ int64_t wallclock;
+ int flags;
+} AVProducerReferenceTime;
+
+/**
+ * Encode extradata length to a buffer. Used by xiph codecs.
+ *
+ * @param s buffer to write to; must be at least (v/255+1) bytes long
+ * @param v size of extradata in bytes
+ * @return number of bytes written to the buffer.
+ */
+unsigned int av_xiphlacing(unsigned char* s, unsigned int v);
+
+#endif // AVCODEC_DEFS_H
diff --git a/dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/packet.h b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/packet.h
new file mode 100644
index 0000000000..58fde480c9
--- /dev/null
+++ b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/packet.h
@@ -0,0 +1,871 @@
+/*
+ * AVPacket public API
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVCODEC_PACKET_H
+#define AVCODEC_PACKET_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "libavutil/attributes.h"
+#include "libavutil/buffer.h"
+#include "libavutil/dict.h"
+#include "libavutil/rational.h"
+#include "libavutil/version.h"
+
+#include "libavcodec/version_major.h"
+
+/**
+ * @defgroup lavc_packet_side_data AVPacketSideData
+ *
+ * Types and functions for working with AVPacketSideData.
+ * @{
+ */
+enum AVPacketSideDataType {
+ /**
+ * An AV_PKT_DATA_PALETTE side data packet contains exactly AVPALETTE_SIZE
+ * bytes worth of palette. This side data signals that a new palette is
+ * present.
+ */
+ AV_PKT_DATA_PALETTE,
+
+ /**
+ * The AV_PKT_DATA_NEW_EXTRADATA is used to notify the codec or the format
+ * that the extradata buffer was changed and the receiving side should
+ * act upon it appropriately. The new extradata is embedded in the side
+ * data buffer and should be immediately used for processing the current
+ * frame or packet.
+ */
+ AV_PKT_DATA_NEW_EXTRADATA,
+
+ /**
+ * An AV_PKT_DATA_PARAM_CHANGE side data packet is laid out as follows:
+ * @code
+ * u32le param_flags
+ * if (param_flags & AV_SIDE_DATA_PARAM_CHANGE_CHANNEL_COUNT)
+ * s32le channel_count
+ * if (param_flags & AV_SIDE_DATA_PARAM_CHANGE_CHANNEL_LAYOUT)
+ * u64le channel_layout
+ * if (param_flags & AV_SIDE_DATA_PARAM_CHANGE_SAMPLE_RATE)
+ * s32le sample_rate
+ * if (param_flags & AV_SIDE_DATA_PARAM_CHANGE_DIMENSIONS)
+ * s32le width
+ * s32le height
+ * @endcode
+ */
+ AV_PKT_DATA_PARAM_CHANGE,
+
+ /**
+ * An AV_PKT_DATA_H263_MB_INFO side data packet contains a number of
+ * structures with info about macroblocks relevant to splitting the
+ * packet into smaller packets on macroblock edges (e.g. as for RFC 2190).
+ * That is, it does not necessarily contain info about all macroblocks,
+ * as long as the distance between macroblocks in the info is smaller
+ * than the target payload size.
+ * Each MB info structure is 12 bytes, and is laid out as follows:
+ * @code
+ * u32le bit offset from the start of the packet
+ * u8 current quantizer at the start of the macroblock
+ * u8 GOB number
+ * u16le macroblock address within the GOB
+ * u8 horizontal MV predictor
+ * u8 vertical MV predictor
+ * u8 horizontal MV predictor for block number 3
+ * u8 vertical MV predictor for block number 3
+ * @endcode
+ */
+ AV_PKT_DATA_H263_MB_INFO,
+
+ /**
+ * This side data should be associated with an audio stream and contains
+ * ReplayGain information in form of the AVReplayGain struct.
+ */
+ AV_PKT_DATA_REPLAYGAIN,
+
+ /**
+ * This side data contains a 3x3 transformation matrix describing an affine
+ * transformation that needs to be applied to the decoded video frames for
+ * correct presentation.
+ *
+ * See libavutil/display.h for a detailed description of the data.
+ */
+ AV_PKT_DATA_DISPLAYMATRIX,
+
+ /**
+ * This side data should be associated with a video stream and contains
+ * Stereoscopic 3D information in form of the AVStereo3D struct.
+ */
+ AV_PKT_DATA_STEREO3D,
+
+ /**
+ * This side data should be associated with an audio stream and corresponds
+ * to enum AVAudioServiceType.
+ */
+ AV_PKT_DATA_AUDIO_SERVICE_TYPE,
+
+ /**
+ * This side data contains quality related information from the encoder.
+ * @code
+ * u32le quality factor of the compressed frame. Allowed range is between 1
+ * (good) and FF_LAMBDA_MAX (bad). u8 picture type u8 error count u16
+ * reserved u64le[error count] sum of squared differences between encoder in
+ * and output
+ * @endcode
+ */
+ AV_PKT_DATA_QUALITY_STATS,
+
+ /**
+ * This side data contains an integer value representing the stream index
+ * of a "fallback" track. A fallback track indicates an alternate
+ * track to use when the current track can not be decoded for some reason.
+ * e.g. no decoder available for codec.
+ */
+ AV_PKT_DATA_FALLBACK_TRACK,
+
+ /**
+ * This side data corresponds to the AVCPBProperties struct.
+ */
+ AV_PKT_DATA_CPB_PROPERTIES,
+
+ /**
+ * Recommmends skipping the specified number of samples
+ * @code
+ * u32le number of samples to skip from start of this packet
+ * u32le number of samples to skip from end of this packet
+ * u8 reason for start skip
+ * u8 reason for end skip (0=padding silence, 1=convergence)
+ * @endcode
+ */
+ AV_PKT_DATA_SKIP_SAMPLES,
+
+ /**
+ * An AV_PKT_DATA_JP_DUALMONO side data packet indicates that
+ * the packet may contain "dual mono" audio specific to Japanese DTV
+ * and if it is true, recommends only the selected channel to be used.
+ * @code
+ * u8 selected channels (0=main/left, 1=sub/right, 2=both)
+ * @endcode
+ */
+ AV_PKT_DATA_JP_DUALMONO,
+
+ /**
+ * A list of zero terminated key/value strings. There is no end marker for
+ * the list, so it is required to rely on the side data size to stop.
+ */
+ AV_PKT_DATA_STRINGS_METADATA,
+
+ /**
+ * Subtitle event position
+ * @code
+ * u32le x1
+ * u32le y1
+ * u32le x2
+ * u32le y2
+ * @endcode
+ */
+ AV_PKT_DATA_SUBTITLE_POSITION,
+
+ /**
+ * Data found in BlockAdditional element of matroska container. There is
+ * no end marker for the data, so it is required to rely on the side data
+ * size to recognize the end. 8 byte id (as found in BlockAddId) followed
+ * by data.
+ */
+ AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL,
+
+ /**
+ * The optional first identifier line of a WebVTT cue.
+ */
+ AV_PKT_DATA_WEBVTT_IDENTIFIER,
+
+ /**
+ * The optional settings (rendering instructions) that immediately
+ * follow the timestamp specifier of a WebVTT cue.
+ */
+ AV_PKT_DATA_WEBVTT_SETTINGS,
+
+ /**
+ * A list of zero terminated key/value strings. There is no end marker for
+ * the list, so it is required to rely on the side data size to stop. This
+ * side data includes updated metadata which appeared in the stream.
+ */
+ AV_PKT_DATA_METADATA_UPDATE,
+
+ /**
+ * MPEGTS stream ID as uint8_t, this is required to pass the stream ID
+ * information from the demuxer to the corresponding muxer.
+ */
+ AV_PKT_DATA_MPEGTS_STREAM_ID,
+
+ /**
+ * Mastering display metadata (based on SMPTE-2086:2014). This metadata
+ * should be associated with a video stream and contains data in the form
+ * of the AVMasteringDisplayMetadata struct.
+ */
+ AV_PKT_DATA_MASTERING_DISPLAY_METADATA,
+
+ /**
+ * This side data should be associated with a video stream and corresponds
+ * to the AVSphericalMapping structure.
+ */
+ AV_PKT_DATA_SPHERICAL,
+
+ /**
+ * Content light level (based on CTA-861.3). This metadata should be
+ * associated with a video stream and contains data in the form of the
+ * AVContentLightMetadata struct.
+ */
+ AV_PKT_DATA_CONTENT_LIGHT_LEVEL,
+
+ /**
+ * ATSC A53 Part 4 Closed Captions. This metadata should be associated with
+ * a video stream. A53 CC bitstream is stored as uint8_t in
+ * AVPacketSideData.data. The number of bytes of CC data is
+ * AVPacketSideData.size.
+ */
+ AV_PKT_DATA_A53_CC,
+
+ /**
+ * This side data is encryption initialization data.
+ * The format is not part of ABI, use av_encryption_init_info_* methods to
+ * access.
+ */
+ AV_PKT_DATA_ENCRYPTION_INIT_INFO,
+
+ /**
+ * This side data contains encryption info for how to decrypt the packet.
+ * The format is not part of ABI, use av_encryption_info_* methods to access.
+ */
+ AV_PKT_DATA_ENCRYPTION_INFO,
+
+ /**
+ * Active Format Description data consisting of a single byte as specified
+ * in ETSI TS 101 154 using AVActiveFormatDescription enum.
+ */
+ AV_PKT_DATA_AFD,
+
+ /**
+ * Producer Reference Time data corresponding to the AVProducerReferenceTime
+ * struct, usually exported by some encoders (on demand through the prft flag
+ * set in the AVCodecContext export_side_data field).
+ */
+ AV_PKT_DATA_PRFT,
+
+ /**
+ * ICC profile data consisting of an opaque octet buffer following the
+ * format described by ISO 15076-1.
+ */
+ AV_PKT_DATA_ICC_PROFILE,
+
+ /**
+ * DOVI configuration
+ * ref:
+ * dolby-vision-bitstreams-within-the-iso-base-media-file-format-v2.1.2,
+ * section 2.2
+ * dolby-vision-bitstreams-in-mpeg-2-transport-stream-multiplex-v1.2,
+ * section 3.3 Tags are stored in struct AVDOVIDecoderConfigurationRecord.
+ */
+ AV_PKT_DATA_DOVI_CONF,
+
+ /**
+ * Timecode which conforms to SMPTE ST 12-1:2014. The data is an array of 4
+ * uint32_t where the first uint32_t describes how many (1-3) of the other
+ * timecodes are used. The timecode format is described in the documentation
+ * of av_timecode_get_smpte_from_framenum() function in libavutil/timecode.h.
+ */
+ AV_PKT_DATA_S12M_TIMECODE,
+
+ /**
+ * HDR10+ dynamic metadata associated with a video frame. The metadata is in
+ * the form of the AVDynamicHDRPlus struct and contains
+ * information for color volume transform - application 4 of
+ * SMPTE 2094-40:2016 standard.
+ */
+ AV_PKT_DATA_DYNAMIC_HDR10_PLUS,
+
+ /**
+ * IAMF Mix Gain Parameter Data associated with the audio frame. This metadata
+ * is in the form of the AVIAMFParamDefinition struct and contains information
+ * defined in sections 3.6.1 and 3.8.1 of the Immersive Audio Model and
+ * Formats standard.
+ */
+ AV_PKT_DATA_IAMF_MIX_GAIN_PARAM,
+
+ /**
+ * IAMF Demixing Info Parameter Data associated with the audio frame. This
+ * metadata is in the form of the AVIAMFParamDefinition struct and contains
+ * information defined in sections 3.6.1 and 3.8.2 of the Immersive Audio
+ * Model and Formats standard.
+ */
+ AV_PKT_DATA_IAMF_DEMIXING_INFO_PARAM,
+
+ /**
+ * IAMF Recon Gain Info Parameter Data associated with the audio frame. This
+ * metadata is in the form of the AVIAMFParamDefinition struct and contains
+ * information defined in sections 3.6.1 and 3.8.3 of the Immersive Audio
+ * Model and Formats standard.
+ */
+ AV_PKT_DATA_IAMF_RECON_GAIN_INFO_PARAM,
+
+ /**
+ * Ambient viewing environment metadata, as defined by H.274. This metadata
+ * should be associated with a video stream and contains data in the form
+ * of the AVAmbientViewingEnvironment struct.
+ */
+ AV_PKT_DATA_AMBIENT_VIEWING_ENVIRONMENT,
+
+ /**
+ * The number of side data types.
+ * This is not part of the public API/ABI in the sense that it may
+ * change when new side data types are added.
+ * This must stay the last enum value.
+ * If its value becomes huge, some code using it
+ * needs to be updated as it assumes it to be smaller than other limits.
+ */
+ AV_PKT_DATA_NB
+};
+
+#if FF_API_QUALITY_FACTOR
+# define AV_PKT_DATA_QUALITY_FACTOR AV_PKT_DATA_QUALITY_STATS // DEPRECATED
+#endif
+
+/**
+ * This structure stores auxiliary information for decoding, presenting, or
+ * otherwise processing the coded stream. It is typically exported by demuxers
+ * and encoders and can be fed to decoders and muxers either in a per packet
+ * basis, or as global side data (applying to the entire coded stream).
+ *
+ * Global side data is handled as follows:
+ * - During demuxing, it may be exported through
+ * @ref AVStream.codecpar.side_data "AVStream's codec parameters", which can
+ * then be passed as input to decoders through the
+ * @ref AVCodecContext.coded_side_data "decoder context's side data", for
+ * initialization.
+ * - For muxing, it can be fed through @ref AVStream.codecpar.side_data
+ * "AVStream's codec parameters", typically the output of encoders through
+ * the @ref AVCodecContext.coded_side_data "encoder context's side data", for
+ * initialization.
+ *
+ * Packet specific side data is handled as follows:
+ * - During demuxing, it may be exported through @ref AVPacket.side_data
+ * "AVPacket's side data", which can then be passed as input to decoders.
+ * - For muxing, it can be fed through @ref AVPacket.side_data "AVPacket's
+ * side data", typically the output of encoders.
+ *
+ * Different modules may accept or export different types of side data
+ * depending on media type and codec. Refer to @ref AVPacketSideDataType for a
+ * list of defined types and where they may be found or used.
+ */
+typedef struct AVPacketSideData {
+ uint8_t* data;
+ size_t size;
+ enum AVPacketSideDataType type;
+} AVPacketSideData;
+
+/**
+ * Allocate a new packet side data.
+ *
+ * @param sd pointer to an array of side data to which the side data should
+ * be added. *sd may be NULL, in which case the array will be
+ * initialized.
+ * @param nb_sd pointer to an integer containing the number of entries in
+ * the array. The integer value will be increased by 1 on success.
+ * @param type side data type
+ * @param size desired side data size
+ * @param flags currently unused. Must be zero
+ *
+ * @return pointer to freshly allocated side data on success, or NULL otherwise.
+ */
+AVPacketSideData* av_packet_side_data_new(AVPacketSideData** psd, int* pnb_sd,
+ enum AVPacketSideDataType type,
+ size_t size, int flags);
+
+/**
+ * Wrap existing data as packet side data.
+ *
+ * @param sd pointer to an array of side data to which the side data should
+ * be added. *sd may be NULL, in which case the array will be
+ * initialized
+ * @param nb_sd pointer to an integer containing the number of entries in
+ * the array. The integer value will be increased by 1 on success.
+ * @param type side data type
+ * @param data a data array. It must be allocated with the av_malloc() family
+ * of functions. The ownership of the data is transferred to the
+ * side data array on success
+ * @param size size of the data array
+ * @param flags currently unused. Must be zero
+ *
+ * @return pointer to freshly allocated side data on success, or NULL otherwise
+ * On failure, the side data array is unchanged and the data remains
+ * owned by the caller.
+ */
+AVPacketSideData* av_packet_side_data_add(AVPacketSideData** sd, int* nb_sd,
+ enum AVPacketSideDataType type,
+ void* data, size_t size, int flags);
+
+/**
+ * Get side information from a side data array.
+ *
+ * @param sd the array from which the side data should be fetched
+ * @param nb_sd value containing the number of entries in the array.
+ * @param type desired side information type
+ *
+ * @return pointer to side data if present or NULL otherwise
+ */
+const AVPacketSideData* av_packet_side_data_get(const AVPacketSideData* sd,
+ int nb_sd,
+ enum AVPacketSideDataType type);
+
+/**
+ * Remove side data of the given type from a side data array.
+ *
+ * @param sd the array from which the side data should be removed
+ * @param nb_sd pointer to an integer containing the number of entries in
+ * the array. Will be reduced by the amount of entries removed
+ * upon return
+ * @param type side information type
+ */
+void av_packet_side_data_remove(AVPacketSideData* sd, int* nb_sd,
+ enum AVPacketSideDataType type);
+
+/**
+ * Convenience function to free all the side data stored in an array, and
+ * the array itself.
+ *
+ * @param sd pointer to array of side data to free. Will be set to NULL
+ * upon return.
+ * @param nb_sd pointer to an integer containing the number of entries in
+ * the array. Will be set to 0 upon return.
+ */
+void av_packet_side_data_free(AVPacketSideData** sd, int* nb_sd);
+
+const char* av_packet_side_data_name(enum AVPacketSideDataType type);
+
+/**
+ * @}
+ */
+
+/**
+ * @defgroup lavc_packet AVPacket
+ *
+ * Types and functions for working with AVPacket.
+ * @{
+ */
+
+/**
+ * This structure stores compressed data. It is typically exported by demuxers
+ * and then passed as input to decoders, or received as output from encoders and
+ * then passed to muxers.
+ *
+ * For video, it should typically contain one compressed frame. For audio it may
+ * contain several compressed frames. Encoders are allowed to output empty
+ * packets, with no compressed data, containing only side data
+ * (e.g. to update some stream parameters at the end of encoding).
+ *
+ * The semantics of data ownership depends on the buf field.
+ * If it is set, the packet data is dynamically allocated and is
+ * valid indefinitely until a call to av_packet_unref() reduces the
+ * reference count to 0.
+ *
+ * If the buf field is not set av_packet_ref() would make a copy instead
+ * of increasing the reference count.
+ *
+ * The side data is always allocated with av_malloc(), copied by
+ * av_packet_ref() and freed by av_packet_unref().
+ *
+ * sizeof(AVPacket) being a part of the public ABI is deprecated. once
+ * av_init_packet() is removed, new packets will only be able to be allocated
+ * with av_packet_alloc(), and new fields may be added to the end of the struct
+ * with a minor bump.
+ *
+ * @see av_packet_alloc
+ * @see av_packet_ref
+ * @see av_packet_unref
+ */
+typedef struct AVPacket {
+ /**
+ * A reference to the reference-counted buffer where the packet data is
+ * stored.
+ * May be NULL, then the packet data is not reference-counted.
+ */
+ AVBufferRef* buf;
+ /**
+ * Presentation timestamp in AVStream->time_base units; the time at which
+ * the decompressed packet will be presented to the user.
+ * Can be AV_NOPTS_VALUE if it is not stored in the file.
+ * pts MUST be larger or equal to dts as presentation cannot happen before
+ * decompression, unless one wants to view hex dumps. Some formats misuse
+ * the terms dts and pts/cts to mean something different. Such timestamps
+ * must be converted to true pts/dts before they are stored in AVPacket.
+ */
+ int64_t pts;
+ /**
+ * Decompression timestamp in AVStream->time_base units; the time at which
+ * the packet is decompressed.
+ * Can be AV_NOPTS_VALUE if it is not stored in the file.
+ */
+ int64_t dts;
+ uint8_t* data;
+ int size;
+ int stream_index;
+ /**
+ * A combination of AV_PKT_FLAG values
+ */
+ int flags;
+ /**
+ * Additional packet data that can be provided by the container.
+ * Packet can contain several types of side information.
+ */
+ AVPacketSideData* side_data;
+ int side_data_elems;
+
+ /**
+ * Duration of this packet in AVStream->time_base units, 0 if unknown.
+ * Equals next_pts - this_pts in presentation order.
+ */
+ int64_t duration;
+
+ int64_t pos; ///< byte position in stream, -1 if unknown
+
+ /**
+ * for some private data of the user
+ */
+ void* opaque;
+
+ /**
+ * AVBufferRef for free use by the API user. FFmpeg will never check the
+ * contents of the buffer ref. FFmpeg calls av_buffer_unref() on it when
+ * the packet is unreferenced. av_packet_copy_props() calls create a new
+ * reference with av_buffer_ref() for the target packet's opaque_ref field.
+ *
+ * This is unrelated to the opaque field, although it serves a similar
+ * purpose.
+ */
+ AVBufferRef* opaque_ref;
+
+ /**
+ * Time base of the packet's timestamps.
+ * In the future, this field may be set on packets output by encoders or
+ * demuxers, but its value will be by default ignored on input to decoders
+ * or muxers.
+ */
+ AVRational time_base;
+} AVPacket;
+
+#if FF_API_INIT_PACKET
+attribute_deprecated typedef struct AVPacketList {
+ AVPacket pkt;
+ struct AVPacketList* next;
+} AVPacketList;
+#endif
+
+#define AV_PKT_FLAG_KEY 0x0001 ///< The packet contains a keyframe
+#define AV_PKT_FLAG_CORRUPT 0x0002 ///< The packet content is corrupted
+/**
+ * Flag is used to discard packets which are required to maintain valid
+ * decoder state but are not required for output and should be dropped
+ * after decoding.
+ **/
+#define AV_PKT_FLAG_DISCARD 0x0004
+/**
+ * The packet comes from a trusted source.
+ *
+ * Otherwise-unsafe constructs such as arbitrary pointers to data
+ * outside the packet may be followed.
+ */
+#define AV_PKT_FLAG_TRUSTED 0x0008
+/**
+ * Flag is used to indicate packets that contain frames that can
+ * be discarded by the decoder. I.e. Non-reference frames.
+ */
+#define AV_PKT_FLAG_DISPOSABLE 0x0010
+
+enum AVSideDataParamChangeFlags {
+ AV_SIDE_DATA_PARAM_CHANGE_SAMPLE_RATE = 0x0004,
+ AV_SIDE_DATA_PARAM_CHANGE_DIMENSIONS = 0x0008,
+};
+
+/**
+ * Allocate an AVPacket and set its fields to default values. The resulting
+ * struct must be freed using av_packet_free().
+ *
+ * @return An AVPacket filled with default values or NULL on failure.
+ *
+ * @note this only allocates the AVPacket itself, not the data buffers. Those
+ * must be allocated through other means such as av_new_packet.
+ *
+ * @see av_new_packet
+ */
+AVPacket* av_packet_alloc(void);
+
+/**
+ * Create a new packet that references the same data as src.
+ *
+ * This is a shortcut for av_packet_alloc()+av_packet_ref().
+ *
+ * @return newly created AVPacket on success, NULL on error.
+ *
+ * @see av_packet_alloc
+ * @see av_packet_ref
+ */
+AVPacket* av_packet_clone(const AVPacket* src);
+
+/**
+ * Free the packet, if the packet is reference counted, it will be
+ * unreferenced first.
+ *
+ * @param pkt packet to be freed. The pointer will be set to NULL.
+ * @note passing NULL is a no-op.
+ */
+void av_packet_free(AVPacket** pkt);
+
+#if FF_API_INIT_PACKET
+/**
+ * Initialize optional fields of a packet with default values.
+ *
+ * Note, this does not touch the data and size members, which have to be
+ * initialized separately.
+ *
+ * @param pkt packet
+ *
+ * @see av_packet_alloc
+ * @see av_packet_unref
+ *
+ * @deprecated This function is deprecated. Once it's removed,
+ sizeof(AVPacket) will not be a part of the ABI anymore.
+ */
+attribute_deprecated void av_init_packet(AVPacket* pkt);
+#endif
+
+/**
+ * Allocate the payload of a packet and initialize its fields with
+ * default values.
+ *
+ * @param pkt packet
+ * @param size wanted payload size
+ * @return 0 if OK, AVERROR_xxx otherwise
+ */
+int av_new_packet(AVPacket* pkt, int size);
+
+/**
+ * Reduce packet size, correctly zeroing padding
+ *
+ * @param pkt packet
+ * @param size new size
+ */
+void av_shrink_packet(AVPacket* pkt, int size);
+
+/**
+ * Increase packet size, correctly zeroing padding
+ *
+ * @param pkt packet
+ * @param grow_by number of bytes by which to increase the size of the packet
+ */
+int av_grow_packet(AVPacket* pkt, int grow_by);
+
+/**
+ * Initialize a reference-counted packet from av_malloc()ed data.
+ *
+ * @param pkt packet to be initialized. This function will set the data, size,
+ * and buf fields, all others are left untouched.
+ * @param data Data allocated by av_malloc() to be used as packet data. If this
+ * function returns successfully, the data is owned by the underlying
+ * AVBuffer. The caller may not access the data through other means.
+ * @param size size of data in bytes, without the padding. I.e. the full buffer
+ * size is assumed to be size + AV_INPUT_BUFFER_PADDING_SIZE.
+ *
+ * @return 0 on success, a negative AVERROR on error
+ */
+int av_packet_from_data(AVPacket* pkt, uint8_t* data, int size);
+
+/**
+ * Allocate new information of a packet.
+ *
+ * @param pkt packet
+ * @param type side information type
+ * @param size side information size
+ * @return pointer to fresh allocated data or NULL otherwise
+ */
+uint8_t* av_packet_new_side_data(AVPacket* pkt, enum AVPacketSideDataType type,
+ size_t size);
+
+/**
+ * Wrap an existing array as a packet side data.
+ *
+ * @param pkt packet
+ * @param type side information type
+ * @param data the side data array. It must be allocated with the av_malloc()
+ * family of functions. The ownership of the data is transferred to
+ * pkt.
+ * @param size side information size
+ * @return a non-negative number on success, a negative AVERROR code on
+ * failure. On failure, the packet is unchanged and the data remains
+ * owned by the caller.
+ */
+int av_packet_add_side_data(AVPacket* pkt, enum AVPacketSideDataType type,
+ uint8_t* data, size_t size);
+
+/**
+ * Shrink the already allocated side data buffer
+ *
+ * @param pkt packet
+ * @param type side information type
+ * @param size new side information size
+ * @return 0 on success, < 0 on failure
+ */
+int av_packet_shrink_side_data(AVPacket* pkt, enum AVPacketSideDataType type,
+ size_t size);
+
+/**
+ * Get side information from packet.
+ *
+ * @param pkt packet
+ * @param type desired side information type
+ * @param size If supplied, *size will be set to the size of the side data
+ * or to zero if the desired side data is not present.
+ * @return pointer to data if present or NULL otherwise
+ */
+uint8_t* av_packet_get_side_data(const AVPacket* pkt,
+ enum AVPacketSideDataType type, size_t* size);
+
+/**
+ * Pack a dictionary for use in side_data.
+ *
+ * @param dict The dictionary to pack.
+ * @param size pointer to store the size of the returned data
+ * @return pointer to data if successful, NULL otherwise
+ */
+uint8_t* av_packet_pack_dictionary(AVDictionary* dict, size_t* size);
+/**
+ * Unpack a dictionary from side_data.
+ *
+ * @param data data from side_data
+ * @param size size of the data
+ * @param dict the metadata storage dictionary
+ * @return 0 on success, < 0 on failure
+ */
+int av_packet_unpack_dictionary(const uint8_t* data, size_t size,
+ AVDictionary** dict);
+
+/**
+ * Convenience function to free all the side data stored.
+ * All the other fields stay untouched.
+ *
+ * @param pkt packet
+ */
+void av_packet_free_side_data(AVPacket* pkt);
+
+/**
+ * Setup a new reference to the data described by a given packet
+ *
+ * If src is reference-counted, setup dst as a new reference to the
+ * buffer in src. Otherwise allocate a new buffer in dst and copy the
+ * data from src into it.
+ *
+ * All the other fields are copied from src.
+ *
+ * @see av_packet_unref
+ *
+ * @param dst Destination packet. Will be completely overwritten.
+ * @param src Source packet
+ *
+ * @return 0 on success, a negative AVERROR on error. On error, dst
+ * will be blank (as if returned by av_packet_alloc()).
+ */
+int av_packet_ref(AVPacket* dst, const AVPacket* src);
+
+/**
+ * Wipe the packet.
+ *
+ * Unreference the buffer referenced by the packet and reset the
+ * remaining packet fields to their default values.
+ *
+ * @param pkt The packet to be unreferenced.
+ */
+void av_packet_unref(AVPacket* pkt);
+
+/**
+ * Move every field in src to dst and reset src.
+ *
+ * @see av_packet_unref
+ *
+ * @param src Source packet, will be reset
+ * @param dst Destination packet
+ */
+void av_packet_move_ref(AVPacket* dst, AVPacket* src);
+
+/**
+ * Copy only "properties" fields from src to dst.
+ *
+ * Properties for the purpose of this function are all the fields
+ * beside those related to the packet data (buf, data, size)
+ *
+ * @param dst Destination packet
+ * @param src Source packet
+ *
+ * @return 0 on success AVERROR on failure.
+ */
+int av_packet_copy_props(AVPacket* dst, const AVPacket* src);
+
+/**
+ * Ensure the data described by a given packet is reference counted.
+ *
+ * @note This function does not ensure that the reference will be writable.
+ * Use av_packet_make_writable instead for that purpose.
+ *
+ * @see av_packet_ref
+ * @see av_packet_make_writable
+ *
+ * @param pkt packet whose data should be made reference counted.
+ *
+ * @return 0 on success, a negative AVERROR on error. On failure, the
+ * packet is unchanged.
+ */
+int av_packet_make_refcounted(AVPacket* pkt);
+
+/**
+ * Create a writable reference for the data described by a given packet,
+ * avoiding data copy if possible.
+ *
+ * @param pkt Packet whose data should be made writable.
+ *
+ * @return 0 on success, a negative AVERROR on failure. On failure, the
+ * packet is unchanged.
+ */
+int av_packet_make_writable(AVPacket* pkt);
+
+/**
+ * Convert valid timing fields (timestamps / durations) in a packet from one
+ * timebase to another. Timestamps with unknown values (AV_NOPTS_VALUE) will be
+ * ignored.
+ *
+ * @param pkt packet on which the conversion will be performed
+ * @param tb_src source timebase, in which the timing fields in pkt are
+ * expressed
+ * @param tb_dst destination timebase, to which the timing fields will be
+ * converted
+ */
+void av_packet_rescale_ts(AVPacket* pkt, AVRational tb_src, AVRational tb_dst);
+
+/**
+ * @}
+ */
+
+#endif // AVCODEC_PACKET_H
diff --git a/dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/vdpau.h b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/vdpau.h
new file mode 100644
index 0000000000..4f0d956ce7
--- /dev/null
+++ b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/vdpau.h
@@ -0,0 +1,168 @@
+/*
+ * The Video Decode and Presentation API for UNIX (VDPAU) is used for
+ * hardware-accelerated decoding of MPEG-1/2, H.264 and VC-1.
+ *
+ * Copyright (C) 2008 NVIDIA
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVCODEC_VDPAU_H
+#define AVCODEC_VDPAU_H
+
+/**
+ * @file
+ * @ingroup lavc_codec_hwaccel_vdpau
+ * Public libavcodec VDPAU header.
+ */
+
+/**
+ * @defgroup lavc_codec_hwaccel_vdpau VDPAU Decoder and Renderer
+ * @ingroup lavc_codec_hwaccel
+ *
+ * VDPAU hardware acceleration has two modules
+ * - VDPAU decoding
+ * - VDPAU presentation
+ *
+ * The VDPAU decoding module parses all headers using FFmpeg
+ * parsing mechanisms and uses VDPAU for the actual decoding.
+ *
+ * As per the current implementation, the actual decoding
+ * and rendering (API calls) are done as part of the VDPAU
+ * presentation (vo_vdpau.c) module.
+ *
+ * @{
+ */
+
+#include <vdpau/vdpau.h>
+
+#include "libavutil/avconfig.h"
+#include "libavutil/attributes.h"
+
+#include "avcodec.h"
+
+struct AVCodecContext;
+struct AVFrame;
+
+typedef int (*AVVDPAU_Render2)(struct AVCodecContext*, struct AVFrame*,
+ const VdpPictureInfo*, uint32_t,
+ const VdpBitstreamBuffer*);
+
+/**
+ * This structure is used to share data between the libavcodec library and
+ * the client video application.
+ * This structure will be allocated and stored in AVCodecContext.hwaccel_context
+ * by av_vdpau_bind_context(). Members can be set by the user once
+ * during initialization or through each AVCodecContext.get_buffer()
+ * function call. In any case, they must be valid prior to calling
+ * decoding functions.
+ *
+ * The size of this structure is not a part of the public ABI and must not
+ * be used outside of libavcodec.
+ */
+typedef struct AVVDPAUContext {
+ /**
+ * VDPAU decoder handle
+ *
+ * Set by user.
+ */
+ VdpDecoder decoder;
+
+ /**
+ * VDPAU decoder render callback
+ *
+ * Set by the user.
+ */
+ VdpDecoderRender* render;
+
+ AVVDPAU_Render2 render2;
+} AVVDPAUContext;
+
+#if FF_API_VDPAU_ALLOC_GET_SET
+/**
+ * @brief allocation function for AVVDPAUContext
+ *
+ * Allows extending the struct without breaking API/ABI
+ * @deprecated use av_vdpau_bind_context() instead
+ */
+attribute_deprecated AVVDPAUContext* av_alloc_vdpaucontext(void);
+
+/**
+ * @deprecated render2 is public and can be accessed directly
+ */
+attribute_deprecated AVVDPAU_Render2
+av_vdpau_hwaccel_get_render2(const AVVDPAUContext*);
+/**
+ * @deprecated render2 is public and can be accessed directly
+ */
+attribute_deprecated void av_vdpau_hwaccel_set_render2(AVVDPAUContext*,
+ AVVDPAU_Render2);
+#endif
+
+/**
+ * Associate a VDPAU device with a codec context for hardware acceleration.
+ * This function is meant to be called from the get_format() codec callback,
+ * or earlier. It can also be called after avcodec_flush_buffers() to change
+ * the underlying VDPAU device mid-stream (e.g. to recover from non-transparent
+ * display preemption).
+ *
+ * @note get_format() must return AV_PIX_FMT_VDPAU if this function completes
+ * successfully.
+ *
+ * @param avctx decoding context whose get_format() callback is invoked
+ * @param device VDPAU device handle to use for hardware acceleration
+ * @param get_proc_address VDPAU device driver
+ * @param flags zero of more OR'd AV_HWACCEL_FLAG_* flags
+ *
+ * @return 0 on success, an AVERROR code on failure.
+ */
+int av_vdpau_bind_context(AVCodecContext* avctx, VdpDevice device,
+ VdpGetProcAddress* get_proc_address, unsigned flags);
+
+/**
+ * Gets the parameters to create an adequate VDPAU video surface for the codec
+ * context using VDPAU hardware decoding acceleration.
+ *
+ * @note Behavior is undefined if the context was not successfully bound to a
+ * VDPAU device using av_vdpau_bind_context().
+ *
+ * @param avctx the codec context being used for decoding the stream
+ * @param type storage space for the VDPAU video surface chroma type
+ * (or NULL to ignore)
+ * @param width storage space for the VDPAU video surface pixel width
+ * (or NULL to ignore)
+ * @param height storage space for the VDPAU video surface pixel height
+ * (or NULL to ignore)
+ *
+ * @return 0 on success, a negative AVERROR code on failure.
+ */
+int av_vdpau_get_surface_parameters(AVCodecContext* avctx, VdpChromaType* type,
+ uint32_t* width, uint32_t* height);
+
+#if FF_API_VDPAU_ALLOC_GET_SET
+/**
+ * Allocate an AVVDPAUContext.
+ *
+ * @return Newly-allocated AVVDPAUContext or NULL on failure.
+ * @deprecated use av_vdpau_bind_context() instead
+ */
+attribute_deprecated AVVDPAUContext* av_vdpau_alloc_context(void);
+#endif
+
+/** @} */
+
+#endif /* AVCODEC_VDPAU_H */
diff --git a/dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/version.h b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/version.h
new file mode 100644
index 0000000000..8d91b7db19
--- /dev/null
+++ b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/version.h
@@ -0,0 +1,45 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVCODEC_VERSION_H
+#define AVCODEC_VERSION_H
+
+/**
+ * @file
+ * @ingroup libavc
+ * Libavcodec version macros.
+ */
+
+#include "libavutil/version.h"
+
+#include "version_major.h"
+
+#define LIBAVCODEC_VERSION_MINOR 5
+#define LIBAVCODEC_VERSION_MICRO 101
+
+#define LIBAVCODEC_VERSION_INT \
+ AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, LIBAVCODEC_VERSION_MINOR, \
+ LIBAVCODEC_VERSION_MICRO)
+#define LIBAVCODEC_VERSION \
+ AV_VERSION(LIBAVCODEC_VERSION_MAJOR, LIBAVCODEC_VERSION_MINOR, \
+ LIBAVCODEC_VERSION_MICRO)
+#define LIBAVCODEC_BUILD LIBAVCODEC_VERSION_INT
+
+#define LIBAVCODEC_IDENT "Lavc" AV_STRINGIFY(LIBAVCODEC_VERSION)
+
+#endif /* AVCODEC_VERSION_H */
diff --git a/dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/version_major.h b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/version_major.h
new file mode 100644
index 0000000000..f987a3dd04
--- /dev/null
+++ b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavcodec/version_major.h
@@ -0,0 +1,52 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVCODEC_VERSION_MAJOR_H
+#define AVCODEC_VERSION_MAJOR_H
+
+/**
+ * @file
+ * @ingroup libavc
+ * Libavcodec version macros.
+ */
+
+#define LIBAVCODEC_VERSION_MAJOR 61
+
+/**
+ * FF_API_* defines may be placed below to indicate public API that will be
+ * dropped at a future version bump. The defines themselves are not part of
+ * the public API and may change, break or disappear at any time.
+ *
+ * @note, when bumping the major version it is recommended to manually
+ * disable each FF_API_* in its own commit instead of disabling them all
+ * at once through the bump. This improves the git bisect-ability of the change.
+ */
+
+#define FF_API_INIT_PACKET (LIBAVCODEC_VERSION_MAJOR < 62)
+#define FF_API_SUBFRAMES (LIBAVCODEC_VERSION_MAJOR < 62)
+#define FF_API_TICKS_PER_FRAME (LIBAVCODEC_VERSION_MAJOR < 62)
+#define FF_API_DROPCHANGED (LIBAVCODEC_VERSION_MAJOR < 62)
+
+#define FF_API_AVFFT (LIBAVCODEC_VERSION_MAJOR < 62)
+#define FF_API_FF_PROFILE_LEVEL (LIBAVCODEC_VERSION_MAJOR < 62)
+#define FF_API_AVCODEC_CLOSE (LIBAVCODEC_VERSION_MAJOR < 62)
+#define FF_API_BUFFER_MIN_SIZE (LIBAVCODEC_VERSION_MAJOR < 62)
+#define FF_API_VDPAU_ALLOC_GET_SET (LIBAVCODEC_VERSION_MAJOR < 62)
+#define FF_API_QUALITY_FACTOR (LIBAVCODEC_VERSION_MAJOR < 62)
+
+#endif /* AVCODEC_VERSION_MAJOR_H */
diff --git a/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/attributes.h b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/attributes.h
new file mode 100644
index 0000000000..774d1fe916
--- /dev/null
+++ b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/attributes.h
@@ -0,0 +1,173 @@
+/*
+ * copyright (c) 2006 Michael Niedermayer <michaelni@gmx.at>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * Macro definitions for various function/variable attributes
+ */
+
+#ifndef AVUTIL_ATTRIBUTES_H
+#define AVUTIL_ATTRIBUTES_H
+
+#ifdef __GNUC__
+# define AV_GCC_VERSION_AT_LEAST(x, y) \
+ (__GNUC__ > (x) || __GNUC__ == (x) && __GNUC_MINOR__ >= (y))
+# define AV_GCC_VERSION_AT_MOST(x, y) \
+ (__GNUC__ < (x) || __GNUC__ == (x) && __GNUC_MINOR__ <= (y))
+#else
+# define AV_GCC_VERSION_AT_LEAST(x, y) 0
+# define AV_GCC_VERSION_AT_MOST(x, y) 0
+#endif
+
+#ifdef __has_builtin
+# define AV_HAS_BUILTIN(x) __has_builtin(x)
+#else
+# define AV_HAS_BUILTIN(x) 0
+#endif
+
+#ifndef av_always_inline
+# if AV_GCC_VERSION_AT_LEAST(3, 1)
+# define av_always_inline __attribute__((always_inline)) inline
+# elif defined(_MSC_VER)
+# define av_always_inline __forceinline
+# else
+# define av_always_inline inline
+# endif
+#endif
+
+#ifndef av_extern_inline
+# if defined(__ICL) && __ICL >= 1210 || defined(__GNUC_STDC_INLINE__)
+# define av_extern_inline extern inline
+# else
+# define av_extern_inline inline
+# endif
+#endif
+
+#if AV_GCC_VERSION_AT_LEAST(3, 4)
+# define av_warn_unused_result __attribute__((warn_unused_result))
+#else
+# define av_warn_unused_result
+#endif
+
+#if AV_GCC_VERSION_AT_LEAST(3, 1)
+# define av_noinline __attribute__((noinline))
+#elif defined(_MSC_VER)
+# define av_noinline __declspec(noinline)
+#else
+# define av_noinline
+#endif
+
+#if AV_GCC_VERSION_AT_LEAST(3, 1) || defined(__clang__)
+# define av_pure __attribute__((pure))
+#else
+# define av_pure
+#endif
+
+#if AV_GCC_VERSION_AT_LEAST(2, 6) || defined(__clang__)
+# define av_const __attribute__((const))
+#else
+# define av_const
+#endif
+
+#if AV_GCC_VERSION_AT_LEAST(4, 3) || defined(__clang__)
+# define av_cold __attribute__((cold))
+#else
+# define av_cold
+#endif
+
+#if AV_GCC_VERSION_AT_LEAST(4, 1) && !defined(__llvm__)
+# define av_flatten __attribute__((flatten))
+#else
+# define av_flatten
+#endif
+
+#if AV_GCC_VERSION_AT_LEAST(3, 1)
+# define attribute_deprecated __attribute__((deprecated))
+#elif defined(_MSC_VER)
+# define attribute_deprecated __declspec(deprecated)
+#else
+# define attribute_deprecated
+#endif
+
+/**
+ * Disable warnings about deprecated features
+ * This is useful for sections of code kept for backward compatibility and
+ * scheduled for removal.
+ */
+#ifndef AV_NOWARN_DEPRECATED
+# if AV_GCC_VERSION_AT_LEAST(4, 6) || defined(__clang__)
+# define AV_NOWARN_DEPRECATED(code) \
+ _Pragma("GCC diagnostic push") \
+ _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") \
+ code _Pragma("GCC diagnostic pop")
+# elif defined(_MSC_VER)
+# define AV_NOWARN_DEPRECATED(code) \
+ __pragma(warning(push)) __pragma(warning(disable : 4996)) code; \
+ __pragma(warning(pop))
+# else
+# define AV_NOWARN_DEPRECATED(code) code
+# endif
+#endif
+
+#if defined(__GNUC__) || defined(__clang__)
+# define av_unused __attribute__((unused))
+#else
+# define av_unused
+#endif
+
+/**
+ * Mark a variable as used and prevent the compiler from optimizing it
+ * away. This is useful for variables accessed only from inline
+ * assembler without the compiler being aware.
+ */
+#if AV_GCC_VERSION_AT_LEAST(3, 1) || defined(__clang__)
+# define av_used __attribute__((used))
+#else
+# define av_used
+#endif
+
+#if AV_GCC_VERSION_AT_LEAST(3, 3) || defined(__clang__)
+# define av_alias __attribute__((may_alias))
+#else
+# define av_alias
+#endif
+
+#if (defined(__GNUC__) || defined(__clang__)) && !defined(__INTEL_COMPILER)
+# define av_uninit(x) x = x
+#else
+# define av_uninit(x) x
+#endif
+
+#if defined(__GNUC__) || defined(__clang__)
+# define av_builtin_constant_p __builtin_constant_p
+# define av_printf_format(fmtpos, attrpos) \
+ __attribute__((__format__(__printf__, fmtpos, attrpos)))
+#else
+# define av_builtin_constant_p(x) 0
+# define av_printf_format(fmtpos, attrpos)
+#endif
+
+#if AV_GCC_VERSION_AT_LEAST(2, 5) || defined(__clang__)
+# define av_noreturn __attribute__((noreturn))
+#else
+# define av_noreturn
+#endif
+
+#endif /* AVUTIL_ATTRIBUTES_H */
diff --git a/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/avconfig.h b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/avconfig.h
new file mode 100644
index 0000000000..c289fbb551
--- /dev/null
+++ b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/avconfig.h
@@ -0,0 +1,6 @@
+/* Generated by ffmpeg configure */
+#ifndef AVUTIL_AVCONFIG_H
+#define AVUTIL_AVCONFIG_H
+#define AV_HAVE_BIGENDIAN 0
+#define AV_HAVE_FAST_UNALIGNED 1
+#endif /* AVUTIL_AVCONFIG_H */
diff --git a/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/avutil.h b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/avutil.h
new file mode 100644
index 0000000000..480a64e852
--- /dev/null
+++ b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/avutil.h
@@ -0,0 +1,363 @@
+/*
+ * copyright (c) 2006 Michael Niedermayer <michaelni@gmx.at>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVUTIL_AVUTIL_H
+#define AVUTIL_AVUTIL_H
+
+/**
+ * @file
+ * @ingroup lavu
+ * Convenience header that includes @ref lavu "libavutil"'s core.
+ */
+
+/**
+ * @mainpage
+ *
+ * @section ffmpeg_intro Introduction
+ *
+ * This document describes the usage of the different libraries
+ * provided by FFmpeg.
+ *
+ * @li @ref libavc "libavcodec" encoding/decoding library
+ * @li @ref lavfi "libavfilter" graph-based frame editing library
+ * @li @ref libavf "libavformat" I/O and muxing/demuxing library
+ * @li @ref lavd "libavdevice" special devices muxing/demuxing library
+ * @li @ref lavu "libavutil" common utility library
+ * @li @ref lswr "libswresample" audio resampling, format conversion and mixing
+ * @li @ref lpp "libpostproc" post processing library
+ * @li @ref libsws "libswscale" color conversion and scaling library
+ *
+ * @section ffmpeg_versioning Versioning and compatibility
+ *
+ * Each of the FFmpeg libraries contains a version.h header, which defines a
+ * major, minor and micro version number with the
+ * <em>LIBRARYNAME_VERSION_{MAJOR,MINOR,MICRO}</em> macros. The major version
+ * number is incremented with backward incompatible changes - e.g. removing
+ * parts of the public API, reordering public struct members, etc. The minor
+ * version number is incremented for backward compatible API changes or major
+ * new features - e.g. adding a new public function or a new decoder. The micro
+ * version number is incremented for smaller changes that a calling program
+ * might still want to check for - e.g. changing behavior in a previously
+ * unspecified situation.
+ *
+ * FFmpeg guarantees backward API and ABI compatibility for each library as long
+ * as its major version number is unchanged. This means that no public symbols
+ * will be removed or renamed. Types and names of the public struct members and
+ * values of public macros and enums will remain the same (unless they were
+ * explicitly declared as not part of the public API). Documented behavior will
+ * not change.
+ *
+ * In other words, any correct program that works with a given FFmpeg snapshot
+ * should work just as well without any changes with any later snapshot with the
+ * same major versions. This applies to both rebuilding the program against new
+ * FFmpeg versions or to replacing the dynamic FFmpeg libraries that a program
+ * links against.
+ *
+ * However, new public symbols may be added and new members may be appended to
+ * public structs whose size is not part of public ABI (most public structs in
+ * FFmpeg). New macros and enum values may be added. Behavior in undocumented
+ * situations may change slightly (and be documented). All those are accompanied
+ * by an entry in doc/APIchanges and incrementing either the minor or micro
+ * version number.
+ */
+
+/**
+ * @defgroup lavu libavutil
+ * Common code shared across all FFmpeg libraries.
+ *
+ * @note
+ * libavutil is designed to be modular. In most cases, in order to use the
+ * functions provided by one component of libavutil you must explicitly include
+ * the specific header containing that feature. If you are only using
+ * media-related components, you could simply include libavutil/avutil.h, which
+ * brings in most of the "core" components.
+ *
+ * @{
+ *
+ * @defgroup lavu_crypto Crypto and Hashing
+ *
+ * @{
+ * @}
+ *
+ * @defgroup lavu_math Mathematics
+ * @{
+ *
+ * @}
+ *
+ * @defgroup lavu_string String Manipulation
+ *
+ * @{
+ *
+ * @}
+ *
+ * @defgroup lavu_mem Memory Management
+ *
+ * @{
+ *
+ * @}
+ *
+ * @defgroup lavu_data Data Structures
+ * @{
+ *
+ * @}
+ *
+ * @defgroup lavu_video Video related
+ *
+ * @{
+ *
+ * @}
+ *
+ * @defgroup lavu_audio Audio related
+ *
+ * @{
+ *
+ * @}
+ *
+ * @defgroup lavu_error Error Codes
+ *
+ * @{
+ *
+ * @}
+ *
+ * @defgroup lavu_log Logging Facility
+ *
+ * @{
+ *
+ * @}
+ *
+ * @defgroup lavu_misc Other
+ *
+ * @{
+ *
+ * @defgroup preproc_misc Preprocessor String Macros
+ *
+ * @{
+ *
+ * @}
+ *
+ * @defgroup version_utils Library Version Macros
+ *
+ * @{
+ *
+ * @}
+ */
+
+/**
+ * @addtogroup lavu_ver
+ * @{
+ */
+
+/**
+ * Return the LIBAVUTIL_VERSION_INT constant.
+ */
+unsigned avutil_version(void);
+
+/**
+ * Return an informative version string. This usually is the actual release
+ * version number or a git commit description. This string has no fixed format
+ * and can change any time. It should never be parsed by code.
+ */
+const char* av_version_info(void);
+
+/**
+ * Return the libavutil build-time configuration.
+ */
+const char* avutil_configuration(void);
+
+/**
+ * Return the libavutil license.
+ */
+const char* avutil_license(void);
+
+/**
+ * @}
+ */
+
+/**
+ * @addtogroup lavu_media Media Type
+ * @brief Media Type
+ */
+
+enum AVMediaType {
+ AVMEDIA_TYPE_UNKNOWN = -1, ///< Usually treated as AVMEDIA_TYPE_DATA
+ AVMEDIA_TYPE_VIDEO,
+ AVMEDIA_TYPE_AUDIO,
+ AVMEDIA_TYPE_DATA, ///< Opaque data information usually continuous
+ AVMEDIA_TYPE_SUBTITLE,
+ AVMEDIA_TYPE_ATTACHMENT, ///< Opaque data information usually sparse
+ AVMEDIA_TYPE_NB
+};
+
+/**
+ * Return a string describing the media_type enum, NULL if media_type
+ * is unknown.
+ */
+const char* av_get_media_type_string(enum AVMediaType media_type);
+
+/**
+ * @defgroup lavu_const Constants
+ * @{
+ *
+ * @defgroup lavu_enc Encoding specific
+ *
+ * @note those definition should move to avcodec
+ * @{
+ */
+
+#define FF_LAMBDA_SHIFT 7
+#define FF_LAMBDA_SCALE (1 << FF_LAMBDA_SHIFT)
+#define FF_QP2LAMBDA 118 ///< factor to convert from H.263 QP to lambda
+#define FF_LAMBDA_MAX (256 * 128 - 1)
+
+#define FF_QUALITY_SCALE FF_LAMBDA_SCALE // FIXME maybe remove
+
+/**
+ * @}
+ * @defgroup lavu_time Timestamp specific
+ *
+ * FFmpeg internal timebase and timestamp definitions
+ *
+ * @{
+ */
+
+/**
+ * @brief Undefined timestamp value
+ *
+ * Usually reported by demuxer that work on containers that do not provide
+ * either pts or dts.
+ */
+
+#define AV_NOPTS_VALUE ((int64_t)UINT64_C(0x8000000000000000))
+
+/**
+ * Internal time base represented as integer
+ */
+
+#define AV_TIME_BASE 1000000
+
+/**
+ * Internal time base represented as fractional value
+ */
+
+#ifdef __cplusplus
+/* ISO C++ forbids compound-literals. */
+# define AV_TIME_BASE_Q av_make_q(1, AV_TIME_BASE)
+#else
+# define AV_TIME_BASE_Q \
+ (AVRational) { 1, AV_TIME_BASE }
+#endif
+
+/**
+ * @}
+ * @}
+ * @defgroup lavu_picture Image related
+ *
+ * AVPicture types, pixel formats and basic image planes manipulation.
+ *
+ * @{
+ */
+
+enum AVPictureType {
+ AV_PICTURE_TYPE_NONE = 0, ///< Undefined
+ AV_PICTURE_TYPE_I, ///< Intra
+ AV_PICTURE_TYPE_P, ///< Predicted
+ AV_PICTURE_TYPE_B, ///< Bi-dir predicted
+ AV_PICTURE_TYPE_S, ///< S(GMC)-VOP MPEG-4
+ AV_PICTURE_TYPE_SI, ///< Switching Intra
+ AV_PICTURE_TYPE_SP, ///< Switching Predicted
+ AV_PICTURE_TYPE_BI, ///< BI type
+};
+
+/**
+ * Return a single letter to describe the given picture type
+ * pict_type.
+ *
+ * @param[in] pict_type the picture type @return a single character
+ * representing the picture type, '?' if pict_type is unknown
+ */
+char av_get_picture_type_char(enum AVPictureType pict_type);
+
+/**
+ * @}
+ */
+
+#include "common.h"
+#include "rational.h"
+#include "version.h"
+#include "macros.h"
+#include "mathematics.h"
+#include "log.h"
+#include "pixfmt.h"
+
+/**
+ * Return x default pointer in case p is NULL.
+ */
+static inline void* av_x_if_null(const void* p, const void* x) {
+ return (void*)(intptr_t)(p ? p : x);
+}
+
+/**
+ * Compute the length of an integer list.
+ *
+ * @param elsize size in bytes of each list element (only 1, 2, 4 or 8)
+ * @param term list terminator (usually 0 or -1)
+ * @param list pointer to the list
+ * @return length of the list, in elements, not counting the terminator
+ */
+unsigned av_int_list_length_for_size(unsigned elsize, const void* list,
+ uint64_t term) av_pure;
+
+/**
+ * Compute the length of an integer list.
+ *
+ * @param term list terminator (usually 0 or -1)
+ * @param list pointer to the list
+ * @return length of the list, in elements, not counting the terminator
+ */
+#define av_int_list_length(list, term) \
+ av_int_list_length_for_size(sizeof(*(list)), list, term)
+
+/**
+ * Return the fractional representation of the internal time base.
+ */
+AVRational av_get_time_base_q(void);
+
+#define AV_FOURCC_MAX_STRING_SIZE 32
+
+#define av_fourcc2str(fourcc) \
+ av_fourcc_make_string((char[AV_FOURCC_MAX_STRING_SIZE]){0}, fourcc)
+
+/**
+ * Fill the provided buffer with a string containing a FourCC (four-character
+ * code) representation.
+ *
+ * @param buf a buffer with size in bytes of at least
+ * AV_FOURCC_MAX_STRING_SIZE
+ * @param fourcc the fourcc to represent
+ * @return the buffer in input
+ */
+char* av_fourcc_make_string(char* buf, uint32_t fourcc);
+
+/**
+ * @}
+ * @}
+ */
+
+#endif /* AVUTIL_AVUTIL_H */
diff --git a/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/buffer.h b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/buffer.h
new file mode 100644
index 0000000000..372de093f9
--- /dev/null
+++ b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/buffer.h
@@ -0,0 +1,324 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * @ingroup lavu_buffer
+ * refcounted data buffer API
+ */
+
+#ifndef AVUTIL_BUFFER_H
+#define AVUTIL_BUFFER_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+/**
+ * @defgroup lavu_buffer AVBuffer
+ * @ingroup lavu_data
+ *
+ * @{
+ * AVBuffer is an API for reference-counted data buffers.
+ *
+ * There are two core objects in this API -- AVBuffer and AVBufferRef. AVBuffer
+ * represents the data buffer itself; it is opaque and not meant to be accessed
+ * by the caller directly, but only through AVBufferRef. However, the caller may
+ * e.g. compare two AVBuffer pointers to check whether two different references
+ * are describing the same data buffer. AVBufferRef represents a single
+ * reference to an AVBuffer and it is the object that may be manipulated by the
+ * caller directly.
+ *
+ * There are two functions provided for creating a new AVBuffer with a single
+ * reference -- av_buffer_alloc() to just allocate a new buffer, and
+ * av_buffer_create() to wrap an existing array in an AVBuffer. From an existing
+ * reference, additional references may be created with av_buffer_ref().
+ * Use av_buffer_unref() to free a reference (this will automatically free the
+ * data once all the references are freed).
+ *
+ * The convention throughout this API and the rest of FFmpeg is such that the
+ * buffer is considered writable if there exists only one reference to it (and
+ * it has not been marked as read-only). The av_buffer_is_writable() function is
+ * provided to check whether this is true and av_buffer_make_writable() will
+ * automatically create a new writable buffer when necessary.
+ * Of course nothing prevents the calling code from violating this convention,
+ * however that is safe only when all the existing references are under its
+ * control.
+ *
+ * @note Referencing and unreferencing the buffers is thread-safe and thus
+ * may be done from multiple threads simultaneously without any need for
+ * additional locking.
+ *
+ * @note Two different references to the same buffer can point to different
+ * parts of the buffer (i.e. their AVBufferRef.data will not be equal).
+ */
+
+/**
+ * A reference counted buffer type. It is opaque and is meant to be used through
+ * references (AVBufferRef).
+ */
+typedef struct AVBuffer AVBuffer;
+
+/**
+ * A reference to a data buffer.
+ *
+ * The size of this struct is not a part of the public ABI and it is not meant
+ * to be allocated directly.
+ */
+typedef struct AVBufferRef {
+ AVBuffer* buffer;
+
+ /**
+ * The data buffer. It is considered writable if and only if
+ * this is the only reference to the buffer, in which case
+ * av_buffer_is_writable() returns 1.
+ */
+ uint8_t* data;
+ /**
+ * Size of data in bytes.
+ */
+ size_t size;
+} AVBufferRef;
+
+/**
+ * Allocate an AVBuffer of the given size using av_malloc().
+ *
+ * @return an AVBufferRef of given size or NULL when out of memory
+ */
+AVBufferRef* av_buffer_alloc(size_t size);
+
+/**
+ * Same as av_buffer_alloc(), except the returned buffer will be initialized
+ * to zero.
+ */
+AVBufferRef* av_buffer_allocz(size_t size);
+
+/**
+ * Always treat the buffer as read-only, even when it has only one
+ * reference.
+ */
+#define AV_BUFFER_FLAG_READONLY (1 << 0)
+
+/**
+ * Create an AVBuffer from an existing array.
+ *
+ * If this function is successful, data is owned by the AVBuffer. The caller may
+ * only access data through the returned AVBufferRef and references derived from
+ * it.
+ * If this function fails, data is left untouched.
+ * @param data data array
+ * @param size size of data in bytes
+ * @param free a callback for freeing this buffer's data
+ * @param opaque parameter to be got for processing or passed to free
+ * @param flags a combination of AV_BUFFER_FLAG_*
+ *
+ * @return an AVBufferRef referring to data on success, NULL on failure.
+ */
+AVBufferRef* av_buffer_create(uint8_t* data, size_t size,
+ void (*free)(void* opaque, uint8_t* data),
+ void* opaque, int flags);
+
+/**
+ * Default free callback, which calls av_free() on the buffer data.
+ * This function is meant to be passed to av_buffer_create(), not called
+ * directly.
+ */
+void av_buffer_default_free(void* opaque, uint8_t* data);
+
+/**
+ * Create a new reference to an AVBuffer.
+ *
+ * @return a new AVBufferRef referring to the same AVBuffer as buf or NULL on
+ * failure.
+ */
+AVBufferRef* av_buffer_ref(const AVBufferRef* buf);
+
+/**
+ * Free a given reference and automatically free the buffer if there are no more
+ * references to it.
+ *
+ * @param buf the reference to be freed. The pointer is set to NULL on return.
+ */
+void av_buffer_unref(AVBufferRef** buf);
+
+/**
+ * @return 1 if the caller may write to the data referred to by buf (which is
+ * true if and only if buf is the only reference to the underlying AVBuffer).
+ * Return 0 otherwise.
+ * A positive answer is valid until av_buffer_ref() is called on buf.
+ */
+int av_buffer_is_writable(const AVBufferRef* buf);
+
+/**
+ * @return the opaque parameter set by av_buffer_create.
+ */
+void* av_buffer_get_opaque(const AVBufferRef* buf);
+
+int av_buffer_get_ref_count(const AVBufferRef* buf);
+
+/**
+ * Create a writable reference from a given buffer reference, avoiding data copy
+ * if possible.
+ *
+ * @param buf buffer reference to make writable. On success, buf is either left
+ * untouched, or it is unreferenced and a new writable AVBufferRef is
+ * written in its place. On failure, buf is left untouched.
+ * @return 0 on success, a negative AVERROR on failure.
+ */
+int av_buffer_make_writable(AVBufferRef** buf);
+
+/**
+ * Reallocate a given buffer.
+ *
+ * @param buf a buffer reference to reallocate. On success, buf will be
+ * unreferenced and a new reference with the required size will be
+ * written in its place. On failure buf will be left untouched. *buf
+ * may be NULL, then a new buffer is allocated.
+ * @param size required new buffer size.
+ * @return 0 on success, a negative AVERROR on failure.
+ *
+ * @note the buffer is actually reallocated with av_realloc() only if it was
+ * initially allocated through av_buffer_realloc(NULL) and there is only one
+ * reference to it (i.e. the one passed to this function). In all other cases
+ * a new buffer is allocated and the data is copied.
+ */
+int av_buffer_realloc(AVBufferRef** buf, size_t size);
+
+/**
+ * Ensure dst refers to the same data as src.
+ *
+ * When *dst is already equivalent to src, do nothing. Otherwise unreference dst
+ * and replace it with a new reference to src.
+ *
+ * @param dst Pointer to either a valid buffer reference or NULL. On success,
+ * this will point to a buffer reference equivalent to src. On
+ * failure, dst will be left untouched.
+ * @param src A buffer reference to replace dst with. May be NULL, then this
+ * function is equivalent to av_buffer_unref(dst).
+ * @return 0 on success
+ * AVERROR(ENOMEM) on memory allocation failure.
+ */
+int av_buffer_replace(AVBufferRef** dst, const AVBufferRef* src);
+
+/**
+ * @}
+ */
+
+/**
+ * @defgroup lavu_bufferpool AVBufferPool
+ * @ingroup lavu_data
+ *
+ * @{
+ * AVBufferPool is an API for a lock-free thread-safe pool of AVBuffers.
+ *
+ * Frequently allocating and freeing large buffers may be slow. AVBufferPool is
+ * meant to solve this in cases when the caller needs a set of buffers of the
+ * same size (the most obvious use case being buffers for raw video or audio
+ * frames).
+ *
+ * At the beginning, the user must call av_buffer_pool_init() to create the
+ * buffer pool. Then whenever a buffer is needed, call av_buffer_pool_get() to
+ * get a reference to a new buffer, similar to av_buffer_alloc(). This new
+ * reference works in all aspects the same way as the one created by
+ * av_buffer_alloc(). However, when the last reference to this buffer is
+ * unreferenced, it is returned to the pool instead of being freed and will be
+ * reused for subsequent av_buffer_pool_get() calls.
+ *
+ * When the caller is done with the pool and no longer needs to allocate any new
+ * buffers, av_buffer_pool_uninit() must be called to mark the pool as freeable.
+ * Once all the buffers are released, it will automatically be freed.
+ *
+ * Allocating and releasing buffers with this API is thread-safe as long as
+ * either the default alloc callback is used, or the user-supplied one is
+ * thread-safe.
+ */
+
+/**
+ * The buffer pool. This structure is opaque and not meant to be accessed
+ * directly. It is allocated with av_buffer_pool_init() and freed with
+ * av_buffer_pool_uninit().
+ */
+typedef struct AVBufferPool AVBufferPool;
+
+/**
+ * Allocate and initialize a buffer pool.
+ *
+ * @param size size of each buffer in this pool
+ * @param alloc a function that will be used to allocate new buffers when the
+ * pool is empty. May be NULL, then the default allocator will be used
+ * (av_buffer_alloc()).
+ * @return newly created buffer pool on success, NULL on error.
+ */
+AVBufferPool* av_buffer_pool_init(size_t size,
+ AVBufferRef* (*alloc)(size_t size));
+
+/**
+ * Allocate and initialize a buffer pool with a more complex allocator.
+ *
+ * @param size size of each buffer in this pool
+ * @param opaque arbitrary user data used by the allocator
+ * @param alloc a function that will be used to allocate new buffers when the
+ * pool is empty. May be NULL, then the default allocator will be
+ * used (av_buffer_alloc()).
+ * @param pool_free a function that will be called immediately before the pool
+ * is freed. I.e. after av_buffer_pool_uninit() is called
+ * by the caller and all the frames are returned to the pool
+ * and freed. It is intended to uninitialize the user opaque
+ * data. May be NULL.
+ * @return newly created buffer pool on success, NULL on error.
+ */
+AVBufferPool* av_buffer_pool_init2(size_t size, void* opaque,
+ AVBufferRef* (*alloc)(void* opaque,
+ size_t size),
+ void (*pool_free)(void* opaque));
+
+/**
+ * Mark the pool as being available for freeing. It will actually be freed only
+ * once all the allocated buffers associated with the pool are released. Thus it
+ * is safe to call this function while some of the allocated buffers are still
+ * in use.
+ *
+ * @param pool pointer to the pool to be freed. It will be set to NULL.
+ */
+void av_buffer_pool_uninit(AVBufferPool** pool);
+
+/**
+ * Allocate a new AVBuffer, reusing an old buffer from the pool when available.
+ * This function may be called simultaneously from multiple threads.
+ *
+ * @return a reference to the new buffer on success, NULL on error.
+ */
+AVBufferRef* av_buffer_pool_get(AVBufferPool* pool);
+
+/**
+ * Query the original opaque parameter of an allocated buffer in the pool.
+ *
+ * @param ref a buffer reference to a buffer returned by av_buffer_pool_get.
+ * @return the opaque parameter set by the buffer allocator function of the
+ * buffer pool.
+ *
+ * @note the opaque parameter of ref is used by the buffer pool implementation,
+ * therefore you have to use this function to access the original opaque
+ * parameter of an allocated buffer.
+ */
+void* av_buffer_pool_buffer_get_opaque(const AVBufferRef* ref);
+
+/**
+ * @}
+ */
+
+#endif /* AVUTIL_BUFFER_H */
diff --git a/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/channel_layout.h b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/channel_layout.h
new file mode 100644
index 0000000000..df0abca669
--- /dev/null
+++ b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/channel_layout.h
@@ -0,0 +1,804 @@
+/*
+ * Copyright (c) 2006 Michael Niedermayer <michaelni@gmx.at>
+ * Copyright (c) 2008 Peter Ross
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVUTIL_CHANNEL_LAYOUT_H
+#define AVUTIL_CHANNEL_LAYOUT_H
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "version.h"
+#include "attributes.h"
+
+/**
+ * @file
+ * @ingroup lavu_audio_channels
+ * Public libavutil channel layout APIs header.
+ */
+
+/**
+ * @defgroup lavu_audio_channels Audio channels
+ * @ingroup lavu_audio
+ *
+ * Audio channel layout utility functions
+ *
+ * @{
+ */
+
+enum AVChannel {
+ ///< Invalid channel index
+ AV_CHAN_NONE = -1,
+ AV_CHAN_FRONT_LEFT,
+ AV_CHAN_FRONT_RIGHT,
+ AV_CHAN_FRONT_CENTER,
+ AV_CHAN_LOW_FREQUENCY,
+ AV_CHAN_BACK_LEFT,
+ AV_CHAN_BACK_RIGHT,
+ AV_CHAN_FRONT_LEFT_OF_CENTER,
+ AV_CHAN_FRONT_RIGHT_OF_CENTER,
+ AV_CHAN_BACK_CENTER,
+ AV_CHAN_SIDE_LEFT,
+ AV_CHAN_SIDE_RIGHT,
+ AV_CHAN_TOP_CENTER,
+ AV_CHAN_TOP_FRONT_LEFT,
+ AV_CHAN_TOP_FRONT_CENTER,
+ AV_CHAN_TOP_FRONT_RIGHT,
+ AV_CHAN_TOP_BACK_LEFT,
+ AV_CHAN_TOP_BACK_CENTER,
+ AV_CHAN_TOP_BACK_RIGHT,
+ /** Stereo downmix. */
+ AV_CHAN_STEREO_LEFT = 29,
+ /** See above. */
+ AV_CHAN_STEREO_RIGHT,
+ AV_CHAN_WIDE_LEFT,
+ AV_CHAN_WIDE_RIGHT,
+ AV_CHAN_SURROUND_DIRECT_LEFT,
+ AV_CHAN_SURROUND_DIRECT_RIGHT,
+ AV_CHAN_LOW_FREQUENCY_2,
+ AV_CHAN_TOP_SIDE_LEFT,
+ AV_CHAN_TOP_SIDE_RIGHT,
+ AV_CHAN_BOTTOM_FRONT_CENTER,
+ AV_CHAN_BOTTOM_FRONT_LEFT,
+ AV_CHAN_BOTTOM_FRONT_RIGHT,
+
+ /** Channel is empty can be safely skipped. */
+ AV_CHAN_UNUSED = 0x200,
+
+ /** Channel contains data, but its position is unknown. */
+ AV_CHAN_UNKNOWN = 0x300,
+
+ /**
+ * Range of channels between AV_CHAN_AMBISONIC_BASE and
+ * AV_CHAN_AMBISONIC_END represent Ambisonic components using the ACN system.
+ *
+ * Given a channel id `<i>` between AV_CHAN_AMBISONIC_BASE and
+ * AV_CHAN_AMBISONIC_END (inclusive), the ACN index of the channel `<n>` is
+ * `<n> = <i> - AV_CHAN_AMBISONIC_BASE`.
+ *
+ * @note these values are only used for AV_CHANNEL_ORDER_CUSTOM channel
+ * orderings, the AV_CHANNEL_ORDER_AMBISONIC ordering orders the channels
+ * implicitly by their position in the stream.
+ */
+ AV_CHAN_AMBISONIC_BASE = 0x400,
+ // leave space for 1024 ids, which correspond to maximum order-32 harmonics,
+ // which should be enough for the foreseeable use cases
+ AV_CHAN_AMBISONIC_END = 0x7ff,
+};
+
+enum AVChannelOrder {
+ /**
+ * Only the channel count is specified, without any further information
+ * about the channel order.
+ */
+ AV_CHANNEL_ORDER_UNSPEC,
+ /**
+ * The native channel order, i.e. the channels are in the same order in
+ * which they are defined in the AVChannel enum. This supports up to 63
+ * different channels.
+ */
+ AV_CHANNEL_ORDER_NATIVE,
+ /**
+ * The channel order does not correspond to any other predefined order and
+ * is stored as an explicit map. For example, this could be used to support
+ * layouts with 64 or more channels, or with empty/skipped (AV_CHAN_UNUSED)
+ * channels at arbitrary positions.
+ */
+ AV_CHANNEL_ORDER_CUSTOM,
+ /**
+ * The audio is represented as the decomposition of the sound field into
+ * spherical harmonics. Each channel corresponds to a single expansion
+ * component. Channels are ordered according to ACN (Ambisonic Channel
+ * Number).
+ *
+ * The channel with the index n in the stream contains the spherical
+ * harmonic of degree l and order m given by
+ * @code{.unparsed}
+ * l = floor(sqrt(n)),
+ * m = n - l * (l + 1).
+ * @endcode
+ *
+ * Conversely given a spherical harmonic of degree l and order m, the
+ * corresponding channel index n is given by
+ * @code{.unparsed}
+ * n = l * (l + 1) + m.
+ * @endcode
+ *
+ * Normalization is assumed to be SN3D (Schmidt Semi-Normalization)
+ * as defined in AmbiX format $ 2.1.
+ */
+ AV_CHANNEL_ORDER_AMBISONIC,
+ /**
+ * Number of channel orders, not part of ABI/API
+ */
+ FF_CHANNEL_ORDER_NB
+};
+
+/**
+ * @defgroup channel_masks Audio channel masks
+ *
+ * A channel layout is a 64-bits integer with a bit set for every channel.
+ * The number of bits set must be equal to the number of channels.
+ * The value 0 means that the channel layout is not known.
+ * @note this data structure is not powerful enough to handle channels
+ * combinations that have the same channel multiple times, such as
+ * dual-mono.
+ *
+ * @{
+ */
+#define AV_CH_FRONT_LEFT (1ULL << AV_CHAN_FRONT_LEFT)
+#define AV_CH_FRONT_RIGHT (1ULL << AV_CHAN_FRONT_RIGHT)
+#define AV_CH_FRONT_CENTER (1ULL << AV_CHAN_FRONT_CENTER)
+#define AV_CH_LOW_FREQUENCY (1ULL << AV_CHAN_LOW_FREQUENCY)
+#define AV_CH_BACK_LEFT (1ULL << AV_CHAN_BACK_LEFT)
+#define AV_CH_BACK_RIGHT (1ULL << AV_CHAN_BACK_RIGHT)
+#define AV_CH_FRONT_LEFT_OF_CENTER (1ULL << AV_CHAN_FRONT_LEFT_OF_CENTER)
+#define AV_CH_FRONT_RIGHT_OF_CENTER (1ULL << AV_CHAN_FRONT_RIGHT_OF_CENTER)
+#define AV_CH_BACK_CENTER (1ULL << AV_CHAN_BACK_CENTER)
+#define AV_CH_SIDE_LEFT (1ULL << AV_CHAN_SIDE_LEFT)
+#define AV_CH_SIDE_RIGHT (1ULL << AV_CHAN_SIDE_RIGHT)
+#define AV_CH_TOP_CENTER (1ULL << AV_CHAN_TOP_CENTER)
+#define AV_CH_TOP_FRONT_LEFT (1ULL << AV_CHAN_TOP_FRONT_LEFT)
+#define AV_CH_TOP_FRONT_CENTER (1ULL << AV_CHAN_TOP_FRONT_CENTER)
+#define AV_CH_TOP_FRONT_RIGHT (1ULL << AV_CHAN_TOP_FRONT_RIGHT)
+#define AV_CH_TOP_BACK_LEFT (1ULL << AV_CHAN_TOP_BACK_LEFT)
+#define AV_CH_TOP_BACK_CENTER (1ULL << AV_CHAN_TOP_BACK_CENTER)
+#define AV_CH_TOP_BACK_RIGHT (1ULL << AV_CHAN_TOP_BACK_RIGHT)
+#define AV_CH_STEREO_LEFT (1ULL << AV_CHAN_STEREO_LEFT)
+#define AV_CH_STEREO_RIGHT (1ULL << AV_CHAN_STEREO_RIGHT)
+#define AV_CH_WIDE_LEFT (1ULL << AV_CHAN_WIDE_LEFT)
+#define AV_CH_WIDE_RIGHT (1ULL << AV_CHAN_WIDE_RIGHT)
+#define AV_CH_SURROUND_DIRECT_LEFT (1ULL << AV_CHAN_SURROUND_DIRECT_LEFT)
+#define AV_CH_SURROUND_DIRECT_RIGHT (1ULL << AV_CHAN_SURROUND_DIRECT_RIGHT)
+#define AV_CH_LOW_FREQUENCY_2 (1ULL << AV_CHAN_LOW_FREQUENCY_2)
+#define AV_CH_TOP_SIDE_LEFT (1ULL << AV_CHAN_TOP_SIDE_LEFT)
+#define AV_CH_TOP_SIDE_RIGHT (1ULL << AV_CHAN_TOP_SIDE_RIGHT)
+#define AV_CH_BOTTOM_FRONT_CENTER (1ULL << AV_CHAN_BOTTOM_FRONT_CENTER)
+#define AV_CH_BOTTOM_FRONT_LEFT (1ULL << AV_CHAN_BOTTOM_FRONT_LEFT)
+#define AV_CH_BOTTOM_FRONT_RIGHT (1ULL << AV_CHAN_BOTTOM_FRONT_RIGHT)
+
+/**
+ * @}
+ * @defgroup channel_mask_c Audio channel layouts
+ * @{
+ * */
+#define AV_CH_LAYOUT_MONO (AV_CH_FRONT_CENTER)
+#define AV_CH_LAYOUT_STEREO (AV_CH_FRONT_LEFT | AV_CH_FRONT_RIGHT)
+#define AV_CH_LAYOUT_2POINT1 (AV_CH_LAYOUT_STEREO | AV_CH_LOW_FREQUENCY)
+#define AV_CH_LAYOUT_2_1 (AV_CH_LAYOUT_STEREO | AV_CH_BACK_CENTER)
+#define AV_CH_LAYOUT_SURROUND (AV_CH_LAYOUT_STEREO | AV_CH_FRONT_CENTER)
+#define AV_CH_LAYOUT_3POINT1 (AV_CH_LAYOUT_SURROUND | AV_CH_LOW_FREQUENCY)
+#define AV_CH_LAYOUT_4POINT0 (AV_CH_LAYOUT_SURROUND | AV_CH_BACK_CENTER)
+#define AV_CH_LAYOUT_4POINT1 (AV_CH_LAYOUT_4POINT0 | AV_CH_LOW_FREQUENCY)
+#define AV_CH_LAYOUT_2_2 \
+ (AV_CH_LAYOUT_STEREO | AV_CH_SIDE_LEFT | AV_CH_SIDE_RIGHT)
+#define AV_CH_LAYOUT_QUAD \
+ (AV_CH_LAYOUT_STEREO | AV_CH_BACK_LEFT | AV_CH_BACK_RIGHT)
+#define AV_CH_LAYOUT_5POINT0 \
+ (AV_CH_LAYOUT_SURROUND | AV_CH_SIDE_LEFT | AV_CH_SIDE_RIGHT)
+#define AV_CH_LAYOUT_5POINT1 (AV_CH_LAYOUT_5POINT0 | AV_CH_LOW_FREQUENCY)
+#define AV_CH_LAYOUT_5POINT0_BACK \
+ (AV_CH_LAYOUT_SURROUND | AV_CH_BACK_LEFT | AV_CH_BACK_RIGHT)
+#define AV_CH_LAYOUT_5POINT1_BACK \
+ (AV_CH_LAYOUT_5POINT0_BACK | AV_CH_LOW_FREQUENCY)
+#define AV_CH_LAYOUT_6POINT0 (AV_CH_LAYOUT_5POINT0 | AV_CH_BACK_CENTER)
+#define AV_CH_LAYOUT_6POINT0_FRONT \
+ (AV_CH_LAYOUT_2_2 | AV_CH_FRONT_LEFT_OF_CENTER | AV_CH_FRONT_RIGHT_OF_CENTER)
+#define AV_CH_LAYOUT_HEXAGONAL (AV_CH_LAYOUT_5POINT0_BACK | AV_CH_BACK_CENTER)
+#define AV_CH_LAYOUT_3POINT1POINT2 \
+ (AV_CH_LAYOUT_3POINT1 | AV_CH_TOP_FRONT_LEFT | AV_CH_TOP_FRONT_RIGHT)
+#define AV_CH_LAYOUT_6POINT1 (AV_CH_LAYOUT_5POINT1 | AV_CH_BACK_CENTER)
+#define AV_CH_LAYOUT_6POINT1_BACK \
+ (AV_CH_LAYOUT_5POINT1_BACK | AV_CH_BACK_CENTER)
+#define AV_CH_LAYOUT_6POINT1_FRONT \
+ (AV_CH_LAYOUT_6POINT0_FRONT | AV_CH_LOW_FREQUENCY)
+#define AV_CH_LAYOUT_7POINT0 \
+ (AV_CH_LAYOUT_5POINT0 | AV_CH_BACK_LEFT | AV_CH_BACK_RIGHT)
+#define AV_CH_LAYOUT_7POINT0_FRONT \
+ (AV_CH_LAYOUT_5POINT0 | AV_CH_FRONT_LEFT_OF_CENTER | \
+ AV_CH_FRONT_RIGHT_OF_CENTER)
+#define AV_CH_LAYOUT_7POINT1 \
+ (AV_CH_LAYOUT_5POINT1 | AV_CH_BACK_LEFT | AV_CH_BACK_RIGHT)
+#define AV_CH_LAYOUT_7POINT1_WIDE \
+ (AV_CH_LAYOUT_5POINT1 | AV_CH_FRONT_LEFT_OF_CENTER | \
+ AV_CH_FRONT_RIGHT_OF_CENTER)
+#define AV_CH_LAYOUT_7POINT1_WIDE_BACK \
+ (AV_CH_LAYOUT_5POINT1_BACK | AV_CH_FRONT_LEFT_OF_CENTER | \
+ AV_CH_FRONT_RIGHT_OF_CENTER)
+#define AV_CH_LAYOUT_5POINT1POINT2_BACK \
+ (AV_CH_LAYOUT_5POINT1_BACK | AV_CH_TOP_FRONT_LEFT | AV_CH_TOP_FRONT_RIGHT)
+#define AV_CH_LAYOUT_OCTAGONAL \
+ (AV_CH_LAYOUT_5POINT0 | AV_CH_BACK_LEFT | AV_CH_BACK_CENTER | \
+ AV_CH_BACK_RIGHT)
+#define AV_CH_LAYOUT_CUBE \
+ (AV_CH_LAYOUT_QUAD | AV_CH_TOP_FRONT_LEFT | AV_CH_TOP_FRONT_RIGHT | \
+ AV_CH_TOP_BACK_LEFT | AV_CH_TOP_BACK_RIGHT)
+#define AV_CH_LAYOUT_5POINT1POINT4_BACK \
+ (AV_CH_LAYOUT_5POINT1POINT2_BACK | AV_CH_TOP_BACK_LEFT | AV_CH_TOP_BACK_RIGHT)
+#define AV_CH_LAYOUT_7POINT1POINT2 \
+ (AV_CH_LAYOUT_7POINT1 | AV_CH_TOP_FRONT_LEFT | AV_CH_TOP_FRONT_RIGHT)
+#define AV_CH_LAYOUT_7POINT1POINT4_BACK \
+ (AV_CH_LAYOUT_7POINT1POINT2 | AV_CH_TOP_BACK_LEFT | AV_CH_TOP_BACK_RIGHT)
+#define AV_CH_LAYOUT_7POINT2POINT3 \
+ (AV_CH_LAYOUT_7POINT1POINT2 | AV_CH_TOP_BACK_CENTER | AV_CH_LOW_FREQUENCY_2)
+#define AV_CH_LAYOUT_9POINT1POINT4_BACK \
+ (AV_CH_LAYOUT_7POINT1POINT4_BACK | AV_CH_FRONT_LEFT_OF_CENTER | \
+ AV_CH_FRONT_RIGHT_OF_CENTER)
+#define AV_CH_LAYOUT_HEXADECAGONAL \
+ (AV_CH_LAYOUT_OCTAGONAL | AV_CH_WIDE_LEFT | AV_CH_WIDE_RIGHT | \
+ AV_CH_TOP_BACK_LEFT | AV_CH_TOP_BACK_RIGHT | AV_CH_TOP_BACK_CENTER | \
+ AV_CH_TOP_FRONT_CENTER | AV_CH_TOP_FRONT_LEFT | AV_CH_TOP_FRONT_RIGHT)
+#define AV_CH_LAYOUT_STEREO_DOWNMIX (AV_CH_STEREO_LEFT | AV_CH_STEREO_RIGHT)
+#define AV_CH_LAYOUT_22POINT2 \
+ (AV_CH_LAYOUT_7POINT1POINT4_BACK | AV_CH_FRONT_LEFT_OF_CENTER | \
+ AV_CH_FRONT_RIGHT_OF_CENTER | AV_CH_BACK_CENTER | AV_CH_LOW_FREQUENCY_2 | \
+ AV_CH_TOP_FRONT_CENTER | AV_CH_TOP_CENTER | AV_CH_TOP_SIDE_LEFT | \
+ AV_CH_TOP_SIDE_RIGHT | AV_CH_TOP_BACK_CENTER | AV_CH_BOTTOM_FRONT_CENTER | \
+ AV_CH_BOTTOM_FRONT_LEFT | AV_CH_BOTTOM_FRONT_RIGHT)
+
+#define AV_CH_LAYOUT_7POINT1_TOP_BACK AV_CH_LAYOUT_5POINT1POINT2_BACK
+
+enum AVMatrixEncoding {
+ AV_MATRIX_ENCODING_NONE,
+ AV_MATRIX_ENCODING_DOLBY,
+ AV_MATRIX_ENCODING_DPLII,
+ AV_MATRIX_ENCODING_DPLIIX,
+ AV_MATRIX_ENCODING_DPLIIZ,
+ AV_MATRIX_ENCODING_DOLBYEX,
+ AV_MATRIX_ENCODING_DOLBYHEADPHONE,
+ AV_MATRIX_ENCODING_NB
+};
+
+/**
+ * @}
+ */
+
+/**
+ * An AVChannelCustom defines a single channel within a custom order layout
+ *
+ * Unlike most structures in FFmpeg, sizeof(AVChannelCustom) is a part of the
+ * public ABI.
+ *
+ * No new fields may be added to it without a major version bump.
+ */
+typedef struct AVChannelCustom {
+ enum AVChannel id;
+ char name[16];
+ void* opaque;
+} AVChannelCustom;
+
+/**
+ * An AVChannelLayout holds information about the channel layout of audio data.
+ *
+ * A channel layout here is defined as a set of channels ordered in a specific
+ * way (unless the channel order is AV_CHANNEL_ORDER_UNSPEC, in which case an
+ * AVChannelLayout carries only the channel count).
+ * All orders may be treated as if they were AV_CHANNEL_ORDER_UNSPEC by
+ * ignoring everything but the channel count, as long as
+ * av_channel_layout_check() considers they are valid.
+ *
+ * Unlike most structures in FFmpeg, sizeof(AVChannelLayout) is a part of the
+ * public ABI and may be used by the caller. E.g. it may be allocated on stack
+ * or embedded in caller-defined structs.
+ *
+ * AVChannelLayout can be initialized as follows:
+ * - default initialization with {0}, followed by setting all used fields
+ * correctly;
+ * - by assigning one of the predefined AV_CHANNEL_LAYOUT_* initializers;
+ * - with a constructor function, such as av_channel_layout_default(),
+ * av_channel_layout_from_mask() or av_channel_layout_from_string().
+ *
+ * The channel layout must be unitialized with av_channel_layout_uninit()
+ *
+ * Copying an AVChannelLayout via assigning is forbidden,
+ * av_channel_layout_copy() must be used instead (and its return value should
+ * be checked)
+ *
+ * No new fields may be added to it without a major version bump, except for
+ * new elements of the union fitting in sizeof(uint64_t).
+ */
+typedef struct AVChannelLayout {
+ /**
+ * Channel order used in this layout.
+ * This is a mandatory field.
+ */
+ enum AVChannelOrder order;
+
+ /**
+ * Number of channels in this layout. Mandatory field.
+ */
+ int nb_channels;
+
+ /**
+ * Details about which channels are present in this layout.
+ * For AV_CHANNEL_ORDER_UNSPEC, this field is undefined and must not be
+ * used.
+ */
+ union {
+ /**
+ * This member must be used for AV_CHANNEL_ORDER_NATIVE, and may be used
+ * for AV_CHANNEL_ORDER_AMBISONIC to signal non-diegetic channels.
+ * It is a bitmask, where the position of each set bit means that the
+ * AVChannel with the corresponding value is present.
+ *
+ * I.e. when (mask & (1 << AV_CHAN_FOO)) is non-zero, then AV_CHAN_FOO
+ * is present in the layout. Otherwise it is not present.
+ *
+ * @note when a channel layout using a bitmask is constructed or
+ * modified manually (i.e. not using any of the av_channel_layout_*
+ * functions), the code doing it must ensure that the number of set bits
+ * is equal to nb_channels.
+ */
+ uint64_t mask;
+ /**
+ * This member must be used when the channel order is
+ * AV_CHANNEL_ORDER_CUSTOM. It is a nb_channels-sized array, with each
+ * element signalling the presence of the AVChannel with the
+ * corresponding value in map[i].id.
+ *
+ * I.e. when map[i].id is equal to AV_CHAN_FOO, then AV_CH_FOO is the
+ * i-th channel in the audio data.
+ *
+ * When map[i].id is in the range between AV_CHAN_AMBISONIC_BASE and
+ * AV_CHAN_AMBISONIC_END (inclusive), the channel contains an ambisonic
+ * component with ACN index (as defined above)
+ * n = map[i].id - AV_CHAN_AMBISONIC_BASE.
+ *
+ * map[i].name may be filled with a 0-terminated string, in which case
+ * it will be used for the purpose of identifying the channel with the
+ * convenience functions below. Otherise it must be zeroed.
+ */
+ AVChannelCustom* map;
+ } u;
+
+ /**
+ * For some private data of the user.
+ */
+ void* opaque;
+} AVChannelLayout;
+
+/**
+ * Macro to define native channel layouts
+ *
+ * @note This doesn't use designated initializers for compatibility with C++ 17
+ * and older.
+ */
+#define AV_CHANNEL_LAYOUT_MASK(nb, m) \
+ { /* .order */ \
+ AV_CHANNEL_ORDER_NATIVE, /* .nb_channels */ (nb), /* .u.mask */ {m}, \
+ /* .opaque */ NULL \
+ }
+
+/**
+ * @name Common pre-defined channel layouts
+ * @{
+ */
+#define AV_CHANNEL_LAYOUT_MONO AV_CHANNEL_LAYOUT_MASK(1, AV_CH_LAYOUT_MONO)
+#define AV_CHANNEL_LAYOUT_STEREO AV_CHANNEL_LAYOUT_MASK(2, AV_CH_LAYOUT_STEREO)
+#define AV_CHANNEL_LAYOUT_2POINT1 \
+ AV_CHANNEL_LAYOUT_MASK(3, AV_CH_LAYOUT_2POINT1)
+#define AV_CHANNEL_LAYOUT_2_1 AV_CHANNEL_LAYOUT_MASK(3, AV_CH_LAYOUT_2_1)
+#define AV_CHANNEL_LAYOUT_SURROUND \
+ AV_CHANNEL_LAYOUT_MASK(3, AV_CH_LAYOUT_SURROUND)
+#define AV_CHANNEL_LAYOUT_3POINT1 \
+ AV_CHANNEL_LAYOUT_MASK(4, AV_CH_LAYOUT_3POINT1)
+#define AV_CHANNEL_LAYOUT_4POINT0 \
+ AV_CHANNEL_LAYOUT_MASK(4, AV_CH_LAYOUT_4POINT0)
+#define AV_CHANNEL_LAYOUT_4POINT1 \
+ AV_CHANNEL_LAYOUT_MASK(5, AV_CH_LAYOUT_4POINT1)
+#define AV_CHANNEL_LAYOUT_2_2 AV_CHANNEL_LAYOUT_MASK(4, AV_CH_LAYOUT_2_2)
+#define AV_CHANNEL_LAYOUT_QUAD AV_CHANNEL_LAYOUT_MASK(4, AV_CH_LAYOUT_QUAD)
+#define AV_CHANNEL_LAYOUT_5POINT0 \
+ AV_CHANNEL_LAYOUT_MASK(5, AV_CH_LAYOUT_5POINT0)
+#define AV_CHANNEL_LAYOUT_5POINT1 \
+ AV_CHANNEL_LAYOUT_MASK(6, AV_CH_LAYOUT_5POINT1)
+#define AV_CHANNEL_LAYOUT_5POINT0_BACK \
+ AV_CHANNEL_LAYOUT_MASK(5, AV_CH_LAYOUT_5POINT0_BACK)
+#define AV_CHANNEL_LAYOUT_5POINT1_BACK \
+ AV_CHANNEL_LAYOUT_MASK(6, AV_CH_LAYOUT_5POINT1_BACK)
+#define AV_CHANNEL_LAYOUT_6POINT0 \
+ AV_CHANNEL_LAYOUT_MASK(6, AV_CH_LAYOUT_6POINT0)
+#define AV_CHANNEL_LAYOUT_6POINT0_FRONT \
+ AV_CHANNEL_LAYOUT_MASK(6, AV_CH_LAYOUT_6POINT0_FRONT)
+#define AV_CHANNEL_LAYOUT_3POINT1POINT2 \
+ AV_CHANNEL_LAYOUT_MASK(6, AV_CH_LAYOUT_3POINT1POINT2)
+#define AV_CHANNEL_LAYOUT_HEXAGONAL \
+ AV_CHANNEL_LAYOUT_MASK(6, AV_CH_LAYOUT_HEXAGONAL)
+#define AV_CHANNEL_LAYOUT_6POINT1 \
+ AV_CHANNEL_LAYOUT_MASK(7, AV_CH_LAYOUT_6POINT1)
+#define AV_CHANNEL_LAYOUT_6POINT1_BACK \
+ AV_CHANNEL_LAYOUT_MASK(7, AV_CH_LAYOUT_6POINT1_BACK)
+#define AV_CHANNEL_LAYOUT_6POINT1_FRONT \
+ AV_CHANNEL_LAYOUT_MASK(7, AV_CH_LAYOUT_6POINT1_FRONT)
+#define AV_CHANNEL_LAYOUT_7POINT0 \
+ AV_CHANNEL_LAYOUT_MASK(7, AV_CH_LAYOUT_7POINT0)
+#define AV_CHANNEL_LAYOUT_7POINT0_FRONT \
+ AV_CHANNEL_LAYOUT_MASK(7, AV_CH_LAYOUT_7POINT0_FRONT)
+#define AV_CHANNEL_LAYOUT_7POINT1 \
+ AV_CHANNEL_LAYOUT_MASK(8, AV_CH_LAYOUT_7POINT1)
+#define AV_CHANNEL_LAYOUT_7POINT1_WIDE \
+ AV_CHANNEL_LAYOUT_MASK(8, AV_CH_LAYOUT_7POINT1_WIDE)
+#define AV_CHANNEL_LAYOUT_7POINT1_WIDE_BACK \
+ AV_CHANNEL_LAYOUT_MASK(8, AV_CH_LAYOUT_7POINT1_WIDE_BACK)
+#define AV_CHANNEL_LAYOUT_5POINT1POINT2_BACK \
+ AV_CHANNEL_LAYOUT_MASK(8, AV_CH_LAYOUT_5POINT1POINT2_BACK)
+#define AV_CHANNEL_LAYOUT_OCTAGONAL \
+ AV_CHANNEL_LAYOUT_MASK(8, AV_CH_LAYOUT_OCTAGONAL)
+#define AV_CHANNEL_LAYOUT_CUBE AV_CHANNEL_LAYOUT_MASK(8, AV_CH_LAYOUT_CUBE)
+#define AV_CHANNEL_LAYOUT_5POINT1POINT4_BACK \
+ AV_CHANNEL_LAYOUT_MASK(10, AV_CH_LAYOUT_5POINT1POINT4_BACK)
+#define AV_CHANNEL_LAYOUT_7POINT1POINT2 \
+ AV_CHANNEL_LAYOUT_MASK(10, AV_CH_LAYOUT_7POINT1POINT2)
+#define AV_CHANNEL_LAYOUT_7POINT1POINT4_BACK \
+ AV_CHANNEL_LAYOUT_MASK(12, AV_CH_LAYOUT_7POINT1POINT4_BACK)
+#define AV_CHANNEL_LAYOUT_7POINT2POINT3 \
+ AV_CHANNEL_LAYOUT_MASK(12, AV_CH_LAYOUT_7POINT2POINT3)
+#define AV_CHANNEL_LAYOUT_9POINT1POINT4_BACK \
+ AV_CHANNEL_LAYOUT_MASK(14, AV_CH_LAYOUT_9POINT1POINT4_BACK)
+#define AV_CHANNEL_LAYOUT_HEXADECAGONAL \
+ AV_CHANNEL_LAYOUT_MASK(16, AV_CH_LAYOUT_HEXADECAGONAL)
+#define AV_CHANNEL_LAYOUT_STEREO_DOWNMIX \
+ AV_CHANNEL_LAYOUT_MASK(2, AV_CH_LAYOUT_STEREO_DOWNMIX)
+#define AV_CHANNEL_LAYOUT_22POINT2 \
+ AV_CHANNEL_LAYOUT_MASK(24, AV_CH_LAYOUT_22POINT2)
+
+#define AV_CHANNEL_LAYOUT_7POINT1_TOP_BACK AV_CHANNEL_LAYOUT_5POINT1POINT2_BACK
+
+#define AV_CHANNEL_LAYOUT_AMBISONIC_FIRST_ORDER \
+ { /* .order */ \
+ AV_CHANNEL_ORDER_AMBISONIC, /* .nb_channels */ 4, /* .u.mask */ {0}, \
+ /* .opaque */ NULL \
+ }
+/** @} */
+
+struct AVBPrint;
+
+/**
+ * Get a human readable string in an abbreviated form describing a given
+ * channel. This is the inverse function of @ref av_channel_from_string().
+ *
+ * @param buf pre-allocated buffer where to put the generated string
+ * @param buf_size size in bytes of the buffer.
+ * @param channel the AVChannel whose name to get
+ * @return amount of bytes needed to hold the output string, or a negative
+ * AVERROR on failure. If the returned value is bigger than buf_size, then the
+ * string was truncated.
+ */
+int av_channel_name(char* buf, size_t buf_size, enum AVChannel channel);
+
+/**
+ * bprint variant of av_channel_name().
+ *
+ * @note the string will be appended to the bprint buffer.
+ */
+void av_channel_name_bprint(struct AVBPrint* bp, enum AVChannel channel_id);
+
+/**
+ * Get a human readable string describing a given channel.
+ *
+ * @param buf pre-allocated buffer where to put the generated string
+ * @param buf_size size in bytes of the buffer.
+ * @param channel the AVChannel whose description to get
+ * @return amount of bytes needed to hold the output string, or a negative
+ * AVERROR on failure. If the returned value is bigger than buf_size, then the
+ * string was truncated.
+ */
+int av_channel_description(char* buf, size_t buf_size, enum AVChannel channel);
+
+/**
+ * bprint variant of av_channel_description().
+ *
+ * @note the string will be appended to the bprint buffer.
+ */
+void av_channel_description_bprint(struct AVBPrint* bp,
+ enum AVChannel channel_id);
+
+/**
+ * This is the inverse function of @ref av_channel_name().
+ *
+ * @return the channel with the given name
+ * AV_CHAN_NONE when name does not identify a known channel
+ */
+enum AVChannel av_channel_from_string(const char* name);
+
+/**
+ * Initialize a custom channel layout with the specified number of channels.
+ * The channel map will be allocated and the designation of all channels will
+ * be set to AV_CHAN_UNKNOWN.
+ *
+ * This is only a convenience helper function, a custom channel layout can also
+ * be constructed without using this.
+ *
+ * @param channel_layout the layout structure to be initialized
+ * @param nb_channels the number of channels
+ *
+ * @return 0 on success
+ * AVERROR(EINVAL) if the number of channels <= 0
+ * AVERROR(ENOMEM) if the channel map could not be allocated
+ */
+int av_channel_layout_custom_init(AVChannelLayout* channel_layout,
+ int nb_channels);
+
+/**
+ * Initialize a native channel layout from a bitmask indicating which channels
+ * are present.
+ *
+ * @param channel_layout the layout structure to be initialized
+ * @param mask bitmask describing the channel layout
+ *
+ * @return 0 on success
+ * AVERROR(EINVAL) for invalid mask values
+ */
+int av_channel_layout_from_mask(AVChannelLayout* channel_layout, uint64_t mask);
+
+/**
+ * Initialize a channel layout from a given string description.
+ * The input string can be represented by:
+ * - the formal channel layout name (returned by av_channel_layout_describe())
+ * - single or multiple channel names (returned by av_channel_name(), eg. "FL",
+ * or concatenated with "+", each optionally containing a custom name after
+ * a "@", eg. "FL@Left+FR@Right+LFE")
+ * - a decimal or hexadecimal value of a native channel layout (eg. "4" or
+ * "0x4")
+ * - the number of channels with default layout (eg. "4c")
+ * - the number of unordered channels (eg. "4C" or "4 channels")
+ * - the ambisonic order followed by optional non-diegetic channels (eg.
+ * "ambisonic 2+stereo")
+ * On error, the channel layout will remain uninitialized, but not necessarily
+ * untouched.
+ *
+ * @param channel_layout uninitialized channel layout for the result
+ * @param str string describing the channel layout
+ * @return 0 on success parsing the channel layout
+ * AVERROR(EINVAL) if an invalid channel layout string was provided
+ * AVERROR(ENOMEM) if there was not enough memory
+ */
+int av_channel_layout_from_string(AVChannelLayout* channel_layout,
+ const char* str);
+
+/**
+ * Get the default channel layout for a given number of channels.
+ *
+ * @param ch_layout the layout structure to be initialized
+ * @param nb_channels number of channels
+ */
+void av_channel_layout_default(AVChannelLayout* ch_layout, int nb_channels);
+
+/**
+ * Iterate over all standard channel layouts.
+ *
+ * @param opaque a pointer where libavutil will store the iteration state. Must
+ * point to NULL to start the iteration.
+ *
+ * @return the standard channel layout or NULL when the iteration is
+ * finished
+ */
+const AVChannelLayout* av_channel_layout_standard(void** opaque);
+
+/**
+ * Free any allocated data in the channel layout and reset the channel
+ * count to 0.
+ *
+ * @param channel_layout the layout structure to be uninitialized
+ */
+void av_channel_layout_uninit(AVChannelLayout* channel_layout);
+
+/**
+ * Make a copy of a channel layout. This differs from just assigning src to dst
+ * in that it allocates and copies the map for AV_CHANNEL_ORDER_CUSTOM.
+ *
+ * @note the destination channel_layout will be always uninitialized before
+ * copy.
+ *
+ * @param dst destination channel layout
+ * @param src source channel layout
+ * @return 0 on success, a negative AVERROR on error.
+ */
+int av_channel_layout_copy(AVChannelLayout* dst, const AVChannelLayout* src);
+
+/**
+ * Get a human-readable string describing the channel layout properties.
+ * The string will be in the same format that is accepted by
+ * @ref av_channel_layout_from_string(), allowing to rebuild the same
+ * channel layout, except for opaque pointers.
+ *
+ * @param channel_layout channel layout to be described
+ * @param buf pre-allocated buffer where to put the generated string
+ * @param buf_size size in bytes of the buffer.
+ * @return amount of bytes needed to hold the output string, or a negative
+ * AVERROR on failure. If the returned value is bigger than buf_size, then the
+ * string was truncated.
+ */
+int av_channel_layout_describe(const AVChannelLayout* channel_layout, char* buf,
+ size_t buf_size);
+
+/**
+ * bprint variant of av_channel_layout_describe().
+ *
+ * @note the string will be appended to the bprint buffer.
+ * @return 0 on success, or a negative AVERROR value on failure.
+ */
+int av_channel_layout_describe_bprint(const AVChannelLayout* channel_layout,
+ struct AVBPrint* bp);
+
+/**
+ * Get the channel with the given index in a channel layout.
+ *
+ * @param channel_layout input channel layout
+ * @param idx index of the channel
+ * @return channel with the index idx in channel_layout on success or
+ * AV_CHAN_NONE on failure (if idx is not valid or the channel order is
+ * unspecified)
+ */
+enum AVChannel av_channel_layout_channel_from_index(
+ const AVChannelLayout* channel_layout, unsigned int idx);
+
+/**
+ * Get the index of a given channel in a channel layout. In case multiple
+ * channels are found, only the first match will be returned.
+ *
+ * @param channel_layout input channel layout
+ * @param channel the channel whose index to obtain
+ * @return index of channel in channel_layout on success or a negative number if
+ * channel is not present in channel_layout.
+ */
+int av_channel_layout_index_from_channel(const AVChannelLayout* channel_layout,
+ enum AVChannel channel);
+
+/**
+ * Get the index in a channel layout of a channel described by the given string.
+ * In case multiple channels are found, only the first match will be returned.
+ *
+ * This function accepts channel names in the same format as
+ * @ref av_channel_from_string().
+ *
+ * @param channel_layout input channel layout
+ * @param name string describing the channel whose index to obtain
+ * @return a channel index described by the given string, or a negative AVERROR
+ * value.
+ */
+int av_channel_layout_index_from_string(const AVChannelLayout* channel_layout,
+ const char* name);
+
+/**
+ * Get a channel described by the given string.
+ *
+ * This function accepts channel names in the same format as
+ * @ref av_channel_from_string().
+ *
+ * @param channel_layout input channel layout
+ * @param name string describing the channel to obtain
+ * @return a channel described by the given string in channel_layout on success
+ * or AV_CHAN_NONE on failure (if the string is not valid or the channel
+ * order is unspecified)
+ */
+enum AVChannel av_channel_layout_channel_from_string(
+ const AVChannelLayout* channel_layout, const char* name);
+
+/**
+ * Find out what channels from a given set are present in a channel layout,
+ * without regard for their positions.
+ *
+ * @param channel_layout input channel layout
+ * @param mask a combination of AV_CH_* representing a set of channels
+ * @return a bitfield representing all the channels from mask that are present
+ * in channel_layout
+ */
+uint64_t av_channel_layout_subset(const AVChannelLayout* channel_layout,
+ uint64_t mask);
+
+/**
+ * Check whether a channel layout is valid, i.e. can possibly describe audio
+ * data.
+ *
+ * @param channel_layout input channel layout
+ * @return 1 if channel_layout is valid, 0 otherwise.
+ */
+int av_channel_layout_check(const AVChannelLayout* channel_layout);
+
+/**
+ * Check whether two channel layouts are semantically the same, i.e. the same
+ * channels are present on the same positions in both.
+ *
+ * If one of the channel layouts is AV_CHANNEL_ORDER_UNSPEC, while the other is
+ * not, they are considered to be unequal. If both are AV_CHANNEL_ORDER_UNSPEC,
+ * they are considered equal iff the channel counts are the same in both.
+ *
+ * @param chl input channel layout
+ * @param chl1 input channel layout
+ * @return 0 if chl and chl1 are equal, 1 if they are not equal. A negative
+ * AVERROR code if one or both are invalid.
+ */
+int av_channel_layout_compare(const AVChannelLayout* chl,
+ const AVChannelLayout* chl1);
+
+/**
+ * The conversion must be lossless.
+ */
+#define AV_CHANNEL_LAYOUT_RETYPE_FLAG_LOSSLESS (1 << 0)
+
+/**
+ * The specified retype target order is ignored and the simplest possible
+ * (canonical) order is used for which the input layout can be losslessy
+ * represented.
+ */
+#define AV_CHANNEL_LAYOUT_RETYPE_FLAG_CANONICAL (1 << 1)
+
+/**
+ * Change the AVChannelOrder of a channel layout.
+ *
+ * Change of AVChannelOrder can be either lossless or lossy. In case of a
+ * lossless conversion all the channel designations and the associated channel
+ * names (if any) are kept. On a lossy conversion the channel names and channel
+ * designations might be lost depending on the capabilities of the desired
+ * AVChannelOrder. Note that some conversions are simply not possible in which
+ * case this function returns AVERROR(ENOSYS).
+ *
+ * The following conversions are supported:
+ *
+ * Any -> Custom : Always possible, always lossless.
+ * Any -> Unspecified: Always possible, lossless if channel designations
+ * are all unknown and channel names are not used, lossy otherwise.
+ * Custom -> Ambisonic : Possible if it contains ambisonic channels with
+ * optional non-diegetic channels in the end. Lossy if the channels have
+ * custom names, lossless otherwise.
+ * Custom -> Native : Possible if it contains native channels in native
+ * order. Lossy if the channels have custom names, lossless otherwise.
+ *
+ * On error this function keeps the original channel layout untouched.
+ *
+ * @param channel_layout channel layout which will be changed
+ * @param order the desired channel layout order
+ * @param flags a combination of AV_CHANNEL_LAYOUT_RETYPE_FLAG_* constants
+ * @return 0 if the conversion was successful and lossless or if the channel
+ * layout was already in the desired order
+ * >0 if the conversion was successful but lossy
+ * AVERROR(ENOSYS) if the conversion was not possible (or would be
+ * lossy and AV_CHANNEL_LAYOUT_RETYPE_FLAG_LOSSLESS was specified)
+ * AVERROR(EINVAL), AVERROR(ENOMEM) on error
+ */
+int av_channel_layout_retype(AVChannelLayout* channel_layout,
+ enum AVChannelOrder order, int flags);
+
+/**
+ * @}
+ */
+
+#endif /* AVUTIL_CHANNEL_LAYOUT_H */
diff --git a/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/common.h b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/common.h
new file mode 100644
index 0000000000..fa8398889a
--- /dev/null
+++ b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/common.h
@@ -0,0 +1,587 @@
+/*
+ * copyright (c) 2006 Michael Niedermayer <michaelni@gmx.at>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * common internal and external API header
+ */
+
+#ifndef AVUTIL_COMMON_H
+#define AVUTIL_COMMON_H
+
+#if defined(__cplusplus) && !defined(__STDC_CONSTANT_MACROS) && \
+ !defined(UINT64_C)
+# error missing -D__STDC_CONSTANT_MACROS / #define __STDC_CONSTANT_MACROS
+#endif
+
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <math.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "attributes.h"
+#include "error.h"
+#include "macros.h"
+
+#ifdef HAVE_AV_CONFIG_H
+# include "config.h"
+# include "intmath.h"
+# include "internal.h"
+#else
+# include "mem.h"
+#endif /* HAVE_AV_CONFIG_H */
+
+// rounded division & shift
+#define RSHIFT(a, b) \
+ ((a) > 0 ? ((a) + ((1 << (b)) >> 1)) >> (b) \
+ : ((a) + ((1 << (b)) >> 1) - 1) >> (b))
+/* assume b>0 */
+#define ROUNDED_DIV(a, b) \
+ (((a) >= 0 ? (a) + ((b) >> 1) : (a) - ((b) >> 1)) / (b))
+/* Fast a/(1<<b) rounded toward +inf. Assume a>=0 and b>=0 */
+#define AV_CEIL_RSHIFT(a, b) \
+ (!av_builtin_constant_p(b) ? -((-(a)) >> (b)) : ((a) + (1 << (b)) - 1) >> (b))
+/* Backwards compat. */
+#define FF_CEIL_RSHIFT AV_CEIL_RSHIFT
+
+#define FFUDIV(a, b) (((a) > 0 ? (a) : (a) - (b) + 1) / (b))
+#define FFUMOD(a, b) ((a) - (b) * FFUDIV(a, b))
+
+/**
+ * Absolute value, Note, INT_MIN / INT64_MIN result in undefined behavior as
+ * they are not representable as absolute values of their type. This is the same
+ * as with *abs()
+ * @see FFNABS()
+ */
+#define FFABS(a) ((a) >= 0 ? (a) : (-(a)))
+#define FFSIGN(a) ((a) > 0 ? 1 : -1)
+
+/**
+ * Negative Absolute value.
+ * this works for all integers of all types.
+ * As with many macros, this evaluates its argument twice, it thus must not have
+ * a sideeffect, that is FFNABS(x++) has undefined behavior.
+ */
+#define FFNABS(a) ((a) <= 0 ? (a) : (-(a)))
+
+/**
+ * Unsigned Absolute value.
+ * This takes the absolute value of a signed int and returns it as a unsigned.
+ * This also works with INT_MIN which would otherwise not be representable
+ * As with many macros, this evaluates its argument twice.
+ */
+#define FFABSU(a) ((a) <= 0 ? -(unsigned)(a) : (unsigned)(a))
+#define FFABS64U(a) ((a) <= 0 ? -(uint64_t)(a) : (uint64_t)(a))
+
+/* misc math functions */
+
+#ifndef av_ceil_log2
+# define av_ceil_log2 av_ceil_log2_c
+#endif
+#ifndef av_clip
+# define av_clip av_clip_c
+#endif
+#ifndef av_clip64
+# define av_clip64 av_clip64_c
+#endif
+#ifndef av_clip_uint8
+# define av_clip_uint8 av_clip_uint8_c
+#endif
+#ifndef av_clip_int8
+# define av_clip_int8 av_clip_int8_c
+#endif
+#ifndef av_clip_uint16
+# define av_clip_uint16 av_clip_uint16_c
+#endif
+#ifndef av_clip_int16
+# define av_clip_int16 av_clip_int16_c
+#endif
+#ifndef av_clipl_int32
+# define av_clipl_int32 av_clipl_int32_c
+#endif
+#ifndef av_clip_intp2
+# define av_clip_intp2 av_clip_intp2_c
+#endif
+#ifndef av_clip_uintp2
+# define av_clip_uintp2 av_clip_uintp2_c
+#endif
+#ifndef av_mod_uintp2
+# define av_mod_uintp2 av_mod_uintp2_c
+#endif
+#ifndef av_sat_add32
+# define av_sat_add32 av_sat_add32_c
+#endif
+#ifndef av_sat_dadd32
+# define av_sat_dadd32 av_sat_dadd32_c
+#endif
+#ifndef av_sat_sub32
+# define av_sat_sub32 av_sat_sub32_c
+#endif
+#ifndef av_sat_dsub32
+# define av_sat_dsub32 av_sat_dsub32_c
+#endif
+#ifndef av_sat_add64
+# define av_sat_add64 av_sat_add64_c
+#endif
+#ifndef av_sat_sub64
+# define av_sat_sub64 av_sat_sub64_c
+#endif
+#ifndef av_clipf
+# define av_clipf av_clipf_c
+#endif
+#ifndef av_clipd
+# define av_clipd av_clipd_c
+#endif
+#ifndef av_popcount
+# define av_popcount av_popcount_c
+#endif
+#ifndef av_popcount64
+# define av_popcount64 av_popcount64_c
+#endif
+#ifndef av_parity
+# define av_parity av_parity_c
+#endif
+
+#ifndef av_log2
+av_const int av_log2(unsigned v);
+#endif
+
+#ifndef av_log2_16bit
+av_const int av_log2_16bit(unsigned v);
+#endif
+
+/**
+ * Clip a signed integer value into the amin-amax range.
+ * @param a value to clip
+ * @param amin minimum value of the clip range
+ * @param amax maximum value of the clip range
+ * @return clipped value
+ */
+static av_always_inline av_const int av_clip_c(int a, int amin, int amax) {
+#if defined(HAVE_AV_CONFIG_H) && defined(ASSERT_LEVEL) && ASSERT_LEVEL >= 2
+ if (amin > amax) abort();
+#endif
+ if (a < amin)
+ return amin;
+ else if (a > amax)
+ return amax;
+ else
+ return a;
+}
+
+/**
+ * Clip a signed 64bit integer value into the amin-amax range.
+ * @param a value to clip
+ * @param amin minimum value of the clip range
+ * @param amax maximum value of the clip range
+ * @return clipped value
+ */
+static av_always_inline av_const int64_t av_clip64_c(int64_t a, int64_t amin,
+ int64_t amax) {
+#if defined(HAVE_AV_CONFIG_H) && defined(ASSERT_LEVEL) && ASSERT_LEVEL >= 2
+ if (amin > amax) abort();
+#endif
+ if (a < amin)
+ return amin;
+ else if (a > amax)
+ return amax;
+ else
+ return a;
+}
+
+/**
+ * Clip a signed integer value into the 0-255 range.
+ * @param a value to clip
+ * @return clipped value
+ */
+static av_always_inline av_const uint8_t av_clip_uint8_c(int a) {
+ if (a & (~0xFF))
+ return (~a) >> 31;
+ else
+ return a;
+}
+
+/**
+ * Clip a signed integer value into the -128,127 range.
+ * @param a value to clip
+ * @return clipped value
+ */
+static av_always_inline av_const int8_t av_clip_int8_c(int a) {
+ if ((a + 0x80U) & ~0xFF)
+ return (a >> 31) ^ 0x7F;
+ else
+ return a;
+}
+
+/**
+ * Clip a signed integer value into the 0-65535 range.
+ * @param a value to clip
+ * @return clipped value
+ */
+static av_always_inline av_const uint16_t av_clip_uint16_c(int a) {
+ if (a & (~0xFFFF))
+ return (~a) >> 31;
+ else
+ return a;
+}
+
+/**
+ * Clip a signed integer value into the -32768,32767 range.
+ * @param a value to clip
+ * @return clipped value
+ */
+static av_always_inline av_const int16_t av_clip_int16_c(int a) {
+ if ((a + 0x8000U) & ~0xFFFF)
+ return (a >> 31) ^ 0x7FFF;
+ else
+ return a;
+}
+
+/**
+ * Clip a signed 64-bit integer value into the -2147483648,2147483647 range.
+ * @param a value to clip
+ * @return clipped value
+ */
+static av_always_inline av_const int32_t av_clipl_int32_c(int64_t a) {
+ if ((a + 0x80000000u) & ~UINT64_C(0xFFFFFFFF))
+ return (int32_t)((a >> 63) ^ 0x7FFFFFFF);
+ else
+ return (int32_t)a;
+}
+
+/**
+ * Clip a signed integer into the -(2^p),(2^p-1) range.
+ * @param a value to clip
+ * @param p bit position to clip at
+ * @return clipped value
+ */
+static av_always_inline av_const int av_clip_intp2_c(int a, int p) {
+ if (((unsigned)a + (1 << p)) & ~((2 << p) - 1))
+ return (a >> 31) ^ ((1 << p) - 1);
+ else
+ return a;
+}
+
+/**
+ * Clip a signed integer to an unsigned power of two range.
+ * @param a value to clip
+ * @param p bit position to clip at
+ * @return clipped value
+ */
+static av_always_inline av_const unsigned av_clip_uintp2_c(int a, int p) {
+ if (a & ~((1 << p) - 1))
+ return (~a) >> 31 & ((1 << p) - 1);
+ else
+ return a;
+}
+
+/**
+ * Clear high bits from an unsigned integer starting with specific bit position
+ * @param a value to clip
+ * @param p bit position to clip at
+ * @return clipped value
+ */
+static av_always_inline av_const unsigned av_mod_uintp2_c(unsigned a,
+ unsigned p) {
+ return a & ((1U << p) - 1);
+}
+
+/**
+ * Add two signed 32-bit values with saturation.
+ *
+ * @param a one value
+ * @param b another value
+ * @return sum with signed saturation
+ */
+static av_always_inline int av_sat_add32_c(int a, int b) {
+ return av_clipl_int32((int64_t)a + b);
+}
+
+/**
+ * Add a doubled value to another value with saturation at both stages.
+ *
+ * @param a first value
+ * @param b value doubled and added to a
+ * @return sum sat(a + sat(2*b)) with signed saturation
+ */
+static av_always_inline int av_sat_dadd32_c(int a, int b) {
+ return av_sat_add32(a, av_sat_add32(b, b));
+}
+
+/**
+ * Subtract two signed 32-bit values with saturation.
+ *
+ * @param a one value
+ * @param b another value
+ * @return difference with signed saturation
+ */
+static av_always_inline int av_sat_sub32_c(int a, int b) {
+ return av_clipl_int32((int64_t)a - b);
+}
+
+/**
+ * Subtract a doubled value from another value with saturation at both stages.
+ *
+ * @param a first value
+ * @param b value doubled and subtracted from a
+ * @return difference sat(a - sat(2*b)) with signed saturation
+ */
+static av_always_inline int av_sat_dsub32_c(int a, int b) {
+ return av_sat_sub32(a, av_sat_add32(b, b));
+}
+
+/**
+ * Add two signed 64-bit values with saturation.
+ *
+ * @param a one value
+ * @param b another value
+ * @return sum with signed saturation
+ */
+static av_always_inline int64_t av_sat_add64_c(int64_t a, int64_t b) {
+#if (!defined(__INTEL_COMPILER) && AV_GCC_VERSION_AT_LEAST(5, 1)) || \
+ AV_HAS_BUILTIN(__builtin_add_overflow)
+ int64_t tmp;
+ return !__builtin_add_overflow(a, b, &tmp)
+ ? tmp
+ : (tmp < 0 ? INT64_MAX : INT64_MIN);
+#else
+ int64_t s = a + (uint64_t)b;
+ if ((int64_t)(a ^ b | ~s ^ b) >= 0) return INT64_MAX ^ (b >> 63);
+ return s;
+#endif
+}
+
+/**
+ * Subtract two signed 64-bit values with saturation.
+ *
+ * @param a one value
+ * @param b another value
+ * @return difference with signed saturation
+ */
+static av_always_inline int64_t av_sat_sub64_c(int64_t a, int64_t b) {
+#if (!defined(__INTEL_COMPILER) && AV_GCC_VERSION_AT_LEAST(5, 1)) || \
+ AV_HAS_BUILTIN(__builtin_sub_overflow)
+ int64_t tmp;
+ return !__builtin_sub_overflow(a, b, &tmp)
+ ? tmp
+ : (tmp < 0 ? INT64_MAX : INT64_MIN);
+#else
+ if (b <= 0 && a >= INT64_MAX + b) return INT64_MAX;
+ if (b >= 0 && a <= INT64_MIN + b) return INT64_MIN;
+ return a - b;
+#endif
+}
+
+/**
+ * Clip a float value into the amin-amax range.
+ * If a is nan or -inf amin will be returned.
+ * If a is +inf amax will be returned.
+ * @param a value to clip
+ * @param amin minimum value of the clip range
+ * @param amax maximum value of the clip range
+ * @return clipped value
+ */
+static av_always_inline av_const float av_clipf_c(float a, float amin,
+ float amax) {
+#if defined(HAVE_AV_CONFIG_H) && defined(ASSERT_LEVEL) && ASSERT_LEVEL >= 2
+ if (amin > amax) abort();
+#endif
+ return FFMIN(FFMAX(a, amin), amax);
+}
+
+/**
+ * Clip a double value into the amin-amax range.
+ * If a is nan or -inf amin will be returned.
+ * If a is +inf amax will be returned.
+ * @param a value to clip
+ * @param amin minimum value of the clip range
+ * @param amax maximum value of the clip range
+ * @return clipped value
+ */
+static av_always_inline av_const double av_clipd_c(double a, double amin,
+ double amax) {
+#if defined(HAVE_AV_CONFIG_H) && defined(ASSERT_LEVEL) && ASSERT_LEVEL >= 2
+ if (amin > amax) abort();
+#endif
+ return FFMIN(FFMAX(a, amin), amax);
+}
+
+/** Compute ceil(log2(x)).
+ * @param x value used to compute ceil(log2(x))
+ * @return computed ceiling of log2(x)
+ */
+static av_always_inline av_const int av_ceil_log2_c(int x) {
+ return av_log2((x - 1U) << 1);
+}
+
+/**
+ * Count number of bits set to one in x
+ * @param x value to count bits of
+ * @return the number of bits set to one in x
+ */
+static av_always_inline av_const int av_popcount_c(uint32_t x) {
+ x -= (x >> 1) & 0x55555555;
+ x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
+ x = (x + (x >> 4)) & 0x0F0F0F0F;
+ x += x >> 8;
+ return (x + (x >> 16)) & 0x3F;
+}
+
+/**
+ * Count number of bits set to one in x
+ * @param x value to count bits of
+ * @return the number of bits set to one in x
+ */
+static av_always_inline av_const int av_popcount64_c(uint64_t x) {
+ return av_popcount((uint32_t)x) + av_popcount((uint32_t)(x >> 32));
+}
+
+static av_always_inline av_const int av_parity_c(uint32_t v) {
+ return av_popcount(v) & 1;
+}
+
+/**
+ * Convert a UTF-8 character (up to 4 bytes) to its 32-bit UCS-4 encoded form.
+ *
+ * @param val Output value, must be an lvalue of type uint32_t.
+ * @param GET_BYTE Expression reading one byte from the input.
+ * Evaluated up to 7 times (4 for the currently
+ * assigned Unicode range). With a memory buffer
+ * input, this could be *ptr++, or if you want to make sure
+ * that *ptr stops at the end of a NULL terminated string then
+ * *ptr ? *ptr++ : 0
+ * @param ERROR Expression to be evaluated on invalid input,
+ * typically a goto statement.
+ *
+ * @warning ERROR should not contain a loop control statement which
+ * could interact with the internal while loop, and should force an
+ * exit from the macro code (e.g. through a goto or a return) in order
+ * to prevent undefined results.
+ */
+#define GET_UTF8(val, GET_BYTE, ERROR) \
+ val = (GET_BYTE); \
+ { \
+ uint32_t top = (val & 128) >> 1; \
+ if ((val & 0xc0) == 0x80 || val >= 0xFE) { \
+ ERROR \
+ } \
+ while (val & top) { \
+ unsigned int tmp = (GET_BYTE)-128; \
+ if (tmp >> 6) { \
+ ERROR \
+ } \
+ val = (val << 6) + tmp; \
+ top <<= 5; \
+ } \
+ val &= (top << 1) - 1; \
+ }
+
+/**
+ * Convert a UTF-16 character (2 or 4 bytes) to its 32-bit UCS-4 encoded form.
+ *
+ * @param val Output value, must be an lvalue of type uint32_t.
+ * @param GET_16BIT Expression returning two bytes of UTF-16 data converted
+ * to native byte order. Evaluated one or two times.
+ * @param ERROR Expression to be evaluated on invalid input,
+ * typically a goto statement.
+ */
+#define GET_UTF16(val, GET_16BIT, ERROR) \
+ val = (GET_16BIT); \
+ { \
+ unsigned int hi = val - 0xD800; \
+ if (hi < 0x800) { \
+ val = (GET_16BIT)-0xDC00; \
+ if (val > 0x3FFU || hi > 0x3FFU) { \
+ ERROR \
+ } \
+ val += (hi << 10) + 0x10000; \
+ } \
+ }
+
+/**
+ * @def PUT_UTF8(val, tmp, PUT_BYTE)
+ * Convert a 32-bit Unicode character to its UTF-8 encoded form (up to 4 bytes
+ * long).
+ * @param val is an input-only argument and should be of type uint32_t. It holds
+ * a UCS-4 encoded Unicode character that is to be converted to UTF-8. If
+ * val is given as a function it is executed only once.
+ * @param tmp is a temporary variable and should be of type uint8_t. It
+ * represents an intermediate value during conversion that is to be
+ * output by PUT_BYTE.
+ * @param PUT_BYTE writes the converted UTF-8 bytes to any proper destination.
+ * It could be a function or a statement, and uses tmp as the input byte.
+ * For example, PUT_BYTE could be "*output++ = tmp;" PUT_BYTE will be
+ * executed up to 4 times for values in the valid UTF-8 range and up to
+ * 7 times in the general case, depending on the length of the converted
+ * Unicode character.
+ */
+#define PUT_UTF8(val, tmp, PUT_BYTE) \
+ { \
+ int bytes, shift; \
+ uint32_t in = val; \
+ if (in < 0x80) { \
+ tmp = in; \
+ PUT_BYTE \
+ } else { \
+ bytes = (av_log2(in) + 4) / 5; \
+ shift = (bytes - 1) * 6; \
+ tmp = (256 - (256 >> bytes)) | (in >> shift); \
+ PUT_BYTE \
+ while (shift >= 6) { \
+ shift -= 6; \
+ tmp = 0x80 | ((in >> shift) & 0x3f); \
+ PUT_BYTE \
+ } \
+ } \
+ }
+
+/**
+ * @def PUT_UTF16(val, tmp, PUT_16BIT)
+ * Convert a 32-bit Unicode character to its UTF-16 encoded form (2 or 4 bytes).
+ * @param val is an input-only argument and should be of type uint32_t. It holds
+ * a UCS-4 encoded Unicode character that is to be converted to UTF-16. If
+ * val is given as a function it is executed only once.
+ * @param tmp is a temporary variable and should be of type uint16_t. It
+ * represents an intermediate value during conversion that is to be
+ * output by PUT_16BIT.
+ * @param PUT_16BIT writes the converted UTF-16 data to any proper destination
+ * in desired endianness. It could be a function or a statement, and uses tmp
+ * as the input byte. For example, PUT_BYTE could be "*output++ = tmp;"
+ * PUT_BYTE will be executed 1 or 2 times depending on input character.
+ */
+#define PUT_UTF16(val, tmp, PUT_16BIT) \
+ { \
+ uint32_t in = val; \
+ if (in < 0x10000) { \
+ tmp = in; \
+ PUT_16BIT \
+ } else { \
+ tmp = 0xD800 | ((in - 0x10000) >> 10); \
+ PUT_16BIT \
+ tmp = 0xDC00 | ((in - 0x10000) & 0x3FF); \
+ PUT_16BIT \
+ } \
+ }
+
+#endif /* AVUTIL_COMMON_H */
diff --git a/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/cpu.h b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/cpu.h
new file mode 100644
index 0000000000..3eb950fdd0
--- /dev/null
+++ b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/cpu.h
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2000, 2001, 2002 Fabrice Bellard
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVUTIL_CPU_H
+#define AVUTIL_CPU_H
+
+#include <stddef.h>
+
+#define AV_CPU_FLAG_FORCE 0x80000000 /* force usage of selected flags (OR) */
+
+/* lower 16 bits - CPU features */
+#define AV_CPU_FLAG_MMX 0x0001 ///< standard MMX
+#define AV_CPU_FLAG_MMXEXT 0x0002 ///< SSE integer functions or AMD MMX ext
+#define AV_CPU_FLAG_MMX2 0x0002 ///< SSE integer functions or AMD MMX ext
+#define AV_CPU_FLAG_3DNOW 0x0004 ///< AMD 3DNOW
+#define AV_CPU_FLAG_SSE 0x0008 ///< SSE functions
+#define AV_CPU_FLAG_SSE2 0x0010 ///< PIV SSE2 functions
+#define AV_CPU_FLAG_SSE2SLOW \
+ 0x40000000 ///< SSE2 supported, but usually not faster
+ ///< than regular MMX/SSE (e.g. Core1)
+#define AV_CPU_FLAG_3DNOWEXT 0x0020 ///< AMD 3DNowExt
+#define AV_CPU_FLAG_SSE3 0x0040 ///< Prescott SSE3 functions
+#define AV_CPU_FLAG_SSE3SLOW \
+ 0x20000000 ///< SSE3 supported, but usually not faster
+ ///< than regular MMX/SSE (e.g. Core1)
+#define AV_CPU_FLAG_SSSE3 0x0080 ///< Conroe SSSE3 functions
+#define AV_CPU_FLAG_SSSE3SLOW \
+ 0x4000000 ///< SSSE3 supported, but usually not faster
+#define AV_CPU_FLAG_ATOM \
+ 0x10000000 ///< Atom processor, some SSSE3 instructions are slower
+#define AV_CPU_FLAG_SSE4 0x0100 ///< Penryn SSE4.1 functions
+#define AV_CPU_FLAG_SSE42 0x0200 ///< Nehalem SSE4.2 functions
+#define AV_CPU_FLAG_AESNI 0x80000 ///< Advanced Encryption Standard functions
+#define AV_CPU_FLAG_AVX \
+ 0x4000 ///< AVX functions: requires OS support even if YMM registers aren't
+ ///< used
+#define AV_CPU_FLAG_AVXSLOW \
+ 0x8000000 ///< AVX supported, but slow when using YMM registers (e.g.
+ ///< Bulldozer)
+#define AV_CPU_FLAG_XOP 0x0400 ///< Bulldozer XOP functions
+#define AV_CPU_FLAG_FMA4 0x0800 ///< Bulldozer FMA4 functions
+#define AV_CPU_FLAG_CMOV 0x1000 ///< supports cmov instruction
+#define AV_CPU_FLAG_AVX2 \
+ 0x8000 ///< AVX2 functions: requires OS support even if YMM registers aren't
+ ///< used
+#define AV_CPU_FLAG_FMA3 0x10000 ///< Haswell FMA3 functions
+#define AV_CPU_FLAG_BMI1 0x20000 ///< Bit Manipulation Instruction Set 1
+#define AV_CPU_FLAG_BMI2 0x40000 ///< Bit Manipulation Instruction Set 2
+#define AV_CPU_FLAG_AVX512 \
+ 0x100000 ///< AVX-512 functions: requires OS support even if YMM/ZMM
+ ///< registers aren't used
+#define AV_CPU_FLAG_AVX512ICL \
+ 0x200000 ///< F/CD/BW/DQ/VL/VNNI/IFMA/VBMI/VBMI2/VPOPCNTDQ/BITALG/GFNI/VAES/VPCLMULQDQ
+#define AV_CPU_FLAG_SLOW_GATHER 0x2000000 ///< CPU has slow gathers.
+
+#define AV_CPU_FLAG_ALTIVEC 0x0001 ///< standard
+#define AV_CPU_FLAG_VSX 0x0002 ///< ISA 2.06
+#define AV_CPU_FLAG_POWER8 0x0004 ///< ISA 2.07
+
+#define AV_CPU_FLAG_ARMV5TE (1 << 0)
+#define AV_CPU_FLAG_ARMV6 (1 << 1)
+#define AV_CPU_FLAG_ARMV6T2 (1 << 2)
+#define AV_CPU_FLAG_VFP (1 << 3)
+#define AV_CPU_FLAG_VFPV3 (1 << 4)
+#define AV_CPU_FLAG_NEON (1 << 5)
+#define AV_CPU_FLAG_ARMV8 (1 << 6)
+#define AV_CPU_FLAG_VFP_VM \
+ (1 << 7) ///< VFPv2 vector mode, deprecated in ARMv7-A and unavailable in
+ ///< various CPUs implementations
+#define AV_CPU_FLAG_DOTPROD (1 << 8)
+#define AV_CPU_FLAG_I8MM (1 << 9)
+#define AV_CPU_FLAG_SETEND (1 << 16)
+
+#define AV_CPU_FLAG_MMI (1 << 0)
+#define AV_CPU_FLAG_MSA (1 << 1)
+
+// Loongarch SIMD extension.
+#define AV_CPU_FLAG_LSX (1 << 0)
+#define AV_CPU_FLAG_LASX (1 << 1)
+
+// RISC-V extensions
+#define AV_CPU_FLAG_RVI (1 << 0) ///< I (full GPR bank)
+#define AV_CPU_FLAG_RVF (1 << 1) ///< F (single precision FP)
+#define AV_CPU_FLAG_RVD (1 << 2) ///< D (double precision FP)
+#define AV_CPU_FLAG_RVV_I32 (1 << 3) ///< Vectors of 8/16/32-bit int's */
+#define AV_CPU_FLAG_RVV_F32 (1 << 4) ///< Vectors of float's */
+#define AV_CPU_FLAG_RVV_I64 (1 << 5) ///< Vectors of 64-bit int's */
+#define AV_CPU_FLAG_RVV_F64 (1 << 6) ///< Vectors of double's
+#define AV_CPU_FLAG_RVB_BASIC (1 << 7) ///< Basic bit-manipulations
+#define AV_CPU_FLAG_RVB_ADDR (1 << 8) ///< Address bit-manipulations
+
+/**
+ * Return the flags which specify extensions supported by the CPU.
+ * The returned value is affected by av_force_cpu_flags() if that was used
+ * before. So av_get_cpu_flags() can easily be used in an application to
+ * detect the enabled cpu flags.
+ */
+int av_get_cpu_flags(void);
+
+/**
+ * Disables cpu detection and forces the specified flags.
+ * -1 is a special case that disables forcing of specific flags.
+ */
+void av_force_cpu_flags(int flags);
+
+/**
+ * Parse CPU caps from a string and update the given AV_CPU_* flags based on
+ * that.
+ *
+ * @return negative on error.
+ */
+int av_parse_cpu_caps(unsigned* flags, const char* s);
+
+/**
+ * @return the number of logical CPU cores present.
+ */
+int av_cpu_count(void);
+
+/**
+ * Overrides cpu count detection and forces the specified count.
+ * Count < 1 disables forcing of specific count.
+ */
+void av_cpu_force_count(int count);
+
+/**
+ * Get the maximum data alignment that may be required by FFmpeg.
+ *
+ * Note that this is affected by the build configuration and the CPU flags mask,
+ * so e.g. if the CPU supports AVX, but libavutil has been built with
+ * --disable-avx or the AV_CPU_FLAG_AVX flag has been disabled through
+ * av_set_cpu_flags_mask(), then this function will behave as if AVX is not
+ * present.
+ */
+size_t av_cpu_max_align(void);
+
+#endif /* AVUTIL_CPU_H */
diff --git a/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/dict.h b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/dict.h
new file mode 100644
index 0000000000..967a1c8041
--- /dev/null
+++ b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/dict.h
@@ -0,0 +1,259 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * Public dictionary API.
+ * @deprecated
+ * AVDictionary is provided for compatibility with libav. It is both in
+ * implementation as well as API inefficient. It does not scale and is
+ * extremely slow with large dictionaries.
+ * It is recommended that new code uses our tree container from tree.c/h
+ * where applicable, which uses AVL trees to achieve O(log n) performance.
+ */
+
+#ifndef AVUTIL_DICT_H
+#define AVUTIL_DICT_H
+
+#include <stdint.h>
+
+/**
+ * @addtogroup lavu_dict AVDictionary
+ * @ingroup lavu_data
+ *
+ * @brief Simple key:value store
+ *
+ * @{
+ * Dictionaries are used for storing key-value pairs.
+ *
+ * - To **create an AVDictionary**, simply pass an address of a NULL
+ * pointer to av_dict_set(). NULL can be used as an empty dictionary
+ * wherever a pointer to an AVDictionary is required.
+ * - To **insert an entry**, use av_dict_set().
+ * - Use av_dict_get() to **retrieve an entry**.
+ * - To **iterate over all entries**, use av_dict_iterate().
+ * - In order to **free the dictionary and all its contents**, use
+ av_dict_free().
+ *
+ @code
+ AVDictionary *d = NULL; // "create" an empty dictionary
+ AVDictionaryEntry *t = NULL;
+
+ av_dict_set(&d, "foo", "bar", 0); // add an entry
+
+ char *k = av_strdup("key"); // if your strings are already allocated,
+ char *v = av_strdup("value"); // you can avoid copying them like this
+ av_dict_set(&d, k, v, AV_DICT_DONT_STRDUP_KEY | AV_DICT_DONT_STRDUP_VAL);
+
+ while ((t = av_dict_iterate(d, t))) {
+ <....> // iterate over all entries in d
+ }
+ av_dict_free(&d);
+ @endcode
+ */
+
+/**
+ * @name AVDictionary Flags
+ * Flags that influence behavior of the matching of keys or insertion to the
+ * dictionary.
+ * @{
+ */
+#define AV_DICT_MATCH_CASE \
+ 1 /**< Only get an entry with exact-case key match. Only relevant in \
+ av_dict_get(). */
+#define AV_DICT_IGNORE_SUFFIX \
+ 2 /**< Return first entry in a dictionary whose first part corresponds to \
+ the search key, ignoring the suffix of the found key string. Only \
+ relevant in av_dict_get(). */
+#define AV_DICT_DONT_STRDUP_KEY \
+ 4 /**< Take ownership of a key that's been \
+ allocated with av_malloc() or another memory allocation function. */
+#define AV_DICT_DONT_STRDUP_VAL \
+ 8 /**< Take ownership of a value that's been \
+ allocated with av_malloc() or another memory allocation function. */
+#define AV_DICT_DONT_OVERWRITE 16 /**< Don't overwrite existing entries. */
+#define AV_DICT_APPEND \
+ 32 /**< If the entry already exists, append to it. Note that no \
+ delimiter is added, the strings are simply concatenated. */
+#define AV_DICT_MULTIKEY \
+ 64 /**< Allow to store several equal keys in the dictionary */
+/**
+ * @}
+ */
+
+typedef struct AVDictionaryEntry {
+ char* key;
+ char* value;
+} AVDictionaryEntry;
+
+typedef struct AVDictionary AVDictionary;
+
+/**
+ * Get a dictionary entry with matching key.
+ *
+ * The returned entry key or value must not be changed, or it will
+ * cause undefined behavior.
+ *
+ * @param prev Set to the previous matching element to find the next.
+ * If set to NULL the first matching element is returned.
+ * @param key Matching key
+ * @param flags A collection of AV_DICT_* flags controlling how the
+ * entry is retrieved
+ *
+ * @return Found entry or NULL in case no matching entry was found in the
+ * dictionary
+ */
+AVDictionaryEntry* av_dict_get(const AVDictionary* m, const char* key,
+ const AVDictionaryEntry* prev, int flags);
+
+/**
+ * Iterate over a dictionary
+ *
+ * Iterates through all entries in the dictionary.
+ *
+ * @warning The returned AVDictionaryEntry key/value must not be changed.
+ *
+ * @warning As av_dict_set() invalidates all previous entries returned
+ * by this function, it must not be called while iterating over the dict.
+ *
+ * Typical usage:
+ * @code
+ * const AVDictionaryEntry *e = NULL;
+ * while ((e = av_dict_iterate(m, e))) {
+ * // ...
+ * }
+ * @endcode
+ *
+ * @param m The dictionary to iterate over
+ * @param prev Pointer to the previous AVDictionaryEntry, NULL initially
+ *
+ * @retval AVDictionaryEntry* The next element in the dictionary
+ * @retval NULL No more elements in the dictionary
+ */
+const AVDictionaryEntry* av_dict_iterate(const AVDictionary* m,
+ const AVDictionaryEntry* prev);
+
+/**
+ * Get number of entries in dictionary.
+ *
+ * @param m dictionary
+ * @return number of entries in dictionary
+ */
+int av_dict_count(const AVDictionary* m);
+
+/**
+ * Set the given entry in *pm, overwriting an existing entry.
+ *
+ * Note: If AV_DICT_DONT_STRDUP_KEY or AV_DICT_DONT_STRDUP_VAL is set,
+ * these arguments will be freed on error.
+ *
+ * @warning Adding a new entry to a dictionary invalidates all existing entries
+ * previously returned with av_dict_get() or av_dict_iterate().
+ *
+ * @param pm Pointer to a pointer to a dictionary struct. If *pm is NULL
+ * a dictionary struct is allocated and put in *pm.
+ * @param key Entry key to add to *pm (will either be av_strduped or added
+ * as a new key depending on flags)
+ * @param value Entry value to add to *pm (will be av_strduped or added as a
+ * new key depending on flags). Passing a NULL value will cause an existing
+ * entry to be deleted.
+ *
+ * @return >= 0 on success otherwise an error code <0
+ */
+int av_dict_set(AVDictionary** pm, const char* key, const char* value,
+ int flags);
+
+/**
+ * Convenience wrapper for av_dict_set() that converts the value to a string
+ * and stores it.
+ *
+ * Note: If ::AV_DICT_DONT_STRDUP_KEY is set, key will be freed on error.
+ */
+int av_dict_set_int(AVDictionary** pm, const char* key, int64_t value,
+ int flags);
+
+/**
+ * Parse the key/value pairs list and add the parsed entries to a dictionary.
+ *
+ * In case of failure, all the successfully set entries are stored in
+ * *pm. You may need to manually free the created dictionary.
+ *
+ * @param key_val_sep A 0-terminated list of characters used to separate
+ * key from value
+ * @param pairs_sep A 0-terminated list of characters used to separate
+ * two pairs from each other
+ * @param flags Flags to use when adding to the dictionary.
+ * ::AV_DICT_DONT_STRDUP_KEY and ::AV_DICT_DONT_STRDUP_VAL
+ * are ignored since the key/value tokens will always
+ * be duplicated.
+ *
+ * @return 0 on success, negative AVERROR code on failure
+ */
+int av_dict_parse_string(AVDictionary** pm, const char* str,
+ const char* key_val_sep, const char* pairs_sep,
+ int flags);
+
+/**
+ * Copy entries from one AVDictionary struct into another.
+ *
+ * @note Metadata is read using the ::AV_DICT_IGNORE_SUFFIX flag
+ *
+ * @param dst Pointer to a pointer to a AVDictionary struct to copy into. If
+ * *dst is NULL, this function will allocate a struct for you and put it in *dst
+ * @param src Pointer to the source AVDictionary struct to copy items from.
+ * @param flags Flags to use when setting entries in *dst
+ *
+ * @return 0 on success, negative AVERROR code on failure. If dst was allocated
+ * by this function, callers should free the associated memory.
+ */
+int av_dict_copy(AVDictionary** dst, const AVDictionary* src, int flags);
+
+/**
+ * Free all the memory allocated for an AVDictionary struct
+ * and all keys and values.
+ */
+void av_dict_free(AVDictionary** m);
+
+/**
+ * Get dictionary entries as a string.
+ *
+ * Create a string containing dictionary's entries.
+ * Such string may be passed back to av_dict_parse_string().
+ * @note String is escaped with backslashes ('\').
+ *
+ * @warning Separators cannot be neither '\\' nor '\0'. They also cannot be the
+ * same.
+ *
+ * @param[in] m The dictionary
+ * @param[out] buffer Pointer to buffer that will be allocated with
+ * string containg entries. Buffer must be freed by the caller when is no longer
+ * needed.
+ * @param[in] key_val_sep Character used to separate key from value
+ * @param[in] pairs_sep Character used to separate two pairs from each
+ * other
+ *
+ * @return >= 0 on success, negative on error
+ */
+int av_dict_get_string(const AVDictionary* m, char** buffer,
+ const char key_val_sep, const char pairs_sep);
+
+/**
+ * @}
+ */
+
+#endif /* AVUTIL_DICT_H */
diff --git a/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/error.h b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/error.h
new file mode 100644
index 0000000000..74af5b1534
--- /dev/null
+++ b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/error.h
@@ -0,0 +1,158 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * error code definitions
+ */
+
+#ifndef AVUTIL_ERROR_H
+#define AVUTIL_ERROR_H
+
+#include <errno.h>
+#include <stddef.h>
+
+#include "macros.h"
+
+/**
+ * @addtogroup lavu_error
+ *
+ * @{
+ */
+
+/* error handling */
+#if EDOM > 0
+# define AVERROR(e) \
+ (-(e)) ///< Returns a negative error code from a POSIX error code, to
+ ///< return from library functions.
+# define AVUNERROR(e) \
+ (-(e)) ///< Returns a POSIX error code from a library function error return
+ ///< value.
+#else
+/* Some platforms have E* and errno already negated. */
+# define AVERROR(e) (e)
+# define AVUNERROR(e) (e)
+#endif
+
+#define FFERRTAG(a, b, c, d) (-(int)MKTAG(a, b, c, d))
+
+#define AVERROR_BSF_NOT_FOUND \
+ FFERRTAG(0xF8, 'B', 'S', 'F') ///< Bitstream filter not found
+#define AVERROR_BUG \
+ FFERRTAG('B', 'U', 'G', '!') ///< Internal bug, also see AVERROR_BUG2
+#define AVERROR_BUFFER_TOO_SMALL \
+ FFERRTAG('B', 'U', 'F', 'S') ///< Buffer too small
+#define AVERROR_DECODER_NOT_FOUND \
+ FFERRTAG(0xF8, 'D', 'E', 'C') ///< Decoder not found
+#define AVERROR_DEMUXER_NOT_FOUND \
+ FFERRTAG(0xF8, 'D', 'E', 'M') ///< Demuxer not found
+#define AVERROR_ENCODER_NOT_FOUND \
+ FFERRTAG(0xF8, 'E', 'N', 'C') ///< Encoder not found
+#define AVERROR_EOF FFERRTAG('E', 'O', 'F', ' ') ///< End of file
+#define AVERROR_EXIT \
+ FFERRTAG('E', 'X', 'I', 'T') ///< Immediate exit was requested; the called
+ ///< function should not be restarted
+#define AVERROR_EXTERNAL \
+ FFERRTAG('E', 'X', 'T', ' ') ///< Generic error in an external library
+#define AVERROR_FILTER_NOT_FOUND \
+ FFERRTAG(0xF8, 'F', 'I', 'L') ///< Filter not found
+#define AVERROR_INVALIDDATA \
+ FFERRTAG('I', 'N', 'D', 'A') ///< Invalid data found when processing input
+#define AVERROR_MUXER_NOT_FOUND \
+ FFERRTAG(0xF8, 'M', 'U', 'X') ///< Muxer not found
+#define AVERROR_OPTION_NOT_FOUND \
+ FFERRTAG(0xF8, 'O', 'P', 'T') ///< Option not found
+#define AVERROR_PATCHWELCOME \
+ FFERRTAG('P', 'A', 'W', \
+ 'E') ///< Not yet implemented in FFmpeg, patches welcome
+#define AVERROR_PROTOCOL_NOT_FOUND \
+ FFERRTAG(0xF8, 'P', 'R', 'O') ///< Protocol not found
+
+#define AVERROR_STREAM_NOT_FOUND \
+ FFERRTAG(0xF8, 'S', 'T', 'R') ///< Stream not found
+/**
+ * This is semantically identical to AVERROR_BUG
+ * it has been introduced in Libav after our AVERROR_BUG and with a modified
+ * value.
+ */
+#define AVERROR_BUG2 FFERRTAG('B', 'U', 'G', ' ')
+#define AVERROR_UNKNOWN \
+ FFERRTAG('U', 'N', 'K', \
+ 'N') ///< Unknown error, typically from an external library
+#define AVERROR_EXPERIMENTAL \
+ (-0x2bb2afa8) ///< Requested feature is flagged experimental. Set
+ ///< strict_std_compliance if you really want to use it.
+#define AVERROR_INPUT_CHANGED \
+ (-0x636e6701) ///< Input changed between calls. Reconfiguration is required.
+ ///< (can be OR-ed with AVERROR_OUTPUT_CHANGED)
+#define AVERROR_OUTPUT_CHANGED \
+ (-0x636e6702) ///< Output changed between calls. Reconfiguration is required.
+ ///< (can be OR-ed with AVERROR_INPUT_CHANGED)
+/* HTTP & RTSP errors */
+#define AVERROR_HTTP_BAD_REQUEST FFERRTAG(0xF8, '4', '0', '0')
+#define AVERROR_HTTP_UNAUTHORIZED FFERRTAG(0xF8, '4', '0', '1')
+#define AVERROR_HTTP_FORBIDDEN FFERRTAG(0xF8, '4', '0', '3')
+#define AVERROR_HTTP_NOT_FOUND FFERRTAG(0xF8, '4', '0', '4')
+#define AVERROR_HTTP_OTHER_4XX FFERRTAG(0xF8, '4', 'X', 'X')
+#define AVERROR_HTTP_SERVER_ERROR FFERRTAG(0xF8, '5', 'X', 'X')
+
+#define AV_ERROR_MAX_STRING_SIZE 64
+
+/**
+ * Put a description of the AVERROR code errnum in errbuf.
+ * In case of failure the global variable errno is set to indicate the
+ * error. Even in case of failure av_strerror() will print a generic
+ * error message indicating the errnum provided to errbuf.
+ *
+ * @param errnum error code to describe
+ * @param errbuf buffer to which description is written
+ * @param errbuf_size the size in bytes of errbuf
+ * @return 0 on success, a negative value if a description for errnum
+ * cannot be found
+ */
+int av_strerror(int errnum, char* errbuf, size_t errbuf_size);
+
+/**
+ * Fill the provided buffer with a string containing an error string
+ * corresponding to the AVERROR code errnum.
+ *
+ * @param errbuf a buffer
+ * @param errbuf_size size in bytes of errbuf
+ * @param errnum error code to describe
+ * @return the buffer in input, filled with the error description
+ * @see av_strerror()
+ */
+static inline char* av_make_error_string(char* errbuf, size_t errbuf_size,
+ int errnum) {
+ av_strerror(errnum, errbuf, errbuf_size);
+ return errbuf;
+}
+
+/**
+ * Convenience macro, the return value should be used only directly in
+ * function arguments but never stand-alone.
+ */
+#define av_err2str(errnum) \
+ av_make_error_string((char[AV_ERROR_MAX_STRING_SIZE]){0}, \
+ AV_ERROR_MAX_STRING_SIZE, errnum)
+
+/**
+ * @}
+ */
+
+#endif /* AVUTIL_ERROR_H */
diff --git a/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/frame.h b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/frame.h
new file mode 100644
index 0000000000..82abfb925d
--- /dev/null
+++ b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/frame.h
@@ -0,0 +1,1112 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * @ingroup lavu_frame
+ * reference-counted frame API
+ */
+
+#ifndef AVUTIL_FRAME_H
+#define AVUTIL_FRAME_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "avutil.h"
+#include "buffer.h"
+#include "channel_layout.h"
+#include "dict.h"
+#include "rational.h"
+#include "samplefmt.h"
+#include "pixfmt.h"
+#include "version.h"
+
+/**
+ * @defgroup lavu_frame AVFrame
+ * @ingroup lavu_data
+ *
+ * @{
+ * AVFrame is an abstraction for reference-counted raw multimedia data.
+ */
+
+enum AVFrameSideDataType {
+ /**
+ * The data is the AVPanScan struct defined in libavcodec.
+ */
+ AV_FRAME_DATA_PANSCAN,
+ /**
+ * ATSC A53 Part 4 Closed Captions.
+ * A53 CC bitstream is stored as uint8_t in AVFrameSideData.data.
+ * The number of bytes of CC data is AVFrameSideData.size.
+ */
+ AV_FRAME_DATA_A53_CC,
+ /**
+ * Stereoscopic 3d metadata.
+ * The data is the AVStereo3D struct defined in libavutil/stereo3d.h.
+ */
+ AV_FRAME_DATA_STEREO3D,
+ /**
+ * The data is the AVMatrixEncoding enum defined in
+ * libavutil/channel_layout.h.
+ */
+ AV_FRAME_DATA_MATRIXENCODING,
+ /**
+ * Metadata relevant to a downmix procedure.
+ * The data is the AVDownmixInfo struct defined in libavutil/downmix_info.h.
+ */
+ AV_FRAME_DATA_DOWNMIX_INFO,
+ /**
+ * ReplayGain information in the form of the AVReplayGain struct.
+ */
+ AV_FRAME_DATA_REPLAYGAIN,
+ /**
+ * This side data contains a 3x3 transformation matrix describing an affine
+ * transformation that needs to be applied to the frame for correct
+ * presentation.
+ *
+ * See libavutil/display.h for a detailed description of the data.
+ */
+ AV_FRAME_DATA_DISPLAYMATRIX,
+ /**
+ * Active Format Description data consisting of a single byte as specified
+ * in ETSI TS 101 154 using AVActiveFormatDescription enum.
+ */
+ AV_FRAME_DATA_AFD,
+ /**
+ * Motion vectors exported by some codecs (on demand through the export_mvs
+ * flag set in the libavcodec AVCodecContext flags2 option).
+ * The data is the AVMotionVector struct defined in
+ * libavutil/motion_vector.h.
+ */
+ AV_FRAME_DATA_MOTION_VECTORS,
+ /**
+ * Recommmends skipping the specified number of samples. This is exported
+ * only if the "skip_manual" AVOption is set in libavcodec.
+ * This has the same format as AV_PKT_DATA_SKIP_SAMPLES.
+ * @code
+ * u32le number of samples to skip from start of this packet
+ * u32le number of samples to skip from end of this packet
+ * u8 reason for start skip
+ * u8 reason for end skip (0=padding silence, 1=convergence)
+ * @endcode
+ */
+ AV_FRAME_DATA_SKIP_SAMPLES,
+ /**
+ * This side data must be associated with an audio frame and corresponds to
+ * enum AVAudioServiceType defined in avcodec.h.
+ */
+ AV_FRAME_DATA_AUDIO_SERVICE_TYPE,
+ /**
+ * Mastering display metadata associated with a video frame. The payload is
+ * an AVMasteringDisplayMetadata type and contains information about the
+ * mastering display color volume.
+ */
+ AV_FRAME_DATA_MASTERING_DISPLAY_METADATA,
+ /**
+ * The GOP timecode in 25 bit timecode format. Data format is 64-bit integer.
+ * This is set on the first frame of a GOP that has a temporal reference of 0.
+ */
+ AV_FRAME_DATA_GOP_TIMECODE,
+
+ /**
+ * The data represents the AVSphericalMapping structure defined in
+ * libavutil/spherical.h.
+ */
+ AV_FRAME_DATA_SPHERICAL,
+
+ /**
+ * Content light level (based on CTA-861.3). This payload contains data in
+ * the form of the AVContentLightMetadata struct.
+ */
+ AV_FRAME_DATA_CONTENT_LIGHT_LEVEL,
+
+ /**
+ * The data contains an ICC profile as an opaque octet buffer following the
+ * format described by ISO 15076-1 with an optional name defined in the
+ * metadata key entry "name".
+ */
+ AV_FRAME_DATA_ICC_PROFILE,
+
+ /**
+ * Timecode which conforms to SMPTE ST 12-1. The data is an array of 4
+ * uint32_t where the first uint32_t describes how many (1-3) of the other
+ * timecodes are used. The timecode format is described in the documentation
+ * of av_timecode_get_smpte_from_framenum() function in libavutil/timecode.h.
+ */
+ AV_FRAME_DATA_S12M_TIMECODE,
+
+ /**
+ * HDR dynamic metadata associated with a video frame. The payload is
+ * an AVDynamicHDRPlus type and contains information for color
+ * volume transform - application 4 of SMPTE 2094-40:2016 standard.
+ */
+ AV_FRAME_DATA_DYNAMIC_HDR_PLUS,
+
+ /**
+ * Regions Of Interest, the data is an array of AVRegionOfInterest type, the
+ * number of array element is implied by AVFrameSideData.size /
+ * AVRegionOfInterest.self_size.
+ */
+ AV_FRAME_DATA_REGIONS_OF_INTEREST,
+
+ /**
+ * Encoding parameters for a video frame, as described by AVVideoEncParams.
+ */
+ AV_FRAME_DATA_VIDEO_ENC_PARAMS,
+
+ /**
+ * User data unregistered metadata associated with a video frame.
+ * This is the H.26[45] UDU SEI message, and shouldn't be used for any other
+ * purpose The data is stored as uint8_t in AVFrameSideData.data which is 16
+ * bytes of uuid_iso_iec_11578 followed by AVFrameSideData.size - 16 bytes of
+ * user_data_payload_byte.
+ */
+ AV_FRAME_DATA_SEI_UNREGISTERED,
+
+ /**
+ * Film grain parameters for a frame, described by AVFilmGrainParams.
+ * Must be present for every frame which should have film grain applied.
+ *
+ * May be present multiple times, for example when there are multiple
+ * alternative parameter sets for different video signal characteristics.
+ * The user should select the most appropriate set for the application.
+ */
+ AV_FRAME_DATA_FILM_GRAIN_PARAMS,
+
+ /**
+ * Bounding boxes for object detection and classification,
+ * as described by AVDetectionBBoxHeader.
+ */
+ AV_FRAME_DATA_DETECTION_BBOXES,
+
+ /**
+ * Dolby Vision RPU raw data, suitable for passing to x265
+ * or other libraries. Array of uint8_t, with NAL emulation
+ * bytes intact.
+ */
+ AV_FRAME_DATA_DOVI_RPU_BUFFER,
+
+ /**
+ * Parsed Dolby Vision metadata, suitable for passing to a software
+ * implementation. The payload is the AVDOVIMetadata struct defined in
+ * libavutil/dovi_meta.h.
+ */
+ AV_FRAME_DATA_DOVI_METADATA,
+
+ /**
+ * HDR Vivid dynamic metadata associated with a video frame. The payload is
+ * an AVDynamicHDRVivid type and contains information for color
+ * volume transform - CUVA 005.1-2021.
+ */
+ AV_FRAME_DATA_DYNAMIC_HDR_VIVID,
+
+ /**
+ * Ambient viewing environment metadata, as defined by H.274.
+ */
+ AV_FRAME_DATA_AMBIENT_VIEWING_ENVIRONMENT,
+
+ /**
+ * Provide encoder-specific hinting information about changed/unchanged
+ * portions of a frame. It can be used to pass information about which
+ * macroblocks can be skipped because they didn't change from the
+ * corresponding ones in the previous frame. This could be useful for
+ * applications which know this information in advance to speed up
+ * encoding.
+ */
+ AV_FRAME_DATA_VIDEO_HINT,
+};
+
+enum AVActiveFormatDescription {
+ AV_AFD_SAME = 8,
+ AV_AFD_4_3 = 9,
+ AV_AFD_16_9 = 10,
+ AV_AFD_14_9 = 11,
+ AV_AFD_4_3_SP_14_9 = 13,
+ AV_AFD_16_9_SP_14_9 = 14,
+ AV_AFD_SP_4_3 = 15,
+};
+
+/**
+ * Structure to hold side data for an AVFrame.
+ *
+ * sizeof(AVFrameSideData) is not a part of the public ABI, so new fields may be
+ * added to the end with a minor bump.
+ */
+typedef struct AVFrameSideData {
+ enum AVFrameSideDataType type;
+ uint8_t* data;
+ size_t size;
+ AVDictionary* metadata;
+ AVBufferRef* buf;
+} AVFrameSideData;
+
+enum AVSideDataProps {
+ /**
+ * The side data type can be used in stream-global structures.
+ * Side data types without this property are only meaningful on per-frame
+ * basis.
+ */
+ AV_SIDE_DATA_PROP_GLOBAL = (1 << 0),
+
+ /**
+ * Multiple instances of this side data type can be meaningfully present in
+ * a single side data array.
+ */
+ AV_SIDE_DATA_PROP_MULTI = (1 << 1),
+};
+
+/**
+ * This struct describes the properties of a side data type. Its instance
+ * corresponding to a given type can be obtained from av_frame_side_data_desc().
+ */
+typedef struct AVSideDataDescriptor {
+ /**
+ * Human-readable side data description.
+ */
+ const char* name;
+
+ /**
+ * Side data property flags, a combination of AVSideDataProps values.
+ */
+ unsigned props;
+} AVSideDataDescriptor;
+
+/**
+ * Structure describing a single Region Of Interest.
+ *
+ * When multiple regions are defined in a single side-data block, they
+ * should be ordered from most to least important - some encoders are only
+ * capable of supporting a limited number of distinct regions, so will have
+ * to truncate the list.
+ *
+ * When overlapping regions are defined, the first region containing a given
+ * area of the frame applies.
+ */
+typedef struct AVRegionOfInterest {
+ /**
+ * Must be set to the size of this data structure (that is,
+ * sizeof(AVRegionOfInterest)).
+ */
+ uint32_t self_size;
+ /**
+ * Distance in pixels from the top edge of the frame to the top and
+ * bottom edges and from the left edge of the frame to the left and
+ * right edges of the rectangle defining this region of interest.
+ *
+ * The constraints on a region are encoder dependent, so the region
+ * actually affected may be slightly larger for alignment or other
+ * reasons.
+ */
+ int top;
+ int bottom;
+ int left;
+ int right;
+ /**
+ * Quantisation offset.
+ *
+ * Must be in the range -1 to +1. A value of zero indicates no quality
+ * change. A negative value asks for better quality (less quantisation),
+ * while a positive value asks for worse quality (greater quantisation).
+ *
+ * The range is calibrated so that the extreme values indicate the
+ * largest possible offset - if the rest of the frame is encoded with the
+ * worst possible quality, an offset of -1 indicates that this region
+ * should be encoded with the best possible quality anyway. Intermediate
+ * values are then interpolated in some codec-dependent way.
+ *
+ * For example, in 10-bit H.264 the quantisation parameter varies between
+ * -12 and 51. A typical qoffset value of -1/10 therefore indicates that
+ * this region should be encoded with a QP around one-tenth of the full
+ * range better than the rest of the frame. So, if most of the frame
+ * were to be encoded with a QP of around 30, this region would get a QP
+ * of around 24 (an offset of approximately -1/10 * (51 - -12) = -6.3).
+ * An extreme value of -1 would indicate that this region should be
+ * encoded with the best possible quality regardless of the treatment of
+ * the rest of the frame - that is, should be encoded at a QP of -12.
+ */
+ AVRational qoffset;
+} AVRegionOfInterest;
+
+/**
+ * This structure describes decoded (raw) audio or video data.
+ *
+ * AVFrame must be allocated using av_frame_alloc(). Note that this only
+ * allocates the AVFrame itself, the buffers for the data must be managed
+ * through other means (see below).
+ * AVFrame must be freed with av_frame_free().
+ *
+ * AVFrame is typically allocated once and then reused multiple times to hold
+ * different data (e.g. a single AVFrame to hold frames received from a
+ * decoder). In such a case, av_frame_unref() will free any references held by
+ * the frame and reset it to its original clean state before it
+ * is reused again.
+ *
+ * The data described by an AVFrame is usually reference counted through the
+ * AVBuffer API. The underlying buffer references are stored in AVFrame.buf /
+ * AVFrame.extended_buf. An AVFrame is considered to be reference counted if at
+ * least one reference is set, i.e. if AVFrame.buf[0] != NULL. In such a case,
+ * every single data plane must be contained in one of the buffers in
+ * AVFrame.buf or AVFrame.extended_buf.
+ * There may be a single buffer for all the data, or one separate buffer for
+ * each plane, or anything in between.
+ *
+ * sizeof(AVFrame) is not a part of the public ABI, so new fields may be added
+ * to the end with a minor bump.
+ *
+ * Fields can be accessed through AVOptions, the name string used, matches the
+ * C structure field name for fields accessible through AVOptions. The AVClass
+ * for AVFrame can be obtained from avcodec_get_frame_class()
+ */
+typedef struct AVFrame {
+#define AV_NUM_DATA_POINTERS 8
+ /**
+ * pointer to the picture/channel planes.
+ * This might be different from the first allocated byte. For video,
+ * it could even point to the end of the image data.
+ *
+ * All pointers in data and extended_data must point into one of the
+ * AVBufferRef in buf or extended_buf.
+ *
+ * Some decoders access areas outside 0,0 - width,height, please
+ * see avcodec_align_dimensions2(). Some filters and swscale can read
+ * up to 16 bytes beyond the planes, if these filters are to be used,
+ * then 16 extra bytes must be allocated.
+ *
+ * NOTE: Pointers not needed by the format MUST be set to NULL.
+ *
+ * @attention In case of video, the data[] pointers can point to the
+ * end of image data in order to reverse line order, when used in
+ * combination with negative values in the linesize[] array.
+ */
+ uint8_t* data[AV_NUM_DATA_POINTERS];
+
+ /**
+ * For video, a positive or negative value, which is typically indicating
+ * the size in bytes of each picture line, but it can also be:
+ * - the negative byte size of lines for vertical flipping
+ * (with data[n] pointing to the end of the data
+ * - a positive or negative multiple of the byte size as for accessing
+ * even and odd fields of a frame (possibly flipped)
+ *
+ * For audio, only linesize[0] may be set. For planar audio, each channel
+ * plane must be the same size.
+ *
+ * For video the linesizes should be multiples of the CPUs alignment
+ * preference, this is 16 or 32 for modern desktop CPUs.
+ * Some code requires such alignment other code can be slower without
+ * correct alignment, for yet other it makes no difference.
+ *
+ * @note The linesize may be larger than the size of usable data -- there
+ * may be extra padding present for performance reasons.
+ *
+ * @attention In case of video, line size values can be negative to achieve
+ * a vertically inverted iteration over image lines.
+ */
+ int linesize[AV_NUM_DATA_POINTERS];
+
+ /**
+ * pointers to the data planes/channels.
+ *
+ * For video, this should simply point to data[].
+ *
+ * For planar audio, each channel has a separate data pointer, and
+ * linesize[0] contains the size of each channel buffer.
+ * For packed audio, there is just one data pointer, and linesize[0]
+ * contains the total size of the buffer for all channels.
+ *
+ * Note: Both data and extended_data should always be set in a valid frame,
+ * but for planar audio with more channels that can fit in data,
+ * extended_data must be used in order to access all channels.
+ */
+ uint8_t** extended_data;
+
+ /**
+ * @name Video dimensions
+ * Video frames only. The coded dimensions (in pixels) of the video frame,
+ * i.e. the size of the rectangle that contains some well-defined values.
+ *
+ * @note The part of the frame intended for display/presentation is further
+ * restricted by the @ref cropping "Cropping rectangle".
+ * @{
+ */
+ int width, height;
+ /**
+ * @}
+ */
+
+ /**
+ * number of audio samples (per channel) described by this frame
+ */
+ int nb_samples;
+
+ /**
+ * format of the frame, -1 if unknown or unset
+ * Values correspond to enum AVPixelFormat for video frames,
+ * enum AVSampleFormat for audio)
+ */
+ int format;
+
+#if FF_API_FRAME_KEY
+ /**
+ * 1 -> keyframe, 0-> not
+ *
+ * @deprecated Use AV_FRAME_FLAG_KEY instead
+ */
+ attribute_deprecated int key_frame;
+#endif
+
+ /**
+ * Picture type of the frame.
+ */
+ enum AVPictureType pict_type;
+
+ /**
+ * Sample aspect ratio for the video frame, 0/1 if unknown/unspecified.
+ */
+ AVRational sample_aspect_ratio;
+
+ /**
+ * Presentation timestamp in time_base units (time when frame should be shown
+ * to user).
+ */
+ int64_t pts;
+
+ /**
+ * DTS copied from the AVPacket that triggered returning this frame. (if frame
+ * threading isn't used) This is also the Presentation time of this AVFrame
+ * calculated from only AVPacket.dts values without pts values.
+ */
+ int64_t pkt_dts;
+
+ /**
+ * Time base for the timestamps in this frame.
+ * In the future, this field may be set on frames output by decoders or
+ * filters, but its value will be by default ignored on input to encoders
+ * or filters.
+ */
+ AVRational time_base;
+
+ /**
+ * quality (between 1 (good) and FF_LAMBDA_MAX (bad))
+ */
+ int quality;
+
+ /**
+ * Frame owner's private data.
+ *
+ * This field may be set by the code that allocates/owns the frame data.
+ * It is then not touched by any library functions, except:
+ * - it is copied to other references by av_frame_copy_props() (and hence by
+ * av_frame_ref());
+ * - it is set to NULL when the frame is cleared by av_frame_unref()
+ * - on the caller's explicit request. E.g. libavcodec encoders/decoders
+ * will copy this field to/from @ref AVPacket "AVPackets" if the caller sets
+ * @ref AV_CODEC_FLAG_COPY_OPAQUE.
+ *
+ * @see opaque_ref the reference-counted analogue
+ */
+ void* opaque;
+
+ /**
+ * Number of fields in this frame which should be repeated, i.e. the total
+ * duration of this frame should be repeat_pict + 2 normal field durations.
+ *
+ * For interlaced frames this field may be set to 1, which signals that this
+ * frame should be presented as 3 fields: beginning with the first field (as
+ * determined by AV_FRAME_FLAG_TOP_FIELD_FIRST being set or not), followed
+ * by the second field, and then the first field again.
+ *
+ * For progressive frames this field may be set to a multiple of 2, which
+ * signals that this frame's duration should be (repeat_pict + 2) / 2
+ * normal frame durations.
+ *
+ * @note This field is computed from MPEG2 repeat_first_field flag and its
+ * associated flags, H.264 pic_struct from picture timing SEI, and
+ * their analogues in other codecs. Typically it should only be used when
+ * higher-layer timing information is not available.
+ */
+ int repeat_pict;
+
+#if FF_API_INTERLACED_FRAME
+ /**
+ * The content of the picture is interlaced.
+ *
+ * @deprecated Use AV_FRAME_FLAG_INTERLACED instead
+ */
+ attribute_deprecated int interlaced_frame;
+
+ /**
+ * If the content is interlaced, is top field displayed first.
+ *
+ * @deprecated Use AV_FRAME_FLAG_TOP_FIELD_FIRST instead
+ */
+ attribute_deprecated int top_field_first;
+#endif
+
+#if FF_API_PALETTE_HAS_CHANGED
+ /**
+ * Tell user application that palette has changed from previous frame.
+ */
+ attribute_deprecated int palette_has_changed;
+#endif
+
+ /**
+ * Sample rate of the audio data.
+ */
+ int sample_rate;
+
+ /**
+ * AVBuffer references backing the data for this frame. All the pointers in
+ * data and extended_data must point inside one of the buffers in buf or
+ * extended_buf. This array must be filled contiguously -- if buf[i] is
+ * non-NULL then buf[j] must also be non-NULL for all j < i.
+ *
+ * There may be at most one AVBuffer per data plane, so for video this array
+ * always contains all the references. For planar audio with more than
+ * AV_NUM_DATA_POINTERS channels, there may be more buffers than can fit in
+ * this array. Then the extra AVBufferRef pointers are stored in the
+ * extended_buf array.
+ */
+ AVBufferRef* buf[AV_NUM_DATA_POINTERS];
+
+ /**
+ * For planar audio which requires more than AV_NUM_DATA_POINTERS
+ * AVBufferRef pointers, this array will hold all the references which
+ * cannot fit into AVFrame.buf.
+ *
+ * Note that this is different from AVFrame.extended_data, which always
+ * contains all the pointers. This array only contains the extra pointers,
+ * which cannot fit into AVFrame.buf.
+ *
+ * This array is always allocated using av_malloc() by whoever constructs
+ * the frame. It is freed in av_frame_unref().
+ */
+ AVBufferRef** extended_buf;
+ /**
+ * Number of elements in extended_buf.
+ */
+ int nb_extended_buf;
+
+ AVFrameSideData** side_data;
+ int nb_side_data;
+
+/**
+ * @defgroup lavu_frame_flags AV_FRAME_FLAGS
+ * @ingroup lavu_frame
+ * Flags describing additional frame properties.
+ *
+ * @{
+ */
+
+/**
+ * The frame data may be corrupted, e.g. due to decoding errors.
+ */
+#define AV_FRAME_FLAG_CORRUPT (1 << 0)
+/**
+ * A flag to mark frames that are keyframes.
+ */
+#define AV_FRAME_FLAG_KEY (1 << 1)
+/**
+ * A flag to mark the frames which need to be decoded, but shouldn't be output.
+ */
+#define AV_FRAME_FLAG_DISCARD (1 << 2)
+/**
+ * A flag to mark frames whose content is interlaced.
+ */
+#define AV_FRAME_FLAG_INTERLACED (1 << 3)
+/**
+ * A flag to mark frames where the top field is displayed first if the content
+ * is interlaced.
+ */
+#define AV_FRAME_FLAG_TOP_FIELD_FIRST (1 << 4)
+ /**
+ * @}
+ */
+
+ /**
+ * Frame flags, a combination of @ref lavu_frame_flags
+ */
+ int flags;
+
+ /**
+ * MPEG vs JPEG YUV range.
+ * - encoding: Set by user
+ * - decoding: Set by libavcodec
+ */
+ enum AVColorRange color_range;
+
+ enum AVColorPrimaries color_primaries;
+
+ enum AVColorTransferCharacteristic color_trc;
+
+ /**
+ * YUV colorspace type.
+ * - encoding: Set by user
+ * - decoding: Set by libavcodec
+ */
+ enum AVColorSpace colorspace;
+
+ enum AVChromaLocation chroma_location;
+
+ /**
+ * frame timestamp estimated using various heuristics, in stream time base
+ * - encoding: unused
+ * - decoding: set by libavcodec, read by user.
+ */
+ int64_t best_effort_timestamp;
+
+#if FF_API_FRAME_PKT
+ /**
+ * reordered pos from the last AVPacket that has been input into the decoder
+ * - encoding: unused
+ * - decoding: Read by user.
+ * @deprecated use AV_CODEC_FLAG_COPY_OPAQUE to pass through arbitrary user
+ * data from packets to frames
+ */
+ attribute_deprecated int64_t pkt_pos;
+#endif
+
+ /**
+ * metadata.
+ * - encoding: Set by user.
+ * - decoding: Set by libavcodec.
+ */
+ AVDictionary* metadata;
+
+ /**
+ * decode error flags of the frame, set to a combination of
+ * FF_DECODE_ERROR_xxx flags if the decoder produced a frame, but there
+ * were errors during the decoding.
+ * - encoding: unused
+ * - decoding: set by libavcodec, read by user.
+ */
+ int decode_error_flags;
+#define FF_DECODE_ERROR_INVALID_BITSTREAM 1
+#define FF_DECODE_ERROR_MISSING_REFERENCE 2
+#define FF_DECODE_ERROR_CONCEALMENT_ACTIVE 4
+#define FF_DECODE_ERROR_DECODE_SLICES 8
+
+#if FF_API_FRAME_PKT
+ /**
+ * size of the corresponding packet containing the compressed
+ * frame.
+ * It is set to a negative value if unknown.
+ * - encoding: unused
+ * - decoding: set by libavcodec, read by user.
+ * @deprecated use AV_CODEC_FLAG_COPY_OPAQUE to pass through arbitrary user
+ * data from packets to frames
+ */
+ attribute_deprecated int pkt_size;
+#endif
+
+ /**
+ * For hwaccel-format frames, this should be a reference to the
+ * AVHWFramesContext describing the frame.
+ */
+ AVBufferRef* hw_frames_ctx;
+
+ /**
+ * Frame owner's private data.
+ *
+ * This field may be set by the code that allocates/owns the frame data.
+ * It is then not touched by any library functions, except:
+ * - a new reference to the underlying buffer is propagated by
+ * av_frame_copy_props() (and hence by av_frame_ref());
+ * - it is unreferenced in av_frame_unref();
+ * - on the caller's explicit request. E.g. libavcodec encoders/decoders
+ * will propagate a new reference to/from @ref AVPacket "AVPackets" if the
+ * caller sets @ref AV_CODEC_FLAG_COPY_OPAQUE.
+ *
+ * @see opaque the plain pointer analogue
+ */
+ AVBufferRef* opaque_ref;
+
+ /**
+ * @anchor cropping
+ * @name Cropping
+ * Video frames only. The number of pixels to discard from the the
+ * top/bottom/left/right border of the frame to obtain the sub-rectangle of
+ * the frame intended for presentation.
+ * @{
+ */
+ size_t crop_top;
+ size_t crop_bottom;
+ size_t crop_left;
+ size_t crop_right;
+ /**
+ * @}
+ */
+
+ /**
+ * AVBufferRef for internal use by a single libav* library.
+ * Must not be used to transfer data between libraries.
+ * Has to be NULL when ownership of the frame leaves the respective library.
+ *
+ * Code outside the FFmpeg libs should never check or change the contents of
+ * the buffer ref.
+ *
+ * FFmpeg calls av_buffer_unref() on it when the frame is unreferenced.
+ * av_frame_copy_props() calls create a new reference with av_buffer_ref()
+ * for the target frame's private_ref field.
+ */
+ AVBufferRef* private_ref;
+
+ /**
+ * Channel layout of the audio data.
+ */
+ AVChannelLayout ch_layout;
+
+ /**
+ * Duration of the frame, in the same units as pts. 0 if unknown.
+ */
+ int64_t duration;
+} AVFrame;
+
+/**
+ * Allocate an AVFrame and set its fields to default values. The resulting
+ * struct must be freed using av_frame_free().
+ *
+ * @return An AVFrame filled with default values or NULL on failure.
+ *
+ * @note this only allocates the AVFrame itself, not the data buffers. Those
+ * must be allocated through other means, e.g. with av_frame_get_buffer() or
+ * manually.
+ */
+AVFrame* av_frame_alloc(void);
+
+/**
+ * Free the frame and any dynamically allocated objects in it,
+ * e.g. extended_data. If the frame is reference counted, it will be
+ * unreferenced first.
+ *
+ * @param frame frame to be freed. The pointer will be set to NULL.
+ */
+void av_frame_free(AVFrame** frame);
+
+/**
+ * Set up a new reference to the data described by the source frame.
+ *
+ * Copy frame properties from src to dst and create a new reference for each
+ * AVBufferRef from src.
+ *
+ * If src is not reference counted, new buffers are allocated and the data is
+ * copied.
+ *
+ * @warning: dst MUST have been either unreferenced with av_frame_unref(dst),
+ * or newly allocated with av_frame_alloc() before calling this
+ * function, or undefined behavior will occur.
+ *
+ * @return 0 on success, a negative AVERROR on error
+ */
+int av_frame_ref(AVFrame* dst, const AVFrame* src);
+
+/**
+ * Ensure the destination frame refers to the same data described by the source
+ * frame, either by creating a new reference for each AVBufferRef from src if
+ * they differ from those in dst, by allocating new buffers and copying data if
+ * src is not reference counted, or by unrefencing it if src is empty.
+ *
+ * Frame properties on dst will be replaced by those from src.
+ *
+ * @return 0 on success, a negative AVERROR on error. On error, dst is
+ * unreferenced.
+ */
+int av_frame_replace(AVFrame* dst, const AVFrame* src);
+
+/**
+ * Create a new frame that references the same data as src.
+ *
+ * This is a shortcut for av_frame_alloc()+av_frame_ref().
+ *
+ * @return newly created AVFrame on success, NULL on error.
+ */
+AVFrame* av_frame_clone(const AVFrame* src);
+
+/**
+ * Unreference all the buffers referenced by frame and reset the frame fields.
+ */
+void av_frame_unref(AVFrame* frame);
+
+/**
+ * Move everything contained in src to dst and reset src.
+ *
+ * @warning: dst is not unreferenced, but directly overwritten without reading
+ * or deallocating its contents. Call av_frame_unref(dst) manually
+ * before calling this function to ensure that no memory is leaked.
+ */
+void av_frame_move_ref(AVFrame* dst, AVFrame* src);
+
+/**
+ * Allocate new buffer(s) for audio or video data.
+ *
+ * The following fields must be set on frame before calling this function:
+ * - format (pixel format for video, sample format for audio)
+ * - width and height for video
+ * - nb_samples and ch_layout for audio
+ *
+ * This function will fill AVFrame.data and AVFrame.buf arrays and, if
+ * necessary, allocate and fill AVFrame.extended_data and AVFrame.extended_buf.
+ * For planar formats, one buffer will be allocated for each plane.
+ *
+ * @warning: if frame already has been allocated, calling this function will
+ * leak memory. In addition, undefined behavior can occur in certain
+ * cases.
+ *
+ * @param frame frame in which to store the new buffers.
+ * @param align Required buffer size alignment. If equal to 0, alignment will be
+ * chosen automatically for the current CPU. It is highly
+ * recommended to pass 0 here unless you know what you are doing.
+ *
+ * @return 0 on success, a negative AVERROR on error.
+ */
+int av_frame_get_buffer(AVFrame* frame, int align);
+
+/**
+ * Check if the frame data is writable.
+ *
+ * @return A positive value if the frame data is writable (which is true if and
+ * only if each of the underlying buffers has only one reference, namely the one
+ * stored in this frame). Return 0 otherwise.
+ *
+ * If 1 is returned the answer is valid until av_buffer_ref() is called on any
+ * of the underlying AVBufferRefs (e.g. through av_frame_ref() or directly).
+ *
+ * @see av_frame_make_writable(), av_buffer_is_writable()
+ */
+int av_frame_is_writable(AVFrame* frame);
+
+/**
+ * Ensure that the frame data is writable, avoiding data copy if possible.
+ *
+ * Do nothing if the frame is writable, allocate new buffers and copy the data
+ * if it is not. Non-refcounted frames behave as non-writable, i.e. a copy
+ * is always made.
+ *
+ * @return 0 on success, a negative AVERROR on error.
+ *
+ * @see av_frame_is_writable(), av_buffer_is_writable(),
+ * av_buffer_make_writable()
+ */
+int av_frame_make_writable(AVFrame* frame);
+
+/**
+ * Copy the frame data from src to dst.
+ *
+ * This function does not allocate anything, dst must be already initialized and
+ * allocated with the same parameters as src.
+ *
+ * This function only copies the frame data (i.e. the contents of the data /
+ * extended data arrays), not any other properties.
+ *
+ * @return >= 0 on success, a negative AVERROR on error.
+ */
+int av_frame_copy(AVFrame* dst, const AVFrame* src);
+
+/**
+ * Copy only "metadata" fields from src to dst.
+ *
+ * Metadata for the purpose of this function are those fields that do not affect
+ * the data layout in the buffers. E.g. pts, sample rate (for audio) or sample
+ * aspect ratio (for video), but not width/height or channel layout.
+ * Side data is also copied.
+ */
+int av_frame_copy_props(AVFrame* dst, const AVFrame* src);
+
+/**
+ * Get the buffer reference a given data plane is stored in.
+ *
+ * @param frame the frame to get the plane's buffer from
+ * @param plane index of the data plane of interest in frame->extended_data.
+ *
+ * @return the buffer reference that contains the plane or NULL if the input
+ * frame is not valid.
+ */
+AVBufferRef* av_frame_get_plane_buffer(const AVFrame* frame, int plane);
+
+/**
+ * Add a new side data to a frame.
+ *
+ * @param frame a frame to which the side data should be added
+ * @param type type of the added side data
+ * @param size size of the side data
+ *
+ * @return newly added side data on success, NULL on error
+ */
+AVFrameSideData* av_frame_new_side_data(AVFrame* frame,
+ enum AVFrameSideDataType type,
+ size_t size);
+
+/**
+ * Add a new side data to a frame from an existing AVBufferRef
+ *
+ * @param frame a frame to which the side data should be added
+ * @param type the type of the added side data
+ * @param buf an AVBufferRef to add as side data. The ownership of
+ * the reference is transferred to the frame.
+ *
+ * @return newly added side data on success, NULL on error. On failure
+ * the frame is unchanged and the AVBufferRef remains owned by
+ * the caller.
+ */
+AVFrameSideData* av_frame_new_side_data_from_buf(AVFrame* frame,
+ enum AVFrameSideDataType type,
+ AVBufferRef* buf);
+
+/**
+ * @return a pointer to the side data of a given type on success, NULL if there
+ * is no side data with such type in this frame.
+ */
+AVFrameSideData* av_frame_get_side_data(const AVFrame* frame,
+ enum AVFrameSideDataType type);
+
+/**
+ * Remove and free all side data instances of the given type.
+ */
+void av_frame_remove_side_data(AVFrame* frame, enum AVFrameSideDataType type);
+
+/**
+ * Flags for frame cropping.
+ */
+enum {
+ /**
+ * Apply the maximum possible cropping, even if it requires setting the
+ * AVFrame.data[] entries to unaligned pointers. Passing unaligned data
+ * to FFmpeg API is generally not allowed, and causes undefined behavior
+ * (such as crashes). You can pass unaligned data only to FFmpeg APIs that
+ * are explicitly documented to accept it. Use this flag only if you
+ * absolutely know what you are doing.
+ */
+ AV_FRAME_CROP_UNALIGNED = 1 << 0,
+};
+
+/**
+ * Crop the given video AVFrame according to its crop_left/crop_top/crop_right/
+ * crop_bottom fields. If cropping is successful, the function will adjust the
+ * data pointers and the width/height fields, and set the crop fields to 0.
+ *
+ * In all cases, the cropping boundaries will be rounded to the inherent
+ * alignment of the pixel format. In some cases, such as for opaque hwaccel
+ * formats, the left/top cropping is ignored. The crop fields are set to 0 even
+ * if the cropping was rounded or ignored.
+ *
+ * @param frame the frame which should be cropped
+ * @param flags Some combination of AV_FRAME_CROP_* flags, or 0.
+ *
+ * @return >= 0 on success, a negative AVERROR on error. If the cropping fields
+ * were invalid, AVERROR(ERANGE) is returned, and nothing is changed.
+ */
+int av_frame_apply_cropping(AVFrame* frame, int flags);
+
+/**
+ * @return a string identifying the side data type
+ */
+const char* av_frame_side_data_name(enum AVFrameSideDataType type);
+
+/**
+ * @return side data descriptor corresponding to a given side data type, NULL
+ * when not available.
+ */
+const AVSideDataDescriptor* av_frame_side_data_desc(
+ enum AVFrameSideDataType type);
+
+/**
+ * Free all side data entries and their contents, then zeroes out the
+ * values which the pointers are pointing to.
+ *
+ * @param sd pointer to array of side data to free. Will be set to NULL
+ * upon return.
+ * @param nb_sd pointer to an integer containing the number of entries in
+ * the array. Will be set to 0 upon return.
+ */
+void av_frame_side_data_free(AVFrameSideData*** sd, int* nb_sd);
+
+#define AV_FRAME_SIDE_DATA_FLAG_UNIQUE (1 << 0)
+
+/**
+ * Add new side data entry to an array.
+ *
+ * @param sd pointer to array of side data to which to add another entry,
+ * or to NULL in order to start a new array.
+ * @param nb_sd pointer to an integer containing the number of entries in
+ * the array.
+ * @param type type of the added side data
+ * @param size size of the side data
+ * @param flags Some combination of AV_FRAME_SIDE_DATA_FLAG_* flags, or 0.
+ *
+ * @return newly added side data on success, NULL on error. In case of
+ * AV_FRAME_SIDE_DATA_FLAG_UNIQUE being set, entries of matching
+ * AVFrameSideDataType will be removed before the addition is
+ * attempted.
+ */
+AVFrameSideData* av_frame_side_data_new(AVFrameSideData*** sd, int* nb_sd,
+ enum AVFrameSideDataType type,
+ size_t size, unsigned int flags);
+
+/**
+ * Add a new side data entry to an array based on existing side data, taking
+ * a reference towards the contained AVBufferRef.
+ *
+ * @param sd pointer to array of side data to which to add another entry,
+ * or to NULL in order to start a new array.
+ * @param nb_sd pointer to an integer containing the number of entries in
+ * the array.
+ * @param src side data to be cloned, with a new reference utilized
+ * for the buffer.
+ * @param flags Some combination of AV_FRAME_SIDE_DATA_FLAG_* flags, or 0.
+ *
+ * @return negative error code on failure, >=0 on success. In case of
+ * AV_FRAME_SIDE_DATA_FLAG_UNIQUE being set, entries of matching
+ * AVFrameSideDataType will be removed before the addition is
+ * attempted.
+ */
+int av_frame_side_data_clone(AVFrameSideData*** sd, int* nb_sd,
+ const AVFrameSideData* src, unsigned int flags);
+
+/**
+ * Get a side data entry of a specific type from an array.
+ *
+ * @param sd array of side data.
+ * @param nb_sd integer containing the number of entries in the array.
+ * @param type type of side data to be queried
+ *
+ * @return a pointer to the side data of a given type on success, NULL if there
+ * is no side data with such type in this set.
+ */
+const AVFrameSideData* av_frame_side_data_get_c(
+ const AVFrameSideData* const* sd, const int nb_sd,
+ enum AVFrameSideDataType type);
+
+/**
+ * Wrapper around av_frame_side_data_get_c() to workaround the limitation
+ * that for any type T the conversion from T * const * to const T * const *
+ * is not performed automatically in C.
+ * @see av_frame_side_data_get_c()
+ */
+static inline const AVFrameSideData* av_frame_side_data_get(
+ AVFrameSideData* const* sd, const int nb_sd,
+ enum AVFrameSideDataType type) {
+ return av_frame_side_data_get_c((const AVFrameSideData* const*)sd, nb_sd,
+ type);
+}
+
+/**
+ * @}
+ */
+
+#endif /* AVUTIL_FRAME_H */
diff --git a/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/hwcontext.h b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/hwcontext.h
new file mode 100644
index 0000000000..f2c5426d5f
--- /dev/null
+++ b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/hwcontext.h
@@ -0,0 +1,594 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVUTIL_HWCONTEXT_H
+#define AVUTIL_HWCONTEXT_H
+
+#include "buffer.h"
+#include "frame.h"
+#include "log.h"
+#include "pixfmt.h"
+
+enum AVHWDeviceType {
+ AV_HWDEVICE_TYPE_NONE,
+ AV_HWDEVICE_TYPE_VDPAU,
+ AV_HWDEVICE_TYPE_CUDA,
+ AV_HWDEVICE_TYPE_VAAPI,
+ AV_HWDEVICE_TYPE_DXVA2,
+ AV_HWDEVICE_TYPE_QSV,
+ AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
+ AV_HWDEVICE_TYPE_D3D11VA,
+ AV_HWDEVICE_TYPE_DRM,
+ AV_HWDEVICE_TYPE_OPENCL,
+ AV_HWDEVICE_TYPE_MEDIACODEC,
+ AV_HWDEVICE_TYPE_VULKAN,
+ AV_HWDEVICE_TYPE_D3D12VA,
+};
+
+/**
+ * This struct aggregates all the (hardware/vendor-specific) "high-level" state,
+ * i.e. state that is not tied to a concrete processing configuration.
+ * E.g., in an API that supports hardware-accelerated encoding and decoding,
+ * this struct will (if possible) wrap the state that is common to both encoding
+ * and decoding and from which specific instances of encoders or decoders can be
+ * derived.
+ *
+ * This struct is reference-counted with the AVBuffer mechanism. The
+ * av_hwdevice_ctx_alloc() constructor yields a reference, whose data field
+ * points to the actual AVHWDeviceContext. Further objects derived from
+ * AVHWDeviceContext (such as AVHWFramesContext, describing a frame pool with
+ * specific properties) will hold an internal reference to it. After all the
+ * references are released, the AVHWDeviceContext itself will be freed,
+ * optionally invoking a user-specified callback for uninitializing the hardware
+ * state.
+ */
+typedef struct AVHWDeviceContext {
+ /**
+ * A class for logging. Set by av_hwdevice_ctx_alloc().
+ */
+ const AVClass* av_class;
+
+ /**
+ * This field identifies the underlying API used for hardware access.
+ *
+ * This field is set when this struct is allocated and never changed
+ * afterwards.
+ */
+ enum AVHWDeviceType type;
+
+ /**
+ * The format-specific data, allocated and freed by libavutil along with
+ * this context.
+ *
+ * Should be cast by the user to the format-specific context defined in the
+ * corresponding header (hwcontext_*.h) and filled as described in the
+ * documentation before calling av_hwdevice_ctx_init().
+ *
+ * After calling av_hwdevice_ctx_init() this struct should not be modified
+ * by the caller.
+ */
+ void* hwctx;
+
+ /**
+ * This field may be set by the caller before calling av_hwdevice_ctx_init().
+ *
+ * If non-NULL, this callback will be called when the last reference to
+ * this context is unreferenced, immediately before it is freed.
+ *
+ * @note when other objects (e.g an AVHWFramesContext) are derived from this
+ * struct, this callback will be invoked after all such child objects
+ * are fully uninitialized and their respective destructors invoked.
+ */
+ void (*free)(struct AVHWDeviceContext* ctx);
+
+ /**
+ * Arbitrary user data, to be used e.g. by the free() callback.
+ */
+ void* user_opaque;
+} AVHWDeviceContext;
+
+/**
+ * This struct describes a set or pool of "hardware" frames (i.e. those with
+ * data not located in normal system memory). All the frames in the pool are
+ * assumed to be allocated in the same way and interchangeable.
+ *
+ * This struct is reference-counted with the AVBuffer mechanism and tied to a
+ * given AVHWDeviceContext instance. The av_hwframe_ctx_alloc() constructor
+ * yields a reference, whose data field points to the actual AVHWFramesContext
+ * struct.
+ */
+typedef struct AVHWFramesContext {
+ /**
+ * A class for logging.
+ */
+ const AVClass* av_class;
+
+ /**
+ * A reference to the parent AVHWDeviceContext. This reference is owned and
+ * managed by the enclosing AVHWFramesContext, but the caller may derive
+ * additional references from it.
+ */
+ AVBufferRef* device_ref;
+
+ /**
+ * The parent AVHWDeviceContext. This is simply a pointer to
+ * device_ref->data provided for convenience.
+ *
+ * Set by libavutil in av_hwframe_ctx_init().
+ */
+ AVHWDeviceContext* device_ctx;
+
+ /**
+ * The format-specific data, allocated and freed automatically along with
+ * this context.
+ *
+ * The user shall ignore this field if the corresponding format-specific
+ * header (hwcontext_*.h) does not define a context to be used as
+ * AVHWFramesContext.hwctx.
+ *
+ * Otherwise, it should be cast by the user to said context and filled
+ * as described in the documentation before calling av_hwframe_ctx_init().
+ *
+ * After any frames using this context are created, the contents of this
+ * struct should not be modified by the caller.
+ */
+ void* hwctx;
+
+ /**
+ * This field may be set by the caller before calling av_hwframe_ctx_init().
+ *
+ * If non-NULL, this callback will be called when the last reference to
+ * this context is unreferenced, immediately before it is freed.
+ */
+ void (*free)(struct AVHWFramesContext* ctx);
+
+ /**
+ * Arbitrary user data, to be used e.g. by the free() callback.
+ */
+ void* user_opaque;
+
+ /**
+ * A pool from which the frames are allocated by av_hwframe_get_buffer().
+ * This field may be set by the caller before calling av_hwframe_ctx_init().
+ * The buffers returned by calling av_buffer_pool_get() on this pool must
+ * have the properties described in the documentation in the corresponding hw
+ * type's header (hwcontext_*.h). The pool will be freed strictly before
+ * this struct's free() callback is invoked.
+ *
+ * This field may be NULL, then libavutil will attempt to allocate a pool
+ * internally. Note that certain device types enforce pools allocated at
+ * fixed size (frame count), which cannot be extended dynamically. In such a
+ * case, initial_pool_size must be set appropriately.
+ */
+ AVBufferPool* pool;
+
+ /**
+ * Initial size of the frame pool. If a device type does not support
+ * dynamically resizing the pool, then this is also the maximum pool size.
+ *
+ * May be set by the caller before calling av_hwframe_ctx_init(). Must be
+ * set if pool is NULL and the device type does not support dynamic pools.
+ */
+ int initial_pool_size;
+
+ /**
+ * The pixel format identifying the underlying HW surface type.
+ *
+ * Must be a hwaccel format, i.e. the corresponding descriptor must have the
+ * AV_PIX_FMT_FLAG_HWACCEL flag set.
+ *
+ * Must be set by the user before calling av_hwframe_ctx_init().
+ */
+ enum AVPixelFormat format;
+
+ /**
+ * The pixel format identifying the actual data layout of the hardware
+ * frames.
+ *
+ * Must be set by the caller before calling av_hwframe_ctx_init().
+ *
+ * @note when the underlying API does not provide the exact data layout, but
+ * only the colorspace/bit depth, this field should be set to the fully
+ * planar version of that format (e.g. for 8-bit 420 YUV it should be
+ * AV_PIX_FMT_YUV420P, not AV_PIX_FMT_NV12 or anything else).
+ */
+ enum AVPixelFormat sw_format;
+
+ /**
+ * The allocated dimensions of the frames in this pool.
+ *
+ * Must be set by the user before calling av_hwframe_ctx_init().
+ */
+ int width, height;
+} AVHWFramesContext;
+
+/**
+ * Look up an AVHWDeviceType by name.
+ *
+ * @param name String name of the device type (case-insensitive).
+ * @return The type from enum AVHWDeviceType, or AV_HWDEVICE_TYPE_NONE if
+ * not found.
+ */
+enum AVHWDeviceType av_hwdevice_find_type_by_name(const char* name);
+
+/** Get the string name of an AVHWDeviceType.
+ *
+ * @param type Type from enum AVHWDeviceType.
+ * @return Pointer to a static string containing the name, or NULL if the type
+ * is not valid.
+ */
+const char* av_hwdevice_get_type_name(enum AVHWDeviceType type);
+
+/**
+ * Iterate over supported device types.
+ *
+ * @param prev AV_HWDEVICE_TYPE_NONE initially, then the previous type
+ * returned by this function in subsequent iterations.
+ * @return The next usable device type from enum AVHWDeviceType, or
+ * AV_HWDEVICE_TYPE_NONE if there are no more.
+ */
+enum AVHWDeviceType av_hwdevice_iterate_types(enum AVHWDeviceType prev);
+
+/**
+ * Allocate an AVHWDeviceContext for a given hardware type.
+ *
+ * @param type the type of the hardware device to allocate.
+ * @return a reference to the newly created AVHWDeviceContext on success or NULL
+ * on failure.
+ */
+AVBufferRef* av_hwdevice_ctx_alloc(enum AVHWDeviceType type);
+
+/**
+ * Finalize the device context before use. This function must be called after
+ * the context is filled with all the required information and before it is
+ * used in any way.
+ *
+ * @param ref a reference to the AVHWDeviceContext
+ * @return 0 on success, a negative AVERROR code on failure
+ */
+int av_hwdevice_ctx_init(AVBufferRef* ref);
+
+/**
+ * Open a device of the specified type and create an AVHWDeviceContext for it.
+ *
+ * This is a convenience function intended to cover the simple cases. Callers
+ * who need to fine-tune device creation/management should open the device
+ * manually and then wrap it in an AVHWDeviceContext using
+ * av_hwdevice_ctx_alloc()/av_hwdevice_ctx_init().
+ *
+ * The returned context is already initialized and ready for use, the caller
+ * should not call av_hwdevice_ctx_init() on it. The user_opaque/free fields of
+ * the created AVHWDeviceContext are set by this function and should not be
+ * touched by the caller.
+ *
+ * @param device_ctx On success, a reference to the newly-created device context
+ * will be written here. The reference is owned by the caller
+ * and must be released with av_buffer_unref() when no longer
+ * needed. On failure, NULL will be written to this pointer.
+ * @param type The type of the device to create.
+ * @param device A type-specific string identifying the device to open.
+ * @param opts A dictionary of additional (type-specific) options to use in
+ * opening the device. The dictionary remains owned by the caller.
+ * @param flags currently unused
+ *
+ * @return 0 on success, a negative AVERROR code on failure.
+ */
+int av_hwdevice_ctx_create(AVBufferRef** device_ctx, enum AVHWDeviceType type,
+ const char* device, AVDictionary* opts, int flags);
+
+/**
+ * Create a new device of the specified type from an existing device.
+ *
+ * If the source device is a device of the target type or was originally
+ * derived from such a device (possibly through one or more intermediate
+ * devices of other types), then this will return a reference to the
+ * existing device of the same type as is requested.
+ *
+ * Otherwise, it will attempt to derive a new device from the given source
+ * device. If direct derivation to the new type is not implemented, it will
+ * attempt the same derivation from each ancestor of the source device in
+ * turn looking for an implemented derivation method.
+ *
+ * @param dst_ctx On success, a reference to the newly-created
+ * AVHWDeviceContext.
+ * @param type The type of the new device to create.
+ * @param src_ctx A reference to an existing AVHWDeviceContext which will be
+ * used to create the new device.
+ * @param flags Currently unused; should be set to zero.
+ * @return Zero on success, a negative AVERROR code on failure.
+ */
+int av_hwdevice_ctx_create_derived(AVBufferRef** dst_ctx,
+ enum AVHWDeviceType type,
+ AVBufferRef* src_ctx, int flags);
+
+/**
+ * Create a new device of the specified type from an existing device.
+ *
+ * This function performs the same action as av_hwdevice_ctx_create_derived,
+ * however, it is able to set options for the new device to be derived.
+ *
+ * @param dst_ctx On success, a reference to the newly-created
+ * AVHWDeviceContext.
+ * @param type The type of the new device to create.
+ * @param src_ctx A reference to an existing AVHWDeviceContext which will be
+ * used to create the new device.
+ * @param options Options for the new device to create, same format as in
+ * av_hwdevice_ctx_create.
+ * @param flags Currently unused; should be set to zero.
+ * @return Zero on success, a negative AVERROR code on failure.
+ */
+int av_hwdevice_ctx_create_derived_opts(AVBufferRef** dst_ctx,
+ enum AVHWDeviceType type,
+ AVBufferRef* src_ctx,
+ AVDictionary* options, int flags);
+
+/**
+ * Allocate an AVHWFramesContext tied to a given device context.
+ *
+ * @param device_ctx a reference to a AVHWDeviceContext. This function will make
+ * a new reference for internal use, the one passed to the
+ * function remains owned by the caller.
+ * @return a reference to the newly created AVHWFramesContext on success or NULL
+ * on failure.
+ */
+AVBufferRef* av_hwframe_ctx_alloc(AVBufferRef* device_ctx);
+
+/**
+ * Finalize the context before use. This function must be called after the
+ * context is filled with all the required information and before it is attached
+ * to any frames.
+ *
+ * @param ref a reference to the AVHWFramesContext
+ * @return 0 on success, a negative AVERROR code on failure
+ */
+int av_hwframe_ctx_init(AVBufferRef* ref);
+
+/**
+ * Allocate a new frame attached to the given AVHWFramesContext.
+ *
+ * @param hwframe_ctx a reference to an AVHWFramesContext
+ * @param frame an empty (freshly allocated or unreffed) frame to be filled with
+ * newly allocated buffers.
+ * @param flags currently unused, should be set to zero
+ * @return 0 on success, a negative AVERROR code on failure
+ */
+int av_hwframe_get_buffer(AVBufferRef* hwframe_ctx, AVFrame* frame, int flags);
+
+/**
+ * Copy data to or from a hw surface. At least one of dst/src must have an
+ * AVHWFramesContext attached.
+ *
+ * If src has an AVHWFramesContext attached, then the format of dst (if set)
+ * must use one of the formats returned by av_hwframe_transfer_get_formats(src,
+ * AV_HWFRAME_TRANSFER_DIRECTION_FROM).
+ * If dst has an AVHWFramesContext attached, then the format of src must use one
+ * of the formats returned by av_hwframe_transfer_get_formats(dst,
+ * AV_HWFRAME_TRANSFER_DIRECTION_TO)
+ *
+ * dst may be "clean" (i.e. with data/buf pointers unset), in which case the
+ * data buffers will be allocated by this function using av_frame_get_buffer().
+ * If dst->format is set, then this format will be used, otherwise (when
+ * dst->format is AV_PIX_FMT_NONE) the first acceptable format will be chosen.
+ *
+ * The two frames must have matching allocated dimensions (i.e. equal to
+ * AVHWFramesContext.width/height), since not all device types support
+ * transferring a sub-rectangle of the whole surface. The display dimensions
+ * (i.e. AVFrame.width/height) may be smaller than the allocated dimensions, but
+ * also have to be equal for both frames. When the display dimensions are
+ * smaller than the allocated dimensions, the content of the padding in the
+ * destination frame is unspecified.
+ *
+ * @param dst the destination frame. dst is not touched on failure.
+ * @param src the source frame.
+ * @param flags currently unused, should be set to zero
+ * @return 0 on success, a negative AVERROR error code on failure.
+ */
+int av_hwframe_transfer_data(AVFrame* dst, const AVFrame* src, int flags);
+
+enum AVHWFrameTransferDirection {
+ /**
+ * Transfer the data from the queried hw frame.
+ */
+ AV_HWFRAME_TRANSFER_DIRECTION_FROM,
+
+ /**
+ * Transfer the data to the queried hw frame.
+ */
+ AV_HWFRAME_TRANSFER_DIRECTION_TO,
+};
+
+/**
+ * Get a list of possible source or target formats usable in
+ * av_hwframe_transfer_data().
+ *
+ * @param hwframe_ctx the frame context to obtain the information for
+ * @param dir the direction of the transfer
+ * @param formats the pointer to the output format list will be written here.
+ * The list is terminated with AV_PIX_FMT_NONE and must be freed
+ * by the caller when no longer needed using av_free().
+ * If this function returns successfully, the format list will
+ * have at least one item (not counting the terminator).
+ * On failure, the contents of this pointer are unspecified.
+ * @param flags currently unused, should be set to zero
+ * @return 0 on success, a negative AVERROR code on failure.
+ */
+int av_hwframe_transfer_get_formats(AVBufferRef* hwframe_ctx,
+ enum AVHWFrameTransferDirection dir,
+ enum AVPixelFormat** formats, int flags);
+
+/**
+ * This struct describes the constraints on hardware frames attached to
+ * a given device with a hardware-specific configuration. This is returned
+ * by av_hwdevice_get_hwframe_constraints() and must be freed by
+ * av_hwframe_constraints_free() after use.
+ */
+typedef struct AVHWFramesConstraints {
+ /**
+ * A list of possible values for format in the hw_frames_ctx,
+ * terminated by AV_PIX_FMT_NONE. This member will always be filled.
+ */
+ enum AVPixelFormat* valid_hw_formats;
+
+ /**
+ * A list of possible values for sw_format in the hw_frames_ctx,
+ * terminated by AV_PIX_FMT_NONE. Can be NULL if this information is
+ * not known.
+ */
+ enum AVPixelFormat* valid_sw_formats;
+
+ /**
+ * The minimum size of frames in this hw_frames_ctx.
+ * (Zero if not known.)
+ */
+ int min_width;
+ int min_height;
+
+ /**
+ * The maximum size of frames in this hw_frames_ctx.
+ * (INT_MAX if not known / no limit.)
+ */
+ int max_width;
+ int max_height;
+} AVHWFramesConstraints;
+
+/**
+ * Allocate a HW-specific configuration structure for a given HW device.
+ * After use, the user must free all members as required by the specific
+ * hardware structure being used, then free the structure itself with
+ * av_free().
+ *
+ * @param device_ctx a reference to the associated AVHWDeviceContext.
+ * @return The newly created HW-specific configuration structure on
+ * success or NULL on failure.
+ */
+void* av_hwdevice_hwconfig_alloc(AVBufferRef* device_ctx);
+
+/**
+ * Get the constraints on HW frames given a device and the HW-specific
+ * configuration to be used with that device. If no HW-specific
+ * configuration is provided, returns the maximum possible capabilities
+ * of the device.
+ *
+ * @param ref a reference to the associated AVHWDeviceContext.
+ * @param hwconfig a filled HW-specific configuration structure, or NULL
+ * to return the maximum possible capabilities of the device.
+ * @return AVHWFramesConstraints structure describing the constraints
+ * on the device, or NULL if not available.
+ */
+AVHWFramesConstraints* av_hwdevice_get_hwframe_constraints(
+ AVBufferRef* ref, const void* hwconfig);
+
+/**
+ * Free an AVHWFrameConstraints structure.
+ *
+ * @param constraints The (filled or unfilled) AVHWFrameConstraints structure.
+ */
+void av_hwframe_constraints_free(AVHWFramesConstraints** constraints);
+
+/**
+ * Flags to apply to frame mappings.
+ */
+enum {
+ /**
+ * The mapping must be readable.
+ */
+ AV_HWFRAME_MAP_READ = 1 << 0,
+ /**
+ * The mapping must be writeable.
+ */
+ AV_HWFRAME_MAP_WRITE = 1 << 1,
+ /**
+ * The mapped frame will be overwritten completely in subsequent
+ * operations, so the current frame data need not be loaded. Any values
+ * which are not overwritten are unspecified.
+ */
+ AV_HWFRAME_MAP_OVERWRITE = 1 << 2,
+ /**
+ * The mapping must be direct. That is, there must not be any copying in
+ * the map or unmap steps. Note that performance of direct mappings may
+ * be much lower than normal memory.
+ */
+ AV_HWFRAME_MAP_DIRECT = 1 << 3,
+};
+
+/**
+ * Map a hardware frame.
+ *
+ * This has a number of different possible effects, depending on the format
+ * and origin of the src and dst frames. On input, src should be a usable
+ * frame with valid buffers and dst should be blank (typically as just created
+ * by av_frame_alloc()). src should have an associated hwframe context, and
+ * dst may optionally have a format and associated hwframe context.
+ *
+ * If src was created by mapping a frame from the hwframe context of dst,
+ * then this function undoes the mapping - dst is replaced by a reference to
+ * the frame that src was originally mapped from.
+ *
+ * If both src and dst have an associated hwframe context, then this function
+ * attempts to map the src frame from its hardware context to that of dst and
+ * then fill dst with appropriate data to be usable there. This will only be
+ * possible if the hwframe contexts and associated devices are compatible -
+ * given compatible devices, av_hwframe_ctx_create_derived() can be used to
+ * create a hwframe context for dst in which mapping should be possible.
+ *
+ * If src has a hwframe context but dst does not, then the src frame is
+ * mapped to normal memory and should thereafter be usable as a normal frame.
+ * If the format is set on dst, then the mapping will attempt to create dst
+ * with that format and fail if it is not possible. If format is unset (is
+ * AV_PIX_FMT_NONE) then dst will be mapped with whatever the most appropriate
+ * format to use is (probably the sw_format of the src hwframe context).
+ *
+ * A return value of AVERROR(ENOSYS) indicates that the mapping is not
+ * possible with the given arguments and hwframe setup, while other return
+ * values indicate that it failed somehow.
+ *
+ * On failure, the destination frame will be left blank, except for the
+ * hw_frames_ctx/format fields thay may have been set by the caller - those will
+ * be preserved as they were.
+ *
+ * @param dst Destination frame, to contain the mapping.
+ * @param src Source frame, to be mapped.
+ * @param flags Some combination of AV_HWFRAME_MAP_* flags.
+ * @return Zero on success, negative AVERROR code on failure.
+ */
+int av_hwframe_map(AVFrame* dst, const AVFrame* src, int flags);
+
+/**
+ * Create and initialise an AVHWFramesContext as a mapping of another existing
+ * AVHWFramesContext on a different device.
+ *
+ * av_hwframe_ctx_init() should not be called after this.
+ *
+ * @param derived_frame_ctx On success, a reference to the newly created
+ * AVHWFramesContext.
+ * @param format The AVPixelFormat for the derived context.
+ * @param derived_device_ctx A reference to the device to create the new
+ * AVHWFramesContext on.
+ * @param source_frame_ctx A reference to an existing AVHWFramesContext
+ * which will be mapped to the derived context.
+ * @param flags Some combination of AV_HWFRAME_MAP_* flags, defining the
+ * mapping parameters to apply to frames which are allocated
+ * in the derived device.
+ * @return Zero on success, negative AVERROR code on failure.
+ */
+int av_hwframe_ctx_create_derived(AVBufferRef** derived_frame_ctx,
+ enum AVPixelFormat format,
+ AVBufferRef* derived_device_ctx,
+ AVBufferRef* source_frame_ctx, int flags);
+
+#endif /* AVUTIL_HWCONTEXT_H */
diff --git a/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/hwcontext_drm.h b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/hwcontext_drm.h
new file mode 100644
index 0000000000..8d8a651b48
--- /dev/null
+++ b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/hwcontext_drm.h
@@ -0,0 +1,169 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVUTIL_HWCONTEXT_DRM_H
+#define AVUTIL_HWCONTEXT_DRM_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+/**
+ * @file
+ * API-specific header for AV_HWDEVICE_TYPE_DRM.
+ *
+ * Internal frame allocation is not currently supported - all frames
+ * must be allocated by the user. Thus AVHWFramesContext is always
+ * NULL, though this may change if support for frame allocation is
+ * added in future.
+ */
+
+enum {
+ /**
+ * The maximum number of layers/planes in a DRM frame.
+ */
+ AV_DRM_MAX_PLANES = 4
+};
+
+/**
+ * DRM object descriptor.
+ *
+ * Describes a single DRM object, addressing it as a PRIME file
+ * descriptor.
+ */
+typedef struct AVDRMObjectDescriptor {
+ /**
+ * DRM PRIME fd for the object.
+ */
+ int fd;
+ /**
+ * Total size of the object.
+ *
+ * (This includes any parts not which do not contain image data.)
+ */
+ size_t size;
+ /**
+ * Format modifier applied to the object (DRM_FORMAT_MOD_*).
+ *
+ * If the format modifier is unknown then this should be set to
+ * DRM_FORMAT_MOD_INVALID.
+ */
+ uint64_t format_modifier;
+} AVDRMObjectDescriptor;
+
+/**
+ * DRM plane descriptor.
+ *
+ * Describes a single plane of a layer, which is contained within
+ * a single object.
+ */
+typedef struct AVDRMPlaneDescriptor {
+ /**
+ * Index of the object containing this plane in the objects
+ * array of the enclosing frame descriptor.
+ */
+ int object_index;
+ /**
+ * Offset within that object of this plane.
+ */
+ ptrdiff_t offset;
+ /**
+ * Pitch (linesize) of this plane.
+ */
+ ptrdiff_t pitch;
+} AVDRMPlaneDescriptor;
+
+/**
+ * DRM layer descriptor.
+ *
+ * Describes a single layer within a frame. This has the structure
+ * defined by its format, and will contain one or more planes.
+ */
+typedef struct AVDRMLayerDescriptor {
+ /**
+ * Format of the layer (DRM_FORMAT_*).
+ */
+ uint32_t format;
+ /**
+ * Number of planes in the layer.
+ *
+ * This must match the number of planes required by format.
+ */
+ int nb_planes;
+ /**
+ * Array of planes in this layer.
+ */
+ AVDRMPlaneDescriptor planes[AV_DRM_MAX_PLANES];
+} AVDRMLayerDescriptor;
+
+/**
+ * DRM frame descriptor.
+ *
+ * This is used as the data pointer for AV_PIX_FMT_DRM_PRIME frames.
+ * It is also used by user-allocated frame pools - allocating in
+ * AVHWFramesContext.pool must return AVBufferRefs which contain
+ * an object of this type.
+ *
+ * The fields of this structure should be set such it can be
+ * imported directly by EGL using the EGL_EXT_image_dma_buf_import
+ * and EGL_EXT_image_dma_buf_import_modifiers extensions.
+ * (Note that the exact layout of a particular format may vary between
+ * platforms - we only specify that the same platform should be able
+ * to import it.)
+ *
+ * The total number of planes must not exceed AV_DRM_MAX_PLANES, and
+ * the order of the planes by increasing layer index followed by
+ * increasing plane index must be the same as the order which would
+ * be used for the data pointers in the equivalent software format.
+ */
+typedef struct AVDRMFrameDescriptor {
+ /**
+ * Number of DRM objects making up this frame.
+ */
+ int nb_objects;
+ /**
+ * Array of objects making up the frame.
+ */
+ AVDRMObjectDescriptor objects[AV_DRM_MAX_PLANES];
+ /**
+ * Number of layers in the frame.
+ */
+ int nb_layers;
+ /**
+ * Array of layers in the frame.
+ */
+ AVDRMLayerDescriptor layers[AV_DRM_MAX_PLANES];
+} AVDRMFrameDescriptor;
+
+/**
+ * DRM device.
+ *
+ * Allocated as AVHWDeviceContext.hwctx.
+ */
+typedef struct AVDRMDeviceContext {
+ /**
+ * File descriptor of DRM device.
+ *
+ * This is used as the device to create frames on, and may also be
+ * used in some derivation and mapping operations.
+ *
+ * If no device is required, set to -1.
+ */
+ int fd;
+} AVDRMDeviceContext;
+
+#endif /* AVUTIL_HWCONTEXT_DRM_H */
diff --git a/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/hwcontext_vaapi.h b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/hwcontext_vaapi.h
new file mode 100644
index 0000000000..058b5f110d
--- /dev/null
+++ b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/hwcontext_vaapi.h
@@ -0,0 +1,117 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVUTIL_HWCONTEXT_VAAPI_H
+#define AVUTIL_HWCONTEXT_VAAPI_H
+
+#include <va/va.h>
+
+/**
+ * @file
+ * API-specific header for AV_HWDEVICE_TYPE_VAAPI.
+ *
+ * Dynamic frame pools are supported, but note that any pool used as a render
+ * target is required to be of fixed size in order to be be usable as an
+ * argument to vaCreateContext().
+ *
+ * For user-allocated pools, AVHWFramesContext.pool must return AVBufferRefs
+ * with the data pointer set to a VASurfaceID.
+ */
+
+enum {
+ /**
+ * The quirks field has been set by the user and should not be detected
+ * automatically by av_hwdevice_ctx_init().
+ */
+ AV_VAAPI_DRIVER_QUIRK_USER_SET = (1 << 0),
+ /**
+ * The driver does not destroy parameter buffers when they are used by
+ * vaRenderPicture(). Additional code will be required to destroy them
+ * separately afterwards.
+ */
+ AV_VAAPI_DRIVER_QUIRK_RENDER_PARAM_BUFFERS = (1 << 1),
+
+ /**
+ * The driver does not support the VASurfaceAttribMemoryType attribute,
+ * so the surface allocation code will not try to use it.
+ */
+ AV_VAAPI_DRIVER_QUIRK_ATTRIB_MEMTYPE = (1 << 2),
+
+ /**
+ * The driver does not support surface attributes at all.
+ * The surface allocation code will never pass them to surface allocation,
+ * and the results of the vaQuerySurfaceAttributes() call will be faked.
+ */
+ AV_VAAPI_DRIVER_QUIRK_SURFACE_ATTRIBUTES = (1 << 3),
+};
+
+/**
+ * VAAPI connection details.
+ *
+ * Allocated as AVHWDeviceContext.hwctx
+ */
+typedef struct AVVAAPIDeviceContext {
+ /**
+ * The VADisplay handle, to be filled by the user.
+ */
+ VADisplay display;
+ /**
+ * Driver quirks to apply - this is filled by av_hwdevice_ctx_init(),
+ * with reference to a table of known drivers, unless the
+ * AV_VAAPI_DRIVER_QUIRK_USER_SET bit is already present. The user
+ * may need to refer to this field when performing any later
+ * operations using VAAPI with the same VADisplay.
+ */
+ unsigned int driver_quirks;
+} AVVAAPIDeviceContext;
+
+/**
+ * VAAPI-specific data associated with a frame pool.
+ *
+ * Allocated as AVHWFramesContext.hwctx.
+ */
+typedef struct AVVAAPIFramesContext {
+ /**
+ * Set by the user to apply surface attributes to all surfaces in
+ * the frame pool. If null, default settings are used.
+ */
+ VASurfaceAttrib* attributes;
+ int nb_attributes;
+ /**
+ * The surfaces IDs of all surfaces in the pool after creation.
+ * Only valid if AVHWFramesContext.initial_pool_size was positive.
+ * These are intended to be used as the render_targets arguments to
+ * vaCreateContext().
+ */
+ VASurfaceID* surface_ids;
+ int nb_surfaces;
+} AVVAAPIFramesContext;
+
+/**
+ * VAAPI hardware pipeline configuration details.
+ *
+ * Allocated with av_hwdevice_hwconfig_alloc().
+ */
+typedef struct AVVAAPIHWConfig {
+ /**
+ * ID of a VAAPI pipeline configuration.
+ */
+ VAConfigID config_id;
+} AVVAAPIHWConfig;
+
+#endif /* AVUTIL_HWCONTEXT_VAAPI_H */
diff --git a/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/intfloat.h b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/intfloat.h
new file mode 100644
index 0000000000..f373c97796
--- /dev/null
+++ b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/intfloat.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2011 Mans Rullgard
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVUTIL_INTFLOAT_H
+#define AVUTIL_INTFLOAT_H
+
+#include <stdint.h>
+#include "attributes.h"
+
+union av_intfloat32 {
+ uint32_t i;
+ float f;
+};
+
+union av_intfloat64 {
+ uint64_t i;
+ double f;
+};
+
+/**
+ * Reinterpret a 32-bit integer as a float.
+ */
+static av_always_inline float av_int2float(uint32_t i) {
+ union av_intfloat32 v;
+ v.i = i;
+ return v.f;
+}
+
+/**
+ * Reinterpret a float as a 32-bit integer.
+ */
+static av_always_inline uint32_t av_float2int(float f) {
+ union av_intfloat32 v;
+ v.f = f;
+ return v.i;
+}
+
+/**
+ * Reinterpret a 64-bit integer as a double.
+ */
+static av_always_inline double av_int2double(uint64_t i) {
+ union av_intfloat64 v;
+ v.i = i;
+ return v.f;
+}
+
+/**
+ * Reinterpret a double as a 64-bit integer.
+ */
+static av_always_inline uint64_t av_double2int(double f) {
+ union av_intfloat64 v;
+ v.f = f;
+ return v.i;
+}
+
+#endif /* AVUTIL_INTFLOAT_H */
diff --git a/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/log.h b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/log.h
new file mode 100644
index 0000000000..e1f2af7b18
--- /dev/null
+++ b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/log.h
@@ -0,0 +1,388 @@
+/*
+ * copyright (c) 2006 Michael Niedermayer <michaelni@gmx.at>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVUTIL_LOG_H
+#define AVUTIL_LOG_H
+
+#include <stdarg.h>
+#include "attributes.h"
+#include "version.h"
+
+typedef enum {
+ AV_CLASS_CATEGORY_NA = 0,
+ AV_CLASS_CATEGORY_INPUT,
+ AV_CLASS_CATEGORY_OUTPUT,
+ AV_CLASS_CATEGORY_MUXER,
+ AV_CLASS_CATEGORY_DEMUXER,
+ AV_CLASS_CATEGORY_ENCODER,
+ AV_CLASS_CATEGORY_DECODER,
+ AV_CLASS_CATEGORY_FILTER,
+ AV_CLASS_CATEGORY_BITSTREAM_FILTER,
+ AV_CLASS_CATEGORY_SWSCALER,
+ AV_CLASS_CATEGORY_SWRESAMPLER,
+ AV_CLASS_CATEGORY_DEVICE_VIDEO_OUTPUT = 40,
+ AV_CLASS_CATEGORY_DEVICE_VIDEO_INPUT,
+ AV_CLASS_CATEGORY_DEVICE_AUDIO_OUTPUT,
+ AV_CLASS_CATEGORY_DEVICE_AUDIO_INPUT,
+ AV_CLASS_CATEGORY_DEVICE_OUTPUT,
+ AV_CLASS_CATEGORY_DEVICE_INPUT,
+ AV_CLASS_CATEGORY_NB ///< not part of ABI/API
+} AVClassCategory;
+
+#define AV_IS_INPUT_DEVICE(category) \
+ (((category) == AV_CLASS_CATEGORY_DEVICE_VIDEO_INPUT) || \
+ ((category) == AV_CLASS_CATEGORY_DEVICE_AUDIO_INPUT) || \
+ ((category) == AV_CLASS_CATEGORY_DEVICE_INPUT))
+
+#define AV_IS_OUTPUT_DEVICE(category) \
+ (((category) == AV_CLASS_CATEGORY_DEVICE_VIDEO_OUTPUT) || \
+ ((category) == AV_CLASS_CATEGORY_DEVICE_AUDIO_OUTPUT) || \
+ ((category) == AV_CLASS_CATEGORY_DEVICE_OUTPUT))
+
+struct AVOptionRanges;
+
+/**
+ * Describe the class of an AVClass context structure. That is an
+ * arbitrary struct of which the first field is a pointer to an
+ * AVClass struct (e.g. AVCodecContext, AVFormatContext etc.).
+ */
+typedef struct AVClass {
+ /**
+ * The name of the class; usually it is the same name as the
+ * context structure type to which the AVClass is associated.
+ */
+ const char* class_name;
+
+ /**
+ * A pointer to a function which returns the name of a context
+ * instance ctx associated with the class.
+ */
+ const char* (*item_name)(void* ctx);
+
+ /**
+ * a pointer to the first option specified in the class if any or NULL
+ *
+ * @see av_set_default_options()
+ */
+ const struct AVOption* option;
+
+ /**
+ * LIBAVUTIL_VERSION with which this structure was created.
+ * This is used to allow fields to be added without requiring major
+ * version bumps everywhere.
+ */
+
+ int version;
+
+ /**
+ * Offset in the structure where log_level_offset is stored.
+ * 0 means there is no such variable
+ */
+ int log_level_offset_offset;
+
+ /**
+ * Offset in the structure where a pointer to the parent context for
+ * logging is stored. For example a decoder could pass its AVCodecContext
+ * to eval as such a parent context, which an av_log() implementation
+ * could then leverage to display the parent context.
+ * The offset can be NULL.
+ */
+ int parent_log_context_offset;
+
+ /**
+ * Category used for visualization (like color)
+ * This is only set if the category is equal for all objects using this class.
+ * available since version (51 << 16 | 56 << 8 | 100)
+ */
+ AVClassCategory category;
+
+ /**
+ * Callback to return the category.
+ * available since version (51 << 16 | 59 << 8 | 100)
+ */
+ AVClassCategory (*get_category)(void* ctx);
+
+ /**
+ * Callback to return the supported/allowed ranges.
+ * available since version (52.12)
+ */
+ int (*query_ranges)(struct AVOptionRanges**, void* obj, const char* key,
+ int flags);
+
+ /**
+ * Return next AVOptions-enabled child or NULL
+ */
+ void* (*child_next)(void* obj, void* prev);
+
+ /**
+ * Iterate over the AVClasses corresponding to potential AVOptions-enabled
+ * children.
+ *
+ * @param iter pointer to opaque iteration state. The caller must initialize
+ * *iter to NULL before the first call.
+ * @return AVClass for the next AVOptions-enabled child or NULL if there are
+ * no more such children.
+ *
+ * @note The difference between child_next and this is that child_next
+ * iterates over _already existing_ objects, while child_class_iterate
+ * iterates over _all possible_ children.
+ */
+ const struct AVClass* (*child_class_iterate)(void** iter);
+} AVClass;
+
+/**
+ * @addtogroup lavu_log
+ *
+ * @{
+ *
+ * @defgroup lavu_log_constants Logging Constants
+ *
+ * @{
+ */
+
+/**
+ * Print no output.
+ */
+#define AV_LOG_QUIET -8
+
+/**
+ * Something went really wrong and we will crash now.
+ */
+#define AV_LOG_PANIC 0
+
+/**
+ * Something went wrong and recovery is not possible.
+ * For example, no header was found for a format which depends
+ * on headers or an illegal combination of parameters is used.
+ */
+#define AV_LOG_FATAL 8
+
+/**
+ * Something went wrong and cannot losslessly be recovered.
+ * However, not all future data is affected.
+ */
+#define AV_LOG_ERROR 16
+
+/**
+ * Something somehow does not look correct. This may or may not
+ * lead to problems. An example would be the use of '-vstrict -2'.
+ */
+#define AV_LOG_WARNING 24
+
+/**
+ * Standard information.
+ */
+#define AV_LOG_INFO 32
+
+/**
+ * Detailed information.
+ */
+#define AV_LOG_VERBOSE 40
+
+/**
+ * Stuff which is only useful for libav* developers.
+ */
+#define AV_LOG_DEBUG 48
+
+/**
+ * Extremely verbose debugging, useful for libav* development.
+ */
+#define AV_LOG_TRACE 56
+
+#define AV_LOG_MAX_OFFSET (AV_LOG_TRACE - AV_LOG_QUIET)
+
+/**
+ * @}
+ */
+
+/**
+ * Sets additional colors for extended debugging sessions.
+ * @code
+ av_log(ctx, AV_LOG_DEBUG|AV_LOG_C(134), "Message in purple\n");
+ @endcode
+ * Requires 256color terminal support. Uses outside debugging is not
+ * recommended.
+ */
+#define AV_LOG_C(x) ((x) << 8)
+
+/**
+ * Send the specified message to the log if the level is less than or equal
+ * to the current av_log_level. By default, all logging messages are sent to
+ * stderr. This behavior can be altered by setting a different logging callback
+ * function.
+ * @see av_log_set_callback
+ *
+ * @param avcl A pointer to an arbitrary struct of which the first field is a
+ * pointer to an AVClass struct or NULL if general log.
+ * @param level The importance level of the message expressed using a @ref
+ * lavu_log_constants "Logging Constant".
+ * @param fmt The format string (printf-compatible) that specifies how
+ * subsequent arguments are converted to output.
+ */
+void av_log(void* avcl, int level, const char* fmt, ...) av_printf_format(3, 4);
+
+/**
+ * Send the specified message to the log once with the initial_level and then
+ * with the subsequent_level. By default, all logging messages are sent to
+ * stderr. This behavior can be altered by setting a different logging callback
+ * function.
+ * @see av_log
+ *
+ * @param avcl A pointer to an arbitrary struct of which the first field is a
+ * pointer to an AVClass struct or NULL if general log.
+ * @param initial_level importance level of the message expressed using a @ref
+ * lavu_log_constants "Logging Constant" for the first occurance.
+ * @param subsequent_level importance level of the message expressed using a
+ * @ref lavu_log_constants "Logging Constant" after the first occurance.
+ * @param fmt The format string (printf-compatible) that specifies how
+ * subsequent arguments are converted to output.
+ * @param state a variable to keep trak of if a message has already been printed
+ * this must be initialized to 0 before the first use. The same state
+ * must not be accessed by 2 Threads simultaneously.
+ */
+void av_log_once(void* avcl, int initial_level, int subsequent_level,
+ int* state, const char* fmt, ...) av_printf_format(5, 6);
+
+/**
+ * Send the specified message to the log if the level is less than or equal
+ * to the current av_log_level. By default, all logging messages are sent to
+ * stderr. This behavior can be altered by setting a different logging callback
+ * function.
+ * @see av_log_set_callback
+ *
+ * @param avcl A pointer to an arbitrary struct of which the first field is a
+ * pointer to an AVClass struct.
+ * @param level The importance level of the message expressed using a @ref
+ * lavu_log_constants "Logging Constant".
+ * @param fmt The format string (printf-compatible) that specifies how
+ * subsequent arguments are converted to output.
+ * @param vl The arguments referenced by the format string.
+ */
+void av_vlog(void* avcl, int level, const char* fmt, va_list vl);
+
+/**
+ * Get the current log level
+ *
+ * @see lavu_log_constants
+ *
+ * @return Current log level
+ */
+int av_log_get_level(void);
+
+/**
+ * Set the log level
+ *
+ * @see lavu_log_constants
+ *
+ * @param level Logging level
+ */
+void av_log_set_level(int level);
+
+/**
+ * Set the logging callback
+ *
+ * @note The callback must be thread safe, even if the application does not use
+ * threads itself as some codecs are multithreaded.
+ *
+ * @see av_log_default_callback
+ *
+ * @param callback A logging function with a compatible signature.
+ */
+void av_log_set_callback(void (*callback)(void*, int, const char*, va_list));
+
+/**
+ * Default logging callback
+ *
+ * It prints the message to stderr, optionally colorizing it.
+ *
+ * @param avcl A pointer to an arbitrary struct of which the first field is a
+ * pointer to an AVClass struct.
+ * @param level The importance level of the message expressed using a @ref
+ * lavu_log_constants "Logging Constant".
+ * @param fmt The format string (printf-compatible) that specifies how
+ * subsequent arguments are converted to output.
+ * @param vl The arguments referenced by the format string.
+ */
+void av_log_default_callback(void* avcl, int level, const char* fmt,
+ va_list vl);
+
+/**
+ * Return the context name
+ *
+ * @param ctx The AVClass context
+ *
+ * @return The AVClass class_name
+ */
+const char* av_default_item_name(void* ctx);
+AVClassCategory av_default_get_category(void* ptr);
+
+/**
+ * Format a line of log the same way as the default callback.
+ * @param line buffer to receive the formatted line
+ * @param line_size size of the buffer
+ * @param print_prefix used to store whether the prefix must be printed;
+ * must point to a persistent integer initially set to 1
+ */
+void av_log_format_line(void* ptr, int level, const char* fmt, va_list vl,
+ char* line, int line_size, int* print_prefix);
+
+/**
+ * Format a line of log the same way as the default callback.
+ * @param line buffer to receive the formatted line;
+ * may be NULL if line_size is 0
+ * @param line_size size of the buffer; at most line_size-1 characters will
+ * be written to the buffer, plus one null terminator
+ * @param print_prefix used to store whether the prefix must be printed;
+ * must point to a persistent integer initially set to 1
+ * @return Returns a negative value if an error occurred, otherwise returns
+ * the number of characters that would have been written for a
+ * sufficiently large buffer, not including the terminating null
+ * character. If the return value is not less than line_size, it means
+ * that the log message was truncated to fit the buffer.
+ */
+int av_log_format_line2(void* ptr, int level, const char* fmt, va_list vl,
+ char* line, int line_size, int* print_prefix);
+
+/**
+ * Skip repeated messages, this requires the user app to use av_log() instead of
+ * (f)printf as the 2 would otherwise interfere and lead to
+ * "Last message repeated x times" messages below (f)printf messages with some
+ * bad luck.
+ * Also to receive the last, "last repeated" line if any, the user app must
+ * call av_log(NULL, AV_LOG_QUIET, "%s", ""); at the end
+ */
+#define AV_LOG_SKIP_REPEATED 1
+
+/**
+ * Include the log severity in messages originating from codecs.
+ *
+ * Results in messages such as:
+ * [rawvideo @ 0xDEADBEEF] [error] encode did not produce valid pts
+ */
+#define AV_LOG_PRINT_LEVEL 2
+
+void av_log_set_flags(int arg);
+int av_log_get_flags(void);
+
+/**
+ * @}
+ */
+
+#endif /* AVUTIL_LOG_H */
diff --git a/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/macros.h b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/macros.h
new file mode 100644
index 0000000000..1578d1a345
--- /dev/null
+++ b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/macros.h
@@ -0,0 +1,87 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * @ingroup lavu
+ * Utility Preprocessor macros
+ */
+
+#ifndef AVUTIL_MACROS_H
+#define AVUTIL_MACROS_H
+
+#include "libavutil/avconfig.h"
+
+#if AV_HAVE_BIGENDIAN
+# define AV_NE(be, le) (be)
+#else
+# define AV_NE(be, le) (le)
+#endif
+
+/**
+ * Comparator.
+ * For two numerical expressions x and y, gives 1 if x > y, -1 if x < y, and 0
+ * if x == y. This is useful for instance in a qsort comparator callback.
+ * Furthermore, compilers are able to optimize this to branchless code, and
+ * there is no risk of overflow with signed types.
+ * As with many macros, this evaluates its argument multiple times, it thus
+ * must not have a side-effect.
+ */
+#define FFDIFFSIGN(x, y) (((x) > (y)) - ((x) < (y)))
+
+#define FFMAX(a, b) ((a) > (b) ? (a) : (b))
+#define FFMAX3(a, b, c) FFMAX(FFMAX(a, b), c)
+#define FFMIN(a, b) ((a) > (b) ? (b) : (a))
+#define FFMIN3(a, b, c) FFMIN(FFMIN(a, b), c)
+
+#define FFSWAP(type, a, b) \
+ do { \
+ type SWAP_tmp = b; \
+ b = a; \
+ a = SWAP_tmp; \
+ } while (0)
+#define FF_ARRAY_ELEMS(a) (sizeof(a) / sizeof((a)[0]))
+
+#define MKTAG(a, b, c, d) \
+ ((a) | ((b) << 8) | ((c) << 16) | ((unsigned)(d) << 24))
+#define MKBETAG(a, b, c, d) \
+ ((d) | ((c) << 8) | ((b) << 16) | ((unsigned)(a) << 24))
+
+/**
+ * @addtogroup preproc_misc Preprocessor String Macros
+ *
+ * String manipulation macros
+ *
+ * @{
+ */
+
+#define AV_STRINGIFY(s) AV_TOSTRING(s)
+#define AV_TOSTRING(s) #s
+
+#define AV_GLUE(a, b) a##b
+#define AV_JOIN(a, b) AV_GLUE(a, b)
+
+/**
+ * @}
+ */
+
+#define AV_PRAGMA(s) _Pragma(#s)
+
+#define FFALIGN(x, a) (((x) + (a)-1) & ~((a)-1))
+
+#endif /* AVUTIL_MACROS_H */
diff --git a/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/mathematics.h b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/mathematics.h
new file mode 100644
index 0000000000..cab0d080d2
--- /dev/null
+++ b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/mathematics.h
@@ -0,0 +1,305 @@
+/*
+ * copyright (c) 2005-2012 Michael Niedermayer <michaelni@gmx.at>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * @addtogroup lavu_math
+ * Mathematical utilities for working with timestamp and time base.
+ */
+
+#ifndef AVUTIL_MATHEMATICS_H
+#define AVUTIL_MATHEMATICS_H
+
+#include <stdint.h>
+#include <math.h>
+#include "attributes.h"
+#include "rational.h"
+#include "intfloat.h"
+
+#ifndef M_E
+# define M_E 2.7182818284590452354 /* e */
+#endif
+#ifndef M_Ef
+# define M_Ef 2.7182818284590452354f /* e */
+#endif
+#ifndef M_LN2
+# define M_LN2 0.69314718055994530942 /* log_e 2 */
+#endif
+#ifndef M_LN2f
+# define M_LN2f 0.69314718055994530942f /* log_e 2 */
+#endif
+#ifndef M_LN10
+# define M_LN10 2.30258509299404568402 /* log_e 10 */
+#endif
+#ifndef M_LN10f
+# define M_LN10f 2.30258509299404568402f /* log_e 10 */
+#endif
+#ifndef M_LOG2_10
+# define M_LOG2_10 3.32192809488736234787 /* log_2 10 */
+#endif
+#ifndef M_LOG2_10f
+# define M_LOG2_10f 3.32192809488736234787f /* log_2 10 */
+#endif
+#ifndef M_PHI
+# define M_PHI 1.61803398874989484820 /* phi / golden ratio */
+#endif
+#ifndef M_PHIf
+# define M_PHIf 1.61803398874989484820f /* phi / golden ratio */
+#endif
+#ifndef M_PI
+# define M_PI 3.14159265358979323846 /* pi */
+#endif
+#ifndef M_PIf
+# define M_PIf 3.14159265358979323846f /* pi */
+#endif
+#ifndef M_PI_2
+# define M_PI_2 1.57079632679489661923 /* pi/2 */
+#endif
+#ifndef M_PI_2f
+# define M_PI_2f 1.57079632679489661923f /* pi/2 */
+#endif
+#ifndef M_PI_4
+# define M_PI_4 0.78539816339744830962 /* pi/4 */
+#endif
+#ifndef M_PI_4f
+# define M_PI_4f 0.78539816339744830962f /* pi/4 */
+#endif
+#ifndef M_1_PI
+# define M_1_PI 0.31830988618379067154 /* 1/pi */
+#endif
+#ifndef M_1_PIf
+# define M_1_PIf 0.31830988618379067154f /* 1/pi */
+#endif
+#ifndef M_2_PI
+# define M_2_PI 0.63661977236758134308 /* 2/pi */
+#endif
+#ifndef M_2_PIf
+# define M_2_PIf 0.63661977236758134308f /* 2/pi */
+#endif
+#ifndef M_2_SQRTPI
+# define M_2_SQRTPI 1.12837916709551257390 /* 2/sqrt(pi) */
+#endif
+#ifndef M_2_SQRTPIf
+# define M_2_SQRTPIf 1.12837916709551257390f /* 2/sqrt(pi) */
+#endif
+#ifndef M_SQRT1_2
+# define M_SQRT1_2 0.70710678118654752440 /* 1/sqrt(2) */
+#endif
+#ifndef M_SQRT1_2f
+# define M_SQRT1_2f 0.70710678118654752440f /* 1/sqrt(2) */
+#endif
+#ifndef M_SQRT2
+# define M_SQRT2 1.41421356237309504880 /* sqrt(2) */
+#endif
+#ifndef M_SQRT2f
+# define M_SQRT2f 1.41421356237309504880f /* sqrt(2) */
+#endif
+#ifndef NAN
+# define NAN av_int2float(0x7fc00000)
+#endif
+#ifndef INFINITY
+# define INFINITY av_int2float(0x7f800000)
+#endif
+
+/**
+ * @addtogroup lavu_math
+ *
+ * @{
+ */
+
+/**
+ * Rounding methods.
+ */
+enum AVRounding {
+ AV_ROUND_ZERO = 0, ///< Round toward zero.
+ AV_ROUND_INF = 1, ///< Round away from zero.
+ AV_ROUND_DOWN = 2, ///< Round toward -infinity.
+ AV_ROUND_UP = 3, ///< Round toward +infinity.
+ AV_ROUND_NEAR_INF =
+ 5, ///< Round to nearest and halfway cases away from zero.
+ /**
+ * Flag telling rescaling functions to pass `INT64_MIN`/`MAX` through
+ * unchanged, avoiding special cases for #AV_NOPTS_VALUE.
+ *
+ * Unlike other values of the enumeration AVRounding, this value is a
+ * bitmask that must be used in conjunction with another value of the
+ * enumeration through a bitwise OR, in order to set behavior for normal
+ * cases.
+ *
+ * @code{.c}
+ * av_rescale_rnd(3, 1, 2, AV_ROUND_UP | AV_ROUND_PASS_MINMAX);
+ * // Rescaling 3:
+ * // Calculating 3 * 1 / 2
+ * // 3 / 2 is rounded up to 2
+ * // => 2
+ *
+ * av_rescale_rnd(AV_NOPTS_VALUE, 1, 2, AV_ROUND_UP | AV_ROUND_PASS_MINMAX);
+ * // Rescaling AV_NOPTS_VALUE:
+ * // AV_NOPTS_VALUE == INT64_MIN
+ * // AV_NOPTS_VALUE is passed through
+ * // => AV_NOPTS_VALUE
+ * @endcode
+ */
+ AV_ROUND_PASS_MINMAX = 8192,
+};
+
+/**
+ * Compute the greatest common divisor of two integer operands.
+ *
+ * @param a Operand
+ * @param b Operand
+ * @return GCD of a and b up to sign; if a >= 0 and b >= 0, return value is >=
+ * 0; if a == 0 and b == 0, returns 0.
+ */
+int64_t av_const av_gcd(int64_t a, int64_t b);
+
+/**
+ * Rescale a 64-bit integer with rounding to nearest.
+ *
+ * The operation is mathematically equivalent to `a * b / c`, but writing that
+ * directly can overflow.
+ *
+ * This function is equivalent to av_rescale_rnd() with #AV_ROUND_NEAR_INF.
+ *
+ * @see av_rescale_rnd(), av_rescale_q(), av_rescale_q_rnd()
+ */
+int64_t av_rescale(int64_t a, int64_t b, int64_t c) av_const;
+
+/**
+ * Rescale a 64-bit integer with specified rounding.
+ *
+ * The operation is mathematically equivalent to `a * b / c`, but writing that
+ * directly can overflow, and does not support different rounding methods.
+ * If the result is not representable then INT64_MIN is returned.
+ *
+ * @see av_rescale(), av_rescale_q(), av_rescale_q_rnd()
+ */
+int64_t av_rescale_rnd(int64_t a, int64_t b, int64_t c,
+ enum AVRounding rnd) av_const;
+
+/**
+ * Rescale a 64-bit integer by 2 rational numbers.
+ *
+ * The operation is mathematically equivalent to `a * bq / cq`.
+ *
+ * This function is equivalent to av_rescale_q_rnd() with #AV_ROUND_NEAR_INF.
+ *
+ * @see av_rescale(), av_rescale_rnd(), av_rescale_q_rnd()
+ */
+int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq) av_const;
+
+/**
+ * Rescale a 64-bit integer by 2 rational numbers with specified rounding.
+ *
+ * The operation is mathematically equivalent to `a * bq / cq`.
+ *
+ * @see av_rescale(), av_rescale_rnd(), av_rescale_q()
+ */
+int64_t av_rescale_q_rnd(int64_t a, AVRational bq, AVRational cq,
+ enum AVRounding rnd) av_const;
+
+/**
+ * Compare two timestamps each in its own time base.
+ *
+ * @return One of the following values:
+ * - -1 if `ts_a` is before `ts_b`
+ * - 1 if `ts_a` is after `ts_b`
+ * - 0 if they represent the same position
+ *
+ * @warning
+ * The result of the function is undefined if one of the timestamps is outside
+ * the `int64_t` range when represented in the other's timebase.
+ */
+int av_compare_ts(int64_t ts_a, AVRational tb_a, int64_t ts_b, AVRational tb_b);
+
+/**
+ * Compare the remainders of two integer operands divided by a common divisor.
+ *
+ * In other words, compare the least significant `log2(mod)` bits of integers
+ * `a` and `b`.
+ *
+ * @code{.c}
+ * av_compare_mod(0x11, 0x02, 0x10) < 0 // since 0x11 % 0x10 (0x1) < 0x02 %
+ * 0x10 (0x2) av_compare_mod(0x11, 0x02, 0x20) > 0 // since 0x11 % 0x20 (0x11)
+ * > 0x02 % 0x20 (0x02)
+ * @endcode
+ *
+ * @param a Operand
+ * @param b Operand
+ * @param mod Divisor; must be a power of 2
+ * @return
+ * - a negative value if `a % mod < b % mod`
+ * - a positive value if `a % mod > b % mod`
+ * - zero if `a % mod == b % mod`
+ */
+int64_t av_compare_mod(uint64_t a, uint64_t b, uint64_t mod);
+
+/**
+ * Rescale a timestamp while preserving known durations.
+ *
+ * This function is designed to be called per audio packet to scale the input
+ * timestamp to a different time base. Compared to a simple av_rescale_q()
+ * call, this function is robust against possible inconsistent frame durations.
+ *
+ * The `last` parameter is a state variable that must be preserved for all
+ * subsequent calls for the same stream. For the first call, `*last` should be
+ * initialized to #AV_NOPTS_VALUE.
+ *
+ * @param[in] in_tb Input time base
+ * @param[in] in_ts Input timestamp
+ * @param[in] fs_tb Duration time base; typically this is finer-grained
+ * (greater) than `in_tb` and `out_tb`
+ * @param[in] duration Duration till the next call to this function (i.e.
+ * duration of the current packet/frame)
+ * @param[in,out] last Pointer to a timestamp expressed in terms of
+ * `fs_tb`, acting as a state variable
+ * @param[in] out_tb Output timebase
+ * @return Timestamp expressed in terms of `out_tb`
+ *
+ * @note In the context of this function, "duration" is in term of samples, not
+ * seconds.
+ */
+int64_t av_rescale_delta(AVRational in_tb, int64_t in_ts, AVRational fs_tb,
+ int duration, int64_t* last, AVRational out_tb);
+
+/**
+ * Add a value to a timestamp.
+ *
+ * This function guarantees that when the same value is repeatly added that
+ * no accumulation of rounding errors occurs.
+ *
+ * @param[in] ts Input timestamp
+ * @param[in] ts_tb Input timestamp time base
+ * @param[in] inc Value to be added
+ * @param[in] inc_tb Time base of `inc`
+ */
+int64_t av_add_stable(AVRational ts_tb, int64_t ts, AVRational inc_tb,
+ int64_t inc);
+
+/**
+ * 0th order modified bessel function of the first kind.
+ */
+double av_bessel_i0(double x);
+
+/**
+ * @}
+ */
+
+#endif /* AVUTIL_MATHEMATICS_H */
diff --git a/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/mem.h b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/mem.h
new file mode 100644
index 0000000000..738c6443bf
--- /dev/null
+++ b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/mem.h
@@ -0,0 +1,611 @@
+/*
+ * copyright (c) 2006 Michael Niedermayer <michaelni@gmx.at>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * @ingroup lavu_mem
+ * Memory handling functions
+ */
+
+#ifndef AVUTIL_MEM_H
+#define AVUTIL_MEM_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "attributes.h"
+
+/**
+ * @addtogroup lavu_mem
+ * Utilities for manipulating memory.
+ *
+ * FFmpeg has several applications of memory that are not required of a typical
+ * program. For example, the computing-heavy components like video decoding and
+ * encoding can be sped up significantly through the use of aligned memory.
+ *
+ * However, for each of FFmpeg's applications of memory, there might not be a
+ * recognized or standardized API for that specific use. Memory alignment, for
+ * instance, varies wildly depending on operating systems, architectures, and
+ * compilers. Hence, this component of @ref libavutil is created to make
+ * dealing with memory consistently possible on all platforms.
+ *
+ * @{
+ */
+
+/**
+ * @defgroup lavu_mem_attrs Function Attributes
+ * Function attributes applicable to memory handling functions.
+ *
+ * These function attributes can help compilers emit more useful warnings, or
+ * generate better code.
+ * @{
+ */
+
+/**
+ * @def av_malloc_attrib
+ * Function attribute denoting a malloc-like function.
+ *
+ * @see <a
+ * href="https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-g_t_0040code_007bmalloc_007d-function-attribute-3251">Function
+ * attribute `malloc` in GCC's documentation</a>
+ */
+
+#if AV_GCC_VERSION_AT_LEAST(3, 1)
+# define av_malloc_attrib __attribute__((__malloc__))
+#else
+# define av_malloc_attrib
+#endif
+
+/**
+ * @def av_alloc_size(...)
+ * Function attribute used on a function that allocates memory, whose size is
+ * given by the specified parameter(s).
+ *
+ * @code{.c}
+ * void *av_malloc(size_t size) av_alloc_size(1);
+ * void *av_calloc(size_t nmemb, size_t size) av_alloc_size(1, 2);
+ * @endcode
+ *
+ * @param ... One or two parameter indexes, separated by a comma
+ *
+ * @see <a
+ * href="https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-g_t_0040code_007balloc_005fsize_007d-function-attribute-3220">Function
+ * attribute `alloc_size` in GCC's documentation</a>
+ */
+
+#if AV_GCC_VERSION_AT_LEAST(4, 3)
+# define av_alloc_size(...) __attribute__((alloc_size(__VA_ARGS__)))
+#else
+# define av_alloc_size(...)
+#endif
+
+/**
+ * @}
+ */
+
+/**
+ * @defgroup lavu_mem_funcs Heap Management
+ * Functions responsible for allocating, freeing, and copying memory.
+ *
+ * All memory allocation functions have a built-in upper limit of `INT_MAX`
+ * bytes. This may be changed with av_max_alloc(), although exercise extreme
+ * caution when doing so.
+ *
+ * @{
+ */
+
+/**
+ * Allocate a memory block with alignment suitable for all memory accesses
+ * (including vectors if available on the CPU).
+ *
+ * @param size Size in bytes for the memory block to be allocated
+ * @return Pointer to the allocated block, or `NULL` if the block cannot
+ * be allocated
+ * @see av_mallocz()
+ */
+void* av_malloc(size_t size) av_malloc_attrib av_alloc_size(1);
+
+/**
+ * Allocate a memory block with alignment suitable for all memory accesses
+ * (including vectors if available on the CPU) and zero all the bytes of the
+ * block.
+ *
+ * @param size Size in bytes for the memory block to be allocated
+ * @return Pointer to the allocated block, or `NULL` if it cannot be allocated
+ * @see av_malloc()
+ */
+void* av_mallocz(size_t size) av_malloc_attrib av_alloc_size(1);
+
+/**
+ * Allocate a memory block for an array with av_malloc().
+ *
+ * The allocated memory will have size `size * nmemb` bytes.
+ *
+ * @param nmemb Number of element
+ * @param size Size of a single element
+ * @return Pointer to the allocated block, or `NULL` if the block cannot
+ * be allocated
+ * @see av_malloc()
+ */
+av_alloc_size(1, 2) void* av_malloc_array(size_t nmemb, size_t size);
+
+/**
+ * Allocate a memory block for an array with av_mallocz().
+ *
+ * The allocated memory will have size `size * nmemb` bytes.
+ *
+ * @param nmemb Number of elements
+ * @param size Size of the single element
+ * @return Pointer to the allocated block, or `NULL` if the block cannot
+ * be allocated
+ *
+ * @see av_mallocz()
+ * @see av_malloc_array()
+ */
+void* av_calloc(size_t nmemb, size_t size) av_malloc_attrib av_alloc_size(1, 2);
+
+/**
+ * Allocate, reallocate, or free a block of memory.
+ *
+ * If `ptr` is `NULL` and `size` > 0, allocate a new block. Otherwise, expand or
+ * shrink that block of memory according to `size`.
+ *
+ * @param ptr Pointer to a memory block already allocated with
+ * av_realloc() or `NULL`
+ * @param size Size in bytes of the memory block to be allocated or
+ * reallocated
+ *
+ * @return Pointer to a newly-reallocated block or `NULL` if the block
+ * cannot be reallocated
+ *
+ * @warning Unlike av_malloc(), the returned pointer is not guaranteed to be
+ * correctly aligned. The returned pointer must be freed after even
+ * if size is zero.
+ * @see av_fast_realloc()
+ * @see av_reallocp()
+ */
+void* av_realloc(void* ptr, size_t size) av_alloc_size(2);
+
+/**
+ * Allocate, reallocate, or free a block of memory through a pointer to a
+ * pointer.
+ *
+ * If `*ptr` is `NULL` and `size` > 0, allocate a new block. If `size` is
+ * zero, free the memory block pointed to by `*ptr`. Otherwise, expand or
+ * shrink that block of memory according to `size`.
+ *
+ * @param[in,out] ptr Pointer to a pointer to a memory block already allocated
+ * with av_realloc(), or a pointer to `NULL`. The pointer
+ * is updated on success, or freed on failure.
+ * @param[in] size Size in bytes for the memory block to be allocated or
+ * reallocated
+ *
+ * @return Zero on success, an AVERROR error code on failure
+ *
+ * @warning Unlike av_malloc(), the allocated memory is not guaranteed to be
+ * correctly aligned.
+ */
+av_warn_unused_result int av_reallocp(void* ptr, size_t size);
+
+/**
+ * Allocate, reallocate, or free a block of memory.
+ *
+ * This function does the same thing as av_realloc(), except:
+ * - It takes two size arguments and allocates `nelem * elsize` bytes,
+ * after checking the result of the multiplication for integer overflow.
+ * - It frees the input block in case of failure, thus avoiding the memory
+ * leak with the classic
+ * @code{.c}
+ * buf = realloc(buf);
+ * if (!buf)
+ * return -1;
+ * @endcode
+ * pattern.
+ */
+void* av_realloc_f(void* ptr, size_t nelem, size_t elsize);
+
+/**
+ * Allocate, reallocate, or free an array.
+ *
+ * If `ptr` is `NULL` and `nmemb` > 0, allocate a new block.
+ *
+ * @param ptr Pointer to a memory block already allocated with
+ * av_realloc() or `NULL`
+ * @param nmemb Number of elements in the array
+ * @param size Size of the single element of the array
+ *
+ * @return Pointer to a newly-reallocated block or NULL if the block
+ * cannot be reallocated
+ *
+ * @warning Unlike av_malloc(), the allocated memory is not guaranteed to be
+ * correctly aligned. The returned pointer must be freed after even if
+ * nmemb is zero.
+ * @see av_reallocp_array()
+ */
+av_alloc_size(2, 3) void* av_realloc_array(void* ptr, size_t nmemb,
+ size_t size);
+
+/**
+ * Allocate, reallocate an array through a pointer to a pointer.
+ *
+ * If `*ptr` is `NULL` and `nmemb` > 0, allocate a new block.
+ *
+ * @param[in,out] ptr Pointer to a pointer to a memory block already
+ * allocated with av_realloc(), or a pointer to `NULL`.
+ * The pointer is updated on success, or freed on failure.
+ * @param[in] nmemb Number of elements
+ * @param[in] size Size of the single element
+ *
+ * @return Zero on success, an AVERROR error code on failure
+ *
+ * @warning Unlike av_malloc(), the allocated memory is not guaranteed to be
+ * correctly aligned. *ptr must be freed after even if nmemb is zero.
+ */
+int av_reallocp_array(void* ptr, size_t nmemb, size_t size);
+
+/**
+ * Reallocate the given buffer if it is not large enough, otherwise do nothing.
+ *
+ * If the given buffer is `NULL`, then a new uninitialized buffer is allocated.
+ *
+ * If the given buffer is not large enough, and reallocation fails, `NULL` is
+ * returned and `*size` is set to 0, but the original buffer is not changed or
+ * freed.
+ *
+ * A typical use pattern follows:
+ *
+ * @code{.c}
+ * uint8_t *buf = ...;
+ * uint8_t *new_buf = av_fast_realloc(buf, &current_size, size_needed);
+ * if (!new_buf) {
+ * // Allocation failed; clean up original buffer
+ * av_freep(&buf);
+ * return AVERROR(ENOMEM);
+ * }
+ * @endcode
+ *
+ * @param[in,out] ptr Already allocated buffer, or `NULL`
+ * @param[in,out] size Pointer to the size of buffer `ptr`. `*size` is
+ * updated to the new allocated size, in particular 0
+ * in case of failure.
+ * @param[in] min_size Desired minimal size of buffer `ptr`
+ * @return `ptr` if the buffer is large enough, a pointer to newly reallocated
+ * buffer if the buffer was not large enough, or `NULL` in case of
+ * error
+ * @see av_realloc()
+ * @see av_fast_malloc()
+ */
+void* av_fast_realloc(void* ptr, unsigned int* size, size_t min_size);
+
+/**
+ * Allocate a buffer, reusing the given one if large enough.
+ *
+ * Contrary to av_fast_realloc(), the current buffer contents might not be
+ * preserved and on error the old buffer is freed, thus no special handling to
+ * avoid memleaks is necessary.
+ *
+ * `*ptr` is allowed to be `NULL`, in which case allocation always happens if
+ * `size_needed` is greater than 0.
+ *
+ * @code{.c}
+ * uint8_t *buf = ...;
+ * av_fast_malloc(&buf, &current_size, size_needed);
+ * if (!buf) {
+ * // Allocation failed; buf already freed
+ * return AVERROR(ENOMEM);
+ * }
+ * @endcode
+ *
+ * @param[in,out] ptr Pointer to pointer to an already allocated buffer.
+ * `*ptr` will be overwritten with pointer to new
+ * buffer on success or `NULL` on failure
+ * @param[in,out] size Pointer to the size of buffer `*ptr`. `*size` is
+ * updated to the new allocated size, in particular 0
+ * in case of failure.
+ * @param[in] min_size Desired minimal size of buffer `*ptr`
+ * @see av_realloc()
+ * @see av_fast_mallocz()
+ */
+void av_fast_malloc(void* ptr, unsigned int* size, size_t min_size);
+
+/**
+ * Allocate and clear a buffer, reusing the given one if large enough.
+ *
+ * Like av_fast_malloc(), but all newly allocated space is initially cleared.
+ * Reused buffer is not cleared.
+ *
+ * `*ptr` is allowed to be `NULL`, in which case allocation always happens if
+ * `size_needed` is greater than 0.
+ *
+ * @param[in,out] ptr Pointer to pointer to an already allocated buffer.
+ * `*ptr` will be overwritten with pointer to new
+ * buffer on success or `NULL` on failure
+ * @param[in,out] size Pointer to the size of buffer `*ptr`. `*size` is
+ * updated to the new allocated size, in particular 0
+ * in case of failure.
+ * @param[in] min_size Desired minimal size of buffer `*ptr`
+ * @see av_fast_malloc()
+ */
+void av_fast_mallocz(void* ptr, unsigned int* size, size_t min_size);
+
+/**
+ * Free a memory block which has been allocated with a function of av_malloc()
+ * or av_realloc() family.
+ *
+ * @param ptr Pointer to the memory block which should be freed.
+ *
+ * @note `ptr = NULL` is explicitly allowed.
+ * @note It is recommended that you use av_freep() instead, to prevent leaving
+ * behind dangling pointers.
+ * @see av_freep()
+ */
+void av_free(void* ptr);
+
+/**
+ * Free a memory block which has been allocated with a function of av_malloc()
+ * or av_realloc() family, and set the pointer pointing to it to `NULL`.
+ *
+ * @code{.c}
+ * uint8_t *buf = av_malloc(16);
+ * av_free(buf);
+ * // buf now contains a dangling pointer to freed memory, and accidental
+ * // dereference of buf will result in a use-after-free, which may be a
+ * // security risk.
+ *
+ * uint8_t *buf = av_malloc(16);
+ * av_freep(&buf);
+ * // buf is now NULL, and accidental dereference will only result in a
+ * // NULL-pointer dereference.
+ * @endcode
+ *
+ * @param ptr Pointer to the pointer to the memory block which should be freed
+ * @note `*ptr = NULL` is safe and leads to no action.
+ * @see av_free()
+ */
+void av_freep(void* ptr);
+
+/**
+ * Duplicate a string.
+ *
+ * @param s String to be duplicated
+ * @return Pointer to a newly-allocated string containing a
+ * copy of `s` or `NULL` if the string cannot be allocated
+ * @see av_strndup()
+ */
+char* av_strdup(const char* s) av_malloc_attrib;
+
+/**
+ * Duplicate a substring of a string.
+ *
+ * @param s String to be duplicated
+ * @param len Maximum length of the resulting string (not counting the
+ * terminating byte)
+ * @return Pointer to a newly-allocated string containing a
+ * substring of `s` or `NULL` if the string cannot be allocated
+ */
+char* av_strndup(const char* s, size_t len) av_malloc_attrib;
+
+/**
+ * Duplicate a buffer with av_malloc().
+ *
+ * @param p Buffer to be duplicated
+ * @param size Size in bytes of the buffer copied
+ * @return Pointer to a newly allocated buffer containing a
+ * copy of `p` or `NULL` if the buffer cannot be allocated
+ */
+void* av_memdup(const void* p, size_t size);
+
+/**
+ * Overlapping memcpy() implementation.
+ *
+ * @param dst Destination buffer
+ * @param back Number of bytes back to start copying (i.e. the initial size of
+ * the overlapping window); must be > 0
+ * @param cnt Number of bytes to copy; must be >= 0
+ *
+ * @note `cnt > back` is valid, this will copy the bytes we just copied,
+ * thus creating a repeating pattern with a period length of `back`.
+ */
+void av_memcpy_backptr(uint8_t* dst, int back, int cnt);
+
+/**
+ * @}
+ */
+
+/**
+ * @defgroup lavu_mem_dynarray Dynamic Array
+ *
+ * Utilities to make an array grow when needed.
+ *
+ * Sometimes, the programmer would want to have an array that can grow when
+ * needed. The libavutil dynamic array utilities fill that need.
+ *
+ * libavutil supports two systems of appending elements onto a dynamically
+ * allocated array, the first one storing the pointer to the value in the
+ * array, and the second storing the value directly. In both systems, the
+ * caller is responsible for maintaining a variable containing the length of
+ * the array, as well as freeing of the array after use.
+ *
+ * The first system stores pointers to values in a block of dynamically
+ * allocated memory. Since only pointers are stored, the function does not need
+ * to know the size of the type. Both av_dynarray_add() and
+ * av_dynarray_add_nofree() implement this system.
+ *
+ * @code
+ * type **array = NULL; //< an array of pointers to values
+ * int nb = 0; //< a variable to keep track of the length of the array
+ *
+ * type to_be_added = ...;
+ * type to_be_added2 = ...;
+ *
+ * av_dynarray_add(&array, &nb, &to_be_added);
+ * if (nb == 0)
+ * return AVERROR(ENOMEM);
+ *
+ * av_dynarray_add(&array, &nb, &to_be_added2);
+ * if (nb == 0)
+ * return AVERROR(ENOMEM);
+ *
+ * // Now:
+ * // nb == 2
+ * // &to_be_added == array[0]
+ * // &to_be_added2 == array[1]
+ *
+ * av_freep(&array);
+ * @endcode
+ *
+ * The second system stores the value directly in a block of memory. As a
+ * result, the function has to know the size of the type. av_dynarray2_add()
+ * implements this mechanism.
+ *
+ * @code
+ * type *array = NULL; //< an array of values
+ * int nb = 0; //< a variable to keep track of the length of the array
+ *
+ * type to_be_added = ...;
+ * type to_be_added2 = ...;
+ *
+ * type *addr = av_dynarray2_add((void **)&array, &nb, sizeof(*array), NULL);
+ * if (!addr)
+ * return AVERROR(ENOMEM);
+ * memcpy(addr, &to_be_added, sizeof(to_be_added));
+ *
+ * // Shortcut of the above.
+ * type *addr = av_dynarray2_add((void **)&array, &nb, sizeof(*array),
+ * (const void *)&to_be_added2);
+ * if (!addr)
+ * return AVERROR(ENOMEM);
+ *
+ * // Now:
+ * // nb == 2
+ * // to_be_added == array[0]
+ * // to_be_added2 == array[1]
+ *
+ * av_freep(&array);
+ * @endcode
+ *
+ * @{
+ */
+
+/**
+ * Add the pointer to an element to a dynamic array.
+ *
+ * The array to grow is supposed to be an array of pointers to
+ * structures, and the element to add must be a pointer to an already
+ * allocated structure.
+ *
+ * The array is reallocated when its size reaches powers of 2.
+ * Therefore, the amortized cost of adding an element is constant.
+ *
+ * In case of success, the pointer to the array is updated in order to
+ * point to the new grown array, and the number pointed to by `nb_ptr`
+ * is incremented.
+ * In case of failure, the array is freed, `*tab_ptr` is set to `NULL` and
+ * `*nb_ptr` is set to 0.
+ *
+ * @param[in,out] tab_ptr Pointer to the array to grow
+ * @param[in,out] nb_ptr Pointer to the number of elements in the array
+ * @param[in] elem Element to add
+ * @see av_dynarray_add_nofree(), av_dynarray2_add()
+ */
+void av_dynarray_add(void* tab_ptr, int* nb_ptr, void* elem);
+
+/**
+ * Add an element to a dynamic array.
+ *
+ * Function has the same functionality as av_dynarray_add(),
+ * but it doesn't free memory on fails. It returns error code
+ * instead and leave current buffer untouched.
+ *
+ * @return >=0 on success, negative otherwise
+ * @see av_dynarray_add(), av_dynarray2_add()
+ */
+av_warn_unused_result int av_dynarray_add_nofree(void* tab_ptr, int* nb_ptr,
+ void* elem);
+
+/**
+ * Add an element of size `elem_size` to a dynamic array.
+ *
+ * The array is reallocated when its number of elements reaches powers of 2.
+ * Therefore, the amortized cost of adding an element is constant.
+ *
+ * In case of success, the pointer to the array is updated in order to
+ * point to the new grown array, and the number pointed to by `nb_ptr`
+ * is incremented.
+ * In case of failure, the array is freed, `*tab_ptr` is set to `NULL` and
+ * `*nb_ptr` is set to 0.
+ *
+ * @param[in,out] tab_ptr Pointer to the array to grow
+ * @param[in,out] nb_ptr Pointer to the number of elements in the array
+ * @param[in] elem_size Size in bytes of an element in the array
+ * @param[in] elem_data Pointer to the data of the element to add. If
+ * `NULL`, the space of the newly added element is
+ * allocated but left uninitialized.
+ *
+ * @return Pointer to the data of the element to copy in the newly allocated
+ * space
+ * @see av_dynarray_add(), av_dynarray_add_nofree()
+ */
+void* av_dynarray2_add(void** tab_ptr, int* nb_ptr, size_t elem_size,
+ const uint8_t* elem_data);
+
+/**
+ * @}
+ */
+
+/**
+ * @defgroup lavu_mem_misc Miscellaneous Functions
+ *
+ * Other functions related to memory allocation.
+ *
+ * @{
+ */
+
+/**
+ * Multiply two `size_t` values checking for overflow.
+ *
+ * @param[in] a Operand of multiplication
+ * @param[in] b Operand of multiplication
+ * @param[out] r Pointer to the result of the operation
+ * @return 0 on success, AVERROR(EINVAL) on overflow
+ */
+int av_size_mult(size_t a, size_t b, size_t* r);
+
+/**
+ * Set the maximum size that may be allocated in one block.
+ *
+ * The value specified with this function is effective for all libavutil's @ref
+ * lavu_mem_funcs "heap management functions."
+ *
+ * By default, the max value is defined as `INT_MAX`.
+ *
+ * @param max Value to be set as the new maximum size
+ *
+ * @warning Exercise extreme caution when using this function. Don't touch
+ * this if you do not understand the full consequence of doing so.
+ */
+void av_max_alloc(size_t max);
+
+/**
+ * @}
+ * @}
+ */
+
+#endif /* AVUTIL_MEM_H */
diff --git a/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/pixfmt.h b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/pixfmt.h
new file mode 100644
index 0000000000..5a33471a05
--- /dev/null
+++ b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/pixfmt.h
@@ -0,0 +1,920 @@
+/*
+ * copyright (c) 2006 Michael Niedermayer <michaelni@gmx.at>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVUTIL_PIXFMT_H
+#define AVUTIL_PIXFMT_H
+
+/**
+ * @file
+ * pixel format definitions
+ */
+
+#include "libavutil/avconfig.h"
+#include "version.h"
+
+#define AVPALETTE_SIZE 1024
+#define AVPALETTE_COUNT 256
+
+/**
+ * Maximum number of planes in any pixel format.
+ * This should be used when a maximum is needed, but code should not
+ * be written to require a maximum for no good reason.
+ */
+#define AV_VIDEO_MAX_PLANES 4
+
+/**
+ * Pixel format.
+ *
+ * @note
+ * AV_PIX_FMT_RGB32 is handled in an endian-specific manner. An RGBA
+ * color is put together as:
+ * (A << 24) | (R << 16) | (G << 8) | B
+ * This is stored as BGRA on little-endian CPU architectures and ARGB on
+ * big-endian CPUs.
+ *
+ * @note
+ * If the resolution is not a multiple of the chroma subsampling factor
+ * then the chroma plane resolution must be rounded up.
+ *
+ * @par
+ * When the pixel format is palettized RGB32 (AV_PIX_FMT_PAL8), the palettized
+ * image data is stored in AVFrame.data[0]. The palette is transported in
+ * AVFrame.data[1], is 1024 bytes long (256 4-byte entries) and is
+ * formatted the same as in AV_PIX_FMT_RGB32 described above (i.e., it is
+ * also endian-specific). Note also that the individual RGB32 palette
+ * components stored in AVFrame.data[1] should be in the range 0..255.
+ * This is important as many custom PAL8 video codecs that were designed
+ * to run on the IBM VGA graphics adapter use 6-bit palette components.
+ *
+ * @par
+ * For all the 8 bits per pixel formats, an RGB32 palette is in data[1] like
+ * for pal8. This palette is filled in automatically by the function
+ * allocating the picture.
+ */
+enum AVPixelFormat {
+ AV_PIX_FMT_NONE = -1,
+ AV_PIX_FMT_YUV420P, ///< planar YUV 4:2:0, 12bpp, (1 Cr & Cb sample per 2x2 Y
+ ///< samples)
+ AV_PIX_FMT_YUYV422, ///< packed YUV 4:2:2, 16bpp, Y0 Cb Y1 Cr
+ AV_PIX_FMT_RGB24, ///< packed RGB 8:8:8, 24bpp, RGBRGB...
+ AV_PIX_FMT_BGR24, ///< packed RGB 8:8:8, 24bpp, BGRBGR...
+ AV_PIX_FMT_YUV422P, ///< planar YUV 4:2:2, 16bpp, (1 Cr & Cb sample per 2x1 Y
+ ///< samples)
+ AV_PIX_FMT_YUV444P, ///< planar YUV 4:4:4, 24bpp, (1 Cr & Cb sample per 1x1 Y
+ ///< samples)
+ AV_PIX_FMT_YUV410P, ///< planar YUV 4:1:0, 9bpp, (1 Cr & Cb sample per 4x4 Y
+ ///< samples)
+ AV_PIX_FMT_YUV411P, ///< planar YUV 4:1:1, 12bpp, (1 Cr & Cb sample per 4x1 Y
+ ///< samples)
+ AV_PIX_FMT_GRAY8, ///< Y , 8bpp
+ AV_PIX_FMT_MONOWHITE, ///< Y , 1bpp, 0 is white, 1 is black,
+ ///< in each byte pixels are ordered from the
+ ///< msb to the lsb
+ AV_PIX_FMT_MONOBLACK, ///< Y , 1bpp, 0 is black, 1 is white,
+ ///< in each byte pixels are ordered from the
+ ///< msb to the lsb
+ AV_PIX_FMT_PAL8, ///< 8 bits with AV_PIX_FMT_RGB32 palette
+ AV_PIX_FMT_YUVJ420P, ///< planar YUV 4:2:0, 12bpp, full scale (JPEG),
+ ///< deprecated in favor of AV_PIX_FMT_YUV420P and
+ ///< setting color_range
+ AV_PIX_FMT_YUVJ422P, ///< planar YUV 4:2:2, 16bpp, full scale (JPEG),
+ ///< deprecated in favor of AV_PIX_FMT_YUV422P and
+ ///< setting color_range
+ AV_PIX_FMT_YUVJ444P, ///< planar YUV 4:4:4, 24bpp, full scale (JPEG),
+ ///< deprecated in favor of AV_PIX_FMT_YUV444P and
+ ///< setting color_range
+ AV_PIX_FMT_UYVY422, ///< packed YUV 4:2:2, 16bpp, Cb Y0 Cr Y1
+ AV_PIX_FMT_UYYVYY411, ///< packed YUV 4:1:1, 12bpp, Cb Y0 Y1 Cr Y2 Y3
+ AV_PIX_FMT_BGR8, ///< packed RGB 3:3:2, 8bpp, (msb)2B 3G 3R(lsb)
+ AV_PIX_FMT_BGR4, ///< packed RGB 1:2:1 bitstream, 4bpp, (msb)1B 2G 1R(lsb),
+ ///< a byte contains two pixels, the first pixel in the byte
+ ///< is the one composed by the 4 msb bits
+ AV_PIX_FMT_BGR4_BYTE, ///< packed RGB 1:2:1, 8bpp, (msb)1B 2G 1R(lsb)
+ AV_PIX_FMT_RGB8, ///< packed RGB 3:3:2, 8bpp, (msb)3R 3G 2B(lsb)
+ AV_PIX_FMT_RGB4, ///< packed RGB 1:2:1 bitstream, 4bpp, (msb)1R 2G 1B(lsb),
+ ///< a byte contains two pixels, the first pixel in the byte
+ ///< is the one composed by the 4 msb bits
+ AV_PIX_FMT_RGB4_BYTE, ///< packed RGB 1:2:1, 8bpp, (msb)1R 2G 1B(lsb)
+ AV_PIX_FMT_NV12, ///< planar YUV 4:2:0, 12bpp, 1 plane for Y and 1 plane for
+ ///< the UV components, which are interleaved (first byte U
+ ///< and the following byte V)
+ AV_PIX_FMT_NV21, ///< as above, but U and V bytes are swapped
+
+ AV_PIX_FMT_ARGB, ///< packed ARGB 8:8:8:8, 32bpp, ARGBARGB...
+ AV_PIX_FMT_RGBA, ///< packed RGBA 8:8:8:8, 32bpp, RGBARGBA...
+ AV_PIX_FMT_ABGR, ///< packed ABGR 8:8:8:8, 32bpp, ABGRABGR...
+ AV_PIX_FMT_BGRA, ///< packed BGRA 8:8:8:8, 32bpp, BGRABGRA...
+
+ AV_PIX_FMT_GRAY16BE, ///< Y , 16bpp, big-endian
+ AV_PIX_FMT_GRAY16LE, ///< Y , 16bpp, little-endian
+ AV_PIX_FMT_YUV440P, ///< planar YUV 4:4:0 (1 Cr & Cb sample per 1x2 Y
+ ///< samples)
+ AV_PIX_FMT_YUVJ440P, ///< planar YUV 4:4:0 full scale (JPEG), deprecated in
+ ///< favor of AV_PIX_FMT_YUV440P and setting color_range
+ AV_PIX_FMT_YUVA420P, ///< planar YUV 4:2:0, 20bpp, (1 Cr & Cb sample per 2x2
+ ///< Y & A samples)
+ AV_PIX_FMT_RGB48BE, ///< packed RGB 16:16:16, 48bpp, 16R, 16G, 16B, the
+ ///< 2-byte value for each R/G/B component is stored as
+ ///< big-endian
+ AV_PIX_FMT_RGB48LE, ///< packed RGB 16:16:16, 48bpp, 16R, 16G, 16B, the
+ ///< 2-byte value for each R/G/B component is stored as
+ ///< little-endian
+
+ AV_PIX_FMT_RGB565BE, ///< packed RGB 5:6:5, 16bpp, (msb) 5R 6G 5B(lsb),
+ ///< big-endian
+ AV_PIX_FMT_RGB565LE, ///< packed RGB 5:6:5, 16bpp, (msb) 5R 6G 5B(lsb),
+ ///< little-endian
+ AV_PIX_FMT_RGB555BE, ///< packed RGB 5:5:5, 16bpp, (msb)1X 5R 5G 5B(lsb),
+ ///< big-endian , X=unused/undefined
+ AV_PIX_FMT_RGB555LE, ///< packed RGB 5:5:5, 16bpp, (msb)1X 5R 5G 5B(lsb),
+ ///< little-endian, X=unused/undefined
+
+ AV_PIX_FMT_BGR565BE, ///< packed BGR 5:6:5, 16bpp, (msb) 5B 6G 5R(lsb),
+ ///< big-endian
+ AV_PIX_FMT_BGR565LE, ///< packed BGR 5:6:5, 16bpp, (msb) 5B 6G 5R(lsb),
+ ///< little-endian
+ AV_PIX_FMT_BGR555BE, ///< packed BGR 5:5:5, 16bpp, (msb)1X 5B 5G 5R(lsb),
+ ///< big-endian , X=unused/undefined
+ AV_PIX_FMT_BGR555LE, ///< packed BGR 5:5:5, 16bpp, (msb)1X 5B 5G 5R(lsb),
+ ///< little-endian, X=unused/undefined
+
+ /**
+ * Hardware acceleration through VA-API, data[3] contains a
+ * VASurfaceID.
+ */
+ AV_PIX_FMT_VAAPI,
+
+ AV_PIX_FMT_YUV420P16LE, ///< planar YUV 4:2:0, 24bpp, (1 Cr & Cb sample per
+ ///< 2x2 Y samples), little-endian
+ AV_PIX_FMT_YUV420P16BE, ///< planar YUV 4:2:0, 24bpp, (1 Cr & Cb sample per
+ ///< 2x2 Y samples), big-endian
+ AV_PIX_FMT_YUV422P16LE, ///< planar YUV 4:2:2, 32bpp, (1 Cr & Cb sample per
+ ///< 2x1 Y samples), little-endian
+ AV_PIX_FMT_YUV422P16BE, ///< planar YUV 4:2:2, 32bpp, (1 Cr & Cb sample per
+ ///< 2x1 Y samples), big-endian
+ AV_PIX_FMT_YUV444P16LE, ///< planar YUV 4:4:4, 48bpp, (1 Cr & Cb sample per
+ ///< 1x1 Y samples), little-endian
+ AV_PIX_FMT_YUV444P16BE, ///< planar YUV 4:4:4, 48bpp, (1 Cr & Cb sample per
+ ///< 1x1 Y samples), big-endian
+ AV_PIX_FMT_DXVA2_VLD, ///< HW decoding through DXVA2, Picture.data[3]
+ ///< contains a LPDIRECT3DSURFACE9 pointer
+
+ AV_PIX_FMT_RGB444LE, ///< packed RGB 4:4:4, 16bpp, (msb)4X 4R 4G 4B(lsb),
+ ///< little-endian, X=unused/undefined
+ AV_PIX_FMT_RGB444BE, ///< packed RGB 4:4:4, 16bpp, (msb)4X 4R 4G 4B(lsb),
+ ///< big-endian, X=unused/undefined
+ AV_PIX_FMT_BGR444LE, ///< packed BGR 4:4:4, 16bpp, (msb)4X 4B 4G 4R(lsb),
+ ///< little-endian, X=unused/undefined
+ AV_PIX_FMT_BGR444BE, ///< packed BGR 4:4:4, 16bpp, (msb)4X 4B 4G 4R(lsb),
+ ///< big-endian, X=unused/undefined
+ AV_PIX_FMT_YA8, ///< 8 bits gray, 8 bits alpha
+
+ AV_PIX_FMT_Y400A = AV_PIX_FMT_YA8, ///< alias for AV_PIX_FMT_YA8
+ AV_PIX_FMT_GRAY8A = AV_PIX_FMT_YA8, ///< alias for AV_PIX_FMT_YA8
+
+ AV_PIX_FMT_BGR48BE, ///< packed RGB 16:16:16, 48bpp, 16B, 16G, 16R, the
+ ///< 2-byte value for each R/G/B component is stored as
+ ///< big-endian
+ AV_PIX_FMT_BGR48LE, ///< packed RGB 16:16:16, 48bpp, 16B, 16G, 16R, the
+ ///< 2-byte value for each R/G/B component is stored as
+ ///< little-endian
+
+ /**
+ * The following 12 formats have the disadvantage of needing 1 format for each
+ * bit depth. Notice that each 9/10 bits sample is stored in 16 bits with
+ * extra padding. If you want to support multiple bit depths, then using
+ * AV_PIX_FMT_YUV420P16* with the bpp stored separately is better.
+ */
+ AV_PIX_FMT_YUV420P9BE, ///< planar YUV 4:2:0, 13.5bpp, (1 Cr & Cb sample per
+ ///< 2x2 Y samples), big-endian
+ AV_PIX_FMT_YUV420P9LE, ///< planar YUV 4:2:0, 13.5bpp, (1 Cr & Cb sample per
+ ///< 2x2 Y samples), little-endian
+ AV_PIX_FMT_YUV420P10BE, ///< planar YUV 4:2:0, 15bpp, (1 Cr & Cb sample per
+ ///< 2x2 Y samples), big-endian
+ AV_PIX_FMT_YUV420P10LE, ///< planar YUV 4:2:0, 15bpp, (1 Cr & Cb sample per
+ ///< 2x2 Y samples), little-endian
+ AV_PIX_FMT_YUV422P10BE, ///< planar YUV 4:2:2, 20bpp, (1 Cr & Cb sample per
+ ///< 2x1 Y samples), big-endian
+ AV_PIX_FMT_YUV422P10LE, ///< planar YUV 4:2:2, 20bpp, (1 Cr & Cb sample per
+ ///< 2x1 Y samples), little-endian
+ AV_PIX_FMT_YUV444P9BE, ///< planar YUV 4:4:4, 27bpp, (1 Cr & Cb sample per
+ ///< 1x1 Y samples), big-endian
+ AV_PIX_FMT_YUV444P9LE, ///< planar YUV 4:4:4, 27bpp, (1 Cr & Cb sample per
+ ///< 1x1 Y samples), little-endian
+ AV_PIX_FMT_YUV444P10BE, ///< planar YUV 4:4:4, 30bpp, (1 Cr & Cb sample per
+ ///< 1x1 Y samples), big-endian
+ AV_PIX_FMT_YUV444P10LE, ///< planar YUV 4:4:4, 30bpp, (1 Cr & Cb sample per
+ ///< 1x1 Y samples), little-endian
+ AV_PIX_FMT_YUV422P9BE, ///< planar YUV 4:2:2, 18bpp, (1 Cr & Cb sample per
+ ///< 2x1 Y samples), big-endian
+ AV_PIX_FMT_YUV422P9LE, ///< planar YUV 4:2:2, 18bpp, (1 Cr & Cb sample per
+ ///< 2x1 Y samples), little-endian
+ AV_PIX_FMT_GBRP, ///< planar GBR 4:4:4 24bpp
+ AV_PIX_FMT_GBR24P = AV_PIX_FMT_GBRP, // alias for #AV_PIX_FMT_GBRP
+ AV_PIX_FMT_GBRP9BE, ///< planar GBR 4:4:4 27bpp, big-endian
+ AV_PIX_FMT_GBRP9LE, ///< planar GBR 4:4:4 27bpp, little-endian
+ AV_PIX_FMT_GBRP10BE, ///< planar GBR 4:4:4 30bpp, big-endian
+ AV_PIX_FMT_GBRP10LE, ///< planar GBR 4:4:4 30bpp, little-endian
+ AV_PIX_FMT_GBRP16BE, ///< planar GBR 4:4:4 48bpp, big-endian
+ AV_PIX_FMT_GBRP16LE, ///< planar GBR 4:4:4 48bpp, little-endian
+ AV_PIX_FMT_YUVA422P, ///< planar YUV 4:2:2 24bpp, (1 Cr & Cb sample per 2x1 Y
+ ///< & A samples)
+ AV_PIX_FMT_YUVA444P, ///< planar YUV 4:4:4 32bpp, (1 Cr & Cb sample per 1x1 Y
+ ///< & A samples)
+ AV_PIX_FMT_YUVA420P9BE, ///< planar YUV 4:2:0 22.5bpp, (1 Cr & Cb sample per
+ ///< 2x2 Y & A samples), big-endian
+ AV_PIX_FMT_YUVA420P9LE, ///< planar YUV 4:2:0 22.5bpp, (1 Cr & Cb sample per
+ ///< 2x2 Y & A samples), little-endian
+ AV_PIX_FMT_YUVA422P9BE, ///< planar YUV 4:2:2 27bpp, (1 Cr & Cb sample per
+ ///< 2x1 Y & A samples), big-endian
+ AV_PIX_FMT_YUVA422P9LE, ///< planar YUV 4:2:2 27bpp, (1 Cr & Cb sample per
+ ///< 2x1 Y & A samples), little-endian
+ AV_PIX_FMT_YUVA444P9BE, ///< planar YUV 4:4:4 36bpp, (1 Cr & Cb sample per
+ ///< 1x1 Y & A samples), big-endian
+ AV_PIX_FMT_YUVA444P9LE, ///< planar YUV 4:4:4 36bpp, (1 Cr & Cb sample per
+ ///< 1x1 Y & A samples), little-endian
+ AV_PIX_FMT_YUVA420P10BE, ///< planar YUV 4:2:0 25bpp, (1 Cr & Cb sample per
+ ///< 2x2 Y & A samples, big-endian)
+ AV_PIX_FMT_YUVA420P10LE, ///< planar YUV 4:2:0 25bpp, (1 Cr & Cb sample per
+ ///< 2x2 Y & A samples, little-endian)
+ AV_PIX_FMT_YUVA422P10BE, ///< planar YUV 4:2:2 30bpp, (1 Cr & Cb sample per
+ ///< 2x1 Y & A samples, big-endian)
+ AV_PIX_FMT_YUVA422P10LE, ///< planar YUV 4:2:2 30bpp, (1 Cr & Cb sample per
+ ///< 2x1 Y & A samples, little-endian)
+ AV_PIX_FMT_YUVA444P10BE, ///< planar YUV 4:4:4 40bpp, (1 Cr & Cb sample per
+ ///< 1x1 Y & A samples, big-endian)
+ AV_PIX_FMT_YUVA444P10LE, ///< planar YUV 4:4:4 40bpp, (1 Cr & Cb sample per
+ ///< 1x1 Y & A samples, little-endian)
+ AV_PIX_FMT_YUVA420P16BE, ///< planar YUV 4:2:0 40bpp, (1 Cr & Cb sample per
+ ///< 2x2 Y & A samples, big-endian)
+ AV_PIX_FMT_YUVA420P16LE, ///< planar YUV 4:2:0 40bpp, (1 Cr & Cb sample per
+ ///< 2x2 Y & A samples, little-endian)
+ AV_PIX_FMT_YUVA422P16BE, ///< planar YUV 4:2:2 48bpp, (1 Cr & Cb sample per
+ ///< 2x1 Y & A samples, big-endian)
+ AV_PIX_FMT_YUVA422P16LE, ///< planar YUV 4:2:2 48bpp, (1 Cr & Cb sample per
+ ///< 2x1 Y & A samples, little-endian)
+ AV_PIX_FMT_YUVA444P16BE, ///< planar YUV 4:4:4 64bpp, (1 Cr & Cb sample per
+ ///< 1x1 Y & A samples, big-endian)
+ AV_PIX_FMT_YUVA444P16LE, ///< planar YUV 4:4:4 64bpp, (1 Cr & Cb sample per
+ ///< 1x1 Y & A samples, little-endian)
+
+ AV_PIX_FMT_VDPAU, ///< HW acceleration through VDPAU, Picture.data[3]
+ ///< contains a VdpVideoSurface
+
+ AV_PIX_FMT_XYZ12LE, ///< packed XYZ 4:4:4, 36 bpp, (msb) 12X, 12Y, 12Z (lsb),
+ ///< the 2-byte value for each X/Y/Z is stored as
+ ///< little-endian, the 4 lower bits are set to 0
+ AV_PIX_FMT_XYZ12BE, ///< packed XYZ 4:4:4, 36 bpp, (msb) 12X, 12Y, 12Z (lsb),
+ ///< the 2-byte value for each X/Y/Z is stored as
+ ///< big-endian, the 4 lower bits are set to 0
+ AV_PIX_FMT_NV16, ///< interleaved chroma YUV 4:2:2, 16bpp, (1 Cr & Cb sample
+ ///< per 2x1 Y samples)
+ AV_PIX_FMT_NV20LE, ///< interleaved chroma YUV 4:2:2, 20bpp, (1 Cr & Cb
+ ///< sample per 2x1 Y samples), little-endian
+ AV_PIX_FMT_NV20BE, ///< interleaved chroma YUV 4:2:2, 20bpp, (1 Cr & Cb
+ ///< sample per 2x1 Y samples), big-endian
+
+ AV_PIX_FMT_RGBA64BE, ///< packed RGBA 16:16:16:16, 64bpp, 16R, 16G, 16B, 16A,
+ ///< the 2-byte value for each R/G/B/A component is
+ ///< stored as big-endian
+ AV_PIX_FMT_RGBA64LE, ///< packed RGBA 16:16:16:16, 64bpp, 16R, 16G, 16B, 16A,
+ ///< the 2-byte value for each R/G/B/A component is
+ ///< stored as little-endian
+ AV_PIX_FMT_BGRA64BE, ///< packed RGBA 16:16:16:16, 64bpp, 16B, 16G, 16R, 16A,
+ ///< the 2-byte value for each R/G/B/A component is
+ ///< stored as big-endian
+ AV_PIX_FMT_BGRA64LE, ///< packed RGBA 16:16:16:16, 64bpp, 16B, 16G, 16R, 16A,
+ ///< the 2-byte value for each R/G/B/A component is
+ ///< stored as little-endian
+
+ AV_PIX_FMT_YVYU422, ///< packed YUV 4:2:2, 16bpp, Y0 Cr Y1 Cb
+
+ AV_PIX_FMT_YA16BE, ///< 16 bits gray, 16 bits alpha (big-endian)
+ AV_PIX_FMT_YA16LE, ///< 16 bits gray, 16 bits alpha (little-endian)
+
+ AV_PIX_FMT_GBRAP, ///< planar GBRA 4:4:4:4 32bpp
+ AV_PIX_FMT_GBRAP16BE, ///< planar GBRA 4:4:4:4 64bpp, big-endian
+ AV_PIX_FMT_GBRAP16LE, ///< planar GBRA 4:4:4:4 64bpp, little-endian
+ /**
+ * HW acceleration through QSV, data[3] contains a pointer to the
+ * mfxFrameSurface1 structure.
+ *
+ * Before FFmpeg 5.0:
+ * mfxFrameSurface1.Data.MemId contains a pointer when importing
+ * the following frames as QSV frames:
+ *
+ * VAAPI:
+ * mfxFrameSurface1.Data.MemId contains a pointer to VASurfaceID
+ *
+ * DXVA2:
+ * mfxFrameSurface1.Data.MemId contains a pointer to IDirect3DSurface9
+ *
+ * FFmpeg 5.0 and above:
+ * mfxFrameSurface1.Data.MemId contains a pointer to the mfxHDLPair
+ * structure when importing the following frames as QSV frames:
+ *
+ * VAAPI:
+ * mfxHDLPair.first contains a VASurfaceID pointer.
+ * mfxHDLPair.second is always MFX_INFINITE.
+ *
+ * DXVA2:
+ * mfxHDLPair.first contains IDirect3DSurface9 pointer.
+ * mfxHDLPair.second is always MFX_INFINITE.
+ *
+ * D3D11:
+ * mfxHDLPair.first contains a ID3D11Texture2D pointer.
+ * mfxHDLPair.second contains the texture array index of the frame if the
+ * ID3D11Texture2D is an array texture, or always MFX_INFINITE if it is a
+ * normal texture.
+ */
+ AV_PIX_FMT_QSV,
+ /**
+ * HW acceleration though MMAL, data[3] contains a pointer to the
+ * MMAL_BUFFER_HEADER_T structure.
+ */
+ AV_PIX_FMT_MMAL,
+
+ AV_PIX_FMT_D3D11VA_VLD, ///< HW decoding through Direct3D11 via old API,
+ ///< Picture.data[3] contains a
+ ///< ID3D11VideoDecoderOutputView pointer
+
+ /**
+ * HW acceleration through CUDA. data[i] contain CUdeviceptr pointers
+ * exactly as for system memory frames.
+ */
+ AV_PIX_FMT_CUDA,
+
+ AV_PIX_FMT_0RGB, ///< packed RGB 8:8:8, 32bpp, XRGBXRGB... X=unused/undefined
+ AV_PIX_FMT_RGB0, ///< packed RGB 8:8:8, 32bpp, RGBXRGBX... X=unused/undefined
+ AV_PIX_FMT_0BGR, ///< packed BGR 8:8:8, 32bpp, XBGRXBGR... X=unused/undefined
+ AV_PIX_FMT_BGR0, ///< packed BGR 8:8:8, 32bpp, BGRXBGRX... X=unused/undefined
+
+ AV_PIX_FMT_YUV420P12BE, ///< planar YUV 4:2:0,18bpp, (1 Cr & Cb sample per
+ ///< 2x2 Y samples), big-endian
+ AV_PIX_FMT_YUV420P12LE, ///< planar YUV 4:2:0,18bpp, (1 Cr & Cb sample per
+ ///< 2x2 Y samples), little-endian
+ AV_PIX_FMT_YUV420P14BE, ///< planar YUV 4:2:0,21bpp, (1 Cr & Cb sample per
+ ///< 2x2 Y samples), big-endian
+ AV_PIX_FMT_YUV420P14LE, ///< planar YUV 4:2:0,21bpp, (1 Cr & Cb sample per
+ ///< 2x2 Y samples), little-endian
+ AV_PIX_FMT_YUV422P12BE, ///< planar YUV 4:2:2,24bpp, (1 Cr & Cb sample per
+ ///< 2x1 Y samples), big-endian
+ AV_PIX_FMT_YUV422P12LE, ///< planar YUV 4:2:2,24bpp, (1 Cr & Cb sample per
+ ///< 2x1 Y samples), little-endian
+ AV_PIX_FMT_YUV422P14BE, ///< planar YUV 4:2:2,28bpp, (1 Cr & Cb sample per
+ ///< 2x1 Y samples), big-endian
+ AV_PIX_FMT_YUV422P14LE, ///< planar YUV 4:2:2,28bpp, (1 Cr & Cb sample per
+ ///< 2x1 Y samples), little-endian
+ AV_PIX_FMT_YUV444P12BE, ///< planar YUV 4:4:4,36bpp, (1 Cr & Cb sample per
+ ///< 1x1 Y samples), big-endian
+ AV_PIX_FMT_YUV444P12LE, ///< planar YUV 4:4:4,36bpp, (1 Cr & Cb sample per
+ ///< 1x1 Y samples), little-endian
+ AV_PIX_FMT_YUV444P14BE, ///< planar YUV 4:4:4,42bpp, (1 Cr & Cb sample per
+ ///< 1x1 Y samples), big-endian
+ AV_PIX_FMT_YUV444P14LE, ///< planar YUV 4:4:4,42bpp, (1 Cr & Cb sample per
+ ///< 1x1 Y samples), little-endian
+ AV_PIX_FMT_GBRP12BE, ///< planar GBR 4:4:4 36bpp, big-endian
+ AV_PIX_FMT_GBRP12LE, ///< planar GBR 4:4:4 36bpp, little-endian
+ AV_PIX_FMT_GBRP14BE, ///< planar GBR 4:4:4 42bpp, big-endian
+ AV_PIX_FMT_GBRP14LE, ///< planar GBR 4:4:4 42bpp, little-endian
+ AV_PIX_FMT_YUVJ411P, ///< planar YUV 4:1:1, 12bpp, (1 Cr & Cb sample per 4x1
+ ///< Y samples) full scale (JPEG), deprecated in favor
+ ///< of AV_PIX_FMT_YUV411P and setting color_range
+
+ AV_PIX_FMT_BAYER_BGGR8, ///< bayer, BGBG..(odd line), GRGR..(even line),
+ ///< 8-bit samples
+ AV_PIX_FMT_BAYER_RGGB8, ///< bayer, RGRG..(odd line), GBGB..(even line),
+ ///< 8-bit samples
+ AV_PIX_FMT_BAYER_GBRG8, ///< bayer, GBGB..(odd line), RGRG..(even line),
+ ///< 8-bit samples
+ AV_PIX_FMT_BAYER_GRBG8, ///< bayer, GRGR..(odd line), BGBG..(even line),
+ ///< 8-bit samples
+ AV_PIX_FMT_BAYER_BGGR16LE, ///< bayer, BGBG..(odd line), GRGR..(even line),
+ ///< 16-bit samples, little-endian
+ AV_PIX_FMT_BAYER_BGGR16BE, ///< bayer, BGBG..(odd line), GRGR..(even line),
+ ///< 16-bit samples, big-endian
+ AV_PIX_FMT_BAYER_RGGB16LE, ///< bayer, RGRG..(odd line), GBGB..(even line),
+ ///< 16-bit samples, little-endian
+ AV_PIX_FMT_BAYER_RGGB16BE, ///< bayer, RGRG..(odd line), GBGB..(even line),
+ ///< 16-bit samples, big-endian
+ AV_PIX_FMT_BAYER_GBRG16LE, ///< bayer, GBGB..(odd line), RGRG..(even line),
+ ///< 16-bit samples, little-endian
+ AV_PIX_FMT_BAYER_GBRG16BE, ///< bayer, GBGB..(odd line), RGRG..(even line),
+ ///< 16-bit samples, big-endian
+ AV_PIX_FMT_BAYER_GRBG16LE, ///< bayer, GRGR..(odd line), BGBG..(even line),
+ ///< 16-bit samples, little-endian
+ AV_PIX_FMT_BAYER_GRBG16BE, ///< bayer, GRGR..(odd line), BGBG..(even line),
+ ///< 16-bit samples, big-endian
+
+ AV_PIX_FMT_YUV440P10LE, ///< planar YUV 4:4:0,20bpp, (1 Cr & Cb sample per
+ ///< 1x2 Y samples), little-endian
+ AV_PIX_FMT_YUV440P10BE, ///< planar YUV 4:4:0,20bpp, (1 Cr & Cb sample per
+ ///< 1x2 Y samples), big-endian
+ AV_PIX_FMT_YUV440P12LE, ///< planar YUV 4:4:0,24bpp, (1 Cr & Cb sample per
+ ///< 1x2 Y samples), little-endian
+ AV_PIX_FMT_YUV440P12BE, ///< planar YUV 4:4:0,24bpp, (1 Cr & Cb sample per
+ ///< 1x2 Y samples), big-endian
+ AV_PIX_FMT_AYUV64LE, ///< packed AYUV 4:4:4,64bpp (1 Cr & Cb sample per 1x1 Y
+ ///< & A samples), little-endian
+ AV_PIX_FMT_AYUV64BE, ///< packed AYUV 4:4:4,64bpp (1 Cr & Cb sample per 1x1 Y
+ ///< & A samples), big-endian
+
+ AV_PIX_FMT_VIDEOTOOLBOX, ///< hardware decoding through Videotoolbox
+
+ AV_PIX_FMT_P010LE, ///< like NV12, with 10bpp per component, data in the high
+ ///< bits, zeros in the low bits, little-endian
+ AV_PIX_FMT_P010BE, ///< like NV12, with 10bpp per component, data in the high
+ ///< bits, zeros in the low bits, big-endian
+
+ AV_PIX_FMT_GBRAP12BE, ///< planar GBR 4:4:4:4 48bpp, big-endian
+ AV_PIX_FMT_GBRAP12LE, ///< planar GBR 4:4:4:4 48bpp, little-endian
+
+ AV_PIX_FMT_GBRAP10BE, ///< planar GBR 4:4:4:4 40bpp, big-endian
+ AV_PIX_FMT_GBRAP10LE, ///< planar GBR 4:4:4:4 40bpp, little-endian
+
+ AV_PIX_FMT_MEDIACODEC, ///< hardware decoding through MediaCodec
+
+ AV_PIX_FMT_GRAY12BE, ///< Y , 12bpp, big-endian
+ AV_PIX_FMT_GRAY12LE, ///< Y , 12bpp, little-endian
+ AV_PIX_FMT_GRAY10BE, ///< Y , 10bpp, big-endian
+ AV_PIX_FMT_GRAY10LE, ///< Y , 10bpp, little-endian
+
+ AV_PIX_FMT_P016LE, ///< like NV12, with 16bpp per component, little-endian
+ AV_PIX_FMT_P016BE, ///< like NV12, with 16bpp per component, big-endian
+
+ /**
+ * Hardware surfaces for Direct3D11.
+ *
+ * This is preferred over the legacy AV_PIX_FMT_D3D11VA_VLD. The new D3D11
+ * hwaccel API and filtering support AV_PIX_FMT_D3D11 only.
+ *
+ * data[0] contains a ID3D11Texture2D pointer, and data[1] contains the
+ * texture array index of the frame as intptr_t if the ID3D11Texture2D is
+ * an array texture (or always 0 if it's a normal texture).
+ */
+ AV_PIX_FMT_D3D11,
+
+ AV_PIX_FMT_GRAY9BE, ///< Y , 9bpp, big-endian
+ AV_PIX_FMT_GRAY9LE, ///< Y , 9bpp, little-endian
+
+ AV_PIX_FMT_GBRPF32BE, ///< IEEE-754 single precision planar GBR 4:4:4, 96bpp,
+ ///< big-endian
+ AV_PIX_FMT_GBRPF32LE, ///< IEEE-754 single precision planar GBR 4:4:4, 96bpp,
+ ///< little-endian
+ AV_PIX_FMT_GBRAPF32BE, ///< IEEE-754 single precision planar GBRA 4:4:4:4,
+ ///< 128bpp, big-endian
+ AV_PIX_FMT_GBRAPF32LE, ///< IEEE-754 single precision planar GBRA 4:4:4:4,
+ ///< 128bpp, little-endian
+
+ /**
+ * DRM-managed buffers exposed through PRIME buffer sharing.
+ *
+ * data[0] points to an AVDRMFrameDescriptor.
+ */
+ AV_PIX_FMT_DRM_PRIME,
+ /**
+ * Hardware surfaces for OpenCL.
+ *
+ * data[i] contain 2D image objects (typed in C as cl_mem, used
+ * in OpenCL as image2d_t) for each plane of the surface.
+ */
+ AV_PIX_FMT_OPENCL,
+
+ AV_PIX_FMT_GRAY14BE, ///< Y , 14bpp, big-endian
+ AV_PIX_FMT_GRAY14LE, ///< Y , 14bpp, little-endian
+
+ AV_PIX_FMT_GRAYF32BE, ///< IEEE-754 single precision Y, 32bpp, big-endian
+ AV_PIX_FMT_GRAYF32LE, ///< IEEE-754 single precision Y, 32bpp, little-endian
+
+ AV_PIX_FMT_YUVA422P12BE, ///< planar YUV 4:2:2,24bpp, (1 Cr & Cb sample per
+ ///< 2x1 Y samples), 12b alpha, big-endian
+ AV_PIX_FMT_YUVA422P12LE, ///< planar YUV 4:2:2,24bpp, (1 Cr & Cb sample per
+ ///< 2x1 Y samples), 12b alpha, little-endian
+ AV_PIX_FMT_YUVA444P12BE, ///< planar YUV 4:4:4,36bpp, (1 Cr & Cb sample per
+ ///< 1x1 Y samples), 12b alpha, big-endian
+ AV_PIX_FMT_YUVA444P12LE, ///< planar YUV 4:4:4,36bpp, (1 Cr & Cb sample per
+ ///< 1x1 Y samples), 12b alpha, little-endian
+
+ AV_PIX_FMT_NV24, ///< planar YUV 4:4:4, 24bpp, 1 plane for Y and 1 plane for
+ ///< the UV components, which are interleaved (first byte U
+ ///< and the following byte V)
+ AV_PIX_FMT_NV42, ///< as above, but U and V bytes are swapped
+
+ /**
+ * Vulkan hardware images.
+ *
+ * data[0] points to an AVVkFrame
+ */
+ AV_PIX_FMT_VULKAN,
+
+ AV_PIX_FMT_Y210BE, ///< packed YUV 4:2:2 like YUYV422, 20bpp, data in the
+ ///< high bits, big-endian
+ AV_PIX_FMT_Y210LE, ///< packed YUV 4:2:2 like YUYV422, 20bpp, data in the
+ ///< high bits, little-endian
+
+ AV_PIX_FMT_X2RGB10LE, ///< packed RGB 10:10:10, 30bpp, (msb)2X 10R 10G
+ ///< 10B(lsb), little-endian, X=unused/undefined
+ AV_PIX_FMT_X2RGB10BE, ///< packed RGB 10:10:10, 30bpp, (msb)2X 10R 10G
+ ///< 10B(lsb), big-endian, X=unused/undefined
+ AV_PIX_FMT_X2BGR10LE, ///< packed BGR 10:10:10, 30bpp, (msb)2X 10B 10G
+ ///< 10R(lsb), little-endian, X=unused/undefined
+ AV_PIX_FMT_X2BGR10BE, ///< packed BGR 10:10:10, 30bpp, (msb)2X 10B 10G
+ ///< 10R(lsb), big-endian, X=unused/undefined
+
+ AV_PIX_FMT_P210BE, ///< interleaved chroma YUV 4:2:2, 20bpp, data in the high
+ ///< bits, big-endian
+ AV_PIX_FMT_P210LE, ///< interleaved chroma YUV 4:2:2, 20bpp, data in the high
+ ///< bits, little-endian
+
+ AV_PIX_FMT_P410BE, ///< interleaved chroma YUV 4:4:4, 30bpp, data in the high
+ ///< bits, big-endian
+ AV_PIX_FMT_P410LE, ///< interleaved chroma YUV 4:4:4, 30bpp, data in the high
+ ///< bits, little-endian
+
+ AV_PIX_FMT_P216BE, ///< interleaved chroma YUV 4:2:2, 32bpp, big-endian
+ AV_PIX_FMT_P216LE, ///< interleaved chroma YUV 4:2:2, 32bpp, little-endian
+
+ AV_PIX_FMT_P416BE, ///< interleaved chroma YUV 4:4:4, 48bpp, big-endian
+ AV_PIX_FMT_P416LE, ///< interleaved chroma YUV 4:4:4, 48bpp, little-endian
+
+ AV_PIX_FMT_VUYA, ///< packed VUYA 4:4:4, 32bpp, VUYAVUYA...
+
+ AV_PIX_FMT_RGBAF16BE, ///< IEEE-754 half precision packed RGBA 16:16:16:16,
+ ///< 64bpp, RGBARGBA..., big-endian
+ AV_PIX_FMT_RGBAF16LE, ///< IEEE-754 half precision packed RGBA 16:16:16:16,
+ ///< 64bpp, RGBARGBA..., little-endian
+
+ AV_PIX_FMT_VUYX, ///< packed VUYX 4:4:4, 32bpp, Variant of VUYA where alpha
+ ///< channel is left undefined
+
+ AV_PIX_FMT_P012LE, ///< like NV12, with 12bpp per component, data in the high
+ ///< bits, zeros in the low bits, little-endian
+ AV_PIX_FMT_P012BE, ///< like NV12, with 12bpp per component, data in the high
+ ///< bits, zeros in the low bits, big-endian
+
+ AV_PIX_FMT_Y212BE, ///< packed YUV 4:2:2 like YUYV422, 24bpp, data in the
+ ///< high bits, zeros in the low bits, big-endian
+ AV_PIX_FMT_Y212LE, ///< packed YUV 4:2:2 like YUYV422, 24bpp, data in the
+ ///< high bits, zeros in the low bits, little-endian
+
+ AV_PIX_FMT_XV30BE, ///< packed XVYU 4:4:4, 32bpp, (msb)2X 10V 10Y 10U(lsb),
+ ///< big-endian, variant of Y410 where alpha channel is
+ ///< left undefined
+ AV_PIX_FMT_XV30LE, ///< packed XVYU 4:4:4, 32bpp, (msb)2X 10V 10Y 10U(lsb),
+ ///< little-endian, variant of Y410 where alpha channel is
+ ///< left undefined
+
+ AV_PIX_FMT_XV36BE, ///< packed XVYU 4:4:4, 48bpp, data in the high bits,
+ ///< zeros in the low bits, big-endian, variant of Y412
+ ///< where alpha channel is left undefined
+ AV_PIX_FMT_XV36LE, ///< packed XVYU 4:4:4, 48bpp, data in the high bits,
+ ///< zeros in the low bits, little-endian, variant of Y412
+ ///< where alpha channel is left undefined
+
+ AV_PIX_FMT_RGBF32BE, ///< IEEE-754 single precision packed RGB 32:32:32,
+ ///< 96bpp, RGBRGB..., big-endian
+ AV_PIX_FMT_RGBF32LE, ///< IEEE-754 single precision packed RGB 32:32:32,
+ ///< 96bpp, RGBRGB..., little-endian
+
+ AV_PIX_FMT_RGBAF32BE, ///< IEEE-754 single precision packed RGBA 32:32:32:32,
+ ///< 128bpp, RGBARGBA..., big-endian
+ AV_PIX_FMT_RGBAF32LE, ///< IEEE-754 single precision packed RGBA 32:32:32:32,
+ ///< 128bpp, RGBARGBA..., little-endian
+
+ AV_PIX_FMT_P212BE, ///< interleaved chroma YUV 4:2:2, 24bpp, data in the high
+ ///< bits, big-endian
+ AV_PIX_FMT_P212LE, ///< interleaved chroma YUV 4:2:2, 24bpp, data in the high
+ ///< bits, little-endian
+
+ AV_PIX_FMT_P412BE, ///< interleaved chroma YUV 4:4:4, 36bpp, data in the high
+ ///< bits, big-endian
+ AV_PIX_FMT_P412LE, ///< interleaved chroma YUV 4:4:4, 36bpp, data in the high
+ ///< bits, little-endian
+
+ AV_PIX_FMT_GBRAP14BE, ///< planar GBR 4:4:4:4 56bpp, big-endian
+ AV_PIX_FMT_GBRAP14LE, ///< planar GBR 4:4:4:4 56bpp, little-endian
+
+ /**
+ * Hardware surfaces for Direct3D 12.
+ *
+ * data[0] points to an AVD3D12VAFrame
+ */
+ AV_PIX_FMT_D3D12,
+
+ AV_PIX_FMT_NB ///< number of pixel formats, DO NOT USE THIS if you want to
+ ///< link with shared libav* because the number of formats
+ ///< might differ between versions
+};
+
+#if AV_HAVE_BIGENDIAN
+# define AV_PIX_FMT_NE(be, le) AV_PIX_FMT_##be
+#else
+# define AV_PIX_FMT_NE(be, le) AV_PIX_FMT_##le
+#endif
+
+#define AV_PIX_FMT_RGB32 AV_PIX_FMT_NE(ARGB, BGRA)
+#define AV_PIX_FMT_RGB32_1 AV_PIX_FMT_NE(RGBA, ABGR)
+#define AV_PIX_FMT_BGR32 AV_PIX_FMT_NE(ABGR, RGBA)
+#define AV_PIX_FMT_BGR32_1 AV_PIX_FMT_NE(BGRA, ARGB)
+#define AV_PIX_FMT_0RGB32 AV_PIX_FMT_NE(0RGB, BGR0)
+#define AV_PIX_FMT_0BGR32 AV_PIX_FMT_NE(0BGR, RGB0)
+
+#define AV_PIX_FMT_GRAY9 AV_PIX_FMT_NE(GRAY9BE, GRAY9LE)
+#define AV_PIX_FMT_GRAY10 AV_PIX_FMT_NE(GRAY10BE, GRAY10LE)
+#define AV_PIX_FMT_GRAY12 AV_PIX_FMT_NE(GRAY12BE, GRAY12LE)
+#define AV_PIX_FMT_GRAY14 AV_PIX_FMT_NE(GRAY14BE, GRAY14LE)
+#define AV_PIX_FMT_GRAY16 AV_PIX_FMT_NE(GRAY16BE, GRAY16LE)
+#define AV_PIX_FMT_YA16 AV_PIX_FMT_NE(YA16BE, YA16LE)
+#define AV_PIX_FMT_RGB48 AV_PIX_FMT_NE(RGB48BE, RGB48LE)
+#define AV_PIX_FMT_RGB565 AV_PIX_FMT_NE(RGB565BE, RGB565LE)
+#define AV_PIX_FMT_RGB555 AV_PIX_FMT_NE(RGB555BE, RGB555LE)
+#define AV_PIX_FMT_RGB444 AV_PIX_FMT_NE(RGB444BE, RGB444LE)
+#define AV_PIX_FMT_RGBA64 AV_PIX_FMT_NE(RGBA64BE, RGBA64LE)
+#define AV_PIX_FMT_BGR48 AV_PIX_FMT_NE(BGR48BE, BGR48LE)
+#define AV_PIX_FMT_BGR565 AV_PIX_FMT_NE(BGR565BE, BGR565LE)
+#define AV_PIX_FMT_BGR555 AV_PIX_FMT_NE(BGR555BE, BGR555LE)
+#define AV_PIX_FMT_BGR444 AV_PIX_FMT_NE(BGR444BE, BGR444LE)
+#define AV_PIX_FMT_BGRA64 AV_PIX_FMT_NE(BGRA64BE, BGRA64LE)
+
+#define AV_PIX_FMT_YUV420P9 AV_PIX_FMT_NE(YUV420P9BE, YUV420P9LE)
+#define AV_PIX_FMT_YUV422P9 AV_PIX_FMT_NE(YUV422P9BE, YUV422P9LE)
+#define AV_PIX_FMT_YUV444P9 AV_PIX_FMT_NE(YUV444P9BE, YUV444P9LE)
+#define AV_PIX_FMT_YUV420P10 AV_PIX_FMT_NE(YUV420P10BE, YUV420P10LE)
+#define AV_PIX_FMT_YUV422P10 AV_PIX_FMT_NE(YUV422P10BE, YUV422P10LE)
+#define AV_PIX_FMT_YUV440P10 AV_PIX_FMT_NE(YUV440P10BE, YUV440P10LE)
+#define AV_PIX_FMT_YUV444P10 AV_PIX_FMT_NE(YUV444P10BE, YUV444P10LE)
+#define AV_PIX_FMT_YUV420P12 AV_PIX_FMT_NE(YUV420P12BE, YUV420P12LE)
+#define AV_PIX_FMT_YUV422P12 AV_PIX_FMT_NE(YUV422P12BE, YUV422P12LE)
+#define AV_PIX_FMT_YUV440P12 AV_PIX_FMT_NE(YUV440P12BE, YUV440P12LE)
+#define AV_PIX_FMT_YUV444P12 AV_PIX_FMT_NE(YUV444P12BE, YUV444P12LE)
+#define AV_PIX_FMT_YUV420P14 AV_PIX_FMT_NE(YUV420P14BE, YUV420P14LE)
+#define AV_PIX_FMT_YUV422P14 AV_PIX_FMT_NE(YUV422P14BE, YUV422P14LE)
+#define AV_PIX_FMT_YUV444P14 AV_PIX_FMT_NE(YUV444P14BE, YUV444P14LE)
+#define AV_PIX_FMT_YUV420P16 AV_PIX_FMT_NE(YUV420P16BE, YUV420P16LE)
+#define AV_PIX_FMT_YUV422P16 AV_PIX_FMT_NE(YUV422P16BE, YUV422P16LE)
+#define AV_PIX_FMT_YUV444P16 AV_PIX_FMT_NE(YUV444P16BE, YUV444P16LE)
+
+#define AV_PIX_FMT_GBRP9 AV_PIX_FMT_NE(GBRP9BE, GBRP9LE)
+#define AV_PIX_FMT_GBRP10 AV_PIX_FMT_NE(GBRP10BE, GBRP10LE)
+#define AV_PIX_FMT_GBRP12 AV_PIX_FMT_NE(GBRP12BE, GBRP12LE)
+#define AV_PIX_FMT_GBRP14 AV_PIX_FMT_NE(GBRP14BE, GBRP14LE)
+#define AV_PIX_FMT_GBRP16 AV_PIX_FMT_NE(GBRP16BE, GBRP16LE)
+#define AV_PIX_FMT_GBRAP10 AV_PIX_FMT_NE(GBRAP10BE, GBRAP10LE)
+#define AV_PIX_FMT_GBRAP12 AV_PIX_FMT_NE(GBRAP12BE, GBRAP12LE)
+#define AV_PIX_FMT_GBRAP14 AV_PIX_FMT_NE(GBRAP14BE, GBRAP14LE)
+#define AV_PIX_FMT_GBRAP16 AV_PIX_FMT_NE(GBRAP16BE, GBRAP16LE)
+
+#define AV_PIX_FMT_BAYER_BGGR16 AV_PIX_FMT_NE(BAYER_BGGR16BE, BAYER_BGGR16LE)
+#define AV_PIX_FMT_BAYER_RGGB16 AV_PIX_FMT_NE(BAYER_RGGB16BE, BAYER_RGGB16LE)
+#define AV_PIX_FMT_BAYER_GBRG16 AV_PIX_FMT_NE(BAYER_GBRG16BE, BAYER_GBRG16LE)
+#define AV_PIX_FMT_BAYER_GRBG16 AV_PIX_FMT_NE(BAYER_GRBG16BE, BAYER_GRBG16LE)
+
+#define AV_PIX_FMT_GBRPF32 AV_PIX_FMT_NE(GBRPF32BE, GBRPF32LE)
+#define AV_PIX_FMT_GBRAPF32 AV_PIX_FMT_NE(GBRAPF32BE, GBRAPF32LE)
+
+#define AV_PIX_FMT_GRAYF32 AV_PIX_FMT_NE(GRAYF32BE, GRAYF32LE)
+
+#define AV_PIX_FMT_YUVA420P9 AV_PIX_FMT_NE(YUVA420P9BE, YUVA420P9LE)
+#define AV_PIX_FMT_YUVA422P9 AV_PIX_FMT_NE(YUVA422P9BE, YUVA422P9LE)
+#define AV_PIX_FMT_YUVA444P9 AV_PIX_FMT_NE(YUVA444P9BE, YUVA444P9LE)
+#define AV_PIX_FMT_YUVA420P10 AV_PIX_FMT_NE(YUVA420P10BE, YUVA420P10LE)
+#define AV_PIX_FMT_YUVA422P10 AV_PIX_FMT_NE(YUVA422P10BE, YUVA422P10LE)
+#define AV_PIX_FMT_YUVA444P10 AV_PIX_FMT_NE(YUVA444P10BE, YUVA444P10LE)
+#define AV_PIX_FMT_YUVA422P12 AV_PIX_FMT_NE(YUVA422P12BE, YUVA422P12LE)
+#define AV_PIX_FMT_YUVA444P12 AV_PIX_FMT_NE(YUVA444P12BE, YUVA444P12LE)
+#define AV_PIX_FMT_YUVA420P16 AV_PIX_FMT_NE(YUVA420P16BE, YUVA420P16LE)
+#define AV_PIX_FMT_YUVA422P16 AV_PIX_FMT_NE(YUVA422P16BE, YUVA422P16LE)
+#define AV_PIX_FMT_YUVA444P16 AV_PIX_FMT_NE(YUVA444P16BE, YUVA444P16LE)
+
+#define AV_PIX_FMT_XYZ12 AV_PIX_FMT_NE(XYZ12BE, XYZ12LE)
+#define AV_PIX_FMT_NV20 AV_PIX_FMT_NE(NV20BE, NV20LE)
+#define AV_PIX_FMT_AYUV64 AV_PIX_FMT_NE(AYUV64BE, AYUV64LE)
+#define AV_PIX_FMT_P010 AV_PIX_FMT_NE(P010BE, P010LE)
+#define AV_PIX_FMT_P012 AV_PIX_FMT_NE(P012BE, P012LE)
+#define AV_PIX_FMT_P016 AV_PIX_FMT_NE(P016BE, P016LE)
+
+#define AV_PIX_FMT_Y210 AV_PIX_FMT_NE(Y210BE, Y210LE)
+#define AV_PIX_FMT_Y212 AV_PIX_FMT_NE(Y212BE, Y212LE)
+#define AV_PIX_FMT_XV30 AV_PIX_FMT_NE(XV30BE, XV30LE)
+#define AV_PIX_FMT_XV36 AV_PIX_FMT_NE(XV36BE, XV36LE)
+#define AV_PIX_FMT_X2RGB10 AV_PIX_FMT_NE(X2RGB10BE, X2RGB10LE)
+#define AV_PIX_FMT_X2BGR10 AV_PIX_FMT_NE(X2BGR10BE, X2BGR10LE)
+
+#define AV_PIX_FMT_P210 AV_PIX_FMT_NE(P210BE, P210LE)
+#define AV_PIX_FMT_P410 AV_PIX_FMT_NE(P410BE, P410LE)
+#define AV_PIX_FMT_P212 AV_PIX_FMT_NE(P212BE, P212LE)
+#define AV_PIX_FMT_P412 AV_PIX_FMT_NE(P412BE, P412LE)
+#define AV_PIX_FMT_P216 AV_PIX_FMT_NE(P216BE, P216LE)
+#define AV_PIX_FMT_P416 AV_PIX_FMT_NE(P416BE, P416LE)
+
+#define AV_PIX_FMT_RGBAF16 AV_PIX_FMT_NE(RGBAF16BE, RGBAF16LE)
+
+#define AV_PIX_FMT_RGBF32 AV_PIX_FMT_NE(RGBF32BE, RGBF32LE)
+#define AV_PIX_FMT_RGBAF32 AV_PIX_FMT_NE(RGBAF32BE, RGBAF32LE)
+
+/**
+ * Chromaticity coordinates of the source primaries.
+ * These values match the ones defined by ISO/IEC 23091-2_2019 subclause 8.1 and
+ * ITU-T H.273.
+ */
+enum AVColorPrimaries {
+ AVCOL_PRI_RESERVED0 = 0,
+ AVCOL_PRI_BT709 =
+ 1, ///< also ITU-R BT1361 / IEC 61966-2-4 / SMPTE RP 177 Annex B
+ AVCOL_PRI_UNSPECIFIED = 2,
+ AVCOL_PRI_RESERVED = 3,
+ AVCOL_PRI_BT470M =
+ 4, ///< also FCC Title 47 Code of Federal Regulations 73.682 (a)(20)
+
+ AVCOL_PRI_BT470BG = 5, ///< also ITU-R BT601-6 625 / ITU-R BT1358 625 / ITU-R
+ ///< BT1700 625 PAL & SECAM
+ AVCOL_PRI_SMPTE170M =
+ 6, ///< also ITU-R BT601-6 525 / ITU-R BT1358 525 / ITU-R BT1700 NTSC
+ AVCOL_PRI_SMPTE240M =
+ 7, ///< identical to above, also called "SMPTE C" even though it uses D65
+ AVCOL_PRI_FILM = 8, ///< colour filters using Illuminant C
+ AVCOL_PRI_BT2020 = 9, ///< ITU-R BT2020
+ AVCOL_PRI_SMPTE428 = 10, ///< SMPTE ST 428-1 (CIE 1931 XYZ)
+ AVCOL_PRI_SMPTEST428_1 = AVCOL_PRI_SMPTE428,
+ AVCOL_PRI_SMPTE431 = 11, ///< SMPTE ST 431-2 (2011) / DCI P3
+ AVCOL_PRI_SMPTE432 = 12, ///< SMPTE ST 432-1 (2010) / P3 D65 / Display P3
+ AVCOL_PRI_EBU3213 = 22, ///< EBU Tech. 3213-E (nothing there) / one of JEDEC
+ ///< P22 group phosphors
+ AVCOL_PRI_JEDEC_P22 = AVCOL_PRI_EBU3213,
+ AVCOL_PRI_NB ///< Not part of ABI
+};
+
+/**
+ * Color Transfer Characteristic.
+ * These values match the ones defined by ISO/IEC 23091-2_2019 subclause 8.2.
+ */
+enum AVColorTransferCharacteristic {
+ AVCOL_TRC_RESERVED0 = 0,
+ AVCOL_TRC_BT709 = 1, ///< also ITU-R BT1361
+ AVCOL_TRC_UNSPECIFIED = 2,
+ AVCOL_TRC_RESERVED = 3,
+ AVCOL_TRC_GAMMA22 = 4, ///< also ITU-R BT470M / ITU-R BT1700 625 PAL & SECAM
+ AVCOL_TRC_GAMMA28 = 5, ///< also ITU-R BT470BG
+ AVCOL_TRC_SMPTE170M = 6, ///< also ITU-R BT601-6 525 or 625 / ITU-R BT1358
+ ///< 525 or 625 / ITU-R BT1700 NTSC
+ AVCOL_TRC_SMPTE240M = 7,
+ AVCOL_TRC_LINEAR = 8, ///< "Linear transfer characteristics"
+ AVCOL_TRC_LOG = 9, ///< "Logarithmic transfer characteristic (100:1 range)"
+ AVCOL_TRC_LOG_SQRT =
+ 10, ///< "Logarithmic transfer characteristic (100 * Sqrt(10) : 1 range)"
+ AVCOL_TRC_IEC61966_2_4 = 11, ///< IEC 61966-2-4
+ AVCOL_TRC_BT1361_ECG = 12, ///< ITU-R BT1361 Extended Colour Gamut
+ AVCOL_TRC_IEC61966_2_1 = 13, ///< IEC 61966-2-1 (sRGB or sYCC)
+ AVCOL_TRC_BT2020_10 = 14, ///< ITU-R BT2020 for 10-bit system
+ AVCOL_TRC_BT2020_12 = 15, ///< ITU-R BT2020 for 12-bit system
+ AVCOL_TRC_SMPTE2084 =
+ 16, ///< SMPTE ST 2084 for 10-, 12-, 14- and 16-bit systems
+ AVCOL_TRC_SMPTEST2084 = AVCOL_TRC_SMPTE2084,
+ AVCOL_TRC_SMPTE428 = 17, ///< SMPTE ST 428-1
+ AVCOL_TRC_SMPTEST428_1 = AVCOL_TRC_SMPTE428,
+ AVCOL_TRC_ARIB_STD_B67 = 18, ///< ARIB STD-B67, known as "Hybrid log-gamma"
+ AVCOL_TRC_NB ///< Not part of ABI
+};
+
+/**
+ * YUV colorspace type.
+ * These values match the ones defined by ISO/IEC 23091-2_2019 subclause 8.3.
+ */
+enum AVColorSpace {
+ AVCOL_SPC_RGB = 0, ///< order of coefficients is actually GBR, also IEC
+ ///< 61966-2-1 (sRGB), YZX and ST 428-1
+ AVCOL_SPC_BT709 = 1, ///< also ITU-R BT1361 / IEC 61966-2-4 xvYCC709 /
+ ///< derived in SMPTE RP 177 Annex B
+ AVCOL_SPC_UNSPECIFIED = 2,
+ AVCOL_SPC_RESERVED =
+ 3, ///< reserved for future use by ITU-T and ISO/IEC just like 15-255 are
+ AVCOL_SPC_FCC =
+ 4, ///< FCC Title 47 Code of Federal Regulations 73.682 (a)(20)
+ AVCOL_SPC_BT470BG = 5, ///< also ITU-R BT601-6 625 / ITU-R BT1358 625 / ITU-R
+ ///< BT1700 625 PAL & SECAM / IEC 61966-2-4 xvYCC601
+ AVCOL_SPC_SMPTE170M =
+ 6, ///< also ITU-R BT601-6 525 / ITU-R BT1358 525 / ITU-R BT1700 NTSC /
+ ///< functionally identical to above
+ AVCOL_SPC_SMPTE240M =
+ 7, ///< derived from 170M primaries and D65 white point, 170M is derived
+ ///< from BT470 System M's primaries
+ AVCOL_SPC_YCGCO =
+ 8, ///< used by Dirac / VC-2 and H.264 FRext, see ITU-T SG16
+ AVCOL_SPC_YCOCG = AVCOL_SPC_YCGCO,
+ AVCOL_SPC_BT2020_NCL = 9, ///< ITU-R BT2020 non-constant luminance system
+ AVCOL_SPC_BT2020_CL = 10, ///< ITU-R BT2020 constant luminance system
+ AVCOL_SPC_SMPTE2085 = 11, ///< SMPTE 2085, Y'D'zD'x
+ AVCOL_SPC_CHROMA_DERIVED_NCL =
+ 12, ///< Chromaticity-derived non-constant luminance system
+ AVCOL_SPC_CHROMA_DERIVED_CL =
+ 13, ///< Chromaticity-derived constant luminance system
+ AVCOL_SPC_ICTCP = 14, ///< ITU-R BT.2100-0, ICtCp
+ AVCOL_SPC_IPT_C2 = 15, ///< SMPTE ST 2128, IPT-C2
+ AVCOL_SPC_YCGCO_RE = 16, ///< YCgCo-R, even addition of bits
+ AVCOL_SPC_YCGCO_RO = 17, ///< YCgCo-R, odd addition of bits
+ AVCOL_SPC_NB ///< Not part of ABI
+};
+
+/**
+ * Visual content value range.
+ *
+ * These values are based on definitions that can be found in multiple
+ * specifications, such as ITU-T BT.709 (3.4 - Quantization of RGB, luminance
+ * and colour-difference signals), ITU-T BT.2020 (Table 5 - Digital
+ * Representation) as well as ITU-T BT.2100 (Table 9 - Digital 10- and 12-bit
+ * integer representation). At the time of writing, the BT.2100 one is
+ * recommended, as it also defines the full range representation.
+ *
+ * Common definitions:
+ * - For RGB and luma planes such as Y in YCbCr and I in ICtCp,
+ * 'E' is the original value in range of 0.0 to 1.0.
+ * - For chroma planes such as Cb,Cr and Ct,Cp, 'E' is the original
+ * value in range of -0.5 to 0.5.
+ * - 'n' is the output bit depth.
+ * - For additional definitions such as rounding and clipping to valid n
+ * bit unsigned integer range, please refer to BT.2100 (Table 9).
+ */
+enum AVColorRange {
+ AVCOL_RANGE_UNSPECIFIED = 0,
+
+ /**
+ * Narrow or limited range content.
+ *
+ * - For luma planes:
+ *
+ * (219 * E + 16) * 2^(n-8)
+ *
+ * F.ex. the range of 16-235 for 8 bits
+ *
+ * - For chroma planes:
+ *
+ * (224 * E + 128) * 2^(n-8)
+ *
+ * F.ex. the range of 16-240 for 8 bits
+ */
+ AVCOL_RANGE_MPEG = 1,
+
+ /**
+ * Full range content.
+ *
+ * - For RGB and luma planes:
+ *
+ * (2^n - 1) * E
+ *
+ * F.ex. the range of 0-255 for 8 bits
+ *
+ * - For chroma planes:
+ *
+ * (2^n - 1) * E + 2^(n - 1)
+ *
+ * F.ex. the range of 1-255 for 8 bits
+ */
+ AVCOL_RANGE_JPEG = 2,
+ AVCOL_RANGE_NB ///< Not part of ABI
+};
+
+/**
+ * Location of chroma samples.
+ *
+ * Illustration showing the location of the first (top left) chroma sample of
+ *the image, the left shows only luma, the right shows the location of the
+ *chroma sample, the 2 could be imagined to overlay each other but are drawn
+ *separately due to limitations of ASCII
+ *
+ * 1st 2nd 1st 2nd horizontal luma sample positions
+ * v v v v
+ * ______ ______
+ *1st luma line > |X X ... |3 4 X ... X are luma samples,
+ * | |1 2 1-6 are possible chroma positions
+ *2nd luma line > |X X ... |5 6 X ... 0 is undefined/unknown position
+ */
+enum AVChromaLocation {
+ AVCHROMA_LOC_UNSPECIFIED = 0,
+ AVCHROMA_LOC_LEFT = 1, ///< MPEG-2/4 4:2:0, H.264 default for 4:2:0
+ AVCHROMA_LOC_CENTER = 2, ///< MPEG-1 4:2:0, JPEG 4:2:0, H.263 4:2:0
+ AVCHROMA_LOC_TOPLEFT =
+ 3, ///< ITU-R 601, SMPTE 274M 296M S314M(DV 4:1:1), mpeg2 4:2:2
+ AVCHROMA_LOC_TOP = 4,
+ AVCHROMA_LOC_BOTTOMLEFT = 5,
+ AVCHROMA_LOC_BOTTOM = 6,
+ AVCHROMA_LOC_NB ///< Not part of ABI
+};
+
+#endif /* AVUTIL_PIXFMT_H */
diff --git a/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/rational.h b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/rational.h
new file mode 100644
index 0000000000..13a3c41deb
--- /dev/null
+++ b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/rational.h
@@ -0,0 +1,226 @@
+/*
+ * rational numbers
+ * Copyright (c) 2003 Michael Niedermayer <michaelni@gmx.at>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * @ingroup lavu_math_rational
+ * Utilties for rational number calculation.
+ * @author Michael Niedermayer <michaelni@gmx.at>
+ */
+
+#ifndef AVUTIL_RATIONAL_H
+#define AVUTIL_RATIONAL_H
+
+#include <stdint.h>
+#include <limits.h>
+#include "attributes.h"
+
+/**
+ * @defgroup lavu_math_rational AVRational
+ * @ingroup lavu_math
+ * Rational number calculation.
+ *
+ * While rational numbers can be expressed as floating-point numbers, the
+ * conversion process is a lossy one, so are floating-point operations. On the
+ * other hand, the nature of FFmpeg demands highly accurate calculation of
+ * timestamps. This set of rational number utilities serves as a generic
+ * interface for manipulating rational numbers as pairs of numerators and
+ * denominators.
+ *
+ * Many of the functions that operate on AVRational's have the suffix `_q`, in
+ * reference to the mathematical symbol "ℚ" (Q) which denotes the set of all
+ * rational numbers.
+ *
+ * @{
+ */
+
+/**
+ * Rational number (pair of numerator and denominator).
+ */
+typedef struct AVRational {
+ int num; ///< Numerator
+ int den; ///< Denominator
+} AVRational;
+
+/**
+ * Create an AVRational.
+ *
+ * Useful for compilers that do not support compound literals.
+ *
+ * @note The return value is not reduced.
+ * @see av_reduce()
+ */
+static inline AVRational av_make_q(int num, int den) {
+ AVRational r = {num, den};
+ return r;
+}
+
+/**
+ * Compare two rationals.
+ *
+ * @param a First rational
+ * @param b Second rational
+ *
+ * @return One of the following values:
+ * - 0 if `a == b`
+ * - 1 if `a > b`
+ * - -1 if `a < b`
+ * - `INT_MIN` if one of the values is of the form `0 / 0`
+ */
+static inline int av_cmp_q(AVRational a, AVRational b) {
+ const int64_t tmp = a.num * (int64_t)b.den - b.num * (int64_t)a.den;
+
+ if (tmp)
+ return (int)((tmp ^ a.den ^ b.den) >> 63) | 1;
+ else if (b.den && a.den)
+ return 0;
+ else if (a.num && b.num)
+ return (a.num >> 31) - (b.num >> 31);
+ else
+ return INT_MIN;
+}
+
+/**
+ * Convert an AVRational to a `double`.
+ * @param a AVRational to convert
+ * @return `a` in floating-point form
+ * @see av_d2q()
+ */
+static inline double av_q2d(AVRational a) { return a.num / (double)a.den; }
+
+/**
+ * Reduce a fraction.
+ *
+ * This is useful for framerate calculations.
+ *
+ * @param[out] dst_num Destination numerator
+ * @param[out] dst_den Destination denominator
+ * @param[in] num Source numerator
+ * @param[in] den Source denominator
+ * @param[in] max Maximum allowed values for `dst_num` & `dst_den`
+ * @return 1 if the operation is exact, 0 otherwise
+ */
+int av_reduce(int* dst_num, int* dst_den, int64_t num, int64_t den,
+ int64_t max);
+
+/**
+ * Multiply two rationals.
+ * @param b First rational
+ * @param c Second rational
+ * @return b*c
+ */
+AVRational av_mul_q(AVRational b, AVRational c) av_const;
+
+/**
+ * Divide one rational by another.
+ * @param b First rational
+ * @param c Second rational
+ * @return b/c
+ */
+AVRational av_div_q(AVRational b, AVRational c) av_const;
+
+/**
+ * Add two rationals.
+ * @param b First rational
+ * @param c Second rational
+ * @return b+c
+ */
+AVRational av_add_q(AVRational b, AVRational c) av_const;
+
+/**
+ * Subtract one rational from another.
+ * @param b First rational
+ * @param c Second rational
+ * @return b-c
+ */
+AVRational av_sub_q(AVRational b, AVRational c) av_const;
+
+/**
+ * Invert a rational.
+ * @param q value
+ * @return 1 / q
+ */
+static av_always_inline AVRational av_inv_q(AVRational q) {
+ AVRational r = {q.den, q.num};
+ return r;
+}
+
+/**
+ * Convert a double precision floating point number to a rational.
+ *
+ * In case of infinity, the returned value is expressed as `{1, 0}` or
+ * `{-1, 0}` depending on the sign.
+ *
+ * In general rational numbers with |num| <= 1<<26 && |den| <= 1<<26
+ * can be recovered exactly from their double representation.
+ * (no exceptions were found within 1B random ones)
+ *
+ * @param d `double` to convert
+ * @param max Maximum allowed numerator and denominator
+ * @return `d` in AVRational form
+ * @see av_q2d()
+ */
+AVRational av_d2q(double d, int max) av_const;
+
+/**
+ * Find which of the two rationals is closer to another rational.
+ *
+ * @param q Rational to be compared against
+ * @param q1 Rational to be tested
+ * @param q2 Rational to be tested
+ * @return One of the following values:
+ * - 1 if `q1` is nearer to `q` than `q2`
+ * - -1 if `q2` is nearer to `q` than `q1`
+ * - 0 if they have the same distance
+ */
+int av_nearer_q(AVRational q, AVRational q1, AVRational q2);
+
+/**
+ * Find the value in a list of rationals nearest a given reference rational.
+ *
+ * @param q Reference rational
+ * @param q_list Array of rationals terminated by `{0, 0}`
+ * @return Index of the nearest value found in the array
+ */
+int av_find_nearest_q_idx(AVRational q, const AVRational* q_list);
+
+/**
+ * Convert an AVRational to a IEEE 32-bit `float` expressed in fixed-point
+ * format.
+ *
+ * @param q Rational to be converted
+ * @return Equivalent floating-point value, expressed as an unsigned 32-bit
+ * integer.
+ * @note The returned value is platform-indepedant.
+ */
+uint32_t av_q2intfloat(AVRational q);
+
+/**
+ * Return the best rational so that a and b are multiple of it.
+ * If the resulting denominator is larger than max_den, return def.
+ */
+AVRational av_gcd_q(AVRational a, AVRational b, int max_den, AVRational def);
+
+/**
+ * @}
+ */
+
+#endif /* AVUTIL_RATIONAL_H */
diff --git a/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/samplefmt.h b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/samplefmt.h
new file mode 100644
index 0000000000..6ba22b0b79
--- /dev/null
+++ b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/samplefmt.h
@@ -0,0 +1,275 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVUTIL_SAMPLEFMT_H
+#define AVUTIL_SAMPLEFMT_H
+
+#include <stdint.h>
+
+/**
+ * @addtogroup lavu_audio
+ * @{
+ *
+ * @defgroup lavu_sampfmts Audio sample formats
+ *
+ * Audio sample format enumeration and related convenience functions.
+ * @{
+ */
+
+/**
+ * Audio sample formats
+ *
+ * - The data described by the sample format is always in native-endian order.
+ * Sample values can be expressed by native C types, hence the lack of a
+ * signed 24-bit sample format even though it is a common raw audio data format.
+ *
+ * - The floating-point formats are based on full volume being in the range
+ * [-1.0, 1.0]. Any values outside this range are beyond full volume level.
+ *
+ * - The data layout as used in av_samples_fill_arrays() and elsewhere in FFmpeg
+ * (such as AVFrame in libavcodec) is as follows:
+ *
+ * @par
+ * For planar sample formats, each audio channel is in a separate data plane,
+ * and linesize is the buffer size, in bytes, for a single plane. All data
+ * planes must be the same size. For packed sample formats, only the first data
+ * plane is used, and samples for each channel are interleaved. In this case,
+ * linesize is the buffer size, in bytes, for the 1 plane.
+ *
+ */
+enum AVSampleFormat {
+ AV_SAMPLE_FMT_NONE = -1,
+ AV_SAMPLE_FMT_U8, ///< unsigned 8 bits
+ AV_SAMPLE_FMT_S16, ///< signed 16 bits
+ AV_SAMPLE_FMT_S32, ///< signed 32 bits
+ AV_SAMPLE_FMT_FLT, ///< float
+ AV_SAMPLE_FMT_DBL, ///< double
+
+ AV_SAMPLE_FMT_U8P, ///< unsigned 8 bits, planar
+ AV_SAMPLE_FMT_S16P, ///< signed 16 bits, planar
+ AV_SAMPLE_FMT_S32P, ///< signed 32 bits, planar
+ AV_SAMPLE_FMT_FLTP, ///< float, planar
+ AV_SAMPLE_FMT_DBLP, ///< double, planar
+ AV_SAMPLE_FMT_S64, ///< signed 64 bits
+ AV_SAMPLE_FMT_S64P, ///< signed 64 bits, planar
+
+ AV_SAMPLE_FMT_NB ///< Number of sample formats. DO NOT USE if linking
+ ///< dynamically
+};
+
+/**
+ * Return the name of sample_fmt, or NULL if sample_fmt is not
+ * recognized.
+ */
+const char* av_get_sample_fmt_name(enum AVSampleFormat sample_fmt);
+
+/**
+ * Return a sample format corresponding to name, or AV_SAMPLE_FMT_NONE
+ * on error.
+ */
+enum AVSampleFormat av_get_sample_fmt(const char* name);
+
+/**
+ * Return the planar<->packed alternative form of the given sample format, or
+ * AV_SAMPLE_FMT_NONE on error. If the passed sample_fmt is already in the
+ * requested planar/packed format, the format returned is the same as the
+ * input.
+ */
+enum AVSampleFormat av_get_alt_sample_fmt(enum AVSampleFormat sample_fmt,
+ int planar);
+
+/**
+ * Get the packed alternative form of the given sample format.
+ *
+ * If the passed sample_fmt is already in packed format, the format returned is
+ * the same as the input.
+ *
+ * @return the packed alternative form of the given sample format or
+ AV_SAMPLE_FMT_NONE on error.
+ */
+enum AVSampleFormat av_get_packed_sample_fmt(enum AVSampleFormat sample_fmt);
+
+/**
+ * Get the planar alternative form of the given sample format.
+ *
+ * If the passed sample_fmt is already in planar format, the format returned is
+ * the same as the input.
+ *
+ * @return the planar alternative form of the given sample format or
+ AV_SAMPLE_FMT_NONE on error.
+ */
+enum AVSampleFormat av_get_planar_sample_fmt(enum AVSampleFormat sample_fmt);
+
+/**
+ * Generate a string corresponding to the sample format with
+ * sample_fmt, or a header if sample_fmt is negative.
+ *
+ * @param buf the buffer where to write the string
+ * @param buf_size the size of buf
+ * @param sample_fmt the number of the sample format to print the
+ * corresponding info string, or a negative value to print the
+ * corresponding header.
+ * @return the pointer to the filled buffer or NULL if sample_fmt is
+ * unknown or in case of other errors
+ */
+char* av_get_sample_fmt_string(char* buf, int buf_size,
+ enum AVSampleFormat sample_fmt);
+
+/**
+ * Return number of bytes per sample.
+ *
+ * @param sample_fmt the sample format
+ * @return number of bytes per sample or zero if unknown for the given
+ * sample format
+ */
+int av_get_bytes_per_sample(enum AVSampleFormat sample_fmt);
+
+/**
+ * Check if the sample format is planar.
+ *
+ * @param sample_fmt the sample format to inspect
+ * @return 1 if the sample format is planar, 0 if it is interleaved
+ */
+int av_sample_fmt_is_planar(enum AVSampleFormat sample_fmt);
+
+/**
+ * Get the required buffer size for the given audio parameters.
+ *
+ * @param[out] linesize calculated linesize, may be NULL
+ * @param nb_channels the number of channels
+ * @param nb_samples the number of samples in a single channel
+ * @param sample_fmt the sample format
+ * @param align buffer size alignment (0 = default, 1 = no alignment)
+ * @return required buffer size, or negative error code on failure
+ */
+int av_samples_get_buffer_size(int* linesize, int nb_channels, int nb_samples,
+ enum AVSampleFormat sample_fmt, int align);
+
+/**
+ * @}
+ *
+ * @defgroup lavu_sampmanip Samples manipulation
+ *
+ * Functions that manipulate audio samples
+ * @{
+ */
+
+/**
+ * Fill plane data pointers and linesize for samples with sample
+ * format sample_fmt.
+ *
+ * The audio_data array is filled with the pointers to the samples data planes:
+ * for planar, set the start point of each channel's data within the buffer,
+ * for packed, set the start point of the entire buffer only.
+ *
+ * The value pointed to by linesize is set to the aligned size of each
+ * channel's data buffer for planar layout, or to the aligned size of the
+ * buffer for all channels for packed layout.
+ *
+ * The buffer in buf must be big enough to contain all the samples
+ * (use av_samples_get_buffer_size() to compute its minimum size),
+ * otherwise the audio_data pointers will point to invalid data.
+ *
+ * @see enum AVSampleFormat
+ * The documentation for AVSampleFormat describes the data layout.
+ *
+ * @param[out] audio_data array to be filled with the pointer for each channel
+ * @param[out] linesize calculated linesize, may be NULL
+ * @param buf the pointer to a buffer containing the samples
+ * @param nb_channels the number of channels
+ * @param nb_samples the number of samples in a single channel
+ * @param sample_fmt the sample format
+ * @param align buffer size alignment (0 = default, 1 = no alignment)
+ * @return minimum size in bytes required for the buffer on
+ * success, or a negative error code on failure
+ */
+int av_samples_fill_arrays(uint8_t** audio_data, int* linesize,
+ const uint8_t* buf, int nb_channels, int nb_samples,
+ enum AVSampleFormat sample_fmt, int align);
+
+/**
+ * Allocate a samples buffer for nb_samples samples, and fill data pointers and
+ * linesize accordingly.
+ * The allocated samples buffer can be freed by using av_freep(&audio_data[0])
+ * Allocated data will be initialized to silence.
+ *
+ * @see enum AVSampleFormat
+ * The documentation for AVSampleFormat describes the data layout.
+ *
+ * @param[out] audio_data array to be filled with the pointer for each channel
+ * @param[out] linesize aligned size for audio buffer(s), may be NULL
+ * @param nb_channels number of audio channels
+ * @param nb_samples number of samples per channel
+ * @param sample_fmt the sample format
+ * @param align buffer size alignment (0 = default, 1 = no alignment)
+ * @return >=0 on success or a negative error code on failure
+ * @todo return the size of the allocated buffer in case of success at the next
+ * bump
+ * @see av_samples_fill_arrays()
+ * @see av_samples_alloc_array_and_samples()
+ */
+int av_samples_alloc(uint8_t** audio_data, int* linesize, int nb_channels,
+ int nb_samples, enum AVSampleFormat sample_fmt, int align);
+
+/**
+ * Allocate a data pointers array, samples buffer for nb_samples
+ * samples, and fill data pointers and linesize accordingly.
+ *
+ * This is the same as av_samples_alloc(), but also allocates the data
+ * pointers array.
+ *
+ * @see av_samples_alloc()
+ */
+int av_samples_alloc_array_and_samples(uint8_t*** audio_data, int* linesize,
+ int nb_channels, int nb_samples,
+ enum AVSampleFormat sample_fmt,
+ int align);
+
+/**
+ * Copy samples from src to dst.
+ *
+ * @param dst destination array of pointers to data planes
+ * @param src source array of pointers to data planes
+ * @param dst_offset offset in samples at which the data will be written to dst
+ * @param src_offset offset in samples at which the data will be read from src
+ * @param nb_samples number of samples to be copied
+ * @param nb_channels number of audio channels
+ * @param sample_fmt audio sample format
+ */
+int av_samples_copy(uint8_t* const* dst, uint8_t* const* src, int dst_offset,
+ int src_offset, int nb_samples, int nb_channels,
+ enum AVSampleFormat sample_fmt);
+
+/**
+ * Fill an audio buffer with silence.
+ *
+ * @param audio_data array of pointers to data planes
+ * @param offset offset in samples at which to start filling
+ * @param nb_samples number of samples to fill
+ * @param nb_channels number of audio channels
+ * @param sample_fmt audio sample format
+ */
+int av_samples_set_silence(uint8_t* const* audio_data, int offset,
+ int nb_samples, int nb_channels,
+ enum AVSampleFormat sample_fmt);
+
+/**
+ * @}
+ * @}
+ */
+#endif /* AVUTIL_SAMPLEFMT_H */
diff --git a/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/version.h b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/version.h
new file mode 100644
index 0000000000..8826d0da6c
--- /dev/null
+++ b/dom/media/platforms/ffmpeg/ffmpeg61/include/libavutil/version.h
@@ -0,0 +1,121 @@
+/*
+ * copyright (c) 2003 Fabrice Bellard
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * @ingroup lavu
+ * Libavutil version macros
+ */
+
+#ifndef AVUTIL_VERSION_H
+#define AVUTIL_VERSION_H
+
+#include "macros.h"
+
+/**
+ * @addtogroup version_utils
+ *
+ * Useful to check and match library version in order to maintain
+ * backward compatibility.
+ *
+ * The FFmpeg libraries follow a versioning sheme very similar to
+ * Semantic Versioning (http://semver.org/)
+ * The difference is that the component called PATCH is called MICRO in FFmpeg
+ * and its value is reset to 100 instead of 0 to keep it above or equal to 100.
+ * Also we do not increase MICRO for every bugfix or change in git master.
+ *
+ * Prior to FFmpeg 3.2 point releases did not change any lib version number to
+ * avoid aliassing different git master checkouts.
+ * Starting with FFmpeg 3.2, the released library versions will occupy
+ * a separate MAJOR.MINOR that is not used on the master development branch.
+ * That is if we branch a release of master 55.10.123 we will bump to 55.11.100
+ * for the release and master will continue at 55.12.100 after it. Each new
+ * point release will then bump the MICRO improving the usefulness of the lib
+ * versions.
+ *
+ * @{
+ */
+
+#define AV_VERSION_INT(a, b, c) ((a) << 16 | (b) << 8 | (c))
+#define AV_VERSION_DOT(a, b, c) a##.##b##.##c
+#define AV_VERSION(a, b, c) AV_VERSION_DOT(a, b, c)
+
+/**
+ * Extract version components from the full ::AV_VERSION_INT int as returned
+ * by functions like ::avformat_version() and ::avcodec_version()
+ */
+#define AV_VERSION_MAJOR(a) ((a) >> 16)
+#define AV_VERSION_MINOR(a) (((a) & 0x00FF00) >> 8)
+#define AV_VERSION_MICRO(a) ((a) & 0xFF)
+
+/**
+ * @}
+ */
+
+/**
+ * @defgroup lavu_ver Version and Build diagnostics
+ *
+ * Macros and function useful to check at compiletime and at runtime
+ * which version of libavutil is in use.
+ *
+ * @{
+ */
+
+#define LIBAVUTIL_VERSION_MAJOR 59
+#define LIBAVUTIL_VERSION_MINOR 13
+#define LIBAVUTIL_VERSION_MICRO 100
+
+#define LIBAVUTIL_VERSION_INT \
+ AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, LIBAVUTIL_VERSION_MINOR, \
+ LIBAVUTIL_VERSION_MICRO)
+#define LIBAVUTIL_VERSION \
+ AV_VERSION(LIBAVUTIL_VERSION_MAJOR, LIBAVUTIL_VERSION_MINOR, \
+ LIBAVUTIL_VERSION_MICRO)
+#define LIBAVUTIL_BUILD LIBAVUTIL_VERSION_INT
+
+#define LIBAVUTIL_IDENT "Lavu" AV_STRINGIFY(LIBAVUTIL_VERSION)
+
+/**
+ * @defgroup lavu_depr_guards Deprecation Guards
+ * FF_API_* defines may be placed below to indicate public API that will be
+ * dropped at a future version bump. The defines themselves are not part of
+ * the public API and may change, break or disappear at any time.
+ *
+ * @note, when bumping the major version it is recommended to manually
+ * disable each FF_API_* in its own commit instead of disabling them all
+ * at once through the bump. This improves the git bisect-ability of the change.
+ *
+ * @{
+ */
+
+#define FF_API_HDR_VIVID_THREE_SPLINE (LIBAVUTIL_VERSION_MAJOR < 60)
+#define FF_API_FRAME_PKT (LIBAVUTIL_VERSION_MAJOR < 60)
+#define FF_API_INTERLACED_FRAME (LIBAVUTIL_VERSION_MAJOR < 60)
+#define FF_API_FRAME_KEY (LIBAVUTIL_VERSION_MAJOR < 60)
+#define FF_API_PALETTE_HAS_CHANGED (LIBAVUTIL_VERSION_MAJOR < 60)
+#define FF_API_VULKAN_CONTIGUOUS_MEMORY (LIBAVUTIL_VERSION_MAJOR < 60)
+#define FF_API_H274_FILM_GRAIN_VCS (LIBAVUTIL_VERSION_MAJOR < 60)
+
+/**
+ * @}
+ * @}
+ */
+
+#endif /* AVUTIL_VERSION_H */
diff --git a/dom/media/platforms/ffmpeg/ffmpeg61/moz.build b/dom/media/platforms/ffmpeg/ffmpeg61/moz.build
new file mode 100644
index 0000000000..d598ae0017
--- /dev/null
+++ b/dom/media/platforms/ffmpeg/ffmpeg61/moz.build
@@ -0,0 +1,47 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+UNIFIED_SOURCES += [
+ "../FFmpegAudioDecoder.cpp",
+ "../FFmpegAudioEncoder.cpp",
+ "../FFmpegDataDecoder.cpp",
+ "../FFmpegDataEncoder.cpp",
+ "../FFmpegDecoderModule.cpp",
+ "../FFmpegEncoderModule.cpp",
+ "../FFmpegVideoDecoder.cpp",
+ "../FFmpegVideoEncoder.cpp",
+]
+LOCAL_INCLUDES += [
+ "..",
+ "/media/mozva",
+ "include",
+]
+
+if CONFIG["CC_TYPE"] in ("clang", "gcc"):
+ CXXFLAGS += ["-Wno-deprecated-declarations"]
+if CONFIG["CC_TYPE"] == "clang":
+ CXXFLAGS += [
+ "-Wno-unknown-attributes",
+ ]
+if CONFIG["CC_TYPE"] == "gcc":
+ CXXFLAGS += [
+ "-Wno-attributes",
+ ]
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]
+if CONFIG["MOZ_ENABLE_VAAPI"] or CONFIG["MOZ_ENABLE_V4L2"]:
+ UNIFIED_SOURCES += ["../FFmpegVideoFramePool.cpp"]
+ LOCAL_INCLUDES += ["/third_party/drm/drm/include/libdrm/"]
+ USE_LIBS += ["mozva"]
+ DEFINES["MOZ_USE_HWDECODE"] = 1
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+LOCAL_INCLUDES += [
+ "/media/libyuv/libyuv/include",
+]
+
+FINAL_LIBRARY = "xul"
diff --git a/dom/media/platforms/ffmpeg/ffvpx/FFVPXRuntimeLinker.cpp b/dom/media/platforms/ffmpeg/ffvpx/FFVPXRuntimeLinker.cpp
index dfc8244f1d..c0a6e01f98 100644
--- a/dom/media/platforms/ffmpeg/ffvpx/FFVPXRuntimeLinker.cpp
+++ b/dom/media/platforms/ffmpeg/ffvpx/FFVPXRuntimeLinker.cpp
@@ -7,14 +7,10 @@
#include "FFVPXRuntimeLinker.h"
#include "FFmpegLibWrapper.h"
#include "FFmpegLog.h"
-#include "BinaryPath.h"
#include "mozilla/FileUtils.h"
#include "nsLocalFile.h"
-#include "prmem.h"
+#include "nsXPCOMPrivate.h"
#include "prlink.h"
-#ifdef XP_WIN
-# include <windows.h>
-#endif
namespace mozilla {
@@ -84,29 +80,37 @@ bool FFVPXRuntimeLinker::Init() {
sFFVPXLib.LinkVAAPILibs();
#endif
- nsCOMPtr<nsIFile> libFile;
- if (NS_FAILED(mozilla::BinaryPath::GetFile(getter_AddRefs(libFile)))) {
+#ifdef XP_WIN
+ PathString path =
+ GetLibraryFilePathname(LXUL_DLL, (PRFuncPtr)&FFVPXRuntimeLinker::Init);
+#else
+ PathString path =
+ GetLibraryFilePathname(XUL_DLL, (PRFuncPtr)&FFVPXRuntimeLinker::Init);
+#endif
+ if (path.IsEmpty()) {
+ return false;
+ }
+ nsCOMPtr<nsIFile> libFile = new nsLocalFile(path);
+ if (libFile->NativePath().IsEmpty()) {
return false;
}
-#ifdef XP_DARWIN
- if (!XRE_IsParentProcess() &&
- (XRE_GetChildProcBinPathType(XRE_GetProcessType()) ==
- BinPathType::PluginContainer)) {
- // On macOS, PluginContainer processes have their binary in a
- // plugin-container.app/Content/MacOS/ directory.
- nsCOMPtr<nsIFile> parentDir1, parentDir2;
- if (NS_FAILED(libFile->GetParent(getter_AddRefs(parentDir1)))) {
- return false;
- }
- if (NS_FAILED(parentDir1->GetParent(getter_AddRefs(parentDir2)))) {
- return false;
- }
- if (NS_FAILED(parentDir2->GetParent(getter_AddRefs(libFile)))) {
+ if (getenv("MOZ_RUN_GTEST")
+#ifdef FUZZING
+ || getenv("FUZZER")
+#endif
+ ) {
+ // The condition above is the same as in
+ // xpcom/glue/standalone/nsXPCOMGlue.cpp. This means we can't reach here
+ // without the gtest libxul being loaded. In turn, that means the path to
+ // libxul leads to a subdirectory of where the libmozav* libraries are, so
+ // we get the parent.
+ nsCOMPtr<nsIFile> parent;
+ if (NS_FAILED(libFile->GetParent(getter_AddRefs(parent)))) {
return false;
}
+ libFile = parent;
}
-#endif
if (NS_FAILED(libFile->SetNativeLeafName(MOZ_DLL_PREFIX
"mozavutil" MOZ_DLL_SUFFIX ""_ns))) {
diff --git a/dom/media/platforms/ffmpeg/ffvpx/moz.build b/dom/media/platforms/ffmpeg/ffvpx/moz.build
index bc72b6d1a7..a9236b25eb 100644
--- a/dom/media/platforms/ffmpeg/ffvpx/moz.build
+++ b/dom/media/platforms/ffmpeg/ffvpx/moz.build
@@ -25,7 +25,7 @@ SOURCES += [
]
LOCAL_INCLUDES += [
"..",
- "../ffmpeg60/include",
+ "../ffmpeg61/include",
"/media/mozva",
]
diff --git a/dom/media/platforms/ffmpeg/moz.build b/dom/media/platforms/ffmpeg/moz.build
index ac78eee289..ce7c06b9a6 100644
--- a/dom/media/platforms/ffmpeg/moz.build
+++ b/dom/media/platforms/ffmpeg/moz.build
@@ -16,6 +16,7 @@ DIRS += [
"ffmpeg58",
"ffmpeg59",
"ffmpeg60",
+ "ffmpeg61",
]
UNIFIED_SOURCES += ["FFmpegRuntimeLinker.cpp"]
diff --git a/dom/media/platforms/omx/OmxDataDecoder.cpp b/dom/media/platforms/omx/OmxDataDecoder.cpp
index e830f77dd2..6c40ca4910 100644
--- a/dom/media/platforms/omx/OmxDataDecoder.cpp
+++ b/dom/media/platforms/omx/OmxDataDecoder.cpp
@@ -521,14 +521,14 @@ nsTArray<RefPtr<OmxPromiseLayer::BufferData>>* OmxDataDecoder::GetBuffers(
return &mOutPortBuffers;
}
-void OmxDataDecoder::ResolveInitPromise(const char* aMethodName) {
+void OmxDataDecoder::ResolveInitPromise(StaticString aMethodName) {
MOZ_ASSERT(mOmxTaskQueue->IsCurrentThreadIn());
- LOG("called from %s", aMethodName);
+ LOG("called from %s", aMethodName.get());
mInitPromise.ResolveIfExists(mTrackInfo->GetType(), aMethodName);
}
void OmxDataDecoder::RejectInitPromise(MediaResult aError,
- const char* aMethodName) {
+ StaticString aMethodName) {
MOZ_ASSERT(mOmxTaskQueue->IsCurrentThreadIn());
mInitPromise.RejectIfExists(aError, aMethodName);
}
diff --git a/dom/media/platforms/omx/OmxDataDecoder.h b/dom/media/platforms/omx/OmxDataDecoder.h
index 69c388ecee..a40aafea36 100644
--- a/dom/media/platforms/omx/OmxDataDecoder.h
+++ b/dom/media/platforms/omx/OmxDataDecoder.h
@@ -88,9 +88,9 @@ class OmxDataDecoder final : public MediaDataDecoder,
protected:
void InitializationTask();
- void ResolveInitPromise(const char* aMethodName);
+ void ResolveInitPromise(StaticString aMethodName);
- void RejectInitPromise(MediaResult aError, const char* aMethodName);
+ void RejectInitPromise(MediaResult aError, StaticString aMethodName);
void OmxStateRunner();
diff --git a/dom/media/platforms/wmf/MFTEncoder.cpp b/dom/media/platforms/wmf/MFTEncoder.cpp
index 410da2733c..424ba7055b 100644
--- a/dom/media/platforms/wmf/MFTEncoder.cpp
+++ b/dom/media/platforms/wmf/MFTEncoder.cpp
@@ -10,6 +10,7 @@
#include "mozilla/StaticPrefs_media.h"
#include "mozilla/mscom/Utils.h"
#include "WMFUtils.h"
+#include <comdef.h>
// Missing from MinGW.
#ifndef CODECAPI_AVEncAdaptiveMode
@@ -231,6 +232,7 @@ HRESULT MFTEncoder::Create(const GUID& aSubtype) {
RefPtr<IMFActivate> factory = CreateFactory(aSubtype);
if (!factory) {
+ MFT_ENC_LOGE("CreateFactory error");
return E_FAIL;
}
@@ -238,12 +240,18 @@ HRESULT MFTEncoder::Create(const GUID& aSubtype) {
RefPtr<IMFTransform> encoder;
HRESULT hr = factory->ActivateObject(
IID_PPV_ARGS(static_cast<IMFTransform**>(getter_AddRefs(encoder))));
- NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ if (FAILED(hr)) {
+ _com_error error(hr);
+ MFT_ENC_LOGE("MFTEncoder::Create: error = 0x%lX, %ls", hr,
+ error.ErrorMessage());
+ return hr;
+ }
RefPtr<ICodecAPI> config;
// Avoid IID_PPV_ARGS() here for MingGW fails to declare UUID for ICodecAPI.
hr = encoder->QueryInterface(IID_ICodecAPI, getter_AddRefs(config));
if (FAILED(hr)) {
+ MFT_ENC_LOGE("QueryInterface IID_ICodecAPI error");
encoder = nullptr;
factory->ShutdownObject();
return hr;
@@ -276,7 +284,12 @@ MFTEncoder::SetMediaTypes(IMFMediaType* aInputType, IMFMediaType* aOutputType) {
MOZ_ASSERT(aInputType && aOutputType);
AsyncMFTResult asyncMFT = AttemptEnableAsync();
- NS_ENSURE_TRUE(asyncMFT.isOk(), asyncMFT.unwrapErr());
+ if (asyncMFT.isErr()) {
+ HRESULT hr = asyncMFT.inspectErr();
+ _com_error error(hr);
+ MFT_ENC_LOGE("AttemptEnableAsync error: %ls", error.ErrorMessage());
+ return asyncMFT.inspectErr();
+ }
HRESULT hr = GetStreamIDs();
NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
@@ -325,6 +338,7 @@ MFTEncoder::AsyncMFTResult MFTEncoder::AttemptEnableAsync() {
IMFAttributes* pAttributes = nullptr;
HRESULT hr = mEncoder->GetAttributes(&pAttributes);
if (FAILED(hr)) {
+ MFT_ENC_LOGE("Encoder->GetAttribute error");
return AsyncMFTResult(hr);
}
@@ -337,6 +351,10 @@ MFTEncoder::AsyncMFTResult MFTEncoder::AttemptEnableAsync() {
}
pAttributes->Release();
+ if (FAILED(hr)) {
+ MFT_ENC_LOGE("Setting async unlock");
+ }
+
return SUCCEEDED(hr) ? AsyncMFTResult(async) : AsyncMFTResult(hr);
}
diff --git a/dom/media/platforms/wmf/WMF.h b/dom/media/platforms/wmf/WMF.h
index 740442ceda..86afcb8e5c 100644
--- a/dom/media/platforms/wmf/WMF.h
+++ b/dom/media/platforms/wmf/WMF.h
@@ -23,6 +23,7 @@
#include <codecapi.h>
#include "mozilla/Atomics.h"
+#include "mozilla/AppShutdown.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/StaticMutex.h"
#include "nsThreadUtils.h"
@@ -74,7 +75,8 @@ class MediaFoundationInitializer final {
if (sIsShutdown) {
return false;
}
- return Get()->mHasInitialized;
+ auto* rv = Get();
+ return rv ? rv->mHasInitialized : false;
}
private:
@@ -82,17 +84,36 @@ class MediaFoundationInitializer final {
{
StaticMutexAutoLock lock(sCreateMutex);
if (!sInitializer) {
+ // Already in shutdown.
+ if (AppShutdown::GetCurrentShutdownPhase() !=
+ ShutdownPhase::NotInShutdown) {
+ sIsShutdown = true;
+ return nullptr;
+ }
sInitializer.reset(new MediaFoundationInitializer());
- GetMainThreadSerialEventTarget()->Dispatch(
- NS_NewRunnableFunction("MediaFoundationInitializer::Get", [&] {
- // Need to run this before MTA thread gets destroyed.
- RunOnShutdown(
- [&] {
- sInitializer.reset();
- sIsShutdown = true;
- },
- ShutdownPhase::XPCOMShutdown);
- }));
+ auto shutdownCleanUp = [&] {
+ if (AppShutdown::GetCurrentShutdownPhase() !=
+ ShutdownPhase::NotInShutdown) {
+ sInitializer.reset();
+ sIsShutdown = true;
+ return;
+ }
+ // As MFShutdown needs to run on the MTA thread that is destroyed
+ // on XPCOMShutdownThreads, so we need to run cleanup before that
+ // phase.
+ RunOnShutdown(
+ [&]() {
+ sInitializer.reset();
+ sIsShutdown = true;
+ },
+ ShutdownPhase::XPCOMShutdown);
+ };
+ if (NS_IsMainThread()) {
+ shutdownCleanUp();
+ } else {
+ GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
+ "MediaFoundationInitializer::Get", shutdownCleanUp));
+ }
}
}
return sInitializer.get();
diff --git a/dom/media/platforms/wmf/WMFDataEncoderUtils.cpp b/dom/media/platforms/wmf/WMFDataEncoderUtils.cpp
new file mode 100644
index 0000000000..3bab3ccb46
--- /dev/null
+++ b/dom/media/platforms/wmf/WMFDataEncoderUtils.cpp
@@ -0,0 +1,221 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#include "WMFDataEncoderUtils.h"
+
+#include "EncoderConfig.h"
+#include "MFTEncoder.h"
+#include "MediaData.h"
+#include "mozilla/Logging.h"
+
+namespace mozilla {
+
+#define WMF_ENC_LOG(arg, ...) \
+ MOZ_LOG(mozilla::sPEMLog, mozilla::LogLevel::Error, \
+ ("WMFDataEncoderUtils::%s: " arg, __func__, ##__VA_ARGS__))
+
+GUID CodecToSubtype(CodecType aCodec) {
+ switch (aCodec) {
+ case CodecType::H264:
+ return MFVideoFormat_H264;
+ case CodecType::VP8:
+ return MFVideoFormat_VP80;
+ case CodecType::VP9:
+ return MFVideoFormat_VP90;
+ default:
+ return GUID_NULL;
+ }
+}
+
+bool CanCreateWMFEncoder(CodecType aCodec) {
+ bool canCreate = false;
+ mscom::EnsureMTA([&]() {
+ if (!wmf::MediaFoundationInitializer::HasInitialized()) {
+ return;
+ }
+ // Try HW encoder first.
+ auto enc = MakeRefPtr<MFTEncoder>(false /* HW not allowed */);
+ canCreate = SUCCEEDED(enc->Create(CodecToSubtype(aCodec)));
+ if (!canCreate) {
+ // Try SW encoder.
+ enc = MakeRefPtr<MFTEncoder>(true /* HW not allowed */);
+ canCreate = SUCCEEDED(enc->Create(CodecToSubtype(aCodec)));
+ }
+ });
+ return canCreate;
+}
+
+static already_AddRefed<MediaByteBuffer> ParseH264Parameters(
+ nsTArray<uint8_t>& aHeader, const bool aAsAnnexB) {
+ size_t length = aHeader.Length();
+ auto annexB = MakeRefPtr<MediaByteBuffer>(length);
+ PodCopy(annexB->Elements(), aHeader.Elements(), length);
+ annexB->SetLength(length);
+ if (aAsAnnexB) {
+ return annexB.forget();
+ }
+
+ // Convert to avcC.
+ nsTArray<AnnexB::NALEntry> paramSets;
+ AnnexB::ParseNALEntries(
+ Span<const uint8_t>(annexB->Elements(), annexB->Length()), paramSets);
+
+ auto avcc = MakeRefPtr<MediaByteBuffer>();
+ AnnexB::NALEntry& sps = paramSets.ElementAt(0);
+ AnnexB::NALEntry& pps = paramSets.ElementAt(1);
+ const uint8_t* spsPtr = annexB->Elements() + sps.mOffset;
+ H264::WriteExtraData(
+ avcc, spsPtr[1], spsPtr[2], spsPtr[3],
+ Span<const uint8_t>(spsPtr, sps.mSize),
+ Span<const uint8_t>(annexB->Elements() + pps.mOffset, pps.mSize));
+ return avcc.forget();
+}
+
+static uint32_t GetProfile(H264_PROFILE aProfileLevel) {
+ switch (aProfileLevel) {
+ case H264_PROFILE_BASE:
+ return eAVEncH264VProfile_Base;
+ case H264_PROFILE_MAIN:
+ return eAVEncH264VProfile_Main;
+ case H264_PROFILE_HIGH:
+ return eAVEncH264VProfile_High;
+ default:
+ return eAVEncH264VProfile_unknown;
+ }
+}
+
+already_AddRefed<IMFMediaType> CreateInputType(EncoderConfig& aConfig) {
+ RefPtr<IMFMediaType> type;
+ HRESULT hr = wmf::MFCreateMediaType(getter_AddRefs(type));
+ if (FAILED(hr)) {
+ WMF_ENC_LOG("MFCreateMediaType (input) error: %lx", hr);
+ return nullptr;
+ }
+ hr = type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
+ if (FAILED(hr)) {
+ WMF_ENC_LOG("Create input type: SetGUID (major type) error: %lx", hr);
+ return nullptr;
+ }
+ // Always NV12 input
+ hr = type->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_NV12);
+ if (FAILED(hr)) {
+ WMF_ENC_LOG("Create input type: SetGUID (subtype) error: %lx", hr);
+ return nullptr;
+ }
+ hr = type->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive);
+ if (FAILED(hr)) {
+ WMF_ENC_LOG("Create input type: interlace mode (input) error: %lx", hr);
+ return nullptr;
+ }
+ // WMF requires a framerate to intialize properly. Provide something
+ // reasonnable if not provided.
+ if (!aConfig.mFramerate) {
+ aConfig.mFramerate = 30;
+ }
+ if (aConfig.mFramerate) {
+ hr = MFSetAttributeRatio(type, MF_MT_FRAME_RATE, aConfig.mFramerate, 1);
+ if (FAILED(hr)) {
+ WMF_ENC_LOG("Create input type: frame rate (input) error: %lx", hr);
+ return nullptr;
+ }
+ }
+ hr = MFSetAttributeSize(type, MF_MT_FRAME_SIZE, aConfig.mSize.width,
+ aConfig.mSize.height);
+ if (FAILED(hr)) {
+ WMF_ENC_LOG("Create input type: frame size (input) error: %lx", hr);
+ return nullptr;
+ }
+ return type.forget();
+}
+
+already_AddRefed<IMFMediaType> CreateOutputType(EncoderConfig& aConfig) {
+ RefPtr<IMFMediaType> type;
+ HRESULT hr = wmf::MFCreateMediaType(getter_AddRefs(type));
+ if (FAILED(hr)) {
+ WMF_ENC_LOG("MFCreateMediaType (output) error: %lx", hr);
+ return nullptr;
+ }
+ hr = type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
+ if (FAILED(hr)) {
+ WMF_ENC_LOG("Create output type: set major type error: %lx", hr);
+ return nullptr;
+ }
+ hr = type->SetGUID(MF_MT_SUBTYPE, CodecToSubtype(aConfig.mCodec));
+ if (FAILED(hr)) {
+ WMF_ENC_LOG("Create output type: set subtype error: %lx", hr);
+ return nullptr;
+ }
+ // A bitrate need to be set here, attempt to make an educated guess if none is
+ // provided. This could be per codec to have nicer defaults.
+ size_t longDimension = std::max(aConfig.mSize.width, aConfig.mSize.height);
+ if (!aConfig.mBitrate) {
+ if (longDimension < 720) {
+ aConfig.mBitrate = 2000000;
+ } else if (longDimension < 1080) {
+ aConfig.mBitrate = 4000000;
+ } else {
+ aConfig.mBitrate = 8000000;
+ }
+ }
+ // No way to set variable / constant here.
+ hr = type->SetUINT32(MF_MT_AVG_BITRATE, aConfig.mBitrate);
+ if (FAILED(hr)) {
+ WMF_ENC_LOG("Create output type: set bitrate error: %lx", hr);
+ return nullptr;
+ }
+ hr = type->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive);
+ if (FAILED(hr)) {
+ WMF_ENC_LOG("Create output type set interlave mode error: %lx", hr);
+ return nullptr;
+ }
+ // A positive rate must always be preset here, see the Input config part.
+ MOZ_ASSERT(aConfig.mFramerate);
+ if (aConfig.mFramerate) {
+ hr = MFSetAttributeRatio(type, MF_MT_FRAME_RATE, aConfig.mFramerate, 1);
+ if (FAILED(hr)) {
+ WMF_ENC_LOG("Create output type set frame rate error: %lx", hr);
+ return nullptr;
+ }
+ }
+ // Required
+ hr = MFSetAttributeSize(type, MF_MT_FRAME_SIZE, aConfig.mSize.width,
+ aConfig.mSize.height);
+ if (FAILED(hr)) {
+ WMF_ENC_LOG("Create output type set frame size error: %lx", hr);
+ return nullptr;
+ }
+
+ if (aConfig.mCodecSpecific) {
+ if (aConfig.mCodecSpecific->is<H264Specific>()) {
+ MOZ_ASSERT(aConfig.mCodec == CodecType::H264);
+ hr = FAILED(type->SetUINT32(
+ MF_MT_MPEG2_PROFILE,
+ GetProfile(aConfig.mCodecSpecific->as<H264Specific>().mProfile)));
+ if (hr) {
+ WMF_ENC_LOG("Create output type set profile error: %lx", hr);
+ return nullptr;
+ }
+ }
+ }
+
+ return type.forget();
+}
+
+HRESULT SetMediaTypes(RefPtr<MFTEncoder>& aEncoder, EncoderConfig& aConfig) {
+ RefPtr<IMFMediaType> inputType = CreateInputType(aConfig);
+ if (!inputType) {
+ return E_FAIL;
+ }
+
+ RefPtr<IMFMediaType> outputType = CreateOutputType(aConfig);
+ if (!outputType) {
+ return E_FAIL;
+ }
+
+ return aEncoder->SetMediaTypes(inputType, outputType);
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/wmf/WMFDataEncoderUtils.h b/dom/media/platforms/wmf/WMFDataEncoderUtils.h
index 19f04e768f..0bb4e00086 100644
--- a/dom/media/platforms/wmf/WMFDataEncoderUtils.h
+++ b/dom/media/platforms/wmf/WMFDataEncoderUtils.h
@@ -2,13 +2,16 @@
* 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/. */
-#include "WMFMediaDataEncoder.h"
-
+#ifndef WMFDATAENCODERUTILS_H_
+#define WMFDATAENCODERUTILS_H_
+#include <mfapi.h>
+#include "EncoderConfig.h"
#include "AnnexB.h"
#include "H264.h"
#include "libyuv.h"
#include "mozilla/Logging.h"
#include "mozilla/mscom/EnsureMTA.h"
+#include "WMF.h"
#define WMF_ENC_LOGD(arg, ...) \
MOZ_LOG( \
@@ -21,133 +24,24 @@
namespace mozilla {
-extern LazyLogModule sPEMLog;
-
-static const GUID CodecToSubtype(CodecType aCodec) {
- switch (aCodec) {
- case CodecType::H264:
- return MFVideoFormat_H264;
- case CodecType::VP8:
- return MFVideoFormat_VP80;
- case CodecType::VP9:
- return MFVideoFormat_VP90;
- default:
- return GUID_NULL;
- }
-}
-
-bool CanCreateWMFEncoder(CodecType aCodec) {
- bool canCreate = false;
- mscom::EnsureMTA([&]() {
- if (!wmf::MediaFoundationInitializer::HasInitialized()) {
- return;
- }
- // Try HW encoder first.
- auto enc = MakeRefPtr<MFTEncoder>(false /* HW not allowed */);
- canCreate = SUCCEEDED(enc->Create(CodecToSubtype(aCodec)));
- if (!canCreate) {
- // Try SW encoder.
- enc = MakeRefPtr<MFTEncoder>(true /* HW not allowed */);
- canCreate = SUCCEEDED(enc->Create(CodecToSubtype(aCodec)));
- }
- });
- return canCreate;
-}
-
-static already_AddRefed<MediaByteBuffer> ParseH264Parameters(
- nsTArray<uint8_t>& aHeader, const bool aAsAnnexB) {
- size_t length = aHeader.Length();
- auto annexB = MakeRefPtr<MediaByteBuffer>(length);
- PodCopy(annexB->Elements(), aHeader.Elements(), length);
- annexB->SetLength(length);
- if (aAsAnnexB) {
- return annexB.forget();
- }
+class MFTEncoder;
- // Convert to avcC.
- nsTArray<AnnexB::NALEntry> paramSets;
- AnnexB::ParseNALEntries(
- Span<const uint8_t>(annexB->Elements(), annexB->Length()), paramSets);
-
- auto avcc = MakeRefPtr<MediaByteBuffer>();
- AnnexB::NALEntry& sps = paramSets.ElementAt(0);
- AnnexB::NALEntry& pps = paramSets.ElementAt(1);
- const uint8_t* spsPtr = annexB->Elements() + sps.mOffset;
- H264::WriteExtraData(
- avcc, spsPtr[1], spsPtr[2], spsPtr[3],
- Span<const uint8_t>(spsPtr, sps.mSize),
- Span<const uint8_t>(annexB->Elements() + pps.mOffset, pps.mSize));
- return avcc.forget();
-}
-
-static uint32_t GetProfile(H264_PROFILE aProfileLevel) {
- switch (aProfileLevel) {
- case H264_PROFILE_BASE:
- return eAVEncH264VProfile_Base;
- case H264_PROFILE_MAIN:
- return eAVEncH264VProfile_Main;
- default:
- return eAVEncH264VProfile_unknown;
- }
-}
+extern LazyLogModule sPEMLog;
-already_AddRefed<IMFMediaType> CreateInputType(EncoderConfig& aConfig) {
- RefPtr<IMFMediaType> type;
- return SUCCEEDED(wmf::MFCreateMediaType(getter_AddRefs(type))) &&
- SUCCEEDED(
- type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video)) &&
- SUCCEEDED(type->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_NV12)) &&
- SUCCEEDED(type->SetUINT32(MF_MT_INTERLACE_MODE,
- MFVideoInterlace_Progressive)) &&
- SUCCEEDED(MFSetAttributeRatio(type, MF_MT_FRAME_RATE,
- aConfig.mFramerate, 1)) &&
- SUCCEEDED(MFSetAttributeSize(type, MF_MT_FRAME_SIZE,
- aConfig.mSize.width,
- aConfig.mSize.height))
- ? type.forget()
- : nullptr;
-}
+GUID CodecToSubtype(CodecType aCodec);
-already_AddRefed<IMFMediaType> CreateOutputType(EncoderConfig& aConfig) {
- RefPtr<IMFMediaType> type;
- if (FAILED(wmf::MFCreateMediaType(getter_AddRefs(type))) ||
- FAILED(type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video)) ||
- FAILED(type->SetGUID(MF_MT_SUBTYPE, CodecToSubtype(aConfig.mCodec))) ||
- FAILED(type->SetUINT32(MF_MT_AVG_BITRATE, aConfig.mBitrate)) ||
- FAILED(type->SetUINT32(MF_MT_INTERLACE_MODE,
- MFVideoInterlace_Progressive)) ||
- FAILED(
- MFSetAttributeRatio(type, MF_MT_FRAME_RATE, aConfig.mFramerate, 1)) ||
- FAILED(MFSetAttributeSize(type, MF_MT_FRAME_SIZE, aConfig.mSize.width,
- aConfig.mSize.height))) {
- return nullptr;
- }
- if (aConfig.mCodecSpecific) {
- if (aConfig.mCodecSpecific->is<H264Specific>()) {
- if (FAILED(type->SetUINT32(
- MF_MT_MPEG2_PROFILE,
- GetProfile(
- aConfig.mCodecSpecific->as<H264Specific>().mProfile)))) {
- return nullptr;
- }
- }
- }
+bool CanCreateWMFEncoder(CodecType aCodec);
- return type.forget();
-}
+already_AddRefed<MediaByteBuffer> ParseH264Parameters(
+ nsTArray<uint8_t>& aHeader, const bool aAsAnnexB);
+uint32_t GetProfile(H264_PROFILE aProfileLevel);
-HRESULT SetMediaTypes(RefPtr<MFTEncoder>& aEncoder, EncoderConfig& aConfig) {
- RefPtr<IMFMediaType> inputType = CreateInputType(aConfig);
- if (!inputType) {
- return E_FAIL;
- }
+already_AddRefed<IMFMediaType> CreateInputType(EncoderConfig& aConfig);
- RefPtr<IMFMediaType> outputType = CreateOutputType(aConfig);
- if (!outputType) {
- return E_FAIL;
- }
+already_AddRefed<IMFMediaType> CreateOutputType(EncoderConfig& aConfig);
- return aEncoder->SetMediaTypes(inputType, outputType);
-}
+HRESULT SetMediaTypes(RefPtr<MFTEncoder>& aEncoder, EncoderConfig& aConfig);
} // namespace mozilla
+
+#endif // WMFDATAENCODERUTILS_H_
diff --git a/dom/media/platforms/wmf/WMFDecoderModule.cpp b/dom/media/platforms/wmf/WMFDecoderModule.cpp
index b3aae1e750..79556b061b 100644
--- a/dom/media/platforms/wmf/WMFDecoderModule.cpp
+++ b/dom/media/platforms/wmf/WMFDecoderModule.cpp
@@ -85,13 +85,9 @@ static bool IsRemoteAcceleratedCompositor(
ident.mParentProcessType == GeckoProcessType_GPU;
}
-static Atomic<bool> sSupportedTypesInitialized(false);
-static EnumSet<WMFStreamType> sSupportedTypes;
-static EnumSet<WMFStreamType> sLackOfExtensionTypes;
-
/* static */
void WMFDecoderModule::Init(Config aConfig) {
- MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+ // TODO : add an assertion to prevent this from running on main thread.
if (XRE_IsContentProcess()) {
// If we're in the content process and the UseGPUDecoder pref is set, it
// means that we've given up on the GPU process (it's been crashing) so we
@@ -134,6 +130,7 @@ void WMFDecoderModule::Init(Config aConfig) {
sDXVAEnabled = sDXVAEnabled && hwVideo;
mozilla::mscom::EnsureMTA([&]() {
+ StaticMutexAutoLock lock(sMutex);
// Store the supported MFT decoders.
sSupportedTypes.clear();
sLackOfExtensionTypes.clear();
@@ -163,7 +160,10 @@ void WMFDecoderModule::Init(Config aConfig) {
}
});
- sSupportedTypesInitialized = true;
+ {
+ StaticMutexAutoLock lock(sMutex);
+ sSupportedTypesInitialized = true;
+ }
WmfDecoderModuleMarkerAndLog("WMFInit Result",
"WMFDecoderModule::Init finishing");
@@ -270,15 +270,13 @@ HRESULT WMFDecoderModule::CreateMFTDecoder(const WMFStreamType& aType,
/* static */
bool WMFDecoderModule::CanCreateMFTDecoder(const WMFStreamType& aType) {
MOZ_ASSERT(WMFStreamType::Unknown < aType && aType < WMFStreamType::SENTINEL);
- if (!sSupportedTypesInitialized) {
- if (NS_IsMainThread()) {
- Init();
- } else {
- nsCOMPtr<nsIRunnable> runnable =
- NS_NewRunnableFunction("WMFDecoderModule::Init", [&]() { Init(); });
- SyncRunnable::DispatchToThread(GetMainThreadSerialEventTarget(),
- runnable);
- }
+ bool hasInitialized = false;
+ {
+ StaticMutexAutoLock lock(sMutex);
+ hasInitialized = sSupportedTypesInitialized;
+ }
+ if (!hasInitialized) {
+ Init();
}
// Check prefs here rather than CreateMFTDecoder so that prefs aren't baked
@@ -324,7 +322,7 @@ bool WMFDecoderModule::CanCreateMFTDecoder(const WMFStreamType& aType) {
break;
}
}
-
+ StaticMutexAutoLock lock(sMutex);
return sSupportedTypes.contains(aType);
}
@@ -380,6 +378,7 @@ media::DecodeSupportSet WMFDecoderModule::Supports(
return media::DecodeSupport::SoftwareDecode;
}
}
+ StaticMutexAutoLock lock(sMutex);
return sLackOfExtensionTypes.contains(type)
? media::DecodeSupport::UnsureDueToLackOfExtension
: media::DecodeSupportSet{};
@@ -486,6 +485,9 @@ bool WMFDecoderModule::IsHEVCSupported() {
return sForceEnableHEVC || StaticPrefs::media_wmf_hevc_enabled() == 1;
}
+/* static */
+void WMFDecoderModule::DisableForceEnableHEVC() { sForceEnableHEVC = false; }
+
} // namespace mozilla
#undef WFM_DECODER_MODULE_STATUS_MARKER
diff --git a/dom/media/platforms/wmf/WMFDecoderModule.h b/dom/media/platforms/wmf/WMFDecoderModule.h
index 3b130fd657..6debdc5836 100644
--- a/dom/media/platforms/wmf/WMFDecoderModule.h
+++ b/dom/media/platforms/wmf/WMFDecoderModule.h
@@ -10,6 +10,7 @@
# include "PlatformDecoderModule.h"
# include "WMF.h"
# include "WMFUtils.h"
+# include "mozilla/Atomics.h"
namespace mozilla {
@@ -43,7 +44,9 @@ class WMFDecoderModule : public PlatformDecoderModule {
ForceEnableHEVC,
};
- // Called on main thread.
+ // Can be called on any thread, but avoid calling this on the main thread
+ // because the initialization takes long time and we don't want to block the
+ // main thread.
static void Init(Config aConfig = Config::None);
// Called from any thread, must call init first
@@ -53,16 +56,24 @@ class WMFDecoderModule : public PlatformDecoderModule {
RefPtr<MFTDecoder>& aDecoder);
static bool CanCreateMFTDecoder(const WMFStreamType& aType);
+ static void DisableForceEnableHEVC();
+
private:
// This is used for GPU process only, where we can't set the preference
// directly (it can only set in the parent process) So we need a way to force
// enable the HEVC in order to report the support information via telemetry.
- static inline bool sForceEnableHEVC = false;
+ static inline Atomic<bool> sForceEnableHEVC{false};
static bool IsHEVCSupported();
WMFDecoderModule() = default;
virtual ~WMFDecoderModule() = default;
+
+ static inline StaticMutex sMutex;
+ static inline bool sSupportedTypesInitialized MOZ_GUARDED_BY(sMutex) = false;
+ static inline EnumSet<WMFStreamType> sSupportedTypes MOZ_GUARDED_BY(sMutex);
+ static inline EnumSet<WMFStreamType> sLackOfExtensionTypes
+ MOZ_GUARDED_BY(sMutex);
};
} // namespace mozilla
diff --git a/dom/media/platforms/wmf/WMFEncoderModule.cpp b/dom/media/platforms/wmf/WMFEncoderModule.cpp
index 7b5af9bf50..9f44ce0c5e 100644
--- a/dom/media/platforms/wmf/WMFEncoderModule.cpp
+++ b/dom/media/platforms/wmf/WMFEncoderModule.cpp
@@ -26,6 +26,9 @@ bool WMFEncoderModule::Supports(const EncoderConfig& aConfig) const {
if (aConfig.IsAudio()) {
return false;
}
+ if (aConfig.mScalabilityMode != ScalabilityMode::None) {
+ return false;
+ }
return SupportsCodec(aConfig.mCodec);
}
diff --git a/dom/media/platforms/wmf/WMFMediaDataEncoder.cpp b/dom/media/platforms/wmf/WMFMediaDataEncoder.cpp
new file mode 100644
index 0000000000..fcacedbd05
--- /dev/null
+++ b/dom/media/platforms/wmf/WMFMediaDataEncoder.cpp
@@ -0,0 +1,399 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#include "WMFMediaDataEncoder.h"
+
+#include "ImageContainer.h"
+#include "ImageConversion.h"
+#include "MFTEncoder.h"
+#include "PlatformEncoderModule.h"
+#include "TimeUnits.h"
+#include "WMFDataEncoderUtils.h"
+#include "WMFUtils.h"
+#include <comdef.h>
+#include "mozilla/WindowsProcessMitigations.h"
+
+namespace mozilla {
+
+using InitPromise = MediaDataEncoder::InitPromise;
+using EncodePromise = MediaDataEncoder::EncodePromise;
+using ReconfigurationPromise = MediaDataEncoder::ReconfigurationPromise;
+
+WMFMediaDataEncoder::WMFMediaDataEncoder(const EncoderConfig& aConfig,
+ const RefPtr<TaskQueue>& aTaskQueue)
+ : mConfig(aConfig),
+ mTaskQueue(aTaskQueue),
+ mHardwareNotAllowed(aConfig.mHardwarePreference ==
+ HardwarePreference::RequireSoftware ||
+ IsWin32kLockedDown()) {
+ WMF_ENC_LOGE("WMFMediaDataEncoder ctor: %s, (hw not allowed: %s)",
+ aConfig.ToString().get(), mHardwareNotAllowed ? "yes" : "no");
+ MOZ_ASSERT(mTaskQueue);
+}
+
+RefPtr<InitPromise> WMFMediaDataEncoder::Init() {
+ return InvokeAsync(mTaskQueue, this, __func__,
+ &WMFMediaDataEncoder::ProcessInit);
+}
+RefPtr<EncodePromise> WMFMediaDataEncoder::Encode(const MediaData* aSample) {
+ MOZ_ASSERT(aSample);
+
+ RefPtr<const VideoData> sample(aSample->As<const VideoData>());
+
+ return InvokeAsync<RefPtr<const VideoData>>(
+ mTaskQueue, this, __func__, &WMFMediaDataEncoder::ProcessEncode,
+ std::move(sample));
+}
+RefPtr<EncodePromise> WMFMediaDataEncoder::Drain() {
+ return InvokeAsync(mTaskQueue, __func__,
+ [self = RefPtr<WMFMediaDataEncoder>(this)]() {
+ nsTArray<RefPtr<IMFSample>> outputs;
+ return SUCCEEDED(self->mEncoder->Drain(outputs))
+ ? self->ProcessOutputSamples(outputs)
+ : EncodePromise::CreateAndReject(
+ NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ });
+}
+RefPtr<ShutdownPromise> WMFMediaDataEncoder::Shutdown() {
+ return InvokeAsync(mTaskQueue, __func__,
+ [self = RefPtr<WMFMediaDataEncoder>(this)]() {
+ if (self->mEncoder) {
+ self->mEncoder->Destroy();
+ self->mEncoder = nullptr;
+ }
+ return ShutdownPromise::CreateAndResolve(true, __func__);
+ });
+}
+RefPtr<GenericPromise> WMFMediaDataEncoder::SetBitrate(uint32_t aBitsPerSec) {
+ return InvokeAsync(
+ mTaskQueue, __func__,
+ [self = RefPtr<WMFMediaDataEncoder>(this), aBitsPerSec]() {
+ MOZ_ASSERT(self->mEncoder);
+ return SUCCEEDED(self->mEncoder->SetBitrate(aBitsPerSec))
+ ? GenericPromise::CreateAndResolve(true, __func__)
+ : GenericPromise::CreateAndReject(
+ NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR, __func__);
+ });
+}
+
+RefPtr<ReconfigurationPromise> WMFMediaDataEncoder::Reconfigure(
+ const RefPtr<const EncoderConfigurationChangeList>& aConfigurationChanges) {
+ // General reconfiguration interface not implemented right now
+ return MediaDataEncoder::ReconfigurationPromise::CreateAndReject(
+ NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+};
+
+nsCString WMFMediaDataEncoder::GetDescriptionName() const {
+ return MFTEncoder::GetFriendlyName(CodecToSubtype(mConfig.mCodec));
+}
+
+RefPtr<InitPromise> WMFMediaDataEncoder::ProcessInit() {
+ AssertOnTaskQueue();
+
+ MOZ_ASSERT(!mEncoder,
+ "Should not initialize encoder again without shutting down");
+
+ if (!wmf::MediaFoundationInitializer::HasInitialized()) {
+ return InitPromise::CreateAndReject(
+ MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("Can't create the MFT encoder.")),
+ __func__);
+ }
+
+ RefPtr<MFTEncoder> encoder = new MFTEncoder(mHardwareNotAllowed);
+ HRESULT hr;
+ mscom::EnsureMTA([&]() { hr = InitMFTEncoder(encoder); });
+
+ if (FAILED(hr)) {
+ _com_error error(hr);
+ WMF_ENC_LOGE("init MFTEncoder: error = 0x%lX, %ls", hr,
+ error.ErrorMessage());
+ return InitPromise::CreateAndReject(
+ MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("Can't create the MFT encoder.")),
+ __func__);
+ }
+
+ mEncoder = std::move(encoder);
+ FillConfigData();
+ return InitPromise::CreateAndResolve(TrackInfo::TrackType::kVideoTrack,
+ __func__);
+}
+
+HRESULT WMFMediaDataEncoder::InitMFTEncoder(RefPtr<MFTEncoder>& aEncoder) {
+ HRESULT hr = aEncoder->Create(CodecToSubtype(mConfig.mCodec));
+ if (FAILED(hr)) {
+ _com_error error(hr);
+ WMF_ENC_LOGE("MFTEncoder::Create: error = 0x%lX, %ls", hr,
+ error.ErrorMessage());
+ return hr;
+ }
+
+ hr = SetMediaTypes(aEncoder, mConfig);
+ if (FAILED(hr)) {
+ _com_error error(hr);
+ WMF_ENC_LOGE("MFTEncoder::SetMediaType: error = 0x%lX, %ls", hr,
+ error.ErrorMessage());
+ return hr;
+ }
+
+ hr = aEncoder->SetModes(mConfig.mBitrate);
+ if (FAILED(hr)) {
+ _com_error error(hr);
+ WMF_ENC_LOGE("MFTEncoder::SetMode: error = 0x%lX, %ls", hr,
+ error.ErrorMessage());
+ return hr;
+ }
+
+ return S_OK;
+}
+
+void WMFMediaDataEncoder::FillConfigData() {
+ nsTArray<UINT8> header;
+ NS_ENSURE_TRUE_VOID(SUCCEEDED(mEncoder->GetMPEGSequenceHeader(header)));
+
+ mConfigData =
+ header.Length() > 0
+ ? ParseH264Parameters(header, mConfig.mUsage == Usage::Realtime)
+ : nullptr;
+}
+
+RefPtr<EncodePromise> WMFMediaDataEncoder::ProcessEncode(
+ RefPtr<const VideoData>&& aSample) {
+ AssertOnTaskQueue();
+ MOZ_ASSERT(mEncoder);
+ MOZ_ASSERT(aSample);
+
+ RefPtr<IMFSample> nv12 = ConvertToNV12InputSample(std::move(aSample));
+ if (!nv12 || FAILED(mEncoder->PushInput(std::move(nv12)))) {
+ WMF_ENC_LOGE("failed to process input sample");
+ return EncodePromise::CreateAndReject(
+ MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("Failed to process input.")),
+ __func__);
+ }
+
+ nsTArray<RefPtr<IMFSample>> outputs;
+ HRESULT hr = mEncoder->TakeOutput(outputs);
+ if (hr == MF_E_TRANSFORM_STREAM_CHANGE) {
+ FillConfigData();
+ } else if (FAILED(hr)) {
+ WMF_ENC_LOGE("failed to process output");
+ return EncodePromise::CreateAndReject(
+ MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("Failed to process output.")),
+ __func__);
+ }
+
+ return ProcessOutputSamples(outputs);
+}
+
+already_AddRefed<IMFSample> WMFMediaDataEncoder::ConvertToNV12InputSample(
+ RefPtr<const VideoData>&& aData) {
+ AssertOnTaskQueue();
+ MOZ_ASSERT(mEncoder);
+
+ struct NV12Info {
+ int32_t mYStride = 0;
+ int32_t mUVStride = 0;
+ size_t mYLength = 0;
+ size_t mBufferLength = 0;
+ } info;
+
+ if (const layers::PlanarYCbCrImage* image =
+ aData->mImage->AsPlanarYCbCrImage()) {
+ // Assume this is I420. If it's not, the whole process fails in
+ // ConvertToNV12 below.
+ const layers::PlanarYCbCrData* yuv = image->GetData();
+ info.mYStride = yuv->mYStride;
+ info.mUVStride = yuv->mCbCrStride * 2;
+ info.mYLength = info.mYStride * yuv->YDataSize().height;
+ info.mBufferLength =
+ info.mYLength + (info.mUVStride * yuv->CbCrDataSize().height);
+ } else {
+ info.mYStride = aData->mImage->GetSize().width;
+ info.mUVStride = info.mYStride;
+
+ const int32_t yHeight = aData->mImage->GetSize().height;
+ const int32_t uvHeight = yHeight / 2;
+
+ CheckedInt<size_t> yLength(info.mYStride);
+ yLength *= yHeight;
+ if (!yLength.isValid()) {
+ WMF_ENC_LOGE("yLength overflows");
+ return nullptr;
+ }
+ info.mYLength = yLength.value();
+
+ CheckedInt<size_t> uvLength(info.mUVStride);
+ uvLength *= uvHeight;
+ if (!uvLength.isValid()) {
+ WMF_ENC_LOGE("uvLength overflows");
+ return nullptr;
+ }
+
+ CheckedInt<size_t> length(yLength);
+ length += uvLength;
+ if (!length.isValid()) {
+ WMF_ENC_LOGE("length overflows");
+ return nullptr;
+ }
+ info.mBufferLength = length.value();
+ }
+
+ RefPtr<IMFSample> input;
+ HRESULT hr = mEncoder->CreateInputSample(&input, info.mBufferLength);
+ if (FAILED(hr)) {
+ _com_error error(hr);
+ WMF_ENC_LOGE("CreateInputSample: error = 0x%lX, %ls", hr,
+ error.ErrorMessage());
+ return nullptr;
+ }
+
+ RefPtr<IMFMediaBuffer> buffer;
+ hr = input->GetBufferByIndex(0, getter_AddRefs(buffer));
+ if (FAILED(hr)) {
+ _com_error error(hr);
+ WMF_ENC_LOGE("GetBufferByIndex: error = 0x%lX, %ls", hr,
+ error.ErrorMessage());
+ return nullptr;
+ }
+
+ hr = buffer->SetCurrentLength(info.mBufferLength);
+ if (FAILED(hr)) {
+ _com_error error(hr);
+ WMF_ENC_LOGE("SetCurrentLength: error = 0x%lX, %ls", hr,
+ error.ErrorMessage());
+ return nullptr;
+ }
+
+ LockBuffer lockBuffer(buffer);
+ hr = lockBuffer.Result();
+ if (FAILED(hr)) {
+ _com_error error(hr);
+ WMF_ENC_LOGE("LockBuffer: error = 0x%lX, %ls", hr, error.ErrorMessage());
+ return nullptr;
+ }
+
+ nsresult rv =
+ ConvertToNV12(aData->mImage, lockBuffer.Data(), info.mYStride,
+ lockBuffer.Data() + info.mYLength, info.mUVStride);
+ if (NS_FAILED(rv)) {
+ WMF_ENC_LOGE("Failed to convert to NV12");
+ return nullptr;
+ }
+
+ hr = input->SetSampleTime(UsecsToHNs(aData->mTime.ToMicroseconds()));
+ if (FAILED(hr)) {
+ _com_error error(hr);
+ WMF_ENC_LOGE("SetSampleTime: error = 0x%lX, %ls", hr, error.ErrorMessage());
+ return nullptr;
+ }
+
+ hr = input->SetSampleDuration(UsecsToHNs(aData->mDuration.ToMicroseconds()));
+ if (FAILED(hr)) {
+ _com_error error(hr);
+ WMF_ENC_LOGE("SetSampleDuration: error = 0x%lX, %ls", hr,
+ error.ErrorMessage());
+ return nullptr;
+ }
+
+ return input.forget();
+}
+
+RefPtr<EncodePromise> WMFMediaDataEncoder::ProcessOutputSamples(
+ nsTArray<RefPtr<IMFSample>>& aSamples) {
+ EncodedData frames;
+ for (auto sample : aSamples) {
+ RefPtr<MediaRawData> frame = IMFSampleToMediaData(sample);
+ if (frame) {
+ frames.AppendElement(std::move(frame));
+ } else {
+ WMF_ENC_LOGE("failed to convert output frame");
+ }
+ }
+ aSamples.Clear();
+ return EncodePromise::CreateAndResolve(std::move(frames), __func__);
+}
+
+already_AddRefed<MediaRawData> WMFMediaDataEncoder::IMFSampleToMediaData(
+ RefPtr<IMFSample>& aSample) {
+ AssertOnTaskQueue();
+ MOZ_ASSERT(aSample);
+
+ RefPtr<IMFMediaBuffer> buffer;
+ HRESULT hr = aSample->GetBufferByIndex(0, getter_AddRefs(buffer));
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+
+ LockBuffer lockBuffer(buffer);
+ NS_ENSURE_TRUE(SUCCEEDED(lockBuffer.Result()), nullptr);
+
+ LONGLONG time = 0;
+ hr = aSample->GetSampleTime(&time);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+
+ LONGLONG duration = 0;
+ hr = aSample->GetSampleDuration(&duration);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+
+ bool isKeyframe =
+ MFGetAttributeUINT32(aSample, MFSampleExtension_CleanPoint, false);
+
+ auto frame = MakeRefPtr<MediaRawData>();
+ if (!WriteFrameData(frame, lockBuffer, isKeyframe)) {
+ return nullptr;
+ }
+
+ frame->mTime = media::TimeUnit::FromMicroseconds(HNsToUsecs(time));
+ frame->mDuration = media::TimeUnit::FromMicroseconds(HNsToUsecs(duration));
+ frame->mKeyframe = isKeyframe;
+
+ return frame.forget();
+}
+
+bool WMFMediaDataEncoder::WriteFrameData(RefPtr<MediaRawData>& aDest,
+ LockBuffer& aSrc, bool aIsKeyframe) {
+ if (mConfig.mCodec == CodecType::H264) {
+ size_t prependLength = 0;
+ RefPtr<MediaByteBuffer> avccHeader;
+ if (aIsKeyframe && mConfigData) {
+ if (mConfig.mUsage == Usage::Realtime) {
+ prependLength = mConfigData->Length();
+ } else {
+ avccHeader = mConfigData;
+ }
+ }
+
+ UniquePtr<MediaRawDataWriter> writer(aDest->CreateWriter());
+ if (!writer->SetSize(prependLength + aSrc.Length())) {
+ WMF_ENC_LOGE("fail to allocate output buffer");
+ return false;
+ }
+
+ if (prependLength > 0) {
+ PodCopy(writer->Data(), mConfigData->Elements(), prependLength);
+ }
+ PodCopy(writer->Data() + prependLength, aSrc.Data(), aSrc.Length());
+
+ if (mConfig.mUsage != Usage::Realtime &&
+ !AnnexB::ConvertSampleToAVCC(aDest, avccHeader)) {
+ WMF_ENC_LOGE("fail to convert annex-b sample to AVCC");
+ return false;
+ }
+
+ return true;
+ }
+ UniquePtr<MediaRawDataWriter> writer(aDest->CreateWriter());
+ if (!writer->SetSize(aSrc.Length())) {
+ WMF_ENC_LOGE("fail to allocate output buffer");
+ return false;
+ }
+
+ PodCopy(writer->Data(), aSrc.Data(), aSrc.Length());
+ return true;
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/wmf/WMFMediaDataEncoder.h b/dom/media/platforms/wmf/WMFMediaDataEncoder.h
index 31a63c8347..db1077e699 100644
--- a/dom/media/platforms/wmf/WMFMediaDataEncoder.h
+++ b/dom/media/platforms/wmf/WMFMediaDataEncoder.h
@@ -7,84 +7,30 @@
#ifndef WMFMediaDataEncoder_h_
#define WMFMediaDataEncoder_h_
-#include "ImageContainer.h"
#include "MFTEncoder.h"
#include "PlatformEncoderModule.h"
-#include "TimeUnits.h"
#include "WMFDataEncoderUtils.h"
#include "WMFUtils.h"
+#include <comdef.h>
+#include "mozilla/WindowsProcessMitigations.h"
namespace mozilla {
class WMFMediaDataEncoder final : public MediaDataEncoder {
public:
WMFMediaDataEncoder(const EncoderConfig& aConfig,
- const RefPtr<TaskQueue>& aTaskQueue)
- : mConfig(aConfig),
- mTaskQueue(aTaskQueue),
- mHardwareNotAllowed(aConfig.mHardwarePreference ==
- HardwarePreference::RequireSoftware ||
- aConfig.mHardwarePreference ==
- HardwarePreference::None) {
- MOZ_ASSERT(mTaskQueue);
- }
+ const RefPtr<TaskQueue>& aTaskQueue);
- RefPtr<InitPromise> Init() override {
- return InvokeAsync(mTaskQueue, this, __func__,
- &WMFMediaDataEncoder::ProcessInit);
- }
- RefPtr<EncodePromise> Encode(const MediaData* aSample) override {
- MOZ_ASSERT(aSample);
-
- RefPtr<const VideoData> sample(aSample->As<const VideoData>());
-
- return InvokeAsync<RefPtr<const VideoData>>(
- mTaskQueue, this, __func__, &WMFMediaDataEncoder::ProcessEncode,
- std::move(sample));
- }
- RefPtr<EncodePromise> Drain() override {
- return InvokeAsync(
- mTaskQueue, __func__, [self = RefPtr<WMFMediaDataEncoder>(this)]() {
- nsTArray<RefPtr<IMFSample>> outputs;
- return SUCCEEDED(self->mEncoder->Drain(outputs))
- ? self->ProcessOutputSamples(outputs)
- : EncodePromise::CreateAndReject(
- NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
- });
- }
- RefPtr<ShutdownPromise> Shutdown() override {
- return InvokeAsync(
- mTaskQueue, __func__, [self = RefPtr<WMFMediaDataEncoder>(this)]() {
- if (self->mEncoder) {
- self->mEncoder->Destroy();
- self->mEncoder = nullptr;
- }
- return ShutdownPromise::CreateAndResolve(true, __func__);
- });
- }
- RefPtr<GenericPromise> SetBitrate(uint32_t aBitsPerSec) override {
- return InvokeAsync(
- mTaskQueue, __func__,
- [self = RefPtr<WMFMediaDataEncoder>(this), aBitsPerSec]() {
- MOZ_ASSERT(self->mEncoder);
- return SUCCEEDED(self->mEncoder->SetBitrate(aBitsPerSec))
- ? GenericPromise::CreateAndResolve(true, __func__)
- : GenericPromise::CreateAndReject(
- NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR, __func__);
- });
- }
+ RefPtr<InitPromise> Init() override;
+ RefPtr<EncodePromise> Encode(const MediaData* aSample) override;
+ RefPtr<EncodePromise> Drain() override;
+ RefPtr<ShutdownPromise> Shutdown() override;
+ RefPtr<GenericPromise> SetBitrate(uint32_t aBitsPerSec) override;
RefPtr<ReconfigurationPromise> Reconfigure(
const RefPtr<const EncoderConfigurationChangeList>& aConfigurationChanges)
- override {
- // General reconfiguration interface not implemented right now
- return MediaDataEncoder::ReconfigurationPromise::CreateAndReject(
- NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
- };
-
- nsCString GetDescriptionName() const override {
- return MFTEncoder::GetFriendlyName(CodecToSubtype(mConfig.mCodec));
- }
+ override;
+ nsCString GetDescriptionName() const override;
private:
// Automatically lock/unlock IMFMediaBuffer.
@@ -107,233 +53,29 @@ class WMFMediaDataEncoder final : public MediaDataEncoder {
private:
RefPtr<IMFMediaBuffer> mBuffer;
- BYTE* mBytes;
- DWORD mCapacity;
- DWORD mLength;
- HRESULT mResult;
+ BYTE* mBytes{};
+ DWORD mCapacity{};
+ DWORD mLength{};
+ HRESULT mResult{};
};
- RefPtr<InitPromise> ProcessInit() {
- AssertOnTaskQueue();
-
- MOZ_ASSERT(!mEncoder,
- "Should not initialize encoder again without shutting down");
-
- if (!wmf::MediaFoundationInitializer::HasInitialized()) {
- return InitPromise::CreateAndReject(
- MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
- RESULT_DETAIL("Can't create the MFT encoder.")),
- __func__);
- }
-
- RefPtr<MFTEncoder> encoder = new MFTEncoder(mHardwareNotAllowed);
- HRESULT hr;
- mscom::EnsureMTA([&]() { hr = InitMFTEncoder(encoder); });
-
- if (FAILED(hr)) {
- WMF_ENC_LOGE("init MFTEncoder: error = 0x%lX", hr);
- return InitPromise::CreateAndReject(
- MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
- RESULT_DETAIL("Can't create the MFT encoder.")),
- __func__);
- }
-
- mEncoder = std::move(encoder);
- FillConfigData();
- return InitPromise::CreateAndResolve(TrackInfo::TrackType::kVideoTrack,
- __func__);
- }
-
- HRESULT InitMFTEncoder(RefPtr<MFTEncoder>& aEncoder) {
- HRESULT hr = aEncoder->Create(CodecToSubtype(mConfig.mCodec));
- NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
-
- hr = SetMediaTypes(aEncoder, mConfig);
- NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
-
- hr = aEncoder->SetModes(mConfig.mBitrate);
- NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+ RefPtr<InitPromise> ProcessInit();
- return S_OK;
- }
+ HRESULT InitMFTEncoder(RefPtr<MFTEncoder>& aEncoder);
+ void FillConfigData();
- void FillConfigData() {
- nsTArray<UINT8> header;
- NS_ENSURE_TRUE_VOID(SUCCEEDED(mEncoder->GetMPEGSequenceHeader(header)));
-
- mConfigData =
- header.Length() > 0
- ? ParseH264Parameters(header, mConfig.mUsage == Usage::Realtime)
- : nullptr;
- }
-
- RefPtr<EncodePromise> ProcessEncode(RefPtr<const VideoData>&& aSample) {
- AssertOnTaskQueue();
- MOZ_ASSERT(mEncoder);
- MOZ_ASSERT(aSample);
-
- RefPtr<IMFSample> nv12 = ConvertToNV12InputSample(std::move(aSample));
- if (!nv12 || FAILED(mEncoder->PushInput(std::move(nv12)))) {
- WMF_ENC_LOGE("failed to process input sample");
- return EncodePromise::CreateAndReject(
- MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
- RESULT_DETAIL("Failed to process input.")),
- __func__);
- }
-
- nsTArray<RefPtr<IMFSample>> outputs;
- HRESULT hr = mEncoder->TakeOutput(outputs);
- if (hr == MF_E_TRANSFORM_STREAM_CHANGE) {
- FillConfigData();
- } else if (FAILED(hr)) {
- WMF_ENC_LOGE("failed to process output");
- return EncodePromise::CreateAndReject(
- MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
- RESULT_DETAIL("Failed to process output.")),
- __func__);
- }
-
- return ProcessOutputSamples(outputs);
- }
+ RefPtr<EncodePromise> ProcessEncode(RefPtr<const VideoData>&& aSample);
already_AddRefed<IMFSample> ConvertToNV12InputSample(
- RefPtr<const VideoData>&& aData) {
- AssertOnTaskQueue();
- MOZ_ASSERT(mEncoder);
-
- const layers::PlanarYCbCrImage* image = aData->mImage->AsPlanarYCbCrImage();
- // TODO: Take care non planar Y-Cb-Cr image (Bug 1881647).
- NS_ENSURE_TRUE(image, nullptr);
-
- const layers::PlanarYCbCrData* yuv = image->GetData();
- auto ySize = yuv->YDataSize();
- auto cbcrSize = yuv->CbCrDataSize();
- size_t yLength = yuv->mYStride * ySize.height;
- size_t length = yLength + (yuv->mCbCrStride * cbcrSize.height * 2);
-
- RefPtr<IMFSample> input;
- HRESULT hr = mEncoder->CreateInputSample(&input, length);
- NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
-
- RefPtr<IMFMediaBuffer> buffer;
- hr = input->GetBufferByIndex(0, getter_AddRefs(buffer));
- NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
-
- hr = buffer->SetCurrentLength(length);
- NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
-
- LockBuffer lockBuffer(buffer);
- NS_ENSURE_TRUE(SUCCEEDED(lockBuffer.Result()), nullptr);
-
- // TODO: Take care non I420 image (Bug 1881647).
- bool ok = libyuv::I420ToNV12(
- yuv->mYChannel, yuv->mYStride, yuv->mCbChannel,
- yuv->mCbCrStride, yuv->mCrChannel, yuv->mCbCrStride,
- lockBuffer.Data(), yuv->mYStride, lockBuffer.Data() + yLength,
- yuv->mCbCrStride * 2, ySize.width, ySize.height) == 0;
- NS_ENSURE_TRUE(ok, nullptr);
-
- hr = input->SetSampleTime(UsecsToHNs(aData->mTime.ToMicroseconds()));
- NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
-
- hr =
- input->SetSampleDuration(UsecsToHNs(aData->mDuration.ToMicroseconds()));
- NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
-
- return input.forget();
- }
+ RefPtr<const VideoData>&& aData);
RefPtr<EncodePromise> ProcessOutputSamples(
- nsTArray<RefPtr<IMFSample>>& aSamples) {
- EncodedData frames;
- for (auto sample : aSamples) {
- RefPtr<MediaRawData> frame = IMFSampleToMediaData(sample);
- if (frame) {
- frames.AppendElement(std::move(frame));
- } else {
- WMF_ENC_LOGE("failed to convert output frame");
- }
- }
- aSamples.Clear();
- return EncodePromise::CreateAndResolve(std::move(frames), __func__);
- }
-
+ nsTArray<RefPtr<IMFSample>>& aSamples);
already_AddRefed<MediaRawData> IMFSampleToMediaData(
- RefPtr<IMFSample>& aSample) {
- AssertOnTaskQueue();
- MOZ_ASSERT(aSample);
-
- RefPtr<IMFMediaBuffer> buffer;
- HRESULT hr = aSample->GetBufferByIndex(0, getter_AddRefs(buffer));
- NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
-
- LockBuffer lockBuffer(buffer);
- NS_ENSURE_TRUE(SUCCEEDED(lockBuffer.Result()), nullptr);
-
- LONGLONG time = 0;
- hr = aSample->GetSampleTime(&time);
- NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
-
- LONGLONG duration = 0;
- hr = aSample->GetSampleDuration(&duration);
- NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
-
- bool isKeyframe =
- MFGetAttributeUINT32(aSample, MFSampleExtension_CleanPoint, false);
-
- auto frame = MakeRefPtr<MediaRawData>();
- if (!WriteFrameData(frame, lockBuffer, isKeyframe)) {
- return nullptr;
- }
-
- frame->mTime = media::TimeUnit::FromMicroseconds(HNsToUsecs(time));
- frame->mDuration = media::TimeUnit::FromMicroseconds(HNsToUsecs(duration));
- frame->mKeyframe = isKeyframe;
-
- return frame.forget();
- }
+ RefPtr<IMFSample>& aSample);
bool WriteFrameData(RefPtr<MediaRawData>& aDest, LockBuffer& aSrc,
- bool aIsKeyframe) {
- if (mConfig.mCodec == CodecType::H264) {
- size_t prependLength = 0;
- RefPtr<MediaByteBuffer> avccHeader;
- if (aIsKeyframe && mConfigData) {
- if (mConfig.mUsage == Usage::Realtime) {
- prependLength = mConfigData->Length();
- } else {
- avccHeader = mConfigData;
- }
- }
-
- UniquePtr<MediaRawDataWriter> writer(aDest->CreateWriter());
- if (!writer->SetSize(prependLength + aSrc.Length())) {
- WMF_ENC_LOGE("fail to allocate output buffer");
- return false;
- }
-
- if (prependLength > 0) {
- PodCopy(writer->Data(), mConfigData->Elements(), prependLength);
- }
- PodCopy(writer->Data() + prependLength, aSrc.Data(), aSrc.Length());
-
- if (mConfig.mUsage != Usage::Realtime &&
- !AnnexB::ConvertSampleToAVCC(aDest, avccHeader)) {
- WMF_ENC_LOGE("fail to convert annex-b sample to AVCC");
- return false;
- }
-
- return true;
- }
- UniquePtr<MediaRawDataWriter> writer(aDest->CreateWriter());
- if (!writer->SetSize(aSrc.Length())) {
- WMF_ENC_LOGE("fail to allocate output buffer");
- return false;
- }
-
- PodCopy(writer->Data(), aSrc.Data(), aSrc.Length());
- return true;
- }
+ bool aIsKeyframe);
void AssertOnTaskQueue() { MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); }
diff --git a/dom/media/platforms/wmf/WMFVideoMFTManager.cpp b/dom/media/platforms/wmf/WMFVideoMFTManager.cpp
index 65480c4a01..2344de94d9 100644
--- a/dom/media/platforms/wmf/WMFVideoMFTManager.cpp
+++ b/dom/media/platforms/wmf/WMFVideoMFTManager.cpp
@@ -711,6 +711,8 @@ WMFVideoMFTManager::CreateBasicVideoFrame(IMFSample* aSample,
aStage.SetImageFormat(DecodeStage::P016);
}
aStage.SetResolution(videoWidth, videoHeight);
+ aStage.SetStartTimeAndEndTime(v->mTime.ToMicroseconds(),
+ v->GetEndTime().ToMicroseconds());
});
v.forget(aOutVideoData);
@@ -753,7 +755,6 @@ WMFVideoMFTManager::CreateD3DVideoFrame(IMFSample* aSample,
TimeUnit::FromMicroseconds(-1));
NS_ENSURE_TRUE(v, E_FAIL);
- v.forget(aOutVideoData);
mPerformanceRecorder.Record(pts.ToMicroseconds(), [&](DecodeStage& aStage) {
aStage.SetColorDepth(mVideoInfo.mColorDepth);
@@ -771,8 +772,11 @@ WMFVideoMFTManager::CreateD3DVideoFrame(IMFSample* aSample,
aStage.SetImageFormat(DecodeStage::P016);
}
aStage.SetResolution(size.width, size.height);
+ aStage.SetStartTimeAndEndTime(v->mTime.ToMicroseconds(),
+ v->GetEndTime().ToMicroseconds());
});
+ v.forget(aOutVideoData);
return S_OK;
}
diff --git a/dom/media/platforms/wmf/moz.build b/dom/media/platforms/wmf/moz.build
index 9e0f3aa94a..13f754af29 100644
--- a/dom/media/platforms/wmf/moz.build
+++ b/dom/media/platforms/wmf/moz.build
@@ -57,9 +57,11 @@ UNIFIED_SOURCES += [
"MFTDecoder.cpp",
"MFTEncoder.cpp",
"WMFAudioMFTManager.cpp",
+ "WMFDataEncoderUtils.cpp",
"WMFDecoderModule.cpp",
"WMFEncoderModule.cpp",
"WMFMediaDataDecoder.cpp",
+ "WMFMediaDataEncoder.cpp",
"WMFVideoMFTManager.cpp",
]
diff --git a/dom/media/test/complete_length_worker.js b/dom/media/test/complete_length_worker.js
new file mode 100644
index 0000000000..ceda63fdd5
--- /dev/null
+++ b/dom/media/test/complete_length_worker.js
@@ -0,0 +1,80 @@
+"use strict";
+
+let client;
+function is(got, expected, name) {
+ client.postMessage({ type: "is", got, expected, name });
+}
+
+self.onactivate = e =>
+ e.waitUntil(
+ (async () => {
+ await self.clients.claim();
+ const allClients = await self.clients.matchAll();
+ client = allClients[0];
+ is(allClients.length, 1, "allClients.length");
+ })()
+ );
+
+let expected_start = 0;
+let response_data = [
+ // One Array element for each response in order:
+ {
+ complete_length: "*",
+ body: "O",
+ },
+ {
+ complete_length: "3",
+ body: "g",
+ },
+ {
+ // Extend length to test that the remainder is fetched.
+ complete_length: "6",
+ body: "g",
+ },
+ {
+ // Reduce length to test that no more is fetched.
+ complete_length: "4",
+ body: "S",
+ },
+];
+
+self.onfetch = e => {
+ if (!e.request.url.endsWith("/media-resource")) {
+ return; // fall back to network fetch
+ }
+ is(
+ response_data.length >= 1,
+ true,
+ `response_data.length (${response_data.length}) > 0`
+ );
+ const { complete_length, body } = response_data.shift();
+ const range = e.request.headers.get("Range");
+ const match = range.match(/^bytes=(\d+)-/);
+ is(Array.isArray(match), true, `Array.isArray(match) for ${range}`);
+ const first = parseInt(match[1]);
+ is(first, expected_start, "first");
+ const last = first + body.length - 1; // inclusive
+ expected_start = last + 1;
+ const init = {
+ status: 206, // Partial Content
+ headers: {
+ "Accept-Ranges": "bytes",
+ "Content-Type": "audio/ogg",
+ "Content-Range": `bytes ${first}-${last}/${complete_length}`,
+ "Content-Length": body.length,
+ },
+ };
+ e.respondWith(new Response(body, init));
+};
+
+self.onmessage = e => {
+ switch (e.data.type) {
+ case "got error event":
+ // Check that all expected requests were received.
+ is(response_data.length, 0, "missing fetch count");
+ client.postMessage({ type: "done" });
+ return;
+ default:
+ is(e.data.type, "__KNOWN__", "e.data.type");
+ }
+};
diff --git a/dom/media/test/mochitest.toml b/dom/media/test/mochitest.toml
index 99bd1c41c8..2490bef305 100644
--- a/dom/media/test/mochitest.toml
+++ b/dom/media/test/mochitest.toml
@@ -444,6 +444,7 @@ support-files = [
"chained-audio-video.ogg^headers^",
"chromeHelper.js",
"cloneElementVisually_helpers.js",
+ "complete_length_worker.js",
"contentType.sjs",
"detodos.opus",
"detodos.opus^headers^",
@@ -767,6 +768,9 @@ tags = "cloneelementvisually"
["test_clone_media_element.html"]
skip-if = ["os == 'android'"] # bug 1108558, android(bug 1232305)
+["test_complete_length.html"]
+scheme = "https"
+
["test_fastSeek-forwards.html"]
["test_fastSeek.html"]
diff --git a/dom/media/test/rdd_process_xpcom/RddProcessTest.cpp b/dom/media/test/rdd_process_xpcom/RddProcessTest.cpp
index fad7d6ee2e..c3e61e3f11 100644
--- a/dom/media/test/rdd_process_xpcom/RddProcessTest.cpp
+++ b/dom/media/test/rdd_process_xpcom/RddProcessTest.cpp
@@ -49,8 +49,7 @@ RddProcessTest::TestTelemetryProbes(JSContext* aCx,
promise->MaybeResolve((int32_t)rddProc->RDDProcessPid());
},
[promise](nsresult aError) {
- MOZ_ASSERT_UNREACHABLE("RddProcessTest; failure to get RDD child");
- promise->MaybeReject(aError);
+ MOZ_CRASH("RddProcessTest; failure to get RDD child");
});
promise.forget(aOutPromise);
diff --git a/dom/media/test/reftest/reftest.list b/dom/media/test/reftest/reftest.list
index bd4cb2d030..8d39975d5a 100644
--- a/dom/media/test/reftest/reftest.list
+++ b/dom/media/test/reftest/reftest.list
@@ -11,5 +11,5 @@ skip-if(Android) fuzzy(0-31,0-573249) fuzzy-if(appleSilicon,0-37,0-543189) == im
skip-if(Android) fuzzy(0-84,0-774213) fails-if(useDrawSnapshot) == uneven_frame_duration_video.html uneven_frame_duration_video-ref.html # Skip on Windows 7 as the resolution of the video is too high for test machines and will fail in the decoder.
# Set media.dormant-on-pause-timeout-ms to avoid decoders becoming dormant and busting test, skip on android as test is too noisy and unstable
skip-if(Android) pref(media.dormant-on-pause-timeout-ms,-1) fuzzy(0-20,0-500) == frame_order_mp4.html frame_order_mp4-ref.html
-skip-if(Android) fuzzy(0-30,0-270000) == incorrect_display_in_bytestream_vp8.html incorrect_display_in_bytestream_vp8-ref.html
+skip-if(Android) fuzzy(0-31,0-270000) == incorrect_display_in_bytestream_vp8.html incorrect_display_in_bytestream_vp8-ref.html
skip-if(Android) fuzzy(0-22,0-381481) == incorrect_display_in_bytestream_vp9.html incorrect_display_in_bytestream_vp9-ref.html
diff --git a/dom/media/test/test_complete_length.html b/dom/media/test/test_complete_length.html
new file mode 100644
index 0000000000..576b00dac2
--- /dev/null
+++ b/dom/media/test/test_complete_length.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test different complete-length fields of Content-Range headers</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/dom/serviceworkers/test/utils.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+</head>
+<script>
+"use strict";
+
+let oncomplete;
+navigator.serviceWorker.addEventListener("message", e => {
+ switch (e.data.type) {
+ case "is":
+ is(e.data.got, e.data.expected, e.data.name);
+ break;
+ case "done":
+ oncomplete();
+ break;
+ default:
+ record(false, "unknown e.data.type", e.data.type);
+ }
+});
+
+add_task(async () => {
+ // Unregister any previous ServiceWorkerRegistrations that may not have been
+ // removed before a page reload.
+ await unregisterAll();
+ const registration =
+ await registerAndWaitForActive("complete_length_worker.js");
+ SimpleTest.registerCleanupFunction(() => registration.unregister());
+
+ const audio = new Audio("media-resource");
+ audio.preload = "metadata";
+ // An error event is generated because the resource is incomplete.
+ const error_promise = new Promise(r => audio.onerror = r);
+ await error_promise;
+ is(audio.error.code, MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED, "error.code");
+ is(audio.error.message, "NS_ERROR_DOM_MEDIA_METADATA_ERR (0x806e0006)",
+ "error.message");
+
+ // Tell the ServiceWorker that media-resource requests have completed.
+ navigator.serviceWorker.controller.postMessage({type: "got error event"});
+ await new Promise(r => oncomplete = r);
+});
+</script>
+</html>
diff --git a/dom/media/tests/crashtests/crashtests.list b/dom/media/tests/crashtests/crashtests.list
index fd4ed80607..96c296a9fa 100644
--- a/dom/media/tests/crashtests/crashtests.list
+++ b/dom/media/tests/crashtests/crashtests.list
@@ -31,8 +31,8 @@ load 1511130.html
load 1510848.html
load 1516292.html
load 1576938.html
-skip-if(Android) pref(media.getusermedia.audiocapture.enabled,true) load 1573536.html
-skip-if(!Android) pref(media.getusermedia.audiocapture.enabled,true) pref(media.navigator.permission.device,false) load 1573536.html # media.navigator.permission.device is mobile-only, so other platforms fail to set it (Bug 1350948)
+skip-if(Android) pref(media.getusermedia.audio.capture.enabled,true) load 1573536.html
+skip-if(!Android) pref(media.getusermedia.audio.capture.enabled,true) pref(media.navigator.permission.device,false) load 1573536.html # media.navigator.permission.device is mobile-only, so other platforms fail to set it (Bug 1350948)
load 1594136.html
load 1749308.html
load 1764915.html
diff --git a/dom/media/utils/PerformanceRecorder.cpp b/dom/media/utils/PerformanceRecorder.cpp
index 3dc2c24a5d..dda76e0d99 100644
--- a/dom/media/utils/PerformanceRecorder.cpp
+++ b/dom/media/utils/PerformanceRecorder.cpp
@@ -245,6 +245,19 @@ ProfilerString8View PlaybackStage::Name() const {
return *mName;
}
+void PlaybackStage::AddMarker(MarkerOptions&& aOption) {
+ if (mStartAndEndTimeUs) {
+ auto& pair = *mStartAndEndTimeUs;
+ profiler_add_marker(Name(), Category(),
+ std::forward<MarkerOptions&&>(aOption),
+ geckoprofiler::markers::MediaSampleMarker{}, pair.first,
+ pair.second, 1 /* queue length */);
+ } else {
+ profiler_add_marker(Name(), Category(),
+ std::forward<MarkerOptions&&>(aOption));
+ }
+}
+
ProfilerString8View CaptureStage::Name() const {
if (!mName) {
auto imageTypeToStr = [](ImageType aType) -> const char* {
@@ -307,4 +320,17 @@ ProfilerString8View DecodeStage::Name() const {
return *mName;
}
+void DecodeStage::AddMarker(MarkerOptions&& aOption) {
+ if (mStartAndEndTimeUs) {
+ auto& pair = *mStartAndEndTimeUs;
+ profiler_add_marker(Name(), Category(),
+ std::forward<MarkerOptions&&>(aOption),
+ geckoprofiler::markers::MediaSampleMarker{}, pair.first,
+ pair.second, 1 /* queue length */);
+ } else {
+ profiler_add_marker(Name(), Category(),
+ std::forward<MarkerOptions&&>(aOption));
+ }
+}
+
} // namespace mozilla
diff --git a/dom/media/utils/PerformanceRecorder.h b/dom/media/utils/PerformanceRecorder.h
index e423c3fb5d..95fdab90ba 100644
--- a/dom/media/utils/PerformanceRecorder.h
+++ b/dom/media/utils/PerformanceRecorder.h
@@ -8,11 +8,13 @@
#define mozilla_PerformanceRecorder_h
#include <type_traits>
+#include <utility>
#include "mozilla/Attributes.h"
#include "mozilla/BaseProfilerMarkersPrerequisites.h"
#include "mozilla/Maybe.h"
#include "mozilla/Mutex.h"
+#include "mozilla/ProfilerMarkerTypes.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/TypedEnumBits.h"
#include "nsStringFwd.h"
@@ -118,7 +120,19 @@ enum class MediaStage : uint8_t {
CopyDecodedVideo,
};
-class PlaybackStage {
+class StageBase {
+ public:
+ virtual void AddMarker(MarkerOptions&& aOption) {
+ profiler_add_marker(Name(), Category(),
+ std::forward<MarkerOptions&&>(aOption));
+ }
+
+ protected:
+ virtual ProfilerString8View Name() const = 0;
+ virtual const MarkerCategory& Category() const = 0;
+};
+
+class PlaybackStage : public StageBase {
public:
explicit PlaybackStage(MediaStage aStage, int32_t aHeight = 0,
MediaInfoFlag aFlag = MediaInfoFlag::None)
@@ -126,20 +140,28 @@ class PlaybackStage {
MOZ_ASSERT(aStage != MediaStage::Invalid);
}
- ProfilerString8View Name() const;
- const MarkerCategory& Category() const {
+ ProfilerString8View Name() const override;
+ const MarkerCategory& Category() const override {
return baseprofiler::category::MEDIA_PLAYBACK;
}
+ void AddMarker(MarkerOptions&& aOption) override;
+
+ void SetStartTimeAndEndTime(uint64_t aStartTime, uint64_t aEndTime) {
+ mStartAndEndTimeUs =
+ Some(std::pair<uint64_t, uint64_t>{aStartTime, aEndTime});
+ }
MediaStage mStage;
int32_t mHeight;
MediaInfoFlag mFlag;
+ Maybe<std::pair<uint64_t, uint64_t>> mStartAndEndTimeUs;
+
private:
mutable Maybe<nsCString> mName;
};
-class CaptureStage {
+class CaptureStage : public StageBase {
public:
enum class ImageType : uint8_t {
Unknown,
@@ -160,8 +182,8 @@ class CaptureStage {
mHeight(aHeight),
mImageType(aImageType) {}
- ProfilerString8View Name() const;
- const MarkerCategory& Category() const {
+ ProfilerString8View Name() const override;
+ const MarkerCategory& Category() const override {
return baseprofiler::category::MEDIA_RT;
}
@@ -175,7 +197,7 @@ class CaptureStage {
mutable Maybe<nsCString> mName;
};
-class CopyVideoStage {
+class CopyVideoStage : public StageBase {
public:
CopyVideoStage(nsCString aSource, TrackingId aTrackingId, int32_t aWidth,
int32_t aHeight)
@@ -184,8 +206,8 @@ class CopyVideoStage {
mWidth(aWidth),
mHeight(aHeight) {}
- ProfilerString8View Name() const;
- const MarkerCategory& Category() const {
+ ProfilerString8View Name() const override;
+ const MarkerCategory& Category() const override {
return baseprofiler::category::MEDIA_RT;
}
@@ -201,7 +223,7 @@ class CopyVideoStage {
mutable Maybe<nsCString> mName;
};
-class DecodeStage {
+class DecodeStage : public StageBase {
public:
enum ImageFormat : uint8_t {
YUV420P,
@@ -223,8 +245,8 @@ class DecodeStage {
: mSource(std::move(aSource)),
mTrackingId(std::move(aTrackingId)),
mFlag(aFlag) {}
- ProfilerString8View Name() const;
- const MarkerCategory& Category() const {
+ ProfilerString8View Name() const override;
+ const MarkerCategory& Category() const override {
return baseprofiler::category::MEDIA_PLAYBACK;
}
@@ -242,6 +264,11 @@ class DecodeStage {
void SetColorDepth(gfx::ColorDepth aColorDepth) {
mColorDepth = Some(aColorDepth);
}
+ void SetStartTimeAndEndTime(uint64_t aStartTime, uint64_t aEndTime) {
+ mStartAndEndTimeUs =
+ Some(std::pair<uint64_t, uint64_t>{aStartTime, aEndTime});
+ }
+ void AddMarker(MarkerOptions&& aOption) override;
// The name of the source that performs this stage.
nsCString mSource;
@@ -256,6 +283,7 @@ class DecodeStage {
Maybe<gfx::ColorRange> mColorRange;
Maybe<gfx::ColorDepth> mColorDepth;
mutable Maybe<nsCString> mName;
+ Maybe<std::pair<uint64_t, uint64_t>> mStartAndEndTimeUs;
};
class PerformanceRecorderBase {
@@ -325,9 +353,7 @@ class PerformanceRecorderImpl : public PerformanceRecorderBase {
MOZ_ASSERT(elapsedTimeUs >= 0, "Elapsed time can't be less than 0!");
aStageMutator(stage);
AUTO_PROFILER_STATS(PROFILER_MARKER_UNTYPED);
- profiler_add_marker(
- stage.Name(), stage.Category(),
- MarkerOptions(MarkerTiming::Interval(startTime, now)));
+ stage.AddMarker(MarkerOptions(MarkerTiming::Interval(startTime, now)));
}
return static_cast<float>(elapsedTimeUs);
}
diff --git a/dom/media/utils/TelemetryProbesReporter.cpp b/dom/media/utils/TelemetryProbesReporter.cpp
index 377cee9abc..e702ae14c5 100644
--- a/dom/media/utils/TelemetryProbesReporter.cpp
+++ b/dom/media/utils/TelemetryProbesReporter.cpp
@@ -7,6 +7,7 @@
#include <cmath>
#include "FrameStatistics.h"
+#include "MediaCodecsSupport.h"
#include "VideoUtils.h"
#include "mozilla/EMEUtils.h"
#include "mozilla/Logging.h"
@@ -791,5 +792,32 @@ double TelemetryProbesReporter::GetAudiblePlayTimeInSeconds() const {
return GetTotalAudioPlayTimeInSeconds() - GetInaudiblePlayTimeInSeconds();
}
+/* static */
+void TelemetryProbesReporter::ReportDeviceMediaCodecSupported(
+ const media::MediaCodecsSupported& aSupported) {
+ static bool sReported = false;
+ if (sReported) {
+ return;
+ }
+ MOZ_ASSERT(ContainHardwareCodecsSupported(aSupported));
+ sReported = true;
+
+ glean::media_playback::device_hardware_decoder_support.Get("h264"_ns).Set(
+ aSupported.contains(
+ mozilla::media::MediaCodecsSupport::H264HardwareDecode));
+ glean::media_playback::device_hardware_decoder_support.Get("vp8"_ns).Set(
+ aSupported.contains(
+ mozilla::media::MediaCodecsSupport::VP8HardwareDecode));
+ glean::media_playback::device_hardware_decoder_support.Get("vp9"_ns).Set(
+ aSupported.contains(
+ mozilla::media::MediaCodecsSupport::VP9HardwareDecode));
+ glean::media_playback::device_hardware_decoder_support.Get("av1"_ns).Set(
+ aSupported.contains(
+ mozilla::media::MediaCodecsSupport::AV1HardwareDecode));
+ glean::media_playback::device_hardware_decoder_support.Get("hevc"_ns).Set(
+ aSupported.contains(
+ mozilla::media::MediaCodecsSupport::HEVCHardwareDecode));
+}
+
#undef LOG
} // namespace mozilla
diff --git a/dom/media/utils/TelemetryProbesReporter.h b/dom/media/utils/TelemetryProbesReporter.h
index 43e05dcadd..2f0e7f1b44 100644
--- a/dom/media/utils/TelemetryProbesReporter.h
+++ b/dom/media/utils/TelemetryProbesReporter.h
@@ -5,6 +5,7 @@
#ifndef DOM_TelemetryProbesReporter_H_
#define DOM_TelemetryProbesReporter_H_
+#include "MediaCodecsSupport.h"
#include "MediaInfo.h"
#include "mozilla/Maybe.h"
#include "mozilla/AwakeTimeStamp.h"
@@ -56,6 +57,9 @@ class TelemetryProbesReporter final {
using AudibleState = dom::AudioChannelService::AudibleState;
+ static void ReportDeviceMediaCodecSupported(
+ const media::MediaCodecsSupported& aSupported);
+
// State transitions
void OnPlay(Visibility aVisibility, MediaContent aContent, bool aIsMuted);
void OnPause(Visibility aVisibility);
diff --git a/dom/media/webaudio/FFTBlock.cpp b/dom/media/webaudio/FFTBlock.cpp
index 79fb934a00..eeeaf1061e 100644
--- a/dom/media/webaudio/FFTBlock.cpp
+++ b/dom/media/webaudio/FFTBlock.cpp
@@ -51,7 +51,8 @@ FFTBlock* FFTBlock::CreateInterpolatedBlock(const FFTBlock& block0,
const FFTBlock& block1,
double interp) {
uint32_t fftSize = block0.FFTSize();
- FFTBlock* newBlock = new FFTBlock(fftSize, 1.0f / AssertedCast<float>(fftSize));
+ FFTBlock* newBlock =
+ new FFTBlock(fftSize, 1.0f / AssertedCast<float>(fftSize));
newBlock->InterpolateFrequencyComponents(block0, block1, interp);
diff --git a/dom/media/webcodecs/DecoderAgent.cpp b/dom/media/webcodecs/DecoderAgent.cpp
index 095852c01d..f7a539fa18 100644
--- a/dom/media/webcodecs/DecoderAgent.cpp
+++ b/dom/media/webcodecs/DecoderAgent.cpp
@@ -96,16 +96,18 @@ RefPtr<DecoderAgent::ConfigurePromise> DecoderAgent::Configure(
auto params = CreateDecoderParams{
*mInfo,
CreateDecoderParams::OptionSet(
- CreateDecoderParams::Option::LowLatency,
aPreferSoftwareDecoder
? CreateDecoderParams::Option::HardwareDecoderNotAllowed
: CreateDecoderParams::Option::Default),
mInfo->GetType(), mImageContainer, knowsCompositor};
+ if (aLowLatency) {
+ params.mOptions += CreateDecoderParams::Option::LowLatency;
+ }
LOG("DecoderAgent #%d (%p) is creating a decoder - PreferSW: %s, "
- "low-latency: %syes",
+ "low-latency: %s",
mId, this, aPreferSoftwareDecoder ? "yes" : "no",
- aLowLatency ? "" : "forcibly ");
+ aLowLatency ? "yes" : "no");
RefPtr<ConfigurePromise> p = mConfigurePromise.Ensure(__func__);
diff --git a/dom/media/webcodecs/DecoderTemplate.cpp b/dom/media/webcodecs/DecoderTemplate.cpp
index 2fc2471a24..896f83b352 100644
--- a/dom/media/webcodecs/DecoderTemplate.cpp
+++ b/dom/media/webcodecs/DecoderTemplate.cpp
@@ -85,28 +85,26 @@ DecoderTemplate<DecoderType>::ConfigureMessage::Create(
template <typename DecoderType>
DecoderTemplate<DecoderType>::DecodeMessage::DecodeMessage(
- Id aId, ConfigId aConfigId, UniquePtr<InputTypeInternal>&& aData)
+ SeqId aSeqId, ConfigId aConfigId, UniquePtr<InputTypeInternal>&& aData)
: ControlMessage(
- nsPrintfCString("decode #%zu (config #%d)", aId, aConfigId)),
- mId(aId),
+ nsPrintfCString("decode #%zu (config #%d)", aSeqId, aConfigId)),
+ mSeqId(aSeqId),
mData(std::move(aData)) {}
-template <typename DecoderType>
-DecoderTemplate<DecoderType>::FlushMessage::FlushMessage(Id aId,
- ConfigId aConfigId,
- Promise* aPromise)
- : ControlMessage(
- nsPrintfCString("flush #%zu (config #%d)", aId, aConfigId)),
- mId(aId),
- mPromise(aPromise) {}
+static int64_t GenerateUniqueId() {
+ // This needs to be atomic since this can run on the main thread or worker
+ // thread.
+ static std::atomic<int64_t> sNextId = 0;
+ return ++sNextId;
+}
template <typename DecoderType>
-void DecoderTemplate<DecoderType>::FlushMessage::RejectPromiseIfAny(
- const nsresult& aReason) {
- if (mPromise) {
- mPromise->MaybeReject(aReason);
- }
-}
+DecoderTemplate<DecoderType>::FlushMessage::FlushMessage(SeqId aSeqId,
+ ConfigId aConfigId)
+ : ControlMessage(
+ nsPrintfCString("flush #%zu (config #%d)", aSeqId, aConfigId)),
+ mSeqId(aSeqId),
+ mUniqueId(GenerateUniqueId()) {}
/*
* Below are DecoderTemplate implementation
@@ -221,10 +219,16 @@ already_AddRefed<Promise> DecoderTemplate<DecoderType>::Flush(
mKeyChunkRequired = true;
- mControlMessageQueue.emplace(UniquePtr<ControlMessage>(
- new FlushMessage(++mFlushCounter, mLatestConfigureId, p)));
- LOG("%s %p enqueues %s", DecoderType::Name.get(), this,
- mControlMessageQueue.back()->ToString().get());
+ auto msg = UniquePtr<ControlMessage>(
+ new FlushMessage(++mFlushCounter, mLatestConfigureId));
+ const auto flushPromiseId = msg->AsFlushMessage()->mUniqueId;
+ MOZ_ASSERT(!mPendingFlushPromises.Contains(flushPromiseId));
+ mPendingFlushPromises.Insert(flushPromiseId, p);
+
+ mControlMessageQueue.emplace(std::move(msg));
+
+ LOG("%s %p enqueues %s, with unique id %" PRId64, DecoderType::Name.get(),
+ this, mControlMessageQueue.back()->ToString().get(), flushPromiseId);
ProcessControlMessageQueue();
return p.forget();
}
@@ -264,7 +268,7 @@ Result<Ok, nsresult> DecoderTemplate<DecoderType>::ResetInternal(
mDecodeCounter = 0;
mFlushCounter = 0;
- CancelPendingControlMessages(aResult);
+ CancelPendingControlMessagesAndFlushPromises(aResult);
DestroyDecoderAgentIfAny();
if (mDecodeQueueSize > 0) {
@@ -390,7 +394,7 @@ void DecoderTemplate<DecoderType>::ProcessControlMessageQueue() {
}
template <typename DecoderType>
-void DecoderTemplate<DecoderType>::CancelPendingControlMessages(
+void DecoderTemplate<DecoderType>::CancelPendingControlMessagesAndFlushPromises(
const nsresult& aResult) {
AssertIsOnOwningThread();
@@ -399,11 +403,6 @@ void DecoderTemplate<DecoderType>::CancelPendingControlMessages(
LOG("%s %p cancels current %s", DecoderType::Name.get(), this,
mProcessingMessage->ToString().get());
mProcessingMessage->Cancel();
-
- if (FlushMessage* flush = mProcessingMessage->AsFlushMessage()) {
- flush->RejectPromiseIfAny(aResult);
- }
-
mProcessingMessage.reset();
}
@@ -411,14 +410,18 @@ void DecoderTemplate<DecoderType>::CancelPendingControlMessages(
while (!mControlMessageQueue.empty()) {
LOG("%s %p cancels pending %s", DecoderType::Name.get(), this,
mControlMessageQueue.front()->ToString().get());
-
MOZ_ASSERT(!mControlMessageQueue.front()->IsProcessing());
- if (FlushMessage* flush = mControlMessageQueue.front()->AsFlushMessage()) {
- flush->RejectPromiseIfAny(aResult);
- }
-
mControlMessageQueue.pop();
}
+
+ // If there are pending flush promises, reject them.
+ mPendingFlushPromises.ForEach(
+ [&](const int64_t& id, const RefPtr<Promise>& p) {
+ LOG("%s %p, reject the promise for flush %" PRId64 " (unique id)",
+ DecoderType::Name.get(), this, id);
+ p->MaybeReject(aResult);
+ });
+ mPendingFlushPromises.Clear();
}
template <typename DecoderType>
@@ -565,7 +568,6 @@ MessageProcessedResult DecoderTemplate<DecoderType>::ProcessDecodeMessage(
mProcessingMessage.reset();
QueueATask("Error during decode",
[self = RefPtr{this}]() MOZ_CAN_RUN_SCRIPT_BOUNDARY {
- MOZ_ASSERT(self->mState != CodecState::Closed);
self->CloseInternal(NS_ERROR_DOM_ENCODING_NOT_SUPPORTED_ERR);
});
return MessageProcessedResult::Processed;
@@ -696,6 +698,8 @@ MessageProcessedResult DecoderTemplate<DecoderType>::ProcessFlushMessage(
msg->Complete();
+ const auto flushPromiseId = msg->mUniqueId;
+
// If flush failed, it means decoder fails to decode the data
// sent before, so we treat it like decode error. We reject
// the promise first and then queue a task to close
@@ -705,14 +709,15 @@ MessageProcessedResult DecoderTemplate<DecoderType>::ProcessFlushMessage(
LOGE("%s %p, DecoderAgent #%d failed to flush: %s",
DecoderType::Name.get(), self.get(), id,
error.Description().get());
- RefPtr<Promise> promise = msg->TakePromise();
// Reject with an EncodingError instead of the error we got
// above.
self->QueueATask(
"Error during flush runnable",
- [self = RefPtr{this}, promise]() MOZ_CAN_RUN_SCRIPT_BOUNDARY {
- promise->MaybeReject(
- NS_ERROR_DOM_ENCODING_NOT_SUPPORTED_ERR);
+ [self = RefPtr{this}]() MOZ_CAN_RUN_SCRIPT_BOUNDARY {
+ // If Reset() was invoked before this task executes, the
+ // promise in mPendingFlushPromises is handled there.
+ // Otherwise, the promise is going to be rejected by
+ // CloseInternal() below.
self->mProcessingMessage.reset();
MOZ_ASSERT(self->mState != CodecState::Closed);
self->CloseInternal(
@@ -733,14 +738,23 @@ MessageProcessedResult DecoderTemplate<DecoderType>::ProcessFlushMessage(
msgStr.get());
}
- RefPtr<Promise> promise = msg->TakePromise();
self->QueueATask(
"Flush: output decoding data task",
- [self = RefPtr{self}, promise, data = std::move(data)]()
- MOZ_CAN_RUN_SCRIPT_BOUNDARY {
- self->OutputDecodedData(std::move(data));
- promise->MaybeResolveWithUndefined();
- });
+ [self = RefPtr{self}, data = std::move(data),
+ flushPromiseId]() MOZ_CAN_RUN_SCRIPT_BOUNDARY {
+ self->OutputDecodedData(std::move(data));
+ // If Reset() was invoked before this task executes, or
+ // during the output callback above in the execution of this
+ // task, the promise in mPendingFlushPromises is handled
+ // there. Otherwise, the promise is resolved here.
+ if (Maybe<RefPtr<Promise>> p =
+ self->mPendingFlushPromises.Take(flushPromiseId)) {
+ LOG("%s %p, resolving the promise for flush %" PRId64
+ " (unique id)",
+ DecoderType::Name.get(), self.get(), flushPromiseId);
+ p.value()->MaybeResolveWithUndefined();
+ }
+ });
self->mProcessingMessage.reset();
self->ProcessControlMessageQueue();
})
diff --git a/dom/media/webcodecs/DecoderTemplate.h b/dom/media/webcodecs/DecoderTemplate.h
index fe0cb5baee..69d4e2f03d 100644
--- a/dom/media/webcodecs/DecoderTemplate.h
+++ b/dom/media/webcodecs/DecoderTemplate.h
@@ -9,6 +9,8 @@
#include <queue>
+#include "SimpleMap.h"
+#include "WebCodecsUtils.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/DecoderAgent.h"
#include "mozilla/MozPromise.h"
@@ -18,7 +20,6 @@
#include "mozilla/dom/WorkerRef.h"
#include "mozilla/media/MediaUtils.h"
#include "nsStringFwd.h"
-#include "WebCodecsUtils.h"
namespace mozilla {
@@ -76,7 +77,8 @@ class DecoderTemplate : public DOMEventTargetHelper {
const ConfigTypeInternal& Config() { return *mConfig; }
UniquePtr<ConfigTypeInternal> TakeConfig() { return std::move(mConfig); }
- const Id mId; // A unique id shown in log.
+ // The id of a configure request.
+ const Id mId;
private:
ConfigureMessage(Id aId, UniquePtr<ConfigTypeInternal>&& aConfig);
@@ -88,16 +90,18 @@ class DecoderTemplate : public DOMEventTargetHelper {
: public ControlMessage,
public MessageRequestHolder<DecoderAgent::DecodePromise> {
public:
- using Id = size_t;
+ using SeqId = size_t;
using ConfigId = typename Self::ConfigureMessage::Id;
- DecodeMessage(Id aId, ConfigId aConfigId,
+ DecodeMessage(SeqId aSeqId, ConfigId aConfigId,
UniquePtr<InputTypeInternal>&& aData);
~DecodeMessage() = default;
virtual void Cancel() override { Disconnect(); }
virtual bool IsProcessing() override { return Exists(); };
virtual DecodeMessage* AsDecodeMessage() override { return this; }
- const Id mId; // A unique id shown in log.
+ // The sequence id of a decode request associated with a specific
+ // configuration.
+ const SeqId mSeqId;
UniquePtr<InputTypeInternal> mData;
};
@@ -105,20 +109,18 @@ class DecoderTemplate : public DOMEventTargetHelper {
: public ControlMessage,
public MessageRequestHolder<DecoderAgent::DecodePromise> {
public:
- using Id = size_t;
+ using SeqId = size_t;
using ConfigId = typename Self::ConfigureMessage::Id;
- FlushMessage(Id aId, ConfigId aConfigId, Promise* aPromise);
+ FlushMessage(SeqId aSeqId, ConfigId aConfigId);
~FlushMessage() = default;
virtual void Cancel() override { Disconnect(); }
virtual bool IsProcessing() override { return Exists(); };
virtual FlushMessage* AsFlushMessage() override { return this; }
- already_AddRefed<Promise> TakePromise() { return mPromise.forget(); }
- void RejectPromiseIfAny(const nsresult& aReason);
-
- const Id mId; // A unique id shown in log.
- private:
- RefPtr<Promise> mPromise;
+ // The sequence id of a flush request associated with a specific
+ // configuration.
+ const SeqId mSeqId;
+ const int64_t mUniqueId;
};
protected:
@@ -176,7 +178,7 @@ class DecoderTemplate : public DOMEventTargetHelper {
nsresult FireEvent(nsAtom* aTypeWithOn, const nsAString& aEventType);
void ProcessControlMessageQueue();
- void CancelPendingControlMessages(const nsresult& aResult);
+ void CancelPendingControlMessagesAndFlushPromises(const nsresult& aResult);
// Queue a task to the control thread. This is to be used when a task needs to
// perform multiple steps.
@@ -209,6 +211,11 @@ class DecoderTemplate : public DOMEventTargetHelper {
std::queue<UniquePtr<ControlMessage>> mControlMessageQueue;
UniquePtr<ControlMessage> mProcessingMessage;
+ // When a flush request is initiated, a promise is created and stored in
+ // mPendingFlushPromises until it is settled in the task delivering the flush
+ // result or Reset() is called before the promise is settled.
+ SimpleMap<int64_t, RefPtr<Promise>> mPendingFlushPromises;
+
uint32_t mDecodeQueueSize;
bool mDequeueEventScheduled;
@@ -216,10 +223,10 @@ class DecoderTemplate : public DOMEventTargetHelper {
// DecoderAgent's Id.
uint32_t mLatestConfigureId;
// Tracking how many decode data has been enqueued and this number will be
- // used as the DecodeMessage's Id.
+ // used as the DecodeMessage's sequence Id.
size_t mDecodeCounter;
// Tracking how many flush request has been enqueued and this number will be
- // used as the FlushMessage's Id.
+ // used as the FlushMessage's sequence Id.
size_t mFlushCounter;
// DecoderAgent will be created every time "configure" is being processed, and
diff --git a/dom/media/webcodecs/EncoderTemplate.cpp b/dom/media/webcodecs/EncoderTemplate.cpp
index 34edfae822..2f70380519 100644
--- a/dom/media/webcodecs/EncoderTemplate.cpp
+++ b/dom/media/webcodecs/EncoderTemplate.cpp
@@ -71,16 +71,8 @@ EncoderTemplate<EncoderType>::EncodeMessage::EncodeMessage(
template <typename EncoderType>
EncoderTemplate<EncoderType>::FlushMessage::FlushMessage(
- WebCodecsId aConfigureId, Promise* aPromise)
- : ControlMessage(aConfigureId), mPromise(aPromise) {}
-
-template <typename EncoderType>
-void EncoderTemplate<EncoderType>::FlushMessage::RejectPromiseIfAny(
- const nsresult& aReason) {
- if (mPromise) {
- mPromise->MaybeReject(aReason);
- }
-}
+ WebCodecsId aConfigureId)
+ : ControlMessage(aConfigureId) {}
/*
* Below are EncoderTemplate implementation
@@ -215,7 +207,13 @@ already_AddRefed<Promise> EncoderTemplate<EncoderType>::Flush(
return p.forget();
}
- mControlMessageQueue.push(MakeRefPtr<FlushMessage>(mLatestConfigureId, p));
+ auto msg = MakeRefPtr<FlushMessage>(mLatestConfigureId);
+ const auto flushPromiseId = static_cast<int64_t>(msg->mMessageId);
+ MOZ_ASSERT(!mPendingFlushPromises.Contains(flushPromiseId));
+ mPendingFlushPromises.Insert(flushPromiseId, p);
+
+ mControlMessageQueue.emplace(std::move(msg));
+
LOG("%s %p enqueues %s", EncoderType::Name.get(), this,
mControlMessageQueue.back()->ToString().get());
ProcessControlMessageQueue();
@@ -259,7 +257,7 @@ Result<Ok, nsresult> EncoderTemplate<EncoderType>::ResetInternal(
mEncodeCounter = 0;
mFlushCounter = 0;
- CancelPendingControlMessages(aResult);
+ CancelPendingControlMessagesAndFlushPromises(aResult);
DestroyEncoderAgentIfAny();
if (mEncodeQueueSize > 0) {
@@ -403,8 +401,8 @@ void EncoderTemplate<VideoEncoderTraits>::OutputEncodedVideoData(
metadata.mDecoderConfig.Construct(std::move(decoderConfig));
mOutputNewDecoderConfig = false;
- LOGE("New config passed to output callback: %s",
- decoderConfigInternal.ToString().get());
+ LOG("New config passed to output callback: %s",
+ decoderConfigInternal.ToString().get());
}
nsAutoCString metadataInfo;
@@ -462,7 +460,7 @@ void EncoderTemplate<AudioEncoderTraits>::OutputEncodedAudioData(
this->EncoderConfigToDecoderConfig(GetParentObject(), data,
*mActiveConfig);
- // Convert VideoDecoderConfigInternal to VideoDecoderConfig
+ // Convert AudioDecoderConfigInternal to AudioDecoderConfig
RootedDictionary<AudioDecoderConfig> decoderConfig(cx);
decoderConfig.mCodec = decoderConfigInternal.mCodec;
decoderConfig.mNumberOfChannels = decoderConfigInternal.mNumberOfChannels;
@@ -473,8 +471,8 @@ void EncoderTemplate<AudioEncoderTraits>::OutputEncodedAudioData(
metadata.mDecoderConfig.Construct(std::move(decoderConfig));
mOutputNewDecoderConfig = false;
- LOGE("New config passed to output callback: %s",
- decoderConfigInternal.ToString().get());
+ LOG("New config passed to output callback: %s",
+ decoderConfigInternal.ToString().get());
}
nsAutoCString metadataInfo;
@@ -578,7 +576,7 @@ void EncoderTemplate<EncoderType>::ProcessControlMessageQueue() {
}
template <typename EncoderType>
-void EncoderTemplate<EncoderType>::CancelPendingControlMessages(
+void EncoderTemplate<EncoderType>::CancelPendingControlMessagesAndFlushPromises(
const nsresult& aResult) {
AssertIsOnOwningThread();
@@ -587,11 +585,6 @@ void EncoderTemplate<EncoderType>::CancelPendingControlMessages(
LOG("%s %p cancels current %s", EncoderType::Name.get(), this,
mProcessingMessage->ToString().get());
mProcessingMessage->Cancel();
-
- if (RefPtr<FlushMessage> flush = mProcessingMessage->AsFlushMessage()) {
- flush->RejectPromiseIfAny(aResult);
- }
-
mProcessingMessage = nullptr;
}
@@ -601,13 +594,17 @@ void EncoderTemplate<EncoderType>::CancelPendingControlMessages(
mControlMessageQueue.front()->ToString().get());
MOZ_ASSERT(!mControlMessageQueue.front()->IsProcessing());
- if (RefPtr<FlushMessage> flush =
- mControlMessageQueue.front()->AsFlushMessage()) {
- flush->RejectPromiseIfAny(aResult);
- }
-
mControlMessageQueue.pop();
}
+
+ // If there are pending flush promises, reject them.
+ mPendingFlushPromises.ForEach(
+ [&](const int64_t& id, const RefPtr<Promise>& p) {
+ LOG("%s %p, reject the promise for flush %" PRId64,
+ EncoderType::Name.get(), this, id);
+ p->MaybeReject(aResult);
+ });
+ mPendingFlushPromises.Clear();
}
template <typename EncoderType>
@@ -1020,78 +1017,88 @@ MessageProcessedResult EncoderTemplate<EncoderType>::ProcessFlushMessage(
}
mAgent->Drain()
- ->Then(
- GetCurrentSerialEventTarget(), __func__,
- [self = RefPtr{this}, id = mAgent->mId, aMessage,
- this](EncoderAgent::EncodePromise::ResolveOrRejectValue&& aResult) {
- MOZ_ASSERT(self->mProcessingMessage);
- MOZ_ASSERT(self->mProcessingMessage->AsFlushMessage());
- MOZ_ASSERT(self->mState == CodecState::Configured);
- MOZ_ASSERT(self->mAgent);
- MOZ_ASSERT(id == self->mAgent->mId);
- MOZ_ASSERT(self->mActiveConfig);
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [self = RefPtr{this}, id = mAgent->mId, aMessage, this](
+ EncoderAgent::EncodePromise::ResolveOrRejectValue&& aResult) {
+ MOZ_ASSERT(self->mProcessingMessage);
+ MOZ_ASSERT(self->mProcessingMessage->AsFlushMessage());
+ MOZ_ASSERT(self->mState == CodecState::Configured);
+ MOZ_ASSERT(self->mAgent);
+ MOZ_ASSERT(id == self->mAgent->mId);
+ MOZ_ASSERT(self->mActiveConfig);
- LOG("%s %p, EncoderAgent #%zu %s has been %s",
- EncoderType::Name.get(), self.get(), id,
- aMessage->ToString().get(),
- aResult.IsResolve() ? "resolved" : "rejected");
+ LOG("%s %p, EncoderAgent #%zu %s has been %s",
+ EncoderType::Name.get(), self.get(), id,
+ aMessage->ToString().get(),
+ aResult.IsResolve() ? "resolved" : "rejected");
- nsCString msgStr = aMessage->ToString();
+ nsCString msgStr = aMessage->ToString();
- aMessage->Complete();
+ aMessage->Complete();
- // If flush failed, it means encoder fails to encode the data
- // sent before, so we treat it like an encode error. We reject
- // the promise first and then queue a task to close VideoEncoder
- // with an EncodingError.
- if (aResult.IsReject()) {
- const MediaResult& error = aResult.RejectValue();
- LOGE("%s %p, EncoderAgent #%zu failed to flush: %s",
- EncoderType::Name.get(), self.get(), id,
- error.Description().get());
- RefPtr<Promise> promise = aMessage->TakePromise();
- // Reject with an EncodingError instead of the error we got
- // above.
- self->QueueATask(
- "Error during flush runnable",
- [self = RefPtr{this}, promise]() MOZ_CAN_RUN_SCRIPT_BOUNDARY {
- promise->MaybeReject(
- NS_ERROR_DOM_ENCODING_NOT_SUPPORTED_ERR);
- self->mProcessingMessage = nullptr;
- MOZ_ASSERT(self->mState != CodecState::Closed);
- self->CloseInternal(
- NS_ERROR_DOM_ENCODING_NOT_SUPPORTED_ERR);
- });
- return;
- }
+ // If flush failed, it means encoder fails to encode the data
+ // sent before, so we treat it like an encode error. We reject
+ // the promise first and then queue a task to close VideoEncoder
+ // with an EncodingError.
+ if (aResult.IsReject()) {
+ const MediaResult& error = aResult.RejectValue();
+ LOGE("%s %p, EncoderAgent #%zu failed to flush: %s",
+ EncoderType::Name.get(), self.get(), id,
+ error.Description().get());
+ // Reject with an EncodingError instead of the error we got
+ // above.
+ self->QueueATask(
+ "Error during flush runnable",
+ [self = RefPtr{this}]() MOZ_CAN_RUN_SCRIPT_BOUNDARY {
+ // If Reset() was invoked before this task executes, the
+ // promise in mPendingFlushPromises is handled there.
+ // Otherwise, the promise is going to be rejected by
+ // CloseInternal() below.
+ self->mProcessingMessage = nullptr;
+ MOZ_ASSERT(self->mState != CodecState::Closed);
+ self->CloseInternal(
+ NS_ERROR_DOM_ENCODING_NOT_SUPPORTED_ERR);
+ });
+ return;
+ }
- // If flush succeeded, schedule to output encoded data first
- // and then resolve the promise, then keep processing the
- // control messages.
- MOZ_ASSERT(aResult.IsResolve());
- nsTArray<RefPtr<MediaRawData>> data =
- std::move(aResult.ResolveValue());
-
- if (data.IsEmpty()) {
- LOG("%s %p gets no data for %s", EncoderType::Name.get(),
- self.get(), msgStr.get());
- } else {
- LOG("%s %p, schedule %zu encoded data output for %s",
- EncoderType::Name.get(), self.get(), data.Length(),
- msgStr.get());
- }
+ // If flush succeeded, schedule to output encoded data first
+ // and then resolve the promise, then keep processing the
+ // control messages.
+ MOZ_ASSERT(aResult.IsResolve());
+ nsTArray<RefPtr<MediaRawData>> data =
+ std::move(aResult.ResolveValue());
- RefPtr<Promise> promise = aMessage->TakePromise();
- self->QueueATask(
- "Flush: output encoded data task",
- [self = RefPtr{self}, promise, data = std::move(data)]()
- MOZ_CAN_RUN_SCRIPT_BOUNDARY {
- self->OutputEncodedData(std::move(data));
- promise->MaybeResolveWithUndefined();
- });
- self->mProcessingMessage = nullptr;
- self->ProcessControlMessageQueue();
- })
+ if (data.IsEmpty()) {
+ LOG("%s %p gets no data for %s", EncoderType::Name.get(),
+ self.get(), msgStr.get());
+ } else {
+ LOG("%s %p, schedule %zu encoded data output for %s",
+ EncoderType::Name.get(), self.get(), data.Length(),
+ msgStr.get());
+ }
+
+ const auto flushPromiseId =
+ static_cast<int64_t>(aMessage->mMessageId);
+ self->QueueATask(
+ "Flush: output encoded data task",
+ [self = RefPtr{self}, data = std::move(data),
+ flushPromiseId]() MOZ_CAN_RUN_SCRIPT_BOUNDARY {
+ self->OutputEncodedData(std::move(data));
+ // If Reset() was invoked before this task executes, or
+ // during the output callback above in the execution of
+ // this task, the promise in mPendingFlushPromises is
+ // handled there. Otherwise, the promise is resolved here.
+ if (Maybe<RefPtr<Promise>> p =
+ self->mPendingFlushPromises.Take(flushPromiseId)) {
+ LOG("%s %p, resolving the promise for flush %" PRId64,
+ EncoderType::Name.get(), self.get(), flushPromiseId);
+ p.value()->MaybeResolveWithUndefined();
+ }
+ });
+ self->mProcessingMessage = nullptr;
+ self->ProcessControlMessageQueue();
+ })
->Track(aMessage->Request());
return MessageProcessedResult::Processed;
diff --git a/dom/media/webcodecs/EncoderTemplate.h b/dom/media/webcodecs/EncoderTemplate.h
index bc65edca46..ecadf68681 100644
--- a/dom/media/webcodecs/EncoderTemplate.h
+++ b/dom/media/webcodecs/EncoderTemplate.h
@@ -11,14 +11,15 @@
#include "EncoderAgent.h"
#include "MediaData.h"
+#include "SimpleMap.h"
#include "WebCodecsUtils.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/MozPromise.h"
#include "mozilla/RefPtr.h"
#include "mozilla/Result.h"
#include "mozilla/UniquePtr.h"
-#include "mozilla/dom/VideoEncoderBinding.h"
#include "mozilla/dom/AudioEncoderBinding.h"
+#include "mozilla/dom/VideoEncoderBinding.h"
#include "mozilla/dom/WorkerRef.h"
#include "mozilla/media/MediaUtils.h"
#include "nsStringFwd.h"
@@ -116,12 +117,10 @@ class EncoderTemplate : public DOMEventTargetHelper {
: public ControlMessage,
public MessageRequestHolder<EncoderAgent::EncodePromise> {
public:
- FlushMessage(WebCodecsId aConfigureId, Promise* aPromise);
+ explicit FlushMessage(WebCodecsId aConfigureId);
virtual void Cancel() override { Disconnect(); }
virtual bool IsProcessing() override { return Exists(); };
virtual RefPtr<FlushMessage> AsFlushMessage() override { return this; }
- already_AddRefed<Promise> TakePromise() { return mPromise.forget(); }
- void RejectPromiseIfAny(const nsresult& aReason);
nsCString ToString() const override {
nsCString rv;
@@ -129,9 +128,6 @@ class EncoderTemplate : public DOMEventTargetHelper {
this->mMessageId);
return rv;
}
-
- private:
- RefPtr<Promise> mPromise;
};
protected:
@@ -207,7 +203,7 @@ class EncoderTemplate : public DOMEventTargetHelper {
const nsresult& aResult);
void ProcessControlMessageQueue();
- void CancelPendingControlMessages(const nsresult& aResult);
+ void CancelPendingControlMessagesAndFlushPromises(const nsresult& aResult);
template <typename Func>
void QueueATask(const char* aName, Func&& aSteps);
@@ -236,6 +232,11 @@ class EncoderTemplate : public DOMEventTargetHelper {
std::queue<RefPtr<ControlMessage>> mControlMessageQueue;
RefPtr<ControlMessage> mProcessingMessage;
+ // When a flush request is initiated, a promise is created and stored in
+ // mPendingFlushPromises until it is settled in the task delivering the flush
+ // result or Reset() is called before the promise is settled.
+ SimpleMap<int64_t, RefPtr<Promise>> mPendingFlushPromises;
+
uint32_t mEncodeQueueSize;
bool mDequeueEventScheduled;
diff --git a/dom/media/webcodecs/VideoEncoder.cpp b/dom/media/webcodecs/VideoEncoder.cpp
index 5407e917b6..4ce74fa0cb 100644
--- a/dom/media/webcodecs/VideoEncoder.cpp
+++ b/dom/media/webcodecs/VideoEncoder.cpp
@@ -343,25 +343,16 @@ static bool CanEncode(const RefPtr<VideoEncoderConfigInternal>& aConfig) {
if (!IsSupportedVideoCodec(parsedCodecString)) {
return false;
}
-
- // TODO (bug 1872879, bug 1872880): Support this on Windows and Mac.
if (aConfig->mScalabilityMode.isSome()) {
- // We only support L1T2 and L1T3 ScalabilityMode in VP8 and VP9 encoders on
- // Linux.
- bool supported = IsOnLinux() && (IsVP8CodecString(parsedCodecString) ||
- IsVP9CodecString(parsedCodecString))
- ? aConfig->mScalabilityMode->EqualsLiteral("L1T2") ||
- aConfig->mScalabilityMode->EqualsLiteral("L1T3")
- : false;
-
- if (!supported) {
+ // Check if ScalabilityMode string is valid.
+ if (!aConfig->mScalabilityMode->EqualsLiteral("L1T2") &&
+ !aConfig->mScalabilityMode->EqualsLiteral("L1T3")) {
LOGE("Scalability mode %s not supported for codec: %s",
NS_ConvertUTF16toUTF8(aConfig->mScalabilityMode.value()).get(),
NS_ConvertUTF16toUTF8(parsedCodecString).get());
return false;
}
}
-
return EncoderSupport::Supports(aConfig);
}
diff --git a/dom/media/webcodecs/crashtests/1889831.html b/dom/media/webcodecs/crashtests/1889831.html
new file mode 100644
index 0000000000..e88a028d16
--- /dev/null
+++ b/dom/media/webcodecs/crashtests/1889831.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<script>
+document.addEventListener("DOMContentLoaded", async () => {
+ const decoder = new VideoDecoder({
+ 'output': (e) => {},
+ 'error': (e) => {},
+ });
+ decoder.configure({
+ codec: 'vp8',
+ codedWidth: 320,
+ codedHeight: 240,
+ visibleRect: {x: 0, y: 0, width: 320, height: 240},
+ displayWidth: 320,
+ displayHeight: 240,
+ });
+ decoder.decode(new EncodedVideoChunk(
+ {type: 'key', timestamp: 0, data: new ArrayBuffer(0)}));
+ decoder.decode(new EncodedVideoChunk(
+ {type: 'key', timestamp: 1, data: new ArrayBuffer(0)}));
+})
+</script>
diff --git a/dom/media/webcodecs/crashtests/crashtests.list b/dom/media/webcodecs/crashtests/crashtests.list
index 16fbd90ff5..9d9a453a42 100644
--- a/dom/media/webcodecs/crashtests/crashtests.list
+++ b/dom/media/webcodecs/crashtests/crashtests.list
@@ -3,4 +3,4 @@ skip-if(Android) pref(dom.media.webcodecs.enabled,true) load 1848460.html
skip-if(Android) pref(dom.media.webcodecs.enabled,true) load 1849271.html
skip-if(Android) pref(dom.media.webcodecs.enabled,true) load 1864475.html
skip-if(Android) pref(dom.media.webcodecs.enabled,true) load 1881079.html
-
+skip-if(Android) pref(dom.media.webcodecs.enabled,true) load 1889831.html
diff --git a/dom/media/webrtc/MediaEnginePrefs.h b/dom/media/webrtc/MediaEnginePrefs.h
index de5daf0ad9..e3eff7eba5 100644
--- a/dom/media/webrtc/MediaEnginePrefs.h
+++ b/dom/media/webrtc/MediaEnginePrefs.h
@@ -28,6 +28,7 @@ class MediaEnginePrefs {
mHeight(0),
mFPS(0),
mFreq(0),
+ mUsePlatformProcessing(false),
mAecOn(false),
mUseAecMobile(false),
mAgcOn(false),
@@ -44,6 +45,7 @@ class MediaEnginePrefs {
int32_t mHeight;
int32_t mFPS;
int32_t mFreq; // for test tones (fake:true)
+ bool mUsePlatformProcessing;
bool mAecOn;
bool mUseAecMobile;
bool mAgcOn;
diff --git a/dom/media/webrtc/MediaEngineWebRTCAudio.cpp b/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
index 220dcf3bd8..c072717e00 100644
--- a/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
@@ -148,7 +148,7 @@ nsresult MediaEngineWebRTCMicrophoneSource::Reconfigure(
}
AudioProcessing::Config AudioInputProcessing::ConfigForPrefs(
- const MediaEnginePrefs& aPrefs) {
+ const MediaEnginePrefs& aPrefs) const {
AudioProcessing::Config config;
config.pipeline.multi_channel_render = true;
@@ -207,6 +207,19 @@ AudioProcessing::Config AudioInputProcessing::ConfigForPrefs(
config.high_pass_filter.enabled = aPrefs.mHPFOn;
+ if (mPlatformProcessingSetParams &
+ CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION) {
+ config.echo_canceller.enabled = false;
+ }
+ if (mPlatformProcessingSetParams &
+ CUBEB_INPUT_PROCESSING_PARAM_AUTOMATIC_GAIN_CONTROL) {
+ config.gain_controller1.enabled = config.gain_controller2.enabled = false;
+ }
+ if (mPlatformProcessingSetParams &
+ CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION) {
+ config.noise_suppression.enabled = false;
+ }
+
return config;
}
@@ -412,11 +425,45 @@ void AudioInputProcessing::Disconnect(MediaTrackGraph* aGraph) {
aGraph->AssertOnGraphThread();
}
+void AudioInputProcessing::NotifySetRequestedInputProcessingParamsResult(
+ MediaTrackGraph* aGraph, cubeb_input_processing_params aRequestedParams,
+ const Result<cubeb_input_processing_params, int>& aResult) {
+ aGraph->AssertOnGraphThread();
+ if (aRequestedParams != RequestedInputProcessingParams(aGraph)) {
+ // This is a result from an old request, wait for a more recent one.
+ return;
+ }
+ if (aResult.isOk()) {
+ if (mPlatformProcessingSetParams == aResult.inspect()) {
+ // No change.
+ return;
+ }
+ mPlatformProcessingSetError = Nothing();
+ mPlatformProcessingSetParams = aResult.inspect();
+ LOG("AudioInputProcessing %p platform processing params are now %s.", this,
+ CubebUtils::ProcessingParamsToString(mPlatformProcessingSetParams)
+ .get());
+ } else {
+ mPlatformProcessingSetError = Some(aResult.inspectErr());
+ mPlatformProcessingSetParams = CUBEB_INPUT_PROCESSING_PARAM_NONE;
+ LOG("AudioInputProcessing %p platform processing params failed to apply. "
+ "Applying input processing config in libwebrtc.",
+ this);
+ }
+ ApplySettingsInternal(aGraph, mSettings);
+}
+
bool AudioInputProcessing::IsPassThrough(MediaTrackGraph* aGraph) const {
aGraph->AssertOnGraphThread();
// The high-pass filter is not taken into account when activating the
// pass through, since it's not controllable from content.
- return !(mSettings.mAecOn || mSettings.mAgcOn || mSettings.mNoiseOn);
+ auto config = AppliedConfig(aGraph);
+ auto aec = [](const auto& config) { return config.echo_canceller.enabled; };
+ auto agc = [](const auto& config) {
+ return config.gain_controller1.enabled || config.gain_controller2.enabled;
+ };
+ auto ns = [](const auto& config) { return config.noise_suppression.enabled; };
+ return !(aec(config) || agc(config) || ns(config));
}
void AudioInputProcessing::PassThroughChanged(MediaTrackGraph* aGraph) {
@@ -438,7 +485,7 @@ void AudioInputProcessing::PassThroughChanged(MediaTrackGraph* aGraph) {
}
}
-uint32_t AudioInputProcessing::GetRequestedInputChannelCount() {
+uint32_t AudioInputProcessing::GetRequestedInputChannelCount() const {
return mSettings.mChannels;
}
@@ -668,6 +715,10 @@ void AudioInputProcessing::ProcessOutputData(AudioProcessingTrack* aTrack,
return;
}
+ if (aChunk.mDuration == 0) {
+ return;
+ }
+
TrackRate sampleRate = aTrack->mSampleRate;
uint32_t framesPerPacket = GetPacketSize(sampleRate); // in frames
// Downmix from aChannels to MAX_CHANNELS if needed.
@@ -868,6 +919,7 @@ void AudioInputProcessing::PacketizeAndProcess(AudioProcessingTrack* aTrack,
!(mPacketCount % 50)) {
AudioProcessingStats stats = mAudioProcessing->GetStatistics();
char msg[1024];
+ msg[0] = '\0';
size_t offset = 0;
#define AddIfValue(format, member) \
if (stats.member.has_value()) { \
@@ -962,14 +1014,61 @@ void AudioInputProcessing::DeviceChanged(MediaTrackGraph* aGraph) {
aGraph, aGraph->CurrentDriver(), this);
}
+cubeb_input_processing_params
+AudioInputProcessing::RequestedInputProcessingParams(
+ MediaTrackGraph* aGraph) const {
+ aGraph->AssertOnGraphThread();
+ if (!mPlatformProcessingEnabled) {
+ return CUBEB_INPUT_PROCESSING_PARAM_NONE;
+ }
+ if (mPlatformProcessingSetError) {
+ return CUBEB_INPUT_PROCESSING_PARAM_NONE;
+ }
+ cubeb_input_processing_params params = CUBEB_INPUT_PROCESSING_PARAM_NONE;
+ if (mSettings.mAecOn) {
+ params |= CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION;
+ }
+ if (mSettings.mAgcOn) {
+ params |= CUBEB_INPUT_PROCESSING_PARAM_AUTOMATIC_GAIN_CONTROL;
+ }
+ if (mSettings.mNoiseOn) {
+ params |= CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION;
+ }
+ return params;
+}
+
void AudioInputProcessing::ApplySettings(MediaTrackGraph* aGraph,
CubebUtils::AudioDeviceID aDeviceID,
const MediaEnginePrefs& aSettings) {
TRACE("AudioInputProcessing::ApplySettings");
aGraph->AssertOnGraphThread();
+ // CUBEB_ERROR_NOT_SUPPORTED means the backend does not support platform
+ // processing. In that case, leave the error in place so we don't request
+ // processing anew.
+ if (mPlatformProcessingSetError.valueOr(CUBEB_OK) !=
+ CUBEB_ERROR_NOT_SUPPORTED) {
+ mPlatformProcessingSetError = Nothing();
+ }
+
// Read previous state from mSettings.
uint32_t oldChannelCount = GetRequestedInputChannelCount();
+
+ ApplySettingsInternal(aGraph, aSettings);
+
+ if (oldChannelCount != GetRequestedInputChannelCount()) {
+ RequestedInputChannelCountChanged(aGraph, aDeviceID);
+ }
+}
+
+void AudioInputProcessing::ApplySettingsInternal(
+ MediaTrackGraph* aGraph, const MediaEnginePrefs& aSettings) {
+ TRACE("AudioInputProcessing::ApplySettingsInternal");
+ aGraph->AssertOnGraphThread();
+
+ mPlatformProcessingEnabled = aSettings.mUsePlatformProcessing;
+
+ // Read previous state from the applied config.
bool wasPassThrough = IsPassThrough(aGraph);
mSettings = aSettings;
@@ -977,14 +1076,20 @@ void AudioInputProcessing::ApplySettings(MediaTrackGraph* aGraph,
mAudioProcessing->ApplyConfig(ConfigForPrefs(aSettings));
}
- if (oldChannelCount != GetRequestedInputChannelCount()) {
- RequestedInputChannelCountChanged(aGraph, aDeviceID);
- }
if (wasPassThrough != IsPassThrough(aGraph)) {
PassThroughChanged(aGraph);
}
}
+webrtc::AudioProcessing::Config AudioInputProcessing::AppliedConfig(
+ MediaTrackGraph* aGraph) const {
+ aGraph->AssertOnGraphThread();
+ if (mAudioProcessing) {
+ return mAudioProcessing->GetConfig();
+ }
+ return ConfigForPrefs(mSettings);
+}
+
void AudioInputProcessing::End() {
mEnded = true;
mSegment.Clear();
@@ -1078,7 +1183,6 @@ void AudioInputProcessing::EnsureAudioProcessing(AudioProcessingTrack* aTrack) {
void AudioInputProcessing::ResetAudioProcessing(MediaTrackGraph* aGraph) {
aGraph->AssertOnGraphThread();
MOZ_ASSERT(IsPassThrough(aGraph) || !mEnabled);
- MOZ_ASSERT(mPacketizerInput);
LOG_FRAME(
"(Graph %p, Driver %p) AudioInputProcessing %p Resetting audio "
@@ -1091,9 +1195,10 @@ void AudioInputProcessing::ResetAudioProcessing(MediaTrackGraph* aGraph) {
mAudioProcessing->Initialize();
}
- MOZ_ASSERT(static_cast<uint32_t>(mSegment.GetDuration()) +
- mPacketizerInput->FramesAvailable() ==
- mPacketizerInput->mPacketSize);
+ MOZ_ASSERT_IF(mPacketizerInput,
+ static_cast<uint32_t>(mSegment.GetDuration()) +
+ mPacketizerInput->FramesAvailable() ==
+ mPacketizerInput->mPacketSize);
// It's ok to clear all the internal buffer here since we won't use mSegment
// in pass-through mode or when audio processing is disabled.
diff --git a/dom/media/webrtc/MediaEngineWebRTCAudio.h b/dom/media/webrtc/MediaEngineWebRTCAudio.h
index 6b1fbf0089..705d33fc38 100644
--- a/dom/media/webrtc/MediaEngineWebRTCAudio.h
+++ b/dom/media/webrtc/MediaEngineWebRTCAudio.h
@@ -117,7 +117,8 @@ class AudioInputProcessing : public AudioDataListener {
// If we're passing data directly without AEC or any other process, this
// means that all voice-processing has been disabled intentionaly. In this
// case, consider that the device is not used for voice input.
- return !IsPassThrough(aGraph);
+ return !IsPassThrough(aGraph) ||
+ mPlatformProcessingSetParams != CUBEB_INPUT_PROCESSING_PARAM_NONE;
}
void Start(MediaTrackGraph* aGraph);
@@ -125,18 +126,27 @@ class AudioInputProcessing : public AudioDataListener {
void DeviceChanged(MediaTrackGraph* aGraph) override;
- uint32_t RequestedInputChannelCount(MediaTrackGraph*) override {
+ uint32_t RequestedInputChannelCount(MediaTrackGraph*) const override {
return GetRequestedInputChannelCount();
}
+ cubeb_input_processing_params RequestedInputProcessingParams(
+ MediaTrackGraph* aGraph) const override;
+
void Disconnect(MediaTrackGraph* aGraph) override;
+ void NotifySetRequestedInputProcessingParamsResult(
+ MediaTrackGraph* aGraph, cubeb_input_processing_params aRequestedParams,
+ const Result<cubeb_input_processing_params, int>& aResult) override;
+
void PacketizeAndProcess(AudioProcessingTrack* aTrack,
const AudioSegment& aSegment);
- uint32_t GetRequestedInputChannelCount();
+ uint32_t GetRequestedInputChannelCount() const;
+
// This is true when all processing is disabled, in which case we can skip
- // packetization, resampling and other processing passes.
+ // packetization, resampling and other processing passes. Processing may still
+ // be applied by the platform on the underlying input track.
bool IsPassThrough(MediaTrackGraph* aGraph) const;
// This allow changing the APM options, enabling or disabling processing
@@ -146,6 +156,9 @@ class AudioInputProcessing : public AudioDataListener {
CubebUtils::AudioDeviceID aDeviceID,
const MediaEnginePrefs& aSettings);
+ // The config currently applied to the audio processing module.
+ webrtc::AudioProcessing::Config AppliedConfig(MediaTrackGraph* aGraph) const;
+
void End();
TrackTime NumBufferedFrames(MediaTrackGraph* aGraph) const;
@@ -163,13 +176,15 @@ class AudioInputProcessing : public AudioDataListener {
private:
~AudioInputProcessing() = default;
webrtc::AudioProcessing::Config ConfigForPrefs(
- const MediaEnginePrefs& aPrefs);
+ const MediaEnginePrefs& aPrefs) const;
void PassThroughChanged(MediaTrackGraph* aGraph);
void RequestedInputChannelCountChanged(MediaTrackGraph* aGraph,
CubebUtils::AudioDeviceID aDeviceId);
void EnsurePacketizer(AudioProcessingTrack* aTrack);
void EnsureAudioProcessing(AudioProcessingTrack* aTrack);
void ResetAudioProcessing(MediaTrackGraph* aGraph);
+ void ApplySettingsInternal(MediaTrackGraph* aGraph,
+ const MediaEnginePrefs& aSettings);
PrincipalHandle GetCheckedPrincipal(const AudioSegment& aSegment);
// This implements the processing algoritm to apply to the input (e.g. a
// microphone). If all algorithms are disabled, this class in not used. This
@@ -186,6 +201,17 @@ class AudioInputProcessing : public AudioDataListener {
// The current settings from about:config preferences and content-provided
// constraints.
MediaEnginePrefs mSettings;
+ // When false, RequestedInputProcessingParams() returns no params, resulting
+ // in platform processing getting disabled in the platform.
+ bool mPlatformProcessingEnabled = false;
+ // The latest error notified to us through
+ // NotifySetRequestedInputProcessingParamsResult, or Nothing if the latest
+ // request was successful, or if a request is pending a result.
+ Maybe<int> mPlatformProcessingSetError;
+ // The processing params currently applied in the platform. This allows
+ // adapting the AudioProcessingConfig accordingly.
+ cubeb_input_processing_params mPlatformProcessingSetParams =
+ CUBEB_INPUT_PROCESSING_PARAM_NONE;
// Buffer for up to one 10ms packet of planar mixed audio output for the
// reverse-stream (speaker data) of mAudioProcessing AEC.
// Length is packet size * channel count, regardless of how many frames are
diff --git a/dom/media/webrtc/jsapi/RTCTransformEventRunnable.cpp b/dom/media/webrtc/jsapi/RTCTransformEventRunnable.cpp
index 6f41baf80f..16e3c2fd28 100644
--- a/dom/media/webrtc/jsapi/RTCTransformEventRunnable.cpp
+++ b/dom/media/webrtc/jsapi/RTCTransformEventRunnable.cpp
@@ -57,8 +57,8 @@ already_AddRefed<Event> RTCTransformEventRunnable::BuildEvent(
// Set transformer.[[writable]] to writable.
RefPtr<RTCRtpScriptTransformer> transformer =
new RTCRtpScriptTransformer(aGlobal);
- nsresult nrv =
- transformer->Init(aCx, aTransformerOptions, mWorkerPrivate, mProxy);
+ nsresult nrv = transformer->Init(aCx, aTransformerOptions,
+ GetCurrentThreadWorkerPrivate(), mProxy);
if (NS_WARN_IF(NS_FAILED(nrv))) {
// TODO: Error handling. Currently unspecified.
return nullptr;
diff --git a/dom/media/webrtc/libwebrtcglue/VideoConduit.cpp b/dom/media/webrtc/libwebrtcglue/VideoConduit.cpp
index 5862237711..e863934ebc 100644
--- a/dom/media/webrtc/libwebrtcglue/VideoConduit.cpp
+++ b/dom/media/webrtc/libwebrtcglue/VideoConduit.cpp
@@ -750,8 +750,20 @@ void WebrtcVideoConduit::OnControlConfigChange() {
// TODO this is for webrtc-priority, but needs plumbing bits
mEncoderConfig.bitrate_priority = 1.0;
+ // Populate simulcast_layers with their config (not dimensions or
+ // dimensions-derived properties, as they're only known as a frame to
+ // be sent is known).
+ mEncoderConfig.simulcast_layers.clear();
+ for (size_t idx = 0; idx < streamCount; ++idx) {
+ webrtc::VideoStream video_stream;
+ auto& encoding = codecConfig->mEncodings[idx];
+ video_stream.active = encoding.active;
+ mEncoderConfig.simulcast_layers.push_back(video_stream);
+ }
+
// Expected max number of encodings
- mEncoderConfig.number_of_streams = streamCount;
+ mEncoderConfig.number_of_streams =
+ mEncoderConfig.simulcast_layers.size();
// libwebrtc disables this by default.
mSendStreamConfig.suspend_below_min_bitrate = false;
diff --git a/dom/media/webrtc/libwebrtcglue/VideoStreamFactory.cpp b/dom/media/webrtc/libwebrtcglue/VideoStreamFactory.cpp
index 0ead26a453..d3047f4fca 100644
--- a/dom/media/webrtc/libwebrtcglue/VideoStreamFactory.cpp
+++ b/dom/media/webrtc/libwebrtcglue/VideoStreamFactory.cpp
@@ -150,6 +150,7 @@ std::vector<webrtc::VideoStream> VideoStreamFactory::CreateEncoderStreams(
: aConfig.number_of_streams;
MOZ_RELEASE_ASSERT(streamCount >= 1, "Should request at least one stream");
+ MOZ_RELEASE_ASSERT(streamCount <= aConfig.simulcast_layers.size());
std::vector<webrtc::VideoStream> streams;
streams.reserve(streamCount);
@@ -160,10 +161,10 @@ std::vector<webrtc::VideoStream> VideoStreamFactory::CreateEncoderStreams(
}
for (size_t idx = 0; idx < streamCount; ++idx) {
- webrtc::VideoStream video_stream;
+ webrtc::VideoStream video_stream = aConfig.simulcast_layers[idx];
auto& encoding = mCodecConfig.mEncodings[idx];
- video_stream.active = encoding.active;
MOZ_ASSERT(encoding.constraints.scaleDownBy >= 1.0);
+ MOZ_ASSERT(video_stream.active == encoding.active);
gfx::IntSize newSize(0, 0);
diff --git a/dom/media/webrtc/metrics.yaml b/dom/media/webrtc/metrics.yaml
index aea5cf17fb..dfa8c120f1 100644
--- a/dom/media/webrtc/metrics.yaml
+++ b/dom/media/webrtc/metrics.yaml
@@ -404,3 +404,84 @@ codec.stats:
notification_emails:
- webrtc-telemetry-alerts@mozilla.com
expires: 132
+
+webrtcdtls:
+ protocol_version:
+ type: labeled_counter
+ description: >
+ The version of DTLS used for each webrtc connection. Can be 1.0, 1.2, or 1.3 (there is no 1.1 version of DTLS)
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1884140
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1884140
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - webrtc-telemetry-alerts@mozilla.com
+ expires: 135
+
+ cipher:
+ type: labeled_counter
+ description: >
+ The CipherSuite used for each webrtc DTLS connection, as a string
+ representation of the CipherSuite's ID in 4 hex digits (eg;
+ TLS_DHE_RSA_WITH_AES_128_CBC_SHA would be "0x0033")
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1884140
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1884140
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - webrtc-telemetry-alerts@mozilla.com
+ expires: 135
+
+ srtp_cipher:
+ type: labeled_counter
+ description: >
+ The SRTPProtectionProfile (see RFC 5764) used for each webrtc SRTP
+ connection, as a string representation of the SRTPProtectionProfile's ID
+ in 4 hex digits (eg; SRTP_AES128_CM_HMAC_SHA1_80 would be "0x0001")
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1884140
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1884140
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - webrtc-telemetry-alerts@mozilla.com
+ expires: 135
+
+ client_handshake_result:
+ type: labeled_counter
+ description: >
+ The result of each webrtc client DTLS handshake as a string containing
+ either the name of the error code (eg; SSL_ERROR_BAD_CERTIFICATE),
+ SUCCESS for successful handshakes, ALPN_FAILURE when ALPN negotiation
+ fails, or CERT_FAILURE when cert validation fails.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1884140
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1884140
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - webrtc-telemetry-alerts@mozilla.com
+ expires: 135
+
+ server_handshake_result:
+ type: labeled_counter
+ description: >
+ The result of each webrtc server DTLS handshake, as the name of the error
+ code (eg; SSL_ERROR_BAD_CERTIFICATE), the empty string for successful
+ handshakes, ALPN_FAILURE when ALPN negotiation fails, or CERT_FAILURE when
+ cert validation fails.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1884140
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1884140
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - webrtc-telemetry-alerts@mozilla.com
+ expires: 135
diff --git a/dom/media/webrtc/sdp/rsdparsa_capi/src/lib.rs b/dom/media/webrtc/sdp/rsdparsa_capi/src/lib.rs
index 20a13900a2..1286a5d338 100644
--- a/dom/media/webrtc/sdp/rsdparsa_capi/src/lib.rs
+++ b/dom/media/webrtc/sdp/rsdparsa_capi/src/lib.rs
@@ -99,7 +99,7 @@ pub unsafe extern "C" fn sdp_free_session(sdp_ptr: *mut SdpSession) {
pub unsafe extern "C" fn sdp_new_reference(session: *mut SdpSession) -> *const SdpSession {
let original = Rc::from_raw(session);
let ret = Rc::into_raw(Rc::clone(&original));
- Rc::into_raw(original); // So the original reference doesn't get dropped
+ std::mem::forget(original); // So the original reference doesn't get dropped
ret
}
diff --git a/dom/media/webrtc/sdp/rsdparsa_capi/src/types.rs b/dom/media/webrtc/sdp/rsdparsa_capi/src/types.rs
index 2522c8333d..7b85a173fb 100644
--- a/dom/media/webrtc/sdp/rsdparsa_capi/src/types.rs
+++ b/dom/media/webrtc/sdp/rsdparsa_capi/src/types.rs
@@ -3,7 +3,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use libc::size_t;
-use std::boxed::Box;
use std::convert::TryInto;
use std::error::Error;
use std::ffi::CStr;
diff --git a/dom/media/webrtc/tests/mochitests/head.js b/dom/media/webrtc/tests/mochitests/head.js
index 1fd559217a..cc0992b469 100644
--- a/dom/media/webrtc/tests/mochitests/head.js
+++ b/dom/media/webrtc/tests/mochitests/head.js
@@ -413,7 +413,7 @@ function setupEnvironment() {
// If either fake audio or video is desired we enable fake streams.
// If loopback devices are set they will be chosen instead of fakes in gecko.
["media.navigator.streams.fake", WANT_FAKE_AUDIO || WANT_FAKE_VIDEO],
- ["media.getusermedia.audiocapture.enabled", true],
+ ["media.getusermedia.audio.capture.enabled", true],
["media.getusermedia.screensharing.enabled", true],
["media.getusermedia.window.focus_source.enabled", false],
["media.recorder.audio_node.enabled", true],
diff --git a/dom/media/webrtc/tests/mochitests/iceTestUtils.js b/dom/media/webrtc/tests/mochitests/iceTestUtils.js
index 9e76e3f7df..23237f563b 100644
--- a/dom/media/webrtc/tests/mochitests/iceTestUtils.js
+++ b/dom/media/webrtc/tests/mochitests/iceTestUtils.js
@@ -75,6 +75,18 @@ async function iceConnected(pc) {
});
}
+async function dtlsConnected(pc) {
+ return new Promise((resolve, reject) => {
+ pc.addEventListener("connectionstatechange", () => {
+ if (["connected", "completed"].includes(pc.connectionState)) {
+ resolve();
+ } else if (pc.connectionState == "failed") {
+ reject(new Error(`Connection failed`));
+ }
+ });
+ });
+}
+
// Set up trickle, but does not wait for it to complete. Can be used by itself
// in cases where we do not expect any new candidates, but want to still set up
// the signal handling in case new candidates _do_ show up.
@@ -87,7 +99,8 @@ async function connect(
answerer,
timeout,
context,
- noTrickleWait = false
+ noTrickleWait = false,
+ waitForDtls = false
) {
const trickle1 = trickleIce(offerer, answerer);
const trickle2 = trickleIce(answerer, offerer);
@@ -110,8 +123,12 @@ async function connect(
}
};
+ const connectionPromises = waitForDtls
+ ? [dtlsConnected(offerer), dtlsConnected(answerer)]
+ : [iceConnected(offerer), iceConnected(answerer)];
+
await Promise.race([
- Promise.all([iceConnected(offerer), iceConnected(answerer)]),
+ Promise.all(connectionPromises),
throwOnTimeout(timeout, context),
]);
} finally {
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_glean.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_glean.html
index 1faf464566..a382949823 100644
--- a/dom/media/webrtc/tests/mochitests/test_peerConnection_glean.html
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_glean.html
@@ -4,6 +4,7 @@
<head>
<script type="application/javascript" src="pc.js"></script>
<script type="application/javascript" src="sdpUtils.js"></script>
+ <script type="application/javascript" src="iceTestUtils.js"></script>
</head>
<body>
@@ -580,6 +581,187 @@
ok(preferredVideoCodec == 6, "checkLoggingMultipleTransceivers glean should show preferred video codec VP8 " + preferredVideoCodec);
},
+ async function checkDtlsHandshakeSuccess() {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+ await gleanResetTestValues();
+ let client_successes = await GleanTest.webrtcdtls.clientHandshakeResult.SUCCESS.testGetValue() || 0;
+ let server_successes = await GleanTest.webrtcdtls.serverHandshakeResult.SUCCESS.testGetValue() || 0;
+ let cipher_count = await GleanTest.webrtcdtls.cipher["0x1301"].testGetValue() || 0;
+ let srtp_cipher_count = await GleanTest.webrtcdtls.srtpCipher["0x0007"].testGetValue() || 0;
+ is(client_successes, 0);
+ is(server_successes, 0);
+ is(cipher_count, 0);
+ is(srtp_cipher_count, 0);
+
+ const stream = await navigator.mediaDevices.getUserMedia({ video: true });
+ pc1.addTrack(stream.getTracks()[0]);
+
+ await connect(pc1, pc2, 32000, "DTLS connected", true, true);
+
+ client_successes = await GleanTest.webrtcdtls.clientHandshakeResult.SUCCESS.testGetValue() || 0;
+ server_successes = await GleanTest.webrtcdtls.serverHandshakeResult.SUCCESS.testGetValue() || 0;
+ cipher_count = await GleanTest.webrtcdtls.cipher["0x1301"].testGetValue() || 0;
+ srtp_cipher_count = await GleanTest.webrtcdtls.srtpCipher["0x0007"].testGetValue() || 0;
+ is(client_successes, 1);
+ is(server_successes, 1);
+ is(cipher_count, 2);
+ is(srtp_cipher_count, 2);
+ },
+
+ async function checkDtlsCipherPrefs() {
+ await withPrefs([["security.tls13.aes_128_gcm_sha256", false],
+ ["security.tls13.aes_256_gcm_sha384", false],
+ ["security.tls13.chacha20_poly1305_sha256", true]],
+ async () => {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+ await gleanResetTestValues();
+ let cipher_count = await GleanTest.webrtcdtls.cipher["0x1303"].testGetValue() || 0;
+ is(cipher_count, 0);
+
+ const stream = await navigator.mediaDevices.getUserMedia({ video: true });
+ pc1.addTrack(stream.getTracks()[0]);
+
+ await connect(pc1, pc2, 32000, "DTLS connected", true, true);
+
+ cipher_count = await GleanTest.webrtcdtls.cipher["0x1303"].testGetValue() || 0;
+ is(cipher_count, 2);
+ });
+ },
+
+ async function checkDtlsHandshakeFailure() {
+ // We don't have many failures we can induce here, but messing up the
+ // fingerprint is one way.
+ const offerer = new RTCPeerConnection();
+ const answerer = new RTCPeerConnection();
+ await gleanResetTestValues();
+ let client_failures = await GleanTest.webrtcdtls.clientHandshakeResult.SSL_ERROR_BAD_CERTIFICATE.testGetValue() || 0;
+ let server_failures = await GleanTest.webrtcdtls.serverHandshakeResult.SSL_ERROR_BAD_CERT_ALERT.testGetValue() || 0;
+ is(client_failures, 0);
+ is(server_failures, 0);
+
+ const stream = await navigator.mediaDevices.getUserMedia({ video: true });
+ offerer.addTrack(stream.getTracks()[0]);
+
+ trickleIce(offerer, answerer);
+ trickleIce(answerer, offerer);
+ await offerer.setLocalDescription();
+ let badSdp = offerer.localDescription.sdp;
+ // Tweak the last digit in the fingerprint sent to the answerer. Answerer
+ // (which will be the DTLS client) will get an SSL_ERROR_BAD_CERTIFICATE
+ // error, and the offerer (which will be the DTLS server) will get an
+ // SSL_ERROR_BAD_CERT_ALERT.
+ const lastDigit = badSdp.match(/a=fingerprint:.*([0-9A-F])$/m)[1];
+ const newLastDigit = lastDigit == '0' ? '1' : '0';
+ badSdp = badSdp.replace(/(a=fingerprint:.*)[0-9A-F]$/m, "$1" + newLastDigit);
+ info(badSdp);
+ await answerer.setRemoteDescription({sdp: badSdp, type: "offer"});
+ await answerer.setLocalDescription();
+ await offerer.setRemoteDescription(answerer.localDescription);
+
+ const throwOnTimeout = async () => {
+ await wait(32000);
+ throw new Error(
+ `ICE did not complete within ${timeout} ms`);
+ };
+
+ const connectionPromises = [connectionStateReached(offerer, "failed"),
+ connectionStateReached(answerer, "failed")];
+
+ await Promise.race([
+ Promise.all(connectionPromises),
+ throwOnTimeout()
+ ]);
+
+ client_failures = await GleanTest.webrtcdtls.clientHandshakeResult.SSL_ERROR_BAD_CERTIFICATE.testGetValue() || 0;
+ server_failures = await GleanTest.webrtcdtls.serverHandshakeResult.SSL_ERROR_BAD_CERT_ALERT.testGetValue() || 0;
+ is(client_failures, 1);
+ is(server_failures, 1);
+ },
+
+ async function checkDtlsVersion1_3() {
+ // 1.3 should be the default
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+ await gleanResetTestValues();
+ let count1_0 = await GleanTest.webrtcdtls.protocolVersion["1.0"].testGetValue() || 0;
+ let count1_2 = await GleanTest.webrtcdtls.protocolVersion["1.2"].testGetValue() || 0;
+ let count1_3 = await GleanTest.webrtcdtls.protocolVersion["1.3"].testGetValue() || 0;
+ is(count1_0, 0);
+ is(count1_2, 0);
+ is(count1_3, 0);
+
+ const stream = await navigator.mediaDevices.getUserMedia({ video: true });
+ pc1.addTrack(stream.getTracks()[0]);
+
+ await connect(pc1, pc2, 32000, "DTLS connected", true, true);
+
+ count1_0 = await GleanTest.webrtcdtls.protocolVersion["1.0"].testGetValue() || 0;
+ count1_2 = await GleanTest.webrtcdtls.protocolVersion["1.2"].testGetValue() || 0;
+ count1_3 = await GleanTest.webrtcdtls.protocolVersion["1.3"].testGetValue() || 0;
+ is(count1_0, 0);
+ is(count1_2, 0);
+ is(count1_3, 2);
+ },
+
+ async function checkDtlsVersion1_2() {
+ // Make 1.2 the default
+ await withPrefs([["media.peerconnection.dtls.version.max", 771]],
+ async () => {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+ await gleanResetTestValues();
+ let count1_0 = await GleanTest.webrtcdtls.protocolVersion["1.0"].testGetValue() || 0;
+ let count1_2 = await GleanTest.webrtcdtls.protocolVersion["1.2"].testGetValue() || 0;
+ let count1_3 = await GleanTest.webrtcdtls.protocolVersion["1.3"].testGetValue() || 0;
+ is(count1_0, 0);
+ is(count1_2, 0);
+ is(count1_3, 0);
+
+ const stream = await navigator.mediaDevices.getUserMedia({ video: true });
+ pc1.addTrack(stream.getTracks()[0]);
+
+ await connect(pc1, pc2, 32000, "DTLS connected", true, true);
+
+ count1_0 = await GleanTest.webrtcdtls.protocolVersion["1.0"].testGetValue() || 0;
+ count1_2 = await GleanTest.webrtcdtls.protocolVersion["1.2"].testGetValue() || 0;
+ count1_3 = await GleanTest.webrtcdtls.protocolVersion["1.3"].testGetValue() || 0;
+ is(count1_0, 0);
+ is(count1_2, 2);
+ is(count1_3, 0);
+ });
+ },
+
+ async function checkDtlsVersion1_0() {
+ // Make 1.0 the default
+ await withPrefs([["media.peerconnection.dtls.version.max", 770],
+ ["media.peerconnection.dtls.version.min", 770]],
+ async () => {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+ await gleanResetTestValues();
+ let count1_0 = await GleanTest.webrtcdtls.protocolVersion["1.0"].testGetValue() || 0;
+ let count1_2 = await GleanTest.webrtcdtls.protocolVersion["1.2"].testGetValue() || 0;
+ let count1_3 = await GleanTest.webrtcdtls.protocolVersion["1.3"].testGetValue() || 0;
+ is(count1_0, 0);
+ is(count1_2, 0);
+ is(count1_3, 0);
+
+ const stream = await navigator.mediaDevices.getUserMedia({ video: true });
+ pc1.addTrack(stream.getTracks()[0]);
+
+ await connect(pc1, pc2, 32000, "DTLS connected", true, true);
+
+ count1_0 = await GleanTest.webrtcdtls.protocolVersion["1.0"].testGetValue() || 0;
+ count1_2 = await GleanTest.webrtcdtls.protocolVersion["1.2"].testGetValue() || 0;
+ count1_3 = await GleanTest.webrtcdtls.protocolVersion["1.3"].testGetValue() || 0;
+ is(count1_0, 2);
+ is(count1_2, 0);
+ is(count1_3, 0);
+ });
+ },
+
];
runNetworkTest(async () => {
diff --git a/dom/media/webrtc/third_party_build/default_config_env b/dom/media/webrtc/third_party_build/default_config_env
index be3c5ba7c1..0fef4d3192 100644
--- a/dom/media/webrtc/third_party_build/default_config_env
+++ b/dom/media/webrtc/third_party_build/default_config_env
@@ -5,41 +5,41 @@
export MOZ_LIBWEBRTC_SRC=$STATE_DIR/moz-libwebrtc
# The previous fast-forward bug number is used for some error messaging.
-export MOZ_PRIOR_FASTFORWARD_BUG="1876843"
+export MOZ_PRIOR_FASTFORWARD_BUG="1883116"
# Fast-forwarding each Chromium version of libwebrtc should be done
# under a separate bugzilla bug. This bug number is used when crafting
# the commit summary as each upstream commit is vendored into the
# mercurial repository. The bug used for the v106 fast-forward was
# 1800920.
-export MOZ_FASTFORWARD_BUG="1883116"
+export MOZ_FASTFORWARD_BUG="1888181"
# MOZ_NEXT_LIBWEBRTC_MILESTONE and MOZ_NEXT_FIREFOX_REL_TARGET are
# not used during fast-forward processing, but facilitate generating this
# default config. To generate an default config for the next update, run
# bash dom/media/webrtc/third_party_build/update_default_config_env.sh
-export MOZ_NEXT_LIBWEBRTC_MILESTONE=122
-export MOZ_NEXT_FIREFOX_REL_TARGET=126
+export MOZ_NEXT_LIBWEBRTC_MILESTONE=123
+export MOZ_NEXT_FIREFOX_REL_TARGET=127
# For Chromium release branches, see:
# https://chromiumdash.appspot.com/branches
-# Chromium's v121 release branch was 6167. This is used to pre-stack
+# Chromium's v122 release branch was 6261. This is used to pre-stack
# the previous release branch's commits onto the appropriate base commit
# (the first common commit between trunk and the release branch).
-export MOZ_PRIOR_UPSTREAM_BRANCH_HEAD_NUM="6167"
+export MOZ_PRIOR_UPSTREAM_BRANCH_HEAD_NUM="6261"
-# New target release branch for v122 is branch-heads/6261. This is used
+# New target release branch for v123 is branch-heads/6312. This is used
# to calculate the next upstream commit.
-export MOZ_TARGET_UPSTREAM_BRANCH_HEAD="branch-heads/6261"
+export MOZ_TARGET_UPSTREAM_BRANCH_HEAD="branch-heads/6312"
# For local development 'mozpatches' is fine for a branch name, but when
# pushing the patch stack to github, it should be named something like
-# 'moz-mods-chr122-for-rel126'.
+# 'moz-mods-chr123-for-rel127'.
export MOZ_LIBWEBRTC_BRANCH="mozpatches"
# After elm has been merged to mozilla-central, the patch stack in
# moz-libwebrtc should be pushed to github. The script
# push_official_branch.sh uses this branch name when pushing to the
# public repo.
-export MOZ_LIBWEBRTC_OFFICIAL_BRANCH="moz-mods-chr122-for-rel126"
+export MOZ_LIBWEBRTC_OFFICIAL_BRANCH="moz-mods-chr123-for-rel127"
diff --git a/dom/media/webrtc/third_party_build/elm_rebase.sh b/dom/media/webrtc/third_party_build/elm_rebase.sh
index 0dbf93d3ce..734fcacc40 100644
--- a/dom/media/webrtc/third_party_build/elm_rebase.sh
+++ b/dom/media/webrtc/third_party_build/elm_rebase.sh
@@ -54,15 +54,32 @@ be as simple as running the following commands:
COMMIT_LIST_FILE=$TMP_DIR/rebase-commit-list.txt
export HGPLAIN=1
+if [ "x$MOZ_TOP_FF" = "x" ]; then
+ MOZ_TOP_FF=""
+fi
+if [ "x$MOZ_BOTTOM_FF" = "x" ]; then
+ MOZ_BOTTOM_FF=""
+fi
+if [ "x$STOP_FOR_REORDER" = "x" ]; then
+ STOP_FOR_REORDER=""
+fi
+
# After this point:
# * eE: All commands should succeed.
+# * u: All variables should be defined before use.
# * o pipefail: All stages of all pipes should succeed.
-set -eEo pipefail
+set -eEuo pipefail
if [ -f $STATE_DIR/rebase_resume_state ]; then
source $STATE_DIR/rebase_resume_state
else
+ # on first run, we want to verify sanity of the patch-stack so
+ # ending guidance is appropriate regarding changes in
+ # third_party/libwebrtc between the old central we're currently
+ # based on and the new central we're rebasing onto.
+ bash dom/media/webrtc/third_party_build/verify_vendoring.sh
+
if [ "x" == "x$MOZ_TOP_FF" ]; then
MOZ_TOP_FF=`hg log -r . -T"{node|short}"`
@@ -119,12 +136,6 @@ That command looks like:
fi
ERROR_HELP=""
- # After this point:
- # * eE: All commands should succeed.
- # * u: All variables should be defined before use.
- # * o pipefail: All stages of all pipes should succeed.
- set -eEuo pipefail
-
MOZ_NEW_CENTRAL=`hg log -r central -T"{node|short}"`
echo "bottom of fast-foward tree is $MOZ_BOTTOM_FF"
diff --git a/dom/media/webrtc/third_party_build/fetch_github_repo.py b/dom/media/webrtc/third_party_build/fetch_github_repo.py
index 8caa55d5c5..1031eb528b 100644
--- a/dom/media/webrtc/third_party_build/fetch_github_repo.py
+++ b/dom/media/webrtc/third_party_build/fetch_github_repo.py
@@ -67,12 +67,16 @@ def fetch_repo(github_path, clone_protocol, force_fetch, tar_path):
"git remote add upstream https://webrtc.googlesource.com/src", github_path
)
run_git("git fetch upstream", github_path)
- run_git("git merge upstream/master", github_path)
else:
print(
"Upstream remote (https://webrtc.googlesource.com/src) already configured"
)
+ # for sanity, ensure we're on master
+ run_git("git checkout master", github_path)
+ # make sure we successfully fetched upstream
+ run_git("git merge upstream/master", github_path)
+
# setup upstream branch-heads
stdout_lines = run_git(
"git config --local --get-all remote.upstream.fetch", github_path
@@ -87,9 +91,12 @@ def fetch_repo(github_path, clone_protocol, force_fetch, tar_path):
else:
print("Upstream remote branch-heads already configured")
+ # verify that a (quite old) branch-head exists
+ run_git("git show branch-heads/5059", github_path)
+
# prevent changing line endings when moving things out of the git repo
# (and into hg for instance)
- run_git("git config --local core.autocrlf false")
+ run_git("git config --local core.autocrlf false", github_path)
# do a sanity fetch in case this was not a freshly cloned copy of the
# repo, meaning it may not have all the mozilla branches present.
diff --git a/dom/media/webrtc/third_party_build/loop-ff.sh b/dom/media/webrtc/third_party_build/loop-ff.sh
index 73ad22822c..9836a8e687 100644
--- a/dom/media/webrtc/third_party_build/loop-ff.sh
+++ b/dom/media/webrtc/third_party_build/loop-ff.sh
@@ -99,9 +99,9 @@ To verify vendoring, run:
When verify_vendoring.sh is successful, please run the following command
in bash:
- (source $SCRIPT_DIR/use_config_env.sh ;
- ./mach python $SCRIPT_DIR/save_patch_stack.py \
- --repo-path $MOZ_LIBWEBRTC_SRC \
+ (source $SCRIPT_DIR/use_config_env.sh ; \\
+ ./mach python $SCRIPT_DIR/save_patch_stack.py \\
+ --repo-path $MOZ_LIBWEBRTC_SRC \\
--target-branch-head $MOZ_TARGET_UPSTREAM_BRANCH_HEAD )
You may resume running this script with the following command:
diff --git a/dom/media/webrtc/third_party_build/prep_repo.sh b/dom/media/webrtc/third_party_build/prep_repo.sh
index 8cd9ff6816..b1a04748b7 100644
--- a/dom/media/webrtc/third_party_build/prep_repo.sh
+++ b/dom/media/webrtc/third_party_build/prep_repo.sh
@@ -13,9 +13,30 @@ trap 'show_error_msg $LINENO' ERR
source dom/media/webrtc/third_party_build/use_config_env.sh
export HGPLAIN=1
+if [ "x$ALLOW_RERUN" = "x" ]; then
+ ALLOW_RERUN="0"
+fi
+
echo "MOZ_LIBWEBRTC_SRC: $MOZ_LIBWEBRTC_SRC"
echo "MOZ_LIBWEBRTC_BRANCH: $MOZ_LIBWEBRTC_BRANCH"
echo "MOZ_FASTFORWARD_BUG: $MOZ_FASTFORWARD_BUG"
+echo "ALLOW_RERUN: $ALLOW_RERUN"
+
+ERROR_HELP=$"
+A copy of moz-libwebrtc already exists at $MOZ_LIBWEBRTC_SRC
+While this script is not technically idempotent, it will try.
+However, the safest way forward is to start fresh by running:
+ rm -rf $STATE_DIR && \\
+ bash $0
+
+If you are sure you want to reuse the existing directory, run:
+ ALLOW_RERUN=1 bash $0
+"
+if [ -d $MOZ_LIBWEBRTC_SRC ] && [ "x$ALLOW_RERUN" != "x1" ]; then
+ echo "$ERROR_HELP"
+ exit 1
+fi
+ERROR_HELP=""
# After this point:
# * eE: All commands should succeed.
@@ -66,8 +87,25 @@ rm -f *.patch
# create a new work branch and "export" a new patch stack to rebase
# find the common commit between our upstream release branch and trunk
-CHERRY_PICK_BASE=`git merge-base branch-heads/$MOZ_PRIOR_UPSTREAM_BRANCH_HEAD_NUM master`
-echo "common commit: $CHERRY_PICK_BASE"
+PREVIOUS_RELEASE_BRANCH_BASE=`git merge-base branch-heads/$MOZ_PRIOR_UPSTREAM_BRANCH_HEAD_NUM master`
+echo "PREVIOUS_RELEASE_BRANCH_BASE: $PREVIOUS_RELEASE_BRANCH_BASE"
+
+NEXT_RELEASE_BRANCH_BASE=`git merge-base $MOZ_TARGET_UPSTREAM_BRANCH_HEAD master`
+echo "NEXT_RELEASE_BRANCH_BASE: $NEXT_RELEASE_BRANCH_BASE"
+
+ERROR_HELP=$"
+The previous release branch base ($PREVIOUS_RELEASE_BRANCH_BASE)
+and the next release branch base ($NEXT_RELEASE_BRANCH_BASE) are the
+same and should not be. This indicates a problem in the git repo at
+$MOZ_LIBWEBRTC_SRC.
+At the least it likely means that 'master' is older than the two
+release branches. Investigation is necessary.
+"
+if [ "x$PREVIOUS_RELEASE_BRANCH_BASE" == "x$NEXT_RELEASE_BRANCH_BASE" ]; then
+ echo "$ERROR_HELP"
+ exit 1
+fi
+ERROR_HELP=""
# find the last upstream commit used by the previous update, so we don't
# accidentally grab release branch commits that were added after we started
@@ -85,9 +123,9 @@ commands will allow the process to continue:
git checkout $MOZ_LIBWEBRTC_BRANCH && \\
git checkout -b $MOZ_LIBWEBRTC_BRANCH-old && \\
git branch -D $MOZ_LIBWEBRTC_BRANCH ) && \\
- bash $0
+ ALLOW_RERUN=1 bash $0
"
-git branch $MOZ_LIBWEBRTC_BRANCH $CHERRY_PICK_BASE
+git branch $MOZ_LIBWEBRTC_BRANCH $PREVIOUS_RELEASE_BRANCH_BASE
ERROR_HELP=""
git checkout $MOZ_LIBWEBRTC_BRANCH
@@ -95,7 +133,7 @@ git checkout $MOZ_LIBWEBRTC_BRANCH
rm -f $TMP_DIR/*.patch $TMP_DIR/*.patch.bak
# grab the patches for all the commits in chrome's release branch for libwebrtc
-git format-patch -o $TMP_DIR -k $CHERRY_PICK_BASE..$LAST_UPSTREAM_COMMIT_SHA
+git format-patch -o $TMP_DIR -k $PREVIOUS_RELEASE_BRANCH_BASE..$LAST_UPSTREAM_COMMIT_SHA
# tweak the release branch commit summaries to show they were cherry picked
sed -i.bak -e "/^Subject: / s/^Subject: /Subject: (cherry-pick-branch-heads\/$MOZ_PRIOR_UPSTREAM_BRANCH_HEAD_NUM) /" $TMP_DIR/*.patch
git am $TMP_DIR/*.patch # applies to branch mozpatches
diff --git a/dom/media/webrtc/third_party_build/verify_vendoring.sh b/dom/media/webrtc/third_party_build/verify_vendoring.sh
index 008ab0d5db..6152e6d003 100644
--- a/dom/media/webrtc/third_party_build/verify_vendoring.sh
+++ b/dom/media/webrtc/third_party_build/verify_vendoring.sh
@@ -13,6 +13,12 @@ trap 'show_error_msg $LINENO' ERR
source dom/media/webrtc/third_party_build/use_config_env.sh
export HGPLAIN=1
+# After this point:
+# * eE: All commands should succeed.
+# * u: All variables should be defined before use.
+# * o pipefail: All stages of all pipes should succeed.
+set -eEuo pipefail
+
echo "MOZ_LIBWEBRTC_SRC: $MOZ_LIBWEBRTC_SRC"
echo "MOZ_LIBWEBRTC_BRANCH: $MOZ_LIBWEBRTC_BRANCH"
echo "MOZ_FASTFORWARD_BUG: $MOZ_FASTFORWARD_BUG"
@@ -30,27 +36,18 @@ LAST_PATCHSTACK_UPDATE_COMMIT_SHA=`echo $LAST_PATCHSTACK_UPDATE_COMMIT \
echo "LAST_PATCHSTACK_UPDATE_COMMIT_SHA: $LAST_PATCHSTACK_UPDATE_COMMIT_SHA"
# grab the oldest, non "Vendor from libwebrtc" line
-OLDEST_CANDIDATE_COMMIT=`hg log --template "{node|short} {desc|firstline}\n" \
- -r $LAST_PATCHSTACK_UPDATE_COMMIT_SHA::. \
- | grep -v "Vendor libwebrtc from" | head -1`
-echo "OLDEST_CANDIDATE_COMMIT: $OLDEST_CANDIDATE_COMMIT"
-
-OLDEST_CANDIDATE_SHA=`echo $OLDEST_CANDIDATE_COMMIT \
- | awk '{ print $1; }'`
-echo "OLDEST_CANDIDATE_SHA: $OLDEST_CANDIDATE_SHA"
+CANDIDATE_COMMITS=`hg log --template "{node|short} {desc|firstline}\n" \
+ -r "children($LAST_PATCHSTACK_UPDATE_COMMIT_SHA)::. - desc('re:(Vendor libwebrtc)')" \
+ --include "third_party/libwebrtc/" | awk 'BEGIN { ORS=" " }; { print $1; }'`
+echo "CANDIDATE_COMMITS:"
+echo "$CANDIDATE_COMMITS"
EXTRACT_COMMIT_RANGE="{start-commit-sha}::."
-if [ "x$CURRENT_SHA" != "x$OLDEST_CANDIDATE_SHA" ]; then
- EXTRACT_COMMIT_RANGE="$OLDEST_CANDIDATE_SHA::."
+if [ "x$CANDIDATE_COMMITS" != "x" ]; then
+ EXTRACT_COMMIT_RANGE="$CANDIDATE_COMMITS"
echo "EXTRACT_COMMIT_RANGE: $EXTRACT_COMMIT_RANGE"
fi
-# After this point:
-# * eE: All commands should succeed.
-# * u: All variables should be defined before use.
-# * o pipefail: All stages of all pipes should succeed.
-set -eEuo pipefail
-
./mach python $SCRIPT_DIR/vendor-libwebrtc.py \
--from-local $MOZ_LIBWEBRTC_SRC \
--commit $MOZ_LIBWEBRTC_BRANCH \
diff --git a/dom/media/webrtc/transport/test/moz.build b/dom/media/webrtc/transport/test/moz.build
index a2b0bc2bc2..3213525abd 100644
--- a/dom/media/webrtc/transport/test/moz.build
+++ b/dom/media/webrtc/transport/test/moz.build
@@ -7,35 +7,39 @@
include("/ipc/chromium/chromium-config.mozbuild")
if CONFIG["OS_TARGET"] != "WINNT":
- if CONFIG["OS_TARGET"] != "Android":
- SOURCES += [
- "ice_unittest.cpp",
- ]
-
SOURCES += [
"buffered_stun_socket_unittest.cpp",
"multi_tcp_socket_unittest.cpp",
- "nrappkit_unittest.cpp",
"proxy_tunnel_socket_unittest.cpp",
- "rlogconnector_unittest.cpp",
"runnable_utils_unittest.cpp",
"simpletokenbucket_unittest.cpp",
- "sockettransportservice_unittest.cpp",
"stunserver.cpp",
"test_nr_socket_ice_unittest.cpp",
"test_nr_socket_unittest.cpp",
"TestSyncRunnable.cpp",
- "transport_unittests.cpp",
"turn_unittest.cpp",
- "webrtcproxychannel_unittest.cpp",
]
- if CONFIG["MOZ_SCTP"]:
+ # Bug 1894419 - Various failures under TSAN
+ if not CONFIG["MOZ_TSAN"]:
+ if CONFIG["OS_TARGET"] != "Android":
+ SOURCES += [
+ "ice_unittest.cpp",
+ ]
+
+ if CONFIG["MOZ_SCTP"]:
+ SOURCES += [
+ "sctp_unittest.cpp",
+ ]
+
SOURCES += [
- "sctp_unittest.cpp",
+ "nrappkit_unittest.cpp",
+ "rlogconnector_unittest.cpp",
+ "sockettransportservice_unittest.cpp",
+ "transport_unittests.cpp",
+ "webrtcproxychannel_unittest.cpp",
]
-
for var in ("HAVE_STRDUP", "NR_SOCKET_IS_VOID_PTR", "SCTP_DEBUG"):
DEFINES[var] = True
diff --git a/dom/media/webrtc/transport/test_nr_socket.cpp b/dom/media/webrtc/transport/test_nr_socket.cpp
index 1a6f226c42..1bf95adc88 100644
--- a/dom/media/webrtc/transport/test_nr_socket.cpp
+++ b/dom/media/webrtc/transport/test_nr_socket.cpp
@@ -141,8 +141,7 @@ TestNat::NatBehavior TestNat::ToNatBehavior(const std::string& type) {
return TestNat::PORT_DEPENDENT;
}
- MOZ_ASSERT(false, "Invalid NAT behavior");
- return TestNat::ENDPOINT_INDEPENDENT;
+ MOZ_CRASH("Invalid NAT behavior");
}
bool TestNat::has_port_mappings() const {
@@ -202,8 +201,8 @@ TestNrSocket::~TestNrSocket() { nat_->erase_socket(this); }
RefPtr<NrSocketBase> TestNrSocket::create_external_socket(
const nr_transport_addr& dest_addr) const {
- MOZ_ASSERT(nat_->enabled_);
- MOZ_ASSERT(!nat_->is_an_internal_tuple(dest_addr));
+ MOZ_RELEASE_ASSERT(nat_->enabled_);
+ MOZ_RELEASE_ASSERT(!nat_->is_an_internal_tuple(dest_addr));
int r;
nr_transport_addr nat_external_addr;
@@ -261,7 +260,7 @@ void TestNrSocket::close() {
}
int TestNrSocket::listen(int backlog) {
- MOZ_ASSERT(internal_socket_->my_addr().protocol == IPPROTO_TCP);
+ MOZ_RELEASE_ASSERT(internal_socket_->my_addr().protocol == IPPROTO_TCP);
r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %p %s listening", this,
internal_socket_->my_addr().as_string);
@@ -269,7 +268,7 @@ int TestNrSocket::listen(int backlog) {
}
int TestNrSocket::accept(nr_transport_addr* addrp, nr_socket** sockp) {
- MOZ_ASSERT(internal_socket_->my_addr().protocol == IPPROTO_TCP);
+ MOZ_RELEASE_ASSERT(internal_socket_->my_addr().protocol == IPPROTO_TCP);
int r = internal_socket_->accept(addrp, sockp);
if (r) {
return r;
@@ -296,7 +295,7 @@ void TestNrSocket::process_delayed_cb(NR_SOCKET s, int how, void* cb_arg) {
int TestNrSocket::sendto(const void* msg, size_t len, int flags,
const nr_transport_addr* to) {
- MOZ_ASSERT(internal_socket_->my_addr().protocol != IPPROTO_TCP);
+ MOZ_RELEASE_ASSERT(internal_socket_->my_addr().protocol != IPPROTO_TCP);
r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %p %s %s", this, __FUNCTION__,
to->as_string);
@@ -347,10 +346,7 @@ int TestNrSocket::sendto(const void* msg, size_t len, int flags,
external_socket = similar_port_mapping->external_socket_;
} else {
external_socket = create_external_socket(*to);
- if (!external_socket) {
- MOZ_ASSERT(false);
- return R_INTERNAL;
- }
+ MOZ_RELEASE_ASSERT(external_socket);
}
port_mapping = create_port_mapping(*to, external_socket);
@@ -371,7 +367,7 @@ int TestNrSocket::sendto(const void* msg, size_t len, int flags,
int TestNrSocket::recvfrom(void* buf, size_t maxlen, size_t* len, int flags,
nr_transport_addr* from) {
- MOZ_ASSERT(internal_socket_->my_addr().protocol != IPPROTO_TCP);
+ MOZ_RELEASE_ASSERT(internal_socket_->my_addr().protocol != IPPROTO_TCP);
if (!read_buffer_.empty()) {
UdpPacket& packet = read_buffer_.front();
@@ -441,8 +437,8 @@ bool TestNrSocket::allow_ingress(const nr_transport_addr& to,
const nr_transport_addr& from,
PortMapping** port_mapping_used) const {
// This is only called for traffic arriving at a port mapping
- MOZ_ASSERT(nat_->enabled_);
- MOZ_ASSERT(!nat_->is_an_internal_tuple(from));
+ MOZ_RELEASE_ASSERT(nat_->enabled_);
+ MOZ_RELEASE_ASSERT(!nat_->is_an_internal_tuple(from));
// Find the port mapping (if any) that this packet landed on
*port_mapping_used = nullptr;
@@ -603,7 +599,7 @@ int TestNrSocket::write(const void* msg, size_t len, size_t* written) {
return R_INTERNAL;
}
// This is TCP only
- MOZ_ASSERT(port_mappings_.size() == 1);
+ MOZ_RELEASE_ASSERT(port_mappings_.size() == 1);
r_log(LOG_GENERIC, LOG_DEBUG, "PortMapping %s -> %s writing",
port_mappings_.front()->external_socket_->my_addr().as_string,
port_mappings_.front()->remote_address_.as_string);
@@ -641,7 +637,7 @@ int TestNrSocket::read(void* buf, size_t maxlen, size_t* len) {
if (port_mappings_.empty()) {
r = internal_socket_->read(buf, maxlen, len);
} else {
- MOZ_ASSERT(port_mappings_.size() == 1);
+ MOZ_RELEASE_ASSERT(port_mappings_.size() == 1);
r = port_mappings_.front()->external_socket_->read(buf, maxlen, len);
if (!r && nat_->refresh_on_ingress_) {
port_mappings_.front()->last_used_ = PR_IntervalNow();
@@ -722,7 +718,7 @@ int TestNrSocket::async_wait(int how, NR_async_cb cb, void* cb_arg,
if (internal_socket_->my_addr().protocol == IPPROTO_TCP) {
// For a TCP connection through a simulated NAT, these signals are
// just passed through.
- MOZ_ASSERT(port_mappings_.size() == 1);
+ MOZ_RELEASE_ASSERT(port_mappings_.size() == 1);
return port_mappings_.front()->async_wait(
how, port_mapping_tcp_passthrough_callback, this, function, line);
@@ -834,7 +830,7 @@ void TestNrSocket::on_socket_readable(NrSocketBase* real_socket) {
}
void TestNrSocket::fire_readable_callback() {
- MOZ_ASSERT(poll_flags() & PR_POLL_READ);
+ MOZ_RELEASE_ASSERT(poll_flags() & PR_POLL_READ);
r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %p %s ready for read", this,
internal_socket_->my_addr().as_string);
fire_callback(NR_ASYNC_WAIT_READ);
@@ -849,7 +845,7 @@ void TestNrSocket::port_mapping_writeable_callback(void* ext_sock_v, int how,
}
void TestNrSocket::write_to_port_mapping(NrSocketBase* external_socket) {
- MOZ_ASSERT(internal_socket_->my_addr().protocol != IPPROTO_TCP);
+ MOZ_RELEASE_ASSERT(internal_socket_->my_addr().protocol != IPPROTO_TCP);
int r = 0;
for (PortMapping* port_mapping : port_mappings_) {
@@ -935,7 +931,7 @@ TestNrSocket::PortMapping::PortMapping(
}
int TestNrSocket::PortMapping::send_from_queue() {
- MOZ_ASSERT(remote_address_.protocol != IPPROTO_TCP);
+ MOZ_RELEASE_ASSERT(remote_address_.protocol != IPPROTO_TCP);
int r = 0;
while (!send_queue_.empty()) {
@@ -967,7 +963,7 @@ int TestNrSocket::PortMapping::send_from_queue() {
int TestNrSocket::PortMapping::sendto(const void* msg, size_t len,
const nr_transport_addr& to) {
- MOZ_ASSERT(remote_address_.protocol != IPPROTO_TCP);
+ MOZ_RELEASE_ASSERT(remote_address_.protocol != IPPROTO_TCP);
r_log(LOG_GENERIC, LOG_DEBUG, "PortMapping %s -> %s sending to %s",
external_socket_->my_addr().as_string, remote_address_.as_string,
to.as_string);
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs.c b/dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs.c
index 362b7d828e..51f72f4179 100644
--- a/dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs.c
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs.c
@@ -62,82 +62,125 @@ nr_stun_is_duplicate_addr(nr_local_addr addrs[], int count, nr_local_addr *addr)
return 0;
}
+static int
+nr_stun_filter_find_first_addr_with_ifname(nr_local_addr addrs[], int count, const char *ifname) {
+ for (int i = 0; i < count; ++i) {
+ if (!strncmp(addrs[i].addr.ifname, ifname, sizeof(addrs[i].addr.ifname))) {
+ return i;
+ }
+ }
+ return count;
+}
+
+static int
+nr_stun_filter_addrs_for_ifname(nr_local_addr src[], const int src_begin, const int src_end, nr_local_addr dest[], int *dest_index, int remove_loopback, int remove_link_local) {
+ int r, _status;
+ /* We prefer temp ipv6 for their privacy properties. If we cannot get
+ * that, we prefer ipv6 that are not based on mac address. */
+ int filter_mac_ipv6 = 0;
+ int filter_teredo_ipv6 = 0;
+ int filter_non_temp_ipv6 = 0;
+
+ const char* ifname = src[src_begin].addr.ifname;
+
+ /* Figure out what we want to filter */
+ for (int i = src_begin; i < src_end; ++i) {
+ if (strncmp(ifname, src[i].addr.ifname, sizeof(src[i].addr.ifname))) {
+ /* Ignore addrs from other interfaces */
+ continue;
+ }
+
+ if (src[i].addr.ip_version == NR_IPV6) {
+ if (nr_transport_addr_is_teredo(&src[i].addr)) {
+ src[i].interface.type |= NR_INTERFACE_TYPE_TEREDO;
+ /* Prefer teredo over mac-based address. Probably will never see
+ * both. */
+ filter_mac_ipv6 = 1;
+ } else {
+ filter_teredo_ipv6 = 1;
+ }
+
+ if (!nr_transport_addr_is_mac_based(&src[i].addr)) {
+ filter_mac_ipv6 = 1;
+ }
+
+ if (src[i].flags & NR_ADDR_FLAG_TEMPORARY) {
+ filter_non_temp_ipv6 = 1;
+ }
+ }
+ }
+
+ /* Perform the filtering */
+ for (int i = src_begin; i < src_end; ++i) {
+ if (strncmp(ifname, src[i].addr.ifname, sizeof(src[i].addr.ifname))) {
+ /* Ignore addrs from other interfaces */
+ continue;
+ }
+
+ if (nr_stun_is_duplicate_addr(dest, *dest_index, &src[i])) {
+ /* skip src[i], it's a duplicate */
+ }
+ else if (remove_loopback && nr_transport_addr_is_loopback(&src[i].addr)) {
+ /* skip src[i], it's a loopback */
+ }
+ else if (remove_link_local &&
+ nr_transport_addr_is_link_local(&src[i].addr)) {
+ /* skip src[i], it's a link-local address */
+ }
+ else if (filter_mac_ipv6 &&
+ nr_transport_addr_is_mac_based(&src[i].addr)) {
+ /* skip src[i], it's MAC based */
+ }
+ else if (filter_teredo_ipv6 &&
+ nr_transport_addr_is_teredo(&src[i].addr)) {
+ /* skip src[i], it's a Teredo address */
+ }
+ else if (filter_non_temp_ipv6 &&
+ (src[i].addr.ip_version == NR_IPV6) &&
+ !(src[i].flags & NR_ADDR_FLAG_TEMPORARY)) {
+ /* skip src[i], it's a non-temporary ipv6, and we have a temporary */
+ }
+ else {
+ /* otherwise, copy it to the destination array */
+ if ((r=nr_local_addr_copy(&dest[*dest_index], &src[i])))
+ ABORT(r);
+ ++(*dest_index);
+ }
+ }
+
+ _status = 0;
+abort:
+ return _status;
+}
+
int
nr_stun_filter_addrs(nr_local_addr addrs[], int remove_loopback, int remove_link_local, int *count)
{
int r, _status;
nr_local_addr *tmp = 0;
- int i;
- int n;
- /* We prefer temp ipv6 for their privacy properties. If we cannot get
- * that, we prefer ipv6 that are not based on mac address. */
- int filter_mac_ipv6 = 0;
- int filter_teredo_ipv6 = 0;
- int filter_non_temp_ipv6 = 0;
+ int dest_index = 0;
tmp = RMALLOC(*count * sizeof(*tmp));
if (!tmp)
ABORT(R_NO_MEMORY);
- for (i = 0; i < *count; ++i) {
- if (addrs[i].addr.ip_version == NR_IPV6) {
- if (nr_transport_addr_is_teredo(&addrs[i].addr)) {
- addrs[i].interface.type |= NR_INTERFACE_TYPE_TEREDO;
- /* Prefer teredo over mac-based address. Probably will never see
- * both. */
- filter_mac_ipv6 = 1;
- } else {
- filter_teredo_ipv6 = 1;
- }
-
- if (!nr_transport_addr_is_mac_based(&addrs[i].addr)) {
- filter_mac_ipv6 = 1;
- }
-
- if (addrs[i].flags & NR_ADDR_FLAG_TEMPORARY) {
- filter_non_temp_ipv6 = 1;
+ for (int i = 0; i < *count; ++i) {
+ if (i == nr_stun_filter_find_first_addr_with_ifname(addrs, *count, addrs[i].addr.ifname)) {
+ /* This is the first address associated with this interface.
+ * Filter for this interface once, now. */
+ if (r = nr_stun_filter_addrs_for_ifname(addrs, i, *count, tmp, &dest_index, remove_loopback, remove_link_local)) {
+ ABORT(r);
}
}
}
- n = 0;
- for (i = 0; i < *count; ++i) {
- if (nr_stun_is_duplicate_addr(tmp, n, &addrs[i])) {
- /* skip addrs[i], it's a duplicate */
- }
- else if (remove_loopback && nr_transport_addr_is_loopback(&addrs[i].addr)) {
- /* skip addrs[i], it's a loopback */
- }
- else if (remove_link_local &&
- nr_transport_addr_is_link_local(&addrs[i].addr)) {
- /* skip addrs[i], it's a link-local address */
- }
- else if (filter_mac_ipv6 &&
- nr_transport_addr_is_mac_based(&addrs[i].addr)) {
- /* skip addrs[i], it's MAC based */
- }
- else if (filter_teredo_ipv6 &&
- nr_transport_addr_is_teredo(&addrs[i].addr)) {
- /* skip addrs[i], it's a Teredo address */
- }
- else if (filter_non_temp_ipv6 &&
- (addrs[i].addr.ip_version == NR_IPV6) &&
- !(addrs[i].flags & NR_ADDR_FLAG_TEMPORARY)) {
- /* skip addrs[i], it's a non-temporary ipv6, and we have a temporary */
- }
- else {
- /* otherwise, copy it to the temporary array */
- if ((r=nr_local_addr_copy(&tmp[n], &addrs[i])))
- ABORT(r);
- ++n;
- }
- }
+ /* Clear the entire array out before copying back */
+ memset(addrs, 0, *count * sizeof(*addrs));
- *count = n;
+ *count = dest_index;
- memset(addrs, 0, *count * sizeof(*addrs));
/* copy temporary array into passed in/out array */
- for (i = 0; i < *count; ++i) {
+ for (int i = 0; i < *count; ++i) {
if ((r=nr_local_addr_copy(&addrs[i], &tmp[i])))
ABORT(r);
}
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/log/r_log.c b/dom/media/webrtc/transport/third_party/nrappkit/src/log/r_log.c
index 09bb24749f..bb47cda879 100644
--- a/dom/media/webrtc/transport/third_party/nrappkit/src/log/r_log.c
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/log/r_log.c
@@ -375,22 +375,10 @@ int r_vlog(int facility,int level,const char *format,va_list ap)
int stderr_vlog(int facility,int level,const char *format,va_list ap)
{
-#if 0 /* remove time stamping, for now */
- char cbuf[30];
- time_t tt;
-
- tt=time(0);
-
- ctime_r(&tt,cbuf);
- cbuf[strlen(cbuf)-1]=0;
-
- fprintf(stderr,"%s: ",cbuf);
-#endif
-
vfprintf(stderr,format,ap);
fprintf(stderr,"\n");
return(0);
- }
+ }
int syslog_vlog(int facility,int level,const char *format,va_list ap)
{
@@ -525,7 +513,7 @@ int r_logging(int facility, int level)
static int r_log_get_default_level(void)
{
- char *log;
+ char *log = 0;
int _status;
log=getenv("R_LOG_LEVEL");
@@ -546,7 +534,7 @@ static int r_log_get_default_level(void)
static int r_log_get_destinations(int usereg)
{
- char *log;
+ char *log = 0;
int i;
int r,_status;
@@ -627,7 +615,7 @@ int r_log_init()
int _r_log_init(int use_reg)
{
#ifndef WIN32
- char *log;
+ char *log = 0;
#endif
if(r_log_initted==0) {
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/registry/registry.c b/dom/media/webrtc/transport/third_party/nrappkit/src/registry/registry.c
index 709b1c3fb7..3134ad1536 100644
--- a/dom/media/webrtc/transport/third_party/nrappkit/src/registry/registry.c
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/registry/registry.c
@@ -563,7 +563,7 @@ NR_reg_make_registry(NR_registry parent, char *child, NR_registry out)
int r, _status;
size_t plen;
size_t clen;
- char *c;
+ char *c = 0;
size_t i;
if ((r=nr_reg_is_valid(parent)))
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/registry/registry_local.c b/dom/media/webrtc/transport/third_party/nrappkit/src/registry/registry_local.c
index ed6e19aaa0..e577b7d4e5 100644
--- a/dom/media/webrtc/transport/third_party/nrappkit/src/registry/registry_local.c
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/registry/registry_local.c
@@ -134,19 +134,9 @@ static int nr_reg_get_array(char *name, unsigned char type, UCHAR *out, size_t s
static int nr_reg_set(char *name, int type, void *data);
static int nr_reg_set_array(char *name, unsigned char type, UCHAR *data, size_t length);
static int nr_reg_set_parent_registries(char *name);
-
-/* make these static OLD_REGISTRY */
-#if 0
-static int nr_reg_fetch_node(char *name, unsigned char type, nr_registry_node **node, int *free_node);
-static char *nr_reg_alloc_node_data(char *name, nr_registry_node *node, int *freeit);
-#else
int nr_reg_fetch_node(char *name, unsigned char type, nr_registry_node **node, int *free_node);
char *nr_reg_alloc_node_data(char *name, nr_registry_node *node, int *freeit);
-#endif
static int nr_reg_rfree(void *ptr);
-#if 0 /* Unused currently */
-static int nr_reg_noop(void *ptr);
-#endif
static int nr_reg_compute_length(char *name, nr_registry_node *node, size_t *length);
char *nr_reg_action_name(int action);
@@ -155,10 +145,6 @@ char *nr_reg_action_name(int action);
* nr_array_registry_node */
static r_assoc *nr_registry = 0;
-#if 0 /* Unused currently */
-static nr_array_registry_node nr_top_level_node;
-#endif
-
typedef struct nr_reg_find_children_arg_ {
size_t size;
NR_registry *children;
@@ -178,7 +164,7 @@ nr_reg_local_iter(NR_registry prefix, int (*action)(void *ptr, r_assoc_iterator
{
int r, _status;
r_assoc_iterator iter;
- char *name;
+ char *name = 0;
int namel;
nr_registry_node *node;
int prefixl;
@@ -246,7 +232,7 @@ nr_reg_local_find_children(void *ptr, r_assoc_iterator *iter, char *prefix, char
{
int _status;
int prefixl = strlen(prefix);
- char *dot;
+ char *dot = 0;
nr_reg_find_children_arg *arg = (void*)ptr;
assert(sizeof(*(arg->children)) == sizeof(NR_registry));
@@ -275,7 +261,7 @@ int
nr_reg_local_count_children(void *ptr, r_assoc_iterator *iter, char *prefix, char *name, nr_registry_node *node)
{
int prefixl = strlen(prefix);
- char *dot;
+ char *dot = 0;
/* only count children */
if (name[prefixl] == '.') {
@@ -296,7 +282,7 @@ nr_reg_local_dump_print(void *ptr, r_assoc_iterator *iter, char *prefix, char *n
{
int _status;
int freeit = 0;
- char *data;
+ char *data = 0;
/* only print leaf nodes */
if (node->type != NR_REG_TYPE_REGISTRY) {
@@ -315,14 +301,6 @@ nr_reg_local_dump_print(void *ptr, r_assoc_iterator *iter, char *prefix, char *n
}
-#if 0 /* Unused currently */
-int
-nr_reg_noop(void *ptr)
-{
- return 0;
-}
-#endif
-
int
nr_reg_rfree(void *ptr)
{
@@ -750,7 +728,7 @@ nr_reg_set_parent_registries(char *name)
{
int r, _status;
char *parent = 0;
- char *dot;
+ char *dot = 0;
if ((parent = r_strdup(name)) == 0)
ABORT(R_NO_MEMORY);
@@ -955,7 +933,7 @@ nr_reg_local_get_type(NR_registry name, NR_registry_type type)
{
int r, _status;
nr_registry_node *node = 0;
- char *str;
+ char *str = 0;
if ((r=nr_reg_is_valid(name)))
ABORT(r);
@@ -1044,7 +1022,7 @@ int
nr_reg_local_get_child_count(NR_registry parent, size_t *count)
{
int r, _status;
- nr_registry_node *ignore1;
+ nr_registry_node *ignore1 = 0;
int ignore2;
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/registry/registrycb.c b/dom/media/webrtc/transport/third_party/nrappkit/src/registry/registrycb.c
index 4b326a1ee2..bd3570cefc 100644
--- a/dom/media/webrtc/transport/third_party/nrappkit/src/registry/registrycb.c
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/registry/registrycb.c
@@ -118,9 +118,9 @@ int
nr_reg_register_callback(NR_registry name, char action, void (*cb)(void *cb_arg, char action, NR_registry name), void *cb_arg)
{
int r, _status;
- r_assoc *assoc;
+ r_assoc *assoc = 0;
int create_assoc = 0;
- nr_reg_cb_info *info;
+ nr_reg_cb_info *info = 0;
int create_info = 0;
unsigned char cb_id[SIZEOF_CB_ID];
@@ -191,7 +191,7 @@ int
nr_reg_unregister_callback(char *name, char action, void (*cb)(void *cb_arg, char action, NR_registry name))
{
int r, _status;
- r_assoc *assoc;
+ r_assoc *assoc = 0;
int size;
unsigned char cb_id[SIZEOF_CB_ID];
@@ -283,12 +283,12 @@ int
nr_reg_raise_event_recurse(char *name, char *tmp, int action)
{
int r, _status;
- r_assoc *assoc;
+ r_assoc *assoc = 0;
nr_reg_cb_info *info;
r_assoc_iterator iter;
- char *key;
+ char *key = 0;
int keyl;
- char *c;
+ char *c = 0;
int free_tmp = 0;
int count;
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_assoc.c b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_assoc.c
index 25b3827d50..eb8cb0b061 100644
--- a/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_assoc.c
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_assoc.c
@@ -150,7 +150,7 @@ int r_assoc_create(assocp,hash_func,bits)
int r_assoc_destroy(assocp)
r_assoc **assocp;
{
- r_assoc *assoc;
+ r_assoc *assoc = 0;
int i;
if(!assocp || !*assocp)
@@ -169,7 +169,7 @@ int r_assoc_destroy(assocp)
static int destroy_assoc_chain(chain)
r_assoc_el *chain;
{
- r_assoc_el *nxt;
+ r_assoc_el *nxt = 0;
while(chain){
nxt=chain->next;
@@ -190,7 +190,7 @@ static int copy_assoc_chain(knewp,old)
r_assoc_el **knewp;
r_assoc_el *old;
{
- r_assoc_el *knew=0,*ptr,*tmp;
+ r_assoc_el *knew = 0, *ptr = 0, *tmp = 0;
int r,_status;
ptr=0; /* Pacify GCC's uninitialized warning.
@@ -245,7 +245,7 @@ static int r_assoc_fetch_bucket(assoc,key,len,bucketp)
r_assoc_el **bucketp;
{
UINT4 hash_value;
- r_assoc_el *bucket;
+ r_assoc_el *bucket = 0;
hash_value=assoc->hash_func(key,len,assoc->bits);
@@ -265,7 +265,7 @@ int r_assoc_fetch(assoc,key,len,datap)
int len;
void **datap;
{
- r_assoc_el *bucket;
+ r_assoc_el *bucket = 0;
int r;
if(r=r_assoc_fetch_bucket(assoc,key,len,&bucket)){
@@ -287,7 +287,7 @@ int r_assoc_insert(assoc,key,len,data,copy,destroy,how)
int (*destroy)(void *ptr);
int how;
{
- r_assoc_el *bucket,*new_bucket=0;
+ r_assoc_el *bucket = 0, *new_bucket = 0;
int r,_status;
if(r=r_assoc_fetch_bucket(assoc,key,len,&bucket)){
@@ -340,7 +340,7 @@ int r_assoc_delete(assoc,key,len)
int len;
{
int r;
- r_assoc_el *bucket;
+ r_assoc_el *bucket = 0;
UINT4 hash_value;
if(r=r_assoc_fetch_bucket(assoc,key,len,&bucket)){
@@ -377,7 +377,7 @@ int r_assoc_copy(knewp,old)
r_assoc *old;
{
int r,_status,i;
- r_assoc *knew;
+ r_assoc *knew = 0;
if(!(knew=(r_assoc *)RCALLOC(sizeof(r_assoc))))
ABORT(R_NO_MEMORY);
@@ -441,7 +441,7 @@ int r_assoc_iter(iter,key,keyl,val)
void **val;
{
int i;
- r_assoc_el *ret;
+ r_assoc_el *ret = 0;
if(!iter->next)
return(R_EOD);
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_crc32.c b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_crc32.c
index 38d3e4da38..6def127dfd 100644
--- a/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_crc32.c
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_crc32.c
@@ -157,7 +157,7 @@ r_crc32(buf, dlen, cval)
u_int32_t *cval;
{
u_int32_t crc = ~0;
- char *p ;
+ char *p = 0;
int i;
u_int32_t crc32_total = 0 ;
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_data.c b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_data.c
index dfb7af2d5c..23df74fb8b 100644
--- a/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_data.c
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_data.c
@@ -183,7 +183,7 @@ int r_data_destroy(dp)
int r_data_destroy_v(v)
void *v;
{
- Data *d;
+ Data *d = 0;
if(!v)
return(0);
@@ -199,7 +199,7 @@ int r_data_destroy_v(v)
int r_data_destroy_vp(v)
void **v;
{
- Data *d;
+ Data *d = 0;
if(!v || !*v)
return(0);
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_list.c b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_list.c
index 4e71d67030..527d39b43a 100644
--- a/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_list.c
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_list.c
@@ -117,8 +117,8 @@ int r_list_create(listp)
int r_list_destroy(listp)
r_list **listp;
{
- r_list *list;
- r_list_el *el;
+ r_list *list = 0;
+ r_list_el *el = 0;
if(!listp || !*listp)
return(0);
@@ -147,7 +147,7 @@ int r_list_copy(outp,in)
r_list *in;
{
r_list *out=0;
- r_list_el *el,*el2,*last=0;
+ r_list_el *el = 0, *el2 = 0, *last = 0;
int r, _status;
if(!in){
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_memory.c b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_memory.c
index 53846fc019..3cfcc916d4 100644
--- a/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_memory.c
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_memory.c
@@ -66,7 +66,7 @@ void *r_malloc(type,size)
size_t size;
{
size_t total;
- r_malloc_chunk *chunk;
+ r_malloc_chunk *chunk = 0;
total=size+sizeof(r_malloc_chunk);
@@ -90,7 +90,7 @@ void *r_calloc(type,number,size)
size_t number;
size_t size;
{
- void *ret;
+ void *ret = 0;
size_t total;
total=number*size;
@@ -106,7 +106,7 @@ void *r_calloc(type,number,size)
void r_free(ptr)
void *ptr;
{
- r_malloc_chunk *chunk;
+ r_malloc_chunk *chunk = 0;
if(!ptr) return;
@@ -125,7 +125,7 @@ void *r_realloc(ptr,size)
void *ptr;
size_t size;
{
- r_malloc_chunk *chunk,*nchunk;
+ r_malloc_chunk *chunk = 0, *nchunk = 0;
size_t total;
if(!ptr) return(r_malloc(255,size));
@@ -154,7 +154,7 @@ char *r_strdup(str)
const char *str;
{
int len;
- char *nstr;
+ char *nstr = 0;
if(!str)
return(0);
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_replace.c b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_replace.c
index 8916b884cc..4dc8feb878 100644
--- a/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_replace.c
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_replace.c
@@ -88,7 +88,7 @@ char *strdup(str)
char *str;
{
int len=strlen(str);
- char *n;
+ char *n = 0;
if(!(n=(char *)malloc(len+1)))
return(0);
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/p_buf.c b/dom/media/webrtc/transport/third_party/nrappkit/src/util/p_buf.c
index 459baecdda..6ada8420ed 100644
--- a/dom/media/webrtc/transport/third_party/nrappkit/src/util/p_buf.c
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/p_buf.c
@@ -72,7 +72,7 @@ int nr_p_buf_ctx_create(size,ctxp)
int nr_p_buf_ctx_destroy(ctxp)
nr_p_buf_ctx **ctxp;
{
- nr_p_buf_ctx *ctx;
+ nr_p_buf_ctx *ctx = 0;
if(!ctxp || !*ctxp)
return(0);
@@ -133,7 +133,7 @@ int nr_p_buf_free_chain(ctx,head)
nr_p_buf_ctx *ctx;
nr_p_buf_head *head;
{
- nr_p_buf *n1,*n2;
+ nr_p_buf *n1 = 0, *n2 = 0;
n1=STAILQ_FIRST(head);
while(n1){
@@ -155,7 +155,7 @@ int nr_p_buf_write_to_chain(ctx,chain,data,len)
UINT4 len;
{
int r,_status;
- nr_p_buf *buf;
+ nr_p_buf *buf = 0;
buf=STAILQ_LAST(chain,nr_p_buf_,entry);
while(len){
@@ -186,7 +186,7 @@ int nr_p_buf_write_to_chain(ctx,chain,data,len)
static int nr_p_buf_destroy_chain(head)
nr_p_buf_head *head;
{
- nr_p_buf *n1,*n2;
+ nr_p_buf *n1 = 0, *n2 = 0;
n1=STAILQ_FIRST(head);
while(n1){
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/util.c b/dom/media/webrtc/transport/third_party/nrappkit/src/util/util.c
index 79b14a8967..51f75832b1 100644
--- a/dom/media/webrtc/transport/third_party/nrappkit/src/util/util.c
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/util.c
@@ -79,38 +79,6 @@ int nr_get_filename(base,name,namep)
return(_status);
}
-#if 0
-int read_RSA_private_key(base,name,keyp)
- char *base;
- char *name;
- RSA **keyp;
- {
- char *keyfile=0;
- BIO *bio=0;
- FILE *fp=0;
- RSA *rsa=0;
- int r,_status;
-
- /* Load the keyfile */
- if(r=get_filename(base,name,&keyfile))
- ABORT(r);
- if(!(fp=fopen(keyfile,"r")))
- ABORT(R_NOT_FOUND);
- if(!(bio=BIO_new(BIO_s_file())))
- ABORT(R_NO_MEMORY);
- BIO_set_fp(bio,fp,BIO_NOCLOSE);
-
- if(!(rsa=PEM_read_bio_RSAPrivateKey(bio,0,0,0)))
- ABORT(R_NOT_FOUND);
-
- *keyp=rsa;
- _status=0;
- abort:
- return(_status);
- }
-#endif
-
-
void nr_errprintf_log(const char *format,...)
{
va_list ap;
@@ -296,55 +264,6 @@ int nr_sha1_file(char *filename,UCHAR *out)
// TODO
#else
-#if 0
-
-#include <fts.h>
-
-int nr_rm_tree(char *path)
- {
- FTS *fts=0;
- FTSENT *p;
- int failed=0;
- int _status;
- char *argv[2];
-
- argv[0]=path;
- argv[1]=0;
-
- if(!(fts=fts_open(argv,0,NULL))){
- r_log_e(LOG_COMMON,LOG_ERR,"Couldn't open directory %s",path);
- ABORT(R_FAILED);
- }
-
- while(p=fts_read(fts)){
- switch(p->fts_info){
- case FTS_D:
- break;
- case FTS_DOT:
- break;
- case FTS_ERR:
- r_log_e(LOG_COMMON,LOG_ERR,"Problem reading %s",p->fts_path);
- break;
- default:
- r_log(LOG_COMMON,LOG_DEBUG,"Removing %s",p->fts_path);
- errno=0;
- if(remove(p->fts_path)){
- r_log_e(LOG_COMMON,LOG_ERR,"Problem removing %s",p->fts_path);
- failed=1;
- }
- }
- }
-
- if(failed)
- ABORT(R_FAILED);
-
- _status=0;
- abort:
- if(fts) fts_close(fts);
- return(_status);
- }
-#endif
-
int nr_write_pid_file(char *pid_filename)
{
FILE *fp;
@@ -625,7 +544,7 @@ inet_ntop6(const unsigned char *src, char *dst, size_t size)
* to use pointer overlays. All the world's not a VAX.
*/
char tmp[sizeof "ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255"];
- char *tp, *ep;
+ char *tp = 0, *ep = 0;
struct { int base, len; } best, cur;
unsigned int words[NS_IN6ADDRSZ / NS_INT16SZ];
int i;
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/util.h b/dom/media/webrtc/transport/third_party/nrappkit/src/util/util.h
index d7861659cd..26c692fbbe 100644
--- a/dom/media/webrtc/transport/third_party/nrappkit/src/util/util.h
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/util.h
@@ -43,11 +43,6 @@
#include "registry.h"
int nr_get_filename(char *base,char *name, char **namep);
-#if 0
-#include <openssl/ssl.h>
-
-int read_RSA_private_key(char *base, char *name,RSA **keyp);
-#endif
void nr_errprintf_log(const char *fmt,...);
void nr_errprintf_log2(void *ignore, const char *fmt,...);
extern int nr_util_default_log_facility;
diff --git a/dom/media/webrtc/transport/transportlayerdtls.cpp b/dom/media/webrtc/transport/transportlayerdtls.cpp
index 4ab8aaa029..1279726bce 100644
--- a/dom/media/webrtc/transport/transportlayerdtls.cpp
+++ b/dom/media/webrtc/transport/transportlayerdtls.cpp
@@ -9,12 +9,14 @@
#include "transportlayerdtls.h"
#include <algorithm>
+#include <iomanip>
#include <queue>
#include <sstream>
#include "dtlsidentity.h"
#include "keyhi.h"
#include "logging.h"
+#include "mozilla/glean/GleanMetrics.h"
#include "mozilla/Telemetry.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h"
@@ -889,6 +891,7 @@ void TransportLayerDtls::Handshake() {
if (!cert_ok_) {
MOZ_MTLOG(ML_ERROR, LAYER_INFO << "Certificate check never occurred");
TL_SET_STATE(TS_ERROR);
+ RecordHandshakeCompletionTelemetry("CERT_FAILURE");
return;
}
if (!CheckAlpn()) {
@@ -897,11 +900,13 @@ void TransportLayerDtls::Handshake() {
// (assuming the close_notify isn't dropped).
ssl_fd_ = nullptr;
TL_SET_STATE(TS_ERROR);
+ RecordHandshakeCompletionTelemetry("ALPN_FAILURE");
return;
}
TL_SET_STATE(TS_OPEN);
+ RecordHandshakeCompletionTelemetry("SUCCESS");
RecordTlsTelemetry();
timer_ = nullptr;
} else {
@@ -932,6 +937,7 @@ void TransportLayerDtls::Handshake() {
MOZ_MTLOG(ML_ERROR, LAYER_INFO << "DTLS handshake error " << err << " ("
<< err_msg << ")");
TL_SET_STATE(TS_ERROR);
+ RecordHandshakeCompletionTelemetry(err_msg);
break;
}
}
@@ -1468,6 +1474,17 @@ void TransportLayerDtls::TimerCallback(nsITimer* timer, void* arg) {
dtls->Handshake();
}
+void TransportLayerDtls::RecordHandshakeCompletionTelemetry(
+ const char* aResult) {
+ if (role_ == CLIENT) {
+ mozilla::glean::webrtcdtls::client_handshake_result.Get(nsCString(aResult))
+ .Add(1);
+ } else {
+ mozilla::glean::webrtcdtls::server_handshake_result.Get(nsCString(aResult))
+ .Add(1);
+ }
+}
+
void TransportLayerDtls::RecordTlsTelemetry() {
MOZ_ASSERT(state_ == TS_OPEN);
SSLChannelInfo info;
@@ -1478,54 +1495,29 @@ void TransportLayerDtls::RecordTlsTelemetry() {
return;
}
- uint16_t telemetry_cipher = 0;
-
- switch (info.cipherSuite) {
- /* Old DHE ciphers: candidates for removal, see bug 1227519 */
- case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
- telemetry_cipher = 1;
- break;
- case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
- telemetry_cipher = 2;
- break;
- /* Current ciphers */
- case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
- telemetry_cipher = 3;
- break;
- case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
- telemetry_cipher = 4;
- break;
- case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
- telemetry_cipher = 5;
- break;
- case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
- telemetry_cipher = 6;
- break;
- case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
- telemetry_cipher = 7;
+ switch (info.protocolVersion) {
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ mozilla::glean::webrtcdtls::protocol_version.Get("1.0"_ns).Add(1);
break;
- case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
- telemetry_cipher = 8;
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ mozilla::glean::webrtcdtls::protocol_version.Get("1.2"_ns).Add(1);
break;
- case TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
- telemetry_cipher = 9;
- break;
- case TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
- telemetry_cipher = 10;
- break;
- /* TLS 1.3 ciphers */
- case TLS_AES_128_GCM_SHA256:
- telemetry_cipher = 11;
- break;
- case TLS_CHACHA20_POLY1305_SHA256:
- telemetry_cipher = 12;
- break;
- case TLS_AES_256_GCM_SHA384:
- telemetry_cipher = 13;
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ mozilla::glean::webrtcdtls::protocol_version.Get("1.3"_ns).Add(1);
break;
+ default:
+ MOZ_CRASH("Unknown SSL version");
}
- Telemetry::Accumulate(Telemetry::WEBRTC_DTLS_CIPHER, telemetry_cipher);
+ {
+ std::ostringstream oss;
+ // Record TLS cipher-suite ID as a string (eg;
+ // TLS_DHE_RSA_WITH_AES_128_CBC_SHA is 0x0033)
+ oss << "0x" << std::setfill('0') << std::setw(4) << std::hex
+ << info.cipherSuite;
+ mozilla::glean::webrtcdtls::cipher.Get(nsCString(oss.str().c_str())).Add(1);
+ MOZ_MTLOG(ML_DEBUG, "cipher: " << oss.str());
+ }
uint16_t cipher;
nsresult rv = GetSrtpCipher(&cipher);
@@ -1535,24 +1527,15 @@ void TransportLayerDtls::RecordTlsTelemetry() {
return;
}
- auto cipher_label = mozilla::Telemetry::LABELS_WEBRTC_SRTP_CIPHER::Unknown;
-
- switch (cipher) {
- case kDtlsSrtpAes128CmHmacSha1_80:
- cipher_label = Telemetry::LABELS_WEBRTC_SRTP_CIPHER::Aes128CmHmacSha1_80;
- break;
- case kDtlsSrtpAes128CmHmacSha1_32:
- cipher_label = Telemetry::LABELS_WEBRTC_SRTP_CIPHER::Aes128CmHmacSha1_32;
- break;
- case kDtlsSrtpAeadAes128Gcm:
- cipher_label = Telemetry::LABELS_WEBRTC_SRTP_CIPHER::AeadAes128Gcm;
- break;
- case kDtlsSrtpAeadAes256Gcm:
- cipher_label = Telemetry::LABELS_WEBRTC_SRTP_CIPHER::AeadAes256Gcm;
- break;
+ {
+ std::ostringstream oss;
+ // Record SRTP cipher-suite ID as a string (eg;
+ // SRTP_AES128_CM_HMAC_SHA1_80 is 0x0001)
+ oss << "0x" << std::setfill('0') << std::setw(4) << std::hex << cipher;
+ mozilla::glean::webrtcdtls::srtp_cipher.Get(nsCString(oss.str().c_str()))
+ .Add(1);
+ MOZ_MTLOG(ML_DEBUG, "srtp cipher: " << oss.str());
}
-
- Telemetry::AccumulateCategorical(cipher_label);
}
} // namespace mozilla
diff --git a/dom/media/webrtc/transport/transportlayerdtls.h b/dom/media/webrtc/transport/transportlayerdtls.h
index d68a6e77d5..e0370a04b2 100644
--- a/dom/media/webrtc/transport/transportlayerdtls.h
+++ b/dom/media/webrtc/transport/transportlayerdtls.h
@@ -144,7 +144,7 @@ class TransportLayerDtls final : public TransportLayer {
SECStatus CheckDigest(const DtlsDigest& digest,
UniqueCERTCertificate& cert) const;
- void RecordHandshakeCompletionTelemetry(TransportLayer::State endState);
+ void RecordHandshakeCompletionTelemetry(const char* aResult);
void RecordTlsTelemetry();
static PRBool WriteSrtpXtn(PRFileDesc* fd, SSLHandshakeType message,
diff --git a/dom/media/webvtt/vtt.sys.mjs b/dom/media/webvtt/vtt.sys.mjs
index 9e8071c427..af400fbbfe 100644
--- a/dom/media/webvtt/vtt.sys.mjs
+++ b/dom/media/webvtt/vtt.sys.mjs
@@ -351,15 +351,44 @@ function parseContent(window, input, mode) {
return consume(m[1] ? m[1] : m[2]);
}
- // Unescape a string 's'.
- function unescape1(e) {
- return ESCAPE[e];
- }
- function unescape(s) {
- let m;
- while ((m = s.match(/&(amp|lt|gt|lrm|rlm|nbsp);/))) {
- s = s.replace(m[0], unescape1);
- }
+ const unescapeHelper = window.document.createElement("div");
+ function unescapeEntities(s) {
+ let match;
+
+ // Decimal numeric character reference
+ s = s.replace(/&#(\d+);?/g, (candidate, number) => {
+ try {
+ const codepoint = parseInt(number);
+ return String.fromCodePoint(codepoint);
+ } catch (_) {
+ return candidate;
+ }
+ });
+
+ // Hexadecimal numeric character reference
+ s = s.replace(/&#x([\dA-Fa-f]+);?/g, (candidate, number) => {
+ try {
+ const codepoint = parseInt(number, 16);
+ return String.fromCodePoint(codepoint);
+ } catch (_) {
+ return candidate;
+ }
+ });
+
+ // Named character references
+ s = s.replace(/&\w[\w\d]*;?/g, candidate => {
+ // The list of entities is huge, so we use innerHTML instead.
+ // We should probably use setHTML instead once that is available (bug 1650370).
+ // Ideally we would be able to use a faster/simpler variant of setHTML (bug 1731215).
+ unescapeHelper.innerHTML = candidate;
+ const unescaped = unescapeHelper.innerText;
+ if (unescaped == candidate) { // not a valid entity
+ return candidate;
+ }
+ return unescaped;
+ });
+ unescapeHelper.innerHTML = "";
+
return s;
}
@@ -432,12 +461,21 @@ function parseContent(window, input, mode) {
while ((t = nextToken()) !== null) {
if (t[0] === '<') {
if (t[1] === "/") {
+ const endTag = t.slice(2, -1);
+ const stackEnd = tagStack.at(-1);
+
// If the closing tag matches, move back up to the parent node.
- if (tagStack.length &&
- tagStack[tagStack.length - 1] === t.substr(2).replace(">", "")) {
+ if (stackEnd == endTag) {
tagStack.pop();
current = current.parentNode;
+
+ // If the closing tag is <ruby> and we're at an <rt>, move back up to
+ // the <ruby>'s parent node.
+ } else if (endTag == "ruby" && current.nodeName == "RT") {
+ tagStack.pop();
+ current = current.parentNode.parentNode;
}
+
// Otherwise just ignore the end tag.
continue;
}
@@ -477,7 +515,7 @@ function parseContent(window, input, mode) {
}
// Text nodes are leaf nodes.
- current.appendChild(window.document.createTextNode(unescape(t)));
+ current.appendChild(window.document.createTextNode(unescapeEntities(t)));
}
return root;
diff --git a/dom/metrics.yaml b/dom/metrics.yaml
index 34b94d8db6..b7fc883ca1 100644
--- a/dom/metrics.yaml
+++ b/dom/metrics.yaml
@@ -46,11 +46,13 @@ perf:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1799727
- https://bugzilla.mozilla.org/show_bug.cgi?id=1834774
- https://bugzilla.mozilla.org/show_bug.cgi?id=1862939
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1892231
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1759744#c5
- https://bugzilla.mozilla.org/show_bug.cgi?id=1799727#c4
- https://bugzilla.mozilla.org/show_bug.cgi?id=1834774#c3
- https://bugzilla.mozilla.org/show_bug.cgi?id=1862939#c5
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1892231#c3
notification_emails:
- perf-telemetry-alerts@mozilla.com
- dpalmeiro@mozilla.com
@@ -104,6 +106,10 @@ perf:
description:
"If true, a normal navigation was performed on the same origin."
type: boolean
+ using_webdriver:
+ description:
+ "If true, a webdriver is running on the client."
+ type: boolean
http_ver:
description:
"Version of HTTP protocol used."
diff --git a/dom/network/ConnectionWorker.cpp b/dom/network/ConnectionWorker.cpp
index e0773455df..8d283ccc73 100644
--- a/dom/network/ConnectionWorker.cpp
+++ b/dom/network/ConnectionWorker.cpp
@@ -109,7 +109,7 @@ class ShutdownRunnable : public WorkerMainThreadRunnable {
}
};
-class NotifyRunnable final : public WorkerRunnable {
+class NotifyRunnable final : public WorkerThreadRunnable {
private:
RefPtr<ConnectionProxy> mProxy;
@@ -120,7 +120,7 @@ class NotifyRunnable final : public WorkerRunnable {
public:
NotifyRunnable(WorkerPrivate* aWorkerPrivate, ConnectionProxy* aProxy,
ConnectionType aType, bool aIsWifi, uint32_t aDHCPGateway)
- : WorkerRunnable(aWorkerPrivate, "NotifyRunnable"),
+ : WorkerThreadRunnable("NotifyRunnable"),
mProxy(aProxy),
mConnectionType(aType),
mIsWifi(aIsWifi),
@@ -183,7 +183,7 @@ void ConnectionProxy::Notify(const hal::NetworkInformation& aNetworkInfo) {
new NotifyRunnable(mWorkerRef->Private(), this,
static_cast<ConnectionType>(aNetworkInfo.type()),
aNetworkInfo.isWifi(), aNetworkInfo.dhcpGateway());
- runnable->Dispatch();
+ runnable->Dispatch(mWorkerRef->Private());
}
void ConnectionProxy::Shutdown() {
diff --git a/dom/network/interfaces/nsITCPSocketCallback.idl b/dom/network/interfaces/nsITCPSocketCallback.idl
index 4a2b7a9ef2..181b84abeb 100644
--- a/dom/network/interfaces/nsITCPSocketCallback.idl
+++ b/dom/network/interfaces/nsITCPSocketCallback.idl
@@ -11,12 +11,6 @@
#include "domstubs.idl"
-%{C++
-#include "nsTArrayForwardDeclare.h"
-%}
-[ref] native nsUint8TArrayRef(nsTArray<uint8_t>);
-[ptr] native JSContextPtr(JSContext);
-
/*
* This interface is implemented in TCPSocket.cpp as an internal interface
@@ -36,7 +30,7 @@ interface nsITCPSocketCallback : nsISupports {
void fireDataStringEvent(in AString type, in ACString data);
// Dispatch a "data" event at this object with an Array
- void fireDataArrayEvent(in AString type, [const] in nsUint8TArrayRef data);
+ void fireDataArrayEvent(in AString type, in Array<uint8_t> data);
// Dispatch an event of the given type at this object.
void fireEvent(in AString type);
diff --git a/dom/notification/Notification.cpp b/dom/notification/Notification.cpp
index fb5b0ea9a3..d0006941e9 100644
--- a/dom/notification/Notification.cpp
+++ b/dom/notification/Notification.cpp
@@ -161,29 +161,36 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(NotificationStorageCallback)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
+nsCOMPtr<nsINotificationStorage> GetNotificationStorage(bool isPrivate) {
+ return do_GetService(isPrivate ? NS_MEMORY_NOTIFICATION_STORAGE_CONTRACTID
+ : NS_NOTIFICATION_STORAGE_CONTRACTID);
+}
+
class NotificationGetRunnable final : public Runnable {
+ bool mIsPrivate;
const nsString mOrigin;
const nsString mTag;
nsCOMPtr<nsINotificationStorageCallback> mCallback;
public:
NotificationGetRunnable(const nsAString& aOrigin, const nsAString& aTag,
- nsINotificationStorageCallback* aCallback)
+ nsINotificationStorageCallback* aCallback,
+ bool aIsPrivate)
: Runnable("NotificationGetRunnable"),
+ mIsPrivate(aIsPrivate),
mOrigin(aOrigin),
mTag(aTag),
mCallback(aCallback) {}
NS_IMETHOD
Run() override {
- nsresult rv;
nsCOMPtr<nsINotificationStorage> notificationStorage =
- do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- return rv;
+ GetNotificationStorage(mIsPrivate);
+ if (NS_WARN_IF(!notificationStorage)) {
+ return NS_ERROR_UNEXPECTED;
}
- rv = notificationStorage->Get(mOrigin, mTag, mCallback);
+ nsresult rv = notificationStorage->Get(mOrigin, mTag, mCallback);
// XXXnsm Is it guaranteed mCallback will be called in case of failure?
Unused << NS_WARN_IF(NS_FAILED(rv));
return rv;
@@ -236,7 +243,7 @@ class ReleaseNotificationControlRunnable final
public:
explicit ReleaseNotificationControlRunnable(Notification* aNotification)
- : MainThreadWorkerControlRunnable(aNotification->mWorkerPrivate),
+ : MainThreadWorkerControlRunnable("ReleaseNotificationControlRunnable"),
mNotification(aNotification) {}
bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
@@ -310,7 +317,7 @@ class NotificationWorkerRunnable : public MainThreadWorkerRunnable {
explicit NotificationWorkerRunnable(
WorkerPrivate* aWorkerPrivate,
const char* aName = "NotificationWorkerRunnable")
- : MainThreadWorkerRunnable(aWorkerPrivate, aName) {}
+ : MainThreadWorkerRunnable(aName) {}
bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
aWorkerPrivate->AssertIsOnWorkerThread();
@@ -426,10 +433,10 @@ class NotificationRef final {
RefPtr<ReleaseNotificationRunnable> r =
new ReleaseNotificationRunnable(notification);
- if (!r->Dispatch()) {
+ if (!r->Dispatch(notification->mWorkerPrivate)) {
RefPtr<ReleaseNotificationControlRunnable> r =
new ReleaseNotificationControlRunnable(notification);
- MOZ_ALWAYS_TRUE(r->Dispatch());
+ MOZ_ALWAYS_TRUE(r->Dispatch(notification->mWorkerPrivate));
}
} else {
notification->AssertIsOnTargetThread();
@@ -822,15 +829,15 @@ Notification::ConstructFromFields(
nsresult Notification::PersistNotification() {
AssertIsOnMainThread();
- nsresult rv;
+
nsCOMPtr<nsINotificationStorage> notificationStorage =
- do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv);
- if (NS_FAILED(rv)) {
- return rv;
+ GetNotificationStorage(IsInPrivateBrowsing());
+ if (NS_WARN_IF(!notificationStorage)) {
+ return NS_ERROR_UNEXPECTED;
}
nsString origin;
- rv = GetOrigin(GetPrincipal(), origin);
+ nsresult rv = GetOrigin(GetPrincipal(), origin);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
@@ -862,7 +869,7 @@ void Notification::UnpersistNotification() {
AssertIsOnMainThread();
if (IsStored()) {
nsCOMPtr<nsINotificationStorage> notificationStorage =
- do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID);
+ GetNotificationStorage(IsInPrivateBrowsing());
if (notificationStorage) {
nsString origin;
nsresult rv = GetOrigin(GetPrincipal(), origin);
@@ -1067,15 +1074,16 @@ class NotificationClickWorkerRunnable final
"NotificationClickWorkerRunnable"),
mNotification(aNotification),
mWindow(aWindow) {
- MOZ_ASSERT_IF(mWorkerPrivate->IsServiceWorker(), !mWindow);
+ MOZ_ASSERT_IF(mNotification->mWorkerPrivate->IsServiceWorker(), !mWindow);
}
void WorkerRunInternal(WorkerPrivate* aWorkerPrivate) override {
bool doDefaultAction = mNotification->DispatchClickEvent();
- MOZ_ASSERT_IF(mWorkerPrivate->IsServiceWorker(), !doDefaultAction);
+ MOZ_ASSERT_IF(mNotification->mWorkerPrivate->IsServiceWorker(),
+ !doDefaultAction);
if (doDefaultAction) {
RefPtr<FocusWindowRunnable> r = new FocusWindowRunnable(mWindow);
- mWorkerPrivate->DispatchToMainThread(r.forget());
+ mNotification->mWorkerPrivate->DispatchToMainThread(r.forget());
}
}
};
@@ -1175,7 +1183,7 @@ WorkerNotificationObserver::Observe(nsISupports* aSubject, const char* aTopic,
MOZ_ASSERT(notification->mWorkerPrivate);
- RefPtr<WorkerRunnable> r;
+ RefPtr<WorkerThreadRunnable> r;
if (!strcmp("alertclickcallback", aTopic)) {
nsPIDOMWindowInner* window = nullptr;
if (!notification->mWorkerPrivate->IsServiceWorker()) {
@@ -1207,7 +1215,7 @@ WorkerNotificationObserver::Observe(nsISupports* aSubject, const char* aTopic,
}
MOZ_ASSERT(r);
- if (!r->Dispatch()) {
+ if (!r->Dispatch(notification->mWorkerPrivate)) {
NS_WARNING("Could not dispatch event to worker notification");
}
return NS_OK;
@@ -1256,7 +1264,7 @@ ServiceWorkerNotificationObserver::Observe(nsISupports* aSubject,
// Remove closed or dismissed persistent notifications.
nsCOMPtr<nsINotificationStorage> notificationStorage =
- do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID);
+ GetNotificationStorage(mPrincipal->GetPrivateBrowsingId() != 0);
if (notificationStorage) {
notificationStorage->Delete(origin, mID);
}
@@ -1316,6 +1324,8 @@ bool Notification::IsInPrivateBrowsing() {
return false;
}
+// Step 4 of
+// https://notifications.spec.whatwg.org/#dom-notification-notification
void Notification::ShowInternal() {
AssertIsOnMainThread();
MOZ_ASSERT(mTempRef,
@@ -1330,13 +1340,15 @@ void Notification::ShowInternal() {
std::swap(ownership, mTempRef);
MOZ_ASSERT(ownership->GetNotification() == this);
- nsresult rv = PersistNotification();
- if (NS_FAILED(rv)) {
- NS_WARNING("Could not persist Notification");
- }
-
nsCOMPtr<nsIAlertsService> alertService = components::Alerts::Service();
+ // Step 4.1: If the result of getting the notifications permission state is
+ // not "granted", then queue a task to fire an event named error on this, and
+ // abort these steps.
+ //
+ // XXX(krosylight): But this function is also triggered by
+ // Notification::ShowPersistentNotification which already does its own
+ // permission check. Can we split this?
ErrorResult result;
NotificationPermission permission = NotificationPermission::Denied;
if (mWorkerPrivate) {
@@ -1351,19 +1363,34 @@ void Notification::ShowInternal() {
if (mWorkerPrivate) {
RefPtr<NotificationEventWorkerRunnable> r =
new NotificationEventWorkerRunnable(this, u"error"_ns);
- if (!r->Dispatch()) {
+ if (!r->Dispatch(mWorkerPrivate)) {
NS_WARNING("Could not dispatch event to worker notification");
}
} else {
DispatchTrustedEvent(u"error"_ns);
}
+ mIsClosed = true;
return;
}
+ // Preparing for Step 4.2 the fetch steps. The actual work happens in
+ // nsIAlertNotification::LoadImage
nsAutoString iconUrl;
nsAutoString soundUrl;
ResolveIconAndSoundURL(iconUrl, soundUrl);
+ // Step 4.3 the show steps, which are almost all about processing `tag` and
+ // then displaying the notification. Both are handled by
+ // nsIAlertsService::ShowAlert/PersistentNotification. The below is all about
+ // constructing the observer (for show and close events) right and ultimately
+ // call the alerts service function.
+
+ // XXX(krosylight): Non-persistent notifications probably don't need this
+ nsresult rv = PersistNotification();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Could not persist Notification");
+ }
+
bool isPersistent = false;
nsCOMPtr<nsIObserver> observer;
if (mScope.IsEmpty()) {
@@ -1688,8 +1715,8 @@ already_AddRefed<Promise> Notification::Get(
nsCOMPtr<nsINotificationStorageCallback> callback =
new NotificationStorageCallback(aWindow->AsGlobal(), aScope, promise);
- RefPtr<NotificationGetRunnable> r =
- new NotificationGetRunnable(origin, aFilter.mTag, callback);
+ RefPtr<NotificationGetRunnable> r = new NotificationGetRunnable(
+ origin, aFilter.mTag, callback, doc->IsInPrivateBrowsing());
aRv = aWindow->AsGlobal()->Dispatch(r.forget());
if (NS_WARN_IF(aRv.Failed())) {
@@ -1765,7 +1792,7 @@ class WorkerGetCallback final : public ScopeCheckingGetCallback {
RefPtr<WorkerGetResultRunnable> r = new WorkerGetResultRunnable(
proxy->GetWorkerPrivate(), proxy, std::move(mStrings));
- r->Dispatch();
+ r->Dispatch(proxy->GetWorkerPrivate());
return NS_OK;
}
@@ -1793,25 +1820,26 @@ class WorkerGetRunnable final : public Runnable {
NS_IMETHOD
Run() override {
AssertIsOnMainThread();
- nsCOMPtr<nsINotificationStorageCallback> callback =
- new WorkerGetCallback(mPromiseProxy, mScope);
-
- nsresult rv;
- nsCOMPtr<nsINotificationStorage> notificationStorage =
- do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- callback->Done();
- return rv;
- }
MutexAutoLock lock(mPromiseProxy->Lock());
if (mPromiseProxy->CleanedUp()) {
return NS_OK;
}
+ auto* principal = mPromiseProxy->GetWorkerPrivate()->GetPrincipal();
+ auto isPrivate = principal->GetPrivateBrowsingId() != 0;
+
+ nsCOMPtr<nsINotificationStorageCallback> callback =
+ new WorkerGetCallback(mPromiseProxy, mScope);
+
+ nsCOMPtr<nsINotificationStorage> notificationStorage =
+ GetNotificationStorage(isPrivate);
+ if (NS_WARN_IF(!notificationStorage)) {
+ callback->Done();
+ return NS_ERROR_UNEXPECTED;
+ }
nsString origin;
- rv = Notification::GetOrigin(
- mPromiseProxy->GetWorkerPrivate()->GetPrincipal(), origin);
+ nsresult rv = Notification::GetOrigin(principal, origin);
if (NS_WARN_IF(NS_FAILED(rv))) {
callback->Done();
return rv;
diff --git a/dom/notification/NotificationStorage.sys.mjs b/dom/notification/NotificationStorage.sys.mjs
index e33277b598..115b9714fe 100644
--- a/dom/notification/NotificationStorage.sys.mjs
+++ b/dom/notification/NotificationStorage.sys.mjs
@@ -11,17 +11,10 @@ ChromeUtils.defineLazyGetter(lazy, "console", () => {
});
});
-const kMessageNotificationGetAllOk = "Notification:GetAll:Return:OK";
-const kMessageNotificationGetAllKo = "Notification:GetAll:Return:KO";
-const kMessageNotificationSaveKo = "Notification:Save:Return:KO";
-const kMessageNotificationDeleteKo = "Notification:Delete:Return:KO";
-
-const kMessages = [
- kMessageNotificationGetAllOk,
- kMessageNotificationGetAllKo,
- kMessageNotificationSaveKo,
- kMessageNotificationDeleteKo,
-];
+const kMessageGetAllOk = "GetAll:Return:OK";
+const kMessageGetAllKo = "GetAll:Return:KO";
+const kMessageSaveKo = "Save:Return:KO";
+const kMessageDeleteKo = "Delete:Return:KO";
export class NotificationStorage {
#requests = {};
@@ -34,14 +27,35 @@ export class NotificationStorage {
this.registerListeners();
}
+ storageQualifier() {
+ return "Notification";
+ }
+
+ prefixStorageQualifier(message) {
+ return `${this.storageQualifier()}:${message}`;
+ }
+
+ formatMessageType(message) {
+ return this.prefixStorageQualifier(message);
+ }
+
+ supportedMessages() {
+ return [
+ this.formatMessageType(kMessageGetAllOk),
+ this.formatMessageType(kMessageGetAllKo),
+ this.formatMessageType(kMessageSaveKo),
+ this.formatMessageType(kMessageDeleteKo),
+ ];
+ }
+
registerListeners() {
- for (let message of kMessages) {
+ for (let message of this.supportedMessages()) {
Services.cpmm.addMessageListener(message, this);
}
}
unregisterListeners() {
- for (let message of kMessages) {
+ for (let message of this.supportedMessages()) {
Services.cpmm.removeMessageListener(message, this);
}
}
@@ -85,7 +99,7 @@ export class NotificationStorage {
serviceWorkerRegistrationScope,
};
- Services.cpmm.sendAsyncMessage("Notification:Save", {
+ Services.cpmm.sendAsyncMessage(this.formatMessageType("Save"), {
origin,
notification,
});
@@ -98,7 +112,7 @@ export class NotificationStorage {
delete(origin, id) {
lazy.console.debug(`DELETE: ${id}`);
- Services.cpmm.sendAsyncMessage("Notification:Delete", {
+ Services.cpmm.sendAsyncMessage(this.formatMessageType("Delete"), {
origin,
id,
});
@@ -108,12 +122,12 @@ export class NotificationStorage {
var request = this.#requests[message.data.requestID];
switch (message.name) {
- case kMessageNotificationGetAllOk:
+ case this.formatMessageType(kMessageGetAllOk):
delete this.#requests[message.data.requestID];
this.#returnNotifications(message.data.notifications, request.callback);
break;
- case kMessageNotificationGetAllKo:
+ case this.formatMessageType(kMessageGetAllKo):
delete this.#requests[message.data.requestID];
try {
request.callback.done();
@@ -121,8 +135,8 @@ export class NotificationStorage {
lazy.console.debug(`Error calling callback done: ${e}`);
}
break;
- case kMessageNotificationSaveKo:
- case kMessageNotificationDeleteKo:
+ case this.formatMessageType(kMessageSaveKo):
+ case this.formatMessageType(kMessageDeleteKo):
lazy.console.debug(
`Error received when treating: '${message.name}': ${message.data.errorMsg}`
);
@@ -149,7 +163,7 @@ export class NotificationStorage {
};
var requestID = this.#getUniqueRequestID();
this.#requests[requestID] = request;
- Services.cpmm.sendAsyncMessage("Notification:GetAll", {
+ Services.cpmm.sendAsyncMessage(this.formatMessageType("GetAll"), {
origin,
tag,
requestID,
@@ -190,3 +204,9 @@ export class NotificationStorage {
QueryInterface = ChromeUtils.generateQI(["nsINotificationStorage"]);
}
+
+export class MemoryNotificationStorage extends NotificationStorage {
+ storageQualifier() {
+ return "MemoryNotification";
+ }
+}
diff --git a/dom/notification/components.conf b/dom/notification/components.conf
index 2cb6bdb33a..bafb6c9976 100644
--- a/dom/notification/components.conf
+++ b/dom/notification/components.conf
@@ -11,4 +11,10 @@ Classes = [
'esModule': 'resource://gre/modules/NotificationStorage.sys.mjs',
'constructor': 'NotificationStorage',
},
+ {
+ 'cid': '{37f819b0-0b5c-11e3-8ffd-0800200c9a67}',
+ 'contract_ids': ['@mozilla.org/memoryNotificationStorage;1'],
+ 'esModule': 'resource://gre/modules/NotificationStorage.sys.mjs',
+ 'constructor': 'MemoryNotificationStorage',
+ }
]
diff --git a/dom/notification/moz.build b/dom/notification/moz.build
index b42e7a13ab..492cb30cfc 100644
--- a/dom/notification/moz.build
+++ b/dom/notification/moz.build
@@ -44,5 +44,6 @@ if CONFIG["MOZ_NEW_NOTIFICATION_STORE"]:
]
else:
EXTRA_JS_MODULES += [
+ "old/MemoryNotificationDB.sys.mjs",
"old/NotificationDB.sys.mjs",
]
diff --git a/dom/notification/old/MemoryNotificationDB.sys.mjs b/dom/notification/old/MemoryNotificationDB.sys.mjs
new file mode 100644
index 0000000000..ecd1b7c015
--- /dev/null
+++ b/dom/notification/old/MemoryNotificationDB.sys.mjs
@@ -0,0 +1,21 @@
+/* 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/. */
+
+import { NotificationDB } from "./NotificationDB.sys.mjs";
+
+class MemoryNotificationDB extends NotificationDB {
+ storageQualifier() {
+ return "MemoryNotification";
+ }
+
+ async load() {
+ this.loaded = true;
+ }
+
+ async createStore() {}
+ async createFile() {}
+ async save() {}
+}
+
+new MemoryNotificationDB();
diff --git a/dom/notification/old/NotificationDB.sys.mjs b/dom/notification/old/NotificationDB.sys.mjs
index 8cd2bff8f6..7cd7e6f926 100644
--- a/dom/notification/old/NotificationDB.sys.mjs
+++ b/dom/notification/old/NotificationDB.sys.mjs
@@ -21,64 +21,84 @@ const NOTIFICATION_STORE_PATH = PathUtils.join(
"notificationstore.json"
);
-const kMessages = [
- "Notification:Save",
- "Notification:Delete",
- "Notification:GetAll",
-];
-
-var NotificationDB = {
+export class NotificationDB {
// Ensure we won't call init() while xpcom-shutdown is performed
- _shutdownInProgress: false,
+ #shutdownInProgress = false;
// A promise that resolves once the ongoing task queue has been drained.
// The value will be reset when the queue starts again.
- _queueDrainedPromise: null,
- _queueDrainedPromiseResolve: null,
-
- init() {
- if (this._shutdownInProgress) {
+ #queueDrainedPromise = null;
+ #queueDrainedPromiseResolve = null;
+
+ #byTag = Object.create(null);
+ #notifications = Object.create(null);
+ #loaded = false;
+ #tasks = [];
+ #runningTask = null;
+
+ storageQualifier() {
+ return "Notification";
+ }
+
+ prefixStorageQualifier(message) {
+ return `${this.storageQualifier()}:${message}`;
+ }
+
+ formatMessageType(message) {
+ return this.prefixStorageQualifier(message);
+ }
+
+ supportedMessageTypes() {
+ return [
+ this.formatMessageType("Save"),
+ this.formatMessageType("Delete"),
+ this.formatMessageType("GetAll"),
+ ];
+ }
+
+ constructor() {
+ if (this.#shutdownInProgress) {
return;
}
- this.notifications = Object.create(null);
- this.byTag = Object.create(null);
- this.loaded = false;
+ this.#notifications = Object.create(null);
+ this.#byTag = Object.create(null);
+ this.#loaded = false;
- this.tasks = []; // read/write operation queue
- this.runningTask = null;
+ this.#tasks = []; // read/write operation queue
+ this.#runningTask = null;
Services.obs.addObserver(this, "xpcom-shutdown");
this.registerListeners();
// This assumes that nothing will queue a new task at profile-change-teardown phase,
- // potentially replacing the _queueDrainedPromise if there was no existing task run.
+ // potentially replacing the #queueDrainedPromise if there was no existing task run.
lazy.AsyncShutdown.profileChangeTeardown.addBlocker(
"NotificationDB: Need to make sure that all notification messages are processed",
- () => this._queueDrainedPromise
+ () => this.#queueDrainedPromise
);
- },
+ }
registerListeners() {
- for (let message of kMessages) {
+ for (let message of this.supportedMessageTypes()) {
Services.ppmm.addMessageListener(message, this);
}
- },
+ }
unregisterListeners() {
- for (let message of kMessages) {
+ for (let message of this.supportedMessageTypes()) {
Services.ppmm.removeMessageListener(message, this);
}
- },
+ }
observe(aSubject, aTopic) {
lazy.console.debug(`Topic: ${aTopic}`);
if (aTopic == "xpcom-shutdown") {
- this._shutdownInProgress = true;
+ this.#shutdownInProgress = true;
Services.obs.removeObserver(this, "xpcom-shutdown");
this.unregisterListeners();
}
- },
+ }
filterNonAppNotifications(notifications) {
let result = Object.create(null);
@@ -100,7 +120,7 @@ var NotificationDB = {
}
return result;
- },
+ }
// Attempt to read notification file, if it's not there we will create it.
load() {
@@ -110,32 +130,34 @@ var NotificationDB = {
if (data.length) {
// Preprocessing phase intends to cleanly separate any migration-related
// tasks.
- this.notifications = this.filterNonAppNotifications(JSON.parse(data));
+ this.#notifications = this.filterNonAppNotifications(
+ JSON.parse(data)
+ );
}
// populate the list of notifications by tag
- if (this.notifications) {
- for (var origin in this.notifications) {
- this.byTag[origin] = Object.create(null);
- for (var id in this.notifications[origin]) {
- var curNotification = this.notifications[origin][id];
+ if (this.#notifications) {
+ for (var origin in this.#notifications) {
+ this.#byTag[origin] = Object.create(null);
+ for (var id in this.#notifications[origin]) {
+ var curNotification = this.#notifications[origin][id];
if (curNotification.tag) {
- this.byTag[origin][curNotification.tag] = curNotification;
+ this.#byTag[origin][curNotification.tag] = curNotification;
}
}
}
}
- this.loaded = true;
+ this.#loaded = true;
},
// If read failed, we assume we have no notifications to load.
() => {
- this.loaded = true;
+ this.#loaded = true;
return this.createStore();
}
);
- },
+ }
// Creates the notification directory.
createStore() {
@@ -143,30 +165,30 @@ var NotificationDB = {
ignoreExisting: true,
});
return promise.then(this.createFile.bind(this));
- },
+ }
// Creates the notification file once the directory is created.
createFile() {
return IOUtils.writeUTF8(NOTIFICATION_STORE_PATH, "", {
tmpPath: NOTIFICATION_STORE_PATH + ".tmp",
});
- },
+ }
// Save current notifications to the file.
save() {
- var data = JSON.stringify(this.notifications);
+ var data = JSON.stringify(this.#notifications);
return IOUtils.writeUTF8(NOTIFICATION_STORE_PATH, data, {
tmpPath: NOTIFICATION_STORE_PATH + ".tmp",
});
- },
+ }
// Helper function: promise will be resolved once file exists and/or is loaded.
- ensureLoaded() {
- if (!this.loaded) {
+ #ensureLoaded() {
+ if (!this.#loaded) {
return this.load();
}
return Promise.resolve();
- },
+ }
receiveMessage(message) {
lazy.console.debug(`Received message: ${message.name}`);
@@ -182,17 +204,17 @@ var NotificationDB = {
}
switch (message.name) {
- case "Notification:GetAll":
+ case this.formatMessageType("GetAll"):
this.queueTask("getall", message.data)
- .then(function (notifications) {
- returnMessage("Notification:GetAll:Return:OK", {
+ .then(notifications => {
+ returnMessage(this.formatMessageType("GetAll:Return:OK"), {
requestID: message.data.requestID,
origin: message.data.origin,
notifications,
});
})
- .catch(function (error) {
- returnMessage("Notification:GetAll:Return:KO", {
+ .catch(error => {
+ returnMessage(this.formatMessageType("GetAll:Return:KO"), {
requestID: message.data.requestID,
origin: message.data.origin,
errorMsg: error,
@@ -200,30 +222,30 @@ var NotificationDB = {
});
break;
- case "Notification:Save":
+ case this.formatMessageType("Save"):
this.queueTask("save", message.data)
- .then(function () {
- returnMessage("Notification:Save:Return:OK", {
+ .then(() => {
+ returnMessage(this.formatMessageType("Save:Return:OK"), {
requestID: message.data.requestID,
});
})
- .catch(function (error) {
- returnMessage("Notification:Save:Return:KO", {
+ .catch(error => {
+ returnMessage(this.formatMessageType("Save:Return:KO"), {
requestID: message.data.requestID,
errorMsg: error,
});
});
break;
- case "Notification:Delete":
+ case this.formatMessageType("Delete"):
this.queueTask("delete", message.data)
- .then(function () {
- returnMessage("Notification:Delete:Return:OK", {
+ .then(() => {
+ returnMessage(this.formatMessageType("Delete:Return:OK"), {
requestID: message.data.requestID,
});
})
- .catch(function (error) {
- returnMessage("Notification:Delete:Return:KO", {
+ .catch(error => {
+ returnMessage(this.formatMessageType("Delete:Return:KO"), {
requestID: message.data.requestID,
errorMsg: error,
});
@@ -233,7 +255,7 @@ var NotificationDB = {
default:
lazy.console.debug(`Invalid message name ${message.name}`);
}
- },
+ }
// We need to make sure any read/write operations are atomic,
// so use a queue to run each operation sequentially.
@@ -242,48 +264,48 @@ var NotificationDB = {
var defer = {};
- this.tasks.push({
+ this.#tasks.push({
operation,
data,
defer,
});
- var promise = new Promise(function (resolve, reject) {
+ var promise = new Promise((resolve, reject) => {
defer.resolve = resolve;
defer.reject = reject;
});
// Only run immediately if we aren't currently running another task.
- if (!this.runningTask) {
+ if (!this.#runningTask) {
lazy.console.debug("Task queue was not running, starting now...");
this.runNextTask();
- this._queueDrainedPromise = new Promise(resolve => {
- this._queueDrainedPromiseResolve = resolve;
+ this.#queueDrainedPromise = new Promise(resolve => {
+ this.#queueDrainedPromiseResolve = resolve;
});
}
return promise;
- },
+ }
runNextTask() {
- if (this.tasks.length === 0) {
+ if (this.#tasks.length === 0) {
lazy.console.debug("No more tasks to run, queue depleted");
- this.runningTask = null;
- if (this._queueDrainedPromiseResolve) {
- this._queueDrainedPromiseResolve();
+ this.#runningTask = null;
+ if (this.#queueDrainedPromiseResolve) {
+ this.#queueDrainedPromiseResolve();
} else {
lazy.console.debug(
- "_queueDrainedPromiseResolve was null somehow, no promise to resolve"
+ "#queueDrainedPromiseResolve was null somehow, no promise to resolve"
);
}
return;
}
- this.runningTask = this.tasks.shift();
+ this.#runningTask = this.#tasks.shift();
// Always make sure we are loaded before performing any read/write tasks.
- this.ensureLoaded()
+ this.#ensureLoaded()
.then(() => {
- var task = this.runningTask;
+ var task = this.#runningTask;
switch (task.operation) {
case "getall":
@@ -302,85 +324,85 @@ var NotificationDB = {
}
})
.then(payload => {
- lazy.console.debug(`Finishing task: ${this.runningTask.operation}`);
- this.runningTask.defer.resolve(payload);
+ lazy.console.debug(`Finishing task: ${this.#runningTask.operation}`);
+ this.#runningTask.defer.resolve(payload);
})
.catch(err => {
lazy.console.debug(
- `Error while running ${this.runningTask.operation}: ${err}`
+ `Error while running ${this.#runningTask.operation}: ${err}`
);
- this.runningTask.defer.reject(err);
+ this.#runningTask.defer.reject(err);
})
.then(() => {
this.runNextTask();
});
- },
+ }
taskGetAll(data) {
lazy.console.debug("Task, getting all");
var origin = data.origin;
var notifications = [];
// Grab only the notifications for specified origin.
- if (this.notifications[origin]) {
+ if (this.#notifications[origin]) {
if (data.tag) {
let n;
- if ((n = this.byTag[origin][data.tag])) {
+ if ((n = this.#byTag[origin][data.tag])) {
notifications.push(n);
}
} else {
- for (var i in this.notifications[origin]) {
- notifications.push(this.notifications[origin][i]);
+ for (var i in this.#notifications[origin]) {
+ notifications.push(this.#notifications[origin][i]);
}
}
}
return Promise.resolve(notifications);
- },
+ }
taskSave(data) {
lazy.console.debug("Task, saving");
var origin = data.origin;
var notification = data.notification;
- if (!this.notifications[origin]) {
- this.notifications[origin] = Object.create(null);
- this.byTag[origin] = Object.create(null);
+ if (!this.#notifications[origin]) {
+ this.#notifications[origin] = Object.create(null);
+ this.#byTag[origin] = Object.create(null);
}
// We might have existing notification with this tag,
// if so we need to remove it before saving the new one.
if (notification.tag) {
- var oldNotification = this.byTag[origin][notification.tag];
+ var oldNotification = this.#byTag[origin][notification.tag];
if (oldNotification) {
- delete this.notifications[origin][oldNotification.id];
+ delete this.#notifications[origin][oldNotification.id];
}
- this.byTag[origin][notification.tag] = notification;
+ this.#byTag[origin][notification.tag] = notification;
}
- this.notifications[origin][notification.id] = notification;
+ this.#notifications[origin][notification.id] = notification;
return this.save();
- },
+ }
taskDelete(data) {
lazy.console.debug("Task, deleting");
var origin = data.origin;
var id = data.id;
- if (!this.notifications[origin]) {
+ if (!this.#notifications[origin]) {
lazy.console.debug(`No notifications found for origin: ${origin}`);
return Promise.resolve();
}
// Make sure we can find the notification to delete.
- var oldNotification = this.notifications[origin][id];
+ var oldNotification = this.#notifications[origin][id];
if (!oldNotification) {
lazy.console.debug(`No notification found with id: ${id}`);
return Promise.resolve();
}
if (oldNotification.tag) {
- delete this.byTag[origin][oldNotification.tag];
+ delete this.#byTag[origin][oldNotification.tag];
}
- delete this.notifications[origin][id];
+ delete this.#notifications[origin][id];
return this.save();
- },
-};
+ }
+}
-NotificationDB.init();
+new NotificationDB();
diff --git a/dom/notification/test/unit/head_notificationdb.js b/dom/notification/test/unit/head_notificationdb.js
index 1b23d88729..44b0d7c01b 100644
--- a/dom/notification/test/unit/head_notificationdb.js
+++ b/dom/notification/test/unit/head_notificationdb.js
@@ -31,6 +31,9 @@ var calendarNotification = getNotificationObject(
// Helper to start the NotificationDB
function startNotificationDB() {
+ ChromeUtils.importESModule(
+ "resource://gre/modules/MemoryNotificationDB.sys.mjs"
+ );
ChromeUtils.importESModule("resource://gre/modules/NotificationDB.sys.mjs");
}
diff --git a/dom/notification/test/unit/test_notificationdb.js b/dom/notification/test/unit/test_notificationdb.js
index b6ca8bd79c..4fc1e6389d 100644
--- a/dom/notification/test/unit/test_notificationdb.js
+++ b/dom/notification/test/unit/test_notificationdb.js
@@ -338,3 +338,89 @@ add_test(function test_delete_previous() {
requestID,
});
});
+
+add_test(function test_notification_onDiskPersistence() {
+ let verifyDisk = async function (expectedId) {
+ const NOTIFICATION_STORE_PATH = PathUtils.join(
+ PathUtils.profileDir,
+ "notificationstore.json"
+ );
+
+ const onDiskNotificationStr = await IOUtils.readUTF8(
+ NOTIFICATION_STORE_PATH
+ );
+ return onDiskNotificationStr.includes(expectedId);
+ };
+
+ let persistedNotification = getNotificationObject(
+ systemNotification.origin,
+ "{315aaf98-6c72-48fe-8e2c-a841e1b00027}",
+ "" /* tag */,
+ true /* scope */
+ );
+
+ addAndSend(
+ "Notification:Save",
+ "Notification:Save:Return:OK",
+ async () => {
+ Assert.ok(await verifyDisk(persistedNotification.id));
+ },
+ {
+ origin: persistedNotification.origin,
+ notification: persistedNotification,
+ requestID: 2,
+ }
+ );
+
+ let nonPersistedNotification = getNotificationObject(
+ systemNotification.origin,
+ "{8110ed62-303f-4f9b-a257-a62487aaa09c}",
+ "" /* tag */,
+ true /* scope */
+ );
+
+ addAndSend(
+ "MemoryNotification:Save",
+ "MemoryNotification:Save:Return:OK",
+ async () => {
+ // memoryonly notification must not exist on disk.
+ Assert.ok(!(await verifyDisk(nonPersistedNotification.id)));
+ },
+ {
+ origin: nonPersistedNotification.origin,
+ notification: nonPersistedNotification,
+ requestID: 3,
+ }
+ );
+
+ let verifyMemory = function (message, expectedId) {
+ return message.data.notifications.some(notification => {
+ return notification.id == expectedId;
+ });
+ };
+
+ addAndSend(
+ "Notification:GetAll",
+ "Notification:GetAll:Return:OK",
+ message => {
+ Assert.ok(verifyMemory(message, persistedNotification.id));
+ },
+ {
+ origin: persistedNotification.origin,
+ requestID: 4,
+ }
+ );
+
+ addAndSend(
+ "MemoryNotification:GetAll",
+ "MemoryNotification:GetAll:Return:OK",
+ message => {
+ // memoryonly notification must exist in-memory
+ Assert.ok(verifyMemory(message, nonPersistedNotification.id));
+ },
+ {
+ origin: persistedNotification.origin,
+ requestID: 5,
+ }
+ );
+});
diff --git a/dom/performance/Performance.cpp b/dom/performance/Performance.cpp
index ce2557100d..070bca9ec4 100644
--- a/dom/performance/Performance.cpp
+++ b/dom/performance/Performance.cpp
@@ -33,6 +33,7 @@
#include "mozilla/dom/PerformanceObserverBinding.h"
#include "mozilla/dom/PerformanceNavigationTiming.h"
#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Perfetto.h"
#include "mozilla/Preferences.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/dom/WorkerPrivate.h"
@@ -764,6 +765,25 @@ already_AddRefed<PerformanceMeasure> Performance::Measure(
detail.setNull();
}
+#ifdef MOZ_PERFETTO
+ // Perfetto requires that events are properly nested within each category.
+ // Since this is not a guarantee here, we need to define a dynamic category
+ // for each measurement so it's not prematurely ended by another measurement
+ // that overlaps. We also use the usertiming category to guard these markers
+ // so it's easy to toggle.
+ if (TRACE_EVENT_CATEGORY_ENABLED("usertiming")) {
+ NS_ConvertUTF16toUTF8 str(aName);
+ perfetto::DynamicCategory category{str.get()};
+ TimeStamp startTimeStamp =
+ CreationTimeStamp() + TimeDuration::FromMilliseconds(startTime);
+ TimeStamp endTimeStamp =
+ CreationTimeStamp() + TimeDuration::FromMilliseconds(endTime);
+ PERFETTO_TRACE_EVENT_BEGIN(category, perfetto::DynamicString{str.get()},
+ startTimeStamp);
+ PERFETTO_TRACE_EVENT_END(category, endTimeStamp);
+ }
+#endif
+
RefPtr<PerformanceMeasure> performanceMeasure = new PerformanceMeasure(
GetParentObject(), aName, startTime, endTime, detail);
InsertUserEntry(performanceMeasure);
diff --git a/dom/performance/PerformanceStorageWorker.cpp b/dom/performance/PerformanceStorageWorker.cpp
index 4fb4815121..3649a6270b 100644
--- a/dom/performance/PerformanceStorageWorker.cpp
+++ b/dom/performance/PerformanceStorageWorker.cpp
@@ -39,8 +39,7 @@ class PerformanceEntryAdder final : public WorkerControlRunnable {
PerformanceEntryAdder(WorkerPrivate* aWorkerPrivate,
PerformanceStorageWorker* aStorage,
UniquePtr<PerformanceProxyData>&& aData)
- : WorkerControlRunnable(aWorkerPrivate, "PerformanceEntryAdder",
- WorkerThread),
+ : WorkerControlRunnable("PerformanceEntryAdder"),
mStorage(aStorage),
mData(std::move(aData)) {}
@@ -119,7 +118,7 @@ void PerformanceStorageWorker::AddEntry(nsIHttpChannel* aChannel,
RefPtr<PerformanceEntryAdder> r =
new PerformanceEntryAdder(workerPrivate, this, std::move(data));
- Unused << NS_WARN_IF(!r->Dispatch());
+ Unused << NS_WARN_IF(!r->Dispatch(workerPrivate));
}
void PerformanceStorageWorker::AddEntry(
diff --git a/dom/promise/Promise.cpp b/dom/promise/Promise.cpp
index 3abb517ac7..4ef34dfae5 100644
--- a/dom/promise/Promise.cpp
+++ b/dom/promise/Promise.cpp
@@ -719,12 +719,11 @@ void Promise::MaybeRejectWithClone(JSContext* aCx,
// A WorkerRunnable to resolve/reject the Promise on the worker thread.
// Calling thread MUST hold PromiseWorkerProxy's mutex before creating this.
-class PromiseWorkerProxyRunnable final : public WorkerRunnable {
+class PromiseWorkerProxyRunnable final : public WorkerThreadRunnable {
public:
PromiseWorkerProxyRunnable(PromiseWorkerProxy* aPromiseWorkerProxy,
PromiseWorkerProxy::RunCallbackFunc aFunc)
- : WorkerRunnable(aPromiseWorkerProxy->GetWorkerPrivate(),
- "PromiseWorkerProxyRunnable", WorkerThread),
+ : WorkerThreadRunnable("PromiseWorkerProxyRunnable"),
mPromiseWorkerProxy(aPromiseWorkerProxy),
mFunc(aFunc) {
MOZ_ASSERT(NS_IsMainThread());
@@ -735,7 +734,6 @@ class PromiseWorkerProxyRunnable final : public WorkerRunnable {
WorkerPrivate* aWorkerPrivate) override {
MOZ_ASSERT(aWorkerPrivate);
aWorkerPrivate->AssertIsOnWorkerThread();
- MOZ_ASSERT(aWorkerPrivate == mWorkerPrivate);
MOZ_ASSERT(mPromiseWorkerProxy);
RefPtr<Promise> workerPromise = mPromiseWorkerProxy->GetWorkerPromise();
@@ -862,7 +860,7 @@ void PromiseWorkerProxy::RunCallback(JSContext* aCx,
RefPtr<PromiseWorkerProxyRunnable> runnable =
new PromiseWorkerProxyRunnable(this, aFunc);
- runnable->Dispatch();
+ runnable->Dispatch(GetWorkerPrivate());
}
void PromiseWorkerProxy::ResolvedCallback(JSContext* aCx,
diff --git a/dom/promise/PromiseNativeHandler.h b/dom/promise/PromiseNativeHandler.h
index f311f2d29e..d505316b13 100644
--- a/dom/promise/PromiseNativeHandler.h
+++ b/dom/promise/PromiseNativeHandler.h
@@ -12,6 +12,7 @@
#include "js/Value.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/Maybe.h"
+#include "mozilla/StaticString.h"
#include "nsISupports.h"
namespace mozilla::dom {
@@ -58,7 +59,7 @@ class MozPromiseRejectOnDestruction final
// (Accepting RefPtr<T> instead of T* because compiler fails to implicitly
// convert it at call sites)
MozPromiseRejectOnDestruction(const RefPtr<T>& aMozPromise,
- const char* aCallSite)
+ StaticString aCallSite)
: mMozPromise(aMozPromise), mCallSite(aCallSite) {
MOZ_ASSERT(aMozPromise);
}
@@ -70,7 +71,7 @@ class MozPromiseRejectOnDestruction final
}
RefPtr<T> mMozPromise;
- const char* mCallSite;
+ StaticString mCallSite;
};
} // namespace mozilla::dom
diff --git a/dom/push/PushManager.cpp b/dom/push/PushManager.cpp
index 74bb4a3d7c..246eef8a55 100644
--- a/dom/push/PushManager.cpp
+++ b/dom/push/PushManager.cpp
@@ -14,7 +14,6 @@
#include "mozilla/dom/PushManagerBinding.h"
#include "mozilla/dom/PushSubscription.h"
#include "mozilla/dom/PushSubscriptionOptionsBinding.h"
-#include "mozilla/dom/PushUtil.h"
#include "mozilla/dom/RootedDictionary.h"
#include "mozilla/dom/ServiceWorker.h"
#include "mozilla/dom/WorkerRunnable.h"
@@ -92,7 +91,7 @@ nsresult GetSubscriptionParams(nsIPushSubscription* aSubscription,
return NS_OK;
}
-class GetSubscriptionResultRunnable final : public WorkerRunnable {
+class GetSubscriptionResultRunnable final : public WorkerThreadRunnable {
public:
GetSubscriptionResultRunnable(WorkerPrivate* aWorkerPrivate,
RefPtr<PromiseWorkerProxy>&& aProxy,
@@ -102,7 +101,7 @@ class GetSubscriptionResultRunnable final : public WorkerRunnable {
nsTArray<uint8_t>&& aRawP256dhKey,
nsTArray<uint8_t>&& aAuthSecret,
nsTArray<uint8_t>&& aAppServerKey)
- : WorkerRunnable(aWorkerPrivate, "GetSubscriptionResultRunnable"),
+ : WorkerThreadRunnable("GetSubscriptionResultRunnable"),
mProxy(std::move(aProxy)),
mStatus(aStatus),
mEndpoint(aEndpoint),
@@ -183,7 +182,7 @@ class GetSubscriptionCallback final : public nsIPushSubscriptionCallback {
worker, std::move(mProxy), aStatus, endpoint, mScope,
std::move(mExpirationTime), std::move(rawP256dhKey),
std::move(authSecret), std::move(appServerKey));
- if (!r->Dispatch()) {
+ if (!r->Dispatch(worker)) {
return NS_ERROR_UNEXPECTED;
}
@@ -292,11 +291,11 @@ class GetSubscriptionRunnable final : public Runnable {
nsTArray<uint8_t> mAppServerKey;
};
-class PermissionResultRunnable final : public WorkerRunnable {
+class PermissionResultRunnable final : public WorkerThreadRunnable {
public:
PermissionResultRunnable(PromiseWorkerProxy* aProxy, nsresult aStatus,
PermissionState aState)
- : WorkerRunnable(aProxy->GetWorkerPrivate(), "PermissionResultRunnable"),
+ : WorkerThreadRunnable("PermissionResultRunnable"),
mProxy(aProxy),
mStatus(aStatus),
mState(aState) {
@@ -351,7 +350,7 @@ class PermissionStateRunnable final : public Runnable {
// This can fail if the worker thread is already shutting down, but there's
// nothing we can do in that case.
- Unused << NS_WARN_IF(!r->Dispatch());
+ Unused << NS_WARN_IF(!r->Dispatch(mProxy->GetWorkerPrivate()));
return NS_OK;
}
diff --git a/dom/push/PushSubscription.cpp b/dom/push/PushSubscription.cpp
index 0afb63eee8..fdc70edd09 100644
--- a/dom/push/PushSubscription.cpp
+++ b/dom/push/PushSubscription.cpp
@@ -17,7 +17,7 @@
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/PromiseWorkerProxy.h"
#include "mozilla/dom/PushSubscriptionOptions.h"
-#include "mozilla/dom/PushUtil.h"
+#include "mozilla/dom/TypedArray.h"
#include "mozilla/dom/WorkerCommon.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/WorkerRunnable.h"
@@ -54,12 +54,12 @@ class UnsubscribeResultCallback final : public nsIUnsubscribeResultCallback {
NS_IMPL_ISUPPORTS(UnsubscribeResultCallback, nsIUnsubscribeResultCallback)
-class UnsubscribeResultRunnable final : public WorkerRunnable {
+class UnsubscribeResultRunnable final : public WorkerThreadRunnable {
public:
UnsubscribeResultRunnable(WorkerPrivate* aWorkerPrivate,
RefPtr<PromiseWorkerProxy>&& aProxy,
nsresult aStatus, bool aSuccess)
- : WorkerRunnable(aWorkerPrivate, "UnsubscribeResultRunnable"),
+ : WorkerThreadRunnable("UnsubscribeResultRunnable"),
mProxy(std::move(aProxy)),
mStatus(aStatus),
mSuccess(aSuccess) {
@@ -117,7 +117,7 @@ class WorkerUnsubscribeResultCallback final
WorkerPrivate* worker = mProxy->GetWorkerPrivate();
RefPtr<UnsubscribeResultRunnable> r = new UnsubscribeResultRunnable(
worker, std::move(mProxy), aStatus, aSuccess);
- MOZ_ALWAYS_TRUE(r->Dispatch());
+ MOZ_ALWAYS_TRUE(r->Dispatch(worker));
return NS_OK;
}
@@ -230,30 +230,24 @@ already_AddRefed<PushSubscription> PushSubscription::Constructor(
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
nsTArray<uint8_t> rawKey;
- if (aInitDict.mP256dhKey.WasPassed() &&
- !aInitDict.mP256dhKey.Value().IsNull() &&
- !aInitDict.mP256dhKey.Value().Value().AppendDataTo(rawKey)) {
+ if (!aInitDict.mP256dhKey.IsNull() &&
+ !aInitDict.mP256dhKey.Value().AppendDataTo(rawKey)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return nullptr;
}
nsTArray<uint8_t> authSecret;
- if (aInitDict.mAuthSecret.WasPassed() &&
- !aInitDict.mAuthSecret.Value().IsNull() &&
- !aInitDict.mAuthSecret.Value().Value().AppendDataTo(authSecret)) {
+ if (!aInitDict.mAuthSecret.IsNull() &&
+ !aInitDict.mAuthSecret.Value().AppendDataTo(authSecret)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return nullptr;
}
nsTArray<uint8_t> appServerKey;
- if (aInitDict.mAppServerKey.WasPassed() &&
- !aInitDict.mAppServerKey.Value().IsNull()) {
- const OwningArrayBufferViewOrArrayBuffer& bufferSource =
- aInitDict.mAppServerKey.Value().Value();
- if (!PushUtil::CopyBufferSourceToArray(bufferSource, appServerKey)) {
- aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
- return nullptr;
- }
+ if (!aInitDict.mAppServerKey.IsNull() &&
+ !AppendTypedArrayDataTo(aInitDict.mAppServerKey.Value(), appServerKey)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
}
Nullable<EpochTimeStamp> expirationTime;
@@ -307,10 +301,10 @@ already_AddRefed<Promise> PushSubscription::Unsubscribe(ErrorResult& aRv) {
void PushSubscription::GetKey(JSContext* aCx, PushEncryptionKeyName aType,
JS::MutableHandle<JSObject*> aKey,
ErrorResult& aRv) {
- if (aType == PushEncryptionKeyName::P256dh) {
- PushUtil::CopyArrayToArrayBuffer(aCx, mRawP256dhKey, aKey, aRv);
- } else if (aType == PushEncryptionKeyName::Auth) {
- PushUtil::CopyArrayToArrayBuffer(aCx, mAuthSecret, aKey, aRv);
+ if (aType == PushEncryptionKeyName::P256dh && !mRawP256dhKey.IsEmpty()) {
+ aKey.set(ArrayBuffer::Create(aCx, mRawP256dhKey, aRv));
+ } else if (aType == PushEncryptionKeyName::Auth && !mAuthSecret.IsEmpty()) {
+ aKey.set(ArrayBuffer::Create(aCx, mAuthSecret, aRv));
} else {
aKey.set(nullptr);
}
diff --git a/dom/push/PushSubscriptionOptions.cpp b/dom/push/PushSubscriptionOptions.cpp
index 1de3d96a2e..b14afa310d 100644
--- a/dom/push/PushSubscriptionOptions.cpp
+++ b/dom/push/PushSubscriptionOptions.cpp
@@ -8,9 +8,9 @@
#include "MainThreadUtils.h"
#include "mozilla/dom/PushSubscriptionOptionsBinding.h"
+#include "mozilla/dom/TypedArray.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/HoldDropJSObjects.h"
-#include "mozilla/dom/PushUtil.h"
#include "nsIGlobalObject.h"
#include "nsWrapperCache.h"
@@ -51,13 +51,10 @@ JSObject* PushSubscriptionOptions::WrapObject(
void PushSubscriptionOptions::GetApplicationServerKey(
JSContext* aCx, JS::MutableHandle<JSObject*> aKey, ErrorResult& aRv) {
if (!mRawAppServerKey.IsEmpty() && !mAppServerKey) {
- JS::Rooted<JSObject*> appServerKey(aCx);
- PushUtil::CopyArrayToArrayBuffer(aCx, mRawAppServerKey, &appServerKey, aRv);
+ mAppServerKey = ArrayBuffer::Create(aCx, mRawAppServerKey, aRv);
if (aRv.Failed()) {
return;
}
- MOZ_ASSERT(appServerKey);
- mAppServerKey = appServerKey;
}
aKey.set(mAppServerKey);
}
diff --git a/dom/push/PushUtil.cpp b/dom/push/PushUtil.cpp
deleted file mode 100644
index b1373391ea..0000000000
--- a/dom/push/PushUtil.cpp
+++ /dev/null
@@ -1,36 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
-/* 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/. */
-
-#include "mozilla/dom/PushUtil.h"
-#include "mozilla/dom/UnionTypes.h"
-
-namespace mozilla::dom {
-
-/* static */
-bool PushUtil::CopyBufferSourceToArray(
- const OwningArrayBufferViewOrArrayBuffer& aSource,
- nsTArray<uint8_t>& aArray) {
- MOZ_ASSERT(aArray.IsEmpty());
- return AppendTypedArrayDataTo(aSource, aArray);
-}
-
-/* static */
-void PushUtil::CopyArrayToArrayBuffer(JSContext* aCx,
- const nsTArray<uint8_t>& aArray,
- JS::MutableHandle<JSObject*> aValue,
- ErrorResult& aRv) {
- if (aArray.IsEmpty()) {
- aValue.set(nullptr);
- return;
- }
- JS::Rooted<JSObject*> buffer(aCx, ArrayBuffer::Create(aCx, aArray, aRv));
- if (NS_WARN_IF(aRv.Failed())) {
- return;
- }
- aValue.set(buffer);
-}
-
-} // namespace mozilla::dom
diff --git a/dom/push/PushUtil.h b/dom/push/PushUtil.h
deleted file mode 100644
index 1e4ccf33a9..0000000000
--- a/dom/push/PushUtil.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
-/* 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/. */
-
-#ifndef mozilla_dom_PushUtil_h
-#define mozilla_dom_PushUtil_h
-
-#include "nsTArray.h"
-
-#include "mozilla/dom/TypedArray.h"
-
-namespace mozilla {
-class ErrorResult;
-
-namespace dom {
-
-class OwningArrayBufferViewOrArrayBuffer;
-
-class PushUtil final {
- private:
- PushUtil() = delete;
-
- public:
- static bool CopyBufferSourceToArray(
- const OwningArrayBufferViewOrArrayBuffer& aSource,
- nsTArray<uint8_t>& aArray);
-
- static void CopyArrayToArrayBuffer(JSContext* aCx,
- const nsTArray<uint8_t>& aArray,
- JS::MutableHandle<JSObject*> aValue,
- ErrorResult& aRv);
-};
-
-} // namespace dom
-} // namespace mozilla
-
-#endif // mozilla_dom_PushUtil_h
diff --git a/dom/push/moz.build b/dom/push/moz.build
index 39da18de23..b2f6f4cc11 100644
--- a/dom/push/moz.build
+++ b/dom/push/moz.build
@@ -44,7 +44,6 @@ EXPORTS.mozilla.dom += [
"PushNotifier.h",
"PushSubscription.h",
"PushSubscriptionOptions.h",
- "PushUtil.h",
]
UNIFIED_SOURCES += [
@@ -52,7 +51,6 @@ UNIFIED_SOURCES += [
"PushNotifier.cpp",
"PushSubscription.cpp",
"PushSubscriptionOptions.cpp",
- "PushUtil.cpp",
]
TEST_DIRS += ["test/xpcshell"]
diff --git a/dom/quota/QuotaCommon.cpp b/dom/quota/QuotaCommon.cpp
index 71b6186d00..a05d52ee3c 100644
--- a/dom/quota/QuotaCommon.cpp
+++ b/dom/quota/QuotaCommon.cpp
@@ -38,18 +38,18 @@
namespace mozilla {
-RefPtr<BoolPromise> CreateAndRejectBoolPromise(const char* aFunc,
+RefPtr<BoolPromise> CreateAndRejectBoolPromise(StaticString aFunc,
nsresult aRv) {
return CreateAndRejectMozPromise<BoolPromise>(aFunc, aRv);
}
-RefPtr<Int64Promise> CreateAndRejectInt64Promise(const char* aFunc,
+RefPtr<Int64Promise> CreateAndRejectInt64Promise(StaticString aFunc,
nsresult aRv) {
return CreateAndRejectMozPromise<Int64Promise>(aFunc, aRv);
}
RefPtr<BoolPromise> CreateAndRejectBoolPromiseFromQMResult(
- const char* aFunc, const QMResult& aRv) {
+ StaticString aFunc, const QMResult& aRv) {
return CreateAndRejectMozPromise<BoolPromise>(aFunc, aRv);
}
diff --git a/dom/quota/QuotaCommon.h b/dom/quota/QuotaCommon.h
index 74855dd16b..166e8de969 100644
--- a/dom/quota/QuotaCommon.h
+++ b/dom/quota/QuotaCommon.h
@@ -22,6 +22,7 @@
#include "mozilla/MacroArgs.h"
#include "mozilla/Maybe.h"
#include "mozilla/ResultExtensions.h"
+#include "mozilla/StaticString.h"
#include "mozilla/Try.h"
#if defined(QM_LOG_ERROR_ENABLED) && defined(QM_ERROR_STACKS_ENABLED)
# include "mozilla/Variant.h"
@@ -1133,7 +1134,7 @@ auto ErrToDefaultOk(const nsresult aValue) -> Result<V, nsresult> {
}
template <typename MozPromiseType, typename RejectValueT = nsresult>
-auto CreateAndRejectMozPromise(const char* aFunc, const RejectValueT& aRv)
+auto CreateAndRejectMozPromise(StaticString aFunc, const RejectValueT& aRv)
-> decltype(auto) {
if constexpr (std::is_same_v<RejectValueT, nsresult>) {
return MozPromiseType::CreateAndReject(aRv, aFunc);
@@ -1142,12 +1143,13 @@ auto CreateAndRejectMozPromise(const char* aFunc, const RejectValueT& aRv)
}
}
-RefPtr<BoolPromise> CreateAndRejectBoolPromise(const char* aFunc, nsresult aRv);
+RefPtr<BoolPromise> CreateAndRejectBoolPromise(StaticString aFunc,
+ nsresult aRv);
-RefPtr<Int64Promise> CreateAndRejectInt64Promise(const char* aFunc,
+RefPtr<Int64Promise> CreateAndRejectInt64Promise(StaticString aFunc,
nsresult aRv);
-RefPtr<BoolPromise> CreateAndRejectBoolPromiseFromQMResult(const char* aFunc,
+RefPtr<BoolPromise> CreateAndRejectBoolPromiseFromQMResult(StaticString aFunc,
const QMResult& aRv);
// Like Rust's collect with a step function, not a generic iterator/range.
@@ -1493,9 +1495,49 @@ Nothing HandleErrorWithCleanupReturnNothing(const char* aExpr, const T& aRv,
return Nothing();
}
-template <size_t NFunc, size_t NExpr, typename T, typename CustomRetVal>
+// Implementation of workaround for GCC bug #114812.
+#if defined(__GNUC__) && !defined(__clang__)
+namespace gcc_detail {
+// usual case: identity function
+template <typename T>
+struct invokabilize_impl {
+ auto operator()(T t) -> T { return t; }
+};
+// reference-to-function: wrap in std::function
+template <typename R, typename... Args>
+struct invokabilize_impl<R (&)(Args...)> {
+ auto operator()(R (&t)(Args...)) -> std::function<R(Args...)> {
+ return std::function{t};
+ }
+};
+// pointer-to-function: wrap in std::function
+template <typename R, typename... Args>
+struct invokabilize_impl<R (*)(Args...)> {
+ auto operator()(R (*t)(Args...)) -> std::function<R(Args...)> {
+ return std::function{t};
+ }
+};
+// entry point
+template <typename T>
+auto invokabilize(T t) {
+ return invokabilize_impl<T>{}(std::forward<T>(t));
+}
+} // namespace gcc_detail
+#endif
+
+template <size_t NFunc, size_t NExpr, typename T, typename CustomRetVal_>
auto HandleCustomRetVal(const char (&aFunc)[NFunc], const char (&aExpr)[NExpr],
- const T& aRv, CustomRetVal&& aCustomRetVal) {
+ const T& aRv, CustomRetVal_&& aCustomRetVal_) {
+#if defined(__GNUC__) && !defined(__clang__)
+ // Workaround for gcc bug #114812. (See either that bug, or our bug 1891541,
+ // for more details.)
+ auto aCustomRetVal =
+ gcc_detail::invokabilize(std::forward<CustomRetVal_>(aCustomRetVal_));
+ using CustomRetVal = decltype(aCustomRetVal);
+#else
+ using CustomRetVal = CustomRetVal_;
+ CustomRetVal& aCustomRetVal = aCustomRetVal_;
+#endif
if constexpr (std::is_invocable<CustomRetVal, const char[NFunc],
const char[NExpr]>::value) {
return std::forward<CustomRetVal>(aCustomRetVal)(aFunc, aExpr);
diff --git a/dom/quota/StorageManager.cpp b/dom/quota/StorageManager.cpp
index ec1e5b274f..cfff5cf202 100644
--- a/dom/quota/StorageManager.cpp
+++ b/dom/quota/StorageManager.cpp
@@ -119,13 +119,13 @@ class RequestResolver final : public nsIQuotaCallback {
};
// This class is used to return promise on worker thread.
-class RequestResolver::FinishWorkerRunnable final : public WorkerRunnable {
+class RequestResolver::FinishWorkerRunnable final
+ : public WorkerThreadRunnable {
RefPtr<RequestResolver> mResolver;
public:
explicit FinishWorkerRunnable(RequestResolver* aResolver)
- : WorkerRunnable(aResolver->mProxy->GetWorkerPrivate(),
- "RequestResolver::FinishWorkerRunnable"),
+ : WorkerThreadRunnable("RequestResolver::FinishWorkerRunnable"),
mResolver(aResolver) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aResolver);
@@ -565,7 +565,7 @@ nsresult RequestResolver::Finish() {
}
RefPtr<FinishWorkerRunnable> runnable = new FinishWorkerRunnable(this);
- if (NS_WARN_IF(!runnable->Dispatch())) {
+ if (NS_WARN_IF(!runnable->Dispatch(mProxy->GetWorkerPrivate()))) {
return NS_ERROR_FAILURE;
}
}
diff --git a/dom/quota/test/gtest/TestEncryptedStream.cpp b/dom/quota/test/gtest/TestEncryptedStream.cpp
index 8937b0c4e7..04fe43f38e 100644
--- a/dom/quota/test/gtest/TestEncryptedStream.cpp
+++ b/dom/quota/test/gtest/TestEncryptedStream.cpp
@@ -127,20 +127,20 @@ ArrayBufferInputStream::Read(char* aBuf, uint32_t aCount,
NS_IMETHODIMP
ArrayBufferInputStream::ReadSegments(nsWriteSegmentFun writer, void* closure,
uint32_t aCount, uint32_t* result) {
- MOZ_ASSERT(result, "null ptr");
- MOZ_ASSERT(mBufferLength >= mPos, "bad stream state");
+ MOZ_RELEASE_ASSERT(result, "null ptr");
+ MOZ_RELEASE_ASSERT(mBufferLength >= mPos, "bad stream state");
if (mClosed) {
return NS_BASE_STREAM_CLOSED;
}
- MOZ_ASSERT(mArrayBuffer || (mPos == mBufferLength),
- "stream inited incorrectly");
+ MOZ_RELEASE_ASSERT(mArrayBuffer || (mPos == mBufferLength),
+ "stream inited incorrectly");
*result = 0;
while (mPos < mBufferLength) {
uint32_t remaining = mBufferLength - mPos;
- MOZ_ASSERT(mArrayBuffer);
+ MOZ_RELEASE_ASSERT(mArrayBuffer);
uint32_t count = std::min(aCount, remaining);
if (count == 0) {
@@ -155,8 +155,9 @@ ArrayBufferInputStream::ReadSegments(nsWriteSegmentFun writer, void* closure,
return NS_OK;
}
- MOZ_ASSERT(written <= count,
- "writer should not write more than we asked it to write");
+ MOZ_RELEASE_ASSERT(
+ written <= count,
+ "writer should not write more than we asked it to write");
mPos += written;
*result += written;
aCount -= written;
@@ -174,7 +175,7 @@ ArrayBufferInputStream::IsNonBlocking(bool* aNonBlocking) {
}
NS_IMETHODIMP ArrayBufferInputStream::Tell(int64_t* const aRetval) {
- MOZ_ASSERT(aRetval);
+ MOZ_RELEASE_ASSERT(aRetval);
*aRetval = mPos;
diff --git a/dom/security/FramingChecker.cpp b/dom/security/FramingChecker.cpp
index ecd7a6863e..bee587e701 100644
--- a/dom/security/FramingChecker.cpp
+++ b/dom/security/FramingChecker.cpp
@@ -151,6 +151,8 @@ bool FramingChecker::CheckFrameOptions(nsIChannel* aChannel,
return true;
}
+ static const char kASCIIWhitespace[] = "\t ";
+
// Step 3-4. reduce the header options to a unique set and count how many
// unique values (that we track) are encountered. this avoids using a set to
// stop attackers from inheriting arbitrary values in memory and reduce the
@@ -158,7 +160,7 @@ bool FramingChecker::CheckFrameOptions(nsIChannel* aChannel,
XFOHeader xfoOptions;
for (const nsACString& next : xfoHeaderValue.Split(',')) {
nsAutoCString option(next);
- option.StripWhitespace();
+ option.Trim(kASCIIWhitespace);
if (option.LowerCaseEqualsLiteral("allowall")) {
xfoOptions.ALLOWALL = true;
diff --git a/dom/security/metrics.yaml b/dom/security/metrics.yaml
index 02084407b1..d48069e4b1 100644
--- a/dom/security/metrics.yaml
+++ b/dom/security/metrics.yaml
@@ -118,8 +118,8 @@ httpsfirst:
description: >
If a HTTPS-First (`dom.security.https_first` enabled) upgrade isn't
successful, measures the timespan between the navigation start and the
- downgrade. This is essentially the overhead caused by HTTPS-First if a
- site does not support HTTPS.
+ downgrade. This does not include the case in which the https request times
+ out and the http request sent after 3s gets a response faster.
time_unit: millisecond
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1868380
@@ -135,11 +135,11 @@ httpsfirst:
downgrade_time_schemeless:
type: timing_distribution
description: >
- If a schemeless HTTPS-First (`dom.security.https_first` disabled, but
- load marked as schemeless) upgrade isn't successful, measures the
- timespan between the navigation start and the downgrade. This is
- essentially the overhead caused by HTTPS-First if a site does not support
- HTTPS.
+ If a schemeless HTTPS-First (`dom.security.https_first` disabled, but load
+ marked as schemeless) upgrade isn't successful, measures the timespan
+ between the navigation start and the downgrade. This does not include the
+ case in which the https request times out and the http request sent after
+ 3s gets a response faster.
time_unit: millisecond
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1868380
diff --git a/dom/security/nsCSPContext.cpp b/dom/security/nsCSPContext.cpp
index de67e2bf1c..b9675e39fc 100644
--- a/dom/security/nsCSPContext.cpp
+++ b/dom/security/nsCSPContext.cpp
@@ -6,6 +6,7 @@
#include <string>
#include <unordered_set>
+#include <utility>
#include "nsCOMPtr.h"
#include "nsContentPolicyUtils.h"
@@ -42,6 +43,7 @@
#include "mozilla/Logging.h"
#include "mozilla/Preferences.h"
#include "mozilla/StaticPrefs_security.h"
+#include "mozilla/Variant.h"
#include "mozilla/dom/CSPReportBinding.h"
#include "mozilla/dom/CSPDictionariesBinding.h"
#include "mozilla/ipc/PBackgroundSharedTypes.h"
@@ -741,16 +743,16 @@ nsCSPContext::LogViolationDetails(
continue;
}
- nsAutoString violatedDirective;
- nsAutoString violatedDirectiveString;
+ nsAutoString violatedDirectiveName;
+ nsAutoString violatedDirectiveNameAndValue;
bool reportSample = false;
mPolicies[p]->getViolatedDirectiveInformation(
- SCRIPT_SRC_DIRECTIVE, violatedDirective, violatedDirectiveString,
- &reportSample);
+ SCRIPT_SRC_DIRECTIVE, violatedDirectiveName,
+ violatedDirectiveNameAndValue, &reportSample);
AsyncReportViolation(
aTriggeringElement, aCSPEventListener, nullptr, blockedContentSource,
- nullptr, violatedDirective, violatedDirectiveString,
+ nullptr, violatedDirectiveName, violatedDirectiveNameAndValue,
CSPDirective::SCRIPT_SRC_DIRECTIVE /* aEffectiveDirective */, p,
observerSubject, aSourceFile, reportSample, aScriptSample, aLineNum,
aColumnNum);
@@ -965,7 +967,7 @@ void StripURIForReporting(nsIURI* aSelfURI, nsIURI* aURI,
}
nsresult nsCSPContext::GatherSecurityPolicyViolationEventData(
- nsIURI* aBlockedURI, const nsACString& aBlockedString, nsIURI* aOriginalURI,
+ Resource& aResource, nsIURI* aOriginalURI,
const nsAString& aEffectiveDirective, uint32_t aViolatedPolicyIndex,
const nsAString& aSourceFile, const nsAString& aScriptSample,
uint32_t aLineNum, uint32_t aColumnNum,
@@ -988,13 +990,19 @@ nsresult nsCSPContext::GatherSecurityPolicyViolationEventData(
CopyUTF8toUTF16(mReferrer, aViolationEventInit.mReferrer);
// blocked-uri
- if (aBlockedURI) {
+ // Corresponds to
+ // <https://w3c.github.io/webappsec-csp/#obtain-violation-blocked-uri>.
+ if (aResource.is<nsIURI*>()) {
nsAutoCString reportBlockedURI;
- StripURIForReporting(mSelfURI, aOriginalURI ? aOriginalURI : aBlockedURI,
+ StripURIForReporting(mSelfURI,
+ aOriginalURI ? aOriginalURI : aResource.as<nsIURI*>(),
aEffectiveDirective, reportBlockedURI);
CopyUTF8toUTF16(reportBlockedURI, aViolationEventInit.mBlockedURI);
} else {
- CopyUTF8toUTF16(aBlockedString, aViolationEventInit.mBlockedURI);
+ nsAutoCString blockedContentSource;
+ BlockedContentSourceToString(aResource.as<BlockedContentSource>(),
+ blockedContentSource);
+ CopyUTF8toUTF16(blockedContentSource, aViolationEventInit.mBlockedURI);
}
// effective-directive
@@ -1372,8 +1380,8 @@ class CSPReportSenderRunnable final : public Runnable {
nsIURI* aBlockedURI,
nsCSPContext::BlockedContentSource aBlockedContentSource,
nsIURI* aOriginalURI, uint32_t aViolatedPolicyIndex, bool aReportOnlyFlag,
- const nsAString& aViolatedDirective,
- const nsAString& aViolatedDirectiveString,
+ const nsAString& aViolatedDirectiveName,
+ const nsAString& aViolatedDirectiveNameAndValue,
const CSPDirective aEffectiveDirective, const nsAString& aObserverSubject,
const nsAString& aSourceFile, bool aReportSample,
const nsAString& aScriptSample, uint32_t aLineNum, uint32_t aColumnNum,
@@ -1387,15 +1395,15 @@ class CSPReportSenderRunnable final : public Runnable {
mViolatedPolicyIndex(aViolatedPolicyIndex),
mReportOnlyFlag(aReportOnlyFlag),
mReportSample(aReportSample),
- mViolatedDirective(aViolatedDirective),
- mViolatedDirectiveString(aViolatedDirectiveString),
+ mViolatedDirectiveName(aViolatedDirectiveName),
+ mViolatedDirectiveNameAndValue(aViolatedDirectiveNameAndValue),
mEffectiveDirective(aEffectiveDirective),
mSourceFile(aSourceFile),
mScriptSample(aScriptSample),
mLineNum(aLineNum),
mColumnNum(aColumnNum),
mCSPContext(aCSPContext) {
- NS_ASSERTION(!aViolatedDirective.IsEmpty(),
+ NS_ASSERTION(!aViolatedDirectiveName.IsEmpty(),
"Can not send reports without a violated directive");
// the observer subject is an nsISupports: either an nsISupportsCString
// from the arg passed in directly, or if that's empty, it's the blocked
@@ -1439,18 +1447,19 @@ class CSPReportSenderRunnable final : public Runnable {
// 0) prepare violation data
mozilla::dom::SecurityPolicyViolationEventInit init;
- nsAutoCString blockedContentSource;
- BlockedContentSourceToString(mBlockedContentSource, blockedContentSource);
-
nsAutoString effectiveDirective;
effectiveDirective.AssignASCII(
CSP_CSPDirectiveToString(mEffectiveDirective));
+ using Resource = nsCSPContext::Resource;
+
+ Resource resource = mBlockedURI ? Resource(mBlockedURI.get())
+ : Resource(mBlockedContentSource);
+
nsresult rv = mCSPContext->GatherSecurityPolicyViolationEventData(
- mBlockedURI, blockedContentSource, mOriginalURI, effectiveDirective,
- mViolatedPolicyIndex, mSourceFile,
- mReportSample ? mScriptSample : EmptyString(), mLineNum, mColumnNum,
- init);
+ resource, mOriginalURI, effectiveDirective, mViolatedPolicyIndex,
+ mSourceFile, mReportSample ? mScriptSample : EmptyString(), mLineNum,
+ mColumnNum, init);
NS_ENSURE_SUCCESS(rv, rv);
// 1) notify observers
@@ -1458,7 +1467,7 @@ class CSPReportSenderRunnable final : public Runnable {
mozilla::services::GetObserverService();
if (mObserverSubject && observerService) {
rv = observerService->NotifyObservers(
- mObserverSubject, CSP_VIOLATION_TOPIC, mViolatedDirective.get());
+ mObserverSubject, CSP_VIOLATION_TOPIC, mViolatedDirectiveName.get());
NS_ENSURE_SUCCESS(rv, rv);
}
@@ -1471,7 +1480,7 @@ class CSPReportSenderRunnable final : public Runnable {
// 4) fire violation event
// A frame-ancestors violation has occurred, but we should not dispatch
// the violation event to a potentially cross-origin ancestor.
- if (!mViolatedDirective.EqualsLiteral("frame-ancestors")) {
+ if (!mViolatedDirectiveName.EqualsLiteral("frame-ancestors")) {
mCSPContext->FireViolationEvent(mTriggeringElement, mCSPEventListener,
init);
}
@@ -1502,7 +1511,7 @@ class CSPReportSenderRunnable final : public Runnable {
: "CSPInlineScriptViolation";
}
- AutoTArray<nsString, 2> params = {mViolatedDirectiveString,
+ AutoTArray<nsString, 2> params = {mViolatedDirectiveNameAndValue,
effectiveDirective};
mCSPContext->logToConsole(errorName, params, mSourceFile, mScriptSample,
mLineNum, mColumnNum,
@@ -1511,7 +1520,7 @@ class CSPReportSenderRunnable final : public Runnable {
}
case nsCSPContext::BlockedContentSource::eEval: {
- AutoTArray<nsString, 2> params = {mViolatedDirectiveString,
+ AutoTArray<nsString, 2> params = {mViolatedDirectiveNameAndValue,
effectiveDirective};
mCSPContext->logToConsole(mReportOnlyFlag ? "CSPROEvalScriptViolation"
: "CSPEvalScriptViolation",
@@ -1521,7 +1530,7 @@ class CSPReportSenderRunnable final : public Runnable {
}
case nsCSPContext::BlockedContentSource::eWasmEval: {
- AutoTArray<nsString, 2> params = {mViolatedDirectiveString,
+ AutoTArray<nsString, 2> params = {mViolatedDirectiveNameAndValue,
effectiveDirective};
mCSPContext->logToConsole(mReportOnlyFlag
? "CSPROWasmEvalScriptViolation"
@@ -1569,8 +1578,8 @@ class CSPReportSenderRunnable final : public Runnable {
: "CSPGenericViolation";
}
- AutoTArray<nsString, 3> params = {mViolatedDirectiveString, source,
- effectiveDirective};
+ AutoTArray<nsString, 3> params = {mViolatedDirectiveNameAndValue,
+ source, effectiveDirective};
mCSPContext->logToConsole(errorName, params, mSourceFile, mScriptSample,
mLineNum, mColumnNum,
nsIScriptError::errorFlag);
@@ -1586,8 +1595,8 @@ class CSPReportSenderRunnable final : public Runnable {
uint32_t mViolatedPolicyIndex;
bool mReportOnlyFlag;
bool mReportSample;
- nsString mViolatedDirective;
- nsString mViolatedDirectiveString;
+ nsString mViolatedDirectiveName;
+ nsString mViolatedDirectiveNameAndValue;
CSPDirective mEffectiveDirective;
nsCOMPtr<nsISupports> mObserverSubject;
nsString mSourceFile;
@@ -1609,7 +1618,7 @@ class CSPReportSenderRunnable final : public Runnable {
* of the violation.
* @param aOriginalUri
* The original URI if the blocked content is a redirect, else null
- * @param aViolatedDirective
+ * @param aViolatedDirectiveName
* the directive that was violated (string).
* @param aViolatedPolicyIndex
* the index of the policy that was violated (so we know where to send
@@ -1629,8 +1638,8 @@ class CSPReportSenderRunnable final : public Runnable {
nsresult nsCSPContext::AsyncReportViolation(
Element* aTriggeringElement, nsICSPEventListener* aCSPEventListener,
nsIURI* aBlockedURI, BlockedContentSource aBlockedContentSource,
- nsIURI* aOriginalURI, const nsAString& aViolatedDirective,
- const nsAString& aViolatedDirectiveString,
+ nsIURI* aOriginalURI, const nsAString& aViolatedDirectiveName,
+ const nsAString& aViolatedDirectiveNameAndValue,
const CSPDirective aEffectiveDirective, uint32_t aViolatedPolicyIndex,
const nsAString& aObserverSubject, const nsAString& aSourceFile,
bool aReportSample, const nsAString& aScriptSample, uint32_t aLineNum,
@@ -1641,9 +1650,10 @@ nsresult nsCSPContext::AsyncReportViolation(
nsCOMPtr<nsIRunnable> task = new CSPReportSenderRunnable(
aTriggeringElement, aCSPEventListener, aBlockedURI, aBlockedContentSource,
aOriginalURI, aViolatedPolicyIndex,
- mPolicies[aViolatedPolicyIndex]->getReportOnlyFlag(), aViolatedDirective,
- aViolatedDirectiveString, aEffectiveDirective, aObserverSubject,
- aSourceFile, aReportSample, aScriptSample, aLineNum, aColumnNum, this);
+ mPolicies[aViolatedPolicyIndex]->getReportOnlyFlag(),
+ aViolatedDirectiveName, aViolatedDirectiveNameAndValue,
+ aEffectiveDirective, aObserverSubject, aSourceFile, aReportSample,
+ aScriptSample, aLineNum, aColumnNum, this);
if (XRE_IsContentProcess()) {
if (mEventTarget) {
diff --git a/dom/security/nsCSPContext.h b/dom/security/nsCSPContext.h
index e4fe5af315..f957c85e48 100644
--- a/dom/security/nsCSPContext.h
+++ b/dom/security/nsCSPContext.h
@@ -32,6 +32,8 @@ class nsIEventTarget;
struct ConsoleMsgQueueElem;
namespace mozilla {
+template <typename... Ts>
+class Variant;
namespace dom {
class Element;
}
@@ -77,11 +79,23 @@ class nsCSPContext : public nsIContentSecurityPolicy {
uint32_t aLineNumber, uint32_t aColumnNumber,
uint32_t aSeverityFlag);
+ enum BlockedContentSource {
+ eUnknown,
+ eInline,
+ eEval,
+ eSelf,
+ eWasmEval,
+ };
+
+ // Roughly implements a violation's resource
+ // (https://w3c.github.io/webappsec-csp/#framework-violation).
+ using Resource = mozilla::Variant<nsIURI*, BlockedContentSource>;
+
/**
* Construct SecurityPolicyViolationEventInit structure.
*
- * @param aBlockedURI
- * A nsIURI: the source of the violation.
+ * @param aResource
+ * The source of the violation.
* @param aOriginalUri
* The original URI if the blocked content is a redirect, else null
* @param aViolatedDirective
@@ -98,10 +112,10 @@ class nsCSPContext : public nsIContentSecurityPolicy {
* The output
*/
nsresult GatherSecurityPolicyViolationEventData(
- nsIURI* aBlockedURI, const nsACString& aBlockedString,
- nsIURI* aOriginalURI, const nsAString& aViolatedDirective,
- uint32_t aViolatedPolicyIndex, const nsAString& aSourceFile,
- const nsAString& aScriptSample, uint32_t aLineNum, uint32_t aColumnNum,
+ Resource& aResource, nsIURI* aOriginalURI,
+ const nsAString& aViolatedDirective, uint32_t aViolatedPolicyIndex,
+ const nsAString& aSourceFile, const nsAString& aScriptSample,
+ uint32_t aLineNum, uint32_t aColumnNum,
mozilla::dom::SecurityPolicyViolationEventInit& aViolationEventInit);
nsresult SendReports(
@@ -114,20 +128,12 @@ class nsCSPContext : public nsIContentSecurityPolicy {
const mozilla::dom::SecurityPolicyViolationEventInit&
aViolationEventInit);
- enum BlockedContentSource {
- eUnknown,
- eInline,
- eEval,
- eSelf,
- eWasmEval,
- };
-
nsresult AsyncReportViolation(
mozilla::dom::Element* aTriggeringElement,
nsICSPEventListener* aCSPEventListener, nsIURI* aBlockedURI,
BlockedContentSource aBlockedContentSource, nsIURI* aOriginalURI,
- const nsAString& aViolatedDirective,
- const nsAString& aViolatedDirectiveString,
+ const nsAString& aViolatedDirectiveName,
+ const nsAString& aViolatedDirectiveNameAndValue,
const CSPDirective aEffectiveDirective, uint32_t aViolatedPolicyIndex,
const nsAString& aObserverSubject, const nsAString& aSourceFile,
bool aReportSample, const nsAString& aScriptSample, uint32_t aLineNum,
diff --git a/dom/security/nsCSPParser.cpp b/dom/security/nsCSPParser.cpp
index 07812470a3..44c2131640 100644
--- a/dom/security/nsCSPParser.cpp
+++ b/dom/security/nsCSPParser.cpp
@@ -8,6 +8,7 @@
#include "mozilla/TextUtils.h"
#include "mozilla/dom/Document.h"
#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPrefs_security.h"
#include "nsCOMPtr.h"
#include "nsContentUtils.h"
@@ -19,6 +20,9 @@
#include "nsServiceManagerUtils.h"
#include "nsUnicharUtils.h"
+#include <cstdint>
+#include <utility>
+
using namespace mozilla;
using namespace mozilla::dom;
@@ -813,6 +817,142 @@ void nsCSPParser::sandboxFlagList(nsCSPDirective* aDir) {
mPolicy->addDirective(aDir);
}
+// https://w3c.github.io/trusted-types/dist/spec/#integration-with-content-security-policy
+static constexpr nsLiteralString kValidRequireTrustedTypesForDirectiveValue =
+ u"'script'"_ns;
+
+static bool IsValidRequireTrustedTypesForDirectiveValue(
+ const nsAString& aToken) {
+ return aToken.Equals(kValidRequireTrustedTypesForDirectiveValue);
+}
+
+void nsCSPParser::handleRequireTrustedTypesForDirective(nsCSPDirective* aDir) {
+ // "srcs" start at index 1. Here "srcs" should represent Trusted Types' sink
+ // groups
+ // (https://w3c.github.io/trusted-types/dist/spec/#require-trusted-types-for-csp-directive).
+
+ if (mCurDir.Length() != 2) {
+ nsString numberOfTokensStr;
+
+ // Casting is required to avoid ambiguous function calls on some platforms.
+ numberOfTokensStr.AppendInt(static_cast<uint64_t>(mCurDir.Length()));
+
+ AutoTArray<nsString, 1> numberOfTokensArr = {std::move(numberOfTokensStr)};
+ logWarningErrorToConsole(nsIScriptError::errorFlag,
+ "invalidNumberOfTrustedTypesForDirectiveValues",
+ numberOfTokensArr);
+ return;
+ }
+
+ mCurToken = mCurDir.LastElement();
+
+ CSPPARSERLOG(
+ ("nsCSPParser::handleRequireTrustedTypesForDirective, mCurToken: %s",
+ NS_ConvertUTF16toUTF8(mCurToken).get()));
+
+ if (!IsValidRequireTrustedTypesForDirectiveValue(mCurToken)) {
+ AutoTArray<nsString, 1> token = {mCurToken};
+ logWarningErrorToConsole(nsIScriptError::errorFlag,
+ "invalidRequireTrustedTypesForDirectiveValue",
+ token);
+ return;
+ }
+
+ nsTArray<nsCSPBaseSrc*> srcs = {
+ new nsCSPRequireTrustedTypesForDirectiveValue(mCurToken)};
+
+ aDir->addSrcs(srcs);
+ mPolicy->addDirective(aDir);
+}
+
+static constexpr auto kTrustedTypesKeywordAllowDuplicates =
+ u"'allow-duplicates'"_ns;
+static constexpr auto kTrustedTypesKeywordNone = u"'none'"_ns;
+
+static bool IsValidTrustedTypesKeyword(const nsAString& aToken) {
+ // tt-keyword = "'allow-duplicates'" / "'none'"
+ return aToken.Equals(kTrustedTypesKeywordAllowDuplicates) ||
+ aToken.Equals(kTrustedTypesKeywordNone);
+}
+
+static bool IsValidTrustedTypesWildcard(const nsAString& aToken) {
+ // tt-wildcard = "*"
+ return aToken.Length() == 1 && aToken.First() == WILDCARD;
+}
+
+static bool IsValidTrustedTypesPolicyNameChar(char16_t aChar) {
+ // tt-policy-name = 1*( ALPHA / DIGIT / "-" / "#" / "=" / "_" / "/" / "@" /
+ // "." / "%")
+ return nsContentUtils::IsAlphanumeric(aChar) || aChar == DASH ||
+ aChar == NUMBER_SIGN || aChar == EQUALS || aChar == UNDERLINE ||
+ aChar == SLASH || aChar == ATSYMBOL || aChar == DOT ||
+ aChar == PERCENT_SIGN;
+}
+
+static bool IsValidTrustedTypesPolicyName(const nsAString& aToken) {
+ // tt-policy-name = 1*( ALPHA / DIGIT / "-" / "#" / "=" / "_" / "/" / "@" /
+ // "." / "%")
+
+ if (aToken.IsEmpty()) {
+ return false;
+ }
+
+ for (uint32_t i = 0; i < aToken.Length(); ++i) {
+ if (!IsValidTrustedTypesPolicyNameChar(aToken.CharAt(i))) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// https://w3c.github.io/trusted-types/dist/spec/#trusted-types-csp-directive
+static bool IsValidTrustedTypesExpression(const nsAString& aToken) {
+ // tt-expression = tt-policy-name / tt-keyword / tt-wildcard
+ return IsValidTrustedTypesPolicyName(aToken) ||
+ IsValidTrustedTypesKeyword(aToken) ||
+ IsValidTrustedTypesWildcard(aToken);
+}
+
+void nsCSPParser::handleTrustedTypesDirective(nsCSPDirective* aDir) {
+ CSPPARSERLOG(("nsCSPParser::handleTrustedTypesDirective"));
+
+ nsTArray<nsCSPBaseSrc*> trustedTypesExpressions;
+
+ // "srcs" start and index 1. Here they should represent the tt-expressions
+ // (https://w3c.github.io/trusted-types/dist/spec/#trusted-types-csp-directive).
+ for (uint32_t i = 1; i < mCurDir.Length(); ++i) {
+ mCurToken = mCurDir[i];
+
+ CSPPARSERLOG(("nsCSPParser::handleTrustedTypesDirective, mCurToken: %s",
+ NS_ConvertUTF16toUTF8(mCurToken).get()));
+
+ if (!IsValidTrustedTypesExpression(mCurToken)) {
+ AutoTArray<nsString, 1> token = {mCurToken};
+ logWarningErrorToConsole(nsIScriptError::errorFlag,
+ "invalidTrustedTypesExpression", token);
+
+ for (auto* trustedTypeExpression : trustedTypesExpressions) {
+ delete trustedTypeExpression;
+ }
+
+ return;
+ }
+
+ trustedTypesExpressions.AppendElement(
+ new nsCSPTrustedTypesDirectivePolicyName(mCurToken));
+ }
+
+ if (trustedTypesExpressions.IsEmpty()) {
+ // No tt-expression is equivalent to 'none', see
+ // <https://w3c.github.io/trusted-types/dist/spec/#trusted-types-csp-directive>.
+ trustedTypesExpressions.AppendElement(new nsCSPKeywordSrc(CSP_NONE));
+ }
+
+ aDir->addSrcs(trustedTypesExpressions);
+ mPolicy->addDirective(aDir);
+}
+
// directive-value = *( WSP / <VCHAR except ";" and ","> )
void nsCSPParser::directiveValue(nsTArray<nsCSPBaseSrc*>& outSrcs) {
CSPPARSERLOG(("nsCSPParser::directiveValue"));
@@ -829,7 +969,11 @@ nsCSPDirective* nsCSPParser::directiveName() {
// Check if it is a valid directive
CSPDirective directive = CSP_StringToCSPDirective(mCurToken);
- if (directive == nsIContentSecurityPolicy::NO_DIRECTIVE) {
+ if (directive == nsIContentSecurityPolicy::NO_DIRECTIVE ||
+ (!StaticPrefs::dom_security_trusted_types_enabled() &&
+ (directive ==
+ nsIContentSecurityPolicy::REQUIRE_TRUSTED_TYPES_FOR_DIRECTIVE ||
+ directive == nsIContentSecurityPolicy::TRUSTED_TYPES_DIRECTIVE))) {
AutoTArray<nsString, 1> params = {mCurToken};
logWarningErrorToConsole(nsIScriptError::warningFlag,
"couldNotProcessUnknownDirective", params);
@@ -1008,6 +1152,19 @@ void nsCSPParser::directive() {
return;
}
+ // Special case handling since these directives don't contain source lists.
+ if (CSP_IsDirective(
+ mCurDir[0],
+ nsIContentSecurityPolicy::REQUIRE_TRUSTED_TYPES_FOR_DIRECTIVE)) {
+ handleRequireTrustedTypesForDirective(cspDir);
+ return;
+ }
+
+ if (cspDir->equals(nsIContentSecurityPolicy::TRUSTED_TYPES_DIRECTIVE)) {
+ handleTrustedTypesDirective(cspDir);
+ return;
+ }
+
// make sure to reset cache variables when trying to invalidate unsafe-inline;
// unsafe-inline might not only appear in script-src, but also in default-src
mHasHashOrNonce = false;
diff --git a/dom/security/nsCSPParser.h b/dom/security/nsCSPParser.h
index 28c24440d0..627acc9820 100644
--- a/dom/security/nsCSPParser.h
+++ b/dom/security/nsCSPParser.h
@@ -72,6 +72,8 @@ class nsCSPParser {
void referrerDirectiveValue(nsCSPDirective* aDir);
void reportURIList(nsCSPDirective* aDir);
void sandboxFlagList(nsCSPDirective* aDir);
+ void handleRequireTrustedTypesForDirective(nsCSPDirective* aDir);
+ void handleTrustedTypesDirective(nsCSPDirective* aDir);
void sourceList(nsTArray<nsCSPBaseSrc*>& outSrcs);
nsCSPBaseSrc* sourceExpression();
nsCSPSchemeSrc* schemeSource();
diff --git a/dom/security/nsCSPUtils.cpp b/dom/security/nsCSPUtils.cpp
index 11d09909f7..f4aecfaf44 100644
--- a/dom/security/nsCSPUtils.cpp
+++ b/dom/security/nsCSPUtils.cpp
@@ -23,6 +23,7 @@
#include "nsServiceManagerUtils.h"
#include "nsWhitespaceTokenizer.h"
+#include "mozilla/Assertions.h"
#include "mozilla/Components.h"
#include "mozilla/dom/CSPDictionariesBinding.h"
#include "mozilla/dom/Document.h"
@@ -243,6 +244,20 @@ void CSP_LogMessage(const nsAString& aMessage, const nsAString& aSourceName,
console->LogMessage(error);
}
+CSPDirective CSP_StringToCSPDirective(const nsAString& aDir) {
+ nsString lowerDir = PromiseFlatString(aDir);
+ ToLowerCase(lowerDir);
+
+ uint32_t numDirs = (sizeof(CSPStrDirectives) / sizeof(CSPStrDirectives[0]));
+
+ for (uint32_t i = 1; i < numDirs; i++) {
+ if (lowerDir.EqualsASCII(CSPStrDirectives[i])) {
+ return static_cast<CSPDirective>(i);
+ }
+ }
+ return nsIContentSecurityPolicy::NO_DIRECTIVE;
+}
+
/**
* Combines CSP_LogMessage and CSP_GetLocalizedStr into one call.
*/
@@ -997,6 +1012,41 @@ void nsCSPSandboxFlags::toString(nsAString& outStr) const {
outStr.Append(mFlags);
}
+/* ===== nsCSPRequireTrustedTypesForDirectiveValue ===================== */
+
+nsCSPRequireTrustedTypesForDirectiveValue::
+ nsCSPRequireTrustedTypesForDirectiveValue(const nsAString& aValue)
+ : mValue{aValue} {}
+
+bool nsCSPRequireTrustedTypesForDirectiveValue::visit(
+ nsCSPSrcVisitor* aVisitor) const {
+ MOZ_ASSERT_UNREACHABLE(
+ "This method should only be called for other overloads of this method.");
+ return false;
+}
+
+void nsCSPRequireTrustedTypesForDirectiveValue::toString(
+ nsAString& aOutStr) const {
+ aOutStr.Append(mValue);
+}
+
+/* =============== nsCSPTrustedTypesDirectivePolicyName =============== */
+
+nsCSPTrustedTypesDirectivePolicyName::nsCSPTrustedTypesDirectivePolicyName(
+ const nsAString& aName)
+ : mName{aName} {}
+
+bool nsCSPTrustedTypesDirectivePolicyName::visit(
+ nsCSPSrcVisitor* aVisitor) const {
+ MOZ_ASSERT_UNREACHABLE(
+ "Should only be called for other overloads of this method.");
+ return false;
+}
+
+void nsCSPTrustedTypesDirectivePolicyName::toString(nsAString& aOutStr) const {
+ aOutStr.Append(mName);
+}
+
/* ===== nsCSPDirective ====================== */
nsCSPDirective::nsCSPDirective(CSPDirective aDirective) {
@@ -1279,6 +1329,9 @@ bool nsCSPDirective::allowsAllInlineBehavior(CSPDirective aDir) const {
void nsCSPDirective::toString(nsAString& outStr) const {
// Append directive name
outStr.AppendASCII(CSP_CSPDirectiveToString(mDirective));
+
+ MOZ_ASSERT(!mSrcs.IsEmpty());
+
outStr.AppendLiteral(" ");
// Append srcs
@@ -1414,6 +1467,21 @@ void nsCSPDirective::toDomCSPStruct(mozilla::dom::CSP& outCSP) const {
outCSP.mScript_src_attr.Value() = std::move(srcs);
return;
+ case nsIContentSecurityPolicy::REQUIRE_TRUSTED_TYPES_FOR_DIRECTIVE:
+ outCSP.mRequire_trusted_types_for.Construct();
+
+ // Here, the srcs represent the sink group
+ // (https://w3c.github.io/trusted-types/dist/spec/#integration-with-content-security-policy).
+ outCSP.mRequire_trusted_types_for.Value() = std::move(srcs);
+ return;
+
+ case nsIContentSecurityPolicy::TRUSTED_TYPES_DIRECTIVE:
+ outCSP.mTrusted_types.Construct();
+ // Here, "srcs" represents tt-expressions
+ // (https://w3c.github.io/trusted-types/dist/spec/#trusted-types-csp-directive).
+ outCSP.mTrusted_types.Value() = std::move(srcs);
+ return;
+
default:
NS_ASSERTION(false, "cannot find directive to convert CSP to JSON");
}
@@ -1693,24 +1761,23 @@ bool nsCSPPolicy::allowsAllInlineBehavior(CSPDirective aDir) const {
/*
* Use this function only after ::allows() returned 'false'. Most and
* foremost it's used to get the violated directive before sending reports.
- * The parameter outDirective is the equivalent of 'outViolatedDirective'
+ * The parameter aDirectiveName is the equivalent of 'outViolatedDirective'
* for the ::permits() function family.
*/
-void nsCSPPolicy::getViolatedDirectiveInformation(CSPDirective aDirective,
- nsAString& outDirective,
- nsAString& outDirectiveString,
- bool* aReportSample) const {
+void nsCSPPolicy::getViolatedDirectiveInformation(
+ CSPDirective aDirective, nsAString& aDirectiveName,
+ nsAString& aDirectiveNameAndValue, bool* aReportSample) const {
*aReportSample = false;
nsCSPDirective* directive = matchingOrDefaultDirective(aDirective);
if (!directive) {
MOZ_ASSERT_UNREACHABLE("Can not query violated directive");
- outDirective.AppendLiteral("couldNotQueryViolatedDirective");
- outDirective.Truncate();
+ aDirectiveName.Truncate();
+ aDirectiveNameAndValue.Truncate();
return;
}
- directive->getDirName(outDirective);
- directive->toString(outDirectiveString);
+ directive->getDirName(aDirectiveName);
+ directive->toString(aDirectiveNameAndValue);
*aReportSample = directive->hasReportSampleKeyword();
}
diff --git a/dom/security/nsCSPUtils.h b/dom/security/nsCSPUtils.h
index eeccaf0c4a..b9ef52967e 100644
--- a/dom/security/nsCSPUtils.h
+++ b/dom/security/nsCSPUtils.h
@@ -93,24 +93,15 @@ static const char* CSPStrDirectives[] = {
"script-src-attr", // SCRIPT_SRC_ATTR_DIRECTIVE
"style-src-elem", // STYLE_SRC_ELEM_DIRECTIVE
"style-src-attr", // STYLE_SRC_ATTR_DIRECTIVE
+ "require-trusted-types-for", // REQUIRE_TRUSTED_TYPES_FOR_DIRECTIVE
+ "trusted-types", // TRUSTED_TYPES_DIRECTIVE
};
inline const char* CSP_CSPDirectiveToString(CSPDirective aDir) {
return CSPStrDirectives[static_cast<uint32_t>(aDir)];
}
-inline CSPDirective CSP_StringToCSPDirective(const nsAString& aDir) {
- nsString lowerDir = PromiseFlatString(aDir);
- ToLowerCase(lowerDir);
-
- uint32_t numDirs = (sizeof(CSPStrDirectives) / sizeof(CSPStrDirectives[0]));
- for (uint32_t i = 1; i < numDirs; i++) {
- if (lowerDir.EqualsASCII(CSPStrDirectives[i])) {
- return static_cast<CSPDirective>(i);
- }
- }
- return nsIContentSecurityPolicy::NO_DIRECTIVE;
-}
+CSPDirective CSP_StringToCSPDirective(const nsAString& aDir);
#define FOR_EACH_CSP_KEYWORD(MACRO) \
MACRO(CSP_SELF, "'self'") \
@@ -396,6 +387,34 @@ class nsCSPSandboxFlags : public nsCSPBaseSrc {
nsString mFlags;
};
+/* =============== nsCSPRequireTrustedTypesForDirectiveValue =============== */
+
+class nsCSPRequireTrustedTypesForDirectiveValue : public nsCSPBaseSrc {
+ public:
+ explicit nsCSPRequireTrustedTypesForDirectiveValue(const nsAString& aValue);
+ virtual ~nsCSPRequireTrustedTypesForDirectiveValue() = default;
+
+ bool visit(nsCSPSrcVisitor* aVisitor) const override;
+ void toString(nsAString& aOutStr) const override;
+
+ private:
+ const nsString mValue;
+};
+
+/* =============== nsCSPTrustedTypesDirectiveExpression =============== */
+
+class nsCSPTrustedTypesDirectivePolicyName : public nsCSPBaseSrc {
+ public:
+ explicit nsCSPTrustedTypesDirectivePolicyName(const nsAString& aName);
+ virtual ~nsCSPTrustedTypesDirectivePolicyName() = default;
+
+ bool visit(nsCSPSrcVisitor* aVisitor) const override;
+ void toString(nsAString& aOutStr) const override;
+
+ private:
+ const nsString mName;
+};
+
/* =============== nsCSPSrcVisitor ================== */
class nsCSPSrcVisitor {
@@ -431,6 +450,7 @@ class nsCSPDirective {
virtual void toString(nsAString& outStr) const;
void toDomCSPStruct(mozilla::dom::CSP& outCSP) const;
+ // Takes ownership of the passed sources.
virtual void addSrcs(const nsTArray<nsCSPBaseSrc*>& aSrcs) {
mSrcs = aSrcs.Clone();
}
@@ -652,8 +672,8 @@ class nsCSPPolicy {
void getReportURIs(nsTArray<nsString>& outReportURIs) const;
void getViolatedDirectiveInformation(CSPDirective aDirective,
- nsAString& outDirective,
- nsAString& outDirectiveString,
+ nsAString& aDirectiveName,
+ nsAString& aDirectiveNameAndValue,
bool* aReportSample) const;
uint32_t getSandboxFlags() const;
diff --git a/dom/security/nsHTTPSOnlyUtils.cpp b/dom/security/nsHTTPSOnlyUtils.cpp
index 31c7408a37..0bc99179dc 100644
--- a/dom/security/nsHTTPSOnlyUtils.cpp
+++ b/dom/security/nsHTTPSOnlyUtils.cpp
@@ -601,35 +601,28 @@ nsHTTPSOnlyUtils::PotentiallyDowngradeHttpsFirstRequest(
if (navigationStart) {
mozilla::TimeDuration duration =
mozilla::TimeStamp::Now() - navigationStart;
+
bool isPrivateWin =
loadInfo->GetOriginAttributes().mPrivateBrowsingId > 0;
+ bool isSchemeless =
+ loadInfo->GetWasSchemelessInput() &&
+ !nsHTTPSOnlyUtils::IsHttpsFirstModeEnabled(isPrivateWin);
- if (loadInfo->GetWasSchemelessInput() &&
- !IsHttpsFirstModeEnabled(isPrivateWin)) {
- mozilla::glean::httpsfirst::downgraded_schemeless.Add();
- if (timing) {
- mozilla::glean::httpsfirst::downgrade_time_schemeless
- .AccumulateRawDuration(duration);
- }
- } else {
- mozilla::glean::httpsfirst::downgraded.Add();
- if (timing) {
- mozilla::glean::httpsfirst::downgrade_time.AccumulateRawDuration(
- duration);
- }
- }
+ using namespace mozilla::glean::httpsfirst;
+ auto downgradedMetric = isSchemeless ? downgraded_schemeless : downgraded;
+ auto downgradedOnTimerMetric =
+ isSchemeless ? downgraded_on_timer_schemeless : downgraded_on_timer;
+ auto downgradeTimeMetric =
+ isSchemeless ? downgrade_time_schemeless : downgrade_time;
nsresult channelStatus;
channel->GetStatus(&channelStatus);
if (channelStatus == NS_ERROR_NET_TIMEOUT_EXTERNAL) {
- if (loadInfo->GetWasSchemelessInput() &&
- !nsHTTPSOnlyUtils::IsHttpsFirstModeEnabled(isPrivateWin)) {
- mozilla::glean::httpsfirst::downgraded_on_timer_schemeless
- .AddToNumerator();
- } else {
- mozilla::glean::httpsfirst::downgraded_on_timer.AddToNumerator();
- }
+ downgradedOnTimerMetric.AddToNumerator();
+ } else {
+ downgradeTimeMetric.AccumulateRawDuration(duration);
}
+ downgradedMetric.Add();
}
}
diff --git a/dom/security/test/gtest/TestCSPParser.cpp b/dom/security/test/gtest/TestCSPParser.cpp
index 19ba0548de..388055f388 100644
--- a/dom/security/test/gtest/TestCSPParser.cpp
+++ b/dom/security/test/gtest/TestCSPParser.cpp
@@ -152,9 +152,14 @@ nsresult runTestSuite(const PolicyTest* aPolicies, uint32_t aPolicyCount,
// Add prefs you need to set to parse CSP here, see comments for example
// bool examplePref = false;
+ bool trustedTypesEnabled = false;
+ constexpr auto kTrustedTypesEnabledPrefName =
+ "dom.security.trusted_types.enabled";
if (prefs) {
// prefs->GetBoolPref("security.csp.examplePref", &examplePref);
// prefs->SetBoolPref("security.csp.examplePref", true);
+ prefs->GetBoolPref(kTrustedTypesEnabledPrefName, &trustedTypesEnabled);
+ prefs->SetBoolPref(kTrustedTypesEnabledPrefName, true);
}
for (uint32_t i = 0; i < aPolicyCount; i++) {
@@ -165,6 +170,7 @@ nsresult runTestSuite(const PolicyTest* aPolicies, uint32_t aPolicyCount,
if (prefs) {
// prefs->SetBoolPref("security.csp.examplePref", examplePref);
+ prefs->SetBoolPref(kTrustedTypesEnabledPrefName, trustedTypesEnabled);
}
return NS_OK;
@@ -220,6 +226,11 @@ TEST(CSPParser, Directives)
"worker-src http://worker.com; frame-src http://frame.com; child-src http://child.com" },
{ "script-src 'unsafe-allow-redirects' http://example.com",
"script-src http://example.com"},
+ { "require-trusted-types-for 'script'",
+ "require-trusted-types-for 'script'" },
+ { "trusted-types somePolicyName", "trusted-types somePolicyName" },
+ { "trusted-types somePolicyName anotherPolicyName 1 - # = _ / @ . % *",
+ "trusted-types somePolicyName anotherPolicyName 1 - # = _ / @ . % *" },
// clang-format on
};
@@ -247,6 +258,11 @@ TEST(CSPParser, Keywords)
"script-src 'wasm-unsafe-eval'" },
{ "img-src 'none'; script-src 'unsafe-eval' 'unsafe-inline'; default-src 'self'",
"img-src 'none'; script-src 'unsafe-eval' 'unsafe-inline'; default-src 'self'" },
+ { "trusted-types somePolicyName 'allow-duplicates'",
+ "trusted-types somePolicyName 'allow-duplicates'" },
+ { "trusted-types 'none'", "trusted-types 'none'" },
+ { "trusted-types", "trusted-types 'none'" },
+ { "trusted-types *", "trusted-types *" },
// clang-format on
};
@@ -589,6 +605,7 @@ TEST(CSPParser, BadPolicies)
{ "report-uri http://:foo", ""},
{ "require-sri-for", ""},
{ "require-sri-for style", ""},
+ { "trusted-types $", ""},
// clang-format on
};
diff --git a/dom/security/test/https-first/browser_httpsfirst.js b/dom/security/test/https-first/browser_httpsfirst.js
index c4437f6051..e0bba26f73 100644
--- a/dom/security/test/https-first/browser_httpsfirst.js
+++ b/dom/security/test/https-first/browser_httpsfirst.js
@@ -99,11 +99,10 @@ add_task(async function () {
is(Glean.httpsfirst.downgradedOnTimerSchemeless.testGetValue(), null);
const downgradeSeconds =
Glean.httpsfirst.downgradeTime.testGetValue().sum / 1_000_000_000;
- ok(
- downgradeSeconds > 2 && downgradeSeconds < 30,
- `Summed downgrade time should be above 2 and below 30 seconds (is ${downgradeSeconds.toFixed(
- 2
- )}s)`
+ Assert.less(
+ downgradeSeconds,
+ 10,
+ "Summed downgrade time should be below 10 seconds"
);
is(null, Glean.httpsfirst.downgradeTimeSchemeless.testGetValue());
});
diff --git a/dom/serializers/nsDocumentEncoder.cpp b/dom/serializers/nsDocumentEncoder.cpp
index 46ea15fecc..c50a94ea8e 100644
--- a/dom/serializers/nsDocumentEncoder.cpp
+++ b/dom/serializers/nsDocumentEncoder.cpp
@@ -341,7 +341,7 @@ class nsDocumentEncoder : public nsIDocumentEncoder {
// argument of nsIContentSerializer::Init().
bool mNeedsPreformatScanning;
bool mIsCopying; // Set to true only while copying
- nsStringBuffer* mCachedBuffer;
+ RefPtr<nsStringBuffer> mCachedBuffer;
class NodeSerializer {
public:
@@ -701,11 +701,7 @@ nsresult nsDocumentEncoder::SerializeWholeDocument(uint32_t aMaxLength) {
return rv;
}
-nsDocumentEncoder::~nsDocumentEncoder() {
- if (mCachedBuffer) {
- mCachedBuffer->Release();
- }
-}
+nsDocumentEncoder::~nsDocumentEncoder() = default;
NS_IMETHODIMP
nsDocumentEncoder::Init(Document* aDocument, const nsAString& aMimeType,
@@ -1372,7 +1368,7 @@ nsDocumentEncoder::EncodeToStringWithMaxLength(uint32_t aMaxLength,
nsString output;
static const size_t kStringBufferSizeInBytes = 2048;
if (!mCachedBuffer) {
- mCachedBuffer = nsStringBuffer::Alloc(kStringBufferSizeInBytes).take();
+ mCachedBuffer = nsStringBuffer::Alloc(kStringBufferSizeInBytes);
if (NS_WARN_IF(!mCachedBuffer)) {
return NS_ERROR_OUT_OF_MEMORY;
}
@@ -1381,9 +1377,7 @@ nsDocumentEncoder::EncodeToStringWithMaxLength(uint32_t aMaxLength,
!mCachedBuffer->IsReadonly(),
"nsIDocumentEncoder shouldn't keep reference to non-readonly buffer!");
static_cast<char16_t*>(mCachedBuffer->Data())[0] = char16_t(0);
- mCachedBuffer->ToString(0, output, true);
- // output owns the buffer now!
- mCachedBuffer = nullptr;
+ output.Assign(mCachedBuffer.forget(), 0);
if (!mSerializer) {
nsAutoCString progId(NS_CONTENTSERIALIZER_CONTRACTID_PREFIX);
@@ -1407,21 +1401,18 @@ nsDocumentEncoder::EncodeToStringWithMaxLength(uint32_t aMaxLength,
rv = mSerializer->FlushAndFinish();
- mCachedBuffer = nsStringBuffer::FromString(output);
// We have to be careful how we set aOutputString, because we don't
// want it to end up sharing mCachedBuffer if we plan to reuse it.
bool setOutput = false;
+ MOZ_ASSERT(!mCachedBuffer);
// Try to cache the buffer.
- if (mCachedBuffer) {
- if ((mCachedBuffer->StorageSize() == kStringBufferSizeInBytes) &&
- !mCachedBuffer->IsReadonly()) {
- mCachedBuffer->AddRef();
- } else {
- if (NS_SUCCEEDED(rv)) {
- mCachedBuffer->ToString(output.Length(), aOutputString);
- setOutput = true;
- }
- mCachedBuffer = nullptr;
+ if (nsStringBuffer* outputBuffer = output.GetStringBuffer()) {
+ if (outputBuffer->StorageSize() == kStringBufferSizeInBytes &&
+ !outputBuffer->IsReadonly()) {
+ mCachedBuffer = outputBuffer;
+ } else if (NS_SUCCEEDED(rv)) {
+ aOutputString.Assign(outputBuffer, output.Length());
+ setOutput = true;
}
}
diff --git a/dom/serviceworkers/ServiceWorkerEvents.cpp b/dom/serviceworkers/ServiceWorkerEvents.cpp
index 6027625021..9fc7bb1a91 100644
--- a/dom/serviceworkers/ServiceWorkerEvents.cpp
+++ b/dom/serviceworkers/ServiceWorkerEvents.cpp
@@ -26,7 +26,6 @@
#include "mozilla/dom/PromiseNativeHandler.h"
#include "mozilla/dom/PushEventBinding.h"
#include "mozilla/dom/PushMessageDataBinding.h"
-#include "mozilla/dom/PushUtil.h"
#include "mozilla/dom/Request.h"
#include "mozilla/dom/Response.h"
#include "mozilla/dom/ServiceWorkerOp.h"
diff --git a/dom/serviceworkers/ServiceWorkerOp.cpp b/dom/serviceworkers/ServiceWorkerOp.cpp
index 9c4fc569d1..2b11742c6c 100644
--- a/dom/serviceworkers/ServiceWorkerOp.cpp
+++ b/dom/serviceworkers/ServiceWorkerOp.cpp
@@ -278,8 +278,7 @@ class ServiceWorkerOp::ServiceWorkerOpRunnable final
ServiceWorkerOpRunnable(RefPtr<ServiceWorkerOp> aOwner,
WorkerPrivate* aWorkerPrivate)
- : WorkerDebuggeeRunnable(aWorkerPrivate, "ServiceWorkerOpRunnable",
- WorkerThread),
+ : WorkerDebuggeeRunnable("ServiceWorkerOpRunnable"),
mOwner(std::move(aOwner)) {
AssertIsOnMainThread();
MOZ_ASSERT(mOwner);
@@ -320,7 +319,7 @@ class ServiceWorkerOp::ServiceWorkerOpRunnable final
};
NS_IMPL_ISUPPORTS_INHERITED0(ServiceWorkerOp::ServiceWorkerOpRunnable,
- WorkerRunnable)
+ WorkerThreadRunnable)
bool ServiceWorkerOp::MaybeStart(RemoteWorkerChild* aOwner,
RemoteWorkerChild::State& aState) {
@@ -403,10 +402,11 @@ void ServiceWorkerOp::StartOnMainThread(RefPtr<RemoteWorkerChild>& aOwner) {
auto lock = aOwner->mState.Lock();
MOZ_ASSERT(lock->is<Running>());
- RefPtr<WorkerRunnable> workerRunnable =
+ RefPtr<WorkerThreadRunnable> workerRunnable =
GetRunnable(lock->as<Running>().mWorkerPrivate);
- if (NS_WARN_IF(!workerRunnable->Dispatch())) {
+ if (NS_WARN_IF(
+ !workerRunnable->Dispatch(lock->as<Running>().mWorkerPrivate))) {
RejectAll(NS_ERROR_FAILURE);
}
}
@@ -452,7 +452,7 @@ bool ServiceWorkerOp::IsTerminationOp() const {
ServiceWorkerOpArgs::TServiceWorkerTerminateWorkerOpArgs;
}
-RefPtr<WorkerRunnable> ServiceWorkerOp::GetRunnable(
+RefPtr<WorkerThreadRunnable> ServiceWorkerOp::GetRunnable(
WorkerPrivate* aWorkerPrivate) {
AssertIsOnMainThread();
MOZ_ASSERT(aWorkerPrivate);
@@ -522,7 +522,7 @@ class UpdateServiceWorkerStateOp final : public ServiceWorkerOp {
UpdateStateOpRunnable(RefPtr<UpdateServiceWorkerStateOp> aOwner,
WorkerPrivate* aWorkerPrivate)
- : MainThreadWorkerControlRunnable(aWorkerPrivate),
+ : MainThreadWorkerControlRunnable("UpdateStateOpRunnable"),
mOwner(std::move(aOwner)) {
AssertIsOnMainThread();
MOZ_ASSERT(mOwner);
@@ -559,7 +559,8 @@ class UpdateServiceWorkerStateOp final : public ServiceWorkerOp {
~UpdateServiceWorkerStateOp() = default;
- RefPtr<WorkerRunnable> GetRunnable(WorkerPrivate* aWorkerPrivate) override {
+ RefPtr<WorkerThreadRunnable> GetRunnable(
+ WorkerPrivate* aWorkerPrivate) override {
AssertIsOnMainThread();
MOZ_ASSERT(aWorkerPrivate);
MOZ_ASSERT(mArgs.type() ==
diff --git a/dom/serviceworkers/ServiceWorkerOp.h b/dom/serviceworkers/ServiceWorkerOp.h
index d485f6f210..3d872a00e5 100644
--- a/dom/serviceworkers/ServiceWorkerOp.h
+++ b/dom/serviceworkers/ServiceWorkerOp.h
@@ -62,7 +62,8 @@ class ServiceWorkerOp : public RemoteWorkerChild::Op {
bool IsTerminationOp() const;
// Override to provide a runnable that's not a `ServiceWorkerOpRunnable.`
- virtual RefPtr<WorkerRunnable> GetRunnable(WorkerPrivate* aWorkerPrivate);
+ virtual RefPtr<WorkerThreadRunnable> GetRunnable(
+ WorkerPrivate* aWorkerPrivate);
// Overridden by ServiceWorkerOp subclasses, it should return true when
// the ServiceWorkerOp was executed successfully (and false if it did fail).
diff --git a/dom/serviceworkers/test/gtest/TestReadWrite.cpp b/dom/serviceworkers/test/gtest/TestReadWrite.cpp
index 66b966f9e2..8c8dcc3810 100644
--- a/dom/serviceworkers/test/gtest/TestReadWrite.cpp
+++ b/dom/serviceworkers/test/gtest/TestReadWrite.cpp
@@ -25,15 +25,10 @@ using namespace mozilla::ipc;
class ServiceWorkerRegistrarTest : public ServiceWorkerRegistrar {
public:
ServiceWorkerRegistrarTest() {
-#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
getter_AddRefs(mProfileDir));
- MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
-#else
- NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
- getter_AddRefs(mProfileDir));
-#endif
- MOZ_DIAGNOSTIC_ASSERT(mProfileDir);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+ MOZ_RELEASE_ASSERT(mProfileDir);
}
nsresult TestReadData() { return ReadData(); }
diff --git a/dom/serviceworkers/test/test_serviceworker_interfaces.js b/dom/serviceworkers/test/test_serviceworker_interfaces.js
index c13c228d71..74dc6db488 100644
--- a/dom/serviceworkers/test/test_serviceworker_interfaces.js
+++ b/dom/serviceworkers/test/test_serviceworker_interfaces.js
@@ -40,6 +40,7 @@ let wasmGlobalInterfaces = [
{ name: "Function", insecureContext: true, nightly: true },
{ name: "Exception", insecureContext: true },
{ name: "Tag", insecureContext: true },
+ { name: "JSTag", insecureContext: true, earlyBetaOrEarlier: true },
{ name: "compile", insecureContext: true },
{ name: "compileStreaming", insecureContext: true },
{ name: "instantiate", insecureContext: true },
diff --git a/dom/svg/SVGAElement.cpp b/dom/svg/SVGAElement.cpp
index 1533cceb34..bffc3d1a98 100644
--- a/dom/svg/SVGAElement.cpp
+++ b/dom/svg/SVGAElement.cpp
@@ -6,11 +6,10 @@
#include "mozilla/dom/SVGAElement.h"
-#include "mozilla/Attributes.h"
#include "mozilla/EventDispatcher.h"
-#include "mozilla/dom/BindContext.h"
#include "mozilla/dom/DocumentInlines.h"
#include "mozilla/dom/SVGAElementBinding.h"
+#include "mozilla/FocusModel.h"
#include "nsCOMPtr.h"
#include "nsContentUtils.h"
#include "nsGkAtoms.h"
@@ -158,7 +157,7 @@ void SVGAElement::UnbindFromTree(UnbindContext& aContext) {
int32_t SVGAElement::TabIndexDefault() { return 0; }
-Focusable SVGAElement::IsFocusableWithoutStyle(bool aWithMouse) {
+Focusable SVGAElement::IsFocusableWithoutStyle(IsFocusableFlags) {
Focusable result;
if (IsSVGFocusable(&result.mFocusable, &result.mTabIndex)) {
return result;
@@ -182,7 +181,7 @@ Focusable SVGAElement::IsFocusableWithoutStyle(bool aWithMouse) {
return {};
}
}
- if ((sTabFocusModel & eTabFocus_linksMask) == 0) {
+ if (!FocusModel::IsTabFocusable(TabFocusableType::Links)) {
result.mTabIndex = -1;
}
return result;
diff --git a/dom/svg/SVGAElement.h b/dom/svg/SVGAElement.h
index bbb552fce6..5e476c033c 100644
--- a/dom/svg/SVGAElement.h
+++ b/dom/svg/SVGAElement.h
@@ -50,7 +50,7 @@ class SVGAElement final : public SVGAElementBase, public Link {
void UnbindFromTree(UnbindContext&) override;
int32_t TabIndexDefault() override;
- Focusable IsFocusableWithoutStyle(bool aWithMouse) override;
+ Focusable IsFocusableWithoutStyle(IsFocusableFlags) override;
void GetLinkTarget(nsAString& aTarget) override;
already_AddRefed<nsIURI> GetHrefURI() const override;
diff --git a/dom/svg/SVGAnimatedLength.cpp b/dom/svg/SVGAnimatedLength.cpp
index 081db5704a..156fde0e23 100644
--- a/dom/svg/SVGAnimatedLength.cpp
+++ b/dom/svg/SVGAnimatedLength.cpp
@@ -225,6 +225,10 @@ CSSSize SVGElementMetrics::GetCSSViewportSize() const {
return GetCSSViewportSizeFromContext(context);
}
+float SVGElementMetrics::GetLineHeight(Type aType) const {
+ return SVGContentUtils::GetLineHeight(GetElementForType(aType));
+}
+
bool SVGElementMetrics::EnsureCtx() const {
if (!mCtx && mSVGElement) {
mCtx = mSVGElement->GetCtx();
@@ -298,6 +302,23 @@ CSSSize NonSVGFrameUserSpaceMetrics::GetCSSViewportSize() const {
return GetCSSViewportSizeFromContext(mFrame->PresContext());
}
+float NonSVGFrameUserSpaceMetrics::GetLineHeight(Type aType) const {
+ auto* context = mFrame->PresContext();
+ switch (aType) {
+ case Type::This: {
+ const auto lineHeightAu = ReflowInput::CalcLineHeight(
+ *mFrame->Style(), context, mFrame->GetContent(), NS_UNCONSTRAINEDSIZE,
+ 1.0f);
+ return CSSPixel::FromAppUnits(lineHeightAu);
+ }
+ case Type::Root:
+ return SVGContentUtils::GetLineHeight(
+ context->Document()->GetRootElement());
+ }
+ MOZ_ASSERT_UNREACHABLE("Was a new value added to the enumeration?");
+ return 1.0f;
+}
+
float UserSpaceMetricsWithSize::GetAxisLength(uint8_t aCtxType) const {
gfx::Size size = GetSize();
float length;
diff --git a/dom/svg/SVGAnimatedLength.h b/dom/svg/SVGAnimatedLength.h
index 6874261cda..0e5c228667 100644
--- a/dom/svg/SVGAnimatedLength.h
+++ b/dom/svg/SVGAnimatedLength.h
@@ -53,6 +53,7 @@ class UserSpaceMetrics {
float GetCapHeight(Type aType) const;
virtual float GetAxisLength(uint8_t aCtxType) const = 0;
virtual CSSSize GetCSSViewportSize() const = 0;
+ virtual float GetLineHeight(Type aType) const = 0;
protected:
virtual GeckoFontMetrics GetFontMetricsForType(Type aType) const = 0;
@@ -75,6 +76,7 @@ class SVGElementMetrics : public UserSpaceMetrics {
}
float GetAxisLength(uint8_t aCtxType) const override;
CSSSize GetCSSViewportSize() const override;
+ float GetLineHeight(Type aType) const override;
private:
bool EnsureCtx() const;
@@ -93,6 +95,7 @@ class NonSVGFrameUserSpaceMetrics : public UserSpaceMetricsWithSize {
float GetEmLength(Type aType) const override;
gfx::Size GetSize() const override;
CSSSize GetCSSViewportSize() const override;
+ float GetLineHeight(Type aType) const override;
private:
GeckoFontMetrics GetFontMetricsForType(Type aType) const override;
diff --git a/dom/svg/SVGContentUtils.cpp b/dom/svg/SVGContentUtils.cpp
index 72534c12e8..6a87c64acc 100644
--- a/dom/svg/SVGContentUtils.cpp
+++ b/dom/svg/SVGContentUtils.cpp
@@ -433,6 +433,26 @@ float SVGContentUtils::GetFontXHeight(const ComputedStyle* aComputedStyle,
return nsPresContext::AppUnitsToFloatCSSPixels(xHeight) /
aPresContext->TextZoom();
}
+
+float SVGContentUtils::GetLineHeight(const Element* aElement) {
+ float result = 16.0f * ReflowInput::kNormalLineHeightFactor;
+ if (!aElement) {
+ return result;
+ }
+ SVGGeometryProperty::DoForComputedStyle(
+ aElement, [&](const ComputedStyle* style) {
+ auto* context = nsContentUtils::GetContextForContent(aElement);
+ if (!context) {
+ return;
+ }
+ const auto lineHeightAu = ReflowInput::CalcLineHeight(
+ *style, context, aElement, NS_UNCONSTRAINEDSIZE, 1.0f);
+ result = CSSPixel::FromAppUnits(lineHeightAu);
+ });
+
+ return result;
+}
+
nsresult SVGContentUtils::ReportToConsole(const Document* doc,
const char* aWarning,
const nsTArray<nsString>& aParams) {
@@ -596,8 +616,12 @@ static gfx::Matrix GetCTMInternal(SVGElement* aElement, bool aScreenCTM,
: tm;
}
-gfx::Matrix SVGContentUtils::GetCTM(SVGElement* aElement, bool aScreenCTM) {
- return GetCTMInternal(aElement, aScreenCTM, false);
+gfx::Matrix SVGContentUtils::GetCTM(SVGElement* aElement) {
+ return GetCTMInternal(aElement, false, false);
+}
+
+gfx::Matrix SVGContentUtils::GetScreenCTM(SVGElement* aElement) {
+ return GetCTMInternal(aElement, true, false);
}
void SVGContentUtils::RectilinearGetStrokeBounds(
diff --git a/dom/svg/SVGContentUtils.h b/dom/svg/SVGContentUtils.h
index 8cd017b709..31e75cbb21 100644
--- a/dom/svg/SVGContentUtils.h
+++ b/dom/svg/SVGContentUtils.h
@@ -180,13 +180,24 @@ class SVGContentUtils {
static float GetFontXHeight(const ComputedStyle*, nsPresContext*);
/*
+ * Get the number of CSS px (user units) per lh (i.e. the line-height in
+ * user units) for an nsIContent.
+ *
+ * Requires the element be styled - if not, a default value assuming
+ * the font-size of 16px and line-height of 1.2 is returned.
+ */
+ static float GetLineHeight(const mozilla::dom::Element* aElement);
+
+ /*
* Report a localized error message to the error console.
*/
static nsresult ReportToConsole(const dom::Document* doc,
const char* aWarning,
const nsTArray<nsString>& aParams);
- static Matrix GetCTM(dom::SVGElement* aElement, bool aScreenCTM);
+ static Matrix GetCTM(dom::SVGElement* aElement);
+
+ static Matrix GetScreenCTM(dom::SVGElement* aElement);
/**
* Gets the tight bounds-space stroke bounds of the non-scaling-stroked rect
diff --git a/dom/svg/SVGDefsElement.h b/dom/svg/SVGDefsElement.h
index 38f6d4fab9..55c2c8fb95 100644
--- a/dom/svg/SVGDefsElement.h
+++ b/dom/svg/SVGDefsElement.h
@@ -25,7 +25,7 @@ class SVGDefsElement final : public SVGGraphicsElement {
public:
// defs elements are not focusable.
- Focusable IsFocusableWithoutStyle(bool aWithMouse) override { return {}; }
+ Focusable IsFocusableWithoutStyle(IsFocusableFlags) override { return {}; }
nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override;
};
diff --git a/dom/svg/SVGGeometryElement.cpp b/dom/svg/SVGGeometryElement.cpp
index de5756ed7d..149dae8ba4 100644
--- a/dom/svg/SVGGeometryElement.cpp
+++ b/dom/svg/SVGGeometryElement.cpp
@@ -201,7 +201,7 @@ bool SVGGeometryElement::IsPointInStroke(const DOMPointInit& aPoint) {
SVGGeometryProperty::DoForComputedStyle(this, [&](const ComputedStyle* s) {
// Per spec, we should take vector-effect into account.
if (s->StyleSVGReset()->HasNonScalingStroke()) {
- auto mat = SVGContentUtils::GetCTM(this, true);
+ auto mat = SVGContentUtils::GetCTM(this);
if (mat.HasNonTranslation()) {
// We have non-scaling-stroke as well as a non-translation transform.
// We should transform the path first then apply the stroke on the
diff --git a/dom/svg/SVGGraphicsElement.cpp b/dom/svg/SVGGraphicsElement.cpp
index 29b6b76728..52bb023e61 100644
--- a/dom/svg/SVGGraphicsElement.cpp
+++ b/dom/svg/SVGGraphicsElement.cpp
@@ -129,7 +129,7 @@ already_AddRefed<SVGMatrix> SVGGraphicsElement::GetCTM() {
// Flush all pending notifications so that our frames are up to date
currentDoc->FlushPendingNotifications(FlushType::Layout);
}
- gfx::Matrix m = SVGContentUtils::GetCTM(this, false);
+ gfx::Matrix m = SVGContentUtils::GetCTM(this);
RefPtr<SVGMatrix> mat =
m.IsSingular() ? nullptr : new SVGMatrix(ThebesMatrix(m));
return mat.forget();
@@ -140,7 +140,7 @@ already_AddRefed<SVGMatrix> SVGGraphicsElement::GetScreenCTM() {
// Flush all pending notifications so that our frames are up to date
currentDoc->FlushPendingNotifications(FlushType::Layout);
}
- gfx::Matrix m = SVGContentUtils::GetCTM(this, true);
+ gfx::Matrix m = SVGContentUtils::GetScreenCTM(this);
RefPtr<SVGMatrix> mat =
m.IsSingular() ? nullptr : new SVGMatrix(ThebesMatrix(m));
return mat.forget();
@@ -164,7 +164,7 @@ bool SVGGraphicsElement::IsSVGFocusable(bool* aIsFocusable,
return false;
}
-Focusable SVGGraphicsElement::IsFocusableWithoutStyle(bool aWithMouse) {
+Focusable SVGGraphicsElement::IsFocusableWithoutStyle(IsFocusableFlags) {
Focusable result;
IsSVGFocusable(&result.mFocusable, &result.mTabIndex);
return result;
diff --git a/dom/svg/SVGGraphicsElement.h b/dom/svg/SVGGraphicsElement.h
index 610436d394..1c5be3e324 100644
--- a/dom/svg/SVGGraphicsElement.h
+++ b/dom/svg/SVGGraphicsElement.h
@@ -34,7 +34,7 @@ class SVGGraphicsElement : public SVGGraphicsElementBase, public SVGTests {
already_AddRefed<SVGMatrix> GetCTM();
already_AddRefed<SVGMatrix> GetScreenCTM();
- Focusable IsFocusableWithoutStyle(bool aWithMouse) override;
+ Focusable IsFocusableWithoutStyle(IsFocusableFlags) override;
bool IsSVGGraphicsElement() const final { return true; }
using nsINode::Clone;
diff --git a/dom/svg/SVGLength.cpp b/dom/svg/SVGLength.cpp
index f7c383a16f..bbe01e6878 100644
--- a/dom/svg/SVGLength.cpp
+++ b/dom/svg/SVGLength.cpp
@@ -20,15 +20,19 @@ using namespace mozilla::dom::SVGLength_Binding;
namespace mozilla {
+// These types are numbered so that different length categories are in
+// contiguous ranges - See `SVGLength::Is[..]Unit()`.
const unsigned short SVG_LENGTHTYPE_Q = 11;
const unsigned short SVG_LENGTHTYPE_CH = 12;
const unsigned short SVG_LENGTHTYPE_REM = 13;
const unsigned short SVG_LENGTHTYPE_IC = 14;
const unsigned short SVG_LENGTHTYPE_CAP = 15;
-const unsigned short SVG_LENGTHTYPE_VW = 16;
-const unsigned short SVG_LENGTHTYPE_VH = 17;
-const unsigned short SVG_LENGTHTYPE_VMIN = 18;
-const unsigned short SVG_LENGTHTYPE_VMAX = 19;
+const unsigned short SVG_LENGTHTYPE_LH = 16;
+const unsigned short SVG_LENGTHTYPE_RLH = 17;
+const unsigned short SVG_LENGTHTYPE_VW = 18;
+const unsigned short SVG_LENGTHTYPE_VH = 19;
+const unsigned short SVG_LENGTHTYPE_VMIN = 20;
+const unsigned short SVG_LENGTHTYPE_VMAX = 21;
void SVGLength::GetValueAsString(nsAString& aValue) const {
nsTextFormatter::ssprintf(aValue, u"%g", (double)mValue);
@@ -74,7 +78,7 @@ bool SVGLength::IsAbsoluteUnit(uint8_t aUnit) {
/*static*/
bool SVGLength::IsFontRelativeUnit(uint8_t aUnit) {
return aUnit == SVG_LENGTHTYPE_EMS || aUnit == SVG_LENGTHTYPE_EXS ||
- (aUnit >= SVG_LENGTHTYPE_CH && aUnit <= SVG_LENGTHTYPE_CAP);
+ (aUnit >= SVG_LENGTHTYPE_CH && aUnit <= SVG_LENGTHTYPE_RLH);
}
/**
@@ -192,6 +196,10 @@ float SVGLength::GetPixelsPerUnit(const UserSpaceMetrics& aMetrics,
auto sz = aMetrics.GetCSSViewportSize();
return std::max(sz.width, sz.height) / 100.f;
}
+ case SVG_LENGTHTYPE_LH:
+ return aMetrics.GetLineHeight(UserSpaceMetrics::Type::This);
+ case SVG_LENGTHTYPE_RLH:
+ return aMetrics.GetLineHeight(UserSpaceMetrics::Type::Root);
default:
MOZ_ASSERT(IsAbsoluteUnit(aUnitType));
return GetAbsUnitsPerAbsUnit(SVG_LENGTHTYPE_PX, aUnitType);
@@ -256,6 +264,12 @@ nsCSSUnit SVGLength::SpecifiedUnitTypeToCSSUnit(uint8_t aSpecifiedUnit) {
case SVG_LENGTHTYPE_VMAX:
return nsCSSUnit::eCSSUnit_VMax;
+ case SVG_LENGTHTYPE_LH:
+ return nsCSSUnit::eCSSUnit_LineHeight;
+
+ case SVG_LENGTHTYPE_RLH:
+ return nsCSSUnit::eCSSUnit_RootLineHeight;
+
default:
MOZ_ASSERT_UNREACHABLE("Unknown unit type");
return nsCSSUnit::eCSSUnit_Pixel;
@@ -322,6 +336,12 @@ void SVGLength::GetUnitString(nsAString& aUnit, uint16_t aUnitType) {
case SVG_LENGTHTYPE_VMAX:
aUnit.AssignLiteral("vmax");
return;
+ case SVG_LENGTHTYPE_LH:
+ aUnit.AssignLiteral("lh");
+ return;
+ case SVG_LENGTHTYPE_RLH:
+ aUnit.AssignLiteral("rlh");
+ return;
}
MOZ_ASSERT_UNREACHABLE(
"Unknown unit type! Someone's using an SVGLength "
@@ -387,6 +407,12 @@ uint16_t SVGLength::GetUnitTypeForString(const nsAString& aUnit) {
if (aUnit.LowerCaseEqualsLiteral("vmax")) {
return SVG_LENGTHTYPE_VMAX;
}
+ if (aUnit.LowerCaseEqualsLiteral("lh")) {
+ return SVG_LENGTHTYPE_LH;
+ }
+ if (aUnit.LowerCaseEqualsLiteral("rlh")) {
+ return SVG_LENGTHTYPE_RLH;
+ }
return SVG_LENGTHTYPE_UNKNOWN;
}
diff --git a/dom/svg/SVGSwitchElement.cpp b/dom/svg/SVGSwitchElement.cpp
index e049d07496..a19dfd8631 100644
--- a/dom/svg/SVGSwitchElement.cpp
+++ b/dom/svg/SVGSwitchElement.cpp
@@ -7,7 +7,6 @@
#include "mozilla/dom/SVGSwitchElement.h"
#include "nsLayoutUtils.h"
-#include "mozilla/Preferences.h"
#include "mozilla/SVGUtils.h"
#include "mozilla/dom/SVGSwitchElementBinding.h"
@@ -46,14 +45,13 @@ void SVGSwitchElement::MaybeInvalidate() {
// InvalidateAndScheduleBoundsUpdate has been called, otherwise
// it will not correctly invalidate the old mActiveChild area.
- nsIContent* newActiveChild = FindActiveChild();
+ auto* newActiveChild = SVGTests::FindActiveSwitchChild(this);
if (newActiveChild == mActiveChild) {
return;
}
- nsIFrame* frame = GetPrimaryFrame();
- if (frame) {
+ if (auto* frame = GetPrimaryFrame()) {
nsLayoutUtils::PostRestyleEvent(this, RestyleHint{0},
nsChangeHint_InvalidateRenderingObservers);
SVGUtils::ScheduleReflowSVG(frame);
@@ -86,54 +84,4 @@ void SVGSwitchElement::RemoveChildNode(nsIContent* aKid, bool aNotify) {
MaybeInvalidate();
}
-//----------------------------------------------------------------------
-// Implementation Helpers:
-
-nsIContent* SVGSwitchElement::FindActiveChild() const {
- nsAutoString acceptLangs;
- Preferences::GetLocalizedString("intl.accept_languages", acceptLangs);
-
- int32_t bestLanguagePreferenceRank = -1;
- nsIContent* bestChild = nullptr;
- nsIContent* defaultChild = nullptr;
- for (nsIContent* child = nsINode::GetFirstChild(); child;
- child = child->GetNextSibling()) {
- if (!child->IsElement()) {
- continue;
- }
- nsCOMPtr<SVGTests> tests(do_QueryInterface(child));
- if (tests) {
- if (tests->PassesConditionalProcessingTestsIgnoringSystemLanguage()) {
- int32_t languagePreferenceRank =
- tests->GetBestLanguagePreferenceRank(acceptLangs);
- switch (languagePreferenceRank) {
- case 0:
- // best possible match
- return child;
- case -1:
- // no match
- break;
- case -2:
- // no systemLanguage attribute. If there's nothing better
- // we'll use the first such child.
- if (!defaultChild) {
- defaultChild = child;
- }
- break;
- default:
- if (bestLanguagePreferenceRank == -1 ||
- languagePreferenceRank < bestLanguagePreferenceRank) {
- bestLanguagePreferenceRank = languagePreferenceRank;
- bestChild = child;
- }
- break;
- }
- }
- } else if (!bestChild) {
- bestChild = child;
- }
- }
- return bestChild ? bestChild : defaultChild;
-}
-
} // namespace mozilla::dom
diff --git a/dom/svg/SVGSwitchElement.h b/dom/svg/SVGSwitchElement.h
index c244c0fe51..68c22e8f71 100644
--- a/dom/svg/SVGSwitchElement.h
+++ b/dom/svg/SVGSwitchElement.h
@@ -50,9 +50,6 @@ class SVGSwitchElement final : public SVGSwitchElementBase {
nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override;
private:
- void UpdateActiveChild() { mActiveChild = FindActiveChild(); }
- nsIContent* FindActiveChild() const;
-
// only this child will be displayed
nsCOMPtr<nsIContent> mActiveChild;
};
diff --git a/dom/svg/SVGSymbolElement.cpp b/dom/svg/SVGSymbolElement.cpp
index eec7851de3..26d8a04ff0 100644
--- a/dom/svg/SVGSymbolElement.cpp
+++ b/dom/svg/SVGSymbolElement.cpp
@@ -29,11 +29,11 @@ SVGSymbolElement::SVGSymbolElement(
already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
: SVGSymbolElementBase(std::move(aNodeInfo)) {}
-Focusable SVGSymbolElement::IsFocusableWithoutStyle(bool aWithMouse) {
+Focusable SVGSymbolElement::IsFocusableWithoutStyle(IsFocusableFlags aFlags) {
if (!CouldBeRendered()) {
return {};
}
- return SVGSymbolElementBase::IsFocusableWithoutStyle(aWithMouse);
+ return SVGSymbolElementBase::IsFocusableWithoutStyle(aFlags);
}
bool SVGSymbolElement::CouldBeRendered() const {
diff --git a/dom/svg/SVGSymbolElement.h b/dom/svg/SVGSymbolElement.h
index 4f49775de2..9f5b59db41 100644
--- a/dom/svg/SVGSymbolElement.h
+++ b/dom/svg/SVGSymbolElement.h
@@ -26,7 +26,7 @@ class SVGSymbolElement final : public SVGSymbolElementBase {
~SVGSymbolElement() = default;
JSObject* WrapNode(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override;
- Focusable IsFocusableWithoutStyle(bool aWithMouse) override;
+ Focusable IsFocusableWithoutStyle(IsFocusableFlags) override;
public:
// interfaces:
diff --git a/dom/svg/SVGTests.cpp b/dom/svg/SVGTests.cpp
index 6603663445..058065618e 100644
--- a/dom/svg/SVGTests.cpp
+++ b/dom/svg/SVGTests.cpp
@@ -5,12 +5,13 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/SVGTests.h"
+
#include "DOMSVGStringList.h"
+#include "nsCharSeparatedTokenizer.h"
#include "nsIContent.h"
#include "nsIContentInlines.h"
#include "mozilla/dom/SVGSwitchElement.h"
-#include "nsCharSeparatedTokenizer.h"
-#include "nsStyleUtil.h"
+#include "mozilla/intl/oxilangtag_ffi_generated.h"
#include "mozilla/Preferences.h"
namespace mozilla::dom {
@@ -58,39 +59,84 @@ bool SVGTests::IsConditionalProcessingAttribute(
return false;
}
-int32_t SVGTests::GetBestLanguagePreferenceRank(
- const nsAString& aAcceptLangs) const {
- if (!mStringListAttributes[LANGUAGE].IsExplicitlySet()) {
- return -2;
+// Find the best match from aAvailLangs for the users accept-languages,
+// returning the index in the aAvailLangs list, or -1 if no match.
+int32_t FindBestLanguage(const nsTArray<nsCString>& aAvailLangs) {
+ AutoTArray<nsCString, 16> reqLangs;
+ nsCString acceptLangs;
+ Preferences::GetLocalizedCString("intl.accept_languages", acceptLangs);
+ nsCCharSeparatedTokenizer languageTokenizer(acceptLangs, ',');
+ while (languageTokenizer.hasMoreTokens()) {
+ reqLangs.AppendElement(languageTokenizer.nextToken());
}
- int32_t lowestRank = -1;
-
- for (uint32_t i = 0; i < mStringListAttributes[LANGUAGE].Length(); i++) {
- int32_t index = 0;
- for (const nsAString& languageToken :
- nsCharSeparatedTokenizer(aAcceptLangs, ',').ToRange()) {
- bool exactMatch = languageToken.Equals(mStringListAttributes[LANGUAGE][i],
- nsCaseInsensitiveStringComparator);
- bool prefixOnlyMatch =
- !exactMatch && nsStyleUtil::DashMatchCompare(
- mStringListAttributes[LANGUAGE][i], languageToken,
- nsCaseInsensitiveStringComparator);
- if (index == 0 && exactMatch) {
- // best possible match
- return 0;
+ for (const auto& req : reqLangs) {
+ for (const auto& avail : aAvailLangs) {
+ if (avail.Length() > req.Length()) {
+ // Ensure that en does not match en-us, i.e. you need to have en in
+ // intl.accept_languages to match en in markup.
+ continue;
}
- if ((exactMatch || prefixOnlyMatch) &&
- (lowestRank == -1 || 2 * index + prefixOnlyMatch < lowestRank)) {
- lowestRank = 2 * index + prefixOnlyMatch;
+ using namespace intl::ffi;
+ struct LangTagDelete {
+ void operator()(LangTag* aLangTag) const { lang_tag_destroy(aLangTag); }
+ };
+ UniquePtr<LangTag, LangTagDelete> langTag(lang_tag_new(&avail));
+ if (langTag && lang_tag_matches(langTag.get(), &req)) {
+ return &avail - &aAvailLangs[0];
}
- ++index;
}
}
- return lowestRank;
+ return -1;
}
-bool SVGTests::PassesConditionalProcessingTestsIgnoringSystemLanguage() const {
+nsIContent* SVGTests::FindActiveSwitchChild(
+ const dom::SVGSwitchElement* aSwitch) {
+ AutoTArray<nsCString, 16> availLocales;
+ AutoTArray<nsIContent*, 16> children;
+ nsIContent* defaultChild = nullptr;
+ for (auto* child = aSwitch->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ if (!child->IsElement()) {
+ continue;
+ }
+ nsCOMPtr<SVGTests> tests(do_QueryInterface(child));
+ if (tests) {
+ if (!tests->mPassesConditionalProcessingTests.valueOr(true) ||
+ !tests->PassesRequiredExtensionsTests()) {
+ continue;
+ }
+ const auto& languages = tests->mStringListAttributes[LANGUAGE];
+ if (!languages.IsExplicitlySet()) {
+ if (!defaultChild) {
+ defaultChild = child;
+ }
+ continue;
+ }
+ for (uint32_t i = 0; i < languages.Length(); i++) {
+ children.AppendElement(child);
+ availLocales.AppendElement(NS_ConvertUTF16toUTF8(languages[i]));
+ }
+ }
+ }
+
+ // For each entry in availLocales, we expect to have a corresponding entry
+ // in children that provides the child node associated with that locale.
+ MOZ_ASSERT(children.Length() == availLocales.Length());
+
+ if (availLocales.IsEmpty()) {
+ return defaultChild;
+ }
+
+ int32_t index = FindBestLanguage(availLocales);
+ if (index >= 0) {
+ return children[index];
+ }
+
+ return defaultChild;
+}
+
+bool SVGTests::PassesRequiredExtensionsTests() const {
// Required Extensions
//
// The requiredExtensions attribute defines a list of required language
@@ -98,12 +144,15 @@ bool SVGTests::PassesConditionalProcessingTestsIgnoringSystemLanguage() const {
// go beyond the feature set defined in the SVG specification.
// Each extension is identified by a URI reference.
// For now, claim that mozilla's SVG implementation supports XHTML and MathML.
- if (mStringListAttributes[EXTENSIONS].IsExplicitlySet()) {
- if (mStringListAttributes[EXTENSIONS].IsEmpty()) {
+ const auto& extensions = mStringListAttributes[EXTENSIONS];
+ if (extensions.IsExplicitlySet()) {
+ if (extensions.IsEmpty()) {
+ mPassesConditionalProcessingTests = Some(false);
return false;
}
- for (uint32_t i = 0; i < mStringListAttributes[EXTENSIONS].Length(); i++) {
- if (!HasExtension(mStringListAttributes[EXTENSIONS][i])) {
+ for (uint32_t i = 0; i < extensions.Length(); i++) {
+ if (!HasExtension(extensions[i])) {
+ mPassesConditionalProcessingTests = Some(false);
return false;
}
}
@@ -112,45 +161,36 @@ bool SVGTests::PassesConditionalProcessingTestsIgnoringSystemLanguage() const {
}
bool SVGTests::PassesConditionalProcessingTests() const {
- if (!PassesConditionalProcessingTestsIgnoringSystemLanguage()) {
+ if (mPassesConditionalProcessingTests) {
+ return mPassesConditionalProcessingTests.value();
+ }
+ if (!PassesRequiredExtensionsTests()) {
return false;
}
// systemLanguage
//
- // Evaluates to "true" if one of the languages indicated by user preferences
- // exactly equals one of the languages given in the value of this parameter,
- // or if one of the languages indicated by user preferences exactly equals a
- // prefix of one of the languages given in the value of this parameter such
- // that the first tag character following the prefix is "-".
- if (mStringListAttributes[LANGUAGE].IsExplicitlySet()) {
- if (mStringListAttributes[LANGUAGE].IsEmpty()) {
+ // Evaluates to true if there's a BCP 47 match for the one of the user
+ // preference languages with one of the languages given in the value of
+ // this parameter.
+ const auto& languages = mStringListAttributes[LANGUAGE];
+ if (languages.IsExplicitlySet()) {
+ if (languages.IsEmpty()) {
+ mPassesConditionalProcessingTests = Some(false);
return false;
}
- // Get our language preferences
- nsAutoString acceptLangs;
- Preferences::GetLocalizedString("intl.accept_languages", acceptLangs);
-
- if (acceptLangs.IsEmpty()) {
- NS_WARNING(
- "no default language specified for systemLanguage conditional test");
- return false;
+ AutoTArray<nsCString, 4> availLocales;
+ for (uint32_t i = 0; i < languages.Length(); i++) {
+ availLocales.AppendElement(NS_ConvertUTF16toUTF8(languages[i]));
}
- for (uint32_t i = 0; i < mStringListAttributes[LANGUAGE].Length(); i++) {
- nsCharSeparatedTokenizer languageTokenizer(acceptLangs, ',');
- while (languageTokenizer.hasMoreTokens()) {
- if (nsStyleUtil::DashMatchCompare(mStringListAttributes[LANGUAGE][i],
- languageTokenizer.nextToken(),
- nsCaseInsensitiveStringComparator)) {
- return true;
- }
- }
- }
- return false;
+ mPassesConditionalProcessingTests =
+ Some(FindBestLanguage(availLocales) >= 0);
+ return mPassesConditionalProcessingTests.value();
}
+ mPassesConditionalProcessingTests = Some(true);
return true;
}
@@ -163,6 +203,7 @@ bool SVGTests::ParseConditionalProcessingAttribute(nsAtom* aAttribute,
if (NS_FAILED(rv)) {
mStringListAttributes[i].Clear();
}
+ mPassesConditionalProcessingTests = Nothing();
MaybeInvalidate();
return true;
}
@@ -174,6 +215,7 @@ void SVGTests::UnsetAttr(const nsAtom* aAttribute) {
for (uint32_t i = 0; i < ArrayLength(sStringListNames); i++) {
if (aAttribute == sStringListNames[i]) {
mStringListAttributes[i].Clear();
+ mPassesConditionalProcessingTests = Nothing();
MaybeInvalidate();
return;
}
diff --git a/dom/svg/SVGTests.h b/dom/svg/SVGTests.h
index 148c35fc5c..90991fd60d 100644
--- a/dom/svg/SVGTests.h
+++ b/dom/svg/SVGTests.h
@@ -13,13 +13,15 @@
class nsAttrValue;
class nsAtom;
+class nsIContent;
class nsStaticAtom;
namespace mozilla {
namespace dom {
class DOMSVGStringList;
-}
+class SVGSwitchElement;
+} // namespace dom
#define MOZILLA_DOMSVGTESTS_IID \
{ \
@@ -42,26 +44,10 @@ class SVGTests : public nsISupports {
using SVGStringList = mozilla::SVGStringList;
/**
- * Compare the language name(s) in a systemLanguage attribute to the
- * user's language preferences, as defined in
- * http://www.w3.org/TR/SVG11/struct.html#SystemLanguageAttribute
- * We have a match if a language name in the users language preferences
- * exactly equals one of the language names or exactly equals a prefix of
- * one of the language names in the systemLanguage attribute.
- * @returns 2 * the lowest index in the aAcceptLangs that matches + 1
- * if only the prefix matches, -2 if there's no systemLanguage attribute,
- * or -1 if no indices match.
- * XXX This algorithm is O(M*N).
- */
- int32_t GetBestLanguagePreferenceRank(const nsAString& aAcceptLangs) const;
-
- /**
- * Check whether the conditional processing attributes other than
- * systemLanguage "return true" if they apply to and are specified
- * on the given element. Returns true if this element should be
- * rendered, false if it should not.
+ * Find the active switch child using BCP 47 rules.
*/
- bool PassesConditionalProcessingTestsIgnoringSystemLanguage() const;
+ static nsIContent* FindActiveSwitchChild(
+ const dom::SVGSwitchElement* aSwitch);
/**
* Check whether the conditional processing attributes requiredExtensions
@@ -72,16 +58,6 @@ class SVGTests : public nsISupports {
bool PassesConditionalProcessingTests() const;
/**
- * Check whether the conditional processing attributes requiredExtensions
- * and systemLanguage both "return true" if they apply to
- * and are specified on the given element. Returns true if this element
- * should be rendered, false if it should not.
- *
- * @param aAcceptLangs The value of the intl.accept_languages preference
- */
- bool PassesConditionalProcessingTests(const nsAString& aAcceptLangs) const;
-
- /**
* Returns true if the attribute is one of the conditional processing
* attributes.
*/
@@ -117,9 +93,17 @@ class SVGTests : public nsISupports {
virtual ~SVGTests() = default;
private:
+ /**
+ * Check whether the extensions processing attribute applies to and is
+ * specified on the given element. Returns true if this element should be
+ * rendered, false if it should not.
+ */
+ bool PassesRequiredExtensionsTests() const;
+
enum { EXTENSIONS, LANGUAGE };
SVGStringList mStringListAttributes[2];
static nsStaticAtom* const sStringListNames[2];
+ mutable Maybe<bool> mPassesConditionalProcessingTests = Some(true);
};
NS_DEFINE_STATIC_IID_ACCESSOR(SVGTests, MOZILLA_DOMSVGTESTS_IID)
diff --git a/dom/svg/test/test_switch.xhtml b/dom/svg/test/test_switch.xhtml
index d1fa546e26..a589a934c1 100644
--- a/dom/svg/test/test_switch.xhtml
+++ b/dom/svg/test/test_switch.xhtml
@@ -22,17 +22,6 @@ SimpleTest.waitForExplicitFinish();
var test = 1;
-function checkBounds(element, x, y, w, h) {
- var bbox = element.getBBox();
- var name = element.nodeName;
-
- is(bbox.x, x, test + " " + name + ".getBBox().x");
- is(bbox.y, y, test + " " + name + ".getBBox().y");
- is(bbox.width, w, test + " " + name + ".getBBox().width");
- is(bbox.height, h, test + " " + name + ".getBBox().height");
- ++test;
-}
-
function checkWidth(element, w) {
var bbox = element.getBBox();
var name = element.nodeName;
@@ -41,55 +30,65 @@ function checkWidth(element, w) {
++test;
}
-function run() {
- // Set accept_languages to something we know
- SpecialPowers.pushPrefEnv({"set": [["intl.accept_languages", "en-gb,en,it"]]}, run1);
-}
-
-function run1() {
+async function run() {
try {
- var doc = $("svg").contentDocument;
- var s = doc.getElementById("s");
- var first = doc.getElementById("first");
- var second = doc.getElementById("second");
- var third = doc.getElementById("third");
-
- first.setAttribute("systemLanguage", "fr");
-
- /* test for an exact match */
- second.setAttribute("systemLanguage", "en-gb");
- checkWidth(s, 50);
-
- /* test for a close match i.e. the same language prefix */
- second.setAttribute("systemLanguage", "en-us");
- checkWidth(s, 50);
-
- /* test that we pick the best match */
- second.setAttribute("systemLanguage", "it");
- checkWidth(s, 50);
-
- /* test that we use the default if nothing matches */
- second.setAttribute("systemLanguage", "fr");
- checkWidth(s, 80);
-
- /* test we still ignore non-matches */
- second.removeAttribute("systemLanguage");
- third.setAttribute("systemLanguage", "fr");
- checkWidth(s, 50);
-
- /* check what happens if nothing matches */
- second.setAttribute("systemLanguage", "fr");
- checkWidth(s, 0);
-
- /* test that we pick the best match */
- first.setAttribute("systemLanguage", "en");
- second.setAttribute("systemLanguage", "en-gb");
- checkWidth(s, 50);
+ // Set accept_languages to something we know
+ await SpecialPowers.pushPrefEnv({"set": [["intl.accept_languages", "en-gb,en,it"]]}, run1);
} finally {
SimpleTest.finish();
}
}
+function run1() {
+ let doc = $("svg").contentDocument;
+ let s = doc.getElementById("s");
+ let first = doc.getElementById("first");
+ let second = doc.getElementById("second");
+ let third = doc.getElementById("third");
+
+ first.setAttribute("systemLanguage", "fr");
+
+ /* test for an exact match */
+ second.setAttribute("systemLanguage", "en-gb");
+ checkWidth(s, 50);
+
+ /* test for a close match i.e. the same language prefix */
+ second.setAttribute("systemLanguage", "en");
+ checkWidth(s, 50);
+
+ /* test for a close match regardless of case */
+ second.setAttribute("systemLanguage", "eN");
+ checkWidth(s, 50);
+
+ /* test that different regions don't match */
+ second.setAttribute("systemLanguage", "en-us");
+ checkWidth(s, 80);
+
+ /* test that we pick the best match */
+ second.setAttribute("systemLanguage", "it");
+ checkWidth(s, 50);
+
+ /* test that we use the default if nothing matches */
+ second.setAttribute("systemLanguage", "fr");
+ checkWidth(s, 80);
+
+ /* test we still ignore non-matches */
+ second.removeAttribute("systemLanguage");
+ third.setAttribute("systemLanguage", "fr");
+ checkWidth(s, 50);
+
+ /* check what happens if nothing matches */
+ second.setAttribute("systemLanguage", "fr");
+ third.setAttribute("systemLanguage", "fr");
+ checkWidth(s, 0);
+
+ /* test that we pick the best match */
+ first.setAttribute("systemLanguage", "en");
+ second.setAttribute("systemLanguage", "en-gb");
+ third.removeAttribute("systemLanguage");
+ checkWidth(s, 50);
+}
+
window.addEventListener("load", run);
]]>
diff --git a/dom/svg/test/test_tabindex.html b/dom/svg/test/test_tabindex.html
index 65315420bc..3d29d1070b 100644
--- a/dom/svg/test/test_tabindex.html
+++ b/dom/svg/test/test_tabindex.html
@@ -37,7 +37,6 @@ function main() {
var t = document.getElementById("t");
var l1 = document.getElementById("l1");
var l2 = document.getElementById("l2");
- const isMac = ("nsILocalFileMac" in SpecialPowers.Ci);
try {
// Step 1: Checking by assigning tabIndex
@@ -74,20 +73,10 @@ function main() {
is(document.activeElement.tabIndex, 3, "The active element tabindex is 3");
synthesizeKey("KEY_Tab");
- // On Mac, SVG link elements should not be focused.
- if (isMac) {
- is(document.activeElement.tabIndex, 6, "The active element tabindex is 6");
- } else {
- is(document.activeElement.tabIndex, 4, "The active element tabindex is 4");
- }
+ is(document.activeElement.tabIndex, 4, "The active element tabindex is 4");
synthesizeKey("KEY_Tab");
- // On Mac, SVG link elements should not be focused.
- if (isMac) {
- is(document.activeElement.tabIndex, 7, "The active element tabindex is 7");
- } else {
- is(document.activeElement.tabIndex, 5, "The active element tabindex is 5");
- }
+ is(document.activeElement.tabIndex, 5, "The active element tabindex is 5");
} catch (e) {
ok(false, "Got unexpected exception" + e);
}
diff --git a/dom/tests/browser/browser.toml b/dom/tests/browser/browser.toml
index dfb7c2c569..1e9f994488 100644
--- a/dom/tests/browser/browser.toml
+++ b/dom/tests/browser/browser.toml
@@ -44,6 +44,7 @@ tags = "geolocation"
skip-if = ["os != 'mac'"]
["browser_bug1238427.js"]
+https_first_disabled = true
["browser_bug1316330.js"]
diff --git a/dom/tests/mochitest/chrome/test_focus.xhtml b/dom/tests/mochitest/chrome/test_focus.xhtml
index 3e30104af4..ab383c4e1e 100644
--- a/dom/tests/mochitest/chrome/test_focus.xhtml
+++ b/dom/tests/mochitest/chrome/test_focus.xhtml
@@ -9,10 +9,7 @@
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<script>
-if (navigator.platform.startsWith("Win")) {
- SimpleTest.expectAssertions(0, 1);
-}
-
+SimpleTest.expectAssertions(0, 1);
SimpleTest.waitForExplicitFinish();
async function runTest()
{
diff --git a/dom/tests/mochitest/general/frameStoragePrevented.html b/dom/tests/mochitest/general/frameStoragePrevented.html
index 693beff5a4..baa74b1773 100644
--- a/dom/tests/mochitest/general/frameStoragePrevented.html
+++ b/dom/tests/mochitest/general/frameStoragePrevented.html
@@ -17,7 +17,7 @@
return new Promise((resolve, reject) => {
var w;
try {
- w = new Worker("workerStoragePrevented.js");
+ w = new Worker("workerStoragePrevented.js#outer");
} catch (e) {
ok(true, "Running workers was prevented");
resolve();
@@ -35,7 +35,7 @@
}
// Try to run a worker, which shouldn't be able to access storage
- await runWorker("workerStoragePrevented.js");
+ await runWorker("workerStoragePrevented.js#outer");
});
</script>
diff --git a/dom/tests/mochitest/general/storagePermissionsUtils.js b/dom/tests/mochitest/general/storagePermissionsUtils.js
index cfab2f0a14..b17adab6b4 100644
--- a/dom/tests/mochitest/general/storagePermissionsUtils.js
+++ b/dom/tests/mochitest/general/storagePermissionsUtils.js
@@ -38,7 +38,11 @@ function runIFrame(url) {
return;
}
- ok(!e.data.match(/^FAILURE/), e.data + " (IFRAME = " + url + ")");
+ const isFail = e.data.match(/^FAILURE/);
+ ok(!isFail, e.data + " (IFRAME = " + url + ")");
+ if (isFail) {
+ reject(e);
+ }
}
window.addEventListener("message", onMessage);
@@ -57,6 +61,11 @@ function runWorker(url) {
ok(!e.data.match(/^FAILURE/), e.data + " (WORKER = " + url + ")");
});
+
+ worker.addEventListener("error", function (e) {
+ ok(false, e.data + " (WORKER = " + url + ")");
+ reject(e);
+ });
});
}
@@ -130,24 +139,59 @@ function storageAllowed() {
ok(false, "getting indexedDB should not throw");
}
+ const dbName = "db";
+
try {
var promise = caches.keys();
ok(true, "getting caches didn't throw");
return new Promise((resolve, reject) => {
- promise.then(
- function () {
- ok(location.protocol == "https:", "The promise was not rejected");
- resolve();
- },
- function () {
- ok(
- location.protocol !== "https:",
- "The promise should not have been rejected"
- );
- resolve();
- }
- );
+ const checkCacheKeys = () => {
+ promise.then(
+ () => {
+ ok(location.protocol == "https:", "The promise was not rejected");
+ resolve();
+ },
+ () => {
+ ok(
+ location.protocol !== "https:",
+ "The promise should not have been rejected"
+ );
+ resolve();
+ }
+ );
+ };
+
+ const checkDeleteDbAndTheRest = dbs => {
+ ok(
+ dbs.some(elem => elem.name === dbName),
+ "Expected database should be found"
+ );
+
+ const end = indexedDB.deleteDatabase(dbName);
+ end.onsuccess = checkCacheKeys;
+ end.onerror = err => {
+ ok(false, "querying indexedDB databases should not throw");
+ reject(err);
+ };
+ };
+
+ const checkDatabasesAndTheRest = () => {
+ indexedDB
+ .databases()
+ .then(checkDeleteDbAndTheRest)
+ .catch(err => {
+ ok(false, "deleting an indexedDB database should not throw");
+ reject(err);
+ });
+ };
+
+ const begin = indexedDB.open(dbName);
+ begin.onsuccess = checkDatabasesAndTheRest;
+ begin.onerror = err => {
+ ok(false, "opening an indexedDB database should not throw");
+ reject(err);
+ };
});
} catch (e) {
ok(location.protocol !== "https:", "getting caches should not have thrown");
@@ -184,9 +228,47 @@ function storagePrevented() {
try {
indexedDB;
- ok(false, "getting indexedDB should have thrown");
+ ok(true, "getting indexedDB didn't throw");
} catch (e) {
- ok(true, "getting indexedDB threw");
+ ok(false, "getting indexedDB should not have thrown");
+ }
+
+ const dbName = "album";
+
+ try {
+ indexedDB.open(dbName);
+ ok(false, "opening an indexedDB database didn't throw");
+ } catch (e) {
+ ok(true, "opening an indexedDB database threw");
+ ok(
+ e.name == "SecurityError",
+ "opening indexedDB database emitted a security error"
+ );
+ }
+
+ // Note: Security error is expected to be thrown synchronously.
+ indexedDB.databases().then(
+ () => {
+ ok(false, "querying indexedDB databases didn't reject");
+ },
+ e => {
+ ok(true, "querying indexedDB databases rejected");
+ ok(
+ e.name == "SecurityError",
+ "querying indexedDB databases emitted a security error"
+ );
+ }
+ );
+
+ try {
+ indexedDB.deleteDatabase(dbName);
+ ok(false, "deleting an indexedDB database didn't throw");
+ } catch (e) {
+ ok(true, "deleting an indexedDB database threw");
+ ok(
+ e.name == "SecurityError",
+ "deleting indexedDB database emitted a security error"
+ );
}
try {
@@ -260,8 +342,15 @@ async function runTestInWindow(test) {
};
});
- await new Promise(resolve => {
+ return new Promise((resolve, reject) => {
onmessage = e => {
+ if (!e.data.type) {
+ w.postMessage("FAILURE: " + e.data, document.referrer);
+ ok(false, "No error data type");
+ reject(e);
+ return;
+ }
+
if (e.data.type == "finish") {
w.close();
resolve();
@@ -269,7 +358,15 @@ async function runTestInWindow(test) {
}
if (e.data.type == "check") {
- ok(e.data.test, e.data.msg);
+ const payload = e.data.msg ? e.data.msg : e.data;
+ ok(e.data.test, payload);
+ const isFail = payload.match(/^FAILURE/) || !e.data.test;
+ if (isFail) {
+ w.postMessage("FAILURE: " + e.data, document.referrer);
+ ok(false, payload);
+ w.close();
+ reject(e);
+ }
return;
}
diff --git a/dom/tests/mochitest/general/test_interfaces.js b/dom/tests/mochitest/general/test_interfaces.js
index e6ea910e14..93a26b9238 100644
--- a/dom/tests/mochitest/general/test_interfaces.js
+++ b/dom/tests/mochitest/general/test_interfaces.js
@@ -73,6 +73,7 @@ let wasmGlobalInterfaces = [
{ name: "Function", insecureContext: true, nightly: true },
{ name: "Exception", insecureContext: true },
{ name: "Tag", insecureContext: true },
+ { name: "JSTag", insecureContext: true, earlyBetaOrEarlier: true },
{ name: "compile", insecureContext: true },
{ name: "compileStreaming", insecureContext: true },
{ name: "instantiate", insecureContext: true },
@@ -256,7 +257,7 @@ let interfaceNamesInGlobalScope = [
// IMPORTANT: Do not change this list without review from a DOM peer!
{ name: "ClipboardEvent", insecureContext: true },
// IMPORTANT: Do not change this list without review from a DOM peer!
- { name: "ClipboardItem", earlyBetaOrEarlier: true },
+ { name: "ClipboardItem" },
// IMPORTANT: Do not change this list without review from a DOM peer!
{ name: "CloseEvent", insecureContext: true },
// IMPORTANT: Do not change this list without review from a DOM peer!
@@ -328,7 +329,7 @@ let interfaceNamesInGlobalScope = [
// IMPORTANT: Do not change this list without review from a DOM peer!
{ name: "CSSRuleList", insecureContext: true },
// IMPORTANT: Do not change this list without review from a DOM peer!
- { name: "CSSStartingStyleRule", insecureContext: true, disabled: true },
+ { name: "CSSStartingStyleRule", insecureContext: true, nightly: true },
// IMPORTANT: Do not change this list without review from a DOM peer!
{ name: "CSSStyleDeclaration", insecureContext: true },
// IMPORTANT: Do not change this list without review from a DOM peer!
diff --git a/dom/tests/mochitest/general/test_storagePermissionsReject.html b/dom/tests/mochitest/general/test_storagePermissionsReject.html
index 70dbe856d9..62c06a0639 100644
--- a/dom/tests/mochitest/general/test_storagePermissionsReject.html
+++ b/dom/tests/mochitest/general/test_storagePermissionsReject.html
@@ -35,7 +35,7 @@ task(async function() {
await runIFrame(thirdparty + "frameStorageChrome.html?allowed=no&blockSessionStorage=yes");
// Workers should be unable to access storage
- await runWorker("workerStoragePrevented.js");
+ await runWorker("workerStoragePrevented.js#outer");
});
});
diff --git a/dom/tests/mochitest/general/window_storagePermissions.html b/dom/tests/mochitest/general/window_storagePermissions.html
index 3bab23c13b..92078f2fcc 100644
--- a/dom/tests/mochitest/general/window_storagePermissions.html
+++ b/dom/tests/mochitest/general/window_storagePermissions.html
@@ -25,6 +25,8 @@ onmessage = e => {
let runnable = eval(runnableStr); // eslint-disable-line no-eval
runnable.call(this).then(_ => {
opener.postMessage({ type: "finish" }, "*");
+ }).catch(e => {
+ ok(false, e.data);
});
return;
diff --git a/dom/tests/mochitest/general/workerStorageAllowed.js b/dom/tests/mochitest/general/workerStorageAllowed.js
index 89e0a4b9ce..93c4c94c48 100644
--- a/dom/tests/mochitest/general/workerStorageAllowed.js
+++ b/dom/tests/mochitest/general/workerStorageAllowed.js
@@ -14,50 +14,88 @@ function finishTest() {
self.close();
}
-// Workers don't have access to localstorage or sessionstorage
-ok(typeof self.localStorage == "undefined", "localStorage should be undefined");
-ok(
- typeof self.sessionStorage == "undefined",
- "sessionStorage should be undefined"
-);
-
// Make sure that we can access indexedDB
-try {
- indexedDB;
- ok(true, "WORKER getting indexedDB didn't throw");
-} catch (e) {
- ok(false, "WORKER getting indexedDB should not throw");
+function idbTest() {
+ try {
+ indexedDB;
+
+ const idbcycle = new Promise((resolve, reject) => {
+ const begin = indexedDB.open("door");
+ begin.onerror = e => {
+ reject(e);
+ };
+ begin.onsuccess = () => {
+ indexedDB
+ .databases()
+ .then(dbs => {
+ ok(
+ dbs.some(elem => elem.name === "door"),
+ "WORKER just created database should be found"
+ );
+ const end = indexedDB.deleteDatabase("door");
+ end.onerror = e => {
+ reject(e);
+ };
+ end.onsuccess = () => {
+ resolve();
+ };
+ })
+ .catch(err => {
+ reject(err);
+ });
+ };
+ });
+
+ idbcycle.then(
+ () => {
+ ok(true, "WORKER getting indexedDB didn't throw");
+ cacheTest();
+ },
+ e => {
+ ok(false, "WORKER getting indexedDB threw " + e.message);
+ cacheTest();
+ }
+ );
+ } catch (e) {
+ ok(false, "WORKER getting indexedDB should not throw");
+ cacheTest();
+ }
}
// Make sure that we can access caches
-try {
- var promise = caches.keys();
- ok(true, "WORKER getting caches didn't throw");
+function cacheTest() {
+ try {
+ var promise = caches.keys();
+ ok(true, "WORKER getting caches didn't throw");
- promise.then(
- function () {
- ok(location.protocol == "https:", "WORKER The promise was not rejected");
- workerTest();
- },
- function () {
- ok(
- location.protocol !== "https:",
- "WORKER The promise should not have been rejected"
- );
- workerTest();
- }
- );
-} catch (e) {
- ok(
- location.protocol !== "https:",
- "WORKER getting caches should not have thrown"
- );
- workerTest();
+ promise.then(
+ function () {
+ ok(
+ location.protocol == "https:",
+ "WORKER The promise was not rejected"
+ );
+ workerTest();
+ },
+ function () {
+ ok(
+ location.protocol !== "https:",
+ "WORKER The promise should not have been rejected"
+ );
+ workerTest();
+ }
+ );
+ } catch (e) {
+ ok(
+ location.protocol !== "https:",
+ "WORKER getting caches should not have thrown"
+ );
+ workerTest();
+ }
}
// Try to spawn an inner worker, and make sure that it can also access storage
function workerTest() {
- if (location.hash == "#inner") {
+ if (location.hash != "#outer") {
// Don't recurse infinitely, if we are the inner worker, don't spawn another
finishTest();
return;
@@ -75,4 +113,27 @@ function workerTest() {
e.data + " (WORKER = workerStorageAllowed.js#inner)"
);
});
+
+ worker.addEventListener("error", function (e) {
+ ok(false, e.data + " (WORKER = workerStorageAllowed.js#inner)");
+
+ finishTest();
+ });
+}
+
+try {
+ // Workers don't have access to localstorage or sessionstorage
+ ok(
+ typeof self.localStorage == "undefined",
+ "localStorage should be undefined"
+ );
+ ok(
+ typeof self.sessionStorage == "undefined",
+ "sessionStorage should be undefined"
+ );
+
+ idbTest();
+} catch (e) {
+ ok(false, "WORKER Unwelcome exception received");
+ finishTest();
}
diff --git a/dom/tests/mochitest/general/workerStoragePrevented.js b/dom/tests/mochitest/general/workerStoragePrevented.js
index 467cc09113..7d232d65df 100644
--- a/dom/tests/mochitest/general/workerStoragePrevented.js
+++ b/dom/tests/mochitest/general/workerStoragePrevented.js
@@ -21,40 +21,94 @@ ok(
"sessionStorage should be undefined"
);
-// Make sure that we can't access indexedDB
+// Make sure that we can access indexedDB handle
try {
indexedDB;
- ok(false, "WORKER getting indexedDB should have thrown");
+ ok(true, "WORKER getting indexedDB didn't throw");
} catch (e) {
- ok(true, "WORKER getting indexedDB threw");
+ ok(false, "WORKER getting indexedDB threw");
+}
+
+// Make sure that we cannot access indexedDB methods
+idbOpenTest();
+
+// Make sure that we can't access indexedDB deleteDatabase
+function idbDeleteTest() {
+ try {
+ indexedDB.deleteDatabase("door");
+ ok(false, "WORKER deleting indexedDB database succeeded");
+ } catch (e) {
+ ok(true, "WORKER deleting indexedDB database failed");
+ ok(
+ e.name == "SecurityError",
+ "WORKER deleting indexedDB database threw a security error"
+ );
+ } finally {
+ cacheTest();
+ }
+}
+
+// Make sure that we can't access indexedDB databases
+function idbDatabasesTest() {
+ indexedDB
+ .databases()
+ .then(() => {
+ ok(false, "WORKER querying indexedDB databases succeeded");
+ })
+ .catch(e => {
+ ok(true, "WORKER querying indexedDB databases failed");
+ ok(
+ e.name == "SecurityError",
+ "WORKER querying indexedDB databases threw a security error"
+ );
+ })
+ .finally(idbDeleteTest);
+}
+
+// Make sure that we can't access indexedDB open
+function idbOpenTest() {
+ try {
+ indexedDB.open("door");
+ ok(false, "WORKER opening indexedDB database succeeded");
+ } catch (e) {
+ ok(true, "WORKER opening indexedDB database failed");
+ ok(
+ e.name == "SecurityError",
+ "WORKER opening indexedDB database threw a security error"
+ );
+ } finally {
+ idbDatabasesTest();
+ }
}
// Make sure that we can't access caches
-try {
- var promise = caches.keys();
- ok(true, "WORKER getting caches didn't throw");
+function cacheTest() {
+ try {
+ var promise = caches.keys();
+ ok(true, "WORKER getting caches didn't throw");
- promise.then(
- function () {
- ok(false, "WORKER The promise should have rejected");
- workerTest();
- },
- function () {
- ok(true, "WORKER The promise was rejected");
- workerTest();
- }
- );
-} catch (e) {
- ok(
- location.protocol !== "https:",
- "WORKER getting caches should not have thrown"
- );
- workerTest();
+ promise.then(
+ function () {
+ ok(false, "WORKER The promise should have rejected");
+ workerTest();
+ },
+ function () {
+ ok(true, "WORKER The promise was rejected");
+ workerTest();
+ }
+ );
+ } catch (e) {
+ ok(
+ location.protocol !== "https:",
+ "WORKER getting caches should not have thrown"
+ );
+ workerTest();
+ }
}
// Try to spawn an inner worker, and make sure that it also can't access storage
function workerTest() {
- if (location.hash == "#inner") {
+ if (location.hash != "#outer") {
// Don't recurse infinitely, if we are the inner worker, don't spawn another
finishTest();
return;
@@ -62,14 +116,16 @@ function workerTest() {
// Create the inner worker, and listen for test messages from it
var worker = new Worker("workerStoragePrevented.js#inner");
worker.addEventListener("message", function (e) {
- if (e.data == "done") {
+ const isFail = e.data.match(/^FAILURE/);
+ ok(!isFail, e.data + " (WORKER = workerStoragePrevented.js#inner)");
+
+ if (e.data == "done" || isFail) {
finishTest();
- return;
}
+ });
+ worker.addEventListener("error", function (e) {
+ ok(false, e.data + " (WORKER = workerStoragePrevented.js#inner)");
- ok(
- !e.data.match(/^FAILURE/),
- e.data + " (WORKER = workerStoragePrevented.js#inner)"
- );
+ finishTest();
});
}
diff --git a/dom/tests/mochitest/webcomponents/chrome.toml b/dom/tests/mochitest/webcomponents/chrome.toml
index b29df619c4..5db7f14a37 100644
--- a/dom/tests/mochitest/webcomponents/chrome.toml
+++ b/dom/tests/mochitest/webcomponents/chrome.toml
@@ -1,7 +1,7 @@
[DEFAULT]
support-files = ["dummy_page.html"]
-["test_custom_element_ensure_custom_element.html"]
+["test_custom_element_auto_import.html"]
["test_custom_element_htmlconstructor_chrome.html"]
support-files = [
diff --git a/dom/tests/mochitest/webcomponents/test_custom_element_ensure_custom_element.html b/dom/tests/mochitest/webcomponents/test_custom_element_auto_import.html
index 03ed2e3815..2371600677 100644
--- a/dom/tests/mochitest/webcomponents/test_custom_element_ensure_custom_element.html
+++ b/dom/tests/mochitest/webcomponents/test_custom_element_auto_import.html
@@ -1,11 +1,8 @@
<!DOCTYPE HTML>
<html lang="en">
- <!--
- https://bugzilla.mozilla.org/show_bug.cgi?id=1813077
- -->
<head>
<meta charset="utf-8">
- <title>Test for customElements.ensureCustomElement</title>
+ <title>Test for custom element auto import behavior</title>
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
<script>
@@ -14,36 +11,33 @@
outside of the task that imported them. This can create issues if writing
another test in this file.
*/
- add_task(async function test_ensure_custom_elements() {
+ add_task(async function test_custom_elements_auto_import() {
let registry = SpecialPowers.wrap(customElements);
- ok(window.ensureCustomElements, "should be defined");
// Ensure the custom elements from ESModules are not defined.
is(registry.get("moz-button-group"), undefined, "moz-button-group should be undefined since we have not yet imported it");
is(registry.get("moz-support-link"), undefined, "moz-support-link should be undefined since we have not yet imported it");
is(registry.get("moz-toggle"), undefined, "moz-toggle should be undefined since we have not yet imported it");
- // Import a single custom element and assert it exists in the custom
- // element registry
- let modules = await window.ensureCustomElements("moz-support-link");
- ok(registry.get("moz-support-link"), "moz-support-link should be defined after importing it");
- is(modules, null, "There should not be a return value when using ensureCustomElements");
+ // Create a custom element and assert that it gets imported/defined.
+ document.createElement("moz-support-link");
+ ok(registry.get("moz-support-link"), "moz-support-link should be defined after creation");
- // Import multiple custom elements and assert they exist in the registry
- modules = undefined;
+ // Create multiple custom elements and assert they exist in the registry.
is(registry.get("moz-button-group"), undefined, "moz-button-group should be undefined since we have not yet imported it");
is(registry.get("moz-toggle"), undefined, "moz-toggle should be undefined since we have not yet imported it")
- modules = await window.ensureCustomElements("moz-toggle", "moz-button-group");
- is(modules, null, "There should not be a return value when using ensureCustomElements");
+
+ document.createElement("moz-button-group");
+ document.createElement("moz-toggle");
+
ok(registry.get("moz-toggle"), "moz-toggle should be defined after importing it");
ok(registry.get("moz-button-group"), "moz-button-group should be defined after importing it");
- // Ensure there are no errors if the imported elements are imported
- // again
- modules = undefined;
- modules = await window.ensureCustomElements("moz-support-link", "moz-toggle", "moz-button-group");
+ // Ensure there are no errors if the imported elements are created again.
+ document.createElement("moz-support-link");
+ document.createElement("moz-button-group");
+ document.createElement("moz-toggle");
ok(true, "The custom elements should not throw an error if imported again");
- is(modules, null, "There should not be a return value when using ensureCustomElements");
})
</script>
</head>
diff --git a/dom/webauthn/MacOSWebAuthnService.mm b/dom/webauthn/MacOSWebAuthnService.mm
index fc08ee1a48..24fad770c8 100644
--- a/dom/webauthn/MacOSWebAuthnService.mm
+++ b/dom/webauthn/MacOSWebAuthnService.mm
@@ -941,6 +941,17 @@ void MacOSWebAuthnService::DoGetAssertion(
Unused << aArgs->GetAllowList(allowList);
Unused << aArgs->GetAllowListTransports(allowListTransports);
}
+ // Compute the union of the transport sets.
+ uint8_t transports = 0;
+ for (uint8_t credTransports : allowListTransports) {
+ if (credTransports == 0) {
+ // treat the empty transport set as "all transports".
+ transports = ~0;
+ break;
+ }
+ transports |= credTransports;
+ }
+
NSMutableArray* platformAllowedCredentials =
[[NSMutableArray alloc] init];
for (const auto& allowedCredentialId : allowList) {
@@ -999,6 +1010,15 @@ void MacOSWebAuthnService::DoGetAssertion(
platformAssertionRequest.userVerificationPreference =
*userVerificationPreference;
}
+ if (__builtin_available(macos 13.5, *)) {
+ // Show the hybrid transport option if (1) we have no transport hints
+ // or (2) at least one allow list entry lists the hybrid transport.
+ bool shouldShowHybridTransport =
+ !transports ||
+ (transports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_HYBRID);
+ platformAssertionRequest.shouldShowHybridTransport =
+ shouldShowHybridTransport;
+ }
// Initialize the cross-platform provider with the rpId.
ASAuthorizationSecurityKeyPublicKeyCredentialProvider*
diff --git a/dom/webgpu/Adapter.h b/dom/webgpu/Adapter.h
index 4156588e8e..1daede6119 100644
--- a/dom/webgpu/Adapter.h
+++ b/dom/webgpu/Adapter.h
@@ -11,6 +11,7 @@
#include "mozilla/AlreadyAddRefed.h"
#include "mozilla/dom/NonRefcountedDOMObject.h"
#include "mozilla/webgpu/WebGPUTypes.h"
+#include "mozilla/IntegerPrintfMacros.h"
#include "nsPrintfCString.h"
#include "nsString.h"
#include "ObjectModel.h"
diff --git a/dom/webgpu/CanvasContext.cpp b/dom/webgpu/CanvasContext.cpp
index 46633d9160..62735d1af5 100644
--- a/dom/webgpu/CanvasContext.cpp
+++ b/dom/webgpu/CanvasContext.cpp
@@ -204,6 +204,10 @@ RefPtr<Texture> CanvasContext::GetCurrentTexture(ErrorResult& aRv) {
}
void CanvasContext::MaybeQueueSwapChainPresent() {
+ if (!mConfig) {
+ return;
+ }
+
MOZ_ASSERT(mTexture);
if (mTexture) {
diff --git a/dom/webgpu/ComputePassEncoder.cpp b/dom/webgpu/ComputePassEncoder.cpp
index 190bbf00a7..f7b177c77a 100644
--- a/dom/webgpu/ComputePassEncoder.cpp
+++ b/dom/webgpu/ComputePassEncoder.cpp
@@ -38,65 +38,73 @@ ComputePassEncoder::ComputePassEncoder(
CommandEncoder* const aParent, const dom::GPUComputePassDescriptor& aDesc)
: ChildOf(aParent), mPass(BeginComputePass(aParent->mId, aDesc)) {}
-ComputePassEncoder::~ComputePassEncoder() {
+ComputePassEncoder::~ComputePassEncoder() { Cleanup(); }
+
+void ComputePassEncoder::Cleanup() {
if (mValid) {
- mValid = false;
+ End();
}
}
void ComputePassEncoder::SetBindGroup(
uint32_t aSlot, const BindGroup& aBindGroup,
const dom::Sequence<uint32_t>& aDynamicOffsets) {
- if (mValid) {
- mUsedBindGroups.AppendElement(&aBindGroup);
- ffi::wgpu_recorded_compute_pass_set_bind_group(
- mPass.get(), aSlot, aBindGroup.mId, aDynamicOffsets.Elements(),
- aDynamicOffsets.Length());
+ if (!mValid) {
+ return;
}
+ mUsedBindGroups.AppendElement(&aBindGroup);
+ ffi::wgpu_recorded_compute_pass_set_bind_group(
+ mPass.get(), aSlot, aBindGroup.mId, aDynamicOffsets.Elements(),
+ aDynamicOffsets.Length());
}
void ComputePassEncoder::SetPipeline(const ComputePipeline& aPipeline) {
- if (mValid) {
- mUsedPipelines.AppendElement(&aPipeline);
- ffi::wgpu_recorded_compute_pass_set_pipeline(mPass.get(), aPipeline.mId);
+ if (!mValid) {
+ return;
}
+ mUsedPipelines.AppendElement(&aPipeline);
+ ffi::wgpu_recorded_compute_pass_set_pipeline(mPass.get(), aPipeline.mId);
}
void ComputePassEncoder::DispatchWorkgroups(uint32_t workgroupCountX,
uint32_t workgroupCountY,
uint32_t workgroupCountZ) {
- if (mValid) {
- ffi::wgpu_recorded_compute_pass_dispatch_workgroups(
- mPass.get(), workgroupCountX, workgroupCountY, workgroupCountZ);
+ if (!mValid) {
+ return;
}
+ ffi::wgpu_recorded_compute_pass_dispatch_workgroups(
+ mPass.get(), workgroupCountX, workgroupCountY, workgroupCountZ);
}
void ComputePassEncoder::DispatchWorkgroupsIndirect(
const Buffer& aIndirectBuffer, uint64_t aIndirectOffset) {
- if (mValid) {
- ffi::wgpu_recorded_compute_pass_dispatch_workgroups_indirect(
- mPass.get(), aIndirectBuffer.mId, aIndirectOffset);
+ if (!mValid) {
+ return;
}
+ ffi::wgpu_recorded_compute_pass_dispatch_workgroups_indirect(
+ mPass.get(), aIndirectBuffer.mId, aIndirectOffset);
}
void ComputePassEncoder::PushDebugGroup(const nsAString& aString) {
- if (mValid) {
- const NS_ConvertUTF16toUTF8 utf8(aString);
- ffi::wgpu_recorded_compute_pass_push_debug_group(mPass.get(), utf8.get(),
- 0);
+ if (!mValid) {
+ return;
}
+ const NS_ConvertUTF16toUTF8 utf8(aString);
+ ffi::wgpu_recorded_compute_pass_push_debug_group(mPass.get(), utf8.get(), 0);
}
void ComputePassEncoder::PopDebugGroup() {
- if (mValid) {
- ffi::wgpu_recorded_compute_pass_pop_debug_group(mPass.get());
+ if (!mValid) {
+ return;
}
+ ffi::wgpu_recorded_compute_pass_pop_debug_group(mPass.get());
}
void ComputePassEncoder::InsertDebugMarker(const nsAString& aString) {
- if (mValid) {
- const NS_ConvertUTF16toUTF8 utf8(aString);
- ffi::wgpu_recorded_compute_pass_insert_debug_marker(mPass.get(), utf8.get(),
- 0);
+ if (!mValid) {
+ return;
}
+ const NS_ConvertUTF16toUTF8 utf8(aString);
+ ffi::wgpu_recorded_compute_pass_insert_debug_marker(mPass.get(), utf8.get(),
+ 0);
}
void ComputePassEncoder::End() {
diff --git a/dom/webgpu/ComputePassEncoder.h b/dom/webgpu/ComputePassEncoder.h
index 2455822f79..8096b4e07e 100644
--- a/dom/webgpu/ComputePassEncoder.h
+++ b/dom/webgpu/ComputePassEncoder.h
@@ -41,7 +41,7 @@ class ComputePassEncoder final : public ObjectBase,
private:
virtual ~ComputePassEncoder();
- void Cleanup() {}
+ void Cleanup();
std::unique_ptr<ffi::WGPURecordedComputePass, ffiWGPUComputePassDeleter>
mPass;
diff --git a/dom/webgpu/ComputePipeline.cpp b/dom/webgpu/ComputePipeline.cpp
index ecd43289f3..759d8e16db 100644
--- a/dom/webgpu/ComputePipeline.cpp
+++ b/dom/webgpu/ComputePipeline.cpp
@@ -27,12 +27,16 @@ ComputePipeline::ComputePipeline(Device* const aParent, RawId aId,
ComputePipeline::~ComputePipeline() { Cleanup(); }
void ComputePipeline::Cleanup() {
- if (mValid) {
+ if (!mValid) {
return;
}
mValid = false;
auto bridge = mParent->GetBridge();
+ if (!bridge) {
+ return;
+ }
+
if (bridge->CanSend()) {
bridge->SendComputePipelineDrop(mId);
if (mImplicitPipelineLayoutId) {
@@ -54,6 +58,7 @@ void ComputePipeline::Cleanup() {
already_AddRefed<BindGroupLayout> ComputePipeline::GetBindGroupLayout(
uint32_t aIndex) const {
auto bridge = mParent->GetBridge();
+ MOZ_ASSERT(bridge && bridge->CanSend());
auto* client = bridge->GetClient();
ipc::ByteBuf bb;
diff --git a/dom/webgpu/Device.cpp b/dom/webgpu/Device.cpp
index a9fd5ee44c..62d302c4d8 100644
--- a/dom/webgpu/Device.cpp
+++ b/dom/webgpu/Device.cpp
@@ -667,6 +667,8 @@ RawId CreateComputePipelineImpl(PipelineCreationContext* const aContext,
ipc::ByteBuf* const aByteBuf) {
ffi::WGPUComputePipelineDescriptor desc = {};
nsCString entryPoint;
+ nsTArray<nsCString> constantKeys;
+ nsTArray<ffi::WGPUConstantEntry> constants;
webgpu::StringHelper label(aDesc.mLabel);
desc.label = label.Get();
@@ -685,6 +687,21 @@ RawId CreateComputePipelineImpl(PipelineCreationContext* const aContext,
} else {
desc.stage.entry_point = nullptr;
}
+ if (aDesc.mCompute.mConstants.WasPassed()) {
+ const auto& descConstants = aDesc.mCompute.mConstants.Value().Entries();
+ constantKeys.SetCapacity(descConstants.Length());
+ constants.SetCapacity(descConstants.Length());
+ for (const auto& entry : descConstants) {
+ ffi::WGPUConstantEntry constantEntry = {};
+ nsCString key = NS_ConvertUTF16toUTF8(entry.mKey);
+ constantKeys.AppendElement(key);
+ constantEntry.key = key.get();
+ constantEntry.value = entry.mValue;
+ constants.AppendElement(constantEntry);
+ }
+ desc.stage.constants = constants.Elements();
+ desc.stage.constants_length = constants.Length();
+ }
RawId implicit_bgl_ids[WGPUMAX_BIND_GROUPS] = {};
RawId id = ffi::wgpu_client_create_compute_pipeline(
@@ -708,6 +725,8 @@ RawId CreateRenderPipelineImpl(PipelineCreationContext* const aContext,
nsTArray<ffi::WGPUVertexAttribute> vertexAttributes;
ffi::WGPURenderPipelineDescriptor desc = {};
nsCString vsEntry, fsEntry;
+ nsTArray<nsCString> vsConstantKeys, fsConstantKeys;
+ nsTArray<ffi::WGPUConstantEntry> vsConstants, fsConstants;
ffi::WGPUIndexFormat stripIndexFormat = ffi::WGPUIndexFormat_Uint16;
ffi::WGPUFace cullFace = ffi::WGPUFace_Front;
ffi::WGPUVertexState vertexState = {};
@@ -735,6 +754,21 @@ RawId CreateRenderPipelineImpl(PipelineCreationContext* const aContext,
} else {
vertexState.stage.entry_point = nullptr;
}
+ if (stage.mConstants.WasPassed()) {
+ const auto& descConstants = stage.mConstants.Value().Entries();
+ vsConstantKeys.SetCapacity(descConstants.Length());
+ vsConstants.SetCapacity(descConstants.Length());
+ for (const auto& entry : descConstants) {
+ ffi::WGPUConstantEntry constantEntry = {};
+ nsCString key = NS_ConvertUTF16toUTF8(entry.mKey);
+ vsConstantKeys.AppendElement(key);
+ constantEntry.key = key.get();
+ constantEntry.value = entry.mValue;
+ vsConstants.AppendElement(constantEntry);
+ }
+ vertexState.stage.constants = vsConstants.Elements();
+ vertexState.stage.constants_length = vsConstants.Length();
+ }
for (const auto& vertex_desc : stage.mBuffers) {
ffi::WGPUVertexBufferLayout vb_desc = {};
@@ -775,6 +809,21 @@ RawId CreateRenderPipelineImpl(PipelineCreationContext* const aContext,
} else {
fragmentState.stage.entry_point = nullptr;
}
+ if (stage.mConstants.WasPassed()) {
+ const auto& descConstants = stage.mConstants.Value().Entries();
+ fsConstantKeys.SetCapacity(descConstants.Length());
+ fsConstants.SetCapacity(descConstants.Length());
+ for (const auto& entry : descConstants) {
+ ffi::WGPUConstantEntry constantEntry = {};
+ nsCString key = NS_ConvertUTF16toUTF8(entry.mKey);
+ fsConstantKeys.AppendElement(key);
+ constantEntry.key = key.get();
+ constantEntry.value = entry.mValue;
+ fsConstants.AppendElement(constantEntry);
+ }
+ fragmentState.stage.constants = fsConstants.Elements();
+ fragmentState.stage.constants_length = fsConstants.Length();
+ }
// Note: we pre-collect the blend states into a different array
// so that we can have non-stale pointers into it.
diff --git a/dom/webgpu/RenderBundleEncoder.cpp b/dom/webgpu/RenderBundleEncoder.cpp
index 54ebf12d64..b3b64c01e2 100644
--- a/dom/webgpu/RenderBundleEncoder.cpp
+++ b/dom/webgpu/RenderBundleEncoder.cpp
@@ -72,7 +72,7 @@ RenderBundleEncoder::RenderBundleEncoder(
const dom::GPURenderBundleEncoderDescriptor& aDesc)
: ChildOf(aParent),
mEncoder(CreateRenderBundleEncoder(aParent->mId, aDesc, aBridge)) {
- mValid = mEncoder.get() != nullptr;
+ mValid = !!mEncoder;
}
RenderBundleEncoder::~RenderBundleEncoder() { Cleanup(); }
@@ -80,100 +80,112 @@ RenderBundleEncoder::~RenderBundleEncoder() { Cleanup(); }
void RenderBundleEncoder::Cleanup() {
if (mValid) {
mValid = false;
+ mEncoder.release();
}
}
void RenderBundleEncoder::SetBindGroup(
uint32_t aSlot, const BindGroup& aBindGroup,
const dom::Sequence<uint32_t>& aDynamicOffsets) {
- if (mValid) {
- mUsedBindGroups.AppendElement(&aBindGroup);
- ffi::wgpu_render_bundle_set_bind_group(
- mEncoder.get(), aSlot, aBindGroup.mId, aDynamicOffsets.Elements(),
- aDynamicOffsets.Length());
+ if (!mValid) {
+ return;
}
+ mUsedBindGroups.AppendElement(&aBindGroup);
+ ffi::wgpu_render_bundle_set_bind_group(mEncoder.get(), aSlot, aBindGroup.mId,
+ aDynamicOffsets.Elements(),
+ aDynamicOffsets.Length());
}
void RenderBundleEncoder::SetPipeline(const RenderPipeline& aPipeline) {
- if (mValid) {
- mUsedPipelines.AppendElement(&aPipeline);
- ffi::wgpu_render_bundle_set_pipeline(mEncoder.get(), aPipeline.mId);
+ if (!mValid) {
+ return;
}
+ mUsedPipelines.AppendElement(&aPipeline);
+ ffi::wgpu_render_bundle_set_pipeline(mEncoder.get(), aPipeline.mId);
}
void RenderBundleEncoder::SetIndexBuffer(
const Buffer& aBuffer, const dom::GPUIndexFormat& aIndexFormat,
uint64_t aOffset, uint64_t aSize) {
- if (mValid) {
- mUsedBuffers.AppendElement(&aBuffer);
- const auto iformat = aIndexFormat == dom::GPUIndexFormat::Uint32
- ? ffi::WGPUIndexFormat_Uint32
- : ffi::WGPUIndexFormat_Uint16;
- ffi::wgpu_render_bundle_set_index_buffer(mEncoder.get(), aBuffer.mId,
- iformat, aOffset, aSize);
+ if (!mValid) {
+ return;
}
+ mUsedBuffers.AppendElement(&aBuffer);
+ const auto iformat = aIndexFormat == dom::GPUIndexFormat::Uint32
+ ? ffi::WGPUIndexFormat_Uint32
+ : ffi::WGPUIndexFormat_Uint16;
+ ffi::wgpu_render_bundle_set_index_buffer(mEncoder.get(), aBuffer.mId, iformat,
+ aOffset, aSize);
}
void RenderBundleEncoder::SetVertexBuffer(uint32_t aSlot, const Buffer& aBuffer,
uint64_t aOffset, uint64_t aSize) {
- if (mValid) {
- mUsedBuffers.AppendElement(&aBuffer);
- ffi::wgpu_render_bundle_set_vertex_buffer(mEncoder.get(), aSlot,
- aBuffer.mId, aOffset, aSize);
+ if (!mValid) {
+ return;
}
+ mUsedBuffers.AppendElement(&aBuffer);
+ ffi::wgpu_render_bundle_set_vertex_buffer(mEncoder.get(), aSlot, aBuffer.mId,
+ aOffset, aSize);
}
void RenderBundleEncoder::Draw(uint32_t aVertexCount, uint32_t aInstanceCount,
uint32_t aFirstVertex, uint32_t aFirstInstance) {
- if (mValid) {
- ffi::wgpu_render_bundle_draw(mEncoder.get(), aVertexCount, aInstanceCount,
- aFirstVertex, aFirstInstance);
+ if (!mValid) {
+ return;
}
+ ffi::wgpu_render_bundle_draw(mEncoder.get(), aVertexCount, aInstanceCount,
+ aFirstVertex, aFirstInstance);
}
void RenderBundleEncoder::DrawIndexed(uint32_t aIndexCount,
uint32_t aInstanceCount,
uint32_t aFirstIndex, int32_t aBaseVertex,
uint32_t aFirstInstance) {
- if (mValid) {
- ffi::wgpu_render_bundle_draw_indexed(mEncoder.get(), aIndexCount,
- aInstanceCount, aFirstIndex,
- aBaseVertex, aFirstInstance);
+ if (!mValid) {
+ return;
}
+ ffi::wgpu_render_bundle_draw_indexed(mEncoder.get(), aIndexCount,
+ aInstanceCount, aFirstIndex, aBaseVertex,
+ aFirstInstance);
}
void RenderBundleEncoder::DrawIndirect(const Buffer& aIndirectBuffer,
uint64_t aIndirectOffset) {
- if (mValid) {
- ffi::wgpu_render_bundle_draw_indirect(mEncoder.get(), aIndirectBuffer.mId,
- aIndirectOffset);
+ if (!mValid) {
+ return;
}
+ ffi::wgpu_render_bundle_draw_indirect(mEncoder.get(), aIndirectBuffer.mId,
+ aIndirectOffset);
}
void RenderBundleEncoder::DrawIndexedIndirect(const Buffer& aIndirectBuffer,
uint64_t aIndirectOffset) {
- if (mValid) {
- ffi::wgpu_render_bundle_draw_indexed_indirect(
- mEncoder.get(), aIndirectBuffer.mId, aIndirectOffset);
+ if (!mValid) {
+ return;
}
+ ffi::wgpu_render_bundle_draw_indexed_indirect(
+ mEncoder.get(), aIndirectBuffer.mId, aIndirectOffset);
}
void RenderBundleEncoder::PushDebugGroup(const nsAString& aString) {
- if (mValid) {
- const NS_ConvertUTF16toUTF8 utf8(aString);
- ffi::wgpu_render_bundle_push_debug_group(mEncoder.get(), utf8.get());
+ if (!mValid) {
+ return;
}
+ const NS_ConvertUTF16toUTF8 utf8(aString);
+ ffi::wgpu_render_bundle_push_debug_group(mEncoder.get(), utf8.get());
}
void RenderBundleEncoder::PopDebugGroup() {
- if (mValid) {
- ffi::wgpu_render_bundle_pop_debug_group(mEncoder.get());
+ if (!mValid) {
+ return;
}
+ ffi::wgpu_render_bundle_pop_debug_group(mEncoder.get());
}
void RenderBundleEncoder::InsertDebugMarker(const nsAString& aString) {
- if (mValid) {
- const NS_ConvertUTF16toUTF8 utf8(aString);
- ffi::wgpu_render_bundle_insert_debug_marker(mEncoder.get(), utf8.get());
+ if (!mValid) {
+ return;
}
+ const NS_ConvertUTF16toUTF8 utf8(aString);
+ ffi::wgpu_render_bundle_insert_debug_marker(mEncoder.get(), utf8.get());
}
already_AddRefed<RenderBundle> RenderBundleEncoder::Finish(
@@ -189,11 +201,8 @@ already_AddRefed<RenderBundle> RenderBundleEncoder::Finish(
ipc::ByteBuf bb;
RawId id;
if (mValid) {
- mValid = false;
-
- auto* encoder = mEncoder.release();
- id = ffi::wgpu_client_create_render_bundle(bridge->GetClient(), encoder,
- deviceId, &desc, ToFFI(&bb));
+ id = ffi::wgpu_client_create_render_bundle(
+ bridge->GetClient(), mEncoder.get(), deviceId, &desc, ToFFI(&bb));
} else {
id = ffi::wgpu_client_create_render_bundle_error(
@@ -204,6 +213,8 @@ already_AddRefed<RenderBundle> RenderBundleEncoder::Finish(
bridge->SendDeviceAction(deviceId, std::move(bb));
}
+ Cleanup();
+
RefPtr<RenderBundle> bundle = new RenderBundle(mParent, id);
return bundle.forget();
}
diff --git a/dom/webgpu/RenderBundleEncoder.h b/dom/webgpu/RenderBundleEncoder.h
index d21a26b833..f8619953b8 100644
--- a/dom/webgpu/RenderBundleEncoder.h
+++ b/dom/webgpu/RenderBundleEncoder.h
@@ -33,7 +33,9 @@ class RenderBundleEncoder final : public ObjectBase, public ChildOf<Device> {
~RenderBundleEncoder();
void Cleanup();
- std::unique_ptr<ffi::WGPURenderBundleEncoder, ffiWGPURenderBundleEncoderDeleter> mEncoder;
+ std::unique_ptr<ffi::WGPURenderBundleEncoder,
+ ffiWGPURenderBundleEncoderDeleter>
+ mEncoder;
// keep all the used objects alive while the encoder is finished
nsTArray<RefPtr<const BindGroup>> mUsedBindGroups;
nsTArray<RefPtr<const Buffer>> mUsedBuffers;
diff --git a/dom/webgpu/RenderPassEncoder.cpp b/dom/webgpu/RenderPassEncoder.cpp
index 03c16ea3a4..f658df1b8b 100644
--- a/dom/webgpu/RenderPassEncoder.cpp
+++ b/dom/webgpu/RenderPassEncoder.cpp
@@ -157,8 +157,8 @@ ffi::WGPURecordedRenderPass* BeginRenderPass(
RenderPassEncoder::RenderPassEncoder(CommandEncoder* const aParent,
const dom::GPURenderPassDescriptor& aDesc)
: ChildOf(aParent), mPass(BeginRenderPass(aParent, aDesc)) {
- if (!mPass) {
- mValid = false;
+ mValid = !!mPass;
+ if (!mValid) {
return;
}
@@ -171,149 +171,165 @@ RenderPassEncoder::RenderPassEncoder(CommandEncoder* const aParent,
}
}
-RenderPassEncoder::~RenderPassEncoder() {
+RenderPassEncoder::~RenderPassEncoder() { Cleanup(); }
+
+void RenderPassEncoder::Cleanup() {
if (mValid) {
- mValid = false;
+ End();
}
}
void RenderPassEncoder::SetBindGroup(
uint32_t aSlot, const BindGroup& aBindGroup,
const dom::Sequence<uint32_t>& aDynamicOffsets) {
- if (mValid) {
- mUsedBindGroups.AppendElement(&aBindGroup);
- ffi::wgpu_recorded_render_pass_set_bind_group(
- mPass.get(), aSlot, aBindGroup.mId, aDynamicOffsets.Elements(),
- aDynamicOffsets.Length());
+ if (!mValid) {
+ return;
}
+ mUsedBindGroups.AppendElement(&aBindGroup);
+ ffi::wgpu_recorded_render_pass_set_bind_group(
+ mPass.get(), aSlot, aBindGroup.mId, aDynamicOffsets.Elements(),
+ aDynamicOffsets.Length());
}
void RenderPassEncoder::SetPipeline(const RenderPipeline& aPipeline) {
- if (mValid) {
- mUsedPipelines.AppendElement(&aPipeline);
- ffi::wgpu_recorded_render_pass_set_pipeline(mPass.get(), aPipeline.mId);
+ if (!mValid) {
+ return;
}
+ mUsedPipelines.AppendElement(&aPipeline);
+ ffi::wgpu_recorded_render_pass_set_pipeline(mPass.get(), aPipeline.mId);
}
void RenderPassEncoder::SetIndexBuffer(const Buffer& aBuffer,
const dom::GPUIndexFormat& aIndexFormat,
uint64_t aOffset, uint64_t aSize) {
- if (mValid) {
- mUsedBuffers.AppendElement(&aBuffer);
- const auto iformat = aIndexFormat == dom::GPUIndexFormat::Uint32
- ? ffi::WGPUIndexFormat_Uint32
- : ffi::WGPUIndexFormat_Uint16;
- ffi::wgpu_recorded_render_pass_set_index_buffer(mPass.get(), aBuffer.mId,
- iformat, aOffset, aSize);
+ if (!mValid) {
+ return;
}
+ mUsedBuffers.AppendElement(&aBuffer);
+ const auto iformat = aIndexFormat == dom::GPUIndexFormat::Uint32
+ ? ffi::WGPUIndexFormat_Uint32
+ : ffi::WGPUIndexFormat_Uint16;
+ ffi::wgpu_recorded_render_pass_set_index_buffer(mPass.get(), aBuffer.mId,
+ iformat, aOffset, aSize);
}
void RenderPassEncoder::SetVertexBuffer(uint32_t aSlot, const Buffer& aBuffer,
uint64_t aOffset, uint64_t aSize) {
- if (mValid) {
- mUsedBuffers.AppendElement(&aBuffer);
- ffi::wgpu_recorded_render_pass_set_vertex_buffer(
- mPass.get(), aSlot, aBuffer.mId, aOffset, aSize);
+ if (!mValid) {
+ return;
}
+ mUsedBuffers.AppendElement(&aBuffer);
+ ffi::wgpu_recorded_render_pass_set_vertex_buffer(mPass.get(), aSlot,
+ aBuffer.mId, aOffset, aSize);
}
void RenderPassEncoder::Draw(uint32_t aVertexCount, uint32_t aInstanceCount,
uint32_t aFirstVertex, uint32_t aFirstInstance) {
- if (mValid) {
- ffi::wgpu_recorded_render_pass_draw(mPass.get(), aVertexCount,
- aInstanceCount, aFirstVertex,
- aFirstInstance);
+ if (!mValid) {
+ return;
}
+ ffi::wgpu_recorded_render_pass_draw(mPass.get(), aVertexCount, aInstanceCount,
+ aFirstVertex, aFirstInstance);
}
void RenderPassEncoder::DrawIndexed(uint32_t aIndexCount,
uint32_t aInstanceCount,
uint32_t aFirstIndex, int32_t aBaseVertex,
uint32_t aFirstInstance) {
- if (mValid) {
- ffi::wgpu_recorded_render_pass_draw_indexed(mPass.get(), aIndexCount,
- aInstanceCount, aFirstIndex,
- aBaseVertex, aFirstInstance);
+ if (!mValid) {
+ return;
}
+ ffi::wgpu_recorded_render_pass_draw_indexed(mPass.get(), aIndexCount,
+ aInstanceCount, aFirstIndex,
+ aBaseVertex, aFirstInstance);
}
void RenderPassEncoder::DrawIndirect(const Buffer& aIndirectBuffer,
uint64_t aIndirectOffset) {
- if (mValid) {
- ffi::wgpu_recorded_render_pass_draw_indirect(
- mPass.get(), aIndirectBuffer.mId, aIndirectOffset);
+ if (!mValid) {
+ return;
}
+ ffi::wgpu_recorded_render_pass_draw_indirect(mPass.get(), aIndirectBuffer.mId,
+ aIndirectOffset);
}
void RenderPassEncoder::DrawIndexedIndirect(const Buffer& aIndirectBuffer,
uint64_t aIndirectOffset) {
- if (mValid) {
- ffi::wgpu_recorded_render_pass_draw_indexed_indirect(
- mPass.get(), aIndirectBuffer.mId, aIndirectOffset);
+ if (!mValid) {
+ return;
}
+ ffi::wgpu_recorded_render_pass_draw_indexed_indirect(
+ mPass.get(), aIndirectBuffer.mId, aIndirectOffset);
}
void RenderPassEncoder::SetViewport(float x, float y, float width, float height,
float minDepth, float maxDepth) {
- if (mValid) {
- ffi::wgpu_recorded_render_pass_set_viewport(mPass.get(), x, y, width,
- height, minDepth, maxDepth);
+ if (!mValid) {
+ return;
}
+ ffi::wgpu_recorded_render_pass_set_viewport(mPass.get(), x, y, width, height,
+ minDepth, maxDepth);
}
void RenderPassEncoder::SetScissorRect(uint32_t x, uint32_t y, uint32_t width,
uint32_t height) {
- if (mValid) {
- ffi::wgpu_recorded_render_pass_set_scissor_rect(mPass.get(), x, y, width,
- height);
+ if (!mValid) {
+ return;
}
+ ffi::wgpu_recorded_render_pass_set_scissor_rect(mPass.get(), x, y, width,
+ height);
}
void RenderPassEncoder::SetBlendConstant(
const dom::DoubleSequenceOrGPUColorDict& color) {
- if (mValid) {
- ffi::WGPUColor aColor = ConvertColor(color);
- ffi::wgpu_recorded_render_pass_set_blend_constant(mPass.get(), &aColor);
+ if (!mValid) {
+ return;
}
+ ffi::WGPUColor aColor = ConvertColor(color);
+ ffi::wgpu_recorded_render_pass_set_blend_constant(mPass.get(), &aColor);
}
void RenderPassEncoder::SetStencilReference(uint32_t reference) {
- if (mValid) {
- ffi::wgpu_recorded_render_pass_set_stencil_reference(mPass.get(),
- reference);
+ if (!mValid) {
+ return;
}
+ ffi::wgpu_recorded_render_pass_set_stencil_reference(mPass.get(), reference);
}
void RenderPassEncoder::ExecuteBundles(
const dom::Sequence<OwningNonNull<RenderBundle>>& aBundles) {
- if (mValid) {
- nsTArray<ffi::WGPURenderBundleId> renderBundles(aBundles.Length());
- for (const auto& bundle : aBundles) {
- mUsedRenderBundles.AppendElement(bundle);
- renderBundles.AppendElement(bundle->mId);
- }
- ffi::wgpu_recorded_render_pass_execute_bundles(
- mPass.get(), renderBundles.Elements(), renderBundles.Length());
+ if (!mValid) {
+ return;
+ }
+ nsTArray<ffi::WGPURenderBundleId> renderBundles(aBundles.Length());
+ for (const auto& bundle : aBundles) {
+ mUsedRenderBundles.AppendElement(bundle);
+ renderBundles.AppendElement(bundle->mId);
}
+ ffi::wgpu_recorded_render_pass_execute_bundles(
+ mPass.get(), renderBundles.Elements(), renderBundles.Length());
}
void RenderPassEncoder::PushDebugGroup(const nsAString& aString) {
- if (mValid) {
- const NS_ConvertUTF16toUTF8 utf8(aString);
- ffi::wgpu_recorded_render_pass_push_debug_group(mPass.get(), utf8.get(), 0);
+ if (!mValid) {
+ return;
}
+ const NS_ConvertUTF16toUTF8 utf8(aString);
+ ffi::wgpu_recorded_render_pass_push_debug_group(mPass.get(), utf8.get(), 0);
}
void RenderPassEncoder::PopDebugGroup() {
- if (mValid) {
- ffi::wgpu_recorded_render_pass_pop_debug_group(mPass.get());
+ if (!mValid) {
+ return;
}
+ ffi::wgpu_recorded_render_pass_pop_debug_group(mPass.get());
}
void RenderPassEncoder::InsertDebugMarker(const nsAString& aString) {
- if (mValid) {
- const NS_ConvertUTF16toUTF8 utf8(aString);
- ffi::wgpu_recorded_render_pass_insert_debug_marker(mPass.get(), utf8.get(),
- 0);
+ if (!mValid) {
+ return;
}
+ const NS_ConvertUTF16toUTF8 utf8(aString);
+ ffi::wgpu_recorded_render_pass_insert_debug_marker(mPass.get(), utf8.get(),
+ 0);
}
void RenderPassEncoder::End() {
diff --git a/dom/webgpu/RenderPassEncoder.h b/dom/webgpu/RenderPassEncoder.h
index b6008bd013..eb96abdcd4 100644
--- a/dom/webgpu/RenderPassEncoder.h
+++ b/dom/webgpu/RenderPassEncoder.h
@@ -49,7 +49,7 @@ class RenderPassEncoder final : public ObjectBase,
protected:
virtual ~RenderPassEncoder();
- void Cleanup() {}
+ void Cleanup();
std::unique_ptr<ffi::WGPURecordedRenderPass, ffiWGPURenderPassDeleter> mPass;
// keep all the used objects alive while the pass is recorded
diff --git a/dom/webgpu/RenderPipeline.cpp b/dom/webgpu/RenderPipeline.cpp
index aa9ffcdf9a..a8a0348f9d 100644
--- a/dom/webgpu/RenderPipeline.cpp
+++ b/dom/webgpu/RenderPipeline.cpp
@@ -58,6 +58,7 @@ void RenderPipeline::Cleanup() {
already_AddRefed<BindGroupLayout> RenderPipeline::GetBindGroupLayout(
uint32_t aIndex) const {
auto bridge = mParent->GetBridge();
+ MOZ_ASSERT(bridge && bridge->CanSend());
auto* client = bridge->GetClient();
ipc::ByteBuf bb;
diff --git a/dom/webgpu/tests/cts/checkout/.github/pull_request_template.md b/dom/webgpu/tests/cts/checkout/.github/pull_request_template.md
index 7fadba0fc3..0ee7f37372 100644
--- a/dom/webgpu/tests/cts/checkout/.github/pull_request_template.md
+++ b/dom/webgpu/tests/cts/checkout/.github/pull_request_template.md
@@ -10,6 +10,7 @@ Issue: #<!-- Fill in the issue number here. See docs/intro/life_of.md -->
- [ ] All missing test coverage is tracked with "TODO" or `.unimplemented()`.
- [ ] New helpers are `/** documented */` and new helper files are found in `helper_index.txt`.
- [ ] Test behaves as expected in a WebGPU implementation. (If not passing, explain above.)
+- [ ] Test have be tested with compatibility mode validation enabled and behave as expected. (If not passing, explain above.)
**Requirements for [reviewer sign-off](https://github.com/gpuweb/cts/blob/main/docs/reviews.md):**
diff --git a/dom/webgpu/tests/cts/checkout/.github/workflows/pr.yml b/dom/webgpu/tests/cts/checkout/.github/workflows/pr.yml
index a398bf13ac..53144304bb 100644
--- a/dom/webgpu/tests/cts/checkout/.github/workflows/pr.yml
+++ b/dom/webgpu/tests/cts/checkout/.github/workflows/pr.yml
@@ -4,22 +4,31 @@ on:
pull_request:
branches: [main]
+concurrency:
+ group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
+ cancel-in-progress: true
+
jobs:
build:
runs-on: ubuntu-latest
+ timeout-minutes: 15
steps:
- uses: actions/checkout@v3
with:
persist-credentials: false
- uses: actions/setup-node@v3
with:
- node-version: "16.x"
+ node-version: '16.x'
- run: npm ci
+ - name: validating cache
+ run: npx grunt run:validate-cache
- run: npm test
- name: copy out-wpt to wpt tree
run: |
git clone --depth 2 https://github.com/web-platform-tests/wpt.git
rsync -av out-wpt/ wpt/webgpu
+ - name: adding wpt lint ignore rule for *.bin
+ run: 'echo "TRAILING WHITESPACE, INDENT TABS, CR AT EOL: *.bin" >> wpt/lint.ignore'
- name: test wpt lint
run: ./wpt lint
working-directory: ./wpt
diff --git a/dom/webgpu/tests/cts/checkout/.github/workflows/push.yml b/dom/webgpu/tests/cts/checkout/.github/workflows/push.yml
index 6aa7a34e04..f1645e120a 100644
--- a/dom/webgpu/tests/cts/checkout/.github/workflows/push.yml
+++ b/dom/webgpu/tests/cts/checkout/.github/workflows/push.yml
@@ -4,9 +4,14 @@ on:
push:
branches: [main]
+concurrency:
+ group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
+ cancel-in-progress: true
+
jobs:
build:
runs-on: ubuntu-latest
+ timeout-minutes: 15
steps:
- uses: actions/checkout@v2.3.1
with:
diff --git a/dom/webgpu/tests/cts/checkout/.gitignore b/dom/webgpu/tests/cts/checkout/.gitignore
index f115ad4f69..6a7a4a913a 100644
--- a/dom/webgpu/tests/cts/checkout/.gitignore
+++ b/dom/webgpu/tests/cts/checkout/.gitignore
@@ -2,6 +2,7 @@
.vscode/
# Build files
+/gen/
/out/
/out-wpt/
/out-node/
diff --git a/dom/webgpu/tests/cts/checkout/Gruntfile.js b/dom/webgpu/tests/cts/checkout/Gruntfile.js
index cf2207fcff..eba38704f3 100644
--- a/dom/webgpu/tests/cts/checkout/Gruntfile.js
+++ b/dom/webgpu/tests/cts/checkout/Gruntfile.js
@@ -3,6 +3,10 @@
/* eslint-disable no-console */
const timer = require('grunt-timer');
+const { spawnSync } = require('child_process');
+const path = require('path');
+
+const kAllSuites = ['webgpu', 'stress', 'manual', 'unittests', 'demo'];
module.exports = function (grunt) {
timer.init(grunt);
@@ -12,7 +16,7 @@ module.exports = function (grunt) {
pkg: grunt.file.readJSON('package.json'),
clean: {
- out: ['out/', 'out-wpt/', 'out-node/'],
+ out: ['gen/', 'out/', 'out-wpt/', 'out-node/'],
},
run: {
@@ -20,38 +24,38 @@ module.exports = function (grunt) {
cmd: 'node',
args: ['tools/gen_version'],
},
- 'generate-listings': {
- // Overwrites the listings.js files in out/. Must run before copy:out-wpt-generated;
- // must not run before run:build-out (if it is run).
+ 'generate-listings-and-webworkers': {
cmd: 'node',
- args: ['tools/gen_listings', 'out/', 'src/webgpu', 'src/stress', 'src/manual', 'src/unittests', 'src/demo'],
+ args: ['tools/gen_listings_and_webworkers', 'gen/', ...kAllSuites.map(s => 'src/' + s)],
},
validate: {
cmd: 'node',
- args: ['tools/validate', 'src/webgpu', 'src/stress', 'src/manual', 'src/unittests', 'src/demo'],
+ args: ['tools/validate', ...kAllSuites.map(s => 'src/' + s)],
+ },
+ 'generate-cache': {
+ // Note this generates files into the src/ directory (not the gen/ directory).
+ cmd: 'node',
+ args: ['tools/gen_cache', 'src/webgpu'],
},
'validate-cache': {
cmd: 'node',
- args: ['tools/gen_cache', 'out', 'src/webgpu', '--validate'],
+ args: ['tools/gen_cache', 'src/webgpu', '--validate'],
},
- 'generate-wpt-cts-html': {
+ 'write-out-wpt-cts-html': {
+ // Note this generates directly into the out-wpt/ directory rather than the gen/ directory.
cmd: 'node',
args: ['tools/gen_wpt_cts_html', 'tools/gen_wpt_cfg_unchunked.json'],
},
- 'generate-wpt-cts-html-chunked2sec': {
+ 'write-out-wpt-cts-html-chunked2sec': {
+ // Note this generates directly into the out-wpt/ directory rather than the gen/ directory.
cmd: 'node',
args: ['tools/gen_wpt_cts_html', 'tools/gen_wpt_cfg_chunked2sec.json'],
},
- 'generate-cache': {
- cmd: 'node',
- args: ['tools/gen_cache', 'out', 'src/webgpu'],
- },
unittest: {
cmd: 'node',
args: ['tools/run_node', 'unittests:*'],
},
'build-out': {
- // Must run before run:generate-listings, which will overwrite some files.
cmd: 'node',
args: [
'node_modules/@babel/cli/bin/babel',
@@ -59,6 +63,9 @@ module.exports = function (grunt) {
'--source-maps=true',
'--out-dir=out/',
'src/',
+ // These files will be generated, instead of compiled from TypeScript.
+ '--ignore=src/common/internal/version.ts',
+ '--ignore=src/*/listing.ts',
],
},
'build-out-wpt': {
@@ -75,7 +82,7 @@ module.exports = function (grunt) {
'--only=src/webgpu/',
// These files will be generated, instead of compiled from TypeScript.
'--ignore=src/common/internal/version.ts',
- '--ignore=src/webgpu/listing.ts',
+ '--ignore=src/*/listing.ts',
// These files are only used by non-WPT builds.
'--ignore=src/common/runtime/cmdline.ts',
'--ignore=src/common/runtime/server.ts',
@@ -110,6 +117,15 @@ module.exports = function (grunt) {
'--copy-files'
],
},
+ 'copy-assets-node': {
+ cmd: 'node',
+ args: [
+ 'node_modules/@babel/cli/bin/babel',
+ 'src/resources/',
+ '--out-dir=out-node/resources/',
+ '--copy-files'
+ ],
+ },
lint: {
cmd: 'node',
args: ['node_modules/eslint/bin/eslint', 'src/**/*.ts', '--max-warnings=0'],
@@ -139,34 +155,71 @@ module.exports = function (grunt) {
},
copy: {
- 'out-wpt-generated': {
+ 'gen-to-out': {
+ // Must run after generate-common and run:build-out.
+ files: [
+ { expand: true, dest: 'out/', cwd: 'gen', src: 'common/internal/version.js' },
+ { expand: true, dest: 'out/', cwd: 'gen', src: '*/**/*.js' },
+ ],
+ },
+ 'gen-to-out-wpt': {
+ // Must run after generate-common and run:build-out-wpt.
files: [
- // Must run after run:generate-version and run:generate-listings.
- { expand: true, cwd: 'out', src: 'common/internal/version.js', dest: 'out-wpt/' },
- { expand: true, cwd: 'out', src: 'webgpu/listing.js', dest: 'out-wpt/' },
+ { expand: true, dest: 'out-wpt/', cwd: 'gen', src: 'common/internal/version.js' },
+ { expand: true, dest: 'out-wpt/', cwd: 'gen', src: 'webgpu/**/*.js' },
],
},
- 'out-wpt-htmlfiles': {
+ 'htmlfiles-to-out': {
+ // Must run after run:build-out.
files: [
- { expand: true, cwd: 'src', src: 'webgpu/**/*.html', dest: 'out-wpt/' },
+ { expand: true, dest: 'out/', cwd: 'src', src: 'webgpu/**/*.html' },
+ ],
+ },
+ 'htmlfiles-to-out-wpt': {
+ // Must run after run:build-out-wpt.
+ files: [
+ { expand: true, dest: 'out-wpt/', cwd: 'src', src: 'webgpu/**/*.html' },
],
},
},
- ts: {
- check: {
- tsconfig: {
- tsconfig: 'tsconfig.json',
- passThrough: true,
- },
+ concurrent: {
+ 'write-out-wpt-cts-html-all': {
+ tasks: [
+ 'run:write-out-wpt-cts-html',
+ 'run:write-out-wpt-cts-html-chunked2sec',
+ ],
+ },
+ 'all-builds': {
+ tasks: [
+ 'build-standalone',
+ 'build-wpt',
+ 'run:build-out-node',
+ ],
+ },
+ 'all-checks': {
+ tasks: [
+ 'ts-check',
+ 'run:validate',
+ 'run:validate-cache',
+ 'run:unittest',
+ 'run:lint',
+ 'run:tsdoc-treatWarningsAsErrors',
+ ],
+ },
+ 'all-builds-and-checks': {
+ tasks: [
+ 'build-all', // Internally concurrent
+ 'concurrent:all-checks',
+ ],
},
},
});
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-copy');
+ grunt.loadNpmTasks('grunt-concurrent');
grunt.loadNpmTasks('grunt-run');
- grunt.loadNpmTasks('grunt-ts');
const helpMessageTasks = [];
function registerTaskAndAddToHelp(name, desc, deps) {
@@ -177,71 +230,96 @@ module.exports = function (grunt) {
helpMessageTasks.push({ name, desc });
}
- grunt.registerTask('build-standalone', 'Build out/ (no listings, no checks, no WPT)', [
+ grunt.registerTask('ts-check', function() {
+ spawnSync(path.join('node_modules', '.bin', 'tsc'), [
+ '--project',
+ 'tsconfig.json',
+ '--noEmit',
+ ], {
+ shell: true,
+ stdio: 'inherit',
+ });
+ });
+
+ grunt.registerTask('generate-common', 'Generate files into gen/ and src/', [
+ 'run:generate-version',
+ 'run:generate-listings-and-webworkers',
+ 'run:generate-cache',
+ ]);
+ grunt.registerTask('build-standalone', 'Build out/ (no checks; run after generate-common)', [
'run:build-out',
'run:copy-assets',
- 'run:generate-version',
+ 'copy:gen-to-out',
+ 'copy:htmlfiles-to-out',
]);
- grunt.registerTask('build-wpt', 'Build out-wpt/ (no checks; run after generate-listings)', [
+ grunt.registerTask('build-wpt', 'Build out-wpt/ (no checks; run after generate-common)', [
'run:build-out-wpt',
'run:copy-assets-wpt',
+ 'copy:gen-to-out-wpt',
+ 'copy:htmlfiles-to-out-wpt',
+ 'concurrent:write-out-wpt-cts-html-all',
'run:autoformat-out-wpt',
- 'run:generate-version',
- 'copy:out-wpt-generated',
- 'copy:out-wpt-htmlfiles',
- 'run:generate-wpt-cts-html',
- 'run:generate-wpt-cts-html-chunked2sec',
+ ]);
+ grunt.registerTask('build-node', 'Build out-node/ (no checks; run after generate-common)', [
+ 'run:build-out-node',
+ 'run:copy-assets-node',
+ ]);
+ grunt.registerTask('build-all', 'Build out*/ (no checks; run after generate-common)', [
+ 'concurrent:all-builds',
+ 'build-done-message',
]);
grunt.registerTask('build-done-message', () => {
- process.stderr.write('\nBuild completed! Running checks/tests');
+ grunt.log.writeln(`\
+=====================================================
+==== Build completed! Continuing checks/tests... ====
+=====================================================`);
});
- registerTaskAndAddToHelp('pre', 'Run all presubmit checks: standalone+wpt+typecheck+unittest+lint', [
+ grunt.registerTask('pre', ['all']);
+
+ registerTaskAndAddToHelp('all', 'Run all builds and checks', [
'clean',
- 'run:validate',
- 'run:validate-cache',
- 'build-standalone',
- 'run:generate-listings',
- 'build-wpt',
- 'run:build-out-node',
- 'build-done-message',
- 'ts:check',
- 'run:unittest',
- 'run:lint',
- 'run:tsdoc-treatWarningsAsErrors',
+ 'generate-common',
+ 'concurrent:all-builds-and-checks',
]);
- registerTaskAndAddToHelp('standalone', 'Build standalone and typecheck', [
+ registerTaskAndAddToHelp('standalone', 'Build standalone (out/) (no checks)', [
+ 'generate-common',
'build-standalone',
- 'run:generate-listings',
'build-done-message',
- 'run:validate',
- 'ts:check',
]);
- registerTaskAndAddToHelp('wpt', 'Build for WPT and typecheck', [
- 'run:generate-listings',
+ registerTaskAndAddToHelp('wpt', 'Build for WPT (out-wpt/) (no checks)', [
+ 'generate-common',
'build-wpt',
'build-done-message',
- 'run:validate',
- 'ts:check',
]);
- registerTaskAndAddToHelp('unittest', 'Build standalone, typecheck, and unittest', [
- 'standalone',
+ registerTaskAndAddToHelp('node', 'Build node (out-node/) (no checks)', [
+ 'generate-common',
+ 'build-node',
+ 'build-done-message',
+ ]);
+ registerTaskAndAddToHelp('checks', 'Run all checks (and build tsdoc)', [
+ 'concurrent:all-checks',
+ ]);
+ registerTaskAndAddToHelp('unittest', 'Just run unittests', [
'run:unittest',
]);
- registerTaskAndAddToHelp('check', 'Just typecheck', [
- 'ts:check',
+ registerTaskAndAddToHelp('typecheck', 'Just typecheck', [
+ 'ts-check',
+ ]);
+ registerTaskAndAddToHelp('tsdoc', 'Just build tsdoc', [
+ 'run:tsdoc',
]);
- registerTaskAndAddToHelp('serve', 'Serve out/ on 127.0.0.1:8080 (does NOT compile source)', ['run:serve']);
+ registerTaskAndAddToHelp('serve', 'Serve out/ (without building anything)', ['run:serve']);
registerTaskAndAddToHelp('fix', 'Fix lint and formatting', ['run:fix']);
- addExistingTaskToHelp('clean', 'Clean out/ and out-wpt/');
+ addExistingTaskToHelp('clean', 'Delete built and generated files');
grunt.registerTask('default', '', () => {
- console.error('\nAvailable tasks (see grunt --help for info):');
+ console.error('\nRecommended tasks:');
+ let nameColumnSize = Math.max(...helpMessageTasks.map(({ name }) => name.length));
for (const { name, desc } of helpMessageTasks) {
- console.error(`$ grunt ${name}`);
- console.error(` ${desc}`);
+ console.error(`$ grunt ${name.padEnd(nameColumnSize)} # ${desc}`);
}
});
};
diff --git a/dom/webgpu/tests/cts/checkout/cts.code-workspace b/dom/webgpu/tests/cts/checkout/cts.code-workspace
index 9c7320ce4b..d27f9ccc83 100644
--- a/dom/webgpu/tests/cts/checkout/cts.code-workspace
+++ b/dom/webgpu/tests/cts/checkout/cts.code-workspace
@@ -10,6 +10,11 @@
"path": "src/webgpu"
}
],
+ "extensions": {
+ "recommendations": [
+ "esbenp.prettier-vscode"
+ ]
+ },
"settings": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.detectIndentation": false,
diff --git a/dom/webgpu/tests/cts/checkout/docs/adding_timing_metadata.md b/dom/webgpu/tests/cts/checkout/docs/adding_timing_metadata.md
index fe32cead20..e251524177 100644
--- a/dom/webgpu/tests/cts/checkout/docs/adding_timing_metadata.md
+++ b/dom/webgpu/tests/cts/checkout/docs/adding_timing_metadata.md
@@ -6,14 +6,22 @@
The raw data may be edited manually, to add entries or change timing values.
-The **list** of tests must stay up to date, so it can be used by external
-tools. This is verified by presubmit checks.
+The list of tests in this file is **not** guaranteed to stay up to date.
+Use the generated `gen/*_variant_list*.json` if you need a complete list.
-The `subcaseMS` values are estimates. They can be set to 0 if for some reason
+The `subcaseMS` values are estimates. They can be set to 0 or omitted if for some reason
you can't estimate the time (or there's an existing test with a long name and
-slow subcases that would result in query strings that are too long), but this
-will produce a non-fatal warning. Avoid creating new warnings whenever
-possible. Any existing failures should be fixed (eventually).
+slow subcases that would result in query strings that are too long).
+It's OK if the number is estimated too high.
+
+These entries are estimates for the amount of time that subcases take to run,
+and are used as inputs into the WPT tooling to attempt to portion out tests into
+approximately same-sized chunks. High estimates are OK, they just may generate
+more chunks than necessary.
+
+To check for missing or 0 entries, run
+`tools/validate --print-metadata-warnings src/webgpu`
+and look at the resulting warnings.
### Performance
@@ -25,46 +33,25 @@ should still execute in under 5 seconds on lower-end computers.
## Problem
-When adding new tests to the CTS you may occasionally see an error like this
+When renaming or removing tests from the CTS you will see an error like this
when running `npm test` or `npm run standalone`:
```
-ERROR: Tests missing from listing_meta.json. Please add the new tests (set subcaseMS to 0 if you cannot estimate it):
- webgpu:shader,execution,expression,binary,af_matrix_addition:matrix:*
-
-/home/runner/work/cts/cts/src/common/util/util.ts:38
- throw new Error(msg && (typeof msg === 'string' ? msg : msg()));
- ^
-Error:
- at assert (/home/runner/work/cts/cts/src/common/util/util.ts:38:11)
- at crawl (/home/runner/work/cts/cts/src/common/tools/crawl.ts:155:11)
-Warning: non-zero exit code 1
- Use --force to continue.
-
-Aborted due to warnings.
+ERROR: Non-existent tests found in listing_meta.json. Please update:
+ webgpu:api,operation,adapter,requestAdapter:old_test_that_got_renamed:*
```
-What this error message is trying to tell us, is that there is no entry for
-`webgpu:shader,execution,expression,binary,af_matrix_addition:matrix:*` in
-`src/webgpu/listing_meta.json`.
+## Solution
-These entries are estimates for the amount of time that subcases take to run,
-and are used as inputs into the WPT tooling to attempt to portion out tests into
-approximately same-sized chunks.
+This means there is a stale line in `src/webgpu/listing_meta.json` that needs
+to be deleted, or updated to match the rename that you did.
-If a value has been defaulted to 0 by someone, you will see warnings like this:
-
-```
-...
-WARNING: subcaseMS≤0 found in listing_meta.json (allowed, but try to avoid):
- webgpu:shader,execution,expression,binary,af_matrix_addition:matrix:*
-...
-```
+## Problem
-These messages should be resolved by adding appropriate entries to the JSON
-file.
+You run `tools/validate --print-metadata-warnings src/webgpu`
+and want to fix the warnings.
-## Solution 1 (manual, best for simple tests)
+## Solution 1 (manual, best for one-off updates of simple tests)
If you're developing new tests and need to update this file, it is sometimes
easiest to do so manually. Run your tests under your usual development workflow
@@ -82,30 +69,18 @@ these values, though they do require some manual intervention. The rest of this
doc will be a walkthrough of running these tools.
Timing data can be captured in bulk and "merged" into this file using
-the `merge_listing_times` tool. This is useful when a large number of tests
+the `merge_listing_times` tool. This is
+This is useful when a large number of tests
change or otherwise a lot of tests need to be updated, but it also automates the
manual steps above.
The tool can also be used without any inputs to reformat `listing_meta.json`.
Please read the help message of `merge_listing_times` for more information.
-### Placeholder Value
-
-If your development workflow requires a clean build, the first step is to add a
-placeholder value for entry to `src/webgpu/listing_meta.json`, since there is a
-chicken-and-egg problem for updating these values.
-
-```
- "webgpu:shader,execution,expression,binary,af_matrix_addition:matrix:*": { "subcaseMS": 0 },
-```
-
-(It should have a value of 0, since later tooling updates the value if the newer
-value is higher.)
-
### Websocket Logger
The first tool that needs to be run is `websocket-logger`, which receives data
-on a WebSocket channel to capture timing data when CTS is run. This
+on a WebSocket channel at `localhost:59497` to capture timing data when CTS is run. This
should be run in a separate process/terminal, since it needs to stay running
throughout the following steps.
@@ -125,10 +100,19 @@ Writing to wslog-2023-09-12T18-57-34.txt
...
```
+See also [tools/websocket-logger/README.md](../tools/websocket-logger/README.md).
+
### Running CTS
Now we need to run the specific cases in CTS that we need to time.
-This should be possible under any development workflow (as long as its runtime environment, like Node, supports WebSockets), but the most well-tested way is using the standalone web runner.
+
+This should be possible under any development workflow by logging through a
+side-channel (as long as its runtime environment, like Node, supports WebSockets).
+Regardless of development workflow, you need to enable logToWebSocket flag
+(`?log_to_web_socket=1` in browser, `--log-to-web-socket` on command line, or
+just hack it in by switching the default in `options.ts`).
+
+The most well-tested way to do this is using the standalone web runner.
This requires serving the CTS locally. In the project root:
@@ -141,7 +125,7 @@ Once this is started you can then direct a WebGPU enabled browser to the
specific CTS entry and run the tests, for example:
```
-http://localhost:8080/standalone/?q=webgpu:shader,execution,expression,binary,af_matrix_addition:matrix:*
+http://localhost:8080/standalone/?log_to_web_socket=1&q=webgpu:*
```
If the tests have a high variance in runtime, you can run them multiple times.
@@ -156,8 +140,9 @@ This can be done using the following command:
```
tools/merge_listing_times webgpu -- tools/websocket-logger/wslog-2023-09-12T18-57-34.txt
+tools/merge_listing_times webgpu -- tools/websocket-logger/wslog-*.txt
```
-where the text file is the result file from websocket-logger.
+Or, you can point it to one of the log files from a specific invocation of websocket-logger.
Now you just need to commit the pending diff in your repo.
diff --git a/dom/webgpu/tests/cts/checkout/docs/build.md b/dom/webgpu/tests/cts/checkout/docs/build.md
index 2d7b2f968c..d786bbc18c 100644
--- a/dom/webgpu/tests/cts/checkout/docs/build.md
+++ b/dom/webgpu/tests/cts/checkout/docs/build.md
@@ -1,16 +1,48 @@
# Building
Building the project is not usually needed for local development.
-However, for exports to WPT, or deployment (https://gpuweb.github.io/cts/),
+However, for exports to WPT, NodeJS, [or deployment](https://gpuweb.github.io/cts/),
files can be pre-generated.
-The project builds into two directories:
+## Build types
-- `out/`: Built framework and test files, needed to run standalone or command line.
-- `out-wpt/`: Build directory for export into WPT. Contains:
- - An adapter for running WebGPU CTS tests under WPT
- - A copy of the needed files from `out/`
- - A copy of any `.html` test cases from `src/`
+The project can be built several different ways, each with a different output directory:
+
+### 0. on-the-fly builds (no output directory)
+
+Use `npm run start` to launch a server that live-compiles everything as needed.
+Use `tools/run_node` and other tools to run under `ts-node` which compiles at runtime.
+
+### 1. `out` directory
+
+**Built with**: `npm run standalone`
+
+**Serve locally with**: `npx grunt serve`
+
+**Used for**: Static deployment of the CTS, primarily for [gpuweb.github.io/cts](https://gpuweb.github.io/cts/).
+
+### 2. `out-wpt` directory
+
+**Built with**: `npm run wpt`
+
+**Used for**: Deploying into [Web Platform Tests](https://web-platform-tests.org/). See [below](#export-to-wpt) for more information.
+
+Contains:
+
+- An adapter for running WebGPU CTS tests under WPT
+- A copy of the needed files from `out/`
+- A copy of any `.html` test cases from `src/`
+
+### 3. `out-node` directory
+
+**Built with**: `npm run node`
+
+**Used for**: Running NodeJS tools, if you want to specifically avoid the live-compilation overhead of the `tools/` versions, or are running on a deployment which no longer has access to `ts-node` (which is a build-time dependency). For example:
+
+- `node out-node/common/runtime/cmdline.js` ([source](../src/common/runtime/cmdline.ts)) - A command line interface test runner
+- `node out-node/common/runtime/server.js` ([source](../src/common/runtime/server.ts)) - An HTTP server for executing CTS tests with a REST interface
+
+## Testing
To build and run all pre-submit checks (including type and lint checks and
unittests), use:
@@ -25,15 +57,6 @@ For checks only:
npm run check
```
-For a quicker iterative build:
-
-```sh
-npm run standalone
-```
-
-## Run
-
-To serve the built files (rather than using the dev server), run `npx grunt serve`.
## Export to WPT
diff --git a/dom/webgpu/tests/cts/checkout/docs/case_cache.md b/dom/webgpu/tests/cts/checkout/docs/case_cache.md
new file mode 100644
index 0000000000..c3ba8718b5
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/docs/case_cache.md
@@ -0,0 +1,81 @@
+# Case Cache
+
+The WebGPU CTS contains many tests that check that the results of an operation
+fall within limits defined by the WebGPU and WGSL specifications. The
+computation of these allowed limits can be very expensive to calculate, however
+the values do not vary by platform or device, and can be precomputed and reused
+for multiple CTS runs.
+
+## File cache
+
+To speed up execution of the CTS, the CTS git repo holds holds pre-computed
+test cases, generated from `*.cache.ts` files and serialized in a set of binary
+files under [`src/resources/cache`](../src/resources/cache).
+
+These files are regenerated by [`src/common/tools/gen_cache.ts`](../src/common/tools/gen_cache.ts)
+which can be run with `npx grunt run:generate-cache`.
+This tool is automatically run by the various Grunt build commands.
+
+As generating the cache is expensive (hence why we build it ahead of time!) the
+cache generation tool will only re-build the cache files it believes may be out
+of date. To determine which files it needs to rebuild, the tool calculates a
+hash of all the transitive source TypeScript files that are used to build the
+output, and compares this hash to the hash stored in
+[`src/resources/cache/hashes.json`](`../src/resources/cache/hashes.json`). Only
+those cache files with differing hashes are rebuilt.
+
+Transitive imports easily grow, and these can cause unnecessary rebuilds of the cache.
+To help avoid unnecessary rebuilds, files that are known to not be used by the cache can be
+annotated with a `MUST_NOT_BE_IMPORTED_BY_DATA_CACHE` comment anywhere in the file. If a file with
+this comment is transitively imported by a `.cache.ts` file, then the cache generation tool will
+error with a trace of the imports from the `.cache.ts` file to the file with this comment.
+
+The cache files are copied from [`src/resources/cache`](../src/resources/cache)
+to the `resources/cache` subdirectory of the
+[`out` and `out-node` build directories](build.md#build-types), so the runner
+can load these cache files.
+
+The GitHub presubmit checks will error if the cache files or
+[`hashes.json`](`../src/resources/cache/hashes.json`) need updating.
+
+## In memory cache
+
+If a cache file cannot be found, then the [`CaseCache`](../src/webgpu/shader/execution/expression/case_cache.ts)
+will build the cases during CTS execution and store the results in an in-memory LRU cache.
+
+## Using the cache
+
+To add test cases to the cache:
+
+1. Create a new <code><i>my_file</i>.cache.ts</code> file.
+
+2. In that file, import `makeCaseCache` from [`'case_cache.js'`](../src/webgpu/shader/execution/expression/case_cache.ts);
+
+```ts
+import { makeCaseCache } from '../case_cache.js'; // your relative path may vary
+```
+
+3. Declare an exported global variable with the name `d`, assigned with the return value of `makeCaseCache()`:
+
+```ts
+export const d = makeCaseCache('unique/path/of/your/cache/file', {
+ // Declare any number of fields that build the test cases
+ name_of_your_case: () => {
+ return fullI32Range().map(e => { // example case builder
+ return { input: i32(e), expected: i32(-e) };
+ });
+ },
+});
+```
+
+4. To use the cached cases in a <code><i>my_file</i>.spec.ts</code> file, import `d` from <code><i>my_file</i>.cache.js</code>, and use `d.get();`
+
+```ts
+import { d } from './my_file.cache.js';
+
+const cases = await d.get('name_of_your_case');
+// cases will either be loaded from the cache file, loaded from the in-memory
+// LRU, or built on the fly.
+```
+
+5. Run `npx grunt run generate-cache` to generate the new cache file.
diff --git a/dom/webgpu/tests/cts/checkout/docs/fp_primer.md b/dom/webgpu/tests/cts/checkout/docs/fp_primer.md
index a8302fb461..769657c8f8 100644
--- a/dom/webgpu/tests/cts/checkout/docs/fp_primer.md
+++ b/dom/webgpu/tests/cts/checkout/docs/fp_primer.md
@@ -39,7 +39,8 @@ A floating point number system defines
- Arithmetic operations on those representatives, trying to approximate the
ideal operations on real numbers.
-The cardinality mismatch alone implies that any floating point number system necessarily loses information.
+The cardinality mismatch alone implies that any floating point number system
+necessarily loses information.
This means that not all numbers in the bounds can be exactly represented as a
floating point value.
@@ -114,7 +115,7 @@ Implementations may assume that infinities are not present. When an evaluation
at runtime would produce an infinity, an indeterminate value is produced
instead.
-When a value goes out of bounds for a specific precision there are special
+When a value goes out-of-bounds for a specific precision there are special
rounding rules that apply. If it is 'near' the edge of finite values for that
precision, it is considered to be near-overflowing, and the implementation may
choose to round it to the edge value or the appropriate infinity. If it is not
@@ -163,7 +164,7 @@ the rules for compile time execution will be discussed below.)
Signaling NaNs are treated as quiet NaNs in the WGSL spec. And quiet NaNs have
the same "may-convert-to-indeterminate-value" behaviour that infinities have, so
-for the purpose of the CTS they are handled by the infinite/out of bounds logic
+for the purpose of the CTS they are handled by the infinite/out-of-bounds logic
normally.
## Notation/Terminology
@@ -231,14 +232,20 @@ referred to as the beginning of the interval and `b` as the end of the interval.
When talking about intervals, this doc and the code endeavours to avoid using
the term **range** to refer to the span of values that an interval covers,
-instead using the term bounds to avoid confusion of terminology around output of
-operations.
+instead using the term **endpoints** to avoid confusion of terminology around
+output of operations.
+
+The term **endpoints** is generally used to refer to the conceptual numeric
+spaces, i.e. f32 or abstract float.
+
+Thus a specific interval can have **endpoints** that are either in or out of
+bounds for a specific floating point precision.
## Accuracy
As mentioned above floating point numbers are not able to represent all the
-possible values over their bounds, but instead represent discrete values in that
-interval, and approximate the remainder.
+possible values over their range, but instead represent discrete values in that
+space, and approximate the remainder.
Additionally, floating point numbers are not evenly distributed over the real
number line, but instead are more densely clustered around zero, with the space
@@ -398,7 +405,7 @@ That would be very inefficient though and make your reviewer sad to read.
For mapping intervals to intervals the key insight is that we only need to be
concerned with the extrema of the operation in the interval, since the
-acceptance interval is the bounds of the possible outputs.
+acceptance interval is defined by largest and smallest of the possible outputs.
In more precise terms:
```
@@ -538,6 +545,65 @@ This algorithmically looks something like this:
Return division result
```
+### Out of Bounds
+When calculating inherited intervals, if a intermediate calculation goes out of
+bounds this will flow through to later calculations, even if a later calculation
+would pull the result back inbounds.
+
+For example, `fp.positive.max + fp.positive.max - fp.positive.max` could be
+simplified to just `fp.positive.max` before execution, but it would also be
+valid for an implementation to naively perform left to right evaluation. Thus
+the addition would produce an intermediate value of `2 * fp.positive.max`. Again
+the implementation may hoist intermediate calculation to a higher precision to
+avoid overflow here, but is not required to. So a conforming implementation at
+this point may just return any value since the calculation when out of bounds.
+Thus the execution tests in the CTS should accept any value returned, so the
+case is just effectively confirming the computation completes.
+
+When looking at validation tests there is some subtleties about out of bounds
+behaviour, specifically how far out of bounds the value went that will influence
+the expected results, which will be discussed in more detail below.
+
+#### Vectors and Matrices
+The above discussion about inheritance of out of bounds intervals assumed scalar
+calculations, so all the result intervals were dependent on the input intervals,
+so if an out-of-bounds input occurred naturally all the output values were
+effectively out of bounds.
+
+For vector and matrix operations, this is not always true. Operations on these
+data structures can either define an element-wise mapping, where for each output
+element the result is calculated by executing a scalar operation on a input
+element (sometimes referred to as component-wise), or where the operation is
+defined such the output elements are dependent on the entire input.
+
+For concrete examples, constant scaling (`c * vec` of `c * mat`) is an
+element-wise operation, because one can define a simple mapping
+`o[i]` = `c * i[i]`, where the ith output only depends on the ith input.
+
+A non-element-wise operation would be something like cross product of vectors
+or the determinant of a matrix, where each output element is dependent on
+multiple input elements.
+
+For component-wise operations, out of bounds-ness flows through per element,
+i.e. if the ith input element was considered to be have gone out of bounds, then
+the ith output is considered to have too also regardless of the operation
+performed. Thus an input may be a mixture of out of bounds elements & inbounds
+elements, and produce another mixture, assuming the operation being performed
+itself does not push elements out of bounds.
+
+For non-component-wise operations, out of bounds-ness flows through the entire
+operation, i.e. if any of the input elements is out of bounds, then all the
+output elements are considered to be out of bounds. Additionally, if the
+calculation for any of the elements in output goes out of bounds, then the
+entire output is considered to have gone out of bounds, even if other individual
+elements stayed inbounds.
+
+For some non-element-wise operations one could define mappings for individual
+output elements that do not depend on all the input elements and consider only
+if those inputs that are used, but for the purposes of WGSL and the CTS, OOB
+inheritance is not so finely defined as to consider the difference between using
+some and all the input elements for non-element-wise operations.
+
## Compile vs Run Time Evaluation
The above discussions have been primarily agnostic to when and where a
@@ -553,14 +619,14 @@ These are compile vs run time, and CPU vs GPU. Broadly speaking compile time
execution happens on the host CPU, and run time evaluation occurs on a dedicated
GPU.
-(Software graphics implementations like WARP and SwiftShader technically break this by
-being a software emulation of a GPU that runs on the CPU, but conceptually one can
-think of these implementations being a type of GPU in this context, since it has
-similar constraints when it comes to precision, etc.)
+(Software graphics implementations like WARP and SwiftShader technically break
+this by being a software emulation of a GPU that runs on the CPU, but
+conceptually one can think of these implementations being a type of GPU in this
+context, since it has similar constraints when it comes to precision, etc.)
Compile time evaluation is execution that occurs when setting up a shader
module, i.e. when compiling WGSL to a platform specific shading language. It is
-part of resolving values for things like constants, and occurs once before the
+part of resolving values for things like constants, and occurs once, before the
shader is run by the caller. It includes constant evaluation and override
evaluation. All AbstractFloat operations are compile time evaluated.
@@ -623,7 +689,7 @@ near-overflow vs far-overflow behaviour. Thankfully this can be broken down into
a case by case basis based on where an interval falls.
Assuming `X`, is the well-defined result of an operation, i.e. not indeterminate
-due to the operation isn't defined for the inputs:
+due to the operation not being defined for the inputs:
| Region | | Result |
|------------------------------|------------------------------------------------------|--------------------------------|
@@ -643,7 +709,9 @@ behaviour in this region as rigorously defined nor tested, so fully testing
here would likely find lots of issues that would just need to be mitigated in
the CTS.
-Currently, we choose to avoid testing validation of near-overflow scenarios.
+Currently, we have chosen to not test validation of near-overflow scenarios to
+avoid this complexity. If this becomes a significant source of bugs and/or
+incompatibility between implementations this can be revisited in the future.
### Additional Technical Limitations
@@ -652,7 +720,7 @@ the theoretical world that the intervals being used for testing are infinitely
precise, when in actuality they are implemented by the ECMAScript `number` type,
which is implemented as a f64 value.
-For the vast majority of cases, even out of bounds and overflow, this is
+For the vast majority of cases, even out-of-bounds and overflow, this is
sufficient. There is one small slice where this breaks down. Specifically if
the result just outside the finite range by less than 1 f64 ULP of the edge
value. An example of this is `2 ** -11 + f32.max`. This will be between `f32.max`
@@ -752,7 +820,7 @@ const_assert upper > foo(x) // Result was above the acceptance interval
```
where lower and upper would actually be string replaced with literals for the
-bounds of the acceptance interval when generating the shader text.
+endpoints of the acceptance interval when generating the shader text.
This approach has a number of limitations that made it unacceptable for the CTS.
First, how errors are reported is a pain to debug. Someone working with the CTS
diff --git a/dom/webgpu/tests/cts/checkout/docs/intro/developing.md b/dom/webgpu/tests/cts/checkout/docs/intro/developing.md
index 5b1aeed36d..0016c2c048 100644
--- a/dom/webgpu/tests/cts/checkout/docs/intro/developing.md
+++ b/dom/webgpu/tests/cts/checkout/docs/intro/developing.md
@@ -34,6 +34,11 @@ the standalone runner.)
Note: The first load of a test suite may take some time as generating the test suite listing can
take a few seconds.
+## Documentation
+
+In addition to the documentation pages you're reading, there is TSDoc documentation.
+Start at the [helper index](https://gpuweb.github.io/cts/docs/tsdoc/).
+
## Standalone Test Runner / Test Plan Viewer
**The standalone test runner also serves as a test plan viewer.**
@@ -43,7 +48,7 @@ You can use this to preview how your test plan will appear.
You can view different suites (webgpu, unittests, stress, etc.) or different subtrees of
the test suite.
-- `http://localhost:8080/standalone/` (defaults to `?runnow=0&worker=0&debug=0&q=webgpu:*`)
+- `http://localhost:8080/standalone/` (defaults to `?runnow=0&debug=0&q=webgpu:*`)
- `http://localhost:8080/standalone/?q=unittests:*`
- `http://localhost:8080/standalone/?q=unittests:basic:*`
@@ -51,7 +56,9 @@ The following url parameters change how the harness runs:
- `runnow=1` runs all matching tests on page load.
- `debug=1` enables verbose debug logging from tests.
-- `worker=1` runs the tests on a Web Worker instead of the main thread.
+- `worker=dedicated` (or `worker` or `worker=1`) runs the tests on a dedicated worker instead of the main thread.
+- `worker=shared` runs the tests on a shared worker instead of the main thread.
+- `worker=service` runs the tests on a service worker instead of the main thread.
- `power_preference=low-power` runs most tests passing `powerPreference: low-power` to `requestAdapter`
- `power_preference=high-performance` runs most tests passing `powerPreference: high-performance` to `requestAdapter`
@@ -112,15 +119,17 @@ Opening a pull request will automatically notify reviewers.
To make the review process smoother, once a reviewer has started looking at your change:
- Avoid major additions or changes that would be best done in a follow-up PR.
-- Avoid rebases (`git rebase`) and force pushes (`git push -f`). These can make
- it difficult for reviewers to review incremental changes as GitHub often cannot
+- Avoid deleting commits that have already been reviewed, which occurs when using
+ rebases (`git rebase`) and force pushes (`git push -f`). These can make
+ it difficult for reviewers to review incremental changes as GitHub usually cannot
view a useful diff across a rebase. If it's necessary to resolve conflicts
with upstream changes, use a merge commit (`git merge`) and don't include any
- consequential changes in the merge, so a reviewer can skip over merge commits
+ unnecessary changes in the merge, so that a reviewer can skip over merge commits
when working through the individual commits in the PR.
-- When you address a review comment, mark the thread as "Resolved".
-Pull requests will (usually) be landed with the "Squash and merge" option.
+ The "Create a merge commit" merge option is disabled, so `main` history always
+ remains linear (no merge commits). PRs are usually landed using "Squash and merge".
+- When you address a review comment, mark the thread as "Resolved".
### TODOs
diff --git a/dom/webgpu/tests/cts/checkout/docs/terms.md b/dom/webgpu/tests/cts/checkout/docs/terms.md
index 032639be57..0dc6f0ca17 100644
--- a/dom/webgpu/tests/cts/checkout/docs/terms.md
+++ b/dom/webgpu/tests/cts/checkout/docs/terms.md
@@ -111,7 +111,7 @@ Each Suite has one Listing File (`suite/listing.[tj]s`), containing a list of th
in the suite.
In `src/suite/listing.ts`, this is computed dynamically.
-In `out/suite/listing.js`, the listing has been pre-baked (by `tools/gen_listings`).
+In `out/suite/listing.js`, the listing has been pre-baked (by `tools/gen_listings_and_webworkers`).
**Type:** Once `import`ed, `ListingFile`
diff --git a/dom/webgpu/tests/cts/checkout/package-lock.json b/dom/webgpu/tests/cts/checkout/package-lock.json
index 361ee369cd..83dbb0a58e 100644
--- a/dom/webgpu/tests/cts/checkout/package-lock.json
+++ b/dom/webgpu/tests/cts/checkout/package-lock.json
@@ -24,7 +24,7 @@
"@types/serve-index": "^1.9.3",
"@typescript-eslint/eslint-plugin": "^6.9.1",
"@typescript-eslint/parser": "^6.9.1",
- "@webgpu/types": "^0.1.38",
+ "@webgpu/types": "^0.1.40",
"ansi-colors": "4.1.3",
"babel-plugin-add-header-comment": "^1.0.3",
"babel-plugin-const-enum": "^1.2.0",
@@ -37,11 +37,11 @@
"express": "^4.18.2",
"grunt": "^1.6.1",
"grunt-cli": "^1.4.3",
+ "grunt-concurrent": "^3.0.0",
"grunt-contrib-clean": "^2.0.1",
"grunt-contrib-copy": "^1.0.0",
"grunt-run": "^0.8.1",
"grunt-timer": "^0.6.0",
- "grunt-ts": "^6.0.0-beta.22",
"gts": "^5.2.0",
"http-server": "^14.1.1",
"morgan": "^1.10.0",
@@ -1523,9 +1523,9 @@
"dev": true
},
"node_modules/@webgpu/types": {
- "version": "0.1.38",
- "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.38.tgz",
- "integrity": "sha512-7LrhVKz2PRh+DD7+S+PVaFd5HxaWQvoMqBbsV9fNJO1pjUs1P8bM2vQVNfk+3URTqbuTI7gkXi0rfsN0IadoBA==",
+ "version": "0.1.40",
+ "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.40.tgz",
+ "integrity": "sha512-/BBkHLS6/eQjyWhY2H7Dx5DHcVrS2ICj9owvSRdgtQT6KcafLZA86tPze0xAOsd4FbsYKCUBUQyNi87q7gV7kw==",
"dev": true
},
"node_modules/abbrev": {
@@ -1672,33 +1672,6 @@
"sprintf-js": "~1.0.2"
}
},
- "node_modules/arr-diff": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
- "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/arr-flatten": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
- "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/arr-union": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
- "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/array-buffer-byte-length": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz",
@@ -1764,15 +1737,6 @@
"node": ">=8"
}
},
- "node_modules/array-unique": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
- "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/array.prototype.findlastindex": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz",
@@ -1858,39 +1822,12 @@
"node": ">=0.10.0"
}
},
- "node_modules/assign-symbols": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
- "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/async": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
"integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==",
"dev": true
},
- "node_modules/async-each": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz",
- "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==",
- "dev": true
- },
- "node_modules/atob": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
- "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
- "dev": true,
- "bin": {
- "atob": "bin/atob.js"
- },
- "engines": {
- "node": ">= 4.5.0"
- }
- },
"node_modules/available-typed-arrays": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
@@ -1929,74 +1866,6 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
- "node_modules/base": {
- "version": "0.11.2",
- "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
- "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==",
- "dev": true,
- "dependencies": {
- "cache-base": "^1.0.1",
- "class-utils": "^0.3.5",
- "component-emitter": "^1.2.1",
- "define-property": "^1.0.0",
- "isobject": "^3.0.1",
- "mixin-deep": "^1.2.0",
- "pascalcase": "^0.1.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/base/node_modules/define-property": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
- "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
- "dev": true,
- "dependencies": {
- "is-descriptor": "^1.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/base/node_modules/is-accessor-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
- "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
- "dev": true,
- "dependencies": {
- "kind-of": "^6.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/base/node_modules/is-data-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
- "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
- "dev": true,
- "dependencies": {
- "kind-of": "^6.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/base/node_modules/is-descriptor": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
- "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
- "dev": true,
- "dependencies": {
- "is-accessor-descriptor": "^1.0.0",
- "is-data-descriptor": "^1.0.0",
- "kind-of": "^6.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/bash-color": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/bash-color/-/bash-color-0.0.3.tgz",
@@ -2039,16 +1908,6 @@
"node": ">=8"
}
},
- "node_modules/bindings": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
- "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
- "dev": true,
- "optional": true,
- "dependencies": {
- "file-uri-to-path": "1.0.0"
- }
- },
"node_modules/body-parser": {
"version": "1.20.1",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
@@ -2199,26 +2058,6 @@
"node": ">= 0.8"
}
},
- "node_modules/cache-base": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
- "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==",
- "dev": true,
- "dependencies": {
- "collection-visit": "^1.0.0",
- "component-emitter": "^1.2.1",
- "get-value": "^2.0.6",
- "has-value": "^1.0.0",
- "isobject": "^3.0.1",
- "set-value": "^2.0.0",
- "to-object-path": "^0.3.0",
- "union-value": "^1.0.0",
- "unset-value": "^1.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/call-bind": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz",
@@ -2335,21 +2174,6 @@
"fsevents": "~2.3.2"
}
},
- "node_modules/class-utils": {
- "version": "0.3.6",
- "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
- "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==",
- "dev": true,
- "dependencies": {
- "arr-union": "^3.1.0",
- "define-property": "^0.2.5",
- "isobject": "^3.0.0",
- "static-extend": "^0.1.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/cli-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
@@ -2371,19 +2195,6 @@
"node": ">= 10"
}
},
- "node_modules/collection-visit": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
- "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=",
- "dev": true,
- "dependencies": {
- "map-visit": "^1.0.0",
- "object-visit": "^1.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@@ -2408,12 +2219,6 @@
"node": ">=0.1.90"
}
},
- "node_modules/component-emitter": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
- "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
- "dev": true
- },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -2482,15 +2287,6 @@
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=",
"dev": true
},
- "node_modules/copy-descriptor": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
- "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
@@ -2526,33 +2322,6 @@
"node": ">= 8"
}
},
- "node_modules/csproj2ts": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/csproj2ts/-/csproj2ts-1.1.0.tgz",
- "integrity": "sha512-sk0RTT51t4lUNQ7UfZrqjQx7q4g0m3iwNA6mvyh7gLsgQYvwKzfdyoAgicC9GqJvkoIkU0UmndV9c7VZ8pJ45Q==",
- "dev": true,
- "dependencies": {
- "es6-promise": "^4.1.1",
- "lodash": "^4.17.4",
- "semver": "^5.4.1",
- "xml2js": "^0.4.19"
- }
- },
- "node_modules/csproj2ts/node_modules/es6-promise": {
- "version": "4.2.8",
- "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
- "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==",
- "dev": true
- },
- "node_modules/csproj2ts/node_modules/semver": {
- "version": "5.7.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
- "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
- "dev": true,
- "bin": {
- "semver": "bin/semver"
- }
- },
"node_modules/d": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
@@ -2620,15 +2389,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/decode-uri-component": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
- "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
- "dev": true,
- "engines": {
- "node": ">=0.10"
- }
- },
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -2822,18 +2582,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/define-property": {
- "version": "0.2.5",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
- "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
- "dev": true,
- "dependencies": {
- "is-descriptor": "^0.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
@@ -2862,27 +2610,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/detect-indent": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz",
- "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=",
- "dev": true,
- "dependencies": {
- "repeating": "^2.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/detect-newline": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz",
- "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
@@ -2916,6 +2643,18 @@
"node": ">=6.0.0"
}
},
+ "node_modules/duplexify": {
+ "version": "3.7.1",
+ "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
+ "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==",
+ "dev": true,
+ "dependencies": {
+ "end-of-stream": "^1.0.0",
+ "inherits": "^2.0.1",
+ "readable-stream": "^2.0.0",
+ "stream-shift": "^1.0.0"
+ }
+ },
"node_modules/duration": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/duration/-/duration-0.2.2.tgz",
@@ -2953,6 +2692,15 @@
"node": ">= 0.8"
}
},
+ "node_modules/end-of-stream": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
+ "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
+ "dev": true,
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
"node_modules/error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@@ -3081,12 +2829,6 @@
"es6-symbol": "^3.1.1"
}
},
- "node_modules/es6-promise": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-0.1.2.tgz",
- "integrity": "sha1-8RLCn+paCZhTn8tqL9IUQ9KPBfc=",
- "dev": true
- },
"node_modules/es6-symbol": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz",
@@ -3701,39 +3443,6 @@
"node": ">= 0.8.0"
}
},
- "node_modules/expand-brackets": {
- "version": "2.1.4",
- "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
- "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
- "dev": true,
- "dependencies": {
- "debug": "^2.3.3",
- "define-property": "^0.2.5",
- "extend-shallow": "^2.0.1",
- "posix-character-classes": "^0.1.0",
- "regex-not": "^1.0.0",
- "snapdragon": "^0.8.1",
- "to-regex": "^3.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/expand-brackets/node_modules/debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "dev": true,
- "dependencies": {
- "ms": "2.0.0"
- }
- },
- "node_modules/expand-brackets/node_modules/ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
- "dev": true
- },
"node_modules/expand-tilde": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz",
@@ -3874,18 +3583,6 @@
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"dev": true
},
- "node_modules/extend-shallow": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
- "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
- "dev": true,
- "dependencies": {
- "is-extendable": "^0.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/external-editor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
@@ -3900,75 +3597,6 @@
"node": ">=4"
}
},
- "node_modules/extglob": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
- "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
- "dev": true,
- "dependencies": {
- "array-unique": "^0.3.2",
- "define-property": "^1.0.0",
- "expand-brackets": "^2.1.4",
- "extend-shallow": "^2.0.1",
- "fragment-cache": "^0.2.1",
- "regex-not": "^1.0.0",
- "snapdragon": "^0.8.1",
- "to-regex": "^3.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/extglob/node_modules/define-property": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
- "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
- "dev": true,
- "dependencies": {
- "is-descriptor": "^1.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/extglob/node_modules/is-accessor-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
- "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
- "dev": true,
- "dependencies": {
- "kind-of": "^6.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/extglob/node_modules/is-data-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
- "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
- "dev": true,
- "dependencies": {
- "kind-of": "^6.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/extglob/node_modules/is-descriptor": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
- "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
- "dev": true,
- "dependencies": {
- "is-accessor-descriptor": "^1.0.0",
- "is-data-descriptor": "^1.0.0",
- "kind-of": "^6.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -4051,13 +3679,6 @@
"integrity": "sha1-peeo/7+kk7Q7kju9TKiaU7Y7YSs=",
"dev": true
},
- "node_modules/file-uri-to-path": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
- "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
- "dev": true,
- "optional": true
- },
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -4258,18 +3879,6 @@
"node": ">= 0.6"
}
},
- "node_modules/fragment-cache": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
- "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=",
- "dev": true,
- "dependencies": {
- "map-cache": "^0.2.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
@@ -4402,15 +4011,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/get-value": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
- "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/getobject": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/getobject/-/getobject-1.0.2.tgz",
@@ -4565,12 +4165,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/graceful-fs": {
- "version": "4.2.9",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz",
- "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==",
- "dev": true
- },
"node_modules/graphemer": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
@@ -4636,6 +4230,33 @@
"nopt": "bin/nopt.js"
}
},
+ "node_modules/grunt-concurrent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/grunt-concurrent/-/grunt-concurrent-3.0.0.tgz",
+ "integrity": "sha512-AgXtjUJESHEGeGX8neL3nmXBTHSj1QC48ABQ3ng2/vjuSBpDD8gKcVHSlXP71pFkIR8TQHf+eomOx6OSYSgfrA==",
+ "dev": true,
+ "dependencies": {
+ "arrify": "^2.0.1",
+ "async": "^3.1.0",
+ "indent-string": "^4.0.0",
+ "pad-stream": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "peerDependencies": {
+ "grunt": ">=1"
+ }
+ },
+ "node_modules/grunt-concurrent/node_modules/arrify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz",
+ "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/grunt-contrib-clean": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-2.0.1.tgz",
@@ -4908,344 +4529,6 @@
"node": ">= 0.8.x"
}
},
- "node_modules/grunt-ts": {
- "version": "6.0.0-beta.22",
- "resolved": "https://registry.npmjs.org/grunt-ts/-/grunt-ts-6.0.0-beta.22.tgz",
- "integrity": "sha512-g9e+ZImQ7W38dfpwhp0+GUltXWidy3YGPfIA/IyGL5HMv6wmVmMMoSgscI5swhs2HSPf8yAvXAAJbwrouijoRg==",
- "dev": true,
- "dependencies": {
- "chokidar": "^2.0.4",
- "csproj2ts": "^1.1.0",
- "detect-indent": "^4.0.0",
- "detect-newline": "^2.1.0",
- "es6-promise": "~0.1.1",
- "jsmin2": "^1.2.1",
- "lodash": "~4.17.10",
- "ncp": "0.5.1",
- "rimraf": "2.2.6",
- "semver": "^5.3.0",
- "strip-bom": "^2.0.0"
- },
- "engines": {
- "node": ">= 0.8.0"
- },
- "peerDependencies": {
- "grunt": "^1.0.0 || ^0.4.0",
- "typescript": ">=1"
- }
- },
- "node_modules/grunt-ts/node_modules/anymatch": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
- "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==",
- "dev": true,
- "dependencies": {
- "micromatch": "^3.1.4",
- "normalize-path": "^2.1.1"
- }
- },
- "node_modules/grunt-ts/node_modules/anymatch/node_modules/normalize-path": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
- "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
- "dev": true,
- "dependencies": {
- "remove-trailing-separator": "^1.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/grunt-ts/node_modules/binary-extensions": {
- "version": "1.13.1",
- "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz",
- "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/grunt-ts/node_modules/braces": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
- "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
- "dev": true,
- "dependencies": {
- "arr-flatten": "^1.1.0",
- "array-unique": "^0.3.2",
- "extend-shallow": "^2.0.1",
- "fill-range": "^4.0.0",
- "isobject": "^3.0.1",
- "repeat-element": "^1.1.2",
- "snapdragon": "^0.8.1",
- "snapdragon-node": "^2.0.1",
- "split-string": "^3.0.2",
- "to-regex": "^3.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/grunt-ts/node_modules/chokidar": {
- "version": "2.1.8",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
- "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==",
- "deprecated": "Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies",
- "dev": true,
- "dependencies": {
- "anymatch": "^2.0.0",
- "async-each": "^1.0.1",
- "braces": "^2.3.2",
- "glob-parent": "^3.1.0",
- "inherits": "^2.0.3",
- "is-binary-path": "^1.0.0",
- "is-glob": "^4.0.0",
- "normalize-path": "^3.0.0",
- "path-is-absolute": "^1.0.0",
- "readdirp": "^2.2.1",
- "upath": "^1.1.1"
- },
- "optionalDependencies": {
- "fsevents": "^1.2.7"
- }
- },
- "node_modules/grunt-ts/node_modules/define-property": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
- "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
- "dev": true,
- "dependencies": {
- "is-descriptor": "^1.0.2",
- "isobject": "^3.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/grunt-ts/node_modules/fill-range": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
- "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
- "dev": true,
- "dependencies": {
- "extend-shallow": "^2.0.1",
- "is-number": "^3.0.0",
- "repeat-string": "^1.6.1",
- "to-regex-range": "^2.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/grunt-ts/node_modules/fsevents": {
- "version": "1.2.13",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
- "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
- "deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.",
- "dev": true,
- "hasInstallScript": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "dependencies": {
- "bindings": "^1.5.0",
- "nan": "^2.12.1"
- },
- "engines": {
- "node": ">= 4.0"
- }
- },
- "node_modules/grunt-ts/node_modules/glob-parent": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
- "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
- "dev": true,
- "dependencies": {
- "is-glob": "^3.1.0",
- "path-dirname": "^1.0.0"
- }
- },
- "node_modules/grunt-ts/node_modules/glob-parent/node_modules/is-glob": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
- "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
- "dev": true,
- "dependencies": {
- "is-extglob": "^2.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/grunt-ts/node_modules/is-accessor-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
- "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
- "dev": true,
- "dependencies": {
- "kind-of": "^6.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/grunt-ts/node_modules/is-binary-path": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
- "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=",
- "dev": true,
- "dependencies": {
- "binary-extensions": "^1.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/grunt-ts/node_modules/is-data-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
- "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
- "dev": true,
- "dependencies": {
- "kind-of": "^6.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/grunt-ts/node_modules/is-descriptor": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
- "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
- "dev": true,
- "dependencies": {
- "is-accessor-descriptor": "^1.0.0",
- "is-data-descriptor": "^1.0.0",
- "kind-of": "^6.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/grunt-ts/node_modules/is-extendable": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
- "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
- "dev": true,
- "dependencies": {
- "is-plain-object": "^2.0.4"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/grunt-ts/node_modules/is-number": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
- "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
- "dev": true,
- "dependencies": {
- "kind-of": "^3.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/grunt-ts/node_modules/is-number/node_modules/kind-of": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
- "dev": true,
- "dependencies": {
- "is-buffer": "^1.1.5"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/grunt-ts/node_modules/micromatch": {
- "version": "3.1.10",
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
- "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
- "dev": true,
- "dependencies": {
- "arr-diff": "^4.0.0",
- "array-unique": "^0.3.2",
- "braces": "^2.3.1",
- "define-property": "^2.0.2",
- "extend-shallow": "^3.0.2",
- "extglob": "^2.0.4",
- "fragment-cache": "^0.2.1",
- "kind-of": "^6.0.2",
- "nanomatch": "^1.2.9",
- "object.pick": "^1.3.0",
- "regex-not": "^1.0.0",
- "snapdragon": "^0.8.1",
- "to-regex": "^3.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/grunt-ts/node_modules/micromatch/node_modules/extend-shallow": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
- "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
- "dev": true,
- "dependencies": {
- "assign-symbols": "^1.0.0",
- "is-extendable": "^1.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/grunt-ts/node_modules/readdirp": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz",
- "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==",
- "dev": true,
- "dependencies": {
- "graceful-fs": "^4.1.11",
- "micromatch": "^3.1.10",
- "readable-stream": "^2.0.2"
- },
- "engines": {
- "node": ">=0.10"
- }
- },
- "node_modules/grunt-ts/node_modules/rimraf": {
- "version": "2.2.6",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.6.tgz",
- "integrity": "sha1-xZWXVpsU2VatKcrMQr3d9fDqT0w=",
- "dev": true,
- "bin": {
- "rimraf": "bin.js"
- }
- },
- "node_modules/grunt-ts/node_modules/semver": {
- "version": "5.7.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
- "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
- "dev": true,
- "bin": {
- "semver": "bin/semver"
- }
- },
- "node_modules/grunt-ts/node_modules/to-regex-range": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
- "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
- "dev": true,
- "dependencies": {
- "is-number": "^3.0.0",
- "repeat-string": "^1.6.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/grunt/node_modules/glob": {
"version": "7.1.7",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz",
@@ -5830,69 +5113,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/has-value": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
- "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=",
- "dev": true,
- "dependencies": {
- "get-value": "^2.0.6",
- "has-values": "^1.0.0",
- "isobject": "^3.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/has-values": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz",
- "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=",
- "dev": true,
- "dependencies": {
- "is-number": "^3.0.0",
- "kind-of": "^4.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/has-values/node_modules/is-number": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
- "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
- "dev": true,
- "dependencies": {
- "kind-of": "^3.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/has-values/node_modules/is-number/node_modules/kind-of": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
- "dev": true,
- "dependencies": {
- "is-buffer": "^1.1.5"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/has-values/node_modules/kind-of": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
- "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
- "dev": true,
- "dependencies": {
- "is-buffer": "^1.1.5"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/hasown": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
@@ -6320,30 +5540,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/is-accessor-descriptor": {
- "version": "0.1.6",
- "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
- "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
- "dev": true,
- "dependencies": {
- "kind-of": "^3.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-accessor-descriptor/node_modules/kind-of": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
- "dev": true,
- "dependencies": {
- "is-buffer": "^1.1.5"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/is-array-buffer": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz",
@@ -6404,12 +5600,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-buffer": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
- "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
- "dev": true
- },
"node_modules/is-callable": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
@@ -6434,30 +5624,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-data-descriptor": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
- "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
- "dev": true,
- "dependencies": {
- "kind-of": "^3.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-data-descriptor/node_modules/kind-of": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
- "dev": true,
- "dependencies": {
- "is-buffer": "^1.1.5"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/is-date-object": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
@@ -6473,29 +5639,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-descriptor": {
- "version": "0.1.6",
- "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
- "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
- "dev": true,
- "dependencies": {
- "is-accessor-descriptor": "^0.1.6",
- "is-data-descriptor": "^0.1.4",
- "kind-of": "^5.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-descriptor/node_modules/kind-of": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
- "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/is-docker": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz",
@@ -6511,15 +5654,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/is-extendable": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
- "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -6529,18 +5663,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/is-finite": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz",
- "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
@@ -6755,12 +5877,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/is-utf8": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
- "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=",
- "dev": true
- },
"node_modules/is-weakref": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
@@ -6861,12 +5977,6 @@
"node": ">=4"
}
},
- "node_modules/jsmin2": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/jsmin2/-/jsmin2-1.2.1.tgz",
- "integrity": "sha1-iPvi+/dfCpH2YCD9mBzWk/S/5X4=",
- "dev": true
- },
"node_modules/json-parse-even-better-errors": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
@@ -7071,18 +6181,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/map-visit": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
- "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=",
- "dev": true,
- "dependencies": {
- "object-visit": "^1.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/marked": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz",
@@ -7271,31 +6369,6 @@
"node": ">= 6"
}
},
- "node_modules/mixin-deep": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
- "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==",
- "dev": true,
- "dependencies": {
- "for-in": "^1.0.2",
- "is-extendable": "^1.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/mixin-deep/node_modules/is-extendable": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
- "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
- "dev": true,
- "dependencies": {
- "is-plain-object": "^2.0.4"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/morgan": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz",
@@ -7348,111 +6421,6 @@
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
"dev": true
},
- "node_modules/nan": {
- "version": "2.15.0",
- "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz",
- "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==",
- "dev": true,
- "optional": true
- },
- "node_modules/nanomatch": {
- "version": "1.2.13",
- "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
- "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==",
- "dev": true,
- "dependencies": {
- "arr-diff": "^4.0.0",
- "array-unique": "^0.3.2",
- "define-property": "^2.0.2",
- "extend-shallow": "^3.0.2",
- "fragment-cache": "^0.2.1",
- "is-windows": "^1.0.2",
- "kind-of": "^6.0.2",
- "object.pick": "^1.3.0",
- "regex-not": "^1.0.0",
- "snapdragon": "^0.8.1",
- "to-regex": "^3.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/nanomatch/node_modules/define-property": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
- "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
- "dev": true,
- "dependencies": {
- "is-descriptor": "^1.0.2",
- "isobject": "^3.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/nanomatch/node_modules/extend-shallow": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
- "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
- "dev": true,
- "dependencies": {
- "assign-symbols": "^1.0.0",
- "is-extendable": "^1.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/nanomatch/node_modules/is-accessor-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
- "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
- "dev": true,
- "dependencies": {
- "kind-of": "^6.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/nanomatch/node_modules/is-data-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
- "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
- "dev": true,
- "dependencies": {
- "kind-of": "^6.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/nanomatch/node_modules/is-descriptor": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
- "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
- "dev": true,
- "dependencies": {
- "is-accessor-descriptor": "^1.0.0",
- "is-data-descriptor": "^1.0.0",
- "kind-of": "^6.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/nanomatch/node_modules/is-extendable": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
- "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
- "dev": true,
- "dependencies": {
- "is-plain-object": "^2.0.4"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -7465,15 +6433,6 @@
"integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==",
"dev": true
},
- "node_modules/ncp": {
- "version": "0.5.1",
- "resolved": "https://registry.npmjs.org/ncp/-/ncp-0.5.1.tgz",
- "integrity": "sha1-dDmFMW49tFkoG1hxaehFc1oFQ58=",
- "dev": true,
- "bin": {
- "ncp": "bin/ncp"
- }
- },
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
@@ -7558,32 +6517,6 @@
"node": ">=8"
}
},
- "node_modules/object-copy": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
- "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=",
- "dev": true,
- "dependencies": {
- "copy-descriptor": "^0.1.0",
- "define-property": "^0.2.5",
- "kind-of": "^3.0.3"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/object-copy/node_modules/kind-of": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
- "dev": true,
- "dependencies": {
- "is-buffer": "^1.1.5"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/object-inspect": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
@@ -7602,18 +6535,6 @@
"node": ">= 0.4"
}
},
- "node_modules/object-visit": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
- "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=",
- "dev": true,
- "dependencies": {
- "isobject": "^3.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/object.assign": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
@@ -7865,6 +6786,20 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/pad-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/pad-stream/-/pad-stream-2.0.0.tgz",
+ "integrity": "sha512-3QeQw19K48BQzUGZ9dEf/slX5Jbfy5ZeBTma2XICketO7kFNK7omF00riVcecOKN+DSiJZcK2em1eYKaVOeXKg==",
+ "dev": true,
+ "dependencies": {
+ "pumpify": "^1.3.3",
+ "split2": "^2.1.1",
+ "through2": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -7927,21 +6862,6 @@
"node": ">= 0.8"
}
},
- "node_modules/pascalcase": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
- "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/path-dirname": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
- "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=",
- "dev": true
- },
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -8103,15 +7023,6 @@
"mkdirp": "bin/cmd.js"
}
},
- "node_modules/posix-character-classes": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
- "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -8167,6 +7078,27 @@
"node": ">= 0.10"
}
},
+ "node_modules/pump": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
+ "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
+ "dev": true,
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "node_modules/pumpify": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz",
+ "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==",
+ "dev": true,
+ "dependencies": {
+ "duplexify": "^3.6.0",
+ "inherits": "^2.0.3",
+ "pump": "^2.0.0"
+ }
+ },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -8434,44 +7366,6 @@
"node": ">=8"
}
},
- "node_modules/regex-not": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
- "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==",
- "dev": true,
- "dependencies": {
- "extend-shallow": "^3.0.2",
- "safe-regex": "^1.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/regex-not/node_modules/extend-shallow": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
- "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
- "dev": true,
- "dependencies": {
- "assign-symbols": "^1.0.0",
- "is-extendable": "^1.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/regex-not/node_modules/is-extendable": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
- "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
- "dev": true,
- "dependencies": {
- "is-plain-object": "^2.0.4"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/regexp.prototype.flags": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz",
@@ -8501,42 +7395,6 @@
"url": "https://github.com/sponsors/mysticatea"
}
},
- "node_modules/remove-trailing-separator": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
- "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=",
- "dev": true
- },
- "node_modules/repeat-element": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz",
- "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/repeat-string": {
- "version": "1.6.1",
- "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
- "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=",
- "dev": true,
- "engines": {
- "node": ">=0.10"
- }
- },
- "node_modules/repeating": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz",
- "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=",
- "dev": true,
- "dependencies": {
- "is-finite": "^1.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/requireindex": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz",
@@ -8591,13 +7449,6 @@
"node": ">=4"
}
},
- "node_modules/resolve-url": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
- "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
- "deprecated": "https://github.com/lydell/resolve-url#deprecated",
- "dev": true
- },
"node_modules/restore-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
@@ -8611,15 +7462,6 @@
"node": ">=8"
}
},
- "node_modules/ret": {
- "version": "0.1.15",
- "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
- "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
- "dev": true,
- "engines": {
- "node": ">=0.12"
- }
- },
"node_modules/reusify": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
@@ -8740,15 +7582,6 @@
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true
},
- "node_modules/safe-regex": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
- "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
- "dev": true,
- "dependencies": {
- "ret": "~0.1.10"
- }
- },
"node_modules/safe-regex-test": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz",
@@ -8769,12 +7602,6 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true
},
- "node_modules/sax": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
- "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
- "dev": true
- },
"node_modules/screenshot-ftw": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/screenshot-ftw/-/screenshot-ftw-1.0.5.tgz",
@@ -8975,21 +7802,6 @@
"node": ">= 0.4"
}
},
- "node_modules/set-value": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
- "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==",
- "dev": true,
- "dependencies": {
- "extend-shallow": "^2.0.1",
- "is-extendable": "^0.1.1",
- "is-plain-object": "^2.0.3",
- "split-string": "^3.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
@@ -9058,158 +7870,6 @@
"node": ">=6"
}
},
- "node_modules/snapdragon": {
- "version": "0.8.2",
- "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
- "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==",
- "dev": true,
- "dependencies": {
- "base": "^0.11.1",
- "debug": "^2.2.0",
- "define-property": "^0.2.5",
- "extend-shallow": "^2.0.1",
- "map-cache": "^0.2.2",
- "source-map": "^0.5.6",
- "source-map-resolve": "^0.5.0",
- "use": "^3.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/snapdragon-node": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
- "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==",
- "dev": true,
- "dependencies": {
- "define-property": "^1.0.0",
- "isobject": "^3.0.0",
- "snapdragon-util": "^3.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/snapdragon-node/node_modules/define-property": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
- "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
- "dev": true,
- "dependencies": {
- "is-descriptor": "^1.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/snapdragon-node/node_modules/is-accessor-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
- "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
- "dev": true,
- "dependencies": {
- "kind-of": "^6.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/snapdragon-node/node_modules/is-data-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
- "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
- "dev": true,
- "dependencies": {
- "kind-of": "^6.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/snapdragon-node/node_modules/is-descriptor": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
- "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
- "dev": true,
- "dependencies": {
- "is-accessor-descriptor": "^1.0.0",
- "is-data-descriptor": "^1.0.0",
- "kind-of": "^6.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/snapdragon-util": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
- "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==",
- "dev": true,
- "dependencies": {
- "kind-of": "^3.2.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/snapdragon-util/node_modules/kind-of": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
- "dev": true,
- "dependencies": {
- "is-buffer": "^1.1.5"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/snapdragon/node_modules/debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "dev": true,
- "dependencies": {
- "ms": "2.0.0"
- }
- },
- "node_modules/snapdragon/node_modules/ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
- "dev": true
- },
- "node_modules/source-map": {
- "version": "0.5.7",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
- "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/source-map-resolve": {
- "version": "0.5.3",
- "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz",
- "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==",
- "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated",
- "dev": true,
- "dependencies": {
- "atob": "^2.1.2",
- "decode-uri-component": "^0.2.0",
- "resolve-url": "^0.2.1",
- "source-map-url": "^0.4.0",
- "urix": "^0.1.0"
- }
- },
- "node_modules/source-map-url": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz",
- "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==",
- "deprecated": "See https://github.com/lydell/source-map-url#deprecated",
- "dev": true
- },
"node_modules/spdx-correct": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz",
@@ -9242,41 +7902,13 @@
"integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==",
"dev": true
},
- "node_modules/split-string": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
- "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==",
- "dev": true,
- "dependencies": {
- "extend-shallow": "^3.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/split-string/node_modules/extend-shallow": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
- "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
- "dev": true,
- "dependencies": {
- "assign-symbols": "^1.0.0",
- "is-extendable": "^1.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/split-string/node_modules/is-extendable": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
- "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+ "node_modules/split2": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz",
+ "integrity": "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==",
"dev": true,
"dependencies": {
- "is-plain-object": "^2.0.4"
- },
- "engines": {
- "node": ">=0.10.0"
+ "through2": "^2.0.2"
}
},
"node_modules/sprintf-js": {
@@ -9285,19 +7917,6 @@
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
"dev": true
},
- "node_modules/static-extend": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
- "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=",
- "dev": true,
- "dependencies": {
- "define-property": "^0.2.5",
- "object-copy": "^0.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/statuses": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
@@ -9307,6 +7926,12 @@
"node": ">= 0.6"
}
},
+ "node_modules/stream-shift": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz",
+ "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==",
+ "dev": true
+ },
"node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
@@ -9387,18 +8012,6 @@
"node": ">=8"
}
},
- "node_modules/strip-bom": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
- "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
- "dev": true,
- "dependencies": {
- "is-utf8": "^0.2.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/strip-final-newline": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
@@ -9484,6 +8097,16 @@
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
"dev": true
},
+ "node_modules/through2": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
+ "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
+ "dev": true,
+ "dependencies": {
+ "readable-stream": "~2.3.6",
+ "xtend": "~4.0.1"
+ }
+ },
"node_modules/titleize": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz",
@@ -9517,45 +8140,6 @@
"node": ">=4"
}
},
- "node_modules/to-object-path": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
- "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=",
- "dev": true,
- "dependencies": {
- "kind-of": "^3.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/to-object-path/node_modules/kind-of": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
- "dev": true,
- "dependencies": {
- "is-buffer": "^1.1.5"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/to-regex": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz",
- "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==",
- "dev": true,
- "dependencies": {
- "define-property": "^2.0.2",
- "extend-shallow": "^3.0.2",
- "regex-not": "^1.0.2",
- "safe-regex": "^1.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -9568,82 +8152,6 @@
"node": ">=8.0"
}
},
- "node_modules/to-regex/node_modules/define-property": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
- "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
- "dev": true,
- "dependencies": {
- "is-descriptor": "^1.0.2",
- "isobject": "^3.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/to-regex/node_modules/extend-shallow": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
- "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
- "dev": true,
- "dependencies": {
- "assign-symbols": "^1.0.0",
- "is-extendable": "^1.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/to-regex/node_modules/is-accessor-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
- "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
- "dev": true,
- "dependencies": {
- "kind-of": "^6.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/to-regex/node_modules/is-data-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
- "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
- "dev": true,
- "dependencies": {
- "kind-of": "^6.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/to-regex/node_modules/is-descriptor": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
- "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
- "dev": true,
- "dependencies": {
- "is-accessor-descriptor": "^1.0.0",
- "is-data-descriptor": "^1.0.0",
- "kind-of": "^6.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/to-regex/node_modules/is-extendable": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
- "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
- "dev": true,
- "dependencies": {
- "is-plain-object": "^2.0.4"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
@@ -10004,21 +8512,6 @@
"node": ">= 0.8.0"
}
},
- "node_modules/union-value": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
- "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==",
- "dev": true,
- "dependencies": {
- "arr-union": "^3.1.0",
- "get-value": "^2.0.6",
- "is-extendable": "^0.1.1",
- "set-value": "^2.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@@ -10028,54 +8521,6 @@
"node": ">= 0.8"
}
},
- "node_modules/unset-value": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
- "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=",
- "dev": true,
- "dependencies": {
- "has-value": "^0.3.1",
- "isobject": "^3.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/unset-value/node_modules/has-value": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
- "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=",
- "dev": true,
- "dependencies": {
- "get-value": "^2.0.3",
- "has-values": "^0.1.4",
- "isobject": "^2.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/unset-value/node_modules/has-value/node_modules/isobject": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
- "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
- "dev": true,
- "dependencies": {
- "isarray": "1.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/unset-value/node_modules/has-values": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
- "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/untildify": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
@@ -10085,16 +8530,6 @@
"node": ">=8"
}
},
- "node_modules/upath": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz",
- "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==",
- "dev": true,
- "engines": {
- "node": ">=4",
- "yarn": "*"
- }
- },
"node_modules/update-browserslist-db": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
@@ -10134,28 +8569,12 @@
"punycode": "^2.1.0"
}
},
- "node_modules/urix": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
- "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=",
- "deprecated": "Please see https://github.com/lydell/urix#deprecated",
- "dev": true
- },
"node_modules/url-join": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
"integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==",
"dev": true
},
- "node_modules/use": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
- "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -10313,26 +8732,13 @@
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
}
},
- "node_modules/xml2js": {
- "version": "0.4.23",
- "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
- "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
- "dev": true,
- "dependencies": {
- "sax": ">=0.6.0",
- "xmlbuilder": "~11.0.0"
- },
- "engines": {
- "node": ">=4.0.0"
- }
- },
- "node_modules/xmlbuilder": {
- "version": "11.0.1",
- "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
- "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
+ "node_modules/xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
"dev": true,
"engines": {
- "node": ">=4.0"
+ "node": ">=0.4"
}
},
"node_modules/yallist": {
@@ -11464,9 +9870,9 @@
"dev": true
},
"@webgpu/types": {
- "version": "0.1.38",
- "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.38.tgz",
- "integrity": "sha512-7LrhVKz2PRh+DD7+S+PVaFd5HxaWQvoMqBbsV9fNJO1pjUs1P8bM2vQVNfk+3URTqbuTI7gkXi0rfsN0IadoBA==",
+ "version": "0.1.40",
+ "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.40.tgz",
+ "integrity": "sha512-/BBkHLS6/eQjyWhY2H7Dx5DHcVrS2ICj9owvSRdgtQT6KcafLZA86tPze0xAOsd4FbsYKCUBUQyNi87q7gV7kw==",
"dev": true
},
"abbrev": {
@@ -11577,24 +9983,6 @@
"sprintf-js": "~1.0.2"
}
},
- "arr-diff": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
- "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=",
- "dev": true
- },
- "arr-flatten": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
- "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==",
- "dev": true
- },
- "arr-union": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
- "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=",
- "dev": true
- },
"array-buffer-byte-length": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz",
@@ -11642,12 +10030,6 @@
"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
"dev": true
},
- "array-unique": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
- "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
- "dev": true
- },
"array.prototype.findlastindex": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz",
@@ -11706,30 +10088,12 @@
"integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=",
"dev": true
},
- "assign-symbols": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
- "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=",
- "dev": true
- },
"async": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
"integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==",
"dev": true
},
- "async-each": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz",
- "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==",
- "dev": true
- },
- "atob": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
- "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
- "dev": true
- },
"available-typed-arrays": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
@@ -11759,61 +10123,6 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
- "base": {
- "version": "0.11.2",
- "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
- "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==",
- "dev": true,
- "requires": {
- "cache-base": "^1.0.1",
- "class-utils": "^0.3.5",
- "component-emitter": "^1.2.1",
- "define-property": "^1.0.0",
- "isobject": "^3.0.1",
- "mixin-deep": "^1.2.0",
- "pascalcase": "^0.1.1"
- },
- "dependencies": {
- "define-property": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
- "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
- "dev": true,
- "requires": {
- "is-descriptor": "^1.0.0"
- }
- },
- "is-accessor-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
- "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
- "dev": true,
- "requires": {
- "kind-of": "^6.0.0"
- }
- },
- "is-data-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
- "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
- "dev": true,
- "requires": {
- "kind-of": "^6.0.0"
- }
- },
- "is-descriptor": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
- "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
- "dev": true,
- "requires": {
- "is-accessor-descriptor": "^1.0.0",
- "is-data-descriptor": "^1.0.0",
- "kind-of": "^6.0.2"
- }
- }
- }
- },
"bash-color": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/bash-color/-/bash-color-0.0.3.tgz",
@@ -11847,16 +10156,6 @@
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
"dev": true
},
- "bindings": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
- "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
- "dev": true,
- "optional": true,
- "requires": {
- "file-uri-to-path": "1.0.0"
- }
- },
"body-parser": {
"version": "1.20.1",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
@@ -11964,23 +10263,6 @@
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"dev": true
},
- "cache-base": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
- "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==",
- "dev": true,
- "requires": {
- "collection-visit": "^1.0.0",
- "component-emitter": "^1.2.1",
- "get-value": "^2.0.6",
- "has-value": "^1.0.0",
- "isobject": "^3.0.1",
- "set-value": "^2.0.0",
- "to-object-path": "^0.3.0",
- "union-value": "^1.0.0",
- "unset-value": "^1.0.0"
- }
- },
"call-bind": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz",
@@ -12054,18 +10336,6 @@
"readdirp": "~3.6.0"
}
},
- "class-utils": {
- "version": "0.3.6",
- "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
- "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==",
- "dev": true,
- "requires": {
- "arr-union": "^3.1.0",
- "define-property": "^0.2.5",
- "isobject": "^3.0.0",
- "static-extend": "^0.1.1"
- }
- },
"cli-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
@@ -12081,16 +10351,6 @@
"integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==",
"dev": true
},
- "collection-visit": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
- "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=",
- "dev": true,
- "requires": {
- "map-visit": "^1.0.0",
- "object-visit": "^1.0.0"
- }
- },
"color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@@ -12112,12 +10372,6 @@
"integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=",
"dev": true
},
- "component-emitter": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
- "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
- "dev": true
- },
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -12165,12 +10419,6 @@
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=",
"dev": true
},
- "copy-descriptor": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
- "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
- "dev": true
- },
"core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
@@ -12200,32 +10448,6 @@
"which": "^2.0.1"
}
},
- "csproj2ts": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/csproj2ts/-/csproj2ts-1.1.0.tgz",
- "integrity": "sha512-sk0RTT51t4lUNQ7UfZrqjQx7q4g0m3iwNA6mvyh7gLsgQYvwKzfdyoAgicC9GqJvkoIkU0UmndV9c7VZ8pJ45Q==",
- "dev": true,
- "requires": {
- "es6-promise": "^4.1.1",
- "lodash": "^4.17.4",
- "semver": "^5.4.1",
- "xml2js": "^0.4.19"
- },
- "dependencies": {
- "es6-promise": {
- "version": "4.2.8",
- "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
- "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==",
- "dev": true
- },
- "semver": {
- "version": "5.7.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
- "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
- "dev": true
- }
- }
- },
"d": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
@@ -12275,12 +10497,6 @@
}
}
},
- "decode-uri-component": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
- "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
- "dev": true
- },
"deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -12404,15 +10620,6 @@
"object-keys": "^1.1.1"
}
},
- "define-property": {
- "version": "0.2.5",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
- "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
- "dev": true,
- "requires": {
- "is-descriptor": "^0.1.0"
- }
- },
"depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
@@ -12431,21 +10638,6 @@
"integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=",
"dev": true
},
- "detect-indent": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz",
- "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=",
- "dev": true,
- "requires": {
- "repeating": "^2.0.0"
- }
- },
- "detect-newline": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz",
- "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=",
- "dev": true
- },
"diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
@@ -12470,6 +10662,18 @@
"esutils": "^2.0.2"
}
},
+ "duplexify": {
+ "version": "3.7.1",
+ "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
+ "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==",
+ "dev": true,
+ "requires": {
+ "end-of-stream": "^1.0.0",
+ "inherits": "^2.0.1",
+ "readable-stream": "^2.0.0",
+ "stream-shift": "^1.0.0"
+ }
+ },
"duration": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/duration/-/duration-0.2.2.tgz",
@@ -12504,6 +10708,15 @@
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"dev": true
},
+ "end-of-stream": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
+ "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
+ "dev": true,
+ "requires": {
+ "once": "^1.4.0"
+ }
+ },
"error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@@ -12613,12 +10826,6 @@
"es6-symbol": "^3.1.1"
}
},
- "es6-promise": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-0.1.2.tgz",
- "integrity": "sha1-8RLCn+paCZhTn8tqL9IUQ9KPBfc=",
- "dev": true
- },
"es6-symbol": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz",
@@ -13061,38 +11268,6 @@
"integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=",
"dev": true
},
- "expand-brackets": {
- "version": "2.1.4",
- "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
- "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
- "dev": true,
- "requires": {
- "debug": "^2.3.3",
- "define-property": "^0.2.5",
- "extend-shallow": "^2.0.1",
- "posix-character-classes": "^0.1.0",
- "regex-not": "^1.0.0",
- "snapdragon": "^0.8.1",
- "to-regex": "^3.0.1"
- },
- "dependencies": {
- "debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "dev": true,
- "requires": {
- "ms": "2.0.0"
- }
- },
- "ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
- "dev": true
- }
- }
- },
"expand-tilde": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz",
@@ -13208,15 +11383,6 @@
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"dev": true
},
- "extend-shallow": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
- "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
- "dev": true,
- "requires": {
- "is-extendable": "^0.1.0"
- }
- },
"external-editor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
@@ -13228,62 +11394,6 @@
"tmp": "^0.0.33"
}
},
- "extglob": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
- "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
- "dev": true,
- "requires": {
- "array-unique": "^0.3.2",
- "define-property": "^1.0.0",
- "expand-brackets": "^2.1.4",
- "extend-shallow": "^2.0.1",
- "fragment-cache": "^0.2.1",
- "regex-not": "^1.0.0",
- "snapdragon": "^0.8.1",
- "to-regex": "^3.0.1"
- },
- "dependencies": {
- "define-property": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
- "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
- "dev": true,
- "requires": {
- "is-descriptor": "^1.0.0"
- }
- },
- "is-accessor-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
- "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
- "dev": true,
- "requires": {
- "kind-of": "^6.0.0"
- }
- },
- "is-data-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
- "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
- "dev": true,
- "requires": {
- "kind-of": "^6.0.0"
- }
- },
- "is-descriptor": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
- "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
- "dev": true,
- "requires": {
- "is-accessor-descriptor": "^1.0.0",
- "is-data-descriptor": "^1.0.0",
- "kind-of": "^6.0.2"
- }
- }
- }
- },
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -13354,13 +11464,6 @@
"integrity": "sha1-peeo/7+kk7Q7kju9TKiaU7Y7YSs=",
"dev": true
},
- "file-uri-to-path": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
- "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
- "dev": true,
- "optional": true
- },
"fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -13510,15 +11613,6 @@
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
"dev": true
},
- "fragment-cache": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
- "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=",
- "dev": true,
- "requires": {
- "map-cache": "^0.2.2"
- }
- },
"fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
@@ -13608,12 +11702,6 @@
"get-intrinsic": "^1.1.1"
}
},
- "get-value": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
- "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=",
- "dev": true
- },
"getobject": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/getobject/-/getobject-1.0.2.tgz",
@@ -13730,12 +11818,6 @@
"get-intrinsic": "^1.1.3"
}
},
- "graceful-fs": {
- "version": "4.2.9",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz",
- "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==",
- "dev": true
- },
"graphemer": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
@@ -13822,6 +11904,26 @@
}
}
},
+ "grunt-concurrent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/grunt-concurrent/-/grunt-concurrent-3.0.0.tgz",
+ "integrity": "sha512-AgXtjUJESHEGeGX8neL3nmXBTHSj1QC48ABQ3ng2/vjuSBpDD8gKcVHSlXP71pFkIR8TQHf+eomOx6OSYSgfrA==",
+ "dev": true,
+ "requires": {
+ "arrify": "^2.0.1",
+ "async": "^3.1.0",
+ "indent-string": "^4.0.0",
+ "pad-stream": "^2.0.0"
+ },
+ "dependencies": {
+ "arrify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz",
+ "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==",
+ "dev": true
+ }
+ }
+ },
"grunt-contrib-clean": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-2.0.1.tgz",
@@ -14027,279 +12129,6 @@
"hooker": "^0.2.3"
}
},
- "grunt-ts": {
- "version": "6.0.0-beta.22",
- "resolved": "https://registry.npmjs.org/grunt-ts/-/grunt-ts-6.0.0-beta.22.tgz",
- "integrity": "sha512-g9e+ZImQ7W38dfpwhp0+GUltXWidy3YGPfIA/IyGL5HMv6wmVmMMoSgscI5swhs2HSPf8yAvXAAJbwrouijoRg==",
- "dev": true,
- "requires": {
- "chokidar": "^2.0.4",
- "csproj2ts": "^1.1.0",
- "detect-indent": "^4.0.0",
- "detect-newline": "^2.1.0",
- "es6-promise": "~0.1.1",
- "jsmin2": "^1.2.1",
- "lodash": "~4.17.10",
- "ncp": "0.5.1",
- "rimraf": "2.2.6",
- "semver": "^5.3.0",
- "strip-bom": "^2.0.0"
- },
- "dependencies": {
- "anymatch": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
- "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==",
- "dev": true,
- "requires": {
- "micromatch": "^3.1.4",
- "normalize-path": "^2.1.1"
- },
- "dependencies": {
- "normalize-path": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
- "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
- "dev": true,
- "requires": {
- "remove-trailing-separator": "^1.0.1"
- }
- }
- }
- },
- "binary-extensions": {
- "version": "1.13.1",
- "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz",
- "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==",
- "dev": true
- },
- "braces": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
- "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
- "dev": true,
- "requires": {
- "arr-flatten": "^1.1.0",
- "array-unique": "^0.3.2",
- "extend-shallow": "^2.0.1",
- "fill-range": "^4.0.0",
- "isobject": "^3.0.1",
- "repeat-element": "^1.1.2",
- "snapdragon": "^0.8.1",
- "snapdragon-node": "^2.0.1",
- "split-string": "^3.0.2",
- "to-regex": "^3.0.1"
- }
- },
- "chokidar": {
- "version": "2.1.8",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
- "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==",
- "dev": true,
- "requires": {
- "anymatch": "^2.0.0",
- "async-each": "^1.0.1",
- "braces": "^2.3.2",
- "fsevents": "^1.2.7",
- "glob-parent": "^3.1.0",
- "inherits": "^2.0.3",
- "is-binary-path": "^1.0.0",
- "is-glob": "^4.0.0",
- "normalize-path": "^3.0.0",
- "path-is-absolute": "^1.0.0",
- "readdirp": "^2.2.1",
- "upath": "^1.1.1"
- }
- },
- "define-property": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
- "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
- "dev": true,
- "requires": {
- "is-descriptor": "^1.0.2",
- "isobject": "^3.0.1"
- }
- },
- "fill-range": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
- "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
- "dev": true,
- "requires": {
- "extend-shallow": "^2.0.1",
- "is-number": "^3.0.0",
- "repeat-string": "^1.6.1",
- "to-regex-range": "^2.1.0"
- }
- },
- "fsevents": {
- "version": "1.2.13",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
- "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
- "dev": true,
- "optional": true,
- "requires": {
- "bindings": "^1.5.0",
- "nan": "^2.12.1"
- }
- },
- "glob-parent": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
- "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
- "dev": true,
- "requires": {
- "is-glob": "^3.1.0",
- "path-dirname": "^1.0.0"
- },
- "dependencies": {
- "is-glob": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
- "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
- "dev": true,
- "requires": {
- "is-extglob": "^2.1.0"
- }
- }
- }
- },
- "is-accessor-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
- "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
- "dev": true,
- "requires": {
- "kind-of": "^6.0.0"
- }
- },
- "is-binary-path": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
- "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=",
- "dev": true,
- "requires": {
- "binary-extensions": "^1.0.0"
- }
- },
- "is-data-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
- "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
- "dev": true,
- "requires": {
- "kind-of": "^6.0.0"
- }
- },
- "is-descriptor": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
- "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
- "dev": true,
- "requires": {
- "is-accessor-descriptor": "^1.0.0",
- "is-data-descriptor": "^1.0.0",
- "kind-of": "^6.0.2"
- }
- },
- "is-extendable": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
- "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
- "dev": true,
- "requires": {
- "is-plain-object": "^2.0.4"
- }
- },
- "is-number": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
- "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
- "dev": true,
- "requires": {
- "kind-of": "^3.0.2"
- },
- "dependencies": {
- "kind-of": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
- "dev": true,
- "requires": {
- "is-buffer": "^1.1.5"
- }
- }
- }
- },
- "micromatch": {
- "version": "3.1.10",
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
- "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
- "dev": true,
- "requires": {
- "arr-diff": "^4.0.0",
- "array-unique": "^0.3.2",
- "braces": "^2.3.1",
- "define-property": "^2.0.2",
- "extend-shallow": "^3.0.2",
- "extglob": "^2.0.4",
- "fragment-cache": "^0.2.1",
- "kind-of": "^6.0.2",
- "nanomatch": "^1.2.9",
- "object.pick": "^1.3.0",
- "regex-not": "^1.0.0",
- "snapdragon": "^0.8.1",
- "to-regex": "^3.0.2"
- },
- "dependencies": {
- "extend-shallow": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
- "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
- "dev": true,
- "requires": {
- "assign-symbols": "^1.0.0",
- "is-extendable": "^1.0.1"
- }
- }
- }
- },
- "readdirp": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz",
- "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==",
- "dev": true,
- "requires": {
- "graceful-fs": "^4.1.11",
- "micromatch": "^3.1.10",
- "readable-stream": "^2.0.2"
- }
- },
- "rimraf": {
- "version": "2.2.6",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.6.tgz",
- "integrity": "sha1-xZWXVpsU2VatKcrMQr3d9fDqT0w=",
- "dev": true
- },
- "semver": {
- "version": "5.7.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
- "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
- "dev": true
- },
- "to-regex-range": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
- "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
- "dev": true,
- "requires": {
- "is-number": "^3.0.0",
- "repeat-string": "^1.6.1"
- }
- }
- }
- },
"gts": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/gts/-/gts-5.2.0.tgz",
@@ -14655,58 +12484,6 @@
"has-symbols": "^1.0.2"
}
},
- "has-value": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
- "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=",
- "dev": true,
- "requires": {
- "get-value": "^2.0.6",
- "has-values": "^1.0.0",
- "isobject": "^3.0.0"
- }
- },
- "has-values": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz",
- "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=",
- "dev": true,
- "requires": {
- "is-number": "^3.0.0",
- "kind-of": "^4.0.0"
- },
- "dependencies": {
- "is-number": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
- "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
- "dev": true,
- "requires": {
- "kind-of": "^3.0.2"
- },
- "dependencies": {
- "kind-of": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
- "dev": true,
- "requires": {
- "is-buffer": "^1.1.5"
- }
- }
- }
- },
- "kind-of": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
- "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
- "dev": true,
- "requires": {
- "is-buffer": "^1.1.5"
- }
- }
- }
- },
"hasown": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
@@ -15029,26 +12806,6 @@
"is-windows": "^1.0.1"
}
},
- "is-accessor-descriptor": {
- "version": "0.1.6",
- "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
- "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
- "dev": true,
- "requires": {
- "kind-of": "^3.0.2"
- },
- "dependencies": {
- "kind-of": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
- "dev": true,
- "requires": {
- "is-buffer": "^1.1.5"
- }
- }
- }
- },
"is-array-buffer": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz",
@@ -15094,12 +12851,6 @@
"has-tostringtag": "^1.0.0"
}
},
- "is-buffer": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
- "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
- "dev": true
- },
"is-callable": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
@@ -15115,26 +12866,6 @@
"hasown": "^2.0.0"
}
},
- "is-data-descriptor": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
- "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
- "dev": true,
- "requires": {
- "kind-of": "^3.0.2"
- },
- "dependencies": {
- "kind-of": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
- "dev": true,
- "requires": {
- "is-buffer": "^1.1.5"
- }
- }
- }
- },
"is-date-object": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
@@ -15144,49 +12875,18 @@
"has-tostringtag": "^1.0.0"
}
},
- "is-descriptor": {
- "version": "0.1.6",
- "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
- "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
- "dev": true,
- "requires": {
- "is-accessor-descriptor": "^0.1.6",
- "is-data-descriptor": "^0.1.4",
- "kind-of": "^5.0.0"
- },
- "dependencies": {
- "kind-of": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
- "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
- "dev": true
- }
- }
- },
"is-docker": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz",
"integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==",
"dev": true
},
- "is-extendable": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
- "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
- "dev": true
- },
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
"dev": true
},
- "is-finite": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz",
- "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==",
- "dev": true
- },
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
@@ -15323,12 +13023,6 @@
"unc-path-regex": "^0.1.2"
}
},
- "is-utf8": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
- "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=",
- "dev": true
- },
"is-weakref": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
@@ -15401,12 +13095,6 @@
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
"dev": true
},
- "jsmin2": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/jsmin2/-/jsmin2-1.2.1.tgz",
- "integrity": "sha1-iPvi+/dfCpH2YCD9mBzWk/S/5X4=",
- "dev": true
- },
"json-parse-even-better-errors": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
@@ -15570,15 +13258,6 @@
"integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==",
"dev": true
},
- "map-visit": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
- "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=",
- "dev": true,
- "requires": {
- "object-visit": "^1.0.0"
- }
- },
"marked": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz",
@@ -15712,27 +13391,6 @@
"kind-of": "^6.0.3"
}
},
- "mixin-deep": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
- "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==",
- "dev": true,
- "requires": {
- "for-in": "^1.0.2",
- "is-extendable": "^1.0.1"
- },
- "dependencies": {
- "is-extendable": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
- "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
- "dev": true,
- "requires": {
- "is-plain-object": "^2.0.4"
- }
- }
- }
- },
"morgan": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz",
@@ -15781,92 +13439,6 @@
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
"dev": true
},
- "nan": {
- "version": "2.15.0",
- "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz",
- "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==",
- "dev": true,
- "optional": true
- },
- "nanomatch": {
- "version": "1.2.13",
- "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
- "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==",
- "dev": true,
- "requires": {
- "arr-diff": "^4.0.0",
- "array-unique": "^0.3.2",
- "define-property": "^2.0.2",
- "extend-shallow": "^3.0.2",
- "fragment-cache": "^0.2.1",
- "is-windows": "^1.0.2",
- "kind-of": "^6.0.2",
- "object.pick": "^1.3.0",
- "regex-not": "^1.0.0",
- "snapdragon": "^0.8.1",
- "to-regex": "^3.0.1"
- },
- "dependencies": {
- "define-property": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
- "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
- "dev": true,
- "requires": {
- "is-descriptor": "^1.0.2",
- "isobject": "^3.0.1"
- }
- },
- "extend-shallow": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
- "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
- "dev": true,
- "requires": {
- "assign-symbols": "^1.0.0",
- "is-extendable": "^1.0.1"
- }
- },
- "is-accessor-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
- "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
- "dev": true,
- "requires": {
- "kind-of": "^6.0.0"
- }
- },
- "is-data-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
- "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
- "dev": true,
- "requires": {
- "kind-of": "^6.0.0"
- }
- },
- "is-descriptor": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
- "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
- "dev": true,
- "requires": {
- "is-accessor-descriptor": "^1.0.0",
- "is-data-descriptor": "^1.0.0",
- "kind-of": "^6.0.2"
- }
- },
- "is-extendable": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
- "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
- "dev": true,
- "requires": {
- "is-plain-object": "^2.0.4"
- }
- }
- }
- },
"natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -15879,12 +13451,6 @@
"integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==",
"dev": true
},
- "ncp": {
- "version": "0.5.1",
- "resolved": "https://registry.npmjs.org/ncp/-/ncp-0.5.1.tgz",
- "integrity": "sha1-dDmFMW49tFkoG1hxaehFc1oFQ58=",
- "dev": true
- },
"negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
@@ -15950,28 +13516,6 @@
"path-key": "^3.0.0"
}
},
- "object-copy": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
- "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=",
- "dev": true,
- "requires": {
- "copy-descriptor": "^0.1.0",
- "define-property": "^0.2.5",
- "kind-of": "^3.0.3"
- },
- "dependencies": {
- "kind-of": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
- "dev": true,
- "requires": {
- "is-buffer": "^1.1.5"
- }
- }
- }
- },
"object-inspect": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
@@ -15984,15 +13528,6 @@
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
"dev": true
},
- "object-visit": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
- "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=",
- "dev": true,
- "requires": {
- "isobject": "^3.0.0"
- }
- },
"object.assign": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
@@ -16175,6 +13710,17 @@
"p-limit": "^3.0.2"
}
},
+ "pad-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/pad-stream/-/pad-stream-2.0.0.tgz",
+ "integrity": "sha512-3QeQw19K48BQzUGZ9dEf/slX5Jbfy5ZeBTma2XICketO7kFNK7omF00riVcecOKN+DSiJZcK2em1eYKaVOeXKg==",
+ "dev": true,
+ "requires": {
+ "pumpify": "^1.3.3",
+ "split2": "^2.1.1",
+ "through2": "^2.0.0"
+ }
+ },
"parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -16219,18 +13765,6 @@
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
"dev": true
},
- "pascalcase": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
- "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=",
- "dev": true
- },
- "path-dirname": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
- "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=",
- "dev": true
- },
"path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -16352,12 +13886,6 @@
}
}
},
- "posix-character-classes": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
- "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=",
- "dev": true
- },
"prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -16395,6 +13923,27 @@
"ipaddr.js": "1.9.1"
}
},
+ "pump": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
+ "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
+ "dev": true,
+ "requires": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "pumpify": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz",
+ "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==",
+ "dev": true,
+ "requires": {
+ "duplexify": "^3.6.0",
+ "inherits": "^2.0.3",
+ "pump": "^2.0.0"
+ }
+ },
"punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -16589,37 +14138,6 @@
"strip-indent": "^3.0.0"
}
},
- "regex-not": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
- "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==",
- "dev": true,
- "requires": {
- "extend-shallow": "^3.0.2",
- "safe-regex": "^1.1.0"
- },
- "dependencies": {
- "extend-shallow": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
- "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
- "dev": true,
- "requires": {
- "assign-symbols": "^1.0.0",
- "is-extendable": "^1.0.1"
- }
- },
- "is-extendable": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
- "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
- "dev": true,
- "requires": {
- "is-plain-object": "^2.0.4"
- }
- }
- }
- },
"regexp.prototype.flags": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz",
@@ -16637,33 +14155,6 @@
"integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
"dev": true
},
- "remove-trailing-separator": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
- "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=",
- "dev": true
- },
- "repeat-element": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz",
- "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==",
- "dev": true
- },
- "repeat-string": {
- "version": "1.6.1",
- "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
- "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=",
- "dev": true
- },
- "repeating": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz",
- "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=",
- "dev": true,
- "requires": {
- "is-finite": "^1.0.0"
- }
- },
"requireindex": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz",
@@ -16703,12 +14194,6 @@
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true
},
- "resolve-url": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
- "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
- "dev": true
- },
"restore-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
@@ -16719,12 +14204,6 @@
"signal-exit": "^3.0.2"
}
},
- "ret": {
- "version": "0.1.15",
- "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
- "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
- "dev": true
- },
"reusify": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
@@ -16807,15 +14286,6 @@
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true
},
- "safe-regex": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
- "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
- "dev": true,
- "requires": {
- "ret": "~0.1.10"
- }
- },
"safe-regex-test": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz",
@@ -16833,12 +14303,6 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true
},
- "sax": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
- "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
- "dev": true
- },
"screenshot-ftw": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/screenshot-ftw/-/screenshot-ftw-1.0.5.tgz",
@@ -17015,18 +14479,6 @@
"has-property-descriptors": "^1.0.0"
}
},
- "set-value": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
- "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==",
- "dev": true,
- "requires": {
- "extend-shallow": "^2.0.1",
- "is-extendable": "^0.1.1",
- "is-plain-object": "^2.0.3",
- "split-string": "^3.0.1"
- }
- },
"setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
@@ -17083,135 +14535,6 @@
"integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
"dev": true
},
- "snapdragon": {
- "version": "0.8.2",
- "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
- "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==",
- "dev": true,
- "requires": {
- "base": "^0.11.1",
- "debug": "^2.2.0",
- "define-property": "^0.2.5",
- "extend-shallow": "^2.0.1",
- "map-cache": "^0.2.2",
- "source-map": "^0.5.6",
- "source-map-resolve": "^0.5.0",
- "use": "^3.1.0"
- },
- "dependencies": {
- "debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "dev": true,
- "requires": {
- "ms": "2.0.0"
- }
- },
- "ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
- "dev": true
- }
- }
- },
- "snapdragon-node": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
- "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==",
- "dev": true,
- "requires": {
- "define-property": "^1.0.0",
- "isobject": "^3.0.0",
- "snapdragon-util": "^3.0.1"
- },
- "dependencies": {
- "define-property": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
- "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
- "dev": true,
- "requires": {
- "is-descriptor": "^1.0.0"
- }
- },
- "is-accessor-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
- "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
- "dev": true,
- "requires": {
- "kind-of": "^6.0.0"
- }
- },
- "is-data-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
- "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
- "dev": true,
- "requires": {
- "kind-of": "^6.0.0"
- }
- },
- "is-descriptor": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
- "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
- "dev": true,
- "requires": {
- "is-accessor-descriptor": "^1.0.0",
- "is-data-descriptor": "^1.0.0",
- "kind-of": "^6.0.2"
- }
- }
- }
- },
- "snapdragon-util": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
- "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==",
- "dev": true,
- "requires": {
- "kind-of": "^3.2.0"
- },
- "dependencies": {
- "kind-of": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
- "dev": true,
- "requires": {
- "is-buffer": "^1.1.5"
- }
- }
- }
- },
- "source-map": {
- "version": "0.5.7",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
- "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
- "dev": true
- },
- "source-map-resolve": {
- "version": "0.5.3",
- "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz",
- "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==",
- "dev": true,
- "requires": {
- "atob": "^2.1.2",
- "decode-uri-component": "^0.2.0",
- "resolve-url": "^0.2.1",
- "source-map-url": "^0.4.0",
- "urix": "^0.1.0"
- }
- },
- "source-map-url": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz",
- "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==",
- "dev": true
- },
"spdx-correct": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz",
@@ -17244,34 +14567,13 @@
"integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==",
"dev": true
},
- "split-string": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
- "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==",
+ "split2": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz",
+ "integrity": "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==",
"dev": true,
"requires": {
- "extend-shallow": "^3.0.0"
- },
- "dependencies": {
- "extend-shallow": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
- "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
- "dev": true,
- "requires": {
- "assign-symbols": "^1.0.0",
- "is-extendable": "^1.0.1"
- }
- },
- "is-extendable": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
- "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
- "dev": true,
- "requires": {
- "is-plain-object": "^2.0.4"
- }
- }
+ "through2": "^2.0.2"
}
},
"sprintf-js": {
@@ -17280,22 +14582,18 @@
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
"dev": true
},
- "static-extend": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
- "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=",
- "dev": true,
- "requires": {
- "define-property": "^0.2.5",
- "object-copy": "^0.1.0"
- }
- },
"statuses": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
"dev": true
},
+ "stream-shift": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz",
+ "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==",
+ "dev": true
+ },
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
@@ -17358,15 +14656,6 @@
"ansi-regex": "^5.0.1"
}
},
- "strip-bom": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
- "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
- "dev": true,
- "requires": {
- "is-utf8": "^0.2.0"
- }
- },
"strip-final-newline": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
@@ -17425,6 +14714,16 @@
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
"dev": true
},
+ "through2": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
+ "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
+ "dev": true,
+ "requires": {
+ "readable-stream": "~2.3.6",
+ "xtend": "~4.0.1"
+ }
+ },
"titleize": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz",
@@ -17446,98 +14745,6 @@
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
"dev": true
},
- "to-object-path": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
- "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=",
- "dev": true,
- "requires": {
- "kind-of": "^3.0.2"
- },
- "dependencies": {
- "kind-of": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
- "dev": true,
- "requires": {
- "is-buffer": "^1.1.5"
- }
- }
- }
- },
- "to-regex": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz",
- "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==",
- "dev": true,
- "requires": {
- "define-property": "^2.0.2",
- "extend-shallow": "^3.0.2",
- "regex-not": "^1.0.2",
- "safe-regex": "^1.1.0"
- },
- "dependencies": {
- "define-property": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
- "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
- "dev": true,
- "requires": {
- "is-descriptor": "^1.0.2",
- "isobject": "^3.0.1"
- }
- },
- "extend-shallow": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
- "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
- "dev": true,
- "requires": {
- "assign-symbols": "^1.0.0",
- "is-extendable": "^1.0.1"
- }
- },
- "is-accessor-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
- "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
- "dev": true,
- "requires": {
- "kind-of": "^6.0.0"
- }
- },
- "is-data-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
- "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
- "dev": true,
- "requires": {
- "kind-of": "^6.0.0"
- }
- },
- "is-descriptor": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
- "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
- "dev": true,
- "requires": {
- "is-accessor-descriptor": "^1.0.0",
- "is-data-descriptor": "^1.0.0",
- "kind-of": "^6.0.2"
- }
- },
- "is-extendable": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
- "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
- "dev": true,
- "requires": {
- "is-plain-object": "^2.0.4"
- }
- }
- }
- },
"to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -17806,76 +15013,18 @@
"qs": "^6.4.0"
}
},
- "union-value": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
- "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==",
- "dev": true,
- "requires": {
- "arr-union": "^3.1.0",
- "get-value": "^2.0.6",
- "is-extendable": "^0.1.1",
- "set-value": "^2.0.1"
- }
- },
"unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
"dev": true
},
- "unset-value": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
- "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=",
- "dev": true,
- "requires": {
- "has-value": "^0.3.1",
- "isobject": "^3.0.0"
- },
- "dependencies": {
- "has-value": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
- "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=",
- "dev": true,
- "requires": {
- "get-value": "^2.0.3",
- "has-values": "^0.1.4",
- "isobject": "^2.0.0"
- },
- "dependencies": {
- "isobject": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
- "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
- "dev": true,
- "requires": {
- "isarray": "1.0.0"
- }
- }
- }
- },
- "has-values": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
- "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=",
- "dev": true
- }
- }
- },
"untildify": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
"integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==",
"dev": true
},
- "upath": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz",
- "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==",
- "dev": true
- },
"update-browserslist-db": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
@@ -17895,24 +15044,12 @@
"punycode": "^2.1.0"
}
},
- "urix": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
- "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=",
- "dev": true
- },
"url-join": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
"integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==",
"dev": true
},
- "use": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
- "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
- "dev": true
- },
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -18039,20 +15176,10 @@
"signal-exit": "^3.0.7"
}
},
- "xml2js": {
- "version": "0.4.23",
- "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
- "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
- "dev": true,
- "requires": {
- "sax": ">=0.6.0",
- "xmlbuilder": "~11.0.0"
- }
- },
- "xmlbuilder": {
- "version": "11.0.1",
- "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
- "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
+ "xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
"dev": true
},
"yallist": {
diff --git a/dom/webgpu/tests/cts/checkout/package.json b/dom/webgpu/tests/cts/checkout/package.json
index 21f74065af..8f2718ab37 100644
--- a/dom/webgpu/tests/cts/checkout/package.json
+++ b/dom/webgpu/tests/cts/checkout/package.json
@@ -3,15 +3,18 @@
"version": "0.1.0",
"description": "WebGPU Conformance Test Suite",
"scripts": {
- "test": "grunt pre",
- "check": "grunt check",
+ "test": "grunt all",
+ "all": "grunt all",
"standalone": "grunt standalone",
"wpt": "grunt wpt",
- "fix": "grunt fix",
+ "node": "grunt node",
+ "checks": "grunt checks",
"unittest": "grunt unittest",
+ "typecheck": "grunt typecheck",
+ "fix": "grunt fix",
"gen_wpt_cts_html": "node tools/gen_wpt_cts_html",
"gen_cache": "node tools/gen_cache",
- "tsdoc": "grunt run:tsdoc",
+ "tsdoc": "grunt tsdoc",
"start": "node tools/dev_server",
"dev": "node tools/dev_server"
},
@@ -46,7 +49,7 @@
"@types/serve-index": "^1.9.3",
"@typescript-eslint/eslint-plugin": "^6.9.1",
"@typescript-eslint/parser": "^6.9.1",
- "@webgpu/types": "^0.1.38",
+ "@webgpu/types": "^0.1.40",
"ansi-colors": "4.1.3",
"babel-plugin-add-header-comment": "^1.0.3",
"babel-plugin-const-enum": "^1.2.0",
@@ -59,11 +62,11 @@
"express": "^4.18.2",
"grunt": "^1.6.1",
"grunt-cli": "^1.4.3",
+ "grunt-concurrent": "^3.0.0",
"grunt-contrib-clean": "^2.0.1",
"grunt-contrib-copy": "^1.0.0",
"grunt-run": "^0.8.1",
"grunt-timer": "^0.6.0",
- "grunt-ts": "^6.0.0-beta.22",
"gts": "^5.2.0",
"http-server": "^14.1.1",
"morgan": "^1.10.0",
diff --git a/dom/webgpu/tests/cts/checkout/src/common/framework/fixture.ts b/dom/webgpu/tests/cts/checkout/src/common/framework/fixture.ts
index 77875e047d..616023e20c 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/framework/fixture.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/framework/fixture.ts
@@ -15,7 +15,8 @@ export type TestParams = {
type DestroyableObject =
| { destroy(): void }
| { close(): void }
- | { getExtension(extensionName: 'WEBGL_lose_context'): WEBGL_lose_context };
+ | { getExtension(extensionName: 'WEBGL_lose_context'): WEBGL_lose_context }
+ | HTMLVideoElement;
export class SubcaseBatchState {
constructor(
@@ -124,8 +125,12 @@ export class Fixture<S extends SubcaseBatchState = SubcaseBatchState> {
if (WEBGL_lose_context) WEBGL_lose_context.loseContext();
} else if ('destroy' in o) {
o.destroy();
- } else {
+ } else if ('close' in o) {
o.close();
+ } else {
+ // HTMLVideoElement
+ o.src = '';
+ o.srcObject = null;
}
}
}
@@ -161,6 +166,14 @@ export class Fixture<S extends SubcaseBatchState = SubcaseBatchState> {
this.rec.debug(new Error(msg));
}
+ /**
+ * Log an info message.
+ * **Use sparingly. Use `debug()` instead if logs are only needed with debug logging enabled.**
+ */
+ info(msg: string): void {
+ this.rec.info(new Error(msg));
+ }
+
/** Throws an exception marking the subcase as skipped. */
skip(msg: string): never {
throw new SkipTestCase(msg);
diff --git a/dom/webgpu/tests/cts/checkout/src/common/framework/test_config.ts b/dom/webgpu/tests/cts/checkout/src/common/framework/test_config.ts
index 2575418299..e6624ae120 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/framework/test_config.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/framework/test_config.ts
@@ -1,4 +1,9 @@
export type TestConfig = {
+ /**
+ * Enable debug-level logs (normally logged via `Fixture.debug()`).
+ */
+ enableDebugLogs: boolean;
+
maxSubcasesInFlight: number;
testHeartbeatCallback: () => void;
noRaceWithRejectOnTimeout: boolean;
@@ -21,12 +26,25 @@ export type TestConfig = {
* Whether or not we're running in compatibility mode.
*/
compatibility: boolean;
+
+ /**
+ * Whether or not to request a fallback adapter.
+ */
+ forceFallbackAdapter: boolean;
+
+ /**
+ * Whether to enable the `logToWebSocket` function used for out-of-band test logging.
+ */
+ logToWebSocket: boolean;
};
export const globalTestConfig: TestConfig = {
+ enableDebugLogs: false,
maxSubcasesInFlight: 500,
testHeartbeatCallback: () => {},
noRaceWithRejectOnTimeout: false,
unrollConstEvalLoops: false,
compatibility: false,
+ forceFallbackAdapter: false,
+ logToWebSocket: false,
};
diff --git a/dom/webgpu/tests/cts/checkout/src/common/internal/file_loader.ts b/dom/webgpu/tests/cts/checkout/src/common/internal/file_loader.ts
index b5e1b1a446..aae4b87995 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/internal/file_loader.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/internal/file_loader.ts
@@ -73,8 +73,9 @@ export abstract class TestFileLoader extends EventTarget {
query: TestQuery,
{
subqueriesToExpand = [],
+ fullyExpandSubtrees = [],
maxChunkTime = Infinity,
- }: { subqueriesToExpand?: string[]; maxChunkTime?: number } = {}
+ }: { subqueriesToExpand?: string[]; fullyExpandSubtrees?: string[]; maxChunkTime?: number } = {}
): Promise<TestTree> {
const tree = await loadTreeForQuery(this, query, {
subqueriesToExpand: subqueriesToExpand.map(s => {
@@ -82,6 +83,7 @@ export abstract class TestFileLoader extends EventTarget {
assert(q.level >= 2, () => `subqueriesToExpand entries should not be multi-file:\n ${q}`);
return q;
}),
+ fullyExpandSubtrees: fullyExpandSubtrees.map(s => parseQuery(s)),
maxChunkTime,
});
this.dispatchEvent(new MessageEvent<void>('finish'));
diff --git a/dom/webgpu/tests/cts/checkout/src/common/internal/logging/log_message.ts b/dom/webgpu/tests/cts/checkout/src/common/internal/logging/log_message.ts
index ee006cdeb3..b01c08b56e 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/internal/logging/log_message.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/internal/logging/log_message.ts
@@ -1,19 +1,36 @@
import { ErrorWithExtra } from '../../util/util.js';
import { extractImportantStackTrace } from '../stack.js';
+import { LogMessageRawData } from './result.js';
+
export class LogMessageWithStack extends Error {
readonly extra: unknown;
private stackHiddenMessage: string | undefined = undefined;
- constructor(name: string, ex: Error | ErrorWithExtra) {
- super(ex.message);
+ /**
+ * Wrap an Error (which was created to capture the stack at that point) into a
+ * LogMessageWithStack (which has extra stuff for good log messages).
+ *
+ * The original `ex.name` is ignored. Inclued it in the `name` parameter if it
+ * needs to be preserved.
+ */
+ static wrapError(name: string, ex: Error | ErrorWithExtra) {
+ return new LogMessageWithStack({
+ name,
+ message: ex.message,
+ stackHiddenMessage: undefined,
+ stack: ex.stack,
+ extra: 'extra' in ex ? ex.extra : undefined,
+ });
+ }
- this.name = name;
- this.stack = ex.stack;
- if ('extra' in ex) {
- this.extra = ex.extra;
- }
+ constructor(o: LogMessageRawData) {
+ super(o.message);
+ this.name = o.name;
+ this.stackHiddenMessage = o.stackHiddenMessage;
+ this.stack = o.stack;
+ this.extra = o.extra;
}
/** Set a flag so the stack is not printed in toJSON(). */
@@ -21,6 +38,11 @@ export class LogMessageWithStack extends Error {
this.stackHiddenMessage ??= stackHiddenMessage;
}
+ /**
+ * Print the message for display.
+ *
+ * Note: This is toJSON instead of toString to make it easy to save logs using JSON.stringify.
+ */
toJSON(): string {
let m = this.name;
if (this.message) m += ': ' + this.message;
@@ -33,6 +55,21 @@ export class LogMessageWithStack extends Error {
}
return m;
}
+
+ /**
+ * Flatten the message for sending over a message channel.
+ *
+ * Note `extra` may get mangled by postMessage.
+ */
+ toRawData(): LogMessageRawData {
+ return {
+ name: this.name,
+ message: this.message,
+ stackHiddenMessage: this.stackHiddenMessage,
+ stack: this.stack,
+ extra: this.extra,
+ };
+ }
}
/**
diff --git a/dom/webgpu/tests/cts/checkout/src/common/internal/logging/logger.ts b/dom/webgpu/tests/cts/checkout/src/common/internal/logging/logger.ts
index e4526cff54..6b95f48b74 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/internal/logging/logger.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/internal/logging/logger.ts
@@ -1,3 +1,4 @@
+import { globalTestConfig } from '../../framework/test_config.js';
import { version } from '../version.js';
import { LiveTestCaseResult } from './result.js';
@@ -6,8 +7,6 @@ import { TestCaseRecorder } from './test_case_recorder.js';
export type LogResults = Map<string, LiveTestCaseResult>;
export class Logger {
- static globalDebugMode: boolean = false;
-
readonly overriddenDebugMode: boolean | undefined;
readonly results: LogResults = new Map();
@@ -19,7 +18,7 @@ export class Logger {
const result: LiveTestCaseResult = { status: 'running', timems: -1 };
this.results.set(name, result);
return [
- new TestCaseRecorder(result, this.overriddenDebugMode ?? Logger.globalDebugMode),
+ new TestCaseRecorder(result, this.overriddenDebugMode ?? globalTestConfig.enableDebugLogs),
result,
];
}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/internal/logging/result.ts b/dom/webgpu/tests/cts/checkout/src/common/internal/logging/result.ts
index 3318e8c937..9968f3d359 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/internal/logging/result.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/internal/logging/result.ts
@@ -14,8 +14,24 @@ export interface LiveTestCaseResult extends TestCaseResult {
logs?: LogMessageWithStack[];
}
+/**
+ * Raw data for a test log message.
+ *
+ * This form is sendable over a message channel, except `extra` may get mangled.
+ */
+export interface LogMessageRawData {
+ name: string;
+ message: string;
+ stackHiddenMessage: string | undefined;
+ stack: string | undefined;
+ extra: unknown;
+}
+
+/**
+ * Test case results in a form sendable over a message channel.
+ *
+ * Note `extra` may get mangled by postMessage.
+ */
export interface TransferredTestCaseResult extends TestCaseResult {
- // When transferred from a worker, a LogMessageWithStack turns into a generic Error
- // (its prototype gets lost and replaced with Error).
- logs?: Error[];
+ logs?: LogMessageRawData[];
}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/internal/logging/test_case_recorder.ts b/dom/webgpu/tests/cts/checkout/src/common/internal/logging/test_case_recorder.ts
index f5c3252b5c..78f625269e 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/internal/logging/test_case_recorder.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/internal/logging/test_case_recorder.ts
@@ -45,8 +45,6 @@ export class TestCaseRecorder {
private logs: LogMessageWithStack[] = [];
private logLinesAtCurrentSeverity = 0;
private debugging = false;
- /** Used to dedup log messages which have identical stacks. */
- private messagesForPreviouslySeenStacks = new Map<string, LogMessageWithStack>();
constructor(result: LiveTestCaseResult, debugging: boolean) {
this.result = result;
@@ -143,13 +141,15 @@ export class TestCaseRecorder {
this.skipped(ex);
return;
}
- this.logImpl(LogSeverity.ThrewException, 'EXCEPTION', ex);
+ // logImpl will discard the original error's ex.name. Preserve it here.
+ const name = ex instanceof Error ? `EXCEPTION: ${ex.name}` : 'EXCEPTION';
+ this.logImpl(LogSeverity.ThrewException, name, ex);
}
private logImpl(level: LogSeverity, name: string, baseException: unknown): void {
assert(baseException instanceof Error, 'test threw a non-Error object');
globalTestConfig.testHeartbeatCallback();
- const logMessage = new LogMessageWithStack(name, baseException);
+ const logMessage = LogMessageWithStack.wrapError(name, baseException);
// Final case status should be the "worst" of all log entries.
if (this.inSubCase) {
diff --git a/dom/webgpu/tests/cts/checkout/src/common/internal/query/compare.ts b/dom/webgpu/tests/cts/checkout/src/common/internal/query/compare.ts
index a9419b87c1..f49833f5a2 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/internal/query/compare.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/internal/query/compare.ts
@@ -58,7 +58,10 @@ function compareOneLevel(ordering: Ordering, aIsBig: boolean, bIsBig: boolean):
return Ordering.Unordered;
}
-function comparePaths(a: readonly string[], b: readonly string[]): Ordering {
+/**
+ * Compare two file paths, or file-local test paths, returning an Ordering between the two.
+ */
+export function comparePaths(a: readonly string[], b: readonly string[]): Ordering {
const shorter = Math.min(a.length, b.length);
for (let i = 0; i < shorter; ++i) {
diff --git a/dom/webgpu/tests/cts/checkout/src/common/internal/query/parseQuery.ts b/dom/webgpu/tests/cts/checkout/src/common/internal/query/parseQuery.ts
index 996835b0ec..0a9b355804 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/internal/query/parseQuery.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/internal/query/parseQuery.ts
@@ -17,12 +17,49 @@ import {
import { kBigSeparator, kWildcard, kPathSeparator, kParamSeparator } from './separators.js';
import { validQueryPart } from './validQueryPart.js';
-export function parseQuery(s: string): TestQuery {
+/**
+ * converts foo/bar/src/webgpu/this/that/file.spec.ts to webgpu:this,that,file,*
+ */
+function convertPathToQuery(path: string) {
+ // removes .spec.ts and splits by directory separators.
+ const parts = path.substring(0, path.length - 8).split(/\/|\\/g);
+ // Gets parts only after the last `src`. Example: returns ['webgpu', 'foo', 'bar', 'test']
+ // for ['Users', 'me', 'src', 'cts', 'src', 'webgpu', 'foo', 'bar', 'test']
+ const partsAfterSrc = parts.slice(parts.lastIndexOf('src') + 1);
+ const suite = partsAfterSrc.shift();
+ return `${suite}:${partsAfterSrc.join(',')},*`;
+}
+
+/**
+ * If a query looks like a path (ends in .spec.ts and has directory separators)
+ * then convert try to convert it to a query.
+ */
+function convertPathLikeToQuery(queryOrPath: string) {
+ return queryOrPath.endsWith('.spec.ts') &&
+ (queryOrPath.includes('/') || queryOrPath.includes('\\'))
+ ? convertPathToQuery(queryOrPath)
+ : queryOrPath;
+}
+
+/**
+ * Convert long suite names (the part before the first colon) to the
+ * shortest last word
+ * foo.bar.moo:test,subtest,foo -> moo:test,subtest,foo
+ */
+function shortenSuiteName(query: string) {
+ const parts = query.split(':');
+ // converts foo.bar.moo to moo
+ const suite = parts.shift()?.replace(/.*\.(\w+)$/, '$1');
+ return [suite, ...parts].join(':');
+}
+
+export function parseQuery(queryLike: string): TestQuery {
try {
- return parseQueryImpl(s);
+ const query = shortenSuiteName(convertPathLikeToQuery(queryLike));
+ return parseQueryImpl(query);
} catch (ex) {
if (ex instanceof Error) {
- ex.message += '\n on: ' + s;
+ ex.message += `\n on: ${queryLike}`;
}
throw ex;
}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/internal/query/query.ts b/dom/webgpu/tests/cts/checkout/src/common/internal/query/query.ts
index 7c72a62f88..676ac46d38 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/internal/query/query.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/internal/query/query.ts
@@ -1,5 +1,5 @@
import { TestParams } from '../../framework/fixture.js';
-import { optionEnabled } from '../../runtime/helper/options.js';
+import { optionWorkerMode } from '../../runtime/helper/options.js';
import { assert, unreachable } from '../../util/util.js';
import { Expectation } from '../logging/result.js';
@@ -188,12 +188,12 @@ export function parseExpectationsForTestQuery(
assert(
expectationURL.pathname === wptURL.pathname,
`Invalid expectation path ${expectationURL.pathname}
-Expectation should be of the form path/to/cts.https.html?worker=0&q=suite:test_path:test_name:foo=1;bar=2;...
+Expectation should be of the form path/to/cts.https.html?debug=0&q=suite:test_path:test_name:foo=1;bar=2;...
`
);
const params = expectationURL.searchParams;
- if (optionEnabled('worker', params) !== optionEnabled('worker', wptURL.searchParams)) {
+ if (optionWorkerMode('worker', params) !== optionWorkerMode('worker', wptURL.searchParams)) {
continue;
}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/internal/test_group.ts b/dom/webgpu/tests/cts/checkout/src/common/internal/test_group.ts
index 632a822ef1..e1d0cde12d 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/internal/test_group.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/internal/test_group.ts
@@ -34,7 +34,7 @@ import { validQueryPart } from '../internal/query/validQueryPart.js';
import { DeepReadonly } from '../util/types.js';
import { assert, unreachable } from '../util/util.js';
-import { logToWebsocket } from './websocket_logger.js';
+import { logToWebSocket } from './websocket_logger.js';
export type RunFn = (
rec: TestCaseRecorder,
@@ -294,9 +294,11 @@ class TestBuilder<S extends SubcaseBatchState, F extends Fixture> {
(this.description ? this.description + '\n\n' : '') + 'TODO: .unimplemented()';
this.isUnimplemented = true;
- this.testFn = () => {
+ // Use the beforeFn to skip the test, so we don't have to iterate the subcases.
+ this.beforeFn = () => {
throw new SkipTestCase('test unimplemented');
};
+ this.testFn = () => {};
}
/** Perform various validation/"lint" chenks. */
@@ -350,7 +352,7 @@ class TestBuilder<S extends SubcaseBatchState, F extends Fixture> {
const testcaseStringUnique = stringifyPublicParamsUniquely(params);
assert(
!seen.has(testcaseStringUnique),
- `Duplicate public test case+subcase params for test ${testPathString}: ${testcaseString}`
+ `Duplicate public test case+subcase params for test ${testPathString}: ${testcaseString} (${caseQuery})`
);
seen.add(testcaseStringUnique);
}
@@ -737,7 +739,7 @@ class RunCaseSpecific implements RunCase {
timems: rec.result.timems,
nonskippedSubcaseCount: rec.nonskippedSubcaseCount,
};
- logToWebsocket(JSON.stringify(msg));
+ logToWebSocket(JSON.stringify(msg));
}
}
}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/internal/test_suite_listing.ts b/dom/webgpu/tests/cts/checkout/src/common/internal/test_suite_listing.ts
index 2d2b555366..c5a0e11448 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/internal/test_suite_listing.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/internal/test_suite_listing.ts
@@ -1,6 +1,6 @@
// A listing of all specs within a single suite. This is the (awaited) type of
// `groups` in '{cts,unittests}/listing.ts' and `listing` in the auto-generated
-// 'out/{cts,unittests}/listing.js' files (see tools/gen_listings).
+// 'out/{cts,unittests}/listing.js' files (see tools/gen_listings_and_webworkers).
export type TestSuiteListing = TestSuiteListingEntry[];
export type TestSuiteListingEntry = TestSuiteListingEntrySpec | TestSuiteListingEntryReadme;
diff --git a/dom/webgpu/tests/cts/checkout/src/common/internal/tree.ts b/dom/webgpu/tests/cts/checkout/src/common/internal/tree.ts
index 594837059c..f2fad59037 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/internal/tree.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/internal/tree.ts
@@ -286,8 +286,9 @@ export async function loadTreeForQuery(
queryToLoad: TestQuery,
{
subqueriesToExpand,
+ fullyExpandSubtrees = [],
maxChunkTime = Infinity,
- }: { subqueriesToExpand: TestQuery[]; maxChunkTime?: number }
+ }: { subqueriesToExpand: TestQuery[]; fullyExpandSubtrees?: TestQuery[]; maxChunkTime?: number }
): Promise<TestTree> {
const suite = queryToLoad.suite;
const specs = await loader.listing(suite);
@@ -303,6 +304,10 @@ export async function loadTreeForQuery(
// If toExpand == subquery, no expansion is needed (but it's still "seen").
if (ordering === Ordering.Equal) seenSubqueriesToExpand[i] = true;
return ordering !== Ordering.StrictSubset;
+ }) &&
+ fullyExpandSubtrees.every(toExpand => {
+ const ordering = compareQueries(toExpand, subquery);
+ return ordering === Ordering.Unordered;
});
// L0 = suite-level, e.g. suite:*
diff --git a/dom/webgpu/tests/cts/checkout/src/common/internal/websocket_logger.ts b/dom/webgpu/tests/cts/checkout/src/common/internal/websocket_logger.ts
index 30246df843..373378e7c2 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/internal/websocket_logger.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/internal/websocket_logger.ts
@@ -1,3 +1,5 @@
+import { globalTestConfig } from '../framework/test_config.js';
+
/**
* - 'uninitialized' means we haven't tried to connect yet
* - Promise means it's pending
@@ -8,12 +10,15 @@ let connection: Promise<WebSocket | 'failed'> | WebSocket | 'failed' | 'uninitia
'uninitialized';
/**
- * Log a string to a websocket at `localhost:59497`. See `tools/websocket-logger`.
+ * If the logToWebSocket option is enabled (?log_to_web_socket=1 in browser,
+ * --log-to-web-socket on command line, or enable it by default in options.ts),
+ * log a string to a websocket at `localhost:59497`. See `tools/websocket-logger`.
*
- * This does nothing if a connection couldn't be established on the first call.
+ * This does nothing if a logToWebSocket is not enabled, or if a connection
+ * couldn't be established on the first call.
*/
-export function logToWebsocket(msg: string) {
- if (connection === 'failed') {
+export function logToWebSocket(msg: string) {
+ if (!globalTestConfig.logToWebSocket || connection === 'failed') {
return;
}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/runtime/cmdline.ts b/dom/webgpu/tests/cts/checkout/src/common/runtime/cmdline.ts
index 44a73fb38b..2a00640f0e 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/runtime/cmdline.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/runtime/cmdline.ts
@@ -3,6 +3,7 @@
import * as fs from 'fs';
import { dataCache } from '../framework/data_cache.js';
+import { getResourcePath, setBaseResourcePath } from '../framework/resources.js';
import { globalTestConfig } from '../framework/test_config.js';
import { DefaultTestFileLoader } from '../internal/file_loader.js';
import { prettyPrintLog } from '../internal/logging/log_message.js';
@@ -37,6 +38,12 @@ Options:
return sys.exit(rc);
}
+if (!sys.existsSync('src/common/runtime/cmdline.ts')) {
+ console.log('Must be run from repository root');
+ usage(1);
+}
+setBaseResourcePath('out-node/resources');
+
// The interface that exposes creation of the GPU, and optional interface to code coverage.
interface GPUProviderModule {
// @returns a GPU with the given flags
@@ -60,12 +67,10 @@ Colors.enabled = false;
let verbose = false;
let emitCoverage = false;
let listMode: listModes = 'none';
-let debug = false;
let printJSON = false;
let quiet = false;
let loadWebGPUExpectations: Promise<unknown> | undefined = undefined;
let gpuProviderModule: GPUProviderModule | undefined = undefined;
-let dataPath: string | undefined = undefined;
const queries: string[] = [];
const gpuProviderFlags: string[] = [];
@@ -83,9 +88,7 @@ for (let i = 0; i < sys.args.length; ++i) {
} else if (a === '--list-unimplemented') {
listMode = 'unimplemented';
} else if (a === '--debug') {
- debug = true;
- } else if (a === '--data') {
- dataPath = sys.args[++i];
+ globalTestConfig.enableDebugLogs = true;
} else if (a === '--print-json') {
printJSON = true;
} else if (a === '--expectations') {
@@ -102,6 +105,10 @@ for (let i = 0; i < sys.args.length; ++i) {
globalTestConfig.unrollConstEvalLoops = true;
} else if (a === '--compat') {
globalTestConfig.compatibility = true;
+ } else if (a === '--force-fallback-adapter') {
+ globalTestConfig.forceFallbackAdapter = true;
+ } else if (a === '--log-to-websocket') {
+ globalTestConfig.logToWebSocket = true;
} else {
console.log('unrecognized flag: ', a);
usage(1);
@@ -113,9 +120,12 @@ for (let i = 0; i < sys.args.length; ++i) {
let codeCoverage: CodeCoverageProvider | undefined = undefined;
-if (globalTestConfig.compatibility) {
+if (globalTestConfig.compatibility || globalTestConfig.forceFallbackAdapter) {
// MAINTENANCE_TODO: remove the cast once compatibilityMode is officially added
- setDefaultRequestAdapterOptions({ compatibilityMode: true } as GPURequestAdapterOptions);
+ setDefaultRequestAdapterOptions({
+ compatibilityMode: globalTestConfig.compatibility,
+ forceFallbackAdapter: globalTestConfig.forceFallbackAdapter,
+ } as GPURequestAdapterOptions);
}
if (gpuProviderModule) {
@@ -132,21 +142,20 @@ Did you remember to build with code coverage instrumentation enabled?`
}
}
-if (dataPath !== undefined) {
- dataCache.setStore({
- load: (path: string) => {
- return new Promise<Uint8Array>((resolve, reject) => {
- fs.readFile(`${dataPath}/${path}`, (err, data) => {
- if (err !== null) {
- reject(err.message);
- } else {
- resolve(data);
- }
- });
+dataCache.setStore({
+ load: (path: string) => {
+ return new Promise<Uint8Array>((resolve, reject) => {
+ fs.readFile(getResourcePath(`cache/${path}`), (err, data) => {
+ if (err !== null) {
+ reject(err.message);
+ } else {
+ resolve(data);
+ }
});
- },
- });
-}
+ });
+ },
+});
+
if (verbose) {
dataCache.setDebugLogger(console.log);
}
@@ -166,7 +175,6 @@ if (queries.length === 0) {
filterQuery
);
- Logger.globalDebugMode = debug;
const log = new Logger();
const failed: Array<[string, LiveTestCaseResult]> = [];
diff --git a/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/options.ts b/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/options.ts
index 38974b803f..4a82c7d292 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/options.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/options.ts
@@ -1,3 +1,5 @@
+import { unreachable } from '../../util/util.js';
+
let windowURL: URL | undefined = undefined;
function getWindowURL() {
if (windowURL === undefined) {
@@ -6,6 +8,7 @@ function getWindowURL() {
return windowURL;
}
+/** Parse a runner option that is always boolean-typed. False if missing or '0'. */
export function optionEnabled(
opt: string,
searchParams: URLSearchParams = getWindowURL().searchParams
@@ -14,30 +17,55 @@ export function optionEnabled(
return val !== null && val !== '0';
}
+/** Parse a runner option that is string-typed. If the option is missing, returns `null`. */
export function optionString(
opt: string,
searchParams: URLSearchParams = getWindowURL().searchParams
-): string {
- return searchParams.get(opt) || '';
+): string | null {
+ return searchParams.get(opt);
+}
+
+/** Runtime modes for running tests in different types of workers. */
+export type WorkerMode = 'dedicated' | 'service' | 'shared';
+/** Parse a runner option for different worker modes (as in `?worker=shared`). Null if no worker. */
+export function optionWorkerMode(
+ opt: string,
+ searchParams: URLSearchParams = getWindowURL().searchParams
+): WorkerMode | null {
+ const value = searchParams.get(opt);
+ if (value === null || value === '0') {
+ return null;
+ } else if (value === 'service') {
+ return 'service';
+ } else if (value === 'shared') {
+ return 'shared';
+ } else if (value === '' || value === '1' || value === 'dedicated') {
+ return 'dedicated';
+ }
+ unreachable('invalid worker= option value');
}
/**
* The possible options for the tests.
*/
export interface CTSOptions {
- worker: boolean;
+ worker: WorkerMode | null;
debug: boolean;
compatibility: boolean;
+ forceFallbackAdapter: boolean;
unrollConstEvalLoops: boolean;
- powerPreference?: GPUPowerPreference | '';
+ powerPreference: GPUPowerPreference | null;
+ logToWebSocket: boolean;
}
export const kDefaultCTSOptions: CTSOptions = {
- worker: false,
+ worker: null,
debug: true,
compatibility: false,
+ forceFallbackAdapter: false,
unrollConstEvalLoops: false,
- powerPreference: '',
+ powerPreference: null,
+ logToWebSocket: false,
};
/**
@@ -45,8 +73,8 @@ export const kDefaultCTSOptions: CTSOptions = {
*/
export interface OptionInfo {
description: string;
- parser?: (key: string, searchParams?: URLSearchParams) => boolean | string;
- selectValueDescriptions?: { value: string; description: string }[];
+ parser?: (key: string, searchParams?: URLSearchParams) => boolean | string | null;
+ selectValueDescriptions?: { value: string | null; description: string }[];
}
/**
@@ -59,19 +87,30 @@ export type OptionsInfos<Type> = Record<keyof Type, OptionInfo>;
* Options to the CTS.
*/
export const kCTSOptionsInfo: OptionsInfos<CTSOptions> = {
- worker: { description: 'run in a worker' },
+ worker: {
+ description: 'run in a worker',
+ parser: optionWorkerMode,
+ selectValueDescriptions: [
+ { value: null, description: 'no worker' },
+ { value: 'dedicated', description: 'dedicated worker' },
+ { value: 'shared', description: 'shared worker' },
+ { value: 'service', description: 'service worker' },
+ ],
+ },
debug: { description: 'show more info' },
compatibility: { description: 'run in compatibility mode' },
+ forceFallbackAdapter: { description: 'pass forceFallbackAdapter: true to requestAdapter' },
unrollConstEvalLoops: { description: 'unroll const eval loops in WGSL' },
powerPreference: {
description: 'set default powerPreference for some tests',
parser: optionString,
selectValueDescriptions: [
- { value: '', description: 'default' },
+ { value: null, description: 'default' },
{ value: 'low-power', description: 'low-power' },
{ value: 'high-performance', description: 'high-performance' },
],
},
+ logToWebSocket: { description: 'send some logs to ws://localhost:59497/' },
};
/**
@@ -95,7 +134,7 @@ function getOptionsInfoFromSearchString<Type extends CTSOptions>(
searchString: string
): Type {
const searchParams = new URLSearchParams(searchString);
- const optionValues: Record<string, boolean | string> = {};
+ const optionValues: Record<string, boolean | string | null> = {};
for (const [optionName, info] of Object.entries(optionsInfos)) {
const parser = info.parser || optionEnabled;
optionValues[optionName] = parser(camelCaseToSnakeCase(optionName), searchParams);
diff --git a/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/test_worker-worker.ts b/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/test_worker-worker.ts
index e8d187ea7e..ebc206c3b2 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/test_worker-worker.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/test_worker-worker.ts
@@ -1,15 +1,11 @@
import { setBaseResourcePath } from '../../framework/resources.js';
-import { globalTestConfig } from '../../framework/test_config.js';
import { DefaultTestFileLoader } from '../../internal/file_loader.js';
-import { Logger } from '../../internal/logging/logger.js';
import { parseQuery } from '../../internal/query/parseQuery.js';
-import { TestQueryWithExpectation } from '../../internal/query/query.js';
-import { setDefaultRequestAdapterOptions } from '../../util/navigator_gpu.js';
import { assert } from '../../util/util.js';
-import { CTSOptions } from './options.js';
+import { setupWorkerEnvironment, WorkerTestRunRequest } from './utils_worker.js';
-// Should be DedicatedWorkerGlobalScope, but importing lib "webworker" conflicts with lib "dom".
+// Should be WorkerGlobalScope, but importing lib "webworker" conflicts with lib "dom".
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
declare const self: any;
@@ -17,25 +13,10 @@ const loader = new DefaultTestFileLoader();
setBaseResourcePath('../../../resources');
-self.onmessage = async (ev: MessageEvent) => {
- const query: string = ev.data.query;
- const expectations: TestQueryWithExpectation[] = ev.data.expectations;
- const ctsOptions: CTSOptions = ev.data.ctsOptions;
+async function reportTestResults(this: MessagePort | Worker, ev: MessageEvent) {
+ const { query, expectations, ctsOptions } = ev.data as WorkerTestRunRequest;
- const { debug, unrollConstEvalLoops, powerPreference, compatibility } = ctsOptions;
- globalTestConfig.unrollConstEvalLoops = unrollConstEvalLoops;
- globalTestConfig.compatibility = compatibility;
-
- Logger.globalDebugMode = debug;
- const log = new Logger();
-
- if (powerPreference || compatibility) {
- setDefaultRequestAdapterOptions({
- ...(powerPreference && { powerPreference }),
- // MAINTENANCE_TODO: Change this to whatever the option ends up being
- ...(compatibility && { compatibilityMode: true }),
- });
- }
+ const log = setupWorkerEnvironment(ctsOptions);
const testcases = Array.from(await loader.loadCases(parseQuery(query)));
assert(testcases.length === 1, 'worker query resulted in != 1 cases');
@@ -44,5 +25,23 @@ self.onmessage = async (ev: MessageEvent) => {
const [rec, result] = log.record(testcase.query.toString());
await testcase.run(rec, expectations);
- self.postMessage({ query, result });
+ this.postMessage({
+ query,
+ result: {
+ ...result,
+ logs: result.logs?.map(l => l.toRawData()),
+ },
+ });
+}
+
+self.onmessage = (ev: MessageEvent) => {
+ void reportTestResults.call(ev.source || self, ev);
+};
+
+self.onconnect = (event: MessageEvent) => {
+ const port = event.ports[0];
+
+ port.onmessage = (ev: MessageEvent) => {
+ void reportTestResults.call(port, ev);
+ };
};
diff --git a/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/test_worker.ts b/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/test_worker.ts
index 9bbcab0946..f9a44bb7bc 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/test_worker.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/test_worker.ts
@@ -2,48 +2,190 @@ import { LogMessageWithStack } from '../../internal/logging/log_message.js';
import { TransferredTestCaseResult, LiveTestCaseResult } from '../../internal/logging/result.js';
import { TestCaseRecorder } from '../../internal/logging/test_case_recorder.js';
import { TestQueryWithExpectation } from '../../internal/query/query.js';
+import { timeout } from '../../util/timeout.js';
+import { assert } from '../../util/util.js';
-import { CTSOptions, kDefaultCTSOptions } from './options.js';
+import { CTSOptions, WorkerMode, kDefaultCTSOptions } from './options.js';
+import { WorkerTestRunRequest } from './utils_worker.js';
-export class TestWorker {
- private readonly ctsOptions: CTSOptions;
- private readonly worker: Worker;
- private readonly resolvers = new Map<string, (result: LiveTestCaseResult) => void>();
+/** Query all currently-registered service workers, and unregister them. */
+function unregisterAllServiceWorkers() {
+ void navigator.serviceWorker.getRegistrations().then(registrations => {
+ for (const registration of registrations) {
+ void registration.unregister();
+ }
+ });
+}
- constructor(ctsOptions?: CTSOptions) {
- this.ctsOptions = { ...(ctsOptions || kDefaultCTSOptions), ...{ worker: true } };
- const selfPath = import.meta.url;
- const selfPathDir = selfPath.substring(0, selfPath.lastIndexOf('/'));
- const workerPath = selfPathDir + '/test_worker-worker.js';
- this.worker = new Worker(workerPath, { type: 'module' });
- this.worker.onmessage = ev => {
- const query: string = ev.data.query;
- const result: TransferredTestCaseResult = ev.data.result;
- if (result.logs) {
- for (const l of result.logs) {
- Object.setPrototypeOf(l, LogMessageWithStack.prototype);
- }
- }
- this.resolvers.get(query)!(result as LiveTestCaseResult);
+// NOTE: This code runs on startup for any runtime with worker support. Here, we use that chance to
+// delete any leaked service workers, and register to clean up after ourselves at shutdown.
+unregisterAllServiceWorkers();
+window.addEventListener('beforeunload', () => {
+ unregisterAllServiceWorkers();
+});
+
+abstract class TestBaseWorker {
+ protected readonly ctsOptions: CTSOptions;
+ protected readonly resolvers = new Map<string, (result: LiveTestCaseResult) => void>();
+
+ constructor(worker: WorkerMode, ctsOptions?: CTSOptions) {
+ this.ctsOptions = { ...(ctsOptions || kDefaultCTSOptions), ...{ worker } };
+ }
- // MAINTENANCE_TODO(kainino0x): update the Logger with this result (or don't have a logger and
- // update the entire results JSON somehow at some point).
+ onmessage(ev: MessageEvent) {
+ const query: string = ev.data.query;
+ const transferredResult: TransferredTestCaseResult = ev.data.result;
+
+ const result: LiveTestCaseResult = {
+ status: transferredResult.status,
+ timems: transferredResult.timems,
+ logs: transferredResult.logs?.map(l => new LogMessageWithStack(l)),
};
+
+ this.resolvers.get(query)!(result);
+ this.resolvers.delete(query);
+
+ // MAINTENANCE_TODO(kainino0x): update the Logger with this result (or don't have a logger and
+ // update the entire results JSON somehow at some point).
}
- async run(
- rec: TestCaseRecorder,
+ makeRequestAndRecordResult(
+ target: MessagePort | Worker | ServiceWorker,
query: string,
- expectations: TestQueryWithExpectation[] = []
- ): Promise<void> {
- this.worker.postMessage({
+ expectations: TestQueryWithExpectation[]
+ ): Promise<LiveTestCaseResult> {
+ const request: WorkerTestRunRequest = {
query,
expectations,
ctsOptions: this.ctsOptions,
- });
- const workerResult = await new Promise<LiveTestCaseResult>(resolve => {
+ };
+ target.postMessage(request);
+
+ return new Promise<LiveTestCaseResult>(resolve => {
+ assert(!this.resolvers.has(query), "can't request same query twice simultaneously");
this.resolvers.set(query, resolve);
});
- rec.injectResult(workerResult);
+ }
+
+ async run(
+ rec: TestCaseRecorder,
+ query: string,
+ expectations: TestQueryWithExpectation[] = []
+ ): Promise<void> {
+ try {
+ rec.injectResult(await this.runImpl(query, expectations));
+ } catch (ex) {
+ rec.start();
+ rec.threw(ex);
+ rec.finish();
+ }
+ }
+
+ protected abstract runImpl(
+ query: string,
+ expectations: TestQueryWithExpectation[]
+ ): Promise<LiveTestCaseResult>;
+}
+
+export class TestDedicatedWorker extends TestBaseWorker {
+ private readonly worker: Worker | Error;
+
+ constructor(ctsOptions?: CTSOptions) {
+ super('dedicated', ctsOptions);
+ try {
+ if (typeof Worker === 'undefined') {
+ throw new Error('Dedicated Workers not available');
+ }
+
+ const selfPath = import.meta.url;
+ const selfPathDir = selfPath.substring(0, selfPath.lastIndexOf('/'));
+ const workerPath = selfPathDir + '/test_worker-worker.js';
+ this.worker = new Worker(workerPath, { type: 'module' });
+ this.worker.onmessage = ev => this.onmessage(ev);
+ } catch (ex) {
+ assert(ex instanceof Error);
+ // Save the exception to re-throw in runImpl().
+ this.worker = ex;
+ }
+ }
+
+ override runImpl(query: string, expectations: TestQueryWithExpectation[] = []) {
+ if (this.worker instanceof Worker) {
+ return this.makeRequestAndRecordResult(this.worker, query, expectations);
+ } else {
+ throw this.worker;
+ }
+ }
+}
+
+/** @deprecated Use TestDedicatedWorker instead. */
+export class TestWorker extends TestDedicatedWorker {}
+
+export class TestSharedWorker extends TestBaseWorker {
+ /** MessagePort to the SharedWorker, or an Error if it couldn't be initialized. */
+ private readonly port: MessagePort | Error;
+
+ constructor(ctsOptions?: CTSOptions) {
+ super('shared', ctsOptions);
+ try {
+ if (typeof SharedWorker === 'undefined') {
+ throw new Error('Shared Workers not available');
+ }
+
+ const selfPath = import.meta.url;
+ const selfPathDir = selfPath.substring(0, selfPath.lastIndexOf('/'));
+ const workerPath = selfPathDir + '/test_worker-worker.js';
+ const worker = new SharedWorker(workerPath, { type: 'module' });
+ this.port = worker.port;
+ this.port.start();
+ this.port.onmessage = ev => this.onmessage(ev);
+ } catch (ex) {
+ assert(ex instanceof Error);
+ // Save the exception to re-throw in runImpl().
+ this.port = ex;
+ }
+ }
+
+ override runImpl(query: string, expectations: TestQueryWithExpectation[] = []) {
+ if (this.port instanceof MessagePort) {
+ return this.makeRequestAndRecordResult(this.port, query, expectations);
+ } else {
+ throw this.port;
+ }
+ }
+}
+
+export class TestServiceWorker extends TestBaseWorker {
+ constructor(ctsOptions?: CTSOptions) {
+ super('service', ctsOptions);
+ }
+
+ override async runImpl(query: string, expectations: TestQueryWithExpectation[] = []) {
+ if (!('serviceWorker' in navigator)) {
+ throw new Error('Service Workers not available');
+ }
+ const [suite, name] = query.split(':', 2);
+ const fileName = name.split(',').join('/');
+
+ const selfPath = import.meta.url;
+ const selfPathDir = selfPath.substring(0, selfPath.lastIndexOf('/'));
+ // Construct the path to the worker file, then use URL to resolve the `../` components.
+ const serviceWorkerURL = new URL(
+ `${selfPathDir}/../../../${suite}/webworker/${fileName}.worker.js`
+ ).toString();
+
+ // If a registration already exists for this path, it will be ignored.
+ const registration = await navigator.serviceWorker.register(serviceWorkerURL, {
+ type: 'module',
+ });
+ // Make sure the registration we just requested is active. (We don't worry about it being
+ // outdated from a previous page load, because we wipe all service workers on shutdown/startup.)
+ while (!registration.active || registration.active.scriptURL !== serviceWorkerURL) {
+ await new Promise(resolve => timeout(resolve, 0));
+ }
+ const serviceWorker = registration.active;
+
+ navigator.serviceWorker.onmessage = ev => this.onmessage(ev);
+ return this.makeRequestAndRecordResult(serviceWorker, query, expectations);
}
}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/utils_worker.ts b/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/utils_worker.ts
new file mode 100644
index 0000000000..13880635bc
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/utils_worker.ts
@@ -0,0 +1,35 @@
+import { globalTestConfig } from '../../framework/test_config.js';
+import { Logger } from '../../internal/logging/logger.js';
+import { TestQueryWithExpectation } from '../../internal/query/query.js';
+import { setDefaultRequestAdapterOptions } from '../../util/navigator_gpu.js';
+
+import { CTSOptions } from './options.js';
+
+export interface WorkerTestRunRequest {
+ query: string;
+ expectations: TestQueryWithExpectation[];
+ ctsOptions: CTSOptions;
+}
+
+/**
+ * Set config environment for workers with ctsOptions and return a Logger.
+ */
+export function setupWorkerEnvironment(ctsOptions: CTSOptions): Logger {
+ const { powerPreference, compatibility } = ctsOptions;
+ globalTestConfig.enableDebugLogs = ctsOptions.debug;
+ globalTestConfig.unrollConstEvalLoops = ctsOptions.unrollConstEvalLoops;
+ globalTestConfig.compatibility = compatibility;
+ globalTestConfig.logToWebSocket = ctsOptions.logToWebSocket;
+
+ const log = new Logger();
+
+ if (powerPreference || compatibility) {
+ setDefaultRequestAdapterOptions({
+ ...(powerPreference && { powerPreference }),
+ // MAINTENANCE_TODO: Change this to whatever the option ends up being
+ ...(compatibility && { compatibilityMode: true }),
+ });
+ }
+
+ return log;
+}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/wrap_for_worker.ts b/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/wrap_for_worker.ts
new file mode 100644
index 0000000000..5f600fe89d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/wrap_for_worker.ts
@@ -0,0 +1,54 @@
+import { Fixture } from '../../framework/fixture';
+import { LogMessageWithStack } from '../../internal/logging/log_message.js';
+import { comparePaths, comparePublicParamsPaths, Ordering } from '../../internal/query/compare.js';
+import { parseQuery } from '../../internal/query/parseQuery.js';
+import { TestQuerySingleCase } from '../../internal/query/query.js';
+import { TestGroup } from '../../internal/test_group.js';
+import { assert } from '../../util/util.js';
+
+import { setupWorkerEnvironment, WorkerTestRunRequest } from './utils_worker.js';
+
+/**
+ * Sets up the currently running Web Worker to wrap the TestGroup object `g`.
+ * `g` is the `g` exported from a `.spec.ts` file: a TestGroupBuilder<F> interface,
+ * which underneath is actually a TestGroup<F> object.
+ *
+ * This is used in the generated `.worker.js` files that are generated to use as service workers.
+ */
+export function wrapTestGroupForWorker(g: TestGroup<Fixture>) {
+ self.onmessage = async (ev: MessageEvent) => {
+ const { query, expectations, ctsOptions } = ev.data as WorkerTestRunRequest;
+ try {
+ const log = setupWorkerEnvironment(ctsOptions);
+
+ const testQuery = parseQuery(query);
+ assert(testQuery instanceof TestQuerySingleCase);
+ let testcase = null;
+ for (const t of g.iterate()) {
+ if (comparePaths(t.testPath, testQuery.testPathParts) !== Ordering.Equal) {
+ continue;
+ }
+ for (const c of t.iterate(testQuery.params)) {
+ if (comparePublicParamsPaths(c.id.params, testQuery.params) === Ordering.Equal) {
+ testcase = c;
+ }
+ }
+ }
+ assert(!!testcase, 'testcase not found');
+ const [rec, result] = log.record(query);
+ await testcase.run(rec, testQuery, expectations);
+
+ ev.source?.postMessage({ query, result });
+ } catch (thrown) {
+ const ex = thrown instanceof Error ? thrown : new Error(`${thrown}`);
+ ev.source?.postMessage({
+ query,
+ result: {
+ status: 'fail',
+ timems: 0,
+ logs: [LogMessageWithStack.wrapError('INTERNAL', ex)],
+ },
+ });
+ }
+ };
+}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/runtime/server.ts b/dom/webgpu/tests/cts/checkout/src/common/runtime/server.ts
index 8310784e3a..3999b285ba 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/runtime/server.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/runtime/server.ts
@@ -5,6 +5,7 @@ import * as http from 'http';
import { AddressInfo } from 'net';
import { dataCache } from '../framework/data_cache.js';
+import { getResourcePath, setBaseResourcePath } from '../framework/resources.js';
import { globalTestConfig } from '../framework/test_config.js';
import { DefaultTestFileLoader } from '../internal/file_loader.js';
import { prettyPrintLog } from '../internal/logging/log_message.js';
@@ -20,21 +21,23 @@ import sys from './helper/sys.js';
function usage(rc: number): never {
console.log(`Usage:
- tools/run_${sys.type} [OPTIONS...]
+ tools/server [OPTIONS...]
Options:
--colors Enable ANSI colors in output.
--compat Run tests in compatibility mode.
--coverage Add coverage data to each result.
- --data Path to the data cache directory.
--verbose Print result/log of every test as it runs.
+ --debug Include debug messages in logging.
--gpu-provider Path to node module that provides the GPU implementation.
--gpu-provider-flag Flag to set on the gpu-provider as <flag>=<value>
--unroll-const-eval-loops Unrolls loops in constant-evaluation shader execution tests
--u Flag to set on the gpu-provider as <flag>=<value>
Provides an HTTP server used for running tests via an HTTP RPC interface
-To run a test, perform an HTTP GET or POST at the URL:
- http://localhost:port/run?<test-name>
+First, load some tree or subtree of tests:
+ http://localhost:port/load?unittests:basic:*
+To run a single test case, perform an HTTP GET or POST at the URL:
+ http://localhost:port/run?unittests:basic:test,sync
To shutdown the server perform an HTTP GET or POST at the URL:
http://localhost:port/terminate
`);
@@ -46,6 +49,8 @@ interface RunResult {
status: Status;
// Any additional messages printed
message: string;
+ // The time it took to execute the test
+ durationMS: number;
// Code coverage data, if the server was started with `--coverage`
// This data is opaque (implementation defined).
coverageData?: string;
@@ -71,13 +76,13 @@ if (!sys.existsSync('src/common/runtime/cmdline.ts')) {
console.log('Must be run from repository root');
usage(1);
}
+setBaseResourcePath('out-node/resources');
Colors.enabled = false;
let emitCoverage = false;
let verbose = false;
let gpuProviderModule: GPUProviderModule | undefined = undefined;
-let dataPath: string | undefined = undefined;
const gpuProviderFlags: string[] = [];
for (let i = 0; i < sys.args.length; ++i) {
@@ -89,13 +94,17 @@ for (let i = 0; i < sys.args.length; ++i) {
globalTestConfig.compatibility = true;
} else if (a === '--coverage') {
emitCoverage = true;
- } else if (a === '--data') {
- dataPath = sys.args[++i];
+ } else if (a === '--force-fallback-adapter') {
+ globalTestConfig.forceFallbackAdapter = true;
+ } else if (a === '--log-to-websocket') {
+ globalTestConfig.logToWebSocket = true;
} else if (a === '--gpu-provider') {
const modulePath = sys.args[++i];
gpuProviderModule = require(modulePath);
} else if (a === '--gpu-provider-flag') {
gpuProviderFlags.push(sys.args[++i]);
+ } else if (a === '--debug') {
+ globalTestConfig.enableDebugLogs = true;
} else if (a === '--unroll-const-eval-loops') {
globalTestConfig.unrollConstEvalLoops = true;
} else if (a === '--help') {
@@ -110,9 +119,12 @@ for (let i = 0; i < sys.args.length; ++i) {
let codeCoverage: CodeCoverageProvider | undefined = undefined;
-if (globalTestConfig.compatibility) {
+if (globalTestConfig.compatibility || globalTestConfig.forceFallbackAdapter) {
// MAINTENANCE_TODO: remove the cast once compatibilityMode is officially added
- setDefaultRequestAdapterOptions({ compatibilityMode: true } as GPURequestAdapterOptions);
+ setDefaultRequestAdapterOptions({
+ compatibilityMode: globalTestConfig.compatibility,
+ forceFallbackAdapter: globalTestConfig.forceFallbackAdapter,
+ } as GPURequestAdapterOptions);
}
if (gpuProviderModule) {
@@ -130,28 +142,26 @@ Did you remember to build with code coverage instrumentation enabled?`
}
}
-if (dataPath !== undefined) {
- dataCache.setStore({
- load: (path: string) => {
- return new Promise<Uint8Array>((resolve, reject) => {
- fs.readFile(`${dataPath}/${path}`, (err, data) => {
- if (err !== null) {
- reject(err.message);
- } else {
- resolve(data);
- }
- });
+dataCache.setStore({
+ load: (path: string) => {
+ return new Promise<Uint8Array>((resolve, reject) => {
+ fs.readFile(getResourcePath(`cache/${path}`), (err, data) => {
+ if (err !== null) {
+ reject(err.message);
+ } else {
+ resolve(data);
+ }
});
- },
- });
-}
+ });
+ },
+});
+
if (verbose) {
dataCache.setDebugLogger(console.log);
}
// eslint-disable-next-line @typescript-eslint/require-await
(async () => {
- Logger.globalDebugMode = verbose;
const log = new Logger();
const testcases = new Map<string, TestTreeLeaf>();
@@ -198,14 +208,16 @@ if (verbose) {
if (codeCoverage !== undefined) {
codeCoverage.begin();
}
+ const start = performance.now();
const result = await runTestcase(testcase);
+ const durationMS = performance.now() - start;
const coverageData = codeCoverage !== undefined ? codeCoverage.end() : undefined;
let message = '';
if (result.logs !== undefined) {
message = result.logs.map(log => prettyPrintLog(log)).join('\n');
}
const status = result.status;
- const res: RunResult = { status, message, coverageData };
+ const res: RunResult = { status, message, durationMS, coverageData };
response.statusCode = 200;
response.end(JSON.stringify(res));
} else {
diff --git a/dom/webgpu/tests/cts/checkout/src/common/runtime/standalone.ts b/dom/webgpu/tests/cts/checkout/src/common/runtime/standalone.ts
index 0376f92dda..dc75e6fd01 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/runtime/standalone.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/runtime/standalone.ts
@@ -2,7 +2,7 @@
/* eslint no-console: "off" */
import { dataCache } from '../framework/data_cache.js';
-import { setBaseResourcePath } from '../framework/resources.js';
+import { getResourcePath, setBaseResourcePath } from '../framework/resources.js';
import { globalTestConfig } from '../framework/test_config.js';
import { DefaultTestFileLoader } from '../internal/file_loader.js';
import { Logger } from '../internal/logging/logger.js';
@@ -21,7 +21,7 @@ import {
OptionsInfos,
camelCaseToSnakeCase,
} from './helper/options.js';
-import { TestWorker } from './helper/test_worker.js';
+import { TestDedicatedWorker, TestSharedWorker, TestServiceWorker } from './helper/test_worker.js';
const rootQuerySpec = 'webgpu:*';
let promptBeforeReload = false;
@@ -47,16 +47,26 @@ const { queries: qs, options } = parseSearchParamLikeWithOptions(
kStandaloneOptionsInfos,
window.location.search || rootQuerySpec
);
-const { runnow, debug, unrollConstEvalLoops, powerPreference, compatibility } = options;
-globalTestConfig.unrollConstEvalLoops = unrollConstEvalLoops;
+const { runnow, powerPreference, compatibility, forceFallbackAdapter } = options;
+globalTestConfig.enableDebugLogs = options.debug;
+globalTestConfig.unrollConstEvalLoops = options.unrollConstEvalLoops;
globalTestConfig.compatibility = compatibility;
+globalTestConfig.logToWebSocket = options.logToWebSocket;
-Logger.globalDebugMode = debug;
const logger = new Logger();
setBaseResourcePath('../out/resources');
-const worker = options.worker ? new TestWorker(options) : undefined;
+const testWorker =
+ options.worker === null
+ ? null
+ : options.worker === 'dedicated'
+ ? new TestDedicatedWorker(options)
+ : options.worker === 'shared'
+ ? new TestSharedWorker(options)
+ : options.worker === 'service'
+ ? new TestServiceWorker(options)
+ : unreachable();
const autoCloseOnPass = document.getElementById('autoCloseOnPass') as HTMLInputElement;
const resultsVis = document.getElementById('resultsVis')!;
@@ -70,17 +80,18 @@ stopButtonElem.addEventListener('click', () => {
stopRequested = true;
});
-if (powerPreference || compatibility) {
+if (powerPreference || compatibility || forceFallbackAdapter) {
setDefaultRequestAdapterOptions({
...(powerPreference && { powerPreference }),
// MAINTENANCE_TODO: Change this to whatever the option ends up being
...(compatibility && { compatibilityMode: true }),
+ ...(forceFallbackAdapter && { forceFallbackAdapter: true }),
});
}
dataCache.setStore({
load: async (path: string) => {
- const response = await fetch(`data/${path}`);
+ const response = await fetch(getResourcePath(`cache/${path}`));
if (!response.ok) {
return Promise.reject(response.statusText);
}
@@ -168,8 +179,8 @@ function makeCaseHTML(t: TestTreeLeaf): VisualizedSubtree {
const [rec, res] = logger.record(name);
caseResult = res;
- if (worker) {
- await worker.run(rec, name);
+ if (testWorker) {
+ await testWorker.run(rec, name);
} else {
await t.run(rec);
}
@@ -223,6 +234,12 @@ function makeCaseHTML(t: TestTreeLeaf): VisualizedSubtree {
if (caseResult.logs) {
caselogs.empty();
+ // Show exceptions at the top since they are often unexpected can point out an error in the test itself vs the WebGPU implementation.
+ caseResult.logs
+ .filter(l => l.name === 'EXCEPTION')
+ .forEach(l => {
+ $('<pre>').addClass('testcaselogtext').text(l.toJSON()).appendTo(caselogs);
+ });
for (const l of caseResult.logs) {
const caselog = $('<div>').addClass('testcaselog').appendTo(caselogs);
$('<button>')
@@ -500,13 +517,11 @@ function makeTreeNodeHeaderHTML(
// Collapse s:f:t:* or s:f:t:c by default.
let lastQueryLevelToExpand: TestQueryLevel = 2;
-type ParamValue = string | undefined | null | boolean | string[];
-
/**
* Takes an array of string, ParamValue and returns an array of pairs
* of [key, value] where value is a string. Converts boolean to '0' or '1'.
*/
-function keyValueToPairs([k, v]: [string, ParamValue]): [string, string][] {
+function keyValueToPairs([k, v]: [string, boolean | string | null]): [string, string][] {
const key = camelCaseToSnakeCase(k);
if (typeof v === 'boolean') {
return [[key, v ? '1' : '0']];
@@ -527,9 +542,9 @@ function keyValueToPairs([k, v]: [string, ParamValue]): [string, string][] {
* @param params Some object with key value pairs.
* @returns a search string.
*/
-function prepareParams(params: Record<string, ParamValue>): string {
+function prepareParams(params: Record<string, boolean | string | null>): string {
const pairsArrays = Object.entries(params)
- .filter(([, v]) => !!v)
+ .filter(([, v]) => !(v === false || v === null || v === '0'))
.map(keyValueToPairs);
const pairs = pairsArrays.flat();
return new URLSearchParams(pairs).toString();
@@ -537,7 +552,7 @@ function prepareParams(params: Record<string, ParamValue>): string {
// This is just a cast in one place.
export function optionsToRecord(options: CTSOptions) {
- return options as unknown as Record<string, boolean | string>;
+ return options as unknown as Record<string, boolean | string | null>;
}
/**
@@ -597,15 +612,15 @@ void (async () => {
};
const createSelect = (optionName: string, info: OptionInfo) => {
- const select = $('<select>').on('change', function () {
- optionValues[optionName] = (this as HTMLInputElement).value;
+ const select = $('<select>').on('change', function (this: HTMLSelectElement) {
+ optionValues[optionName] = JSON.parse(this.value);
updateURLsWithCurrentOptions();
});
const currentValue = optionValues[optionName];
for (const { value, description } of info.selectValueDescriptions!) {
$('<option>')
.text(description)
- .val(value)
+ .val(JSON.stringify(value))
.prop('selected', value === currentValue)
.appendTo(select);
}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/runtime/wpt.ts b/dom/webgpu/tests/cts/checkout/src/common/runtime/wpt.ts
index d4a4008154..79ed1b5924 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/runtime/wpt.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/runtime/wpt.ts
@@ -8,8 +8,8 @@ import { parseQuery } from '../internal/query/parseQuery.js';
import { parseExpectationsForTestQuery, relativeQueryString } from '../internal/query/query.js';
import { assert } from '../util/util.js';
-import { optionEnabled } from './helper/options.js';
-import { TestWorker } from './helper/test_worker.js';
+import { optionEnabled, optionWorkerMode } from './helper/options.js';
+import { TestDedicatedWorker, TestServiceWorker, TestSharedWorker } from './helper/test_worker.js';
// testharness.js API (https://web-platform-tests.org/writing-tests/testharness-api.html)
declare interface WptTestObject {
@@ -31,8 +31,10 @@ setup({
});
void (async () => {
- const workerEnabled = optionEnabled('worker');
- const worker = workerEnabled ? new TestWorker() : undefined;
+ const workerString = optionWorkerMode('worker');
+ const dedicatedWorker = workerString === 'dedicated' ? new TestDedicatedWorker() : undefined;
+ const sharedWorker = workerString === 'shared' ? new TestSharedWorker() : undefined;
+ const serviceWorker = workerString === 'service' ? new TestServiceWorker() : undefined;
globalTestConfig.unrollConstEvalLoops = optionEnabled('unroll_const_eval_loops');
@@ -63,8 +65,12 @@ void (async () => {
const wpt_fn = async () => {
const [rec, res] = log.record(name);
- if (worker) {
- await worker.run(rec, name, expectations);
+ if (dedicatedWorker) {
+ await dedicatedWorker.run(rec, name, expectations);
+ } else if (sharedWorker) {
+ await sharedWorker.run(rec, name, expectations);
+ } else if (serviceWorker) {
+ await serviceWorker.run(rec, name, expectations);
} else {
await testcase.run(rec, expectations);
}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/tools/crawl.ts b/dom/webgpu/tests/cts/checkout/src/common/tools/crawl.ts
index 50340dd68b..21a335b11c 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/tools/crawl.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/tools/crawl.ts
@@ -45,13 +45,16 @@ async function crawlFilesRecursively(dir: string): Promise<string[]> {
);
}
-export async function crawl(suiteDir: string, validate: boolean): Promise<TestSuiteListingEntry[]> {
+export async function crawl(
+ suiteDir: string,
+ opts: { validate: boolean; printMetadataWarnings: boolean } | null = null
+): Promise<TestSuiteListingEntry[]> {
if (!fs.existsSync(suiteDir)) {
throw new Error(`Could not find suite: ${suiteDir}`);
}
let validateTimingsEntries;
- if (validate) {
+ if (opts?.validate) {
const metadata = loadMetadataForSuite(suiteDir);
if (metadata) {
validateTimingsEntries = {
@@ -75,7 +78,7 @@ export async function crawl(suiteDir: string, validate: boolean): Promise<TestSu
const suite = path.basename(suiteDir);
- if (validate) {
+ if (opts?.validate) {
const filename = `../../${suite}/${filepathWithoutExtension}.spec.js`;
assert(!process.env.STANDALONE_DEV_SERVER);
@@ -109,8 +112,6 @@ export async function crawl(suiteDir: string, validate: boolean): Promise<TestSu
}
if (validateTimingsEntries) {
- let failed = false;
-
const zeroEntries = [];
const staleEntries = [];
for (const [metadataKey, metadataValue] of Object.entries(validateTimingsEntries.metadata)) {
@@ -125,36 +126,39 @@ export async function crawl(suiteDir: string, validate: boolean): Promise<TestSu
staleEntries.push(metadataKey);
}
}
- if (zeroEntries.length) {
- console.warn('WARNING: subcaseMS≤0 found in listing_meta.json (allowed, but try to avoid):');
+ if (zeroEntries.length && opts?.printMetadataWarnings) {
+ console.warn(
+ 'WARNING: subcaseMS ≤ 0 found in listing_meta.json (see docs/adding_timing_metadata.md):'
+ );
for (const metadataKey of zeroEntries) {
console.warn(` ${metadataKey}`);
}
}
- if (staleEntries.length) {
- console.error('ERROR: Non-existent tests found in listing_meta.json:');
- for (const metadataKey of staleEntries) {
- console.error(` ${metadataKey}`);
- }
- failed = true;
- }
- const missingEntries = [];
- for (const metadataKey of validateTimingsEntries.testsFoundInFiles) {
- if (!(metadataKey in validateTimingsEntries.metadata)) {
- missingEntries.push(metadataKey);
+ if (opts?.printMetadataWarnings) {
+ const missingEntries = [];
+ for (const metadataKey of validateTimingsEntries.testsFoundInFiles) {
+ if (!(metadataKey in validateTimingsEntries.metadata)) {
+ missingEntries.push(metadataKey);
+ }
+ }
+ if (missingEntries.length) {
+ console.error(
+ 'WARNING: Tests missing from listing_meta.json (see docs/adding_timing_metadata.md):'
+ );
+ for (const metadataKey of missingEntries) {
+ console.error(` ${metadataKey}`);
+ }
}
}
- if (missingEntries.length) {
- console.error(
- 'ERROR: Tests missing from listing_meta.json. Please add the new tests (See docs/adding_timing_metadata.md):'
- );
- for (const metadataKey of missingEntries) {
+
+ if (staleEntries.length) {
+ console.error('ERROR: Non-existent tests found in listing_meta.json. Please update:');
+ for (const metadataKey of staleEntries) {
console.error(` ${metadataKey}`);
- failed = true;
}
+ unreachable();
}
- assert(!failed);
}
return entries;
@@ -163,5 +167,5 @@ export async function crawl(suiteDir: string, validate: boolean): Promise<TestSu
export function makeListing(filename: string): Promise<TestSuiteListing> {
// Don't validate. This path is only used for the dev server and running tests with Node.
// Validation is done for listing generation and presubmit.
- return crawl(path.dirname(filename), false);
+ return crawl(path.dirname(filename));
}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/tools/dev_server.ts b/dom/webgpu/tests/cts/checkout/src/common/tools/dev_server.ts
index 57cb6a7ea4..8e0e3bdbe6 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/tools/dev_server.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/tools/dev_server.ts
@@ -144,6 +144,19 @@ app.get('/out/:suite([a-zA-Z0-9_-]+)/listing.js', async (req, res, next) => {
}
});
+// Serve .worker.js files by generating the necessary wrapper.
+app.get('/out/:suite([a-zA-Z0-9_-]+)/webworker/:filepath(*).worker.js', (req, res, next) => {
+ const { suite, filepath } = req.params;
+ const result = `\
+import { g } from '/out/${suite}/${filepath}.spec.js';
+import { wrapTestGroupForWorker } from '/out/common/runtime/helper/wrap_for_worker.js';
+
+wrapTestGroupForWorker(g);
+`;
+ res.setHeader('Content-Type', 'application/javascript');
+ res.send(result);
+});
+
// Serve all other .js files by fetching the source .ts file and compiling it.
app.get('/out/**/*.js', async (req, res, next) => {
const jsUrl = path.relative('/out', req.url);
diff --git a/dom/webgpu/tests/cts/checkout/src/common/tools/gen_cache.ts b/dom/webgpu/tests/cts/checkout/src/common/tools/gen_cache.ts
index ce0854aa20..d8309ebcb1 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/tools/gen_cache.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/tools/gen_cache.ts
@@ -3,32 +3,41 @@ import * as path from 'path';
import * as process from 'process';
import { Cacheable, dataCache, setIsBuildingDataCache } from '../framework/data_cache.js';
+import { crc32, toHexString } from '../util/crc32.js';
+import { parseImports } from '../util/parse_imports.js';
function usage(rc: number): void {
- console.error(`Usage: tools/gen_cache [options] [OUT_DIR] [SUITE_DIRS...]
+ console.error(`Usage: tools/gen_cache [options] [SUITE_DIRS...]
For each suite in SUITE_DIRS, pre-compute data that is expensive to generate
-at runtime and store it under OUT_DIR. If the data file is found then the
-DataCache will load this instead of building the expensive data at CTS runtime.
+at runtime and store it under 'src/resources/cache'. If the data file is found
+then the DataCache will load this instead of building the expensive data at CTS
+runtime.
+Note: Due to differences in gzip compression, different versions of node can
+produce radically different binary cache files. gen_cache uses the hashes of the
+source files to determine whether a cache file is 'up to date'. This is faster
+and does not depend on the compressed output.
Options:
--help Print this message and exit.
--list Print the list of output files without writing them.
- --nth i/n Only process every file where (file_index % n == i)
- --validate Check that cache should build (Tests for collisions).
+ --force Rebuild cache even if they're up to date
+ --validate Check the cache is up to date
--verbose Print each action taken.
`);
process.exit(rc);
}
+// Where the cache is generated
+const outDir = 'src/resources/cache';
+
+let forceRebuild = false;
let mode: 'emit' | 'list' | 'validate' = 'emit';
-let nth = { i: 0, n: 1 };
let verbose = false;
const nonFlagsArgs: string[] = [];
-for (let i = 0; i < process.argv.length; i++) {
- const arg = process.argv[i];
+for (const arg of process.argv) {
if (arg.startsWith('-')) {
switch (arg) {
case '--list': {
@@ -39,6 +48,10 @@ for (let i = 0; i < process.argv.length; i++) {
usage(0);
break;
}
+ case '--force': {
+ forceRebuild = true;
+ break;
+ }
case '--verbose': {
verbose = true;
break;
@@ -47,28 +60,6 @@ for (let i = 0; i < process.argv.length; i++) {
mode = 'validate';
break;
}
- case '--nth': {
- const err = () => {
- console.error(
- `--nth requires a value of the form 'i/n', where i and n are positive integers and i < n`
- );
- process.exit(1);
- };
- i++;
- if (i >= process.argv.length) {
- err();
- }
- const value = process.argv[i];
- const parts = value.split('/');
- if (parts.length !== 2) {
- err();
- }
- nth = { i: parseInt(parts[0]), n: parseInt(parts[1]) };
- if (nth.i < 0 || nth.n < 1 || nth.i > nth.n) {
- err();
- }
- break;
- }
default: {
console.log('unrecognized flag: ', arg);
usage(1);
@@ -79,12 +70,10 @@ for (let i = 0; i < process.argv.length; i++) {
}
}
-if (nonFlagsArgs.length < 4) {
+if (nonFlagsArgs.length < 3) {
usage(0);
}
-const outRootDir = nonFlagsArgs[2];
-
dataCache.setStore({
load: (path: string) => {
return new Promise<Uint8Array>((resolve, reject) => {
@@ -100,57 +89,133 @@ dataCache.setStore({
});
setIsBuildingDataCache();
+const cacheFileSuffix = __filename.endsWith('.ts') ? '.cache.ts' : '.cache.js';
+
+/**
+ * @returns a list of all the files under 'dir' that has the given extension
+ * @param dir the directory to search
+ * @param ext the extension of the files to find
+ */
+function glob(dir: string, ext: string) {
+ const files: string[] = [];
+ for (const file of fs.readdirSync(dir)) {
+ const path = `${dir}/${file}`;
+ if (fs.statSync(path).isDirectory()) {
+ for (const child of glob(path, ext)) {
+ files.push(`${file}/${child}`);
+ }
+ }
+
+ if (path.endsWith(ext) && fs.statSync(path).isFile()) {
+ files.push(file);
+ }
+ }
+ return files;
+}
+
+/**
+ * Exception type thrown by SourceHasher.hashFile() when a file annotated with
+ * MUST_NOT_BE_IMPORTED_BY_DATA_CACHE is transitively imported by a .cache.ts file.
+ */
+class InvalidImportException {
+ constructor(path: string) {
+ this.stack = [path];
+ }
+ toString(): string {
+ return `invalid transitive import for cache:\n ${this.stack.join('\n ')}`;
+ }
+ readonly stack: string[];
+}
+/**
+ * SourceHasher is a utility for producing a hash of a source .ts file and its imported source files.
+ */
+class SourceHasher {
+ /**
+ * @param path the source file path
+ * @returns a hash of the source file and all of its imported dependencies.
+ */
+ public hashOf(path: string) {
+ this.u32Array[0] = this.hashFile(path);
+ return this.u32Array[0].toString(16);
+ }
+
+ hashFile(path: string): number {
+ if (!fs.existsSync(path) && path.endsWith('.js')) {
+ path = path.substring(0, path.length - 2) + 'ts';
+ }
+
+ const cached = this.hashes.get(path);
+ if (cached !== undefined) {
+ return cached;
+ }
+
+ this.hashes.set(path, 0); // Store a zero hash to handle cyclic imports
+
+ const content = fs.readFileSync(path, { encoding: 'utf-8' });
+ const normalized = content.replace('\r\n', '\n');
+ let hash = crc32(normalized);
+ for (const importPath of parseImports(path, normalized)) {
+ try {
+ const importHash = this.hashFile(importPath);
+ hash = this.hashCombine(hash, importHash);
+ } catch (ex) {
+ if (ex instanceof InvalidImportException) {
+ ex.stack.push(path);
+ throw ex;
+ }
+ }
+ }
+
+ if (content.includes('MUST_NOT_BE_IMPORTED_BY_DATA_CACHE')) {
+ throw new InvalidImportException(path);
+ }
+
+ this.hashes.set(path, hash);
+ return hash;
+ }
+
+ /** Simple non-cryptographic hash combiner */
+ hashCombine(a: number, b: number): number {
+ return crc32(`${toHexString(a)} ${toHexString(b)}`);
+ }
+
+ private hashes = new Map<string, number>();
+ private u32Array = new Uint32Array(1);
+}
+
void (async () => {
- for (const suiteDir of nonFlagsArgs.slice(3)) {
+ const suiteDirs = nonFlagsArgs.slice(2); // skip <exe> <js>
+ for (const suiteDir of suiteDirs) {
await build(suiteDir);
}
})();
-const specFileSuffix = __filename.endsWith('.ts') ? '.spec.ts' : '.spec.js';
-
-async function crawlFilesRecursively(dir: string): Promise<string[]> {
- const subpathInfo = await Promise.all(
- (await fs.promises.readdir(dir)).map(async d => {
- const p = path.join(dir, d);
- const stats = await fs.promises.stat(p);
- return {
- path: p,
- isDirectory: stats.isDirectory(),
- isFile: stats.isFile(),
- };
- })
- );
-
- const files = subpathInfo
- .filter(i => i.isFile && i.path.endsWith(specFileSuffix))
- .map(i => i.path);
-
- return files.concat(
- await subpathInfo
- .filter(i => i.isDirectory)
- .map(i => crawlFilesRecursively(i.path))
- .reduce(async (a, b) => (await a).concat(await b), Promise.resolve([]))
- );
-}
-
async function build(suiteDir: string) {
if (!fs.existsSync(suiteDir)) {
console.error(`Could not find ${suiteDir}`);
process.exit(1);
}
- // Crawl files and convert paths to be POSIX-style, relative to suiteDir.
- let filesToEnumerate = (await crawlFilesRecursively(suiteDir)).sort();
+ // Load hashes.json
+ const fileHashJsonPath = `${outDir}/hashes.json`;
+ let fileHashes: Record<string, string> = {};
+ if (fs.existsSync(fileHashJsonPath)) {
+ const json = fs.readFileSync(fileHashJsonPath, { encoding: 'utf8' });
+ fileHashes = JSON.parse(json);
+ }
- // Filter out non-spec files
- filesToEnumerate = filesToEnumerate.filter(f => f.endsWith(specFileSuffix));
+ // Crawl files and convert paths to be POSIX-style, relative to suiteDir.
+ const filesToEnumerate = glob(suiteDir, cacheFileSuffix)
+ .map(p => `${suiteDir}/${p}`)
+ .sort();
+ const fileHasher = new SourceHasher();
const cacheablePathToTS = new Map<string, string>();
+ const errors: Array<string> = [];
- let fileIndex = 0;
for (const file of filesToEnumerate) {
- const pathWithoutExtension = file.substring(0, file.length - specFileSuffix.length);
- const mod = await import(`../../../${pathWithoutExtension}.spec.js`);
+ const pathWithoutExtension = file.substring(0, file.length - 3);
+ const mod = await import(`../../../${pathWithoutExtension}.js`);
if (mod.d?.serialize !== undefined) {
const cacheable = mod.d as Cacheable<unknown>;
@@ -158,41 +223,78 @@ async function build(suiteDir: string) {
// Check for collisions
const existing = cacheablePathToTS.get(cacheable.path);
if (existing !== undefined) {
- console.error(
- `error: Cacheable '${cacheable.path}' is emitted by both:
+ errors.push(
+ `'${cacheable.path}' is emitted by both:
'${existing}'
and
'${file}'`
);
- process.exit(1);
}
cacheablePathToTS.set(cacheable.path, file);
}
- const outPath = `${outRootDir}/data/${cacheable.path}`;
+ const outPath = `${outDir}/${cacheable.path}`;
+ const fileHash = fileHasher.hashOf(file);
- if (fileIndex++ % nth.n === nth.i) {
- switch (mode) {
- case 'emit': {
+ switch (mode) {
+ case 'emit': {
+ if (!forceRebuild && fileHashes[cacheable.path] === fileHash) {
if (verbose) {
- console.log(`building '${outPath}'`);
+ console.log(`'${outPath}' is up to date`);
}
- const data = await cacheable.build();
- const serialized = cacheable.serialize(data);
- fs.mkdirSync(path.dirname(outPath), { recursive: true });
- fs.writeFileSync(outPath, serialized, 'binary');
- break;
+ continue;
}
- case 'list': {
- console.log(outPath);
- break;
- }
- case 'validate': {
- // Only check currently performed is the collision detection above
- break;
+ console.log(`building '${outPath}'`);
+ const data = await cacheable.build();
+ const serialized = cacheable.serialize(data);
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
+ fs.writeFileSync(outPath, serialized, 'binary');
+ fileHashes[cacheable.path] = fileHash;
+ break;
+ }
+ case 'list': {
+ console.log(outPath);
+ break;
+ }
+ case 'validate': {
+ if (fileHashes[cacheable.path] !== fileHash) {
+ errors.push(
+ `'${outPath}' needs rebuilding. Generate with 'npx grunt run:generate-cache'`
+ );
+ } else if (verbose) {
+ console.log(`'${outPath}' is up to date`);
}
}
}
}
}
+
+ // Check that there aren't stale files in the cache directory
+ for (const file of glob(outDir, '.bin')) {
+ if (cacheablePathToTS.get(file) === undefined) {
+ switch (mode) {
+ case 'emit':
+ fs.rmSync(file);
+ break;
+ case 'validate':
+ errors.push(
+ `cache file '${outDir}/${file}' is no longer generated. Remove with 'npx grunt run:generate-cache'`
+ );
+ break;
+ }
+ }
+ }
+
+ // Update hashes.json
+ if (mode === 'emit') {
+ const json = JSON.stringify(fileHashes, undefined, ' ');
+ fs.writeFileSync(fileHashJsonPath, json, { encoding: 'utf8' });
+ }
+
+ if (errors.length > 0) {
+ for (const error of errors) {
+ console.error(error);
+ }
+ process.exit(1);
+ }
}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/tools/gen_listings.ts b/dom/webgpu/tests/cts/checkout/src/common/tools/gen_listings.ts
index fc5e1f3cde..7cc8cb78f3 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/tools/gen_listings.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/tools/gen_listings.ts
@@ -9,7 +9,7 @@ function usage(rc: number): void {
For each suite in SUITE_DIRS, generate listings and write each listing.js
into OUT_DIR/{suite}/listing.js. Example:
- tools/gen_listings out/ src/unittests/ src/webgpu/
+ tools/gen_listings gen/ src/unittests/ src/webgpu/
Options:
--help Print this message and exit.
@@ -40,7 +40,7 @@ const outDir = argv[2];
for (const suiteDir of argv.slice(3)) {
// Run concurrently for each suite (might be a tiny bit more efficient)
- void crawl(suiteDir, false).then(listing => {
+ void crawl(suiteDir).then(listing => {
const suite = path.basename(suiteDir);
const outFile = path.normalize(path.join(outDir, `${suite}/listing.js`));
fs.mkdirSync(path.join(outDir, suite), { recursive: true });
@@ -52,12 +52,5 @@ for (const suiteDir of argv.slice(3)) {
export const listing = ${JSON.stringify(listing, undefined, 2)};
`
);
-
- // If there was a sourcemap for the file we just replaced, delete it.
- try {
- fs.unlinkSync(outFile + '.map');
- } catch (ex) {
- // ignore if file didn't exist
- }
});
}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/tools/gen_listings_and_webworkers.ts b/dom/webgpu/tests/cts/checkout/src/common/tools/gen_listings_and_webworkers.ts
new file mode 100644
index 0000000000..04ce669de3
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/common/tools/gen_listings_and_webworkers.ts
@@ -0,0 +1,89 @@
+import * as fs from 'fs';
+import * as path from 'path';
+import * as process from 'process';
+
+import { crawl } from './crawl.js';
+
+function usage(rc: number): void {
+ console.error(`Usage: tools/gen_listings_and_webworkers [options] [OUT_DIR] [SUITE_DIRS...]
+
+For each suite in SUITE_DIRS, generate listings into OUT_DIR/{suite}/listing.js,
+and generate Web Worker proxies in OUT_DIR/{suite}/webworker/**/*.worker.js for
+every .spec.js file. (Note {suite}/webworker/ is reserved for this purpose.)
+
+Example:
+ tools/gen_listings_and_webworkers gen/ src/unittests/ src/webgpu/
+
+Options:
+ --help Print this message and exit.
+`);
+ process.exit(rc);
+}
+
+const argv = process.argv;
+if (argv.indexOf('--help') !== -1) {
+ usage(0);
+}
+
+{
+ // Ignore old argument that is now the default
+ const i = argv.indexOf('--no-validate');
+ if (i !== -1) {
+ argv.splice(i, 1);
+ }
+}
+
+if (argv.length < 4) {
+ usage(0);
+}
+
+const myself = 'src/common/tools/gen_listings_and_webworkers.ts';
+
+const outDir = argv[2];
+
+for (const suiteDir of argv.slice(3)) {
+ // Run concurrently for each suite (might be a tiny bit more efficient)
+ void crawl(suiteDir).then(listing => {
+ const suite = path.basename(suiteDir);
+
+ // Write listing.js
+ const outFile = path.normalize(path.join(outDir, `${suite}/listing.js`));
+ fs.mkdirSync(path.join(outDir, suite), { recursive: true });
+ fs.writeFileSync(
+ outFile,
+ `\
+// AUTO-GENERATED - DO NOT EDIT. See ${myself}.
+
+export const listing = ${JSON.stringify(listing, undefined, 2)};
+`
+ );
+
+ // Write suite/webworker/**/*.worker.js
+ for (const entry of listing) {
+ if ('readme' in entry) continue;
+
+ const outFileDir = path.join(
+ outDir,
+ suite,
+ 'webworker',
+ ...entry.file.slice(0, entry.file.length - 1)
+ );
+ const outFile = path.join(outDir, suite, 'webworker', ...entry.file) + '.worker.js';
+
+ const relPathToSuiteRoot = Array<string>(entry.file.length).fill('..').join('/');
+
+ fs.mkdirSync(outFileDir, { recursive: true });
+ fs.writeFileSync(
+ outFile,
+ `\
+// AUTO-GENERATED - DO NOT EDIT. See ${myself}.
+
+import { g } from '${relPathToSuiteRoot}/${entry.file.join('/')}.spec.js';
+import { wrapTestGroupForWorker } from '${relPathToSuiteRoot}/../common/runtime/helper/wrap_for_worker.js';
+
+wrapTestGroupForWorker(g);
+`
+ );
+ }
+ });
+}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/tools/gen_wpt_cts_html.ts b/dom/webgpu/tests/cts/checkout/src/common/tools/gen_wpt_cts_html.ts
index e8161304e9..46c2ae4354 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/tools/gen_wpt_cts_html.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/tools/gen_wpt_cts_html.ts
@@ -23,6 +23,7 @@ gen_wpt_cts_html.ts. Example:
{
"suite": "webgpu",
"out": "path/to/output/cts.https.html",
+ "outJSON": "path/to/output/webgpu_variant_list.json",
"template": "path/to/template/cts.https.html",
"maxChunkTimeMS": 2000
}
@@ -35,15 +36,15 @@ where arguments.txt is a file containing a list of arguments prefixes to both ge
in the expectations. The entire variant list generation runs *once per prefix*, so this
multiplies the size of the variant list.
- ?worker=0&q=
- ?worker=1&q=
+ ?debug=0&q=
+ ?debug=1&q=
and myexpectations.txt is a file containing a list of WPT paths to suppress, e.g.:
- path/to/cts.https.html?worker=0&q=webgpu:a/foo:bar={"x":1}
- path/to/cts.https.html?worker=1&q=webgpu:a/foo:bar={"x":1}
+ path/to/cts.https.html?debug=0&q=webgpu:a/foo:bar={"x":1}
+ path/to/cts.https.html?debug=1&q=webgpu:a/foo:bar={"x":1}
- path/to/cts.https.html?worker=1&q=webgpu:a/foo:bar={"x":3}
+ path/to/cts.https.html?debug=1&q=webgpu:a/foo:bar={"x":3}
`);
process.exit(rc);
}
@@ -51,9 +52,11 @@ and myexpectations.txt is a file containing a list of WPT paths to suppress, e.g
interface ConfigJSON {
/** Test suite to generate from. */
suite: string;
- /** Output filename, relative to JSON file. */
+ /** Output path for HTML file, relative to config file. */
out: string;
- /** Input template filename, relative to JSON file. */
+ /** Output path for JSON file containing the "variant" list, relative to config file. */
+ outVariantList?: string;
+ /** Input template filename, relative to config file. */
template: string;
/**
* Maximum time for a single WPT "variant" chunk, in milliseconds. Defaults to infinity.
@@ -71,18 +74,31 @@ interface ConfigJSON {
/** The prefix to trim from every line of the expectations_file. */
prefix: string;
};
+ /** Expend all subtrees for provided queries */
+ fullyExpandSubtrees?: {
+ file: string;
+ prefix: string;
+ };
+ /*No long path assert */
+ noLongPathAssert?: boolean;
}
interface Config {
suite: string;
out: string;
+ outVariantList?: string;
template: string;
maxChunkTimeMS: number;
argumentsPrefixes: string[];
+ noLongPathAssert: boolean;
expectations?: {
file: string;
prefix: string;
};
+ fullyExpandSubtrees?: {
+ file: string;
+ prefix: string;
+ };
}
let config: Config;
@@ -101,13 +117,23 @@ let config: Config;
template: path.resolve(jsonFileDir, configJSON.template),
maxChunkTimeMS: configJSON.maxChunkTimeMS ?? Infinity,
argumentsPrefixes: configJSON.argumentsPrefixes ?? ['?q='],
+ noLongPathAssert: configJSON.noLongPathAssert ?? false,
};
+ if (configJSON.outVariantList) {
+ config.outVariantList = path.resolve(jsonFileDir, configJSON.outVariantList);
+ }
if (configJSON.expectations) {
config.expectations = {
file: path.resolve(jsonFileDir, configJSON.expectations.file),
prefix: configJSON.expectations.prefix,
};
}
+ if (configJSON.fullyExpandSubtrees) {
+ config.fullyExpandSubtrees = {
+ file: path.resolve(jsonFileDir, configJSON.fullyExpandSubtrees.file),
+ prefix: configJSON.fullyExpandSubtrees.prefix,
+ };
+ }
break;
}
case 4:
@@ -130,6 +156,7 @@ let config: Config;
suite,
maxChunkTimeMS: Infinity,
argumentsPrefixes: ['?q='],
+ noLongPathAssert: false,
};
if (process.argv.length >= 7) {
config.argumentsPrefixes = (await fs.readFile(argsPrefixesFile, 'utf8'))
@@ -153,29 +180,16 @@ let config: Config;
config.argumentsPrefixes.sort((a, b) => b.length - a.length);
// Load expectations (if any)
- let expectationLines = new Set<string>();
- if (config.expectations) {
- expectationLines = new Set(
- (await fs.readFile(config.expectations.file, 'utf8')).split(/\r?\n/).filter(l => l.length)
- );
- }
+ const expectations: Map<string, string[]> = await loadQueryFile(
+ config.argumentsPrefixes,
+ config.expectations
+ );
- const expectations: Map<string, string[]> = new Map();
- for (const prefix of config.argumentsPrefixes) {
- expectations.set(prefix, []);
- }
-
- expLoop: for (const exp of expectationLines) {
- // Take each expectation for the longest prefix it matches.
- for (const argsPrefix of config.argumentsPrefixes) {
- const prefix = config.expectations!.prefix + argsPrefix;
- if (exp.startsWith(prefix)) {
- expectations.get(argsPrefix)!.push(exp.substring(prefix.length));
- continue expLoop;
- }
- }
- console.log('note: ignored expectation: ' + exp);
- }
+ // Load fullyExpandSubtrees queries (if any)
+ const fullyExpand: Map<string, string[]> = await loadQueryFile(
+ config.argumentsPrefixes,
+ config.fullyExpandSubtrees
+ );
const loader = new DefaultTestFileLoader();
const lines = [];
@@ -183,6 +197,7 @@ let config: Config;
const rootQuery = new TestQueryMultiFile(config.suite, []);
const tree = await loader.loadTree(rootQuery, {
subqueriesToExpand: expectations.get(prefix),
+ fullyExpandSubtrees: fullyExpand.get(prefix),
maxChunkTime: config.maxChunkTimeMS,
});
@@ -199,22 +214,24 @@ let config: Config;
alwaysExpandThroughLevel,
})) {
assert(query instanceof TestQueryMultiCase);
- const queryString = query.toString();
- // Check for a safe-ish path length limit. Filename must be <= 255, and on Windows the whole
- // path must be <= 259. Leave room for e.g.:
- // 'c:\b\s\w\xxxxxxxx\layout-test-results\external\wpt\webgpu\cts_worker=0_q=...-actual.txt'
- assert(
- queryString.length < 185,
- `Generated test variant would produce too-long -actual.txt filename. Possible solutions:
+ if (!config.noLongPathAssert) {
+ const queryString = query.toString();
+ // Check for a safe-ish path length limit. Filename must be <= 255, and on Windows the whole
+ // path must be <= 259. Leave room for e.g.:
+ // 'c:\b\s\w\xxxxxxxx\layout-test-results\external\wpt\webgpu\cts_worker=0_q=...-actual.txt'
+ assert(
+ queryString.length < 185,
+ `Generated test variant would produce too-long -actual.txt filename. Possible solutions:
- Reduce the length of the parts of the test query
- Reduce the parameterization of the test
- Make the test function faster and regenerate the listing_meta entry
- Reduce the specificity of test expectations (if you're using them)
${queryString}`
- );
+ );
+ }
lines.push({
- urlQueryString: prefix + query.toString(), // "?worker=0&q=..."
+ urlQueryString: prefix + query.toString(), // "?debug=0&q=..."
comment: useChunking ? `estimated: ${subtreeCounts?.totalTimeMS.toFixed(3)} ms` : undefined,
});
@@ -232,6 +249,39 @@ ${queryString}`
process.exit(1);
});
+async function loadQueryFile(
+ argumentsPrefixes: string[],
+ queryFile?: {
+ file: string;
+ prefix: string;
+ }
+): Promise<Map<string, string[]>> {
+ let lines = new Set<string>();
+ if (queryFile) {
+ lines = new Set(
+ (await fs.readFile(queryFile.file, 'utf8')).split(/\r?\n/).filter(l => l.length)
+ );
+ }
+
+ const result: Map<string, string[]> = new Map();
+ for (const prefix of argumentsPrefixes) {
+ result.set(prefix, []);
+ }
+
+ expLoop: for (const exp of lines) {
+ // Take each expectation for the longest prefix it matches.
+ for (const argsPrefix of argumentsPrefixes) {
+ const prefix = queryFile!.prefix + argsPrefix;
+ if (exp.startsWith(prefix)) {
+ result.get(argsPrefix)!.push(exp.substring(prefix.length));
+ continue expLoop;
+ }
+ }
+ console.log('note: ignored expectation: ' + exp);
+ }
+ return result;
+}
+
async function generateFile(
lines: Array<{ urlQueryString?: string; comment?: string } | undefined>
): Promise<void> {
@@ -240,13 +290,20 @@ async function generateFile(
result += await fs.readFile(config.template, 'utf8');
+ const variantList = [];
for (const line of lines) {
if (line !== undefined) {
- if (line.urlQueryString) result += `<meta name=variant content='${line.urlQueryString}'>`;
+ if (line.urlQueryString) {
+ result += `<meta name=variant content='${line.urlQueryString}'>`;
+ variantList.push(line.urlQueryString);
+ }
if (line.comment) result += `<!-- ${line.comment} -->`;
}
result += '\n';
}
await fs.writeFile(config.out, result);
+ if (config.outVariantList) {
+ await fs.writeFile(config.outVariantList, JSON.stringify(variantList, undefined, 2));
+ }
}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/tools/merge_listing_times.ts b/dom/webgpu/tests/cts/checkout/src/common/tools/merge_listing_times.ts
index fb33ae20fb..a8bef354cc 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/tools/merge_listing_times.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/tools/merge_listing_times.ts
@@ -36,21 +36,13 @@ In more detail:
- For each suite seen, loads its listing_meta.json, takes the max of the old and
new data, and writes it back out.
-How to generate TIMING_LOG_FILES files:
-
-- Launch the 'websocket-logger' tool (see its README.md), which listens for
- log messages on localhost:59497.
-- Run the tests you want to capture data for, on the same system. Since
- logging is done through the websocket side-channel, you can run the tests
- under any runtime (standalone, WPT, etc.) as long as WebSocket support is
- available (always true in browsers).
-- Run \`tools/merge_listing_times webgpu -- tools/websocket-logger/wslog-*.txt\`
+See 'docs/adding_timing_metadata.md' for how to generate TIMING_LOG_FILES files.
`);
process.exit(rc);
}
const kHeader = `{
- "_comment": "SEMI AUTO-GENERATED: Please read docs/adding_timing_metadata.md.",
+ "_comment": "SEMI AUTO-GENERATED. This list is NOT exhaustive. Please read docs/adding_timing_metadata.md.",
`;
const kFooter = `\
"_end": ""
diff --git a/dom/webgpu/tests/cts/checkout/src/common/tools/validate.ts b/dom/webgpu/tests/cts/checkout/src/common/tools/validate.ts
index 164ee3259a..47aa9782a8 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/tools/validate.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/tools/validate.ts
@@ -2,7 +2,7 @@ import * as process from 'process';
import { crawl } from './crawl.js';
-function usage(rc: number): void {
+function usage(rc: number): never {
console.error(`Usage: tools/validate [options] [SUITE_DIRS...]
For each suite in SUITE_DIRS, validate some properties about the file:
@@ -14,23 +14,40 @@ For each suite in SUITE_DIRS, validate some properties about the file:
- That each case query is not too long
Example:
- tools/validate src/unittests/ src/webgpu/
+ tools/validate src/unittests src/webgpu
Options:
- --help Print this message and exit.
+ --help Print this message and exit.
+ --print-metadata-warnings Print non-fatal warnings about listing_meta.json files.
`);
process.exit(rc);
}
const args = process.argv.slice(2);
+if (args.length < 1) {
+ usage(0);
+}
if (args.indexOf('--help') !== -1) {
usage(0);
}
-if (args.length < 1) {
+let printMetadataWarnings = false;
+const suiteDirs = [];
+for (const arg of args) {
+ if (arg === '--print-metadata-warnings') {
+ printMetadataWarnings = true;
+ } else {
+ suiteDirs.push(arg);
+ }
+}
+
+if (suiteDirs.length === 0) {
usage(0);
}
-for (const suiteDir of args) {
- void crawl(suiteDir, true);
+for (const suiteDir of suiteDirs) {
+ void crawl(suiteDir, {
+ validate: true,
+ printMetadataWarnings,
+ });
}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/util/crc32.ts b/dom/webgpu/tests/cts/checkout/src/common/util/crc32.ts
new file mode 100644
index 0000000000..5f74b4662e
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/common/util/crc32.ts
@@ -0,0 +1,57 @@
+/// CRC32 immutable lookup table data.
+const kCRC32LUT = [
+ 0, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832,
+ 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064,
+ 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856,
+ 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8,
+ 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa,
+ 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac,
+ 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e,
+ 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190,
+ 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2,
+ 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4,
+ 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6,
+ 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158,
+ 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a,
+ 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c,
+ 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e,
+ 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320,
+ 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12,
+ 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344,
+ 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76,
+ 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8,
+ 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda,
+ 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c,
+ 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe,
+ 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0,
+ 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82,
+ 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4,
+ 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6,
+ 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278,
+ 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a,
+ 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c,
+ 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e,
+ 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
+];
+
+/**
+ * @param str the input string
+ * @returns the CRC32 of the input string
+ * @see https://en.wikipedia.org/wiki/Cyclic_redundancy_check#CRC-32_algorithm
+ */
+export function crc32(str: string): number {
+ const utf8 = new TextEncoder().encode(str);
+ const u32 = new Uint32Array(1);
+
+ u32[0] = 0xffffffff;
+ for (const c of utf8) {
+ u32[0] = (u32[0] >>> 8) ^ kCRC32LUT[(u32[0] & 0xff) ^ c];
+ }
+ u32[0] = u32[0] ^ 0xffffffff;
+ return u32[0];
+}
+
+/** @returns the input number has a 8-character hex string */
+export function toHexString(number: number): string {
+ return ('00000000' + number.toString(16)).slice(-8);
+}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/util/parse_imports.ts b/dom/webgpu/tests/cts/checkout/src/common/util/parse_imports.ts
new file mode 100644
index 0000000000..4b5604b897
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/common/util/parse_imports.ts
@@ -0,0 +1,36 @@
+/**
+ * Parses all the paths of the typescript `import` statements from content
+ * @param path the current path of the file
+ * @param content the file content
+ * @returns the list of import paths
+ */
+export function parseImports(path: string, content: string): string[] {
+ const out: string[] = [];
+ const importRE = /^import\s[^'"]*(['"])([./\w]*)(\1);/gm;
+ let importMatch: RegExpMatchArray | null;
+ while ((importMatch = importRE.exec(content))) {
+ const importPath = importMatch[2].replace(`'`, '').replace(`"`, '');
+ out.push(joinPath(path, importPath));
+ }
+ return out;
+}
+
+function joinPath(a: string, b: string): string {
+ const aParts = a.split('/');
+ const bParts = b.split('/');
+ aParts.pop(); // remove file
+ let bStart = 0;
+ while (aParts.length > 0) {
+ switch (bParts[bStart]) {
+ case '.':
+ bStart++;
+ continue;
+ case '..':
+ aParts.pop();
+ bStart++;
+ continue;
+ }
+ break;
+ }
+ return [...aParts, ...bParts.slice(bStart)].join('/');
+}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/util/util.ts b/dom/webgpu/tests/cts/checkout/src/common/util/util.ts
index 9433aaddb0..4092b997a3 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/util/util.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/util/util.ts
@@ -1,7 +1,6 @@
import { Float16Array } from '../../external/petamoriken/float16/float16.js';
import { SkipTestCase } from '../framework/fixture.js';
import { globalTestConfig } from '../framework/test_config.js';
-import { Logger } from '../internal/logging/logger.js';
import { keysOf } from './data_tables.js';
import { timeout } from './timeout.js';
@@ -24,7 +23,7 @@ export class ErrorWithExtra extends Error {
super(message);
const oldExtras = baseOrMessage instanceof ErrorWithExtra ? baseOrMessage.extra : {};
- this.extra = Logger.globalDebugMode
+ this.extra = globalTestConfig.enableDebugLogs
? { ...oldExtras, ...newExtra() }
: { omitted: 'pass ?debug=1' };
}
@@ -304,6 +303,8 @@ const TypedArrayBufferViewInstances = [
new Float16Array(),
new Float32Array(),
new Float64Array(),
+ new BigInt64Array(),
+ new BigUint64Array(),
] as const;
export type TypedArrayBufferView = (typeof TypedArrayBufferViewInstances)[number];
diff --git a/dom/webgpu/tests/cts/checkout/src/external/README.md b/dom/webgpu/tests/cts/checkout/src/external/README.md
index 84fbf9c732..29c5f41f3a 100644
--- a/dom/webgpu/tests/cts/checkout/src/external/README.md
+++ b/dom/webgpu/tests/cts/checkout/src/external/README.md
@@ -28,4 +28,4 @@ should be listed below.
| **Name** | **Origin** | **License** | **Version** | **Purpose** |
|----------------------|--------------------------------------------------|-------------|-------------|------------------------------------------------|
-| petamoriken/float16 | [github](https://github.com/petamoriken/float16) | MIT | 3.6.6 | Fluent support for f16 numbers via TypedArrays |
+| petamoriken/float16 | [github](https://github.com/petamoriken/float16) | MIT | 3.8.6 | Fluent support for f16 numbers via TypedArrays |
diff --git a/dom/webgpu/tests/cts/checkout/src/external/petamoriken/float16/LICENSE.txt b/dom/webgpu/tests/cts/checkout/src/external/petamoriken/float16/LICENSE.txt
index e8eacf4e7f..e3d7962fe8 100644
--- a/dom/webgpu/tests/cts/checkout/src/external/petamoriken/float16/LICENSE.txt
+++ b/dom/webgpu/tests/cts/checkout/src/external/petamoriken/float16/LICENSE.txt
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2017-2021 Kenta Moriuchi
+Copyright (c) 2017-2024 Kenta Moriuchi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/dom/webgpu/tests/cts/checkout/src/external/petamoriken/float16/float16.d.ts b/dom/webgpu/tests/cts/checkout/src/external/petamoriken/float16/float16.d.ts
index c9d66ab7ca..7e79bc177c 100644
--- a/dom/webgpu/tests/cts/checkout/src/external/petamoriken/float16/float16.d.ts
+++ b/dom/webgpu/tests/cts/checkout/src/external/petamoriken/float16/float16.d.ts
@@ -331,6 +331,26 @@ export interface Float16Array {
subarray(begin?: number, end?: number): Float16Array;
/**
+ * Copies the array and returns the copy with the elements in reverse order.
+ */
+ toReversed(): Float16Array;
+
+ /**
+ * Copies and sorts the array.
+ * @param compareFn Function used to determine the order of the elements. It is expected to return
+ * a negative value if first argument is less than second argument, zero if they're equal and a positive
+ * value otherwise. If omitted, the elements are sorted in ascending.
+ */
+ toSorted(compareFn?: (a: number, b: number) => number): Float16Array;
+
+ /**
+ * Copies the array and replaces the element at the given index with the provided value.
+ * @param index The zero-based location in the array for which to replace an element.
+ * @param value Element to insert into the array in place of the replaced element.
+ */
+ with(index: number, value: number): Float16Array;
+
+ /**
* Converts a number to a string by using the current locale.
*/
toLocaleString(): string;
@@ -468,4 +488,11 @@ export declare function setFloat16(
* Returns the nearest half-precision float representation of a number.
* @param x A numeric expression.
*/
+export declare function f16round(x: number): number;
+
+/**
+ * Returns the nearest half-precision float representation of a number.
+ * @alias f16round
+ * @param x A numeric expression.
+ */
export declare function hfround(x: number): number;
diff --git a/dom/webgpu/tests/cts/checkout/src/external/petamoriken/float16/float16.js b/dom/webgpu/tests/cts/checkout/src/external/petamoriken/float16/float16.js
index 54843a4842..1031e2bcda 100644
--- a/dom/webgpu/tests/cts/checkout/src/external/petamoriken/float16/float16.js
+++ b/dom/webgpu/tests/cts/checkout/src/external/petamoriken/float16/float16.js
@@ -1,4 +1,4 @@
-/*! @petamoriken/float16 v3.6.6 | MIT License - https://github.com/petamoriken/float16 */
+/*! @petamoriken/float16 v3.8.6 | MIT License - https://github.com/petamoriken/float16 */
const THIS_IS_NOT_AN_OBJECT = "This is not an object";
const THIS_IS_NOT_A_FLOAT16ARRAY_OBJECT = "This is not a Float16Array object";
@@ -19,6 +19,8 @@ const CANNOT_MIX_BIGINT_AND_OTHER_TYPES =
const ITERATOR_PROPERTY_IS_NOT_CALLABLE = "@@iterator property is not callable";
const REDUCE_OF_EMPTY_ARRAY_WITH_NO_INITIAL_VALUE =
"Reduce of empty array with no initial value";
+const THE_COMPARISON_FUNCTION_MUST_BE_EITHER_A_FUNCTION_OR_UNDEFINED =
+ "The comparison function must be either a function or undefined";
const OFFSET_IS_OUT_OF_BOUNDS = "Offset is out of bounds";
function uncurryThis(target) {
@@ -48,7 +50,8 @@ const {
} = Reflect;
const NativeProxy = Proxy;
const {
- MAX_SAFE_INTEGER: MAX_SAFE_INTEGER,
+ EPSILON,
+ MAX_SAFE_INTEGER,
isFinite: NumberIsFinite,
isNaN: NumberIsNaN,
} = Number;
@@ -97,7 +100,10 @@ const ArrayPrototypeToLocaleString = uncurryThis(
);
const NativeArrayPrototypeSymbolIterator = ArrayPrototype[SymbolIterator];
const ArrayPrototypeSymbolIterator = uncurryThis(NativeArrayPrototypeSymbolIterator);
-const MathTrunc = Math.trunc;
+const {
+ abs: MathAbs,
+ trunc: MathTrunc,
+} = Math;
const NativeArrayBuffer = ArrayBuffer;
const ArrayBufferIsView = NativeArrayBuffer.isView;
const ArrayBufferPrototype = NativeArrayBuffer.prototype;
@@ -146,6 +152,7 @@ const TypedArrayPrototypeGetSymbolToStringTag = uncurryThisGetter(
TypedArrayPrototype,
SymbolToStringTag
);
+const NativeUint8Array = Uint8Array;
const NativeUint16Array = Uint16Array;
const Uint16ArrayFrom = (...args) => {
return ReflectApply(TypedArrayFrom, NativeUint16Array, args);
@@ -190,7 +197,10 @@ const SafeIteratorPrototype = ObjectCreate(null, {
},
});
function safeIfNeeded(array) {
- if (array[SymbolIterator] === NativeArrayPrototypeSymbolIterator) {
+ if (
+ array[SymbolIterator] === NativeArrayPrototypeSymbolIterator &&
+ ArrayIteratorPrototype.next === ArrayIteratorPrototypeNext
+ ) {
return array;
}
const safe = ObjectCreate(SafeIteratorPrototype);
@@ -221,8 +231,10 @@ function wrap(generator) {
}
function isObject(value) {
- return (value !== null && typeof value === "object") ||
- typeof value === "function";
+ return (
+ (value !== null && typeof value === "object") ||
+ typeof value === "function"
+ );
}
function isObjectLike(value) {
return value !== null && typeof value === "object";
@@ -232,11 +244,16 @@ function isNativeTypedArray(value) {
}
function isNativeBigIntTypedArray(value) {
const typedArrayName = TypedArrayPrototypeGetSymbolToStringTag(value);
- return typedArrayName === "BigInt64Array" ||
- typedArrayName === "BigUint64Array";
+ return (
+ typedArrayName === "BigInt64Array" ||
+ typedArrayName === "BigUint64Array"
+ );
}
function isArrayBuffer(value) {
try {
+ if (ArrayIsArray(value)) {
+ return false;
+ }
ArrayBufferPrototypeGetByteLength( (value));
return true;
} catch (e) {
@@ -254,25 +271,26 @@ function isSharedArrayBuffer(value) {
return false;
}
}
+function isAnyArrayBuffer(value) {
+ return isArrayBuffer(value) || isSharedArrayBuffer(value);
+}
function isOrdinaryArray(value) {
if (!ArrayIsArray(value)) {
return false;
}
- if (value[SymbolIterator] === NativeArrayPrototypeSymbolIterator) {
- return true;
- }
- const iterator = value[SymbolIterator]();
- return iterator[SymbolToStringTag] === "Array Iterator";
+ return (
+ value[SymbolIterator] === NativeArrayPrototypeSymbolIterator &&
+ ArrayIteratorPrototype.next === ArrayIteratorPrototypeNext
+ );
}
function isOrdinaryNativeTypedArray(value) {
if (!isNativeTypedArray(value)) {
return false;
}
- if (value[SymbolIterator] === NativeTypedArrayPrototypeSymbolIterator) {
- return true;
- }
- const iterator = value[SymbolIterator]();
- return iterator[SymbolToStringTag] === "Array Iterator";
+ return (
+ value[SymbolIterator] === NativeTypedArrayPrototypeSymbolIterator &&
+ ArrayIteratorPrototype.next === ArrayIteratorPrototypeNext
+ );
}
function isCanonicalIntegerIndexString(value) {
if (typeof value !== "string") {
@@ -307,11 +325,37 @@ function hasFloat16ArrayBrand(target) {
return ReflectHas(constructor, brand);
}
+const INVERSE_OF_EPSILON = 1 / EPSILON;
+function roundTiesToEven(num) {
+ return (num + INVERSE_OF_EPSILON) - INVERSE_OF_EPSILON;
+}
+const FLOAT16_MIN_VALUE = 6.103515625e-05;
+const FLOAT16_MAX_VALUE = 65504;
+const FLOAT16_EPSILON = 0.0009765625;
+const FLOAT16_EPSILON_MULTIPLIED_BY_FLOAT16_MIN_VALUE = FLOAT16_EPSILON * FLOAT16_MIN_VALUE;
+const FLOAT16_EPSILON_DEVIDED_BY_EPSILON = FLOAT16_EPSILON * INVERSE_OF_EPSILON;
+function roundToFloat16(num) {
+ const number = +num;
+ if (!NumberIsFinite(number) || number === 0) {
+ return number;
+ }
+ const sign = number > 0 ? 1 : -1;
+ const absolute = MathAbs(number);
+ if (absolute < FLOAT16_MIN_VALUE) {
+ return sign * roundTiesToEven(absolute / FLOAT16_EPSILON_MULTIPLIED_BY_FLOAT16_MIN_VALUE) * FLOAT16_EPSILON_MULTIPLIED_BY_FLOAT16_MIN_VALUE;
+ }
+ const temp = (1 + FLOAT16_EPSILON_DEVIDED_BY_EPSILON) * absolute;
+ const result = temp - (temp - absolute);
+ if (result > FLOAT16_MAX_VALUE || NumberIsNaN(result)) {
+ return sign * Infinity;
+ }
+ return sign * result;
+}
const buffer = new NativeArrayBuffer(4);
const floatView = new NativeFloat32Array(buffer);
const uint32View = new NativeUint32Array(buffer);
-const baseTable = new NativeUint32Array(512);
-const shiftTable = new NativeUint32Array(512);
+const baseTable = new NativeUint16Array(512);
+const shiftTable = new NativeUint8Array(512);
for (let i = 0; i < 256; ++i) {
const e = i - 127;
if (e < -27) {
@@ -342,18 +386,16 @@ for (let i = 0; i < 256; ++i) {
}
}
function roundToFloat16Bits(num) {
- floatView[0] = (num);
+ floatView[0] = roundToFloat16(num);
const f = uint32View[0];
const e = (f >> 23) & 0x1ff;
return baseTable[e] + ((f & 0x007fffff) >> shiftTable[e]);
}
const mantissaTable = new NativeUint32Array(2048);
-const exponentTable = new NativeUint32Array(64);
-const offsetTable = new NativeUint32Array(64);
for (let i = 1; i < 1024; ++i) {
let m = i << 13;
let e = 0;
- while((m & 0x00800000) === 0) {
+ while ((m & 0x00800000) === 0) {
m <<= 1;
e -= 0x00800000;
}
@@ -364,6 +406,7 @@ for (let i = 1; i < 1024; ++i) {
for (let i = 1024; i < 2048; ++i) {
mantissaTable[i] = 0x38000000 + ((i - 1024) << 13);
}
+const exponentTable = new NativeUint32Array(64);
for (let i = 1; i < 31; ++i) {
exponentTable[i] = i << 23;
}
@@ -373,14 +416,15 @@ for (let i = 33; i < 63; ++i) {
exponentTable[i] = 0x80000000 + ((i - 32) << 23);
}
exponentTable[63] = 0xc7800000;
+const offsetTable = new NativeUint16Array(64);
for (let i = 1; i < 64; ++i) {
if (i !== 32) {
offsetTable[i] = 1024;
}
}
function convertToNumber(float16bits) {
- const m = float16bits >> 10;
- uint32View[0] = mantissaTable[offsetTable[m] + (float16bits & 0x3ff)] + exponentTable[m];
+ const i = float16bits >> 10;
+ uint32View[0] = mantissaTable[offsetTable[i] + (float16bits & 0x3ff)] + exponentTable[i];
return floatView[0];
}
@@ -572,26 +616,20 @@ class Float16Array {
let float16bitsArray;
if (isFloat16Array(input)) {
float16bitsArray = ReflectConstruct(NativeUint16Array, [getFloat16BitsArray(input)], new.target);
- } else if (isObject(input) && !isArrayBuffer(input)) {
+ } else if (isObject(input) && !isAnyArrayBuffer(input)) {
let list;
let length;
if (isNativeTypedArray(input)) {
list = input;
length = TypedArrayPrototypeGetLength(input);
const buffer = TypedArrayPrototypeGetBuffer(input);
- const BufferConstructor = !isSharedArrayBuffer(buffer)
- ? (SpeciesConstructor(
- buffer,
- NativeArrayBuffer
- ))
- : NativeArrayBuffer;
if (IsDetachedBuffer(buffer)) {
throw NativeTypeError(ATTEMPTING_TO_ACCESS_DETACHED_ARRAYBUFFER);
}
if (isNativeBigIntTypedArray(input)) {
throw NativeTypeError(CANNOT_MIX_BIGINT_AND_OTHER_TYPES);
}
- const data = new BufferConstructor(
+ const data = new NativeArrayBuffer(
length * BYTES_PER_ELEMENT
);
float16bitsArray = ReflectConstruct(NativeUint16Array, [data], new.target);
@@ -758,6 +796,30 @@ class Float16Array {
}
return convertToNumber(float16bitsArray[k]);
}
+ with(index, value) {
+ assertFloat16Array(this);
+ const float16bitsArray = getFloat16BitsArray(this);
+ const length = TypedArrayPrototypeGetLength(float16bitsArray);
+ const relativeIndex = ToIntegerOrInfinity(index);
+ const k = relativeIndex >= 0 ? relativeIndex : length + relativeIndex;
+ const number = +value;
+ if (k < 0 || k >= length) {
+ throw NativeRangeError(OFFSET_IS_OUT_OF_BOUNDS);
+ }
+ const uint16 = new NativeUint16Array(
+ TypedArrayPrototypeGetBuffer(float16bitsArray),
+ TypedArrayPrototypeGetByteOffset(float16bitsArray),
+ TypedArrayPrototypeGetLength(float16bitsArray)
+ );
+ const cloned = new Float16Array(
+ TypedArrayPrototypeGetBuffer(
+ TypedArrayPrototypeSlice(uint16)
+ )
+ );
+ const array = getFloat16BitsArray(cloned);
+ array[k] = roundToFloat16Bits(number);
+ return cloned;
+ }
map(callback, ...opts) {
assertFloat16Array(this);
const float16bitsArray = getFloat16BitsArray(this);
@@ -995,6 +1057,23 @@ class Float16Array {
TypedArrayPrototypeReverse(float16bitsArray);
return this;
}
+ toReversed() {
+ assertFloat16Array(this);
+ const float16bitsArray = getFloat16BitsArray(this);
+ const uint16 = new NativeUint16Array(
+ TypedArrayPrototypeGetBuffer(float16bitsArray),
+ TypedArrayPrototypeGetByteOffset(float16bitsArray),
+ TypedArrayPrototypeGetLength(float16bitsArray)
+ );
+ const cloned = new Float16Array(
+ TypedArrayPrototypeGetBuffer(
+ TypedArrayPrototypeSlice(uint16)
+ )
+ );
+ const clonedFloat16bitsArray = getFloat16BitsArray(cloned);
+ TypedArrayPrototypeReverse(clonedFloat16bitsArray);
+ return cloned;
+ }
fill(value, ...opts) {
assertFloat16Array(this);
const float16bitsArray = getFloat16BitsArray(this);
@@ -1020,6 +1099,29 @@ class Float16Array {
});
return this;
}
+ toSorted(compareFn) {
+ assertFloat16Array(this);
+ const float16bitsArray = getFloat16BitsArray(this);
+ if (compareFn !== undefined && typeof compareFn !== "function") {
+ throw new NativeTypeError(THE_COMPARISON_FUNCTION_MUST_BE_EITHER_A_FUNCTION_OR_UNDEFINED);
+ }
+ const sortCompare = compareFn !== undefined ? compareFn : defaultCompare;
+ const uint16 = new NativeUint16Array(
+ TypedArrayPrototypeGetBuffer(float16bitsArray),
+ TypedArrayPrototypeGetByteOffset(float16bitsArray),
+ TypedArrayPrototypeGetLength(float16bitsArray)
+ );
+ const cloned = new Float16Array(
+ TypedArrayPrototypeGetBuffer(
+ TypedArrayPrototypeSlice(uint16)
+ )
+ );
+ const clonedFloat16bitsArray = getFloat16BitsArray(cloned);
+ TypedArrayPrototypeSort(clonedFloat16bitsArray, (x, y) => {
+ return sortCompare(convertToNumber(x), convertToNumber(y));
+ });
+ return cloned;
+ }
slice(start, end) {
assertFloat16Array(this);
const float16bitsArray = getFloat16BitsArray(this);
@@ -1216,13 +1318,8 @@ function setFloat16(dataView, byteOffset, value, ...opts) {
);
}
-function hfround(x) {
- const number = +x;
- if (!NumberIsFinite(number) || number === 0) {
- return number;
- }
- const x16 = roundToFloat16Bits(number);
- return convertToNumber(x16);
+function f16round(x) {
+ return roundToFloat16(x);
}
-export { Float16Array, getFloat16, hfround, isFloat16Array, isTypedArray, setFloat16 };
+export { Float16Array, f16round, getFloat16, f16round as hfround, isFloat16Array, isTypedArray, setFloat16 }; \ No newline at end of file
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/README.md b/dom/webgpu/tests/cts/checkout/src/resources/README.md
index 824f82b998..a1ed060417 100644
--- a/dom/webgpu/tests/cts/checkout/src/resources/README.md
+++ b/dom/webgpu/tests/cts/checkout/src/resources/README.md
@@ -2,14 +2,92 @@ Always use `getResourcePath()` to get the appropriate path to these resources de
on the context (WPT, standalone, worker, etc.)
-The test video files were generated with the ffmpeg cmds below:
-ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libvpx -pix_fmt yuv420p -frames 500 -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range tv four-colors-vp8-bt601.webm
-ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libtheora -pix_fmt yuv420p -frames 500 -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range tv four-colors-theora-bt601.ogv
-ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libx264 -pix_fmt yuv420p -frames 500 -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range tv four-colors-h264-bt601.mp4
-ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libvpx-vp9 -pix_fmt yuv420p -frames 500 -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range tv four-colors-vp9-bt601.webm
-ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libvpx-vp9 -pix_fmt yuv420p -frames 500 -colorspace bt709 -color_primaries bt709 -color_trc bt709 -color_range tv -vf scale=out_color_matrix=bt709:out_range=tv four-colors-vp9-bt709.webm
-
-These rotation test files are copies of four-colors-h264-bt601.mp4 with metadata changes.
-ffmpeg.exe -i .\four-colors-h264-bt601.mp4 -c copy -metadata:s:v rotate=90 four-colors-h264-bt601-rotate-90.mp4
-ffmpeg.exe -i .\four-colors-h264-bt601.mp4 -c copy -metadata:s:v rotate=180 four-colors-h264-bt601-rotate-180.mp4
-ffmpeg.exe -i .\four-colors-h264-bt601.mp4 -c copy -metadata:s:v rotate=270 four-colors-h264-bt601-rotate-270.mp4 \ No newline at end of file
+The test video files were generated with by ffmpeg cmds below:
+```
+// Generate four-colors-vp8-bt601.webm, mimeType: 'video/webm; codecs=vp8'
+ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libvpx -pix_fmt yuv420p -frames 50 -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range tv four-colors-vp8-bt601.webm
+
+// Generate four-colors-h264-bt601.mp4, mimeType: 'video/mp4; codecs=avc1.4d400c'
+ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libx264 -pix_fmt yuv420p -frames 50 -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range tv four-colors-h264-bt601.mp4
+
+// Generate four-colors-vp9-bt601.webm, mimeType: 'video/webm; codecs=vp9'
+ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libvpx-vp9 -pix_fmt yuv420p -frames 50 -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range tv four-colors-vp9-bt601.webm
+
+// Generate four-colors-vp9-bt709.webm, mimeType: 'video/webm; codecs=vp9'
+ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libvpx-vp9 -pix_fmt yuv420p -frames 50 -colorspace bt709 -color_primaries bt709 -color_trc bt709 -color_range tv -vf scale=out_color_matrix=bt709:out_range=tv four-colors-vp9-bt709.webm
+
+// Generate four-colors-vp9-bt601.mp4, mimeType: 'video/mp4; codecs=vp9'
+ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libvpx-vp9 -pix_fmt yuv420p -frames 50 -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range tv four-colors-vp9-bt601.mp4
+```
+
+Generate video files to test rotation behaviour.
+Use ffmepg to rotate video content x degrees in cw direction (by using `transpose`) and update transform matrix in metadata through `display_rotation` to x degrees to apply ccw direction rotation.
+
+H264 rotated video files are generated by ffmpeg cmds below:
+```
+// Generate four-colors-h264-bt601-rotate-90.mp4, mimeType: 'video/mp4; codecs=avc1.4d400c'
+ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libx264 -pix_fmt yuv420p -frames 50 -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range tv -vf transpose=2 temp.mp4
+ffmpeg -display_rotation 270 -i temp.mp4 -c copy four-colors-h264-bt601-rotate-90.mp4
+rm temp.mp4
+
+// Generate four-colors-h264-bt601-rotate-180.mp4, mimeType: 'video/mp4; codecs=avc1.4d400c'
+ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libx264 -pix_fmt yuv420p -frames 50 -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range tv -vf transpose=2,transpose=2 temp.mp4
+ffmpeg -display_rotation 180 -i temp.mp4 -c copy four-colors-h264-bt601-rotate-180.mp4
+rm temp.mp4
+
+// Generate four-colors-h264-bt601-rotate-270.mp4, mimeType: 'video/mp4; codecs=avc1.4d400c'
+ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libx264 -pix_fmt yuv420p -frames 50 -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range tv -vf transpose=1 temp.mp4
+ffmpeg -display_rotation 90 -i temp.mp4 -c copy four-colors-h264-bt601-rotate-270.mp4
+rm temp.mp4
+
+```
+
+Vp9 rotated video files are generated by ffmpeg cmds below:
+```
+// Generate four-colors-vp9-bt601-rotate-90.mp4, mimeType: 'video/mp4; codecs=vp9'
+ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libvpx-vp9 -pix_fmt yuv420p -frames 50 -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range tv -vf transpose=2 temp.mp4
+ffmpeg -display_rotation 270 -i temp.mp4 -c copy four-colors-vp9-bt601-rotate-90.mp4
+rm temp.mp4
+
+// Generate four-colors-vp9-bt601-rotate-180.mp4, mimeType: 'video/mp4; codecs=vp9'
+ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libvpx-vp9 -pix_fmt yuv420p -frames 50 -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range tv -vf transpose=2,transpose=2 temp.mp4
+ffmpeg -display_rotation 180 -i temp.mp4 -c copy four-colors-vp9-bt601-rotate-180.mp4
+rm temp.mp4
+
+// Generate four-colors-vp9-bt601-rotate-270.mp4, mimeType: 'video/mp4; codecs=vp9'
+ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libvpx-vp9 -pix_fmt yuv420p -frames 50 -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range tv -vf transpose=1 temp.mp4
+ffmpeg -display_rotation 90 -i temp.mp4 -c copy four-colors-vp9-bt601-rotate-270.mp4
+rm temp.mp4
+
+```
+
+Generate video files to test flip behaviour.
+Use ffmpeg to flip video content. Using `display_hflip` to do horizontal flip and `display_vflip` to do vertical flip.
+
+H264 flip video files are generated by ffmpeg cmds below:
+```
+// Generate four-colors-h264-bt601-hflip.mp4, mimeType: 'video/mp4; codecs=avc1.4d400c'
+ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libx264 -pix_fmt yuv420p -frames 50 -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range tv temp.mp4
+ffmpeg -display_hflip -i temp.mp4 -c copy four-colors-h264-bt601-hflip.mp4
+rm temp.mp4
+
+// Generate four-colors-h264-bt601-vflip.mp4, mimeType: 'video/mp4; codecs=avc1.4d400c'
+ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libx264 -pix_fmt yuv420p -frames 50 -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range tv temp.mp4
+ffmpeg -display_vflip -i temp.mp4 -c copy four-colors-h264-bt601-vflip.mp4
+rm temp.mp4
+
+```
+
+Vp9 flip video files are generated by ffmpeg cmds below:
+```
+// Generate four-colors-vp9-bt601-hflip.mp4, mimeType: 'video/mp4; codecs=vp09.00.10.08'
+ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libvpx-vp9 -pix_fmt yuv420p -frames 50 -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range tv temp.mp4
+ffmpeg -display_hflip -i temp.mp4 -c copy four-colors-vp9-bt601-hflip.mp4
+rm temp.mp4
+
+// Generate four-colors-vp9-bt601-vflip.mp4, mimeType: 'video/mp4; codecs=vp09.00.10.08'
+ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libvpx-vp9 -pix_fmt yuv420p -frames 50 -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range tv temp.mp4
+ffmpeg -display_vflip -i temp.mp4 -c copy four-colors-vp9-bt601-vflip.mp4
+rm temp.mp4
+
+```
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/hashes.json b/dom/webgpu/tests/cts/checkout/src/resources/cache/hashes.json
new file mode 100644
index 0000000000..90efb474ad
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/hashes.json
@@ -0,0 +1,111 @@
+{
+ "webgpu/shader/execution/binary/af_addition.bin": "17c26b18",
+ "webgpu/shader/execution/binary/af_logical.bin": "b4fdda88",
+ "webgpu/shader/execution/binary/af_division.bin": "fa1fc451",
+ "webgpu/shader/execution/binary/af_matrix_addition.bin": "a2bebfc0",
+ "webgpu/shader/execution/binary/af_matrix_subtraction.bin": "5456944c",
+ "webgpu/shader/execution/binary/af_multiplication.bin": "fc54cae0",
+ "webgpu/shader/execution/binary/af_remainder.bin": "2ee1a014",
+ "webgpu/shader/execution/binary/af_subtraction.bin": "ba82dab3",
+ "webgpu/shader/execution/binary/f16_addition.bin": "4ccf0cde",
+ "webgpu/shader/execution/binary/f16_logical.bin": "5ffb4769",
+ "webgpu/shader/execution/binary/f16_division.bin": "c69d5326",
+ "webgpu/shader/execution/binary/f16_matrix_addition.bin": "23006f90",
+ "webgpu/shader/execution/binary/f16_matrix_matrix_multiplication.bin": "3b581360",
+ "webgpu/shader/execution/binary/f16_matrix_scalar_multiplication.bin": "4c2cf2fa",
+ "webgpu/shader/execution/binary/f16_matrix_subtraction.bin": "902ffcbc",
+ "webgpu/shader/execution/binary/f16_matrix_vector_multiplication.bin": "48acf022",
+ "webgpu/shader/execution/binary/f16_multiplication.bin": "e83fedc6",
+ "webgpu/shader/execution/binary/f16_remainder.bin": "68178090",
+ "webgpu/shader/execution/binary/f16_subtraction.bin": "de63294a",
+ "webgpu/shader/execution/binary/f32_addition.bin": "d693585",
+ "webgpu/shader/execution/binary/f32_logical.bin": "57b9e9da",
+ "webgpu/shader/execution/binary/f32_division.bin": "79048538",
+ "webgpu/shader/execution/binary/f32_matrix_addition.bin": "301c58ba",
+ "webgpu/shader/execution/binary/f32_matrix_matrix_multiplication.bin": "41bae804",
+ "webgpu/shader/execution/binary/f32_matrix_scalar_multiplication.bin": "d8a1003a",
+ "webgpu/shader/execution/binary/f32_matrix_subtraction.bin": "3b1cc190",
+ "webgpu/shader/execution/binary/f32_matrix_vector_multiplication.bin": "66a57dbc",
+ "webgpu/shader/execution/binary/f32_multiplication.bin": "c0bf07da",
+ "webgpu/shader/execution/binary/f32_remainder.bin": "238fd8eb",
+ "webgpu/shader/execution/binary/f32_subtraction.bin": "d977904f",
+ "webgpu/shader/execution/binary/i32_arithmetic.bin": "8168bdb4",
+ "webgpu/shader/execution/binary/i32_comparison.bin": "eae9d767",
+ "webgpu/shader/execution/binary/u32_arithmetic.bin": "5c313ea9",
+ "webgpu/shader/execution/binary/u32_comparison.bin": "5ef80f48",
+ "webgpu/shader/execution/abs.bin": "1a23882d",
+ "webgpu/shader/execution/acos.bin": "66020d3b",
+ "webgpu/shader/execution/acosh.bin": "eeed5b15",
+ "webgpu/shader/execution/asin.bin": "e38b87bf",
+ "webgpu/shader/execution/asinh.bin": "d5cf509e",
+ "webgpu/shader/execution/atan.bin": "780f5bf9",
+ "webgpu/shader/execution/atan2.bin": "242c3f80",
+ "webgpu/shader/execution/atanh.bin": "2c6771e5",
+ "webgpu/shader/execution/bitcast.bin": "4ebd46da",
+ "webgpu/shader/execution/ceil.bin": "b190ce63",
+ "webgpu/shader/execution/clamp.bin": "9ba5f3c9",
+ "webgpu/shader/execution/cos.bin": "edeb923",
+ "webgpu/shader/execution/cosh.bin": "c12c748b",
+ "webgpu/shader/execution/cross.bin": "4cfaddf8",
+ "webgpu/shader/execution/degrees.bin": "dc77e03b",
+ "webgpu/shader/execution/determinant.bin": "a87e4d61",
+ "webgpu/shader/execution/distance.bin": "9e397f5a",
+ "webgpu/shader/execution/dot.bin": "db692304",
+ "webgpu/shader/execution/exp.bin": "b0cbd306",
+ "webgpu/shader/execution/exp2.bin": "b32745cd",
+ "webgpu/shader/execution/faceForward.bin": "f0cc892a",
+ "webgpu/shader/execution/floor.bin": "4a460013",
+ "webgpu/shader/execution/fma.bin": "c89c3d19",
+ "webgpu/shader/execution/fract.bin": "f6230a96",
+ "webgpu/shader/execution/frexp.bin": "132962c",
+ "webgpu/shader/execution/inverseSqrt.bin": "cc1b943c",
+ "webgpu/shader/execution/ldexp.bin": "4e14b67d",
+ "webgpu/shader/execution/length.bin": "a75b23ef",
+ "webgpu/shader/execution/log.bin": "61175a12",
+ "webgpu/shader/execution/log2.bin": "d24b375d",
+ "webgpu/shader/execution/max.bin": "5689d61b",
+ "webgpu/shader/execution/min.bin": "8fd8d393",
+ "webgpu/shader/execution/mix.bin": "caf44b85",
+ "webgpu/shader/execution/modf.bin": "223ff03f",
+ "webgpu/shader/execution/normalize.bin": "e0634ba",
+ "webgpu/shader/execution/pack2x16float.bin": "a8ca6b51",
+ "webgpu/shader/execution/pow.bin": "b2bbd5ce",
+ "webgpu/shader/execution/quantizeToF16.bin": "be9ef6ab",
+ "webgpu/shader/execution/radians.bin": "afaf4f61",
+ "webgpu/shader/execution/reflect.bin": "742b05b4",
+ "webgpu/shader/execution/refract.bin": "ad05949f",
+ "webgpu/shader/execution/round.bin": "3a006c0c",
+ "webgpu/shader/execution/saturate.bin": "61753b4f",
+ "webgpu/shader/execution/sign.bin": "71e6517c",
+ "webgpu/shader/execution/sin.bin": "17c44cc8",
+ "webgpu/shader/execution/sinh.bin": "151ae2d1",
+ "webgpu/shader/execution/smoothstep.bin": "35db8f9a",
+ "webgpu/shader/execution/sqrt.bin": "a4b4bdf9",
+ "webgpu/shader/execution/step.bin": "e3cc0f86",
+ "webgpu/shader/execution/tan.bin": "9ad0e1f1",
+ "webgpu/shader/execution/tanh.bin": "99454fdd",
+ "webgpu/shader/execution/transpose.bin": "959a2407",
+ "webgpu/shader/execution/trunc.bin": "1a47263e",
+ "webgpu/shader/execution/unpack2x16float.bin": "39f959fe",
+ "webgpu/shader/execution/unpack2x16snorm.bin": "b409d047",
+ "webgpu/shader/execution/unpack2x16unorm.bin": "49a89145",
+ "webgpu/shader/execution/unpack4x8snorm.bin": "e53a7842",
+ "webgpu/shader/execution/unpack4x8unorm.bin": "9bc3f0ea",
+ "webgpu/shader/execution/unary/af_arithmetic.bin": "e9e06bc7",
+ "webgpu/shader/execution/unary/af_assignment.bin": "5c9160c2",
+ "webgpu/shader/execution/unary/bool_conversion.bin": "a2260d3a",
+ "webgpu/shader/execution/unary/f16_arithmetic.bin": "7a3fd3db",
+ "webgpu/shader/execution/unary/f16_conversion.bin": "b9407945",
+ "webgpu/shader/execution/unary/f32_arithmetic.bin": "6e392701",
+ "webgpu/shader/execution/unary/f32_conversion.bin": "6ae4cce9",
+ "webgpu/shader/execution/unary/i32_arithmetic.bin": "f5ef6485",
+ "webgpu/shader/execution/unary/i32_conversion.bin": "75435733",
+ "webgpu/shader/execution/unary/u32_conversion.bin": "26baf99",
+ "webgpu/shader/execution/unary/ai_assignment.bin": "d1b00a8",
+ "webgpu/shader/execution/binary/ai_arithmetic.bin": "dc777f4e",
+ "webgpu/shader/execution/unary/ai_arithmetic.bin": "272c5df2",
+ "webgpu/shader/execution/binary/af_matrix_matrix_multiplication.bin": "ab6db19f",
+ "webgpu/shader/execution/binary/af_matrix_scalar_multiplication.bin": "1de2ec75",
+ "webgpu/shader/execution/binary/af_matrix_vector_multiplication.bin": "e665650a",
+ "webgpu/shader/execution/derivatives.bin": "899e4e4c"
+} \ No newline at end of file
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/abs.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/abs.bin
new file mode 100644
index 0000000000..4cba9b72df
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/abs.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/acos.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/acos.bin
new file mode 100644
index 0000000000..2ecaaa389a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/acos.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/acosh.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/acosh.bin
new file mode 100644
index 0000000000..d48659f3c3
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/acosh.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/asin.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/asin.bin
new file mode 100644
index 0000000000..b199953eaf
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/asin.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/asinh.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/asinh.bin
new file mode 100644
index 0000000000..b370c53b01
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/asinh.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/atan.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/atan.bin
new file mode 100644
index 0000000000..6ab0ba106a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/atan.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/atan2.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/atan2.bin
new file mode 100644
index 0000000000..0109ddbc37
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/atan2.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/atanh.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/atanh.bin
new file mode 100644
index 0000000000..e6a190b35d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/atanh.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_addition.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_addition.bin
new file mode 100644
index 0000000000..ebd757c1b6
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_addition.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_division.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_division.bin
new file mode 100644
index 0000000000..656356d32e
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_division.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_logical.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_logical.bin
new file mode 100644
index 0000000000..e3594458f2
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_logical.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_addition.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_addition.bin
new file mode 100644
index 0000000000..ba9d123cbf
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_addition.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_matrix_multiplication.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_matrix_multiplication.bin
new file mode 100644
index 0000000000..58d0d40cb9
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_matrix_multiplication.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_scalar_multiplication.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_scalar_multiplication.bin
new file mode 100644
index 0000000000..c8a3b7205a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_scalar_multiplication.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_subtraction.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_subtraction.bin
new file mode 100644
index 0000000000..8f88d196ce
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_subtraction.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_vector_multiplication.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_vector_multiplication.bin
new file mode 100644
index 0000000000..545a5112cf
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_vector_multiplication.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_multiplication.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_multiplication.bin
new file mode 100644
index 0000000000..552d8b4892
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_multiplication.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_remainder.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_remainder.bin
new file mode 100644
index 0000000000..c45792abf4
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_remainder.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_subtraction.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_subtraction.bin
new file mode 100644
index 0000000000..6f0be29785
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_subtraction.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/ai_arithmetic.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/ai_arithmetic.bin
new file mode 100644
index 0000000000..658eb46d39
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/ai_arithmetic.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_addition.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_addition.bin
new file mode 100644
index 0000000000..30f099139d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_addition.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_division.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_division.bin
new file mode 100644
index 0000000000..22e60bef81
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_division.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_logical.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_logical.bin
new file mode 100644
index 0000000000..932af58208
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_logical.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_addition.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_addition.bin
new file mode 100644
index 0000000000..452376760b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_addition.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_matrix_multiplication.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_matrix_multiplication.bin
new file mode 100644
index 0000000000..e823daac6c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_matrix_multiplication.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_scalar_multiplication.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_scalar_multiplication.bin
new file mode 100644
index 0000000000..b48be81ebd
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_scalar_multiplication.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_subtraction.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_subtraction.bin
new file mode 100644
index 0000000000..386558c3f7
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_subtraction.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_vector_multiplication.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_vector_multiplication.bin
new file mode 100644
index 0000000000..cbf224a6b6
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_vector_multiplication.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_multiplication.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_multiplication.bin
new file mode 100644
index 0000000000..e9d27019a3
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_multiplication.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_remainder.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_remainder.bin
new file mode 100644
index 0000000000..d21370aec9
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_remainder.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_subtraction.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_subtraction.bin
new file mode 100644
index 0000000000..97080a8ce5
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_subtraction.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_addition.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_addition.bin
new file mode 100644
index 0000000000..be7b997bd8
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_addition.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_division.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_division.bin
new file mode 100644
index 0000000000..f80461c21b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_division.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_logical.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_logical.bin
new file mode 100644
index 0000000000..a819eab08c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_logical.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_addition.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_addition.bin
new file mode 100644
index 0000000000..b1d7179264
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_addition.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_matrix_multiplication.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_matrix_multiplication.bin
new file mode 100644
index 0000000000..232760828a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_matrix_multiplication.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_scalar_multiplication.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_scalar_multiplication.bin
new file mode 100644
index 0000000000..76f867b959
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_scalar_multiplication.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_subtraction.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_subtraction.bin
new file mode 100644
index 0000000000..0d0fd2460d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_subtraction.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_vector_multiplication.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_vector_multiplication.bin
new file mode 100644
index 0000000000..e139fc9713
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_vector_multiplication.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_multiplication.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_multiplication.bin
new file mode 100644
index 0000000000..1837ce922c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_multiplication.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_remainder.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_remainder.bin
new file mode 100644
index 0000000000..3febfca1d1
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_remainder.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_subtraction.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_subtraction.bin
new file mode 100644
index 0000000000..32b34f690c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_subtraction.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/i32_arithmetic.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/i32_arithmetic.bin
new file mode 100644
index 0000000000..bacd4c0c54
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/i32_arithmetic.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/i32_comparison.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/i32_comparison.bin
new file mode 100644
index 0000000000..d5a745e85c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/i32_comparison.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/u32_arithmetic.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/u32_arithmetic.bin
new file mode 100644
index 0000000000..56ef292864
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/u32_arithmetic.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/u32_comparison.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/u32_comparison.bin
new file mode 100644
index 0000000000..5ba639b3cd
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/u32_comparison.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/bitcast.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/bitcast.bin
new file mode 100644
index 0000000000..2acc4a318b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/bitcast.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/ceil.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/ceil.bin
new file mode 100644
index 0000000000..9b93ed416f
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/ceil.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/clamp.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/clamp.bin
new file mode 100644
index 0000000000..492be017aa
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/clamp.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/cos.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/cos.bin
new file mode 100644
index 0000000000..4e34eff3f1
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/cos.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/cosh.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/cosh.bin
new file mode 100644
index 0000000000..5b30d2786c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/cosh.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/cross.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/cross.bin
new file mode 100644
index 0000000000..c8ee9d3e1a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/cross.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/degrees.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/degrees.bin
new file mode 100644
index 0000000000..662558d78a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/degrees.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/derivatives.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/derivatives.bin
new file mode 100644
index 0000000000..d6d0788775
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/derivatives.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/determinant.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/determinant.bin
new file mode 100644
index 0000000000..16d58c6db6
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/determinant.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/distance.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/distance.bin
new file mode 100644
index 0000000000..23a4756a69
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/distance.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/dot.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/dot.bin
new file mode 100644
index 0000000000..13622a686b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/dot.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/exp.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/exp.bin
new file mode 100644
index 0000000000..29361a2b27
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/exp.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/exp2.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/exp2.bin
new file mode 100644
index 0000000000..367b5a8e90
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/exp2.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/faceForward.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/faceForward.bin
new file mode 100644
index 0000000000..8f065bb97c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/faceForward.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/floor.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/floor.bin
new file mode 100644
index 0000000000..b5341907f8
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/floor.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/fma.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/fma.bin
new file mode 100644
index 0000000000..eb4cb9ebbe
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/fma.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/fract.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/fract.bin
new file mode 100644
index 0000000000..f889961d8f
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/fract.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/frexp.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/frexp.bin
new file mode 100644
index 0000000000..6811dfa295
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/frexp.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/inverseSqrt.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/inverseSqrt.bin
new file mode 100644
index 0000000000..5039345ad0
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/inverseSqrt.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/ldexp.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/ldexp.bin
new file mode 100644
index 0000000000..bab78ed2af
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/ldexp.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/length.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/length.bin
new file mode 100644
index 0000000000..3644d9b683
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/length.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/log.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/log.bin
new file mode 100644
index 0000000000..ba591faad8
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/log.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/log2.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/log2.bin
new file mode 100644
index 0000000000..00641ce119
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/log2.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/max.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/max.bin
new file mode 100644
index 0000000000..3861a94aca
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/max.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/min.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/min.bin
new file mode 100644
index 0000000000..21c29e62ed
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/min.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/mix.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/mix.bin
new file mode 100644
index 0000000000..c42b2aa067
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/mix.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/modf.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/modf.bin
new file mode 100644
index 0000000000..363cc161fd
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/modf.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/normalize.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/normalize.bin
new file mode 100644
index 0000000000..01b8eab700
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/normalize.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/pack2x16float.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/pack2x16float.bin
new file mode 100644
index 0000000000..e95227d36e
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/pack2x16float.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/pow.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/pow.bin
new file mode 100644
index 0000000000..4f5faf3293
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/pow.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/quantizeToF16.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/quantizeToF16.bin
new file mode 100644
index 0000000000..9e4308d5cd
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/quantizeToF16.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/radians.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/radians.bin
new file mode 100644
index 0000000000..f5285d1087
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/radians.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/reflect.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/reflect.bin
new file mode 100644
index 0000000000..30cd7ee925
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/reflect.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/refract.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/refract.bin
new file mode 100644
index 0000000000..c428285817
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/refract.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/round.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/round.bin
new file mode 100644
index 0000000000..c3b30b68f0
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/round.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/saturate.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/saturate.bin
new file mode 100644
index 0000000000..2e1eb821a9
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/saturate.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/sign.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/sign.bin
new file mode 100644
index 0000000000..033f2e8158
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/sign.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/sin.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/sin.bin
new file mode 100644
index 0000000000..a2ca632008
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/sin.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/sinh.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/sinh.bin
new file mode 100644
index 0000000000..1176cd472b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/sinh.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/smoothstep.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/smoothstep.bin
new file mode 100644
index 0000000000..73b65d17c2
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/smoothstep.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/sqrt.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/sqrt.bin
new file mode 100644
index 0000000000..6dd8088c08
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/sqrt.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/step.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/step.bin
new file mode 100644
index 0000000000..f6c6c7b5f3
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/step.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/tan.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/tan.bin
new file mode 100644
index 0000000000..572bee4df2
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/tan.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/tanh.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/tanh.bin
new file mode 100644
index 0000000000..a13028b165
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/tanh.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/transpose.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/transpose.bin
new file mode 100644
index 0000000000..d1b6bf04ee
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/transpose.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/trunc.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/trunc.bin
new file mode 100644
index 0000000000..ba81e2ada4
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/trunc.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/af_arithmetic.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/af_arithmetic.bin
new file mode 100644
index 0000000000..21d3d702ae
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/af_arithmetic.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/af_assignment.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/af_assignment.bin
new file mode 100644
index 0000000000..a92279b5ce
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/af_assignment.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/ai_arithmetic.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/ai_arithmetic.bin
new file mode 100644
index 0000000000..2fa273ff19
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/ai_arithmetic.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/ai_assignment.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/ai_assignment.bin
new file mode 100644
index 0000000000..7956b3652a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/ai_assignment.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/bool_conversion.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/bool_conversion.bin
new file mode 100644
index 0000000000..98a90ea45b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/bool_conversion.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/f16_arithmetic.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/f16_arithmetic.bin
new file mode 100644
index 0000000000..acf8a702ce
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/f16_arithmetic.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/f16_conversion.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/f16_conversion.bin
new file mode 100644
index 0000000000..14299da766
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/f16_conversion.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/f32_arithmetic.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/f32_arithmetic.bin
new file mode 100644
index 0000000000..ebc60029fa
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/f32_arithmetic.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/f32_conversion.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/f32_conversion.bin
new file mode 100644
index 0000000000..bdcc0c7298
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/f32_conversion.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/i32_arithmetic.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/i32_arithmetic.bin
new file mode 100644
index 0000000000..4753b020c9
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/i32_arithmetic.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/i32_conversion.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/i32_conversion.bin
new file mode 100644
index 0000000000..04841df607
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/i32_conversion.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/u32_conversion.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/u32_conversion.bin
new file mode 100644
index 0000000000..277ffc4d76
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/u32_conversion.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack2x16float.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack2x16float.bin
new file mode 100644
index 0000000000..7f06cb0df6
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack2x16float.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack2x16snorm.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack2x16snorm.bin
new file mode 100644
index 0000000000..08c6af9e93
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack2x16snorm.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack2x16unorm.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack2x16unorm.bin
new file mode 100644
index 0000000000..1bb97b9c55
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack2x16unorm.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack4x8snorm.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack4x8snorm.bin
new file mode 100644
index 0000000000..1db9856b05
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack4x8snorm.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack4x8unorm.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack4x8unorm.bin
new file mode 100644
index 0000000000..8d1f3dc7fb
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack4x8unorm.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-hflip.mp4 b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-hflip.mp4
new file mode 100644
index 0000000000..f83b4f9698
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-hflip.mp4
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-rotate-180.mp4 b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-rotate-180.mp4
index 1f0e9094a5..6665ea900d 100644
--- a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-rotate-180.mp4
+++ b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-rotate-180.mp4
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-rotate-270.mp4 b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-rotate-270.mp4
index e0480ceff2..b1e32bc83a 100644
--- a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-rotate-270.mp4
+++ b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-rotate-270.mp4
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-rotate-90.mp4 b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-rotate-90.mp4
index 9a6261056e..66a98d0ed0 100644
--- a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-rotate-90.mp4
+++ b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-rotate-90.mp4
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-vflip.mp4 b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-vflip.mp4
new file mode 100644
index 0000000000..90c3297a9a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-vflip.mp4
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601.mp4 b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601.mp4
index 81a5ade435..5317bbf7c6 100644
--- a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601.mp4
+++ b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601.mp4
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-theora-bt601.ogv b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-theora-bt601.ogv
deleted file mode 100644
index 79ed41163c..0000000000
--- a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-theora-bt601.ogv
+++ /dev/null
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp8-bt601.webm b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp8-bt601.webm
index 20a2178596..d1504ee332 100644
--- a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp8-bt601.webm
+++ b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp8-bt601.webm
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-hflip.mp4 b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-hflip.mp4
new file mode 100644
index 0000000000..f782c32651
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-hflip.mp4
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-rotate-180.mp4 b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-rotate-180.mp4
new file mode 100644
index 0000000000..fc712becd7
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-rotate-180.mp4
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-rotate-270.mp4 b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-rotate-270.mp4
new file mode 100644
index 0000000000..a83558f53c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-rotate-270.mp4
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-rotate-90.mp4 b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-rotate-90.mp4
new file mode 100644
index 0000000000..73a03795ba
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-rotate-90.mp4
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-vflip.mp4 b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-vflip.mp4
new file mode 100644
index 0000000000..c9de14696a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-vflip.mp4
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601.mp4 b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601.mp4
new file mode 100644
index 0000000000..0d8d4f829c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601.mp4
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601.webm b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601.webm
index a4044a9209..47a43a0695 100644
--- a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601.webm
+++ b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601.webm
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt709.webm b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt709.webm
index 189e422035..a9e069ee1c 100644
--- a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt709.webm
+++ b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt709.webm
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/unittests/conversion.spec.ts b/dom/webgpu/tests/cts/checkout/src/unittests/conversion.spec.ts
index 8606aa8717..e144f39288 100644
--- a/dom/webgpu/tests/cts/checkout/src/unittests/conversion.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/unittests/conversion.spec.ts
@@ -18,7 +18,7 @@ import {
i32,
kFloat16Format,
kFloat32Format,
- Matrix,
+ MatrixValue,
numbersApproximatelyEqual,
pack2x16float,
pack2x16snorm,
@@ -26,14 +26,14 @@ import {
pack4x8snorm,
pack4x8unorm,
packRGB9E5UFloat,
- Scalar,
+ ScalarValue,
toMatrix,
u32,
unpackRGB9E5UFloat,
vec2,
vec3,
vec4,
- Vector,
+ VectorValue,
} from '../webgpu/util/conversion.js';
import { UnitTest } from './unit_test.js';
@@ -191,7 +191,7 @@ g.test('floatBitsToULPFromZero,32').fn(t => {
});
g.test('scalarWGSL').fn(t => {
- const cases: Array<[Scalar, string]> = [
+ const cases: Array<[ScalarValue, string]> = [
[f32(0.0), '0.0f'],
// The number -0.0 can be remapped to 0.0 when stored in a Scalar
// object. It is not possible to guarantee that '-0.0f' will
@@ -227,7 +227,7 @@ expect: ${expect}`
});
g.test('vectorWGSL').fn(t => {
- const cases: Array<[Vector, string]> = [
+ const cases: Array<[VectorValue, string]> = [
[vec2(f32(42.0), f32(24.0)), 'vec2(42.0f, 24.0f)'],
[vec2(f16Bits(0x5140), f16Bits(0x4e00)), 'vec2(42.0h, 24.0h)'],
[vec2(u32(42), u32(24)), 'vec2(42u, 24u)'],
@@ -261,7 +261,7 @@ expect: ${expect}`
});
g.test('matrixWGSL').fn(t => {
- const cases: Array<[Matrix, string]> = [
+ const cases: Array<[MatrixValue, string]> = [
[
toMatrix(
[
@@ -391,7 +391,7 @@ g.test('constructorMatrix')
return [...Array(rows).keys()].map(r => scalar_builder(c * cols + r));
});
- const got = new Matrix(elements);
+ const got = new MatrixValue(elements);
const got_type = got.type;
t.expect(
got_type.cols === cols,
diff --git a/dom/webgpu/tests/cts/checkout/src/unittests/crc32.spec.ts b/dom/webgpu/tests/cts/checkout/src/unittests/crc32.spec.ts
new file mode 100644
index 0000000000..5986823c8a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/unittests/crc32.spec.ts
@@ -0,0 +1,28 @@
+export const description = `
+Test for crc32 utility functions.
+`;
+
+import { makeTestGroup } from '../common/framework/test_group.js';
+import { crc32, toHexString } from '../common/util/crc32.js';
+
+import { UnitTest } from './unit_test.js';
+
+class F extends UnitTest {
+ test(content: string, expect: string): void {
+ const got = toHexString(crc32(content));
+ this.expect(
+ expect === got,
+ `
+expected: ${expect}
+got: ${got}`
+ );
+ }
+}
+
+export const g = makeTestGroup(F);
+
+g.test('strings').fn(t => {
+ t.test('', '00000000');
+ t.test('hello world', '0d4a1185');
+ t.test('123456789', 'cbf43926');
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/unittests/floating_point.spec.ts b/dom/webgpu/tests/cts/checkout/src/unittests/floating_point.spec.ts
index e8f8525d7f..31501f77ff 100644
--- a/dom/webgpu/tests/cts/checkout/src/unittests/floating_point.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/unittests/floating_point.spec.ts
@@ -5,7 +5,12 @@ Floating Point unit tests.
import { makeTestGroup } from '../common/framework/test_group.js';
import { objectEquals, unreachable } from '../common/util/util.js';
import { kValue } from '../webgpu/util/constants.js';
-import { FP, FPInterval, FPIntervalParam, IntervalBounds } from '../webgpu/util/floating_point.js';
+import {
+ FP,
+ FPInterval,
+ FPIntervalParam,
+ IntervalEndpoints,
+} from '../webgpu/util/floating_point.js';
import { map2DArray, oneULPF32, oneULPF16, oneULPF64 } from '../webgpu/util/math.js';
import {
reinterpretU16AsF16,
@@ -17,24 +22,19 @@ import { UnitTest } from './unit_test.js';
export const g = makeTestGroup(UnitTest);
-/**
- * For ULP purposes, abstract float behaves like f32, so need to swizzle it in
- * for expectations.
- */
const kFPTraitForULP = {
- abstract: 'f32',
f32: 'f32',
f16: 'f16',
} as const;
-/** Bounds indicating an expectation of unbounded error */
-const kUnboundedBounds: IntervalBounds = [Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY];
+/** Endpoints indicating an expectation of unbounded error */
+const kUnboundedEndpoints: IntervalEndpoints = [Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY];
-/** Interval from kUnboundedBounds */
+/** Interval from kUnboundedEndpoints */
const kUnboundedInterval = {
- f32: FP.f32.toParam(kUnboundedBounds),
- f16: FP.f16.toParam(kUnboundedBounds),
- abstract: FP.abstract.toParam(kUnboundedBounds),
+ f32: FP.f32.toParam(kUnboundedEndpoints),
+ f16: FP.f16.toParam(kUnboundedEndpoints),
+ abstract: FP.abstract.toParam(kUnboundedEndpoints),
};
/** @returns a number N * ULP greater than the provided number */
@@ -89,17 +89,17 @@ const kMinusOneULPFunctions = {
},
};
-/** @returns the expected IntervalBounds adjusted by the given error function
+/** @returns the expected IntervalEndpoints adjusted by the given error function
*
- * @param expected the bounds to be adjusted
- * @param error error function to adjust the bounds via
+ * @param expected the endpoints to be adjusted
+ * @param error error function to adjust the endpoints via
*/
function applyError(
- expected: number | IntervalBounds,
+ expected: number | IntervalEndpoints,
error: (n: number) => number
-): IntervalBounds {
+): IntervalEndpoints {
// Avoiding going through FPInterval to avoid tying this to a specific kind
- const unpack = (n: number | IntervalBounds): [number, number] => {
+ const unpack = (n: number | IntervalEndpoints): [number, number] => {
if (expected instanceof Array) {
switch (expected.length) {
case 1:
@@ -107,7 +107,7 @@ function applyError(
case 2:
return [expected[0], expected[1]];
}
- unreachable(`Tried to unpack an IntervalBounds with length other than 1 or 2`);
+ unreachable(`Tried to unpack an IntervalEndpoints with length other than 1 or 2`);
} else {
// TS doesn't narrow this to number automatically
return [n as number, n as number];
@@ -128,8 +128,8 @@ function applyError(
// FPInterval
interface ConstructorCase {
- input: IntervalBounds;
- expected: IntervalBounds;
+ input: IntervalEndpoints;
+ expected: IntervalEndpoints;
}
g.test('constructor')
@@ -160,7 +160,7 @@ g.test('constructor')
// Infinities
{ input: [0, constants.positive.infinity], expected: [0, Number.POSITIVE_INFINITY] },
{ input: [constants.negative.infinity, 0], expected: [Number.NEGATIVE_INFINITY, 0] },
- { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedBounds },
+ { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
];
// Note: Out of range values are limited to infinities for abstract float, due to abstract
@@ -182,13 +182,13 @@ g.test('constructor')
.fn(t => {
const i = new FPInterval(t.params.trait, ...t.params.input);
t.expect(
- objectEquals(i.bounds(), t.params.expected),
+ objectEquals(i.endpoints(), t.params.expected),
`new FPInterval('${t.params.trait}', [${t.params.input}]) returned ${i}. Expected [${t.params.expected}]`
);
});
interface ContainsNumberCase {
- bounds: number | IntervalBounds;
+ endpoints: number | IntervalEndpoints;
value: number;
expected: boolean;
}
@@ -203,90 +203,90 @@ g.test('contains_number')
// prettier-ignore
const cases: ContainsNumberCase[] = [
// Common usage
- { bounds: [0, 10], value: 0, expected: true },
- { bounds: [0, 10], value: 10, expected: true },
- { bounds: [0, 10], value: 5, expected: true },
- { bounds: [0, 10], value: -5, expected: false },
- { bounds: [0, 10], value: 50, expected: false },
- { bounds: [0, 10], value: Number.NaN, expected: false },
- { bounds: [-5, 10], value: 0, expected: true },
- { bounds: [-5, 10], value: 10, expected: true },
- { bounds: [-5, 10], value: 5, expected: true },
- { bounds: [-5, 10], value: -5, expected: true },
- { bounds: [-5, 10], value: -6, expected: false },
- { bounds: [-5, 10], value: 50, expected: false },
- { bounds: [-5, 10], value: -10, expected: false },
- { bounds: [-1.375, 2.5], value: -10, expected: false },
- { bounds: [-1.375, 2.5], value: 0.5, expected: true },
- { bounds: [-1.375, 2.5], value: 10, expected: false },
+ { endpoints: [0, 10], value: 0, expected: true },
+ { endpoints: [0, 10], value: 10, expected: true },
+ { endpoints: [0, 10], value: 5, expected: true },
+ { endpoints: [0, 10], value: -5, expected: false },
+ { endpoints: [0, 10], value: 50, expected: false },
+ { endpoints: [0, 10], value: Number.NaN, expected: false },
+ { endpoints: [-5, 10], value: 0, expected: true },
+ { endpoints: [-5, 10], value: 10, expected: true },
+ { endpoints: [-5, 10], value: 5, expected: true },
+ { endpoints: [-5, 10], value: -5, expected: true },
+ { endpoints: [-5, 10], value: -6, expected: false },
+ { endpoints: [-5, 10], value: 50, expected: false },
+ { endpoints: [-5, 10], value: -10, expected: false },
+ { endpoints: [-1.375, 2.5], value: -10, expected: false },
+ { endpoints: [-1.375, 2.5], value: 0.5, expected: true },
+ { endpoints: [-1.375, 2.5], value: 10, expected: false },
// Point
- { bounds: 0, value: 0, expected: true },
- { bounds: 0, value: 10, expected: false },
- { bounds: 0, value: -1000, expected: false },
- { bounds: 10, value: 10, expected: true },
- { bounds: 10, value: 0, expected: false },
- { bounds: 10, value: -10, expected: false },
- { bounds: 10, value: 11, expected: false },
+ { endpoints: 0, value: 0, expected: true },
+ { endpoints: 0, value: 10, expected: false },
+ { endpoints: 0, value: -1000, expected: false },
+ { endpoints: 10, value: 10, expected: true },
+ { endpoints: 10, value: 0, expected: false },
+ { endpoints: 10, value: -10, expected: false },
+ { endpoints: 10, value: 11, expected: false },
// Upper infinity
- { bounds: [0, constants.positive.infinity], value: constants.positive.min, expected: true },
- { bounds: [0, constants.positive.infinity], value: constants.positive.max, expected: true },
- { bounds: [0, constants.positive.infinity], value: constants.positive.infinity, expected: true },
- { bounds: [0, constants.positive.infinity], value: constants.negative.min, expected: false },
- { bounds: [0, constants.positive.infinity], value: constants.negative.max, expected: false },
- { bounds: [0, constants.positive.infinity], value: constants.negative.infinity, expected: false },
+ { endpoints: [0, constants.positive.infinity], value: constants.positive.min, expected: true },
+ { endpoints: [0, constants.positive.infinity], value: constants.positive.max, expected: true },
+ { endpoints: [0, constants.positive.infinity], value: constants.positive.infinity, expected: true },
+ { endpoints: [0, constants.positive.infinity], value: constants.negative.min, expected: false },
+ { endpoints: [0, constants.positive.infinity], value: constants.negative.max, expected: false },
+ { endpoints: [0, constants.positive.infinity], value: constants.negative.infinity, expected: false },
// Lower infinity
- { bounds: [constants.negative.infinity, 0], value: constants.positive.min, expected: false },
- { bounds: [constants.negative.infinity, 0], value: constants.positive.max, expected: false },
- { bounds: [constants.negative.infinity, 0], value: constants.positive.infinity, expected: false },
- { bounds: [constants.negative.infinity, 0], value: constants.negative.min, expected: true },
- { bounds: [constants.negative.infinity, 0], value: constants.negative.max, expected: true },
- { bounds: [constants.negative.infinity, 0], value: constants.negative.infinity, expected: true },
+ { endpoints: [constants.negative.infinity, 0], value: constants.positive.min, expected: false },
+ { endpoints: [constants.negative.infinity, 0], value: constants.positive.max, expected: false },
+ { endpoints: [constants.negative.infinity, 0], value: constants.positive.infinity, expected: false },
+ { endpoints: [constants.negative.infinity, 0], value: constants.negative.min, expected: true },
+ { endpoints: [constants.negative.infinity, 0], value: constants.negative.max, expected: true },
+ { endpoints: [constants.negative.infinity, 0], value: constants.negative.infinity, expected: true },
// Full infinity
- { bounds: [constants.negative.infinity, constants.positive.infinity], value: constants.positive.min, expected: true },
- { bounds: [constants.negative.infinity, constants.positive.infinity], value: constants.positive.max, expected: true },
- { bounds: [constants.negative.infinity, constants.positive.infinity], value: constants.positive.infinity, expected: true },
- { bounds: [constants.negative.infinity, constants.positive.infinity], value: constants.negative.min, expected: true },
- { bounds: [constants.negative.infinity, constants.positive.infinity], value: constants.negative.max, expected: true },
- { bounds: [constants.negative.infinity, constants.positive.infinity], value: constants.negative.infinity, expected: true },
- { bounds: [constants.negative.infinity, constants.positive.infinity], value: Number.NaN, expected: true },
+ { endpoints: [constants.negative.infinity, constants.positive.infinity], value: constants.positive.min, expected: true },
+ { endpoints: [constants.negative.infinity, constants.positive.infinity], value: constants.positive.max, expected: true },
+ { endpoints: [constants.negative.infinity, constants.positive.infinity], value: constants.positive.infinity, expected: true },
+ { endpoints: [constants.negative.infinity, constants.positive.infinity], value: constants.negative.min, expected: true },
+ { endpoints: [constants.negative.infinity, constants.positive.infinity], value: constants.negative.max, expected: true },
+ { endpoints: [constants.negative.infinity, constants.positive.infinity], value: constants.negative.infinity, expected: true },
+ { endpoints: [constants.negative.infinity, constants.positive.infinity], value: Number.NaN, expected: true },
// Maximum f32 boundary
- { bounds: [0, constants.positive.max], value: constants.positive.min, expected: true },
- { bounds: [0, constants.positive.max], value: constants.positive.max, expected: true },
- { bounds: [0, constants.positive.max], value: constants.positive.infinity, expected: false },
- { bounds: [0, constants.positive.max], value: constants.negative.min, expected: false },
- { bounds: [0, constants.positive.max], value: constants.negative.max, expected: false },
- { bounds: [0, constants.positive.max], value: constants.negative.infinity, expected: false },
+ { endpoints: [0, constants.positive.max], value: constants.positive.min, expected: true },
+ { endpoints: [0, constants.positive.max], value: constants.positive.max, expected: true },
+ { endpoints: [0, constants.positive.max], value: constants.positive.infinity, expected: false },
+ { endpoints: [0, constants.positive.max], value: constants.negative.min, expected: false },
+ { endpoints: [0, constants.positive.max], value: constants.negative.max, expected: false },
+ { endpoints: [0, constants.positive.max], value: constants.negative.infinity, expected: false },
// Minimum f32 boundary
- { bounds: [constants.negative.min, 0], value: constants.positive.min, expected: false },
- { bounds: [constants.negative.min, 0], value: constants.positive.max, expected: false },
- { bounds: [constants.negative.min, 0], value: constants.positive.infinity, expected: false },
- { bounds: [constants.negative.min, 0], value: constants.negative.min, expected: true },
- { bounds: [constants.negative.min, 0], value: constants.negative.max, expected: true },
- { bounds: [constants.negative.min, 0], value: constants.negative.infinity, expected: false },
+ { endpoints: [constants.negative.min, 0], value: constants.positive.min, expected: false },
+ { endpoints: [constants.negative.min, 0], value: constants.positive.max, expected: false },
+ { endpoints: [constants.negative.min, 0], value: constants.positive.infinity, expected: false },
+ { endpoints: [constants.negative.min, 0], value: constants.negative.min, expected: true },
+ { endpoints: [constants.negative.min, 0], value: constants.negative.max, expected: true },
+ { endpoints: [constants.negative.min, 0], value: constants.negative.infinity, expected: false },
// Subnormals
- { bounds: [0, constants.positive.min], value: constants.positive.subnormal.min, expected: true },
- { bounds: [0, constants.positive.min], value: constants.positive.subnormal.max, expected: true },
- { bounds: [0, constants.positive.min], value: constants.negative.subnormal.min, expected: false },
- { bounds: [0, constants.positive.min], value: constants.negative.subnormal.max, expected: false },
- { bounds: [constants.negative.max, 0], value: constants.positive.subnormal.min, expected: false },
- { bounds: [constants.negative.max, 0], value: constants.positive.subnormal.max, expected: false },
- { bounds: [constants.negative.max, 0], value: constants.negative.subnormal.min, expected: true },
- { bounds: [constants.negative.max, 0], value: constants.negative.subnormal.max, expected: true },
- { bounds: [0, constants.positive.subnormal.min], value: constants.positive.subnormal.min, expected: true },
- { bounds: [0, constants.positive.subnormal.min], value: constants.positive.subnormal.max, expected: false },
- { bounds: [0, constants.positive.subnormal.min], value: constants.negative.subnormal.min, expected: false },
- { bounds: [0, constants.positive.subnormal.min], value: constants.negative.subnormal.max, expected: false },
- { bounds: [constants.negative.subnormal.max, 0], value: constants.positive.subnormal.min, expected: false },
- { bounds: [constants.negative.subnormal.max, 0], value: constants.positive.subnormal.max, expected: false },
- { bounds: [constants.negative.subnormal.max, 0], value: constants.negative.subnormal.min, expected: false },
- { bounds: [constants.negative.subnormal.max, 0], value: constants.negative.subnormal.max, expected: true },
+ { endpoints: [0, constants.positive.min], value: constants.positive.subnormal.min, expected: true },
+ { endpoints: [0, constants.positive.min], value: constants.positive.subnormal.max, expected: true },
+ { endpoints: [0, constants.positive.min], value: constants.negative.subnormal.min, expected: false },
+ { endpoints: [0, constants.positive.min], value: constants.negative.subnormal.max, expected: false },
+ { endpoints: [constants.negative.max, 0], value: constants.positive.subnormal.min, expected: false },
+ { endpoints: [constants.negative.max, 0], value: constants.positive.subnormal.max, expected: false },
+ { endpoints: [constants.negative.max, 0], value: constants.negative.subnormal.min, expected: true },
+ { endpoints: [constants.negative.max, 0], value: constants.negative.subnormal.max, expected: true },
+ { endpoints: [0, constants.positive.subnormal.min], value: constants.positive.subnormal.min, expected: true },
+ { endpoints: [0, constants.positive.subnormal.min], value: constants.positive.subnormal.max, expected: false },
+ { endpoints: [0, constants.positive.subnormal.min], value: constants.negative.subnormal.min, expected: false },
+ { endpoints: [0, constants.positive.subnormal.min], value: constants.negative.subnormal.max, expected: false },
+ { endpoints: [constants.negative.subnormal.max, 0], value: constants.positive.subnormal.min, expected: false },
+ { endpoints: [constants.negative.subnormal.max, 0], value: constants.positive.subnormal.max, expected: false },
+ { endpoints: [constants.negative.subnormal.max, 0], value: constants.negative.subnormal.min, expected: false },
+ { endpoints: [constants.negative.subnormal.max, 0], value: constants.negative.subnormal.max, expected: true },
];
// Note: Out of range values are limited to infinities for abstract float, due to abstract
@@ -296,20 +296,20 @@ g.test('contains_number')
// prettier-ignore
cases.push(...[
// Out of range high
- { bounds: [0, 2 * constants.positive.max], value: constants.positive.min, expected: true },
- { bounds: [0, 2 * constants.positive.max], value: constants.positive.max, expected: true },
- { bounds: [0, 2 * constants.positive.max], value: constants.positive.infinity, expected: false },
- { bounds: [0, 2 * constants.positive.max], value: constants.negative.min, expected: false },
- { bounds: [0, 2 * constants.positive.max], value: constants.negative.max, expected: false },
- { bounds: [0, 2 * constants.positive.max], value: constants.negative.infinity, expected: false },
+ { endpoints: [0, 2 * constants.positive.max], value: constants.positive.min, expected: true },
+ { endpoints: [0, 2 * constants.positive.max], value: constants.positive.max, expected: true },
+ { endpoints: [0, 2 * constants.positive.max], value: constants.positive.infinity, expected: false },
+ { endpoints: [0, 2 * constants.positive.max], value: constants.negative.min, expected: false },
+ { endpoints: [0, 2 * constants.positive.max], value: constants.negative.max, expected: false },
+ { endpoints: [0, 2 * constants.positive.max], value: constants.negative.infinity, expected: false },
// Out of range low
- { bounds: [2 * constants.negative.min, 0], value: constants.positive.min, expected: false },
- { bounds: [2 * constants.negative.min, 0], value: constants.positive.max, expected: false },
- { bounds: [2 * constants.negative.min, 0], value: constants.positive.infinity, expected: false },
- { bounds: [2 * constants.negative.min, 0], value: constants.negative.min, expected: true },
- { bounds: [2 * constants.negative.min, 0], value: constants.negative.max, expected: true },
- { bounds: [2 * constants.negative.min, 0], value: constants.negative.infinity, expected: false },
+ { endpoints: [2 * constants.negative.min, 0], value: constants.positive.min, expected: false },
+ { endpoints: [2 * constants.negative.min, 0], value: constants.positive.max, expected: false },
+ { endpoints: [2 * constants.negative.min, 0], value: constants.positive.infinity, expected: false },
+ { endpoints: [2 * constants.negative.min, 0], value: constants.negative.min, expected: true },
+ { endpoints: [2 * constants.negative.min, 0], value: constants.negative.max, expected: true },
+ { endpoints: [2 * constants.negative.min, 0], value: constants.negative.infinity, expected: false },
] as ContainsNumberCase[]);
}
@@ -318,7 +318,7 @@ g.test('contains_number')
)
.fn(t => {
const trait = FP[t.params.trait];
- const i = trait.toInterval(t.params.bounds);
+ const i = trait.toInterval(t.params.endpoints);
const value = t.params.value;
const expected = t.params.expected;
@@ -327,8 +327,8 @@ g.test('contains_number')
});
interface ContainsIntervalCase {
- lhs: number | IntervalBounds;
- rhs: number | IntervalBounds;
+ lhs: number | IntervalEndpoints;
+ rhs: number | IntervalEndpoints;
expected: boolean;
}
@@ -440,8 +440,8 @@ g.test('contains_interval')
// Utilities
interface SpanIntervalsCase {
- intervals: (number | IntervalBounds)[];
- expected: number | IntervalBounds;
+ intervals: (number | IntervalEndpoints)[];
+ expected: number | IntervalEndpoints;
}
g.test('spanIntervals')
@@ -467,7 +467,7 @@ g.test('spanIntervals')
{ intervals: [[2, 5], [0, 1]], expected: [0, 5] },
{ intervals: [[0, 2], [1, 5]], expected: [0, 5] },
{ intervals: [[0, 5], [1, 2]], expected: [0, 5] },
- { intervals: [[constants.negative.infinity, 0], [0, constants.positive.infinity]], expected: kUnboundedBounds },
+ { intervals: [[constants.negative.infinity, 0], [0, constants.positive.infinity]], expected: kUnboundedEndpoints },
// Multiple Intervals
{ intervals: [[0, 1], [2, 3], [4, 5]], expected: [0, 5] },
@@ -494,7 +494,7 @@ g.test('spanIntervals')
});
interface isVectorCase {
- input: (number | IntervalBounds | FPIntervalParam)[];
+ input: (number | IntervalEndpoints | FPIntervalParam)[];
expected: boolean;
}
@@ -511,7 +511,7 @@ g.test('isVector')
{ input: [1, 2, 3], expected: false },
{ input: [1, 2, 3, 4], expected: false },
- // IntervalBounds
+ // IntervalEndpoints
{ input: [[1], [2]], expected: false },
{ input: [[1], [2], [3]], expected: false },
{ input: [[1], [2], [3], [4]], expected: false },
@@ -600,8 +600,8 @@ g.test('isVector')
});
interface toVectorCase {
- input: (number | IntervalBounds | FPIntervalParam)[];
- expected: (number | IntervalBounds)[];
+ input: (number | IntervalEndpoints | FPIntervalParam)[];
+ expected: (number | IntervalEndpoints)[];
}
g.test('toVector')
@@ -617,7 +617,7 @@ g.test('toVector')
{ input: [1, 2, 3], expected: [1, 2, 3] },
{ input: [1, 2, 3, 4], expected: [1, 2, 3, 4] },
- // IntervalBounds
+ // IntervalEndpoints
{ input: [[1], [2]], expected: [1, 2] },
{ input: [[1], [2], [3]], expected: [1, 2, 3] },
{ input: [[1], [2], [3], [4]], expected: [1, 2, 3, 4] },
@@ -704,7 +704,7 @@ g.test('toVector')
{ input: [1, trait.toParam([2]), [3], 4], expected: [1, 2, 3, 4] },
{
input: [1, [2], [2, 3], kUnboundedInterval[p.trait]],
- expected: [1, 2, [2, 3], kUnboundedBounds],
+ expected: [1, 2, [2, 3], kUnboundedEndpoints],
},
];
})
@@ -722,7 +722,7 @@ g.test('toVector')
});
interface isMatrixCase {
- input: (number | IntervalBounds | FPIntervalParam)[][];
+ input: (number | IntervalEndpoints | FPIntervalParam)[][];
expected: boolean;
}
@@ -808,7 +808,7 @@ g.test('isMatrix')
expected: false,
},
- // IntervalBounds
+ // IntervalEndpoints
{
input: [
[[1], [2]],
@@ -1157,8 +1157,8 @@ g.test('isMatrix')
});
interface toMatrixCase {
- input: (number | IntervalBounds | FPIntervalParam)[][];
- expected: (number | IntervalBounds)[][];
+ input: (number | IntervalEndpoints | FPIntervalParam)[][];
+ expected: (number | IntervalEndpoints)[][];
}
g.test('toMatrix')
@@ -1279,7 +1279,7 @@ g.test('toMatrix')
],
},
- // IntervalBounds
+ // IntervalEndpoints
{
input: [
[[1], [2]],
@@ -1822,7 +1822,7 @@ g.test('toMatrix')
interface AbsoluteErrorCase {
value: number;
error: number;
- expected: number | IntervalBounds;
+ expected: number | IntervalEndpoints;
}
// Special values used for testing absolute error interval
@@ -1856,23 +1856,24 @@ g.test('absoluteErrorInterval')
const smallErr = kSmallAbsoluteErrorValue[p.trait];
const largeErr = kLargeAbsoluteErrorValue[p.trait];
const subnormalErr = kSubnormalAbsoluteErrorValue[p.trait];
+
// prettier-ignore
return [
// Edge Cases
- // 1. Interval around infinity would be kUnboundedBounds
- { value: constants.positive.infinity, error: 0, expected: kUnboundedBounds },
- { value: constants.positive.infinity, error: largeErr, expected: kUnboundedBounds },
- { value: constants.positive.infinity, error: 1, expected: kUnboundedBounds },
- { value: constants.negative.infinity, error: 0, expected: kUnboundedBounds },
- { value: constants.negative.infinity, error: largeErr, expected: kUnboundedBounds },
- { value: constants.negative.infinity, error: 1, expected: kUnboundedBounds },
+ // 1. Interval around infinity would be kUnboundedEndpoints
+ { value: constants.positive.infinity, error: 0, expected: kUnboundedEndpoints },
+ { value: constants.positive.infinity, error: largeErr, expected: kUnboundedEndpoints },
+ { value: constants.positive.infinity, error: 1, expected: kUnboundedEndpoints },
+ { value: constants.negative.infinity, error: 0, expected: kUnboundedEndpoints },
+ { value: constants.negative.infinity, error: largeErr, expected: kUnboundedEndpoints },
+ { value: constants.negative.infinity, error: 1, expected: kUnboundedEndpoints },
// 2. Interval around largest finite positive/negative
{ value: constants.positive.max, error: 0, expected: constants.positive.max },
- { value: constants.positive.max, error: largeErr, expected: kUnboundedBounds},
- { value: constants.positive.max, error: constants.positive.max, expected: kUnboundedBounds},
+ { value: constants.positive.max, error: largeErr, expected: kUnboundedEndpoints},
+ { value: constants.positive.max, error: constants.positive.max, expected: kUnboundedEndpoints},
{ value: constants.negative.min, error: 0, expected: constants.negative.min },
- { value: constants.negative.min, error: largeErr, expected: kUnboundedBounds},
- { value: constants.negative.min, error: constants.positive.max, expected: kUnboundedBounds},
+ { value: constants.negative.min, error: largeErr, expected: kUnboundedEndpoints},
+ { value: constants.negative.min, error: constants.positive.max, expected: kUnboundedEndpoints},
// 3. Interval around small but normal center, center should not get flushed.
{ value: constants.positive.min, error: 0, expected: constants.positive.min },
{ value: constants.positive.min, error: smallErr, expected: [constants.positive.min - smallErr, constants.positive.min + smallErr]},
@@ -1898,6 +1899,19 @@ g.test('absoluteErrorInterval')
{ value: constants.negative.subnormal.max, error: smallErr, expected: [constants.negative.subnormal.max - smallErr, smallErr] },
{ value: constants.negative.subnormal.max, error: 1, expected: [constants.negative.subnormal.max - 1, 1] },
+ // Zero
+ { value: 0, error: 0, expected: 0 },
+ { value: 0, error: smallErr, expected: [-smallErr, smallErr] },
+ { value: 0, error: 1, expected: [-1, 1] },
+
+ // Two
+ { value: 2, error: 0, expected: 2 },
+ { value: 2, error: smallErr, expected: [2 - smallErr, 2 + smallErr] },
+ { value: 2, error: 1, expected: [1, 3] },
+ { value: -2, error: 0, expected: -2 },
+ { value: -2, error: smallErr, expected: [-2 - smallErr, -2 + smallErr] },
+ { value: -2, error: 1, expected: [-3, -1] },
+
// 64-bit subnormals, expected to be treated as 0.0 or smallest subnormal of kind.
{ value: reinterpretU64AsF64(0x0000_0000_0000_0001n), error: 0, expected: [0, constants.positive.subnormal.min] },
{ value: reinterpretU64AsF64(0x0000_0000_0000_0001n), error: subnormalErr, expected: [-subnormalErr, constants.positive.subnormal.min + subnormalErr] },
@@ -1912,19 +1926,6 @@ g.test('absoluteErrorInterval')
{ value: reinterpretU64AsF64(0x800f_ffff_ffff_fffen), error: 0, expected: [constants.negative.subnormal.max, 0] },
{ value: reinterpretU64AsF64(0x800f_ffff_ffff_fffen), error: subnormalErr, expected: [constants.negative.subnormal.max - subnormalErr, subnormalErr] },
{ value: reinterpretU64AsF64(0x800f_ffff_ffff_fffen), error: 1, expected: [constants.negative.subnormal.max - 1, 1] },
-
- // Zero
- { value: 0, error: 0, expected: 0 },
- { value: 0, error: smallErr, expected: [-smallErr, smallErr] },
- { value: 0, error: 1, expected: [-1, 1] },
-
- // Two
- { value: 2, error: 0, expected: 2 },
- { value: 2, error: smallErr, expected: [2 - smallErr, 2 + smallErr] },
- { value: 2, error: 1, expected: [1, 3] },
- { value: -2, error: 0, expected: -2 },
- { value: -2, error: smallErr, expected: [-2 - smallErr, -2 + smallErr] },
- { value: -2, error: 1, expected: [-3, -1] },
];
})
)
@@ -1942,7 +1943,7 @@ g.test('absoluteErrorInterval')
interface CorrectlyRoundedCase {
value: number;
- expected: number | IntervalBounds;
+ expected: number | IntervalEndpoints;
}
// Correctly rounded cases that input values are exactly representable normal values of target type
@@ -2034,8 +2035,8 @@ g.test('correctlyRoundedInterval')
// prettier-ignore
return [
// Edge Cases
- { value: constants.positive.infinity, expected: kUnboundedBounds },
- { value: constants.negative.infinity, expected: kUnboundedBounds },
+ { value: constants.positive.infinity, expected: kUnboundedEndpoints },
+ { value: constants.negative.infinity, expected: kUnboundedEndpoints },
{ value: constants.positive.max, expected: constants.positive.max },
{ value: constants.negative.min, expected: constants.negative.min },
{ value: constants.positive.min, expected: constants.positive.min },
@@ -2074,7 +2075,7 @@ g.test('correctlyRoundedInterval')
interface ULPCase {
value: number;
num_ulp: number;
- expected: number | IntervalBounds;
+ expected: number | IntervalEndpoints;
}
// Special values used for testing ULP error interval
@@ -2086,7 +2087,7 @@ const kULPErrorValue = {
g.test('ulpInterval')
.params(u =>
u
- .combine('trait', ['abstract', 'f32', 'f16'] as const)
+ .combine('trait', ['f32', 'f16'] as const)
.beginSubcases()
.expandWithParams<ULPCase>(p => {
const trait = kFPTraitForULP[p.trait];
@@ -2099,21 +2100,21 @@ g.test('ulpInterval')
// prettier-ignore
return [
// Edge Cases
- { value: constants.positive.infinity, num_ulp: 0, expected: kUnboundedBounds },
- { value: constants.positive.infinity, num_ulp: 1, expected: kUnboundedBounds },
- { value: constants.positive.infinity, num_ulp: ULPValue, expected: kUnboundedBounds },
- { value: constants.negative.infinity, num_ulp: 0, expected: kUnboundedBounds },
- { value: constants.negative.infinity, num_ulp: 1, expected: kUnboundedBounds },
- { value: constants.negative.infinity, num_ulp: ULPValue, expected: kUnboundedBounds },
+ { value: constants.positive.infinity, num_ulp: 0, expected: kUnboundedEndpoints },
+ { value: constants.positive.infinity, num_ulp: 1, expected: kUnboundedEndpoints },
+ { value: constants.positive.infinity, num_ulp: ULPValue, expected: kUnboundedEndpoints },
+ { value: constants.negative.infinity, num_ulp: 0, expected: kUnboundedEndpoints },
+ { value: constants.negative.infinity, num_ulp: 1, expected: kUnboundedEndpoints },
+ { value: constants.negative.infinity, num_ulp: ULPValue, expected: kUnboundedEndpoints },
{ value: constants.positive.max, num_ulp: 0, expected: constants.positive.max },
- { value: constants.positive.max, num_ulp: 1, expected: kUnboundedBounds },
- { value: constants.positive.max, num_ulp: ULPValue, expected: kUnboundedBounds },
+ { value: constants.positive.max, num_ulp: 1, expected: kUnboundedEndpoints },
+ { value: constants.positive.max, num_ulp: ULPValue, expected: kUnboundedEndpoints },
{ value: constants.positive.min, num_ulp: 0, expected: constants.positive.min },
{ value: constants.positive.min, num_ulp: 1, expected: [0, plusOneULP(constants.positive.min)] },
{ value: constants.positive.min, num_ulp: ULPValue, expected: [0, plusNULP(constants.positive.min, ULPValue)] },
{ value: constants.negative.min, num_ulp: 0, expected: constants.negative.min },
- { value: constants.negative.min, num_ulp: 1, expected: kUnboundedBounds },
- { value: constants.negative.min, num_ulp: ULPValue, expected: kUnboundedBounds },
+ { value: constants.negative.min, num_ulp: 1, expected: kUnboundedEndpoints },
+ { value: constants.negative.min, num_ulp: ULPValue, expected: kUnboundedEndpoints },
{ value: constants.negative.max, num_ulp: 0, expected: constants.negative.max },
{ value: constants.negative.max, num_ulp: 1, expected: [minusOneULP(constants.negative.max), 0] },
{ value: constants.negative.max, num_ulp: ULPValue, expected: [minusNULP(constants.negative.max, ULPValue), 0] },
@@ -2178,7 +2179,7 @@ const kConstantCorrectlyRoundedExpectation = {
'1.9': [reinterpretU32AsF32(0x3ff33333), reinterpretU32AsF32(0x3ff33334)],
// -1.9 falls between f32 0xBFF33334 and 0xBFF33333
'-1.9': [reinterpretU32AsF32(0xbff33334), reinterpretU32AsF32(0xbff33333)],
- } as { [value in ConstantNumberFrequentlyUsedInCases]: IntervalBounds },
+ } as { [value in ConstantNumberFrequentlyUsedInCases]: IntervalEndpoints },
f16: {
// 0.1 falls between f16 0x2E66 and 0x2E67
'0.1': [reinterpretU16AsF16(0x2e66), reinterpretU16AsF16(0x2e67)],
@@ -2188,7 +2189,7 @@ const kConstantCorrectlyRoundedExpectation = {
'1.9': [reinterpretU16AsF16(0x3f99), reinterpretU16AsF16(0x3f9a)],
// 1.9 falls between f16 0xBF9A and 0xBF99
'-1.9': [reinterpretU16AsF16(0xbf9a), reinterpretU16AsF16(0xbf99)],
- } as { [value in ConstantNumberFrequentlyUsedInCases]: IntervalBounds },
+ } as { [value in ConstantNumberFrequentlyUsedInCases]: IntervalEndpoints },
// Since abstract is actually f64 and JS number is also f64, the JS number value will map to
// identical abstracty value without rounded.
abstract: {
@@ -2201,7 +2202,7 @@ const kConstantCorrectlyRoundedExpectation = {
interface ScalarToIntervalCase {
input: number;
- expected: number | IntervalBounds;
+ expected: number | IntervalEndpoints;
}
g.test('absInterval')
@@ -2224,8 +2225,8 @@ g.test('absInterval')
{ input: -1.9, expected: kConstantCorrectlyRoundedExpectation[p.trait]['1.9']},
// Edge cases
- { input: constants.positive.infinity, expected: kUnboundedBounds },
- { input: constants.negative.infinity, expected: kUnboundedBounds },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
{ input: constants.positive.max, expected: constants.positive.max },
{ input: constants.positive.min, expected: constants.positive.min },
{ input: constants.negative.min, expected: constants.positive.max },
@@ -2290,17 +2291,18 @@ g.test('acosInterval')
const constants = trait.constants();
// prettier-ignore
return [
- // The acceptance interval @ x = -1 and 1 is kUnboundedBounds, because
- // sqrt(1 - x*x) = sqrt(0), and sqrt is defined in terms of inverseqrt
- // The acceptance interval @ x = 0 is kUnboundedBounds, because atan2 is not
- // well-defined/implemented at 0.
- { input: constants.negative.infinity, expected: kUnboundedBounds },
- { input: constants.negative.min, expected: kUnboundedBounds },
- { input: -1, expected: kUnboundedBounds },
- { input: 0, expected: kUnboundedBounds },
- { input: 1, expected: kUnboundedBounds },
- { input: constants.positive.max, expected: kUnboundedBounds },
- { input: constants.positive.infinity, expected: kUnboundedBounds },
+ // The acceptance interval @ x = -1 and 1 is kUnboundedEndpoints,
+ // because sqrt(1 - x*x) = sqrt(0), and sqrt is defined in terms of
+ // inverseqrt.
+ // The acceptance interval @ x = 0 is kUnboundedEndpoints, because atan2
+ // is not well-defined/implemented at 0.
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.min, expected: kUnboundedEndpoints },
+ { input: -1, expected: kUnboundedEndpoints },
+ { input: 0, expected: kUnboundedEndpoints },
+ { input: 1, expected: kUnboundedEndpoints },
+ { input: constants.positive.max, expected: kUnboundedEndpoints },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
// Cases that bounded by absolute error and inherited from atan2(sqrt(1-x*x), x). Note that
// even x is very close to 1.0 and the expected result is close to 0.0, the expected
@@ -2346,13 +2348,13 @@ g.test('acoshAlternativeInterval')
return [
...kAcoshAlternativeIntervalCases[p.trait],
- { input: constants.negative.infinity, expected: kUnboundedBounds },
- { input: constants.negative.min, expected: kUnboundedBounds },
- { input: -1, expected: kUnboundedBounds },
- { input: 0, expected: kUnboundedBounds },
- { input: 1, expected: kUnboundedBounds }, // 1/0 occurs in inverseSqrt in this formulation
- { input: constants.positive.max, expected: kUnboundedBounds },
- { input: constants.positive.infinity, expected: kUnboundedBounds },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.min, expected: kUnboundedEndpoints },
+ { input: -1, expected: kUnboundedEndpoints },
+ { input: 0, expected: kUnboundedEndpoints },
+ { input: 1, expected: kUnboundedEndpoints }, // 1/0 occurs in inverseSqrt in this formulation
+ { input: constants.positive.max, expected: kUnboundedEndpoints },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
];
})
)
@@ -2392,13 +2394,13 @@ g.test('acoshPrimaryInterval')
return [
...kAcoshPrimaryIntervalCases[p.trait],
- { input: constants.negative.infinity, expected: kUnboundedBounds },
- { input: constants.negative.min, expected: kUnboundedBounds },
- { input: -1, expected: kUnboundedBounds },
- { input: 0, expected: kUnboundedBounds },
- { input: 1, expected: kUnboundedBounds }, // 1/0 occurs in inverseSqrt in this formulation
- { input: constants.positive.max, expected: kUnboundedBounds },
- { input: constants.positive.infinity, expected: kUnboundedBounds },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.min, expected: kUnboundedEndpoints },
+ { input: -1, expected: kUnboundedEndpoints },
+ { input: 0, expected: kUnboundedEndpoints },
+ { input: 1, expected: kUnboundedEndpoints }, // 1/0 occurs in inverseSqrt in this formulation
+ { input: constants.positive.max, expected: kUnboundedEndpoints },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
];
})
)
@@ -2434,28 +2436,29 @@ g.test('asinInterval')
.expandWithParams<ScalarToIntervalCase>(p => {
const trait = FP[p.trait];
const constants = trait.constants();
- const abs_error = p.trait === 'f32' ? 6.77e-5 : 3.91e-3;
+ const abs_error = p.trait === 'f32' ? 6.81e-5 : 3.91e-3;
// prettier-ignore
return [
- // The acceptance interval @ x = -1 and 1 is kUnboundedBounds, because
- // sqrt(1 - x*x) = sqrt(0), and sqrt is defined in terms of inversqrt.
- // The acceptance interval @ x = 0 is kUnboundedBounds, because atan2 is not
- // well-defined/implemented at 0.
- { input: constants.negative.infinity, expected: kUnboundedBounds },
- { input: constants.negative.min, expected: kUnboundedBounds },
- { input: -1, expected: kUnboundedBounds },
- // Subnormal input may get flushed to 0, and result in kUnboundedBounds.
- { input: constants.negative.subnormal.min, expected: kUnboundedBounds },
- { input: 0, expected: kUnboundedBounds },
- { input: constants.positive.subnormal.max, expected: kUnboundedBounds },
- { input: 1, expected: kUnboundedBounds },
- { input: constants.positive.max, expected: kUnboundedBounds },
- { input: constants.positive.infinity, expected: kUnboundedBounds },
+ // The acceptance interval @ x = -1 and 1 is kUnboundedEndpoints,
+ // because sqrt(1 - x*x) = sqrt(0), and sqrt is defined in terms of
+ // inversqrt.
+ // The acceptance interval @ x = 0 is kUnboundedEndpoints, because
+ // atan2 is not well-defined/implemented at 0.
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.min, expected: kUnboundedEndpoints },
+ { input: -1, expected: kUnboundedEndpoints },
+ // Subnormal input may get flushed to 0, and result in kUnboundedEndpoints.
+ { input: constants.negative.subnormal.min, expected: kUnboundedEndpoints },
+ { input: 0, expected: kUnboundedEndpoints },
+ { input: constants.positive.subnormal.max, expected: kUnboundedEndpoints },
+ { input: 1, expected: kUnboundedEndpoints },
+ { input: constants.positive.max, expected: kUnboundedEndpoints },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
// When input near 0, the expected result is bounded by absolute error rather than ULP
// error. Away from 0 the atan2 inherited error should be larger.
- { input: constants.negative.max, expected: trait.absoluteErrorInterval(Math.asin(constants.negative.max), abs_error).bounds() }, // ~0
- { input: constants.positive.min, expected: trait.absoluteErrorInterval(Math.asin(constants.positive.min), abs_error).bounds() }, // ~0
+ { input: constants.negative.max, expected: trait.absoluteErrorInterval(Math.asin(constants.negative.max), abs_error).endpoints() }, // ~0
+ { input: constants.positive.min, expected: trait.absoluteErrorInterval(Math.asin(constants.positive.min), abs_error).endpoints() }, // ~0
// Cases that inherited from atan2(x, sqrt(1-x*x))
...kAsinIntervalInheritedCases[p.trait],
@@ -2500,10 +2503,10 @@ g.test('asinhInterval')
return [
...kAsinhIntervalCases[p.trait],
- { input: constants.negative.infinity, expected: kUnboundedBounds },
- { input: constants.negative.min, expected: kUnboundedBounds },
- { input: constants.positive.max, expected: kUnboundedBounds },
- { input: constants.positive.infinity, expected: kUnboundedBounds },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.min, expected: kUnboundedEndpoints },
+ { input: constants.positive.max, expected: kUnboundedEndpoints },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
];
})
)
@@ -2569,8 +2572,8 @@ g.test('atanInterval')
{ input: 0, expected: 0 },
...kAtanIntervalCases[p.trait],
- { input: constants.negative.infinity, expected: kUnboundedBounds },
- { input: constants.positive.infinity, expected: kUnboundedBounds },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
];
})
)
@@ -2619,12 +2622,12 @@ g.test('atanhInterval')
return [
...kAtanhIntervalCases[p.trait],
- { input: constants.negative.infinity, expected: kUnboundedBounds },
- { input: constants.negative.min, expected: kUnboundedBounds },
- { input: -1, expected: kUnboundedBounds },
- { input: 1, expected: kUnboundedBounds },
- { input: constants.positive.max, expected: kUnboundedBounds },
- { input: constants.positive.infinity, expected: kUnboundedBounds },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.min, expected: kUnboundedEndpoints },
+ { input: -1, expected: kUnboundedEndpoints },
+ { input: 1, expected: kUnboundedEndpoints },
+ { input: constants.positive.max, expected: kUnboundedEndpoints },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
];
})
)
@@ -2650,12 +2653,17 @@ const kCeilIntervalCases = {
{ input: -(2 ** 14), expected: -(2 ** 14) },
{ input: 0x8000, expected: 0x8000 }, // https://github.com/gpuweb/cts/issues/2766
],
+ abstract: [
+ { input: 2 ** 52, expected: 2 ** 52 },
+ { input: -(2 ** 52), expected: -(2 ** 52) },
+ { input: 0x8000000000000000, expected: 0x8000000000000000 }, // https://github.com/gpuweb/cts/issues/2766
+ ],
} as const;
g.test('ceilInterval')
.params(u =>
u
- .combine('trait', ['f32', 'f16'] as const)
+ .combine('trait', ['f32', 'f16', 'abstract'] as const)
.beginSubcases()
.expandWithParams<ScalarToIntervalCase>(p => {
const constants = FP[p.trait].constants();
@@ -2674,15 +2682,15 @@ g.test('ceilInterval')
{ input: -1.9, expected: -1 },
// Edge cases
- { input: constants.positive.infinity, expected: kUnboundedBounds },
- { input: constants.negative.infinity, expected: kUnboundedBounds },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
{ input: constants.positive.max, expected: constants.positive.max },
{ input: constants.positive.min, expected: 1 },
{ input: constants.negative.min, expected: constants.negative.min },
{ input: constants.negative.max, expected: 0 },
...kCeilIntervalCases[p.trait],
- // 32-bit subnormals
+ // Subnormals
{ input: constants.positive.subnormal.max, expected: [0, 1] },
{ input: constants.positive.subnormal.min, expected: [0, 1] },
{ input: constants.negative.subnormal.min, expected: 0 },
@@ -2714,12 +2722,12 @@ const kCosIntervalThirdPiCases = {
// cos(-1.046875) = 0.50027931
{
input: kValue.f16.negative.pi.third,
- expected: FP['f16'].correctlyRoundedInterval(0.50027931).bounds(),
+ expected: FP['f16'].correctlyRoundedInterval(0.50027931).endpoints(),
},
// cos(1.046875) = 0.50027931
{
input: kValue.f16.positive.pi.third,
- expected: FP['f16'].correctlyRoundedInterval(0.50027931).bounds(),
+ expected: FP['f16'].correctlyRoundedInterval(0.50027931).endpoints(),
},
],
};
@@ -2740,13 +2748,13 @@ g.test('cosInterval')
// substantially different, so instead of getting 0 you get a value on the
// order of 10^-8 away from 0, thus difficult to express in a
// human-readable manner.
- { input: constants.negative.infinity, expected: kUnboundedBounds },
- { input: constants.negative.min, expected: kUnboundedBounds },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.min, expected: kUnboundedEndpoints },
{ input: constants.negative.pi.whole, expected: [-1, kPlusOneULPFunctions[p.trait](-1)] },
{ input: 0, expected: [1, 1] },
{ input: constants.positive.pi.whole, expected: [-1, kPlusOneULPFunctions[p.trait](-1)] },
- { input: constants.positive.max, expected: kUnboundedBounds },
- { input: constants.positive.infinity, expected: kUnboundedBounds },
+ { input: constants.positive.max, expected: kUnboundedEndpoints },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
...(kCosIntervalThirdPiCases[p.trait] as ScalarToIntervalCase[]),
];
@@ -2796,10 +2804,10 @@ g.test('coshInterval')
return [
...kCoshIntervalCases[p.trait],
- { input: constants.negative.infinity, expected: kUnboundedBounds },
- { input: constants.negative.min, expected: kUnboundedBounds },
- { input: constants.positive.max, expected: kUnboundedBounds },
- { input: constants.positive.infinity, expected: kUnboundedBounds },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.min, expected: kUnboundedEndpoints },
+ { input: constants.positive.max, expected: kUnboundedEndpoints },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
];
})
)
@@ -2843,37 +2851,23 @@ const kDegreesIntervalCases = {
{ input: kValue.f16.positive.pi.three_quarters, expected: [kMinusOneULPFunctions['f16'](135), 135] },
{ input: kValue.f16.positive.pi.whole, expected: [kMinusOneULPFunctions['f16'](180), 180] },
] as ScalarToIntervalCase[],
- abstract: [
- { input: kValue.f64.negative.pi.whole, expected: -180 },
- { input: kValue.f64.negative.pi.three_quarters, expected: -135 },
- { input: kValue.f64.negative.pi.half, expected: -90 },
- { input: kValue.f64.negative.pi.third, expected: kPlusOneULPFunctions['abstract'](-60) },
- { input: kValue.f64.negative.pi.quarter, expected: -45 },
- { input: kValue.f64.negative.pi.sixth, expected: kPlusOneULPFunctions['abstract'](-30) },
- { input: kValue.f64.positive.pi.sixth, expected: kMinusOneULPFunctions['abstract'](30) },
- { input: kValue.f64.positive.pi.quarter, expected: 45 },
- { input: kValue.f64.positive.pi.third, expected: kMinusOneULPFunctions['abstract'](60) },
- { input: kValue.f64.positive.pi.half, expected: 90 },
- { input: kValue.f64.positive.pi.three_quarters, expected: 135 },
- { input: kValue.f64.positive.pi.whole, expected: 180 },
- ] as ScalarToIntervalCase[],
} as const;
g.test('degreesInterval')
.params(u =>
u
- .combine('trait', ['f32', 'f16', 'abstract'] as const)
+ .combine('trait', ['f32', 'f16'] as const)
.beginSubcases()
.expandWithParams<ScalarToIntervalCase>(p => {
const trait = p.trait;
const constants = FP[trait].constants();
// prettier-ignore
return [
- { input: constants.positive.infinity, expected: kUnboundedBounds },
- { input: constants.negative.min, expected: kUnboundedBounds },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.min, expected: kUnboundedEndpoints },
{ input: 0, expected: 0 },
- { input: constants.positive.max, expected: kUnboundedBounds },
- { input: constants.negative.infinity, expected: kUnboundedBounds },
+ { input: constants.positive.max, expected: kUnboundedEndpoints },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
...kDegreesIntervalCases[trait]
];
})
@@ -2895,14 +2889,14 @@ const kExpIntervalCases = {
// exp(88) = 1.6516362549940018555283297962649e+38 = 0x7ef882b6/7.
{ input: 88, expected: [reinterpretU32AsF32(0x7ef882b6), reinterpretU32AsF32(0x7ef882b7)] },
// exp(89) overflow f32.
- { input: 89, expected: kUnboundedBounds },
+ { input: 89, expected: kUnboundedEndpoints },
] as ScalarToIntervalCase[],
f16: [
{ input: 1, expected: [kValue.f16.positive.e, kPlusOneULPFunctions['f16'](kValue.f16.positive.e)] },
// exp(11) = 59874.141715197818455326485792258 = 0x7b4f/0x7b50.
{ input: 11, expected: [reinterpretU16AsF16(0x7b4f), reinterpretU16AsF16(0x7b50)] },
// exp(12) = 162754.79141900392080800520489849 overflow f16.
- { input: 12, expected: kUnboundedBounds },
+ { input: 12, expected: kUnboundedEndpoints },
] as ScalarToIntervalCase[],
} as const;
@@ -2916,7 +2910,7 @@ g.test('expInterval')
const constants = FP[trait].constants();
// prettier-ignore
return [
- { input: constants.negative.infinity, expected: kUnboundedBounds },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
{ input: 0, expected: 1 },
...kExpIntervalCases[trait],
];
@@ -2954,13 +2948,13 @@ const kExp2IntervalCases = {
// exp2(127) = 1.7014118346046923173168730371588e+38 = 0x7f000000, 3 + 2 * 127 = 258 ulps.
{ input: 127, expected: reinterpretU32AsF32(0x7f000000) },
// exp2(128) overflow f32.
- { input: 128, expected: kUnboundedBounds },
+ { input: 128, expected: kUnboundedEndpoints },
] as ScalarToIntervalCase[],
f16: [
// exp2(15) = 32768 = 0x7800, 1 + 2 * 15 = 31 ulps
{ input: 15, expected: reinterpretU16AsF16(0x7800) },
// exp2(16) = 65536 overflow f16.
- { input: 16, expected: kUnboundedBounds },
+ { input: 16, expected: kUnboundedEndpoints },
] as ScalarToIntervalCase[],
} as const;
@@ -2974,7 +2968,7 @@ g.test('exp2Interval')
const constants = FP[trait].constants();
// prettier-ignore
return [
- { input: constants.negative.infinity, expected: kUnboundedBounds },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
{ input: 0, expected: 1 },
{ input: 1, expected: 2 },
...kExp2IntervalCases[trait],
@@ -3051,8 +3045,8 @@ g.test('floorInterval')
{ input: -1.9, expected: -2 },
// Edge cases
- { input: constants.positive.infinity, expected: kUnboundedBounds },
- { input: constants.negative.infinity, expected: kUnboundedBounds },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
{ input: constants.positive.max, expected: constants.positive.max },
{ input: constants.positive.min, expected: 0 },
{ input: constants.negative.min, expected: constants.negative.min },
@@ -3099,12 +3093,24 @@ const kFractIntervalCases = {
{ input: -1.1, expected: [reinterpretU16AsF16(0x3b32), reinterpretU16AsF16(0x3b34)] }, // ~0.9
{ input: 658.5, expected: 0.5 },
] as ScalarToIntervalCase[],
+ abstract: [
+ { input: 0.1, expected: reinterpretU64AsF64(0x3fb999999999999an) },
+ { input: 0.9, expected: reinterpretU64AsF64(0x3feccccccccccccdn) },
+ { input: 1.1, expected: reinterpretU64AsF64(0x3fb99999999999a0n) },
+ { input: -0.1, expected: reinterpretU64AsF64(0x3feccccccccccccdn) },
+ { input: -0.9, expected: reinterpretU64AsF64(0x3fb9999999999998n) },
+ { input: -1.1, expected: reinterpretU64AsF64(0x3fecccccccccccccn) },
+
+ // https://github.com/gpuweb/cts/issues/2766
+ { input: 0x80000000, expected: 0 },
+ ] as ScalarToIntervalCase[],
+
} as const;
g.test('fractInterval')
.params(u =>
u
- .combine('trait', ['f32', 'f16'] as const)
+ .combine('trait', ['f32', 'f16', 'abstract'] as const)
.beginSubcases()
.expandWithParams<ScalarToIntervalCase>(p => {
const constants = FP[p.trait].constants();
@@ -3117,8 +3123,8 @@ g.test('fractInterval')
...kFractIntervalCases[p.trait],
// Edge cases
- { input: constants.positive.infinity, expected: kUnboundedBounds },
- { input: constants.negative.infinity, expected: kUnboundedBounds },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
{ input: constants.positive.max, expected: 0 },
{ input: constants.positive.min, expected: constants.positive.min },
{ input: constants.negative.min, expected: 0 },
@@ -3180,9 +3186,9 @@ g.test('inverseSqrtInterval')
{ input: 100, expected: kConstantCorrectlyRoundedExpectation[p.trait]['0.1'] }, // ~0.1
// Out of definition domain
- { input: -1, expected: kUnboundedBounds },
- { input: 0, expected: kUnboundedBounds },
- { input: constants.positive.infinity, expected: kUnboundedBounds },
+ { input: -1, expected: kUnboundedEndpoints },
+ { input: 0, expected: kUnboundedEndpoints },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
];
})
)
@@ -3215,7 +3221,7 @@ const kRootSumSquareExpectionInterval = {
'[1.0, 1.0]' : [reinterpretU64AsF64(0x3ff6_a09d_b000_0000n), reinterpretU64AsF64(0x3ff6_a09f_1000_0000n)], // ~√2
'[1.0, 1.0, 1.0]' : [reinterpretU64AsF64(0x3ffb_b67a_1000_0000n), reinterpretU64AsF64(0x3ffb_b67b_b000_0000n)], // ~√3
'[1.0, 1.0, 1.0, 1.0]' : [reinterpretU64AsF64(0x3fff_ffff_7000_0000n), reinterpretU64AsF64(0x4000_0000_9000_0000n)], // ~2
- } as {[s: string]: IntervalBounds},
+ } as {[s: string]: IntervalEndpoints},
f16: {
'[0.1]': [reinterpretU64AsF64(0x3fb9_7e00_0000_0000n), reinterpretU64AsF64(0x3fb9_b600_0000_0000n)], // ~0.1
'[1.0]' : [reinterpretU64AsF64(0x3fef_ee00_0000_0000n), reinterpretU64AsF64(0x3ff0_1200_0000_0000n)], // ~1.0
@@ -3223,7 +3229,7 @@ const kRootSumSquareExpectionInterval = {
'[1.0, 1.0]' : [reinterpretU64AsF64(0x3ff6_8a00_0000_0000n), reinterpretU64AsF64(0x3ff6_b600_0000_0000n)], // ~√2
'[1.0, 1.0, 1.0]' : [reinterpretU64AsF64(0x3ffb_9a00_0000_0000n), reinterpretU64AsF64(0x3ffb_d200_0000_0000n)], // ~√3
'[1.0, 1.0, 1.0, 1.0]' : [reinterpretU64AsF64(0x3fff_ee00_0000_0000n), reinterpretU64AsF64(0x4000_1200_0000_0000n)], // ~2
- } as {[s: string]: IntervalBounds},
+ } as {[s: string]: IntervalEndpoints},
} as const;
g.test('lengthIntervalScalar')
@@ -3243,22 +3249,22 @@ g.test('lengthIntervalScalar')
{input: 10.0, expected: kRootSumSquareExpectionInterval[p.trait]['[10]'] }, // ~10
{input: -10.0, expected: kRootSumSquareExpectionInterval[p.trait]['[10]'] }, // ~10
- // length(0) = kUnboundedBounds, because length uses sqrt, which is defined as 1/inversesqrt
- {input: 0, expected: kUnboundedBounds },
+ // length(0) = kUnboundedEndpoints, because length uses sqrt, which is defined as 1/inversesqrt
+ {input: 0, expected: kUnboundedEndpoints },
// Subnormal Cases
- { input: constants.negative.subnormal.min, expected: kUnboundedBounds },
- { input: constants.negative.subnormal.max, expected: kUnboundedBounds },
- { input: constants.positive.subnormal.min, expected: kUnboundedBounds },
- { input: constants.positive.subnormal.max, expected: kUnboundedBounds },
+ { input: constants.negative.subnormal.min, expected: kUnboundedEndpoints },
+ { input: constants.negative.subnormal.max, expected: kUnboundedEndpoints },
+ { input: constants.positive.subnormal.min, expected: kUnboundedEndpoints },
+ { input: constants.positive.subnormal.max, expected: kUnboundedEndpoints },
// Edge cases
- { input: constants.positive.infinity, expected: kUnboundedBounds },
- { input: constants.negative.infinity, expected: kUnboundedBounds },
- { input: constants.negative.min, expected: kUnboundedBounds },
- { input: constants.negative.max, expected: kUnboundedBounds },
- { input: constants.positive.min, expected: kUnboundedBounds },
- { input: constants.positive.max, expected: kUnboundedBounds },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.min, expected: kUnboundedEndpoints },
+ { input: constants.negative.max, expected: kUnboundedEndpoints },
+ { input: constants.positive.min, expected: kUnboundedEndpoints },
+ { input: constants.positive.max, expected: kUnboundedEndpoints },
];
})
)
@@ -3300,8 +3306,8 @@ g.test('logInterval')
.expandWithParams<ScalarToIntervalCase>(p => {
// prettier-ignore
return [
- { input: -1, expected: kUnboundedBounds },
- { input: 0, expected: kUnboundedBounds },
+ { input: -1, expected: kUnboundedEndpoints },
+ { input: 0, expected: kUnboundedEndpoints },
{ input: 1, expected: 0 },
...kLogIntervalCases[p.trait],
];
@@ -3348,8 +3354,8 @@ g.test('log2Interval')
.expandWithParams<ScalarToIntervalCase>(p => {
// prettier-ignore
return [
- { input: -1, expected: kUnboundedBounds },
- { input: 0, expected: kUnboundedBounds },
+ { input: -1, expected: kUnboundedEndpoints },
+ { input: 0, expected: kUnboundedEndpoints },
{ input: 1, expected: 0 },
{ input: 2, expected: 1 },
{ input: 16, expected: 4 },
@@ -3387,8 +3393,8 @@ g.test('negationInterval')
// prettier-ignore
return [
// Edge cases
- { input: constants.positive.infinity, expected: kUnboundedBounds },
- { input: constants.negative.infinity, expected: kUnboundedBounds },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
{ input: constants.positive.max, expected: constants.negative.min },
{ input: constants.positive.min, expected: constants.negative.max },
{ input: constants.negative.min, expected: constants.positive.max },
@@ -3425,8 +3431,8 @@ g.test('quantizeToF16Interval')
.paramsSubcasesOnly<ScalarToIntervalCase>(
// prettier-ignore
[
- { input: kValue.f32.negative.infinity, expected: kUnboundedBounds },
- { input: kValue.f32.negative.min, expected: kUnboundedBounds },
+ { input: kValue.f32.negative.infinity, expected: kUnboundedEndpoints },
+ { input: kValue.f32.negative.min, expected: kUnboundedEndpoints },
{ input: kValue.f16.negative.min, expected: kValue.f16.negative.min },
{ input: -1.9, expected: kConstantCorrectlyRoundedExpectation['f16']['-1.9'] }, // ~-1.9
{ input: -1, expected: -1 },
@@ -3444,8 +3450,8 @@ g.test('quantizeToF16Interval')
{ input: 1, expected: 1 },
{ input: 1.9, expected: kConstantCorrectlyRoundedExpectation['f16']['1.9'] }, // ~1.9
{ input: kValue.f16.positive.max, expected: kValue.f16.positive.max },
- { input: kValue.f32.positive.max, expected: kUnboundedBounds },
- { input: kValue.f32.positive.infinity, expected: kUnboundedBounds },
+ { input: kValue.f32.positive.max, expected: kUnboundedEndpoints },
+ { input: kValue.f32.positive.infinity, expected: kUnboundedEndpoints },
]
)
.fn(t => {
@@ -3488,35 +3494,21 @@ const kRadiansIntervalCases = {
{ input: 135, expected: [kMinusOneULPFunctions['f16'](kValue.f16.positive.pi.three_quarters), kPlusOneULPFunctions['f16'](kValue.f16.positive.pi.three_quarters)] },
{ input: 180, expected: [kMinusOneULPFunctions['f16'](kValue.f16.positive.pi.whole), kPlusOneULPFunctions['f16'](kValue.f16.positive.pi.whole)] },
] as ScalarToIntervalCase[],
- abstract: [
- { input: -180, expected: kValue.f64.negative.pi.whole },
- { input: -135, expected: kValue.f64.negative.pi.three_quarters },
- { input: -90, expected: kValue.f64.negative.pi.half },
- { input: -60, expected: kValue.f64.negative.pi.third },
- { input: -45, expected: kValue.f64.negative.pi.quarter },
- { input: -30, expected: kValue.f64.negative.pi.sixth },
- { input: 30, expected: kValue.f64.positive.pi.sixth },
- { input: 45, expected: kValue.f64.positive.pi.quarter },
- { input: 60, expected: kValue.f64.positive.pi.third },
- { input: 90, expected: kValue.f64.positive.pi.half },
- { input: 135, expected: kValue.f64.positive.pi.three_quarters },
- { input: 180, expected: kValue.f64.positive.pi.whole },
- ] as ScalarToIntervalCase[],
} as const;
g.test('radiansInterval')
.params(u =>
u
- .combine('trait', ['f32', 'f16', 'abstract'] as const)
+ .combine('trait', ['f32', 'f16'] as const)
.beginSubcases()
.expandWithParams<ScalarToIntervalCase>(p => {
const trait = p.trait;
const constants = FP[trait].constants();
// prettier-ignore
return [
- { input: constants.positive.infinity, expected: kUnboundedBounds },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
{ input: 0, expected: 0 },
- { input: constants.negative.infinity, expected: kUnboundedBounds },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
...kRadiansIntervalCases[trait]
];
})
@@ -3536,19 +3528,27 @@ const kRoundIntervalCases = {
f32: [
{ input: 2 ** 30, expected: 2 ** 30 },
{ input: -(2 ** 30), expected: -(2 ** 30) },
- { input: 0x80000000, expected: 0x80000000 }, // https://github.com/gpuweb/cts/issues/2766
+ { input: 0x8000_0000, expected: 0x8000_0000 }, // https://github.com/gpuweb/cts/issues/2766
],
f16: [
{ input: 2 ** 14, expected: 2 ** 14 },
{ input: -(2 ** 14), expected: -(2 ** 14) },
{ input: 0x8000, expected: 0x8000 }, // https://github.com/gpuweb/cts/issues/2766
],
+ abstract: [
+ { input: 2 ** 62, expected: 2 ** 62 },
+ { input: -(2 ** 62), expected: -(2 ** 62) },
+ {
+ input: 0x8000_0000_0000_0000,
+ expected: 0x8000_0000_0000_0000,
+ }, // https://github.com/gpuweb/cts/issues/2766
+ ],
} as const;
g.test('roundInterval')
.params(u =>
u
- .combine('trait', ['f32', 'f16'] as const)
+ .combine('trait', ['f32', 'f16', 'abstract'] as const)
.beginSubcases()
.expandWithParams<ScalarToIntervalCase>(p => {
const constants = FP[p.trait].constants();
@@ -3571,15 +3571,15 @@ g.test('roundInterval')
{ input: -1.9, expected: -2 },
// Edge cases
- { input: constants.positive.infinity, expected: kUnboundedBounds },
- { input: constants.negative.infinity, expected: kUnboundedBounds },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
{ input: constants.positive.max, expected: constants.positive.max },
{ input: constants.positive.min, expected: 0 },
{ input: constants.negative.min, expected: constants.negative.min },
{ input: constants.negative.max, expected: 0 },
...kRoundIntervalCases[p.trait],
- // 32-bit subnormals
+ // Subnormals
{ input: constants.positive.subnormal.max, expected: 0 },
{ input: constants.positive.subnormal.min, expected: 0 },
{ input: constants.negative.subnormal.min, expected: 0 },
@@ -3627,8 +3627,8 @@ g.test('saturateInterval')
{ input: constants.negative.subnormal.max, expected: [constants.negative.subnormal.max, 0.0] },
// Infinities
- { input: constants.positive.infinity, expected: kUnboundedBounds },
- { input: constants.negative.infinity, expected: kUnboundedBounds },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
];
})
)
@@ -3651,7 +3651,7 @@ g.test('signInterval')
const constants = FP[p.trait].constants();
// prettier-ignore
return [
- { input: constants.negative.infinity, expected: kUnboundedBounds },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
{ input: constants.negative.min, expected: -1 },
{ input: -10, expected: -1 },
{ input: -1, expected: -1 },
@@ -3667,7 +3667,7 @@ g.test('signInterval')
{ input: 1, expected: 1 },
{ input: 10, expected: 1 },
{ input: constants.positive.max, expected: 1 },
- { input: constants.positive.infinity, expected: kUnboundedBounds },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
];
})
)
@@ -3696,13 +3696,13 @@ g.test('sinInterval')
// substantially different, so instead of getting 0 you get a value on the
// order of 10^-8 away from it, thus difficult to express in a
// human-readable manner.
- { input: constants.negative.infinity, expected: kUnboundedBounds },
- { input: constants.negative.min, expected: kUnboundedBounds },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.min, expected: kUnboundedEndpoints },
{ input: constants.negative.pi.half, expected: [-1, kPlusOneULPFunctions[p.trait](-1)] },
{ input: 0, expected: 0 },
{ input: constants.positive.pi.half, expected: [kMinusOneULPFunctions[p.trait](1), 1] },
- { input: constants.positive.max, expected: kUnboundedBounds },
- { input: constants.positive.infinity, expected: kUnboundedBounds },
+ { input: constants.positive.max, expected: kUnboundedEndpoints },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
];
})
)
@@ -3750,10 +3750,10 @@ g.test('sinhInterval')
return [
...kSinhIntervalCases[p.trait],
- { input: constants.negative.infinity, expected: kUnboundedBounds },
- { input: constants.negative.min, expected: kUnboundedBounds },
- { input: constants.positive.max, expected: kUnboundedBounds },
- { input: constants.positive.infinity, expected: kUnboundedBounds },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.min, expected: kUnboundedEndpoints },
+ { input: constants.positive.max, expected: kUnboundedEndpoints },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
];
})
)
@@ -3832,9 +3832,9 @@ g.test('sqrtInterval')
...kSqrtIntervalCases[p.trait],
// Cases out of definition domain
- { input: -1, expected: kUnboundedBounds },
- { input: 0, expected: kUnboundedBounds },
- { input: constants.positive.infinity, expected: kUnboundedBounds },
+ { input: -1, expected: kUnboundedEndpoints },
+ { input: 0, expected: kUnboundedEndpoints },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
];
})
)
@@ -3913,12 +3913,12 @@ g.test('tanInterval')
...kTanIntervalCases[p.trait],
// Cases that result in unbounded interval.
- { input: constants.negative.infinity, expected: kUnboundedBounds },
- { input: constants.negative.min, expected: kUnboundedBounds },
- { input: constants.negative.pi.half, expected: kUnboundedBounds },
- { input: constants.positive.pi.half, expected: kUnboundedBounds },
- { input: constants.positive.max, expected: kUnboundedBounds },
- { input: constants.positive.infinity, expected: kUnboundedBounds },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.min, expected: kUnboundedEndpoints },
+ { input: constants.negative.pi.half, expected: kUnboundedEndpoints },
+ { input: constants.positive.pi.half, expected: kUnboundedEndpoints },
+ { input: constants.positive.max, expected: kUnboundedEndpoints },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
];
})
)
@@ -3960,10 +3960,10 @@ g.test('tanhInterval')
return [
...kTanhIntervalCases[p.trait],
- { input: constants.negative.infinity, expected: kUnboundedBounds },
- { input: constants.negative.min, expected: kUnboundedBounds },
- { input: constants.positive.max, expected: kUnboundedBounds },
- { input: constants.positive.infinity, expected: kUnboundedBounds },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.min, expected: kUnboundedEndpoints },
+ { input: constants.positive.max, expected: kUnboundedEndpoints },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
];
})
)
@@ -4007,8 +4007,8 @@ g.test('truncInterval')
{ input: constants.negative.subnormal.max, expected: 0 },
// Edge cases
- { input: constants.positive.infinity, expected: kUnboundedBounds },
- { input: constants.negative.infinity, expected: kUnboundedBounds },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
{ input: constants.positive.max, expected: constants.positive.max },
{ input: constants.positive.min, expected: 0 },
{ input: constants.negative.min, expected: constants.negative.min },
@@ -4030,7 +4030,7 @@ interface ScalarPairToIntervalCase {
// input is a pair of independent values, not a range, so should not be
// converted to a FPInterval.
input: [number, number];
- expected: number | IntervalBounds;
+ expected: number | IntervalEndpoints;
}
// prettier-ignore
@@ -4112,14 +4112,14 @@ g.test('additionInterval')
{ input: [0, constants.negative.subnormal.min], expected: [constants.negative.subnormal.min, 0] },
// Infinities
- { input: [0, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [constants.positive.infinity, 0], expected: kUnboundedBounds },
- { input: [constants.positive.infinity, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [0, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, 0], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [constants.positive.infinity, constants.negative.infinity], expected: kUnboundedBounds },
+ { input: [0, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, 0], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [0, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, 0], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
];
})
)
@@ -4226,33 +4226,33 @@ g.test('atan2Interval')
// Cases that y out of bound.
// positive y, positive x
- { input: [Number.POSITIVE_INFINITY, 1], expected: kUnboundedBounds },
+ { input: [Number.POSITIVE_INFINITY, 1], expected: kUnboundedEndpoints },
// positive y, negative x
- { input: [Number.POSITIVE_INFINITY, -1], expected: kUnboundedBounds },
+ { input: [Number.POSITIVE_INFINITY, -1], expected: kUnboundedEndpoints },
// negative y, negative x
- { input: [Number.NEGATIVE_INFINITY, -1], expected: kUnboundedBounds },
+ { input: [Number.NEGATIVE_INFINITY, -1], expected: kUnboundedEndpoints },
// negative y, positive x
- { input: [Number.NEGATIVE_INFINITY, 1], expected: kUnboundedBounds },
+ { input: [Number.NEGATIVE_INFINITY, 1], expected: kUnboundedEndpoints },
// Discontinuity @ origin (0,0)
- { input: [0, 0], expected: kUnboundedBounds },
- { input: [0, constants.positive.subnormal.max], expected: kUnboundedBounds },
- { input: [0, constants.negative.subnormal.min], expected: kUnboundedBounds },
- { input: [0, constants.positive.min], expected: kUnboundedBounds },
- { input: [0, constants.negative.max], expected: kUnboundedBounds },
- { input: [0, constants.positive.max], expected: kUnboundedBounds },
- { input: [0, constants.negative.min], expected: kUnboundedBounds },
- { input: [0, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [0, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [0, 1], expected: kUnboundedBounds },
- { input: [constants.positive.subnormal.max, 1], expected: kUnboundedBounds },
- { input: [constants.negative.subnormal.min, 1], expected: kUnboundedBounds },
-
- // Very large |x| values should cause kUnboundedBounds to be returned, due to the restrictions on division
- { input: [1, constants.positive.max], expected: kUnboundedBounds },
- { input: [1, constants.positive.nearest_max], expected: kUnboundedBounds },
- { input: [1, constants.negative.min], expected: kUnboundedBounds },
- { input: [1, constants.negative.nearest_min], expected: kUnboundedBounds },
+ { input: [0, 0], expected: kUnboundedEndpoints },
+ { input: [0, constants.positive.subnormal.max], expected: kUnboundedEndpoints },
+ { input: [0, constants.negative.subnormal.min], expected: kUnboundedEndpoints },
+ { input: [0, constants.positive.min], expected: kUnboundedEndpoints },
+ { input: [0, constants.negative.max], expected: kUnboundedEndpoints },
+ { input: [0, constants.positive.max], expected: kUnboundedEndpoints },
+ { input: [0, constants.negative.min], expected: kUnboundedEndpoints },
+ { input: [0, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [0, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [0, 1], expected: kUnboundedEndpoints },
+ { input: [constants.positive.subnormal.max, 1], expected: kUnboundedEndpoints },
+ { input: [constants.negative.subnormal.min, 1], expected: kUnboundedEndpoints },
+
+ // Very large |x| values should cause kUnboundedEndpoints to be returned, due to the restrictions on division
+ { input: [1, constants.positive.max], expected: kUnboundedEndpoints },
+ { input: [1, constants.positive.nearest_max], expected: kUnboundedEndpoints },
+ { input: [1, constants.negative.min], expected: kUnboundedEndpoints },
+ { input: [1, constants.negative.nearest_min], expected: kUnboundedEndpoints },
];
})
)
@@ -4290,25 +4290,25 @@ g.test('distanceIntervalScalar')
{ input: [-10.0, 0], expected: kRootSumSquareExpectionInterval[p.trait]['[10]'] }, // ~10
{ input: [0, -10.0], expected: kRootSumSquareExpectionInterval[p.trait]['[10]'] }, // ~10
- // distance(x, y), where x - y = 0 has an acceptance interval of kUnboundedBounds,
- // because distance(x, y) = length(x - y), and length(0) = kUnboundedBounds
- { input: [0, 0], expected: kUnboundedBounds },
- { input: [1.0, 1.0], expected: kUnboundedBounds },
- { input: [-1.0, -1.0], expected: kUnboundedBounds },
+ // distance(x, y), where x - y = 0 has an acceptance interval of kUnboundedEndpoints,
+ // because distance(x, y) = length(x - y), and length(0) = kUnboundedEndpoints
+ { input: [0, 0], expected: kUnboundedEndpoints },
+ { input: [1.0, 1.0], expected: kUnboundedEndpoints },
+ { input: [-1.0, -1.0], expected: kUnboundedEndpoints },
// Subnormal Cases
- { input: [constants.negative.subnormal.min, 0], expected: kUnboundedBounds },
- { input: [constants.negative.subnormal.max, 0], expected: kUnboundedBounds },
- { input: [constants.positive.subnormal.min, 0], expected: kUnboundedBounds },
- { input: [constants.positive.subnormal.max, 0], expected: kUnboundedBounds },
+ { input: [constants.negative.subnormal.min, 0], expected: kUnboundedEndpoints },
+ { input: [constants.negative.subnormal.max, 0], expected: kUnboundedEndpoints },
+ { input: [constants.positive.subnormal.min, 0], expected: kUnboundedEndpoints },
+ { input: [constants.positive.subnormal.max, 0], expected: kUnboundedEndpoints },
// Edge cases
- { input: [constants.positive.infinity, 0], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, 0], expected: kUnboundedBounds },
- { input: [constants.negative.min, 0], expected: kUnboundedBounds },
- { input: [constants.negative.max, 0], expected: kUnboundedBounds },
- { input: [constants.positive.min, 0], expected: kUnboundedBounds },
- { input: [constants.positive.max, 0], expected: kUnboundedBounds },
+ { input: [constants.positive.infinity, 0], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, 0], expected: kUnboundedEndpoints },
+ { input: [constants.negative.min, 0], expected: kUnboundedEndpoints },
+ { input: [constants.negative.max, 0], expected: kUnboundedEndpoints },
+ { input: [constants.positive.min, 0], expected: kUnboundedEndpoints },
+ { input: [constants.positive.max, 0], expected: kUnboundedEndpoints },
];
})
)
@@ -4371,13 +4371,10 @@ const kDivisionInterval64BitsNormalCases = {
g.test('divisionInterval')
.params(u =>
u
- .combine('trait', ['abstract', 'f32', 'f16'] as const)
+ .combine('trait', ['f32', 'f16'] as const)
.beginSubcases()
.expandWithParams<ScalarPairToIntervalCase>(p => {
- // This is a ULP based interval, so abstract should behave like f32, so
- // swizzling the trait as needed.
- const trait = p.trait === 'abstract' ? 'f32' : p.trait;
- const fp = FP[trait];
+ const fp = FP[p.trait];
const constants = fp.constants();
// prettier-ignore
return [
@@ -4394,26 +4391,23 @@ g.test('divisionInterval')
{ input: [-4, -2], expected: 2 },
// 64-bit normals that can not be exactly represented
- ...kDivisionInterval64BitsNormalCases[trait],
+ ...kDivisionInterval64BitsNormalCases[p.trait],
// Denominator out of range
- { input: [1, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [1, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [constants.positive.infinity, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [1, constants.positive.max], expected: kUnboundedBounds },
- { input: [1, constants.negative.min], expected: kUnboundedBounds },
- { input: [1, 0], expected: kUnboundedBounds },
- { input: [1, constants.positive.subnormal.max], expected: kUnboundedBounds },
+ { input: [1, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [1, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [1, constants.positive.max], expected: kUnboundedEndpoints },
+ { input: [1, constants.negative.min], expected: kUnboundedEndpoints },
+ { input: [1, 0], expected: kUnboundedEndpoints },
+ { input: [1, constants.positive.subnormal.max], expected: kUnboundedEndpoints },
];
})
)
.fn(t => {
- // This is a ULP based interval, so abstract should behave like f32, so
- // swizzling the trait as needed for calculating the expected result.
- const trait = t.params.trait === 'abstract' ? 'f32' : t.params.trait;
- const fp = FP[trait];
+ const fp = FP[t.params.trait];
const error = (n: number): number => {
return 2.5 * fp.oneULP(n);
@@ -4421,7 +4415,6 @@ g.test('divisionInterval')
const [x, y] = t.params.input;
- // Do not swizzle here, so the correct implementation under test is called.
const expected = FP[t.params.trait].toInterval(applyError(t.params.expected, error));
const got = FP[t.params.trait].divisionInterval(x, y);
t.expect(
@@ -4452,11 +4445,11 @@ const kLdexpIntervalCases = {
// e2 + bias <= 0, expect correctly rounded intervals.
{ input: [2 ** 120, -130], expected: 2 ** -10 },
// Out of Bounds
- { input: [1, 128], expected: kUnboundedBounds },
- { input: [-1, 128], expected: kUnboundedBounds },
- { input: [100, 126], expected: kUnboundedBounds },
- { input: [-100, 126], expected: kUnboundedBounds },
- { input: [2 ** 100, 100], expected: kUnboundedBounds },
+ { input: [1, 128], expected: kUnboundedEndpoints },
+ { input: [-1, 128], expected: kUnboundedEndpoints },
+ { input: [100, 126], expected: kUnboundedEndpoints },
+ { input: [-100, 126], expected: kUnboundedEndpoints },
+ { input: [2 ** 100, 100], expected: kUnboundedEndpoints },
] as ScalarPairToIntervalCase[],
f16: [
// 64-bit normals
@@ -4478,25 +4471,66 @@ const kLdexpIntervalCases = {
// e2 + bias <= 0, expect correctly rounded intervals.
{ input: [2 ** 12, -18], expected: 2 ** -6 },
// Out of Bounds
- { input: [1, 16], expected: kUnboundedBounds },
- { input: [-1, 16], expected: kUnboundedBounds },
- { input: [100, 14], expected: kUnboundedBounds },
- { input: [-100, 14], expected: kUnboundedBounds },
- { input: [2 ** 10, 10], expected: kUnboundedBounds },
+ { input: [1, 16], expected: kUnboundedEndpoints },
+ { input: [-1, 16], expected: kUnboundedEndpoints },
+ { input: [100, 14], expected: kUnboundedEndpoints },
+ { input: [-100, 14], expected: kUnboundedEndpoints },
+ { input: [2 ** 10, 10], expected: kUnboundedEndpoints },
+ ] as ScalarPairToIntervalCase[],
+ abstract: [
+ // Edge Cases
+ // 1.9999999999999997779553950749686919152736663818359375 * 2 ** 1023 = f64.positive.max
+ {
+ input: [1.9999999999999997779553950749686919152736663818359375, 1023],
+ expected: kValue.f64.positive.max,
+ },
+ // f64.positive.min = 1 * 2 ** -1022
+ { input: [1, -1022], expected: kValue.f64.positive.min },
+ // f64.positive.subnormal.max = 1.9999999999999997779553950749686919152736663818359375 * 2 ** -1022
+ {
+ input: [0.9999999999999997779553950749686919152736663818359375, -1022],
+ expected: [0, kValue.f64.positive.subnormal.max],
+ },
+ // f64.positive.subnormal.min = 0.0000000000000002220446049250313080847263336181640625 * 2 ** -1022
+ {
+ input: [0.0000000000000002220446049250313080847263336181640625, -1022],
+ expected: [0, kValue.f64.positive.subnormal.min],
+ },
+ {
+ input: [-0.0000000000000002220446049250313080847263336181640625, -1022],
+ expected: [kValue.f64.negative.subnormal.max, 0],
+ },
+ {
+ input: [-0.9999999999999997779553950749686919152736663818359375, -1022],
+ expected: [kValue.f64.negative.subnormal.min, 0],
+ },
+ { input: [-1, -1022], expected: kValue.f64.negative.max },
+ {
+ input: [-1.9999999999999997779553950749686919152736663818359375, 1023],
+ expected: kValue.f64.negative.min,
+ },
+ // e2 + bias <= 0, expect correctly rounded intervals.
+ { input: [2 ** 120, -130], expected: 2 ** -10 },
+ // Out of Bounds
+ { input: [1, 1024], expected: kUnboundedEndpoints },
+ { input: [-1, 1024], expected: kUnboundedEndpoints },
+ { input: [100, 1024], expected: kUnboundedEndpoints },
+ { input: [-100, 1024], expected: kUnboundedEndpoints },
+ { input: [2 ** 100, 1000], expected: kUnboundedEndpoints },
] as ScalarPairToIntervalCase[],
} as const;
g.test('ldexpInterval')
.params(u =>
u
- .combine('trait', ['f32', 'f16'] as const)
+ .combine('trait', ['f32', 'f16', 'abstract'] as const)
.beginSubcases()
.expandWithParams<ScalarPairToIntervalCase>(p => {
const trait = FP[p.trait];
const constants = trait.constants();
// prettier-ignore
return [
- // always exactly represeantable cases
+ // always exactly representable cases
{ input: [0, 0], expected: 0 },
{ input: [0, 1], expected: 0 },
{ input: [0, -1], expected: 0 },
@@ -4512,8 +4546,8 @@ g.test('ldexpInterval')
{ input: [constants.positive.max, kValue.i32.negative.min], expected: 0 },
{ input: [constants.negative.min, kValue.i32.negative.min], expected: 0 },
// Out of Bounds
- { input: [constants.positive.max, kValue.i32.positive.max], expected: kUnboundedBounds },
- { input: [constants.negative.min, kValue.i32.positive.max], expected: kUnboundedBounds },
+ { input: [constants.positive.max, kValue.i32.positive.max], expected: kUnboundedEndpoints },
+ { input: [constants.negative.min, kValue.i32.positive.max], expected: kUnboundedEndpoints },
];
})
)
@@ -4572,14 +4606,14 @@ g.test('maxInterval')
{ input: [constants.negative.subnormal.min, constants.positive.subnormal.max], expected: [constants.negative.subnormal.min, constants.positive.subnormal.max] },
// Infinities
- { input: [0, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [constants.positive.infinity, 0], expected: kUnboundedBounds },
- { input: [constants.positive.infinity, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [0, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, 0], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [constants.positive.infinity, constants.negative.infinity], expected: kUnboundedBounds },
+ { input: [0, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, 0], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [0, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, 0], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
];
})
)
@@ -4638,14 +4672,14 @@ g.test('minInterval')
{ input: [constants.negative.subnormal.min, constants.positive.subnormal.max], expected: [constants.negative.subnormal.min, constants.positive.subnormal.max] },
// Infinities
- { input: [0, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [constants.positive.infinity, 0], expected: kUnboundedBounds },
- { input: [constants.positive.infinity, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [0, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, 0], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [constants.positive.infinity, constants.negative.infinity], expected: kUnboundedBounds },
+ { input: [0, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, 0], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [0, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, 0], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
];
})
)
@@ -4740,22 +4774,22 @@ g.test('multiplicationInterval')
...kMultiplicationInterval64BitsNormalCases[p.trait],
// Infinities
- { input: [0, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [1, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [-1, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [constants.positive.infinity, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [0, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [1, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [-1, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [constants.positive.infinity, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedBounds },
+ { input: [0, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [1, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [-1, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [0, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [1, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [-1, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
// Edges
- { input: [constants.positive.max, constants.positive.max], expected: kUnboundedBounds },
- { input: [constants.negative.min, constants.negative.min], expected: kUnboundedBounds },
- { input: [constants.positive.max, constants.negative.min], expected: kUnboundedBounds },
- { input: [constants.negative.min, constants.positive.max], expected: kUnboundedBounds },
+ { input: [constants.positive.max, constants.positive.max], expected: kUnboundedEndpoints },
+ { input: [constants.negative.min, constants.negative.min], expected: kUnboundedEndpoints },
+ { input: [constants.positive.max, constants.negative.min], expected: kUnboundedEndpoints },
+ { input: [constants.negative.min, constants.positive.max], expected: kUnboundedEndpoints },
];
})
)
@@ -4808,11 +4842,11 @@ g.test('powInterval')
const constants = trait.constants();
// prettier-ignore
return [
- { input: [-1, 0], expected: kUnboundedBounds },
- { input: [0, 0], expected: kUnboundedBounds },
- { input: [0, 1], expected: kUnboundedBounds },
- { input: [1, constants.positive.max], expected: kUnboundedBounds },
- { input: [constants.positive.max, 1], expected: kUnboundedBounds },
+ { input: [-1, 0], expected: kUnboundedEndpoints },
+ { input: [0, 0], expected: kUnboundedEndpoints },
+ { input: [0, 1], expected: kUnboundedEndpoints },
+ { input: [1, constants.positive.max], expected: kUnboundedEndpoints },
+ { input: [constants.positive.max, 1], expected: kUnboundedEndpoints },
...kPowIntervalCases[p.trait],
];
@@ -4848,7 +4882,7 @@ const kRemainderCases = {
g.test('remainderInterval')
.params(u =>
u
- .combine('trait', ['abstract', 'f32', 'f16'] as const)
+ .combine('trait', ['f32', 'f16'] as const)
.beginSubcases()
.expandWithParams<ScalarPairToIntervalCase>(p => {
const trait = kFPTraitForULP[p.trait];
@@ -4878,15 +4912,15 @@ g.test('remainderInterval')
{ input: [1.125, 1], expected: 0.125 },
// Denominator out of range
- { input: [1, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [1, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [constants.positive.infinity, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [1, constants.positive.max], expected: kUnboundedBounds },
- { input: [1, constants.negative.min], expected: kUnboundedBounds },
- { input: [1, 0], expected: kUnboundedBounds },
- { input: [1, constants.positive.subnormal.max], expected: kUnboundedBounds },
+ { input: [1, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [1, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [1, constants.positive.max], expected: kUnboundedEndpoints },
+ { input: [1, constants.negative.min], expected: kUnboundedEndpoints },
+ { input: [1, 0], expected: kUnboundedEndpoints },
+ { input: [1, constants.positive.subnormal.max], expected: kUnboundedEndpoints },
];
})
)
@@ -4904,7 +4938,7 @@ g.test('remainderInterval')
g.test('stepInterval')
.params(u =>
u
- .combine('trait', ['f32', 'f16'] as const)
+ .combine('trait', ['f32', 'f16', 'abstract'] as const)
.beginSubcases()
.expandWithParams<ScalarPairToIntervalCase>(p => {
const constants = FP[p.trait].constants();
@@ -4922,12 +4956,17 @@ g.test('stepInterval')
{ input: [1, -1], expected: 0 },
// 64-bit normals
- { input: [0.1, 0.1], expected: [0, 1] },
+ // number is f64 internally, so the value representing the literal
+ // 0.1/-0.1 will always be exactly representable in AbstractFloat,
+ // since AF is also f64 internally.
+ // It is impossible with normals to cause the rounding ambiguity that
+ // causes the 0 or 1 result.
+ { input: [0.1, 0.1], expected: p.trait === 'abstract' ? 1 : [0, 1] },
{ input: [0, 0.1], expected: 1 },
{ input: [0.1, 0], expected: 0 },
{ input: [0.1, 1], expected: 1 },
{ input: [1, 0.1], expected: 0 },
- { input: [-0.1, -0.1], expected: [0, 1] },
+ { input: [-0.1, -0.1], expected: p.trait === 'abstract' ? 1 : [0, 1] },
{ input: [0, -0.1], expected: 0 },
{ input: [-0.1, 0], expected: 1 },
{ input: [-0.1, -1], expected: 0 },
@@ -4962,14 +5001,14 @@ g.test('stepInterval')
{ input: [constants.positive.subnormal.max, constants.negative.subnormal.min], expected: [0, 1] },
// Infinities
- { input: [0, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [constants.positive.infinity, 0], expected: kUnboundedBounds },
- { input: [constants.positive.infinity, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [0, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, 0], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [constants.positive.infinity, constants.negative.infinity], expected: kUnboundedBounds },
+ { input: [0, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, 0], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [0, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, 0], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
];
})
)
@@ -5060,14 +5099,14 @@ g.test('subtractionInterval')
{ input: [0, constants.negative.subnormal.min], expected: [0, constants.positive.subnormal.max] },
// Infinities
- { input: [0, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [constants.positive.infinity, 0], expected: kUnboundedBounds },
- { input: [constants.positive.infinity, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [0, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, 0], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [constants.positive.infinity, constants.negative.infinity], expected: kUnboundedBounds },
+ { input: [0, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, 0], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [0, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, 0], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
];
})
)
@@ -5084,7 +5123,7 @@ g.test('subtractionInterval')
interface ScalarTripleToIntervalCase {
input: [number, number, number];
- expected: number | IntervalBounds;
+ expected: number | IntervalEndpoints;
}
g.test('clampMedianInterval')
@@ -5127,10 +5166,10 @@ g.test('clampMedianInterval')
{ input: [constants.positive.max, constants.positive.max, constants.positive.subnormal.min], expected: constants.positive.max },
// Infinities
- { input: [0, 1, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [0, constants.positive.infinity, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, constants.positive.infinity, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, constants.positive.infinity, constants.negative.infinity], expected: kUnboundedBounds },
+ { input: [0, 1, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [0, constants.positive.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.positive.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.positive.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
];
})
)
@@ -5185,10 +5224,10 @@ g.test('clampMinMaxInterval')
{ input: [constants.positive.max, constants.positive.max, constants.positive.subnormal.min], expected: [0, constants.positive.subnormal.min] },
// Infinities
- { input: [0, 1, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [0, constants.positive.infinity, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, constants.positive.infinity, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, constants.positive.infinity, constants.negative.infinity], expected: kUnboundedBounds },
+ { input: [0, 1, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [0, constants.positive.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.positive.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.positive.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
];
})
)
@@ -5240,21 +5279,12 @@ const kFmaIntervalCases = {
// minimum case: -1 * [subnormal ulp] + -1 * [subnormal ulp] rounded to [-2 * [subnormal ulp], 0],
// maximum case: -0.0 + -0.0 = 0.
{ input: [kValue.f16.positive.subnormal.max, kValue.f16.negative.subnormal.min, kValue.f16.negative.subnormal.max], expected: [-2 * FP['f16'].oneULP(0, 'no-flush'), 0] }, ] as ScalarTripleToIntervalCase[],
- abstract: [
- // These operations break down in the CTS, because `number` is a f64 under the hood, so precision is sometimes lost
- // if intermediate results are closer to 0 than the smallest subnormal will be precisely 0.
- // See https://github.com/gpuweb/cts/issues/2993 for details
- { input: [kValue.f64.positive.subnormal.max, kValue.f64.positive.subnormal.max, 0], expected: 0 },
- { input: [kValue.f64.positive.subnormal.max, kValue.f64.positive.subnormal.max, kValue.f64.positive.subnormal.max], expected: [0, kValue.f64.positive.subnormal.max] },
- { input: [kValue.f64.positive.subnormal.max, kValue.f64.positive.subnormal.min, kValue.f64.negative.subnormal.max], expected: [kValue.f64.negative.subnormal.max, 0] },
- { input: [kValue.f64.positive.subnormal.max, kValue.f64.negative.subnormal.min, kValue.f64.negative.subnormal.max], expected: [kValue.f64.negative.subnormal.max, 0] },
- ] as ScalarTripleToIntervalCase[],
} as const;
g.test('fmaInterval')
.params(u =>
u
- .combine('trait', ['f32', 'f16', 'abstract'] as const)
+ .combine('trait', ['f32', 'f16'] as const)
.beginSubcases()
.expandWithParams<ScalarTripleToIntervalCase>(p => {
const trait = FP[p.trait];
@@ -5286,11 +5316,11 @@ g.test('fmaInterval')
{ input: [0, constants.positive.subnormal.max, constants.positive.subnormal.max], expected: [0, constants.positive.subnormal.max] },
// Infinities
- { input: [0, 1, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [0, constants.positive.infinity, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, constants.positive.infinity, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, constants.positive.infinity, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [constants.positive.max, constants.positive.max, constants.positive.subnormal.min], expected: kUnboundedBounds },
+ { input: [0, 1, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [0, constants.positive.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.positive.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.positive.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.positive.max, constants.positive.max, constants.positive.subnormal.min], expected: kUnboundedEndpoints },
...kFmaIntervalCases[p.trait],
];
})
@@ -5312,55 +5342,62 @@ g.test('fmaInterval')
// prettier-ignore
const kMixImpreciseIntervalCases = {
f32: [
- // [0.0, 1.0] cases
- { input: [0.0, 1.0, 0.1], expected: [reinterpretU64AsF64(0x3fb9_9999_8000_0000n), reinterpretU64AsF64(0x3fb9_9999_a000_0000n)] }, // ~0.1
- { input: [0.0, 1.0, 0.9], expected: [reinterpretU64AsF64(0x3fec_cccc_c000_0000n), reinterpretU64AsF64(0x3fec_cccc_e000_0000n)] }, // ~0.9
- // [1.0, 0.0] cases
- { input: [1.0, 0.0, 0.1], expected: [reinterpretU64AsF64(0x3fec_cccc_c000_0000n), reinterpretU64AsF64(0x3fec_cccc_e000_0000n)] }, // ~0.9
- { input: [1.0, 0.0, 0.9], expected: [reinterpretU64AsF64(0x3fb9_9999_0000_0000n), reinterpretU64AsF64(0x3fb9_999a_0000_0000n)] }, // ~0.1
- // [0.0, 10.0] cases
- { input: [0.0, 10.0, 0.1], expected: [reinterpretU64AsF64(0x3fef_ffff_e000_0000n), reinterpretU64AsF64(0x3ff0_0000_2000_0000n)] }, // ~1
- { input: [0.0, 10.0, 0.9], expected: [reinterpretU64AsF64(0x4021_ffff_e000_0000n), reinterpretU64AsF64(0x4022_0000_2000_0000n)] }, // ~9
- // [2.0, 10.0] cases
- { input: [2.0, 10.0, 0.1], expected: [reinterpretU64AsF64(0x4006_6666_6000_0000n), reinterpretU64AsF64(0x4006_6666_8000_0000n)] }, // ~2.8
- { input: [2.0, 10.0, 0.9], expected: [reinterpretU64AsF64(0x4022_6666_6000_0000n), reinterpretU64AsF64(0x4022_6666_8000_0000n)] }, // ~9.2
- // [-1.0, 1.0] cases
- { input: [-1.0, 1.0, 0.1], expected: [reinterpretU64AsF64(0xbfe9_9999_a000_0000n), reinterpretU64AsF64(0xbfe9_9999_8000_0000n)] }, // ~-0.8
- { input: [-1.0, 1.0, 0.9], expected: [reinterpretU64AsF64(0x3fe9_9999_8000_0000n), reinterpretU64AsF64(0x3fe9_9999_c000_0000n)] }, // ~0.8
-
- // Showing how precise and imprecise versions diff
- // Note that this expectation is 0 only in f32 as 10.0 is much smaller that f32.negative.min,
- // So that 10 - f32.negative.min == f32.negative.min even in f64. But for f16, there is not
- // a exactly-represenatble f16 value v that make v - f16.negative.min == f16.negative.min
- // in f64, in fact that require v being smaller than 2**-37.
- { input: [kValue.f32.negative.min, 10.0, 1.0], expected: 0.0 },
- // -10.0 is the same, much smaller than f32.negative.min
- { input: [kValue.f32.negative.min, -10.0, 1.0], expected: 0.0 },
+ // [0.0, 1.0] cases
+ { input: [0.0, 1.0, 0.1], expected: [reinterpretU64AsF64(0x3fb9_9999_8000_0000n), reinterpretU64AsF64(0x3fb9_9999_a000_0000n)] }, // ~0.1
+ { input: [0.0, 1.0, 0.9], expected: [reinterpretU64AsF64(0x3fec_cccc_c000_0000n), reinterpretU64AsF64(0x3fec_cccc_e000_0000n)] }, // ~0.9
+ // [1.0, 0.0] cases
+ { input: [1.0, 0.0, 0.1], expected: [reinterpretU64AsF64(0x3fec_cccc_c000_0000n), reinterpretU64AsF64(0x3fec_cccc_e000_0000n)] }, // ~0.9
+ { input: [1.0, 0.0, 0.9], expected: [reinterpretU64AsF64(0x3fb9_9999_0000_0000n), reinterpretU64AsF64(0x3fb9_999a_0000_0000n)] }, // ~0.1
+ // [0.0, 10.0] cases
+ { input: [0.0, 10.0, 0.1], expected: [reinterpretU64AsF64(0x3fef_ffff_e000_0000n), reinterpretU64AsF64(0x3ff0_0000_2000_0000n)] }, // ~1
+ { input: [0.0, 10.0, 0.9], expected: [reinterpretU64AsF64(0x4021_ffff_e000_0000n), reinterpretU64AsF64(0x4022_0000_2000_0000n)] }, // ~9
+ // [2.0, 10.0] cases
+ { input: [2.0, 10.0, 0.1], expected: [reinterpretU64AsF64(0x4006_6666_6000_0000n), reinterpretU64AsF64(0x4006_6666_8000_0000n)] }, // ~2.8
+ { input: [2.0, 10.0, 0.9], expected: [reinterpretU64AsF64(0x4022_6666_6000_0000n), reinterpretU64AsF64(0x4022_6666_8000_0000n)] }, // ~9.2
+ // [-1.0, 1.0] cases
+ { input: [-1.0, 1.0, 0.1], expected: [reinterpretU64AsF64(0xbfe9_9999_a000_0000n), reinterpretU64AsF64(0xbfe9_9999_8000_0000n)] }, // ~-0.8
+ { input: [-1.0, 1.0, 0.9], expected: [reinterpretU64AsF64(0x3fe9_9999_8000_0000n), reinterpretU64AsF64(0x3fe9_9999_c000_0000n)] }, // ~0.8
+
+ // Showing how precise and imprecise versions diff
+ // Note that this expectation is 0 in f32 as |10.0| is much smaller than
+ // |f32.negative.min|.
+ // So that 10 - f32.negative.min == -f32.negative.min even in f64.
+ { input: [kValue.f32.negative.min, 10.0, 1.0], expected: 0.0 },
+ // -10.0 is the same, much smaller than f32.negative.min
+ { input: [kValue.f32.negative.min, -10.0, 1.0], expected: 0.0 },
+ { input: [kValue.f32.negative.min, 10.0, 5.0], expected: kUnboundedEndpoints },
+ { input: [kValue.f32.negative.min, -10.0, 5.0], expected: kUnboundedEndpoints },
+ { input: [kValue.f32.negative.min, 10.0, 0.5], expected: reinterpretU32AsF32(0xfeffffff) },
+ { input: [kValue.f32.negative.min, -10.0, 0.5], expected: reinterpretU32AsF32(0xfeffffff) },
] as ScalarTripleToIntervalCase[],
f16: [
- // [0.0, 1.0] cases
- { input: [0.0, 1.0, 0.1], expected: [reinterpretU64AsF64(0x3fb9_9800_0000_0000n), reinterpretU64AsF64(0x3fb9_9c00_0000_0000n)] }, // ~0.1
- { input: [0.0, 1.0, 0.9], expected: [reinterpretU64AsF64(0x3fec_cc00_0000_0000n), reinterpretU64AsF64(0x3fec_d000_0000_0000n)] }, // ~0.9
- // [1.0, 0.0] cases
- { input: [1.0, 0.0, 0.1], expected: [reinterpretU64AsF64(0x3fec_cc00_0000_0000n), reinterpretU64AsF64(0x3fec_d000_0000_0000n)] }, // ~0.9
- { input: [1.0, 0.0, 0.9], expected: [reinterpretU64AsF64(0x3fb9_8000_0000_0000n), reinterpretU64AsF64(0x3fb9_a000_0000_0000n)] }, // ~0.1
- // [0.0, 10.0] cases
- { input: [0.0, 10.0, 0.1], expected: [reinterpretU64AsF64(0x3fef_fc00_0000_0000n), reinterpretU64AsF64(0x3ff0_0400_0000_0000n)] }, // ~1
- { input: [0.0, 10.0, 0.9], expected: [reinterpretU64AsF64(0x4021_fc00_0000_0000n), reinterpretU64AsF64(0x4022_0400_0000_0000n)] }, // ~9
- // [2.0, 10.0] cases
- { input: [2.0, 10.0, 0.1], expected: [reinterpretU64AsF64(0x4006_6400_0000_0000n), reinterpretU64AsF64(0x4006_6800_0000_0000n)] }, // ~2.8
- { input: [2.0, 10.0, 0.9], expected: [reinterpretU64AsF64(0x4022_6400_0000_0000n), reinterpretU64AsF64(0x4022_6800_0000_0000n)] }, // ~9.2
- // [-1.0, 1.0] cases
- { input: [-1.0, 1.0, 0.1], expected: [reinterpretU64AsF64(0xbfe9_9c00_0000_0000n), reinterpretU64AsF64(0xbfe9_9800_0000_0000n)] }, // ~-0.8
- { input: [-1.0, 1.0, 0.9], expected: [reinterpretU64AsF64(0x3fe9_9800_0000_0000n), reinterpretU64AsF64(0x3fe9_a000_0000_0000n)] }, // ~0.8
-
- // Showing how precise and imprecise versions diff
- // In imprecise version, we compute (y - x), where y = 10 and x = -65504, the result is 65514
- // and cause an overflow in f16.
- { input: [kValue.f16.negative.min, 10.0, 1.0], expected: kUnboundedBounds },
- // (y - x) * 1.0, where y = -10 and x = -65504, the result is 65494 rounded to 65472 or 65504.
- // The result is -65504 + 65472 = -32 or -65504 + 65504 = 0.
- { input: [kValue.f16.negative.min, -10.0, 1.0], expected: [-32, 0] },
+ // [0.0, 1.0] cases
+ { input: [0.0, 1.0, 0.1], expected: [reinterpretU64AsF64(0x3fb9_9800_0000_0000n), reinterpretU64AsF64(0x3fb9_9c00_0000_0000n)] }, // ~0.1
+ { input: [0.0, 1.0, 0.9], expected: [reinterpretU64AsF64(0x3fec_cc00_0000_0000n), reinterpretU64AsF64(0x3fec_d000_0000_0000n)] }, // ~0.9
+ // [1.0, 0.0] cases
+ { input: [1.0, 0.0, 0.1], expected: [reinterpretU64AsF64(0x3fec_cc00_0000_0000n), reinterpretU64AsF64(0x3fec_d000_0000_0000n)] }, // ~0.9
+ { input: [1.0, 0.0, 0.9], expected: [reinterpretU64AsF64(0x3fb9_8000_0000_0000n), reinterpretU64AsF64(0x3fb9_a000_0000_0000n)] }, // ~0.1
+ // [0.0, 10.0] cases
+ { input: [0.0, 10.0, 0.1], expected: [reinterpretU64AsF64(0x3fef_fc00_0000_0000n), reinterpretU64AsF64(0x3ff0_0400_0000_0000n)] }, // ~1
+ { input: [0.0, 10.0, 0.9], expected: [reinterpretU64AsF64(0x4021_fc00_0000_0000n), reinterpretU64AsF64(0x4022_0400_0000_0000n)] }, // ~9
+ // [2.0, 10.0] cases
+ { input: [2.0, 10.0, 0.1], expected: [reinterpretU64AsF64(0x4006_6400_0000_0000n), reinterpretU64AsF64(0x4006_6800_0000_0000n)] }, // ~2.8
+ { input: [2.0, 10.0, 0.9], expected: [reinterpretU64AsF64(0x4022_6400_0000_0000n), reinterpretU64AsF64(0x4022_6800_0000_0000n)] }, // ~9.2
+ // [-1.0, 1.0] cases
+ { input: [-1.0, 1.0, 0.1], expected: [reinterpretU64AsF64(0xbfe9_9c00_0000_0000n), reinterpretU64AsF64(0xbfe9_9800_0000_0000n)] }, // ~-0.8
+ { input: [-1.0, 1.0, 0.9], expected: [reinterpretU64AsF64(0x3fe9_9800_0000_0000n), reinterpretU64AsF64(0x3fe9_a000_0000_0000n)] }, // ~0.8
+
+ // Showing how precise and imprecise versions diff
+ // In imprecise version, we compute (y - x), where y = 10 and x = -65504, the result is 65514
+ // and cause an overflow in f16.
+ { input: [kValue.f16.negative.min, 10.0, 1.0], expected: kUnboundedEndpoints },
+ // (y - x) * 1.0, where y = -10 and x = -65504, the result is 65494 rounded to 65472 or 65504.
+ // The result is -65504 + 65472 = -32 or -65504 + 65504 = 0.
+ { input: [kValue.f16.negative.min, -10.0, 1.0], expected: [-32, 0] },
+ { input: [kValue.f16.negative.min, 10.0, 5.0], expected: kUnboundedEndpoints },
+ { input: [kValue.f16.negative.min, -10.0, 5.0], expected: kUnboundedEndpoints },
+ { input: [kValue.f16.negative.min, 10.0, 0.5], expected: kUnboundedEndpoints },
+ { input: [kValue.f16.negative.min, -10.0, 0.5], expected: [-32768.0, -32752.0] },
] as ScalarTripleToIntervalCase[],
} as const;
@@ -5412,19 +5449,16 @@ g.test('mixImpreciseInterval')
{ input: [-1.0, 1.0, 2.0], expected: 3.0 },
// Infinities
- { input: [0.0, constants.positive.infinity, 0.5], expected: kUnboundedBounds },
- { input: [constants.positive.infinity, 0.0, 0.5], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, 1.0, 0.5], expected: kUnboundedBounds },
- { input: [1.0, constants.negative.infinity, 0.5], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, constants.positive.infinity, 0.5], expected: kUnboundedBounds },
- { input: [constants.positive.infinity, constants.negative.infinity, 0.5], expected: kUnboundedBounds },
- { input: [0.0, 1.0, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [1.0, 0.0, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [0.0, 1.0, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [1.0, 0.0, constants.positive.infinity], expected: kUnboundedBounds },
-
- // The [negative.min, +/-10.0, 1.0] cases has different result for different trait on
- // imprecise version.
+ { input: [0.0, constants.positive.infinity, 0.5], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, 0.0, 0.5], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, 1.0, 0.5], expected: kUnboundedEndpoints },
+ { input: [1.0, constants.negative.infinity, 0.5], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.positive.infinity, 0.5], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, constants.negative.infinity, 0.5], expected: kUnboundedEndpoints },
+ { input: [0.0, 1.0, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [1.0, 0.0, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [0.0, 1.0, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [1.0, 0.0, constants.positive.infinity], expected: kUnboundedEndpoints },
];
})
)
@@ -5459,6 +5493,17 @@ const kMixPreciseIntervalCases = {
// [-1.0, 1.0] cases
{ input: [-1.0, 1.0, 0.1], expected: [reinterpretU64AsF64(0xbfe9_9999_c000_0000n), reinterpretU64AsF64(0xbfe9_9999_8000_0000n)] }, // ~-0.8
{ input: [-1.0, 1.0, 0.9], expected: [reinterpretU64AsF64(0x3fe9_9999_8000_0000n), reinterpretU64AsF64(0x3fe9_9999_c000_0000n)] }, // ~0.8
+
+ // Showing how precise and imprecise versions diff
+ { input: [kValue.f32.negative.min, 10.0, 1.0], expected: 10 },
+ { input: [kValue.f32.negative.min, -10.0, 1.0], expected: -10 },
+ { input: [kValue.f32.negative.min, 10.0, 5.0], expected: kUnboundedEndpoints },
+ { input: [kValue.f32.negative.min, -10.0, 5.0], expected: kUnboundedEndpoints },
+ { input: [kValue.f32.negative.min, 10.0, 0.5], expected: reinterpretU32AsF32(0xfeffffff) },
+ { input: [kValue.f32.negative.min, -10.0, 0.5], expected: reinterpretU32AsF32(0xfeffffff) },
+
+ // Intermediate OOB
+ { input: [1.0, 2.0, kPlusOneULPFunctions['f32'](kValue.f32.positive.max / 2)], expected: kUnboundedEndpoints },
] as ScalarTripleToIntervalCase[],
f16: [
// [0.0, 1.0] cases
@@ -5476,6 +5521,17 @@ const kMixPreciseIntervalCases = {
// [-1.0, 1.0] cases
{ input: [-1.0, 1.0, 0.1], expected: [reinterpretU64AsF64(0xbfe9_a000_0000_0000n), reinterpretU64AsF64(0xbfe9_9800_0000_0000n)] }, // ~-0.8
{ input: [-1.0, 1.0, 0.9], expected: [reinterpretU64AsF64(0x3fe9_9800_0000_0000n), reinterpretU64AsF64(0x3fe9_a000_0000_0000n)] }, // ~0.8
+
+ // Showing how precise and imprecise versions diff
+ { input: [kValue.f64.negative.min, 10.0, 1.0], expected: kUnboundedEndpoints },
+ { input: [kValue.f64.negative.min, -10.0, 1.0], expected: kUnboundedEndpoints },
+ { input: [kValue.f64.negative.min, 10.0, 5.0], expected: kUnboundedEndpoints },
+ { input: [kValue.f64.negative.min, -10.0, 5.0], expected: kUnboundedEndpoints },
+ { input: [kValue.f64.negative.min, 10.0, 0.5], expected: kUnboundedEndpoints },
+ { input: [kValue.f64.negative.min, -10.0, 0.5], expected: kUnboundedEndpoints },
+
+ // Intermediate OOB
+ { input: [1.0, 2.0, kPlusOneULPFunctions['f16'](kValue.f16.positive.max / 2)], expected: kUnboundedEndpoints },
] as ScalarTripleToIntervalCase[],
} as const;
@@ -5527,20 +5583,16 @@ g.test('mixPreciseInterval')
{ input: [-1.0, 1.0, 2.0], expected: 3.0 },
// Infinities
- { input: [0.0, constants.positive.infinity, 0.5], expected: kUnboundedBounds },
- { input: [constants.positive.infinity, 0.0, 0.5], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, 1.0, 0.5], expected: kUnboundedBounds },
- { input: [1.0, constants.negative.infinity, 0.5], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, constants.positive.infinity, 0.5], expected: kUnboundedBounds },
- { input: [constants.positive.infinity, constants.negative.infinity, 0.5], expected: kUnboundedBounds },
- { input: [0.0, 1.0, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [1.0, 0.0, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [0.0, 1.0, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [1.0, 0.0, constants.positive.infinity], expected: kUnboundedBounds },
-
- // Showing how precise and imprecise versions diff
- { input: [constants.negative.min, 10.0, 1.0], expected: 10.0 },
- { input: [constants.negative.min, -10.0, 1.0], expected: -10.0 },
+ { input: [0.0, constants.positive.infinity, 0.5], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, 0.0, 0.5], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, 1.0, 0.5], expected: kUnboundedEndpoints },
+ { input: [1.0, constants.negative.infinity, 0.5], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.positive.infinity, 0.5], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, constants.negative.infinity, 0.5], expected: kUnboundedEndpoints },
+ { input: [0.0, 1.0, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [1.0, 0.0, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [0.0, 1.0, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [1.0, 0.0, constants.positive.infinity], expected: kUnboundedEndpoints },
];
})
)
@@ -5622,18 +5674,18 @@ g.test('smoothStepInterval')
{ input: [0, 1, -10], expected: 0 },
// Subnormals
- { input: [0, constants.positive.subnormal.max, 1], expected: kUnboundedBounds },
- { input: [0, constants.positive.subnormal.min, 1], expected: kUnboundedBounds },
- { input: [0, constants.negative.subnormal.max, 1], expected: kUnboundedBounds },
- { input: [0, constants.negative.subnormal.min, 1], expected: kUnboundedBounds },
+ { input: [0, constants.positive.subnormal.max, 1], expected: kUnboundedEndpoints },
+ { input: [0, constants.positive.subnormal.min, 1], expected: kUnboundedEndpoints },
+ { input: [0, constants.negative.subnormal.max, 1], expected: kUnboundedEndpoints },
+ { input: [0, constants.negative.subnormal.min, 1], expected: kUnboundedEndpoints },
// Infinities
- { input: [0, 2, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [0, 2, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [constants.positive.infinity, 2, 1], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, 2, 1], expected: kUnboundedBounds },
- { input: [0, constants.positive.infinity, 1], expected: kUnboundedBounds },
- { input: [0, constants.negative.infinity, 1], expected: kUnboundedBounds },
+ { input: [0, 2, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [0, 2, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, 2, 1], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, 2, 1], expected: kUnboundedEndpoints },
+ { input: [0, constants.positive.infinity, 1], expected: kUnboundedEndpoints },
+ { input: [0, constants.negative.infinity, 1], expected: kUnboundedEndpoints },
];
})
)
@@ -5650,7 +5702,7 @@ g.test('smoothStepInterval')
interface ScalarToVectorCase {
input: number;
- expected: (number | IntervalBounds)[];
+ expected: (number | IntervalEndpoints)[];
}
g.test('unpack2x16floatInterval')
@@ -5674,8 +5726,8 @@ g.test('unpack2x16floatInterval')
{ input: 0x000083ff, expected: [[kValue.f16.negative.subnormal.min, 0], 0] },
// f16 out of bounds
- { input: 0x7c000000, expected: [kUnboundedBounds, kUnboundedBounds] },
- { input: 0xffff0000, expected: [kUnboundedBounds, kUnboundedBounds] },
+ { input: 0x7c000000, expected: [kUnboundedEndpoints, kUnboundedEndpoints] },
+ { input: 0xffff0000, expected: [kUnboundedEndpoints, kUnboundedEndpoints] },
]
)
.fn(t => {
@@ -5691,24 +5743,24 @@ g.test('unpack2x16floatInterval')
// magic numbers that don't pollute the global namespace or have unwieldy long
// names.
{
- const kZeroBounds: IntervalBounds = [
+ const kZeroEndpoints: IntervalEndpoints = [
reinterpretU32AsF32(0x81400000),
reinterpretU32AsF32(0x01400000),
];
- const kOneBoundsSnorm: IntervalBounds = [
+ const kOneEndpointsSnorm: IntervalEndpoints = [
reinterpretU64AsF64(0x3fef_ffff_a000_0000n),
reinterpretU64AsF64(0x3ff0_0000_3000_0000n),
];
- const kNegOneBoundsSnorm: IntervalBounds = [
+ const kNegOneEndpointsSnorm: IntervalEndpoints = [
reinterpretU64AsF64(0xbff0_0000_3000_0000n),
reinterpretU64AsF64(0xbfef_ffff_a000_0000n),
];
- const kHalfBounds2x16snorm: IntervalBounds = [
+ const kHalfEndpoints2x16snorm: IntervalEndpoints = [
reinterpretU64AsF64(0x3fe0_001f_a000_0000n),
reinterpretU64AsF64(0x3fe0_0020_8000_0000n),
]; // ~0.5..., due to lack of precision in i16
- const kNegHalfBounds2x16snorm: IntervalBounds = [
+ const kNegHalfEndpoints2x16snorm: IntervalEndpoints = [
reinterpretU64AsF64(0xbfdf_ffc0_6000_0000n),
reinterpretU64AsF64(0xbfdf_ffbf_8000_0000n),
]; // ~-0.5..., due to lack of precision in i16
@@ -5717,13 +5769,13 @@ g.test('unpack2x16floatInterval')
.paramsSubcasesOnly<ScalarToVectorCase>(
// prettier-ignore
[
- { input: 0x00000000, expected: [kZeroBounds, kZeroBounds] },
- { input: 0x00007fff, expected: [kOneBoundsSnorm, kZeroBounds] },
- { input: 0x7fff0000, expected: [kZeroBounds, kOneBoundsSnorm] },
- { input: 0x7fff7fff, expected: [kOneBoundsSnorm, kOneBoundsSnorm] },
- { input: 0x80018001, expected: [kNegOneBoundsSnorm, kNegOneBoundsSnorm] },
- { input: 0x40004000, expected: [kHalfBounds2x16snorm, kHalfBounds2x16snorm] },
- { input: 0xc001c001, expected: [kNegHalfBounds2x16snorm, kNegHalfBounds2x16snorm] },
+ { input: 0x00000000, expected: [kZeroEndpoints, kZeroEndpoints] },
+ { input: 0x00007fff, expected: [kOneEndpointsSnorm, kZeroEndpoints] },
+ { input: 0x7fff0000, expected: [kZeroEndpoints, kOneEndpointsSnorm] },
+ { input: 0x7fff7fff, expected: [kOneEndpointsSnorm, kOneEndpointsSnorm] },
+ { input: 0x80018001, expected: [kNegOneEndpointsSnorm, kNegOneEndpointsSnorm] },
+ { input: 0x40004000, expected: [kHalfEndpoints2x16snorm, kHalfEndpoints2x16snorm] },
+ { input: 0xc001c001, expected: [kNegHalfEndpoints2x16snorm, kNegHalfEndpoints2x16snorm] },
]
)
.fn(t => {
@@ -5740,15 +5792,15 @@ g.test('unpack2x16floatInterval')
// magic numbers that don't pollute the global namespace or have unwieldy long
// names.
{
- const kZeroBounds: IntervalBounds = [
+ const kZeroEndpoints: IntervalEndpoints = [
reinterpretU32AsF32(0x8140_0000),
reinterpretU32AsF32(0x0140_0000),
]; // ~0
- const kOneBounds: IntervalBounds = [
+ const kOneEndpoints: IntervalEndpoints = [
reinterpretU64AsF64(0x3fef_ffff_a000_0000n),
reinterpretU64AsF64(0x3ff0_0000_3000_0000n),
]; // ~1
- const kHalfBounds: IntervalBounds = [
+ const kHalfEndpoints: IntervalEndpoints = [
reinterpretU64AsF64(0x3fe0_000f_a000_0000n),
reinterpretU64AsF64(0x3fe0_0010_8000_0000n),
]; // ~0.5..., due to the lack of accuracy in u16
@@ -5757,11 +5809,11 @@ g.test('unpack2x16floatInterval')
.paramsSubcasesOnly<ScalarToVectorCase>(
// prettier-ignore
[
- { input: 0x00000000, expected: [kZeroBounds, kZeroBounds] },
- { input: 0x0000ffff, expected: [kOneBounds, kZeroBounds] },
- { input: 0xffff0000, expected: [kZeroBounds, kOneBounds] },
- { input: 0xffffffff, expected: [kOneBounds, kOneBounds] },
- { input: 0x80008000, expected: [kHalfBounds, kHalfBounds] },
+ { input: 0x00000000, expected: [kZeroEndpoints, kZeroEndpoints] },
+ { input: 0x0000ffff, expected: [kOneEndpoints, kZeroEndpoints] },
+ { input: 0xffff0000, expected: [kZeroEndpoints, kOneEndpoints] },
+ { input: 0xffffffff, expected: [kOneEndpoints, kOneEndpoints] },
+ { input: 0x80008000, expected: [kHalfEndpoints, kHalfEndpoints] },
]
)
.fn(t => {
@@ -5778,23 +5830,23 @@ g.test('unpack2x16floatInterval')
// magic numbers that don't pollute the global namespace or have unwieldy long
// names.
{
- const kZeroBounds: IntervalBounds = [
+ const kZeroEndpoints: IntervalEndpoints = [
reinterpretU32AsF32(0x8140_0000),
reinterpretU32AsF32(0x0140_0000),
]; // ~0
- const kOneBounds: IntervalBounds = [
+ const kOneEndpoints: IntervalEndpoints = [
reinterpretU64AsF64(0x3fef_ffff_a000_0000n),
reinterpretU64AsF64(0x3ff0_0000_3000_0000n),
]; // ~1
- const kNegOneBounds: IntervalBounds = [
+ const kNegOneEndpoints: IntervalEndpoints = [
reinterpretU64AsF64(0xbff0_0000_3000_0000n),
reinterpretU64AsF64(0xbfef_ffff_a0000_000n),
]; // ~-1
- const kHalfBounds: IntervalBounds = [
+ const kHalfEndpoints: IntervalEndpoints = [
reinterpretU64AsF64(0x3fe0_2040_2000_0000n),
reinterpretU64AsF64(0x3fe0_2041_0000_0000n),
]; // ~0.50196..., due to lack of precision in i8
- const kNegHalfBounds: IntervalBounds = [
+ const kNegHalfEndpoints: IntervalEndpoints = [
reinterpretU64AsF64(0xbfdf_bf7f_6000_0000n),
reinterpretU64AsF64(0xbfdf_bf7e_8000_0000n),
]; // ~-0.49606..., due to lack of precision in i8
@@ -5803,27 +5855,27 @@ g.test('unpack2x16floatInterval')
.paramsSubcasesOnly<ScalarToVectorCase>(
// prettier-ignore
[
- { input: 0x00000000, expected: [kZeroBounds, kZeroBounds, kZeroBounds, kZeroBounds] },
- { input: 0x0000007f, expected: [kOneBounds, kZeroBounds, kZeroBounds, kZeroBounds] },
- { input: 0x00007f00, expected: [kZeroBounds, kOneBounds, kZeroBounds, kZeroBounds] },
- { input: 0x007f0000, expected: [kZeroBounds, kZeroBounds, kOneBounds, kZeroBounds] },
- { input: 0x7f000000, expected: [kZeroBounds, kZeroBounds, kZeroBounds, kOneBounds] },
- { input: 0x00007f7f, expected: [kOneBounds, kOneBounds, kZeroBounds, kZeroBounds] },
- { input: 0x7f7f0000, expected: [kZeroBounds, kZeroBounds, kOneBounds, kOneBounds] },
- { input: 0x7f007f00, expected: [kZeroBounds, kOneBounds, kZeroBounds, kOneBounds] },
- { input: 0x007f007f, expected: [kOneBounds, kZeroBounds, kOneBounds, kZeroBounds] },
- { input: 0x7f7f7f7f, expected: [kOneBounds, kOneBounds, kOneBounds, kOneBounds] },
+ { input: 0x00000000, expected: [kZeroEndpoints, kZeroEndpoints, kZeroEndpoints, kZeroEndpoints] },
+ { input: 0x0000007f, expected: [kOneEndpoints, kZeroEndpoints, kZeroEndpoints, kZeroEndpoints] },
+ { input: 0x00007f00, expected: [kZeroEndpoints, kOneEndpoints, kZeroEndpoints, kZeroEndpoints] },
+ { input: 0x007f0000, expected: [kZeroEndpoints, kZeroEndpoints, kOneEndpoints, kZeroEndpoints] },
+ { input: 0x7f000000, expected: [kZeroEndpoints, kZeroEndpoints, kZeroEndpoints, kOneEndpoints] },
+ { input: 0x00007f7f, expected: [kOneEndpoints, kOneEndpoints, kZeroEndpoints, kZeroEndpoints] },
+ { input: 0x7f7f0000, expected: [kZeroEndpoints, kZeroEndpoints, kOneEndpoints, kOneEndpoints] },
+ { input: 0x7f007f00, expected: [kZeroEndpoints, kOneEndpoints, kZeroEndpoints, kOneEndpoints] },
+ { input: 0x007f007f, expected: [kOneEndpoints, kZeroEndpoints, kOneEndpoints, kZeroEndpoints] },
+ { input: 0x7f7f7f7f, expected: [kOneEndpoints, kOneEndpoints, kOneEndpoints, kOneEndpoints] },
{
input: 0x81818181,
- expected: [kNegOneBounds, kNegOneBounds, kNegOneBounds, kNegOneBounds]
+ expected: [kNegOneEndpoints, kNegOneEndpoints, kNegOneEndpoints, kNegOneEndpoints]
},
{
input: 0x40404040,
- expected: [kHalfBounds, kHalfBounds, kHalfBounds, kHalfBounds]
+ expected: [kHalfEndpoints, kHalfEndpoints, kHalfEndpoints, kHalfEndpoints]
},
{
input: 0xc1c1c1c1,
- expected: [kNegHalfBounds, kNegHalfBounds, kNegHalfBounds, kNegHalfBounds]
+ expected: [kNegHalfEndpoints, kNegHalfEndpoints, kNegHalfEndpoints, kNegHalfEndpoints]
},
]
)
@@ -5841,15 +5893,15 @@ g.test('unpack2x16floatInterval')
// magic numbers that don't pollute the global namespace or have unwieldy long
// names.
{
- const kZeroBounds: IntervalBounds = [
+ const kZeroEndpoints: IntervalEndpoints = [
reinterpretU32AsF32(0x8140_0000),
reinterpretU32AsF32(0x0140_0000),
]; // ~0
- const kOneBounds: IntervalBounds = [
+ const kOneEndpoints: IntervalEndpoints = [
reinterpretU64AsF64(0x3fef_ffff_a000_0000n),
reinterpretU64AsF64(0x3ff0_0000_3000_0000n),
]; // ~1
- const kHalfBounds: IntervalBounds = [
+ const kHalfEndpoints: IntervalEndpoints = [
reinterpretU64AsF64(0x3fe0_100f_a000_0000n),
reinterpretU64AsF64(0x3fe0_1010_8000_0000n),
]; // ~0.50196..., due to lack of precision in u8
@@ -5858,19 +5910,19 @@ g.test('unpack2x16floatInterval')
.paramsSubcasesOnly<ScalarToVectorCase>(
// prettier-ignore
[
- { input: 0x00000000, expected: [kZeroBounds, kZeroBounds, kZeroBounds, kZeroBounds] },
- { input: 0x000000ff, expected: [kOneBounds, kZeroBounds, kZeroBounds, kZeroBounds] },
- { input: 0x0000ff00, expected: [kZeroBounds, kOneBounds, kZeroBounds, kZeroBounds] },
- { input: 0x00ff0000, expected: [kZeroBounds, kZeroBounds, kOneBounds, kZeroBounds] },
- { input: 0xff000000, expected: [kZeroBounds, kZeroBounds, kZeroBounds, kOneBounds] },
- { input: 0x0000ffff, expected: [kOneBounds, kOneBounds, kZeroBounds, kZeroBounds] },
- { input: 0xffff0000, expected: [kZeroBounds, kZeroBounds, kOneBounds, kOneBounds] },
- { input: 0xff00ff00, expected: [kZeroBounds, kOneBounds, kZeroBounds, kOneBounds] },
- { input: 0x00ff00ff, expected: [kOneBounds, kZeroBounds, kOneBounds, kZeroBounds] },
- { input: 0xffffffff, expected: [kOneBounds, kOneBounds, kOneBounds, kOneBounds] },
+ { input: 0x00000000, expected: [kZeroEndpoints, kZeroEndpoints, kZeroEndpoints, kZeroEndpoints] },
+ { input: 0x000000ff, expected: [kOneEndpoints, kZeroEndpoints, kZeroEndpoints, kZeroEndpoints] },
+ { input: 0x0000ff00, expected: [kZeroEndpoints, kOneEndpoints, kZeroEndpoints, kZeroEndpoints] },
+ { input: 0x00ff0000, expected: [kZeroEndpoints, kZeroEndpoints, kOneEndpoints, kZeroEndpoints] },
+ { input: 0xff000000, expected: [kZeroEndpoints, kZeroEndpoints, kZeroEndpoints, kOneEndpoints] },
+ { input: 0x0000ffff, expected: [kOneEndpoints, kOneEndpoints, kZeroEndpoints, kZeroEndpoints] },
+ { input: 0xffff0000, expected: [kZeroEndpoints, kZeroEndpoints, kOneEndpoints, kOneEndpoints] },
+ { input: 0xff00ff00, expected: [kZeroEndpoints, kOneEndpoints, kZeroEndpoints, kOneEndpoints] },
+ { input: 0x00ff00ff, expected: [kOneEndpoints, kZeroEndpoints, kOneEndpoints, kZeroEndpoints] },
+ { input: 0xffffffff, expected: [kOneEndpoints, kOneEndpoints, kOneEndpoints, kOneEndpoints] },
{
input: 0x80808080,
- expected: [kHalfBounds, kHalfBounds, kHalfBounds, kHalfBounds]
+ expected: [kHalfEndpoints, kHalfEndpoints, kHalfEndpoints, kHalfEndpoints]
},
]
)
@@ -5886,7 +5938,7 @@ g.test('unpack2x16floatInterval')
interface VectorToIntervalCase {
input: number[];
- expected: number | IntervalBounds;
+ expected: number | IntervalEndpoints;
}
g.test('lengthIntervalVector')
@@ -5926,10 +5978,10 @@ g.test('lengthIntervalVector')
{input: [-1.0, 1.0, -1.0, 1.0], expected: kRootSumSquareExpectionInterval[p.trait]['[1.0, 1.0, 1.0, 1.0]'] }, // ~2
{input: [0.1, 0.0, 0.0, 0.0], expected: kRootSumSquareExpectionInterval[p.trait]['[0.1]'] }, // ~0.1
- // Test that dot going OOB bounds in the intermediate calculations propagates
- { input: [constants.positive.nearest_max, constants.positive.max, constants.negative.min], expected: kUnboundedBounds },
- { input: [constants.positive.max, constants.positive.nearest_max, constants.negative.min], expected: kUnboundedBounds },
- { input: [constants.negative.min, constants.positive.max, constants.positive.nearest_max], expected: kUnboundedBounds },
+ // Test that dot going OOB in the intermediate calculations propagates
+ { input: [constants.positive.nearest_max, constants.positive.max, constants.negative.min], expected: kUnboundedEndpoints },
+ { input: [constants.positive.max, constants.positive.nearest_max, constants.negative.min], expected: kUnboundedEndpoints },
+ { input: [constants.negative.min, constants.positive.max, constants.positive.nearest_max], expected: kUnboundedEndpoints },
];
})
)
@@ -5945,7 +5997,7 @@ g.test('lengthIntervalVector')
interface VectorPairToIntervalCase {
input: [number[], number[]];
- expected: number | IntervalBounds;
+ expected: number | IntervalEndpoints;
}
g.test('distanceIntervalVector')
@@ -5956,11 +6008,11 @@ g.test('distanceIntervalVector')
.expandWithParams<VectorPairToIntervalCase>(p => {
// prettier-ignore
return [
- // distance(x, y), where x - y = 0 has an acceptance interval of kUnboundedBounds,
- // because distance(x, y) = length(x - y), and length(0) = kUnboundedBounds.
+ // distance(x, y), where x - y = 0 has an acceptance interval of kUnboundedEndpoints,
+ // because distance(x, y) = length(x - y), and length(0) = kUnboundedEndpoints.
// vec2
- { input: [[1.0, 0.0], [1.0, 0.0]], expected: kUnboundedBounds },
+ { input: [[1.0, 0.0], [1.0, 0.0]], expected: kUnboundedEndpoints },
{ input: [[1.0, 0.0], [0.0, 0.0]], expected: kRootSumSquareExpectionInterval[p.trait]['[1.0]'] }, // ~1
{ input: [[0.0, 0.0], [1.0, 0.0]], expected: kRootSumSquareExpectionInterval[p.trait]['[1.0]'] }, // ~1
{ input: [[-1.0, 0.0], [0.0, 0.0]], expected: kRootSumSquareExpectionInterval[p.trait]['[1.0]'] }, // ~1
@@ -5969,7 +6021,7 @@ g.test('distanceIntervalVector')
{ input: [[0.1, 0.0], [0.0, 0.0]], expected: kRootSumSquareExpectionInterval[p.trait]['[0.1]'] }, // ~0.1
// vec3
- { input: [[1.0, 0.0, 0.0], [1.0, 0.0, 0.0]], expected: kUnboundedBounds },
+ { input: [[1.0, 0.0, 0.0], [1.0, 0.0, 0.0]], expected: kUnboundedEndpoints },
{ input: [[1.0, 0.0, 0.0], [0.0, 0.0, 0.0]], expected: kRootSumSquareExpectionInterval[p.trait]['[1.0]'] }, // ~1
{ input: [[0.0, 1.0, 0.0], [0.0, 0.0, 0.0]], expected: kRootSumSquareExpectionInterval[p.trait]['[1.0]'] }, // ~1
{ input: [[0.0, 0.0, 1.0], [0.0, 0.0, 0.0]], expected: kRootSumSquareExpectionInterval[p.trait]['[1.0]'] }, // ~1
@@ -5984,7 +6036,7 @@ g.test('distanceIntervalVector')
{ input: [[0.0, 0.0, 0.0], [0.1, 0.0, 0.0]], expected: kRootSumSquareExpectionInterval[p.trait]['[0.1]'] }, // ~0.1
// vec4
- { input: [[1.0, 0.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0]], expected: kUnboundedBounds },
+ { input: [[1.0, 0.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0]], expected: kUnboundedEndpoints },
{ input: [[1.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0]], expected: kRootSumSquareExpectionInterval[p.trait]['[1.0]'] }, // ~1
{ input: [[0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0]], expected: kRootSumSquareExpectionInterval[p.trait]['[1.0]'] }, // ~1
{ input: [[0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 0.0]], expected: kRootSumSquareExpectionInterval[p.trait]['[1.0]'] }, // ~1
@@ -6019,7 +6071,7 @@ const kDotIntervalCases = {
// 3.0*3.0 = 9.0 is much smaller than kValue.f32.positive.max, as a result
// kValue.f32.positive.max + 9.0 = kValue.f32.positive.max in f32 and even f64. So, if the
// positive and negative large number cancel each other first, the result would be
- // 2.0*2.0+3.0*3.0 = 13. Otherwise, the resule would be 0.0 or 4.0 or 9.0.
+ // 2.0*2.0+3.0*3.0 = 13. Otherwise, the result would be 0.0 or 4.0 or 9.0.
// https://github.com/gpuweb/cts/issues/2155
{ input: [[kValue.f32.positive.max, 1.0, 2.0, 3.0], [-1.0, kValue.f32.positive.max, -2.0, -3.0]], expected: [-13, 0] },
{ input: [[kValue.f32.positive.max, 1.0, 2.0, 3.0], [1.0, kValue.f32.negative.min, 2.0, 3.0]], expected: [0, 13] },
@@ -6029,10 +6081,10 @@ const kDotIntervalCases = {
// 3.0*3.0 = 9.0 is not small enough comparing to kValue.f16.positive.max = 65504, as a result
// kValue.f16.positive.max + 9.0 = 65513 is exactly representable in f32 and f64. So, if the
// positive and negative large number don't cancel each other first, the computation will
- // overflow f16 and result in unbounded bounds.
+ // overflow f16 and result in unbounded endpoints.
// https://github.com/gpuweb/cts/issues/2155
- { input: [[kValue.f16.positive.max, 1.0, 2.0, 3.0], [-1.0, kValue.f16.positive.max, -2.0, -3.0]], expected: kUnboundedBounds },
- { input: [[kValue.f16.positive.max, 1.0, 2.0, 3.0], [1.0, kValue.f16.negative.min, 2.0, 3.0]], expected: kUnboundedBounds },
+ { input: [[kValue.f16.positive.max, 1.0, 2.0, 3.0], [-1.0, kValue.f16.positive.max, -2.0, -3.0]], expected: kUnboundedEndpoints },
+ { input: [[kValue.f16.positive.max, 1.0, 2.0, 3.0], [1.0, kValue.f16.negative.min, 2.0, 3.0]], expected: kUnboundedEndpoints },
] as VectorPairToIntervalCase[],
} as const;
@@ -6052,7 +6104,7 @@ g.test('dotInterval')
{ input: [[1.0, 1.0], [1.0, 1.0]], expected: 2.0 },
{ input: [[-1.0, -1.0], [-1.0, -1.0]], expected: 2.0 },
{ input: [[-1.0, 1.0], [1.0, -1.0]], expected: -2.0 },
- { input: [[0.1, 0.0], [1.0, 0.0]], expected: kConstantCorrectlyRoundedExpectation[p.trait]['0.1']}, // correclt rounded of 0.1
+ { input: [[0.1, 0.0], [1.0, 0.0]], expected: kConstantCorrectlyRoundedExpectation[p.trait]['0.1']}, // correctly rounded of 0.1
// vec3
{ input: [[1.0, 0.0, 0.0], [1.0, 0.0, 0.0]], expected: 1.0 },
@@ -6061,7 +6113,7 @@ g.test('dotInterval')
{ input: [[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]], expected: 3.0 },
{ input: [[-1.0, -1.0, -1.0], [-1.0, -1.0, -1.0]], expected: 3.0 },
{ input: [[1.0, -1.0, -1.0], [-1.0, 1.0, -1.0]], expected: -1.0 },
- { input: [[0.1, 0.0, 0.0], [1.0, 0.0, 0.0]], expected: kConstantCorrectlyRoundedExpectation[p.trait]['0.1']}, // correclt rounded of 0.1
+ { input: [[0.1, 0.0, 0.0], [1.0, 0.0, 0.0]], expected: kConstantCorrectlyRoundedExpectation[p.trait]['0.1']}, // correctly rounded of 0.1
// vec4
{ input: [[1.0, 0.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0]], expected: 1.0 },
@@ -6076,12 +6128,12 @@ g.test('dotInterval')
...kDotIntervalCases[p.trait],
// Test that going out of bounds in the intermediate calculations is caught correctly.
- { input: [[constants.positive.nearest_max, constants.positive.max, constants.negative.min], [1.0, 1.0, 1.0]], expected: kUnboundedBounds },
- { input: [[constants.positive.nearest_max, constants.negative.min, constants.positive.max], [1.0, 1.0, 1.0]], expected: kUnboundedBounds },
- { input: [[constants.positive.max, constants.positive.nearest_max, constants.negative.min], [1.0, 1.0, 1.0]], expected: kUnboundedBounds },
- { input: [[constants.negative.min, constants.positive.nearest_max, constants.positive.max], [1.0, 1.0, 1.0]], expected: kUnboundedBounds },
- { input: [[constants.positive.max, constants.negative.min, constants.positive.nearest_max], [1.0, 1.0, 1.0]], expected: kUnboundedBounds },
- { input: [[constants.negative.min, constants.positive.max, constants.positive.nearest_max], [1.0, 1.0, 1.0]], expected: kUnboundedBounds },
+ { input: [[constants.positive.nearest_max, constants.positive.max, constants.negative.min], [1.0, 1.0, 1.0]], expected: kUnboundedEndpoints },
+ { input: [[constants.positive.nearest_max, constants.negative.min, constants.positive.max], [1.0, 1.0, 1.0]], expected: kUnboundedEndpoints },
+ { input: [[constants.positive.max, constants.positive.nearest_max, constants.negative.min], [1.0, 1.0, 1.0]], expected: kUnboundedEndpoints },
+ { input: [[constants.negative.min, constants.positive.nearest_max, constants.positive.max], [1.0, 1.0, 1.0]], expected: kUnboundedEndpoints },
+ { input: [[constants.positive.max, constants.negative.min, constants.positive.nearest_max], [1.0, 1.0, 1.0]], expected: kUnboundedEndpoints },
+ { input: [[constants.negative.min, constants.positive.max, constants.positive.nearest_max], [1.0, 1.0, 1.0]], expected: kUnboundedEndpoints },
];
})
)
@@ -6098,54 +6150,54 @@ g.test('dotInterval')
interface VectorToVectorCase {
input: number[];
- expected: (number | IntervalBounds)[];
+ expected: (number | IntervalEndpoints)[];
}
// prettier-ignore
const kNormalizeIntervalCases = {
f32: [
// vec2
- {input: [1.0, 0.0], expected: [[reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] }, // [ ~1.0, ~0.0]
- {input: [0.0, 1.0], expected: [[reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)]] }, // [ ~0.0, ~1.0]
- {input: [-1.0, 0.0], expected: [[reinterpretU64AsF64(0xbff0_0000_b000_0000n), reinterpretU64AsF64(0xbfef_fffe_7000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] }, // [ ~1.0, ~0.0]
- {input: [1.0, 1.0], expected: [[reinterpretU64AsF64(0x3fe6_a09d_5000_0000n), reinterpretU64AsF64(0x3fe6_a09f_9000_0000n)], [reinterpretU64AsF64(0x3fe6_a09d_5000_0000n), reinterpretU64AsF64(0x3fe6_a09f_9000_0000n)]] }, // [ ~1/√2, ~1/√2]
+ { input: [1.0, 0.0], expected: [[reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] }, // [ ~1.0, ~0.0]
+ { input: [0.0, 1.0], expected: [[reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)]] }, // [ ~0.0, ~1.0]
+ { input: [-1.0, 0.0], expected: [[reinterpretU64AsF64(0xbff0_0000_b000_0000n), reinterpretU64AsF64(0xbfef_fffe_7000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] }, // [ ~1.0, ~0.0]
+ { input: [1.0, 1.0], expected: [[reinterpretU64AsF64(0x3fe6_a09d_5000_0000n), reinterpretU64AsF64(0x3fe6_a09f_9000_0000n)], [reinterpretU64AsF64(0x3fe6_a09d_5000_0000n), reinterpretU64AsF64(0x3fe6_a09f_9000_0000n)]] }, // [ ~1/√2, ~1/√2]
// vec3
- {input: [1.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] }, // [ ~1.0, ~0.0, ~0.0]
- {input: [0.0, 1.0, 0.0], expected: [[reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] }, // [ ~0.0, ~1.0, ~0.0]
- {input: [0.0, 0.0, 1.0], expected: [[reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)]] }, // [ ~0.0, ~0.0, ~1.0]
- {input: [-1.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0xbff0_0000_b000_0000n), reinterpretU64AsF64(0xbfef_fffe_7000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] }, // [ ~1.0, ~0.0, ~0.0]
- {input: [1.0, 1.0, 1.0], expected: [[reinterpretU64AsF64(0x3fe2_79a6_5000_0000n), reinterpretU64AsF64(0x3fe2_79a8_5000_0000n)], [reinterpretU64AsF64(0x3fe2_79a6_5000_0000n), reinterpretU64AsF64(0x3fe2_79a8_5000_0000n)], [reinterpretU64AsF64(0x3fe2_79a6_5000_0000n), reinterpretU64AsF64(0x3fe2_79a8_5000_0000n)]] }, // [ ~1/√3, ~1/√3, ~1/√3]
+ { input: [1.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] }, // [ ~1.0, ~0.0, ~0.0]
+ { input: [0.0, 1.0, 0.0], expected: [[reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] }, // [ ~0.0, ~1.0, ~0.0]
+ { input: [0.0, 0.0, 1.0], expected: [[reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)]] }, // [ ~0.0, ~0.0, ~1.0]
+ { input: [-1.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0xbff0_0000_b000_0000n), reinterpretU64AsF64(0xbfef_fffe_7000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] }, // [ ~1.0, ~0.0, ~0.0]
+ { input: [1.0, 1.0, 1.0], expected: [[reinterpretU64AsF64(0x3fe2_79a6_5000_0000n), reinterpretU64AsF64(0x3fe2_79a8_5000_0000n)], [reinterpretU64AsF64(0x3fe2_79a6_5000_0000n), reinterpretU64AsF64(0x3fe2_79a8_5000_0000n)], [reinterpretU64AsF64(0x3fe2_79a6_5000_0000n), reinterpretU64AsF64(0x3fe2_79a8_5000_0000n)]] }, // [ ~1/√3, ~1/√3, ~1/√3]
// vec4
- {input: [1.0, 0.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] }, // [ ~1.0, ~0.0, ~0.0, ~0.0]
- {input: [0.0, 1.0, 0.0, 0.0], expected: [[reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] }, // [ ~0.0, ~1.0, ~0.0, ~0.0]
- {input: [0.0, 0.0, 1.0, 0.0], expected: [[reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] }, // [ ~0.0, ~0.0, ~1.0, ~0.0]
- {input: [0.0, 0.0, 0.0, 1.0], expected: [[reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)]] }, // [ ~0.0, ~0.0, ~0.0, ~1.0]
- {input: [-1.0, 0.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0xbff0_0000_b000_0000n), reinterpretU64AsF64(0xbfef_fffe_7000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] }, // [ ~1.0, ~0.0, ~0.0, ~0.0]
- {input: [1.0, 1.0, 1.0, 1.0], expected: [[reinterpretU64AsF64(0x3fdf_fffe_7000_0000n), reinterpretU64AsF64(0x3fe0_0000_b000_0000n)], [reinterpretU64AsF64(0x3fdf_fffe_7000_0000n), reinterpretU64AsF64(0x3fe0_0000_b000_0000n)], [reinterpretU64AsF64(0x3fdf_fffe_7000_0000n), reinterpretU64AsF64(0x3fe0_0000_b000_0000n)], [reinterpretU64AsF64(0x3fdf_fffe_7000_0000n), reinterpretU64AsF64(0x3fe0_0000_b000_0000n)]] }, // [ ~1/√4, ~1/√4, ~1/√4]
+ { input: [1.0, 0.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] }, // [ ~1.0, ~0.0, ~0.0, ~0.0]
+ { input: [0.0, 1.0, 0.0, 0.0], expected: [[reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] }, // [ ~0.0, ~1.0, ~0.0, ~0.0]
+ { input: [0.0, 0.0, 1.0, 0.0], expected: [[reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] }, // [ ~0.0, ~0.0, ~1.0, ~0.0]
+ { input: [0.0, 0.0, 0.0, 1.0], expected: [[reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)]] }, // [ ~0.0, ~0.0, ~0.0, ~1.0]
+ { input: [-1.0, 0.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0xbff0_0000_b000_0000n), reinterpretU64AsF64(0xbfef_fffe_7000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] }, // [ ~1.0, ~0.0, ~0.0, ~0.0]
+ { input: [1.0, 1.0, 1.0, 1.0], expected: [[reinterpretU64AsF64(0x3fdf_fffe_7000_0000n), reinterpretU64AsF64(0x3fe0_0000_b000_0000n)], [reinterpretU64AsF64(0x3fdf_fffe_7000_0000n), reinterpretU64AsF64(0x3fe0_0000_b000_0000n)], [reinterpretU64AsF64(0x3fdf_fffe_7000_0000n), reinterpretU64AsF64(0x3fe0_0000_b000_0000n)], [reinterpretU64AsF64(0x3fdf_fffe_7000_0000n), reinterpretU64AsF64(0x3fe0_0000_b000_0000n)]] }, // [ ~1/√4, ~1/√4, ~1/√4]
] as VectorToVectorCase[],
f16: [
// vec2
- {input: [1.0, 0.0], expected: [[reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] }, // [ ~1.0, ~0.0]
- {input: [0.0, 1.0], expected: [[reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)]] }, // [ ~0.0, ~1.0]
- {input: [-1.0, 0.0], expected: [[reinterpretU64AsF64(0xbff0_1600_0000_0000n), reinterpretU64AsF64(0xbfef_ce00_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] }, // [ ~1.0, ~0.0]
- {input: [1.0, 1.0], expected: [[reinterpretU64AsF64(0x3fe6_7e00_0000_0000n), reinterpretU64AsF64(0x3fe6_c600_0000_0000n)], [reinterpretU64AsF64(0x3fe6_7e00_0000_0000n), reinterpretU64AsF64(0x3fe6_c600_0000_0000n)]] }, // [ ~1/√2, ~1/√2]
+ { input: [1.0, 0.0], expected: [[reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] }, // [ ~1.0, ~0.0]
+ { input: [0.0, 1.0], expected: [[reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)]] }, // [ ~0.0, ~1.0]
+ { input: [-1.0, 0.0], expected: [[reinterpretU64AsF64(0xbff0_1600_0000_0000n), reinterpretU64AsF64(0xbfef_ce00_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] }, // [ ~1.0, ~0.0]
+ { input: [1.0, 1.0], expected: [[reinterpretU64AsF64(0x3fe6_7e00_0000_0000n), reinterpretU64AsF64(0x3fe6_c600_0000_0000n)], [reinterpretU64AsF64(0x3fe6_7e00_0000_0000n), reinterpretU64AsF64(0x3fe6_c600_0000_0000n)]] }, // [ ~1/√2, ~1/√2]
// vec3
- {input: [1.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] }, // [ ~1.0, ~0.0, ~0.0]
- {input: [0.0, 1.0, 0.0], expected: [[reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] }, // [ ~0.0, ~1.0, ~0.0]
- {input: [0.0, 0.0, 1.0], expected: [[reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)]] }, // [ ~0.0, ~0.0, ~1.0]
- {input: [-1.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0xbff0_1600_0000_0000n), reinterpretU64AsF64(0xbfef_ce00_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] }, // [ ~1.0, ~0.0, ~0.0]
- {input: [1.0, 1.0, 1.0], expected: [[reinterpretU64AsF64(0x3fe2_5a00_0000_0000n), reinterpretU64AsF64(0x3fe2_9a00_0000_0000n)], [reinterpretU64AsF64(0x3fe2_5a00_0000_0000n), reinterpretU64AsF64(0x3fe2_9a00_0000_0000n)], [reinterpretU64AsF64(0x3fe2_5a00_0000_0000n), reinterpretU64AsF64(0x3fe2_9a00_0000_0000n)]] }, // [ ~1/√3, ~1/√3, ~1/√3]
+ { input: [1.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] }, // [ ~1.0, ~0.0, ~0.0]
+ { input: [0.0, 1.0, 0.0], expected: [[reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] }, // [ ~0.0, ~1.0, ~0.0]
+ { input: [0.0, 0.0, 1.0], expected: [[reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)]] }, // [ ~0.0, ~0.0, ~1.0]
+ { input: [-1.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0xbff0_1600_0000_0000n), reinterpretU64AsF64(0xbfef_ce00_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] }, // [ ~1.0, ~0.0, ~0.0]
+ { input: [1.0, 1.0, 1.0], expected: [[reinterpretU64AsF64(0x3fe2_5a00_0000_0000n), reinterpretU64AsF64(0x3fe2_9a00_0000_0000n)], [reinterpretU64AsF64(0x3fe2_5a00_0000_0000n), reinterpretU64AsF64(0x3fe2_9a00_0000_0000n)], [reinterpretU64AsF64(0x3fe2_5a00_0000_0000n), reinterpretU64AsF64(0x3fe2_9a00_0000_0000n)]] }, // [ ~1/√3, ~1/√3, ~1/√3]
// vec4
- {input: [1.0, 0.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] }, // [ ~1.0, ~0.0, ~0.0, ~0.0]
- {input: [0.0, 1.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] }, // [ ~0.0, ~1.0, ~0.0, ~0.0]
- {input: [0.0, 0.0, 1.0, 0.0], expected: [[reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] }, // [ ~0.0, ~0.0, ~1.0, ~0.0]
- {input: [0.0, 0.0, 0.0, 1.0], expected: [[reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)]] }, // [ ~0.0, ~0.0, ~0.0, ~1.0]
- {input: [-1.0, 0.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0xbff0_1600_0000_0000n), reinterpretU64AsF64(0xbfef_ce00_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] }, // [ ~1.0, ~0.0, ~0.0, ~0.0]
- {input: [1.0, 1.0, 1.0, 1.0], expected: [[reinterpretU64AsF64(0x3fdf_ce00_0000_0000n), reinterpretU64AsF64(0x3fe0_1600_0000_0000n)], [reinterpretU64AsF64(0x3fdf_ce00_0000_0000n), reinterpretU64AsF64(0x3fe0_1600_0000_0000n)], [reinterpretU64AsF64(0x3fdf_ce00_0000_0000n), reinterpretU64AsF64(0x3fe0_1600_0000_0000n)], [reinterpretU64AsF64(0x3fdf_ce00_0000_0000n), reinterpretU64AsF64(0x3fe0_1600_0000_0000n)]] }, // [ ~1/√4, ~1/√4, ~1/√4]
+ { input: [1.0, 0.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] }, // [ ~1.0, ~0.0, ~0.0, ~0.0]
+ { input: [0.0, 1.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] }, // [ ~0.0, ~1.0, ~0.0, ~0.0]
+ { input: [0.0, 0.0, 1.0, 0.0], expected: [[reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] }, // [ ~0.0, ~0.0, ~1.0, ~0.0]
+ { input: [0.0, 0.0, 0.0, 1.0], expected: [[reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)]] }, // [ ~0.0, ~0.0, ~0.0, ~1.0]
+ { input: [-1.0, 0.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0xbff0_1600_0000_0000n), reinterpretU64AsF64(0xbfef_ce00_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] }, // [ ~1.0, ~0.0, ~0.0, ~0.0]
+ { input: [1.0, 1.0, 1.0, 1.0], expected: [[reinterpretU64AsF64(0x3fdf_ce00_0000_0000n), reinterpretU64AsF64(0x3fe0_1600_0000_0000n)], [reinterpretU64AsF64(0x3fdf_ce00_0000_0000n), reinterpretU64AsF64(0x3fe0_1600_0000_0000n)], [reinterpretU64AsF64(0x3fdf_ce00_0000_0000n), reinterpretU64AsF64(0x3fe0_1600_0000_0000n)], [reinterpretU64AsF64(0x3fdf_ce00_0000_0000n), reinterpretU64AsF64(0x3fe0_1600_0000_0000n)]] }, // [ ~1/√4, ~1/√4, ~1/√4]
] as VectorToVectorCase[],
} as const;
@@ -6154,7 +6206,20 @@ g.test('normalizeInterval')
u
.combine('trait', ['f32', 'f16'] as const)
.beginSubcases()
- .expandWithParams<VectorToVectorCase>(p => kNormalizeIntervalCases[p.trait])
+ .expandWithParams<VectorToVectorCase>(p => {
+ const trait = FP[p.trait];
+ const constants = trait.constants();
+ // prettier-ignore
+ return [
+ ...kNormalizeIntervalCases[p.trait],
+
+ // Very small vectors go OOB due to division
+ { input: [constants.positive.subnormal.max, constants.positive.subnormal.max], expected: [kUnboundedEndpoints, kUnboundedEndpoints], },
+
+ // Very large vectors go OOB due to overflow
+ { input: [constants.positive.max, constants.positive.max], expected: [kUnboundedEndpoints, kUnboundedEndpoints], },
+ ];
+ })
)
.fn(t => {
const x = t.params.input;
@@ -6169,7 +6234,7 @@ g.test('normalizeInterval')
interface VectorPairToVectorCase {
input: [number[], number[]];
- expected: (number | IntervalBounds)[];
+ expected: (number | IntervalEndpoints)[];
}
// prettier-ignore
@@ -6218,30 +6283,12 @@ const kCrossIntervalCases = {
]
},
] as VectorPairToVectorCase[],
- abstract: [
- { input: [
- [kValue.f64.positive.subnormal.max, kValue.f64.negative.subnormal.max, kValue.f64.negative.subnormal.min],
- [kValue.f64.negative.subnormal.min, kValue.f64.positive.subnormal.min, kValue.f64.negative.subnormal.max]
- ],
- expected: [0.0, 0.0, 0.0]
- },
- { input: [
- [0.1, -0.1, -0.1],
- [-0.1, 0.1, -0.1]
- ],
- expected: [
- reinterpretU64AsF64(0x3f94_7ae1_47ae_147cn), // ~0.02
- reinterpretU64AsF64(0x3f94_7ae1_47ae_147cn), // ~0.02
- 0.0
- ]
- },
- ] as VectorPairToVectorCase[],
} as const;
g.test('crossInterval')
.params(u =>
u
- .combine('trait', ['f32', 'f16', 'abstract'] as const)
+ .combine('trait', ['f32', 'f16'] as const)
.beginSubcases()
.expandWithParams<VectorPairToVectorCase>(p => {
const trait = FP[p.trait];
@@ -6261,6 +6308,9 @@ g.test('crossInterval')
{ input: [[1.0, -1.0, -1.0], [-1.0, 1.0, -1.0]], expected: [2.0, 2.0, 0.0] },
{ input: [[1.0, 2, 3], [1.0, 5.0, 7.0]], expected: [-1, -4, 3] },
...kCrossIntervalCases[p.trait],
+
+ // OOB
+ { input: [[constants.positive.max, 1.0, 1.0], [1.0, constants.positive.max, -1.0]], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
];
})
)
@@ -6320,6 +6370,7 @@ g.test('reflectInterval')
{ input: [[0.0, 1.0], [1.0, 0.0]], expected: [0.0, 1.0] },
{ input: [[1.0, 1.0], [1.0, 1.0]], expected: [-3.0, -3.0] },
{ input: [[-1.0, -1.0], [1.0, 1.0]], expected: [3.0, 3.0] },
+
// vec3s
{ input: [[1.0, 0.0, 0.0], [1.0, 0.0, 0.0]], expected: [-1.0, 0.0, 0.0] },
{ input: [[0.0, 1.0, 0.0], [1.0, 0.0, 0.0]], expected: [0.0, 1.0, 0.0] },
@@ -6328,6 +6379,7 @@ g.test('reflectInterval')
{ input: [[1.0, 0.0, 0.0], [0.0, 0.0, 1.0]], expected: [1.0, 0.0, 0.0] },
{ input: [[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]], expected: [-5.0, -5.0, -5.0] },
{ input: [[-1.0, -1.0, -1.0], [1.0, 1.0, 1.0]], expected: [5.0, 5.0, 5.0] },
+
// vec4s
{ input: [[1.0, 0.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0]], expected: [-1.0, 0.0, 0.0, 0.0] },
{ input: [[0.0, 1.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0]], expected: [0.0, 1.0, 0.0, 0.0] },
@@ -6337,16 +6389,17 @@ g.test('reflectInterval')
{ input: [[1.0, 0.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0]], expected: [1.0, 0.0, 0.0, 0.0] },
{ input: [[1.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 1.0]], expected: [1.0, 0.0, 0.0, 0.0] },
{ input: [[-1.0, -1.0, -1.0, -1.0], [1.0, 1.0, 1.0, 1.0]], expected: [7.0, 7.0, 7.0, 7.0] },
- // Test that dot going OOB bounds in the intermediate calculations propagates
- { input: [[constants.positive.nearest_max, constants.positive.max, constants.negative.min], [1.0, 1.0, 1.0]], expected: [kUnboundedBounds, kUnboundedBounds, kUnboundedBounds] },
- { input: [[constants.positive.nearest_max, constants.negative.min, constants.positive.max], [1.0, 1.0, 1.0]], expected: [kUnboundedBounds, kUnboundedBounds, kUnboundedBounds] },
- { input: [[constants.positive.max, constants.positive.nearest_max, constants.negative.min], [1.0, 1.0, 1.0]], expected: [kUnboundedBounds, kUnboundedBounds, kUnboundedBounds] },
- { input: [[constants.negative.min, constants.positive.nearest_max, constants.positive.max], [1.0, 1.0, 1.0]], expected: [kUnboundedBounds, kUnboundedBounds, kUnboundedBounds] },
- { input: [[constants.positive.max, constants.negative.min, constants.positive.nearest_max], [1.0, 1.0, 1.0]], expected: [kUnboundedBounds, kUnboundedBounds, kUnboundedBounds] },
- { input: [[constants.negative.min, constants.positive.max, constants.positive.nearest_max], [1.0, 1.0, 1.0]], expected: [kUnboundedBounds, kUnboundedBounds, kUnboundedBounds] },
+
+ // Test that dot going OOB in the intermediate calculations propagates
+ { input: [[constants.positive.nearest_max, constants.positive.max, constants.negative.min], [1.0, 1.0, 1.0]], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
+ { input: [[constants.positive.nearest_max, constants.negative.min, constants.positive.max], [1.0, 1.0, 1.0]], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
+ { input: [[constants.positive.max, constants.positive.nearest_max, constants.negative.min], [1.0, 1.0, 1.0]], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
+ { input: [[constants.negative.min, constants.positive.nearest_max, constants.positive.max], [1.0, 1.0, 1.0]], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
+ { input: [[constants.positive.max, constants.negative.min, constants.positive.nearest_max], [1.0, 1.0, 1.0]], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
+ { input: [[constants.negative.min, constants.positive.max, constants.positive.nearest_max], [1.0, 1.0, 1.0]], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
// Test that post-dot going OOB propagates
- { input: [[constants.positive.max, 1.0, 2.0, 3.0], [-1.0, constants.positive.max, -2.0, -3.0]], expected: [kUnboundedBounds, kUnboundedBounds, kUnboundedBounds, kUnboundedBounds] },
+ { input: [[constants.positive.max, 1.0, 2.0, 3.0], [-1.0, constants.positive.max, -2.0, -3.0]], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
];
})
)
@@ -6365,7 +6418,7 @@ g.test('reflectInterval')
interface MatrixToScalarCase {
input: number[][];
- expected: number | IntervalBounds;
+ expected: number | IntervalEndpoints;
}
g.test('determinantInterval')
@@ -6480,7 +6533,7 @@ g.test('determinantInterval')
interface MatrixToMatrixCase {
input: number[][];
- expected: (number | IntervalBounds)[][];
+ expected: (number | IntervalEndpoints)[][];
}
g.test('transposeInterval')
@@ -6634,7 +6687,7 @@ g.test('transposeInterval')
interface MatrixPairToMatrixCase {
input: [number[][], number[][]];
- expected: (number | IntervalBounds)[][];
+ expected: (number | IntervalEndpoints)[][];
}
g.test('additionMatrixMatrixInterval')
@@ -6642,184 +6695,205 @@ g.test('additionMatrixMatrixInterval')
u
.combine('trait', ['f32', 'f16', 'abstract'] as const)
.beginSubcases()
- .combineWithParams<MatrixPairToMatrixCase>([
- // Only testing that different shapes of matrices are handled correctly
- // here, to reduce test duplication.
- // additionMatrixMatrixInterval uses AdditionIntervalOp for calculating intervals,
- // so the testing for additionInterval covers the actual interval
- // calculations.
- {
- input: [
- [
- [1, 2],
- [3, 4],
+ .expandWithParams<MatrixPairToMatrixCase>(p => {
+ const trait = FP[p.trait];
+ const constants = trait.constants();
+ return [
+ // Only testing that different shapes of matrices are handled correctly
+ // here, to reduce test duplication.
+ // additionMatrixMatrixInterval uses AdditionIntervalOp for calculating intervals,
+ // so the testing for additionInterval covers the actual interval
+ // calculations.
+ {
+ input: [
+ [
+ [1, 2],
+ [3, 4],
+ ],
+ [
+ [10, 20],
+ [30, 40],
+ ],
],
- [
- [10, 20],
- [30, 40],
+ expected: [
+ [11, 22],
+ [33, 44],
],
- ],
- expected: [
- [11, 22],
- [33, 44],
- ],
- },
- {
- input: [
- [
- [1, 2],
- [3, 4],
- [5, 6],
+ },
+ {
+ input: [
+ [
+ [1, 2],
+ [3, 4],
+ [5, 6],
+ ],
+ [
+ [10, 20],
+ [30, 40],
+ [50, 60],
+ ],
],
- [
- [10, 20],
- [30, 40],
- [50, 60],
+ expected: [
+ [11, 22],
+ [33, 44],
+ [55, 66],
],
- ],
- expected: [
- [11, 22],
- [33, 44],
- [55, 66],
- ],
- },
- {
- input: [
- [
- [1, 2],
- [3, 4],
- [5, 6],
- [7, 8],
+ },
+ {
+ input: [
+ [
+ [1, 2],
+ [3, 4],
+ [5, 6],
+ [7, 8],
+ ],
+ [
+ [10, 20],
+ [30, 40],
+ [50, 60],
+ [70, 80],
+ ],
],
- [
- [10, 20],
- [30, 40],
- [50, 60],
- [70, 80],
+ expected: [
+ [11, 22],
+ [33, 44],
+ [55, 66],
+ [77, 88],
],
- ],
- expected: [
- [11, 22],
- [33, 44],
- [55, 66],
- [77, 88],
- ],
- },
- {
- input: [
- [
- [1, 2, 3],
- [4, 5, 6],
+ },
+ {
+ input: [
+ [
+ [1, 2, 3],
+ [4, 5, 6],
+ ],
+ [
+ [10, 20, 30],
+ [40, 50, 60],
+ ],
],
- [
- [10, 20, 30],
- [40, 50, 60],
+ expected: [
+ [11, 22, 33],
+ [44, 55, 66],
],
- ],
- expected: [
- [11, 22, 33],
- [44, 55, 66],
- ],
- },
- {
- input: [
- [
- [1, 2, 3],
- [4, 5, 6],
- [7, 8, 9],
+ },
+ {
+ input: [
+ [
+ [1, 2, 3],
+ [4, 5, 6],
+ [7, 8, 9],
+ ],
+ [
+ [10, 20, 30],
+ [40, 50, 60],
+ [70, 80, 90],
+ ],
],
- [
- [10, 20, 30],
- [40, 50, 60],
- [70, 80, 90],
+ expected: [
+ [11, 22, 33],
+ [44, 55, 66],
+ [77, 88, 99],
],
- ],
- expected: [
- [11, 22, 33],
- [44, 55, 66],
- [77, 88, 99],
- ],
- },
- {
- input: [
- [
- [1, 2, 3],
- [4, 5, 6],
- [7, 8, 9],
- [10, 11, 12],
+ },
+ {
+ input: [
+ [
+ [1, 2, 3],
+ [4, 5, 6],
+ [7, 8, 9],
+ [10, 11, 12],
+ ],
+ [
+ [10, 20, 30],
+ [40, 50, 60],
+ [70, 80, 90],
+ [1000, 1100, 1200],
+ ],
],
- [
- [10, 20, 30],
- [40, 50, 60],
- [70, 80, 90],
- [1000, 1100, 1200],
+ expected: [
+ [11, 22, 33],
+ [44, 55, 66],
+ [77, 88, 99],
+ [1010, 1111, 1212],
],
- ],
- expected: [
- [11, 22, 33],
- [44, 55, 66],
- [77, 88, 99],
- [1010, 1111, 1212],
- ],
- },
- {
- input: [
- [
- [1, 2, 3, 4],
- [5, 6, 7, 8],
+ },
+ {
+ input: [
+ [
+ [1, 2, 3, 4],
+ [5, 6, 7, 8],
+ ],
+ [
+ [10, 20, 30, 40],
+ [50, 60, 70, 80],
+ ],
],
- [
- [10, 20, 30, 40],
- [50, 60, 70, 80],
+ expected: [
+ [11, 22, 33, 44],
+ [55, 66, 77, 88],
],
- ],
- expected: [
- [11, 22, 33, 44],
- [55, 66, 77, 88],
- ],
- },
- {
- input: [
- [
- [1, 2, 3, 4],
- [5, 6, 7, 8],
- [9, 10, 11, 12],
+ },
+ {
+ input: [
+ [
+ [1, 2, 3, 4],
+ [5, 6, 7, 8],
+ [9, 10, 11, 12],
+ ],
+ [
+ [10, 20, 30, 40],
+ [50, 60, 70, 80],
+ [90, 1000, 1100, 1200],
+ ],
],
- [
- [10, 20, 30, 40],
- [50, 60, 70, 80],
- [90, 1000, 1100, 1200],
+ expected: [
+ [11, 22, 33, 44],
+ [55, 66, 77, 88],
+ [99, 1010, 1111, 1212],
],
- ],
- expected: [
- [11, 22, 33, 44],
- [55, 66, 77, 88],
- [99, 1010, 1111, 1212],
- ],
- },
- {
- input: [
- [
- [1, 2, 3, 4],
- [5, 6, 7, 8],
- [9, 10, 11, 12],
- [13, 14, 15, 16],
+ },
+ {
+ input: [
+ [
+ [1, 2, 3, 4],
+ [5, 6, 7, 8],
+ [9, 10, 11, 12],
+ [13, 14, 15, 16],
+ ],
+ [
+ [10, 20, 30, 40],
+ [50, 60, 70, 80],
+ [90, 1000, 1100, 1200],
+ [1300, 1400, 1500, 1600],
+ ],
],
- [
- [10, 20, 30, 40],
- [50, 60, 70, 80],
- [90, 1000, 1100, 1200],
- [1300, 1400, 1500, 1600],
+ expected: [
+ [11, 22, 33, 44],
+ [55, 66, 77, 88],
+ [99, 1010, 1111, 1212],
+ [1313, 1414, 1515, 1616],
],
- ],
- expected: [
- [11, 22, 33, 44],
- [55, 66, 77, 88],
- [99, 1010, 1111, 1212],
- [1313, 1414, 1515, 1616],
- ],
- },
- ])
+ },
+ // Test the OOB is handled component-wise
+ {
+ input: [
+ [
+ [constants.positive.max, 2],
+ [3, 4],
+ ],
+ [
+ [constants.positive.max, 20],
+ [30, 40],
+ ],
+ ],
+ expected: [
+ [kUnboundedEndpoints, 22],
+ [33, 44],
+ ],
+ },
+ ];
+ })
)
.fn(t => {
const [x, y] = t.params.input;
@@ -6839,184 +6913,205 @@ g.test('subtractionMatrixMatrixInterval')
u
.combine('trait', ['f32', 'f16', 'abstract'] as const)
.beginSubcases()
- .combineWithParams<MatrixPairToMatrixCase>([
- // Only testing that different shapes of matrices are handled correctly
- // here, to reduce test duplication.
- // subtractionMatrixMatrixInterval uses AdditionIntervalOp for calculating intervals,
- // so the testing for subtractionInterval covers the actual interval
- // calculations.
- {
- input: [
- [
- [1, 2],
- [3, 4],
+ .expandWithParams<MatrixPairToMatrixCase>(p => {
+ const trait = FP[p.trait];
+ const constants = trait.constants();
+ return [
+ // Only testing that different shapes of matrices are handled correctly
+ // here, to reduce test duplication.
+ // subtractionMatrixMatrixInterval uses AdditionIntervalOp for calculating intervals,
+ // so the testing for subtractionInterval covers the actual interval
+ // calculations.
+ {
+ input: [
+ [
+ [1, 2],
+ [3, 4],
+ ],
+ [
+ [-10, -20],
+ [-30, -40],
+ ],
],
- [
- [-10, -20],
- [-30, -40],
+ expected: [
+ [11, 22],
+ [33, 44],
],
- ],
- expected: [
- [11, 22],
- [33, 44],
- ],
- },
- {
- input: [
- [
- [1, 2],
- [3, 4],
- [5, 6],
+ },
+ {
+ input: [
+ [
+ [1, 2],
+ [3, 4],
+ [5, 6],
+ ],
+ [
+ [-10, -20],
+ [-30, -40],
+ [-50, -60],
+ ],
],
- [
- [-10, -20],
- [-30, -40],
- [-50, -60],
+ expected: [
+ [11, 22],
+ [33, 44],
+ [55, 66],
],
- ],
- expected: [
- [11, 22],
- [33, 44],
- [55, 66],
- ],
- },
- {
- input: [
- [
- [1, 2],
- [3, 4],
- [5, 6],
- [7, 8],
+ },
+ {
+ input: [
+ [
+ [1, 2],
+ [3, 4],
+ [5, 6],
+ [7, 8],
+ ],
+ [
+ [-10, -20],
+ [-30, -40],
+ [-50, -60],
+ [-70, -80],
+ ],
],
- [
- [-10, -20],
- [-30, -40],
- [-50, -60],
- [-70, -80],
+ expected: [
+ [11, 22],
+ [33, 44],
+ [55, 66],
+ [77, 88],
],
- ],
- expected: [
- [11, 22],
- [33, 44],
- [55, 66],
- [77, 88],
- ],
- },
- {
- input: [
- [
- [1, 2, 3],
- [4, 5, 6],
+ },
+ {
+ input: [
+ [
+ [1, 2, 3],
+ [4, 5, 6],
+ ],
+ [
+ [-10, -20, -30],
+ [-40, -50, -60],
+ ],
],
- [
- [-10, -20, -30],
- [-40, -50, -60],
+ expected: [
+ [11, 22, 33],
+ [44, 55, 66],
],
- ],
- expected: [
- [11, 22, 33],
- [44, 55, 66],
- ],
- },
- {
- input: [
- [
- [1, 2, 3],
- [4, 5, 6],
- [7, 8, 9],
+ },
+ {
+ input: [
+ [
+ [1, 2, 3],
+ [4, 5, 6],
+ [7, 8, 9],
+ ],
+ [
+ [-10, -20, -30],
+ [-40, -50, -60],
+ [-70, -80, -90],
+ ],
],
- [
- [-10, -20, -30],
- [-40, -50, -60],
- [-70, -80, -90],
+ expected: [
+ [11, 22, 33],
+ [44, 55, 66],
+ [77, 88, 99],
],
- ],
- expected: [
- [11, 22, 33],
- [44, 55, 66],
- [77, 88, 99],
- ],
- },
- {
- input: [
- [
- [1, 2, 3],
- [4, 5, 6],
- [7, 8, 9],
- [10, 11, 12],
+ },
+ {
+ input: [
+ [
+ [1, 2, 3],
+ [4, 5, 6],
+ [7, 8, 9],
+ [10, 11, 12],
+ ],
+ [
+ [-10, -20, -30],
+ [-40, -50, -60],
+ [-70, -80, -90],
+ [-1000, -1100, -1200],
+ ],
],
- [
- [-10, -20, -30],
- [-40, -50, -60],
- [-70, -80, -90],
- [-1000, -1100, -1200],
+ expected: [
+ [11, 22, 33],
+ [44, 55, 66],
+ [77, 88, 99],
+ [1010, 1111, 1212],
],
- ],
- expected: [
- [11, 22, 33],
- [44, 55, 66],
- [77, 88, 99],
- [1010, 1111, 1212],
- ],
- },
- {
- input: [
- [
- [1, 2, 3, 4],
- [5, 6, 7, 8],
+ },
+ {
+ input: [
+ [
+ [1, 2, 3, 4],
+ [5, 6, 7, 8],
+ ],
+ [
+ [-10, -20, -30, -40],
+ [-50, -60, -70, -80],
+ ],
],
- [
- [-10, -20, -30, -40],
- [-50, -60, -70, -80],
+ expected: [
+ [11, 22, 33, 44],
+ [55, 66, 77, 88],
],
- ],
- expected: [
- [11, 22, 33, 44],
- [55, 66, 77, 88],
- ],
- },
- {
- input: [
- [
- [1, 2, 3, 4],
- [5, 6, 7, 8],
- [9, 10, 11, 12],
+ },
+ {
+ input: [
+ [
+ [1, 2, 3, 4],
+ [5, 6, 7, 8],
+ [9, 10, 11, 12],
+ ],
+ [
+ [-10, -20, -30, -40],
+ [-50, -60, -70, -80],
+ [-90, -1000, -1100, -1200],
+ ],
],
- [
- [-10, -20, -30, -40],
- [-50, -60, -70, -80],
- [-90, -1000, -1100, -1200],
+ expected: [
+ [11, 22, 33, 44],
+ [55, 66, 77, 88],
+ [99, 1010, 1111, 1212],
],
- ],
- expected: [
- [11, 22, 33, 44],
- [55, 66, 77, 88],
- [99, 1010, 1111, 1212],
- ],
- },
- {
- input: [
- [
- [1, 2, 3, 4],
- [5, 6, 7, 8],
- [9, 10, 11, 12],
- [13, 14, 15, 16],
+ },
+ {
+ input: [
+ [
+ [1, 2, 3, 4],
+ [5, 6, 7, 8],
+ [9, 10, 11, 12],
+ [13, 14, 15, 16],
+ ],
+ [
+ [-10, -20, -30, -40],
+ [-50, -60, -70, -80],
+ [-90, -1000, -1100, -1200],
+ [-1300, -1400, -1500, -1600],
+ ],
],
- [
- [-10, -20, -30, -40],
- [-50, -60, -70, -80],
- [-90, -1000, -1100, -1200],
- [-1300, -1400, -1500, -1600],
+ expected: [
+ [11, 22, 33, 44],
+ [55, 66, 77, 88],
+ [99, 1010, 1111, 1212],
+ [1313, 1414, 1515, 1616],
],
- ],
- expected: [
- [11, 22, 33, 44],
- [55, 66, 77, 88],
- [99, 1010, 1111, 1212],
- [1313, 1414, 1515, 1616],
- ],
- },
- ])
+ },
+ // Test the OOB is handled component-wise
+ {
+ input: [
+ [
+ [constants.positive.max, 2],
+ [3, 4],
+ ],
+ [
+ [constants.negative.min, -20],
+ [-30, -40],
+ ],
+ ],
+ expected: [
+ [kUnboundedEndpoints, 22],
+ [33, 44],
+ ],
+ },
+ ];
+ })
)
.fn(t => {
const [x, y] = t.params.input;
@@ -7577,7 +7672,7 @@ g.test('multiplicationMatrixMatrixInterval')
interface MatrixScalarToMatrixCase {
matrix: number[][];
scalar: number;
- expected: (number | IntervalBounds)[][];
+ expected: (number | IntervalEndpoints)[][];
}
const kMultiplicationMatrixScalarIntervalCases = {
@@ -7609,14 +7704,30 @@ const kMultiplicationMatrixScalarIntervalCases = {
],
},
] as MatrixScalarToMatrixCase[],
+ abstract: [
+ // From https://github.com/gpuweb/cts/issues/3044
+ {
+ matrix: [
+ [kValue.f64.negative.min, 0],
+ [0, 0],
+ ],
+ scalar: kValue.f64.negative.subnormal.min,
+ expected: [
+ [[0, reinterpretU64AsF64(0x400ffffffffffffdn)], 0], // [[0, 3.9999995...], 0],
+ [0, 0],
+ ],
+ },
+ ] as MatrixScalarToMatrixCase[],
} as const;
g.test('multiplicationMatrixScalarInterval')
.params(u =>
u
- .combine('trait', ['f32', 'f16'] as const)
+ .combine('trait', ['f32', 'f16', 'abstract'] as const)
.beginSubcases()
.expandWithParams<MatrixScalarToMatrixCase>(p => {
+ const trait = FP[p.trait];
+ const constants = trait.constants();
// Primarily testing that different shapes of matrices are handled correctly
// here, to reduce test duplication. Additional testing for edge case
// discovered in https://github.com/gpuweb/cts/issues/3044.
@@ -7743,6 +7854,18 @@ g.test('multiplicationMatrixScalarInterval')
],
},
...kMultiplicationMatrixScalarIntervalCases[p.trait],
+ // Test that OOB is component-wise
+ {
+ matrix: [
+ [1, 2],
+ [constants.positive.max, 4],
+ ],
+ scalar: 10,
+ expected: [
+ [10, 20],
+ [kUnboundedEndpoints, 40],
+ ],
+ },
];
})
)
@@ -7766,7 +7889,7 @@ g.test('multiplicationMatrixScalarInterval')
interface MatrixVectorToVectorCase {
matrix: number[][];
vector: number[];
- expected: (number | IntervalBounds)[];
+ expected: (number | IntervalEndpoints)[];
}
g.test('multiplicationMatrixVectorInterval')
@@ -7883,7 +8006,7 @@ g.test('multiplicationMatrixVectorInterval')
interface VectorMatrixToVectorCase {
vector: number[];
matrix: number[][];
- expected: (number | IntervalBounds)[];
+ expected: (number | IntervalEndpoints)[];
}
g.test('multiplicationVectorMatrixInterval')
@@ -7897,8 +8020,8 @@ g.test('multiplicationVectorMatrixInterval')
// multiplicationVectorMatrixInterval uses DotIntervalOp for calculating
// intervals, so the testing for dotInterval covers the actual interval
// calculations.
- // Keep all expected result integer no larger than 2047 to ensure that all result is exactly
- // represeantable in both f32 and f16.
+ // Keep all expected result integer no larger than 2047 to ensure that
+ // all result is exactly representable in both f32 and f16.
{
vector: [1, 2],
matrix: [
@@ -8002,7 +8125,7 @@ g.test('multiplicationVectorMatrixInterval')
interface FaceForwardCase {
input: [number[], number[], number[]];
- expected: ((number | IntervalBounds)[] | undefined)[];
+ expected: ((number | IntervalEndpoints)[] | undefined)[];
}
g.test('faceForwardIntervals')
@@ -8081,8 +8204,8 @@ g.test('faceForwardIntervals')
interface ModfCase {
input: number;
- fract: number | IntervalBounds;
- whole: number | IntervalBounds;
+ fract: number | IntervalEndpoints;
+ whole: number | IntervalEndpoints;
}
g.test('modfInterval')
@@ -8135,18 +8258,18 @@ g.test('modfInterval')
interface RefractCase {
input: [number[], number[], number];
- expected: (number | IntervalBounds)[];
+ expected: (number | IntervalEndpoints)[];
}
// Scope for refractInterval tests so that they can have constants for magic
// numbers that don't pollute the global namespace or have unwieldy long names.
{
- const kNegativeOneBounds = {
+ const kNegativeOneEndpoints = {
f32: [
reinterpretU64AsF64(0xbff0_0000_c000_0000n),
reinterpretU64AsF64(0xbfef_ffff_4000_0000n),
- ] as IntervalBounds,
- f16: [reinterpretU16AsF16(0xbc06), reinterpretU16AsF16(0xbbfa)] as IntervalBounds,
+ ] as IntervalEndpoints,
+ f16: [reinterpretU16AsF16(0xbc06), reinterpretU16AsF16(0xbbfa)] as IntervalEndpoints,
} as const;
// prettier-ignore
@@ -8178,7 +8301,7 @@ interface RefractCase {
// vec4
// x = [1, -2, 3, -4], y = [-5, 6, -7, 8], z = 9,
// dot(y, x) = -71, k = 1.0 - 9 * 9 * (1.0 - 71 * 71) = 408241 overflow f16.
- { input: [[1, -2, 3, -4], [-5, 6, -7, 8], 9], expected: [kUnboundedBounds, kUnboundedBounds, kUnboundedBounds, kUnboundedBounds] },
+ { input: [[1, -2, 3, -4], [-5, 6, -7, 8], 9], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
// x = [1, -2, 3, -4], y = [-5, 4, -3, 2], z = 2.5,
// dot(y, x) = -30, k = 1.0 - 2.5 * 2.5 * (1.0 - 30 * 30) = 5619.75.
// a = z * dot(y, x) + sqrt(k) = ~-0.035, result is about z * x - a * y = [~2.325, ~-4.86, ~7.4025, ~-9.93]
@@ -8205,23 +8328,23 @@ interface RefractCase {
{ input: [[1, 1], [0.1, 0], 10], expected: [0, 0] },
// k contains 0
- { input: [[1, 1], [0.1, 0], 1.005038], expected: [kUnboundedBounds, kUnboundedBounds] },
+ { input: [[1, 1], [0.1, 0], 1.005038], expected: [kUnboundedEndpoints, kUnboundedEndpoints] },
// k > 0
// vec2
- { input: [[1, 1], [1, 0], 1], expected: [kNegativeOneBounds[p.trait], 1] },
+ { input: [[1, 1], [1, 0], 1], expected: [kNegativeOneEndpoints[p.trait], 1] },
// vec3
- { input: [[1, 1, 1], [1, 0, 0], 1], expected: [kNegativeOneBounds[p.trait], 1, 1] },
+ { input: [[1, 1, 1], [1, 0, 0], 1], expected: [kNegativeOneEndpoints[p.trait], 1, 1] },
// vec4
- { input: [[1, 1, 1, 1], [1, 0, 0, 0], 1], expected: [kNegativeOneBounds[p.trait], 1, 1, 1] },
-
- // Test that dot going OOB bounds in the intermediate calculations propagates
- { input: [[constants.positive.nearest_max, constants.positive.max, constants.negative.min], [1.0, 1.0, 1.0], 1], expected: [kUnboundedBounds, kUnboundedBounds, kUnboundedBounds] },
- { input: [[constants.positive.nearest_max, constants.negative.min, constants.positive.max], [1.0, 1.0, 1.0], 1], expected: [kUnboundedBounds, kUnboundedBounds, kUnboundedBounds] },
- { input: [[constants.positive.max, constants.positive.nearest_max, constants.negative.min], [1.0, 1.0, 1.0], 1], expected: [kUnboundedBounds, kUnboundedBounds, kUnboundedBounds] },
- { input: [[constants.negative.min, constants.positive.nearest_max, constants.positive.max], [1.0, 1.0, 1.0], 1], expected: [kUnboundedBounds, kUnboundedBounds, kUnboundedBounds] },
- { input: [[constants.positive.max, constants.negative.min, constants.positive.nearest_max], [1.0, 1.0, 1.0], 1], expected: [kUnboundedBounds, kUnboundedBounds, kUnboundedBounds] },
- { input: [[constants.negative.min, constants.positive.max, constants.positive.nearest_max], [1.0, 1.0, 1.0], 1], expected: [kUnboundedBounds, kUnboundedBounds, kUnboundedBounds] },
+ { input: [[1, 1, 1, 1], [1, 0, 0, 0], 1], expected: [kNegativeOneEndpoints[p.trait], 1, 1, 1] },
+
+ // Test that dot going OOB in the intermediate calculations propagates
+ { input: [[constants.positive.nearest_max, constants.positive.max, constants.negative.min], [1.0, 1.0, 1.0], 1], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
+ { input: [[constants.positive.nearest_max, constants.negative.min, constants.positive.max], [1.0, 1.0, 1.0], 1], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
+ { input: [[constants.positive.max, constants.positive.nearest_max, constants.negative.min], [1.0, 1.0, 1.0], 1], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
+ { input: [[constants.negative.min, constants.positive.nearest_max, constants.positive.max], [1.0, 1.0, 1.0], 1], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
+ { input: [[constants.positive.max, constants.negative.min, constants.positive.nearest_max], [1.0, 1.0, 1.0], 1], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
+ { input: [[constants.negative.min, constants.positive.max, constants.positive.nearest_max], [1.0, 1.0, 1.0], 1], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
];
})
)
diff --git a/dom/webgpu/tests/cts/checkout/src/unittests/maths.spec.ts b/dom/webgpu/tests/cts/checkout/src/unittests/maths.spec.ts
index 357c574281..d84299d993 100644
--- a/dom/webgpu/tests/cts/checkout/src/unittests/maths.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/unittests/maths.spec.ts
@@ -22,8 +22,8 @@ import {
correctlyRoundedF32,
FlushMode,
frexp,
- fullF16Range,
- fullF32Range,
+ scalarF16Range,
+ scalarF32Range,
fullI32Range,
lerp,
linearRange,
@@ -36,6 +36,7 @@ import {
oneULPF64,
lerpBigInt,
linearRangeBigInt,
+ biasedRangeBigInt,
} from '../webgpu/util/math.js';
import {
reinterpretU16AsF16,
@@ -1525,6 +1526,41 @@ g.test('linearRangeBigInt')
);
});
+g.test('biasedRangeBigInt')
+ .paramsSimple<rangeBigIntCase>(
+ // prettier-ignore
+ [
+ { a: 0n, b: 0n, num_steps: 10, result: new Array<bigint>(10).fill(0n) },
+ { a: 10n, b: 10n, num_steps: 10, result: new Array<bigint>(10).fill(10n) },
+ { a: 0n, b: 10n, num_steps: 1, result: [0n] },
+ { a: 10n, b: 0n, num_steps: 1, result: [10n] },
+ { a: 0n, b: 10n, num_steps: 11, result: [0n, 0n, 0n, 0n, 1n, 2n, 3n, 4n, 6n, 8n, 10n] },
+ { a: 10n, b: 0n, num_steps: 11, result: [10n, 10n, 10n, 10n, 9n, 8n, 7n, 6n, 4n, 2n, 0n] },
+ { a: 0n, b: 1000n, num_steps: 11, result: [0n, 9n, 39n, 89n, 159n, 249n, 359n, 489n, 639n, 809n, 1000n] },
+ { a: 1000n, b: 0n, num_steps: 11, result: [1000n, 991n, 961n, 911n, 841n, 751n, 641n, 511n, 361n, 191n, 0n] },
+ { a: 1n, b: 5n, num_steps: 5, result: [1n, 1n, 2n, 3n, 5n] },
+ { a: 5n, b: 1n, num_steps: 5, result: [5n, 5n, 4n, 3n, 1n] },
+ { a: 0n, b: 10n, num_steps: 5, result: [0n, 0n, 2n, 5n, 10n] },
+ { a: 10n, b: 0n, num_steps: 5, result: [10n, 10n, 8n, 5n, 0n] },
+ { a: -10n, b: 10n, num_steps: 11, result: [-10n, -10n, -10n, -10n, -8n, -6n, -4n, -2n, 2n, 6n, 10n] },
+ { a: 10n, b: -10n, num_steps: 11, result: [10n, 10n, 10n, 10n, 8n, 6n, 4n, 2n, -2n, -6n, -10n] },
+ { a: -10n, b: 0n, num_steps: 11, result: [-10n, -10n, -10n, -10n, -9n, -8n, -7n, -6n, -4n, -2n, -0n] },
+ { a: 0n, b: -10n, num_steps: 11, result: [0n, 0n, 0n, 0n, -1n, -2n, -3n, -4n, -6n, -8n, -10n] },
+ ]
+ )
+ .fn(test => {
+ const a = test.params.a;
+ const b = test.params.b;
+ const num_steps = test.params.num_steps;
+ const got = biasedRangeBigInt(a, b, num_steps);
+ const expect = test.params.result;
+
+ test.expect(
+ objectEquals(got, expect),
+ `biasedRangeBigInt(${a}, ${b}, ${num_steps}) returned ${got}. Expected ${expect}`
+ );
+ });
+
interface fullF32RangeCase {
neg_norm: number;
neg_sub: number;
@@ -1557,7 +1593,7 @@ g.test('fullF32Range')
const neg_sub = test.params.neg_sub;
const pos_sub = test.params.pos_sub;
const pos_norm = test.params.pos_norm;
- const got = fullF32Range({ neg_norm, neg_sub, pos_sub, pos_norm });
+ const got = scalarF32Range({ neg_norm, neg_sub, pos_sub, pos_norm });
const expect = test.params.expect;
test.expect(
@@ -1598,7 +1634,7 @@ g.test('fullF16Range')
const neg_sub = test.params.neg_sub;
const pos_sub = test.params.pos_sub;
const pos_norm = test.params.pos_norm;
- const got = fullF16Range({ neg_norm, neg_sub, pos_sub, pos_norm });
+ const got = scalarF16Range({ neg_norm, neg_sub, pos_sub, pos_norm });
const expect = test.params.expect;
test.expect(
diff --git a/dom/webgpu/tests/cts/checkout/src/unittests/parse_imports.spec.ts b/dom/webgpu/tests/cts/checkout/src/unittests/parse_imports.spec.ts
new file mode 100644
index 0000000000..0efdc0d171
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/unittests/parse_imports.spec.ts
@@ -0,0 +1,79 @@
+export const description = `
+Test for "parseImports" utility.
+`;
+
+import { makeTestGroup } from '../common/framework/test_group.js';
+import { parseImports } from '../common/util/parse_imports.js';
+
+import { UnitTest } from './unit_test.js';
+
+class F extends UnitTest {
+ test(content: string, expect: string[]): void {
+ const got = parseImports('a/b/c.js', content);
+ const expectJoined = expect.join('\n');
+ const gotJoined = got.join('\n');
+ this.expect(
+ expectJoined === gotJoined,
+ `
+expected: ${expectJoined}
+got: ${gotJoined}`
+ );
+ }
+}
+
+export const g = makeTestGroup(F);
+
+g.test('empty').fn(t => {
+ t.test(``, []);
+ t.test(`\n`, []);
+ t.test(`\n\n`, []);
+});
+
+g.test('simple').fn(t => {
+ t.test(`import 'x/y/z.js';`, ['a/b/x/y/z.js']);
+ t.test(`import * as blah from 'x/y/z.js';`, ['a/b/x/y/z.js']);
+ t.test(`import { blah } from 'x/y/z.js';`, ['a/b/x/y/z.js']);
+});
+
+g.test('multiple').fn(t => {
+ t.test(
+ `
+blah blah blah
+import 'x/y/z.js';
+more blah
+import * as blah from 'm/n/o.js';
+extra blah
+import { blah } from '../h.js';
+ending with blah
+`,
+ ['a/b/x/y/z.js', 'a/b/m/n/o.js', 'a/h.js']
+ );
+});
+
+g.test('multiline').fn(t => {
+ t.test(
+ `import {
+ blah
+} from 'x/y/z.js';`,
+ ['a/b/x/y/z.js']
+ );
+ t.test(
+ `import {
+ blahA,
+ blahB,
+} from 'x/y/z.js';`,
+ ['a/b/x/y/z.js']
+ );
+});
+
+g.test('file_characters').fn(t => {
+ t.test(`import '01234_56789.js';`, ['a/b/01234_56789.js']);
+});
+
+g.test('relative_paths').fn(t => {
+ t.test(`import '../x.js';`, ['a/x.js']);
+ t.test(`import '../x/y.js';`, ['a/x/y.js']);
+ t.test(`import '../../x.js';`, ['x.js']);
+ t.test(`import '../../../x.js';`, ['../x.js']);
+ t.test(`import '../../../../x.js';`, ['../../x.js']);
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/unittests/serialization.spec.ts b/dom/webgpu/tests/cts/checkout/src/unittests/serialization.spec.ts
index 9717ba3ecf..ea6ed5e42f 100644
--- a/dom/webgpu/tests/cts/checkout/src/unittests/serialization.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/unittests/serialization.spec.ts
@@ -16,6 +16,8 @@ import {
} from '../webgpu/util/compare.js';
import { kValue } from '../webgpu/util/constants.js';
import {
+ abstractFloat,
+ abstractInt,
bool,
deserializeValue,
f16,
@@ -61,6 +63,18 @@ g.test('value').fn(t => {
u8(kValue.u8.max - 1),
u8(kValue.u8.max - 0),
+ abstractInt(kValue.i64.negative.min),
+ abstractInt(kValue.i64.negative.min + 1n),
+ abstractInt(kValue.i64.negative.min + 2n),
+ abstractInt(kValue.i64.negative.max - 2n),
+ abstractInt(kValue.i64.negative.max - 1n),
+ abstractInt(kValue.i64.positive.min),
+ abstractInt(kValue.i64.positive.min + 1n),
+ abstractInt(kValue.i64.positive.min + 2n),
+ abstractInt(kValue.i64.positive.max - 2n),
+ abstractInt(kValue.i64.positive.max - 1n),
+ abstractInt(kValue.i64.positive.max),
+
i32(kValue.i32.negative.min + 0),
i32(kValue.i32.negative.min + 1),
i32(kValue.i32.negative.min + 2),
@@ -97,6 +111,21 @@ g.test('value').fn(t => {
i8(kValue.i8.positive.max - 1),
i8(kValue.i8.positive.max - 0),
+ abstractFloat(0),
+ abstractFloat(-0),
+ abstractFloat(1),
+ abstractFloat(-1),
+ abstractFloat(0.5),
+ abstractFloat(-0.5),
+ abstractFloat(kValue.f64.positive.max),
+ abstractFloat(kValue.f64.positive.min),
+ abstractFloat(kValue.f64.positive.subnormal.max),
+ abstractFloat(kValue.f64.positive.subnormal.min),
+ abstractFloat(kValue.f64.negative.subnormal.max),
+ abstractFloat(kValue.f64.negative.subnormal.min),
+ abstractFloat(kValue.f64.positive.infinity),
+ abstractFloat(kValue.f64.negative.infinity),
+
f32(0),
f32(-0),
f32(1),
@@ -139,6 +168,13 @@ g.test('value').fn(t => {
[0.0, 1.0],
[2.0, 3.0],
],
+ abstractFloat
+ ),
+ toMatrix(
+ [
+ [0.0, 1.0],
+ [2.0, 3.0],
+ ],
f32
),
toMatrix(
@@ -153,6 +189,13 @@ g.test('value').fn(t => {
[0.0, 1.0, 2.0, 3.0],
[4.0, 5.0, 6.0, 7.0],
],
+ abstractFloat
+ ),
+ toMatrix(
+ [
+ [0.0, 1.0, 2.0, 3.0],
+ [4.0, 5.0, 6.0, 7.0],
+ ],
f32
),
toMatrix(
@@ -169,6 +212,14 @@ g.test('value').fn(t => {
[3.0, 4.0, 5.0],
[6.0, 7.0, 8.0],
],
+ abstractFloat
+ ),
+ toMatrix(
+ [
+ [0.0, 1.0, 2.0],
+ [3.0, 4.0, 5.0],
+ [6.0, 7.0, 8.0],
+ ],
f32
),
toMatrix(
@@ -186,6 +237,15 @@ g.test('value').fn(t => {
[4.0, 5.0],
[6.0, 7.0],
],
+ abstractFloat
+ ),
+ toMatrix(
+ [
+ [0.0, 1.0],
+ [2.0, 3.0],
+ [4.0, 5.0],
+ [6.0, 7.0],
+ ],
f32
),
toMatrix(
@@ -204,6 +264,15 @@ g.test('value').fn(t => {
[8.0, 9.0, 10.0, 11.0],
[12.0, 13.0, 14.0, 15.0],
],
+ abstractFloat
+ ),
+ toMatrix(
+ [
+ [0.0, 1.0, 2.0, 3.0],
+ [4.0, 5.0, 6.0, 7.0],
+ [8.0, 9.0, 10.0, 11.0],
+ [12.0, 13.0, 14.0, 15.0],
+ ],
f32
),
]) {
diff --git a/dom/webgpu/tests/cts/checkout/src/unittests/texture_ok.spec.ts b/dom/webgpu/tests/cts/checkout/src/unittests/texture_ok.spec.ts
index f1e6971a74..c126832b8d 100644
--- a/dom/webgpu/tests/cts/checkout/src/unittests/texture_ok.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/unittests/texture_ok.spec.ts
@@ -4,7 +4,6 @@ Test for texture_ok utils.
import { makeTestGroup } from '../common/framework/test_group.js';
import { typedArrayFromParam, typedArrayParam } from '../common/util/util.js';
-import { RegularTextureFormat } from '../webgpu/format_info.js';
import { TexelView } from '../webgpu/util/texture/texel_view.js';
import { findFailedPixels } from '../webgpu/util/texture/texture_ok.js';
@@ -30,103 +29,103 @@ g.test('findFailedPixels')
u.combineWithParams([
// Sanity Check
{
- format: 'rgba8unorm' as RegularTextureFormat,
+ format: 'rgba8unorm',
actual: typedArrayParam('Uint8Array', [0x00, 0x40, 0x80, 0xff]),
expected: typedArrayParam('Uint8Array', [0x00, 0x40, 0x80, 0xff]),
isSame: true,
},
// Slightly different values
{
- format: 'rgba8unorm' as RegularTextureFormat,
+ format: 'rgba8unorm',
actual: typedArrayParam('Uint8Array', [0x00, 0x40, 0x80, 0xff]),
expected: typedArrayParam('Uint8Array', [0x00, 0x40, 0x81, 0xff]),
isSame: false,
},
// Different representations of the same value
{
- format: 'rgb9e5ufloat' as RegularTextureFormat,
+ format: 'rgb9e5ufloat',
actual: typedArrayParam('Uint8Array', [0x78, 0x56, 0x34, 0x12]),
expected: typedArrayParam('Uint8Array', [0xf0, 0xac, 0x68, 0x0c]),
isSame: true,
},
// Slightly different values
{
- format: 'rgb9e5ufloat' as RegularTextureFormat,
+ format: 'rgb9e5ufloat',
actual: typedArrayParam('Uint8Array', [0x78, 0x56, 0x34, 0x12]),
expected: typedArrayParam('Uint8Array', [0xf1, 0xac, 0x68, 0x0c]),
isSame: false,
},
// Test NaN === NaN
{
- format: 'r32float' as RegularTextureFormat,
+ format: 'r32float',
actual: typedArrayParam('Float32Array', [parseFloat('abc')]),
expected: typedArrayParam('Float32Array', [parseFloat('def')]),
isSame: true,
},
// Sanity Check
{
- format: 'r32float' as RegularTextureFormat,
+ format: 'r32float',
actual: typedArrayParam('Float32Array', [1.23]),
expected: typedArrayParam('Float32Array', [1.23]),
isSame: true,
},
// Slightly different values.
{
- format: 'r32float' as RegularTextureFormat,
+ format: 'r32float',
actual: typedArrayParam('Uint32Array', [0x3f9d70a4]),
expected: typedArrayParam('Uint32Array', [0x3f9d70a5]),
isSame: false,
},
// Slightly different
{
- format: 'rg11b10ufloat' as RegularTextureFormat,
+ format: 'rg11b10ufloat',
actual: typedArrayParam('Uint32Array', [0x3ce]),
expected: typedArrayParam('Uint32Array', [0x3cf]),
isSame: false,
},
// Positive.Infinity === Positive.Infinity (red)
{
- format: 'rg11b10ufloat' as RegularTextureFormat,
+ format: 'rg11b10ufloat',
actual: typedArrayParam('Uint32Array', [0b11111000000]),
expected: typedArrayParam('Uint32Array', [0b11111000000]),
isSame: true,
},
// Positive.Infinity === Positive.Infinity (green)
{
- format: 'rg11b10ufloat' as RegularTextureFormat,
+ format: 'rg11b10ufloat',
actual: typedArrayParam('Uint32Array', [0b11111000000_00000000000]),
expected: typedArrayParam('Uint32Array', [0b11111000000_00000000000]),
isSame: true,
},
// Positive.Infinity === Positive.Infinity (blue)
{
- format: 'rg11b10ufloat' as RegularTextureFormat,
+ format: 'rg11b10ufloat',
actual: typedArrayParam('Uint32Array', [0b1111100000_00000000000_00000000000]),
expected: typedArrayParam('Uint32Array', [0b1111100000_00000000000_00000000000]),
isSame: true,
},
// NaN === NaN (red)
{
- format: 'rg11b10ufloat' as RegularTextureFormat,
+ format: 'rg11b10ufloat',
actual: typedArrayParam('Uint32Array', [0b11111000001]),
expected: typedArrayParam('Uint32Array', [0b11111000010]),
isSame: true,
},
// NaN === NaN (green)
{
- format: 'rg11b10ufloat' as RegularTextureFormat,
+ format: 'rg11b10ufloat',
actual: typedArrayParam('Uint32Array', [0b11111000100_00000000000]),
expected: typedArrayParam('Uint32Array', [0b11111001000_00000000000]),
isSame: true,
},
// NaN === NaN (blue)
{
- format: 'rg11b10ufloat' as RegularTextureFormat,
+ format: 'rg11b10ufloat',
actual: typedArrayParam('Uint32Array', [0b1111110000_00000000000_00000000000]),
expected: typedArrayParam('Uint32Array', [0b1111101000_00000000000_00000000000]),
isSame: true,
},
- ])
+ ] as const)
)
.fn(t => {
const { format, actual, expected, isSame } = t.params;
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/adapter/requestDevice.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/adapter/requestDevice.spec.ts
index 314da6356e..51ab2303eb 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/adapter/requestDevice.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/adapter/requestDevice.spec.ts
@@ -311,6 +311,65 @@ g.test('limit,better_than_supported')
t.shouldReject('OperationError', adapter.requestDevice({ requiredLimits }));
});
+g.test('limit,out_of_range')
+ .desc(
+ `
+ Test that specifying limits that are out of range (<0, >MAX_SAFE_INTEGER, >2**31-2 for 32-bit
+ limits, =0 for alignment limits) produce the appropriate error (TypeError or OperationError).
+ `
+ )
+ .params(u =>
+ u
+ .combine('limit', kLimits)
+ .beginSubcases()
+ .expand('value', function* () {
+ yield -(2 ** 64);
+ yield Number.MIN_SAFE_INTEGER - 3;
+ yield Number.MIN_SAFE_INTEGER - 1;
+ yield Number.MIN_SAFE_INTEGER;
+ yield -(2 ** 32);
+ yield -1;
+ yield 0;
+ yield 2 ** 32 - 2;
+ yield 2 ** 32 - 1;
+ yield 2 ** 32;
+ yield 2 ** 32 + 1;
+ yield 2 ** 32 + 2;
+ yield Number.MAX_SAFE_INTEGER;
+ yield Number.MAX_SAFE_INTEGER + 1;
+ yield Number.MAX_SAFE_INTEGER + 3;
+ yield 2 ** 64;
+ yield Number.MAX_VALUE;
+ })
+ )
+ .fn(async t => {
+ const { limit, value } = t.params;
+
+ const gpu = getGPU(t.rec);
+ const adapter = await gpu.requestAdapter();
+ assert(adapter !== null);
+ const limitInfo = getDefaultLimitsForAdapter(adapter)[limit];
+
+ const requiredLimits = {
+ [limit]: value,
+ };
+
+ const errorName =
+ value < 0 || value > Number.MAX_SAFE_INTEGER
+ ? 'TypeError'
+ : limitInfo.class === 'maximum' && value > adapter.limits[limit]
+ ? 'OperationError'
+ : limitInfo.class === 'alignment' && (value > 2 ** 31 || !isPowerOfTwo(value))
+ ? 'OperationError'
+ : false;
+
+ if (errorName) {
+ t.shouldReject(errorName, adapter.requestDevice({ requiredLimits }));
+ } else {
+ await adapter.requestDevice({ requiredLimits });
+ }
+ });
+
g.test('limit,worse_than_default')
.desc(
`
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/command_buffer/copyTextureToTexture.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/command_buffer/copyTextureToTexture.spec.ts
index 4c55b5162f..edf08c3840 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/command_buffer/copyTextureToTexture.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/command_buffer/copyTextureToTexture.spec.ts
@@ -1,7 +1,7 @@
export const description = `copyTextureToTexture operation tests`;
import { makeTestGroup } from '../../../../common/framework/test_group.js';
-import { assert, memcpy, unreachable } from '../../../../common/util/util.js';
+import { assert, ErrorWithExtra, memcpy } from '../../../../common/util/util.js';
import {
kBufferSizeAlignment,
kMinDynamicBufferOffsetAlignment,
@@ -18,14 +18,18 @@ import {
ColorTextureFormat,
isCompressedTextureFormat,
viewCompatible,
+ RegularTextureFormat,
+ isRegularTextureFormat,
} from '../../../format_info.js';
import { GPUTest, TextureTestMixin } from '../../../gpu_test.js';
import { makeBufferWithContents } from '../../../util/buffer.js';
-import { checkElementsEqual, checkElementsEqualEither } from '../../../util/check_contents.js';
+import { checkElementsEqual } from '../../../util/check_contents.js';
import { align } from '../../../util/math.js';
import { physicalMipSize } from '../../../util/texture/base.js';
import { DataArrayGenerator } from '../../../util/texture/data_generation.js';
import { kBytesPerRowAlignment, dataBytesForCopyOrFail } from '../../../util/texture/layout.js';
+import { TexelView } from '../../../util/texture/texel_view.js';
+import { findFailedPixels } from '../../../util/texture/texture_ok.js';
const dataGenerator = new DataArrayGenerator();
@@ -207,7 +211,7 @@ class F extends TextureTestMixin(GPUTest) {
align(dstBlocksPerRow * bytesPerBlock, 4);
if (isCompressedTextureFormat(dstTexture.format) && this.isCompatibility) {
- assert(viewCompatible(srcFormat, dstFormat));
+ assert(viewCompatible(this.isCompatibility, srcFormat, dstFormat));
// compare by rendering. We need the expected texture to match
// the dstTexture so we'll create a texture where we supply
// all of the data in JavaScript.
@@ -304,6 +308,7 @@ class F extends TextureTestMixin(GPUTest) {
y: appliedDstOffset.y / blockHeight,
z: appliedDstOffset.z,
};
+ const bytesInRow = appliedCopyBlocksPerRow * bytesPerBlock;
for (let z = 0; z < appliedCopyDepth; ++z) {
const srcOffsetZ = srcCopyOffsetInBlocks.z + z;
@@ -321,7 +326,6 @@ class F extends TextureTestMixin(GPUTest) {
(srcBlockRowsPerImage * srcOffsetZ + srcOffsetYInBlocks) +
srcCopyOffsetInBlocks.x * bytesPerBlock;
- const bytesInRow = appliedCopyBlocksPerRow * bytesPerBlock;
memcpy(
{ src: expectedUint8Data, start: expectedDataOffset, length: bytesInRow },
{ dst: expectedUint8DataWithPadding, start: expectedDataWithPaddingOffset }
@@ -329,46 +333,69 @@ class F extends TextureTestMixin(GPUTest) {
}
}
- let alternateExpectedData = expectedUint8DataWithPadding;
- // For 8-byte snorm formats, allow an alternative encoding of -1.
- // MAINTENANCE_TODO: Use textureContentIsOKByT2B with TexelView.
- if (srcFormat.includes('snorm')) {
- switch (srcFormat) {
- case 'r8snorm':
- case 'rg8snorm':
- case 'rgba8snorm':
- alternateExpectedData = alternateExpectedData.slice();
- for (let i = 0; i < alternateExpectedData.length; ++i) {
- if (alternateExpectedData[i] === 128) {
- alternateExpectedData[i] = 129;
- } else if (alternateExpectedData[i] === 129) {
- alternateExpectedData[i] = 128;
- }
- }
- break;
- case 'bc4-r-snorm':
- case 'bc5-rg-snorm':
- case 'eac-r11snorm':
- case 'eac-rg11snorm':
- break;
- default:
- unreachable();
- }
+ if (isCompressedTextureFormat(dstFormat)) {
+ this.expectGPUBufferValuesPassCheck(
+ dstBuffer,
+ vals => checkElementsEqual(vals, expectedUint8DataWithPadding),
+ {
+ srcByteOffset: 0,
+ type: Uint8Array,
+ typedLength: expectedUint8DataWithPadding.length,
+ }
+ );
+ return;
}
+ assert(isRegularTextureFormat(dstFormat));
+ const regularDstFormat = dstFormat as RegularTextureFormat;
+
// Verify the content of the whole subresource of dstTexture at dstCopyLevel (in dstBuffer) is expected.
- this.expectGPUBufferValuesPassCheck(
- dstBuffer,
- alternateExpectedData === expectedUint8DataWithPadding
- ? vals => checkElementsEqual(vals, expectedUint8DataWithPadding)
- : vals =>
- checkElementsEqualEither(vals, [expectedUint8DataWithPadding, alternateExpectedData]),
- {
- srcByteOffset: 0,
- type: Uint8Array,
- typedLength: expectedUint8DataWithPadding.length,
+ const checkByTextureFormat = (actual: Uint8Array) => {
+ const zero = { x: 0, y: 0, z: 0 };
+
+ const actTexelView = TexelView.fromTextureDataByReference(regularDstFormat, actual, {
+ bytesPerRow: bytesInRow,
+ rowsPerImage: dstBlockRowsPerImage,
+ subrectOrigin: zero,
+ subrectSize: dstTextureSizeAtLevel,
+ });
+ const expTexelView = TexelView.fromTextureDataByReference(
+ regularDstFormat,
+ expectedUint8DataWithPadding,
+ {
+ bytesPerRow: bytesInRow,
+ rowsPerImage: dstBlockRowsPerImage,
+ subrectOrigin: zero,
+ subrectSize: dstTextureSizeAtLevel,
+ }
+ );
+
+ const failedPixelsMessage = findFailedPixels(
+ regularDstFormat,
+ zero,
+ dstTextureSizeAtLevel,
+ { actTexelView, expTexelView },
+ {
+ maxFractionalDiff: 0,
+ }
+ );
+
+ if (failedPixelsMessage !== undefined) {
+ const msg = 'Texture level had unexpected contents:\n' + failedPixelsMessage;
+ return new ErrorWithExtra(msg, () => ({
+ expTexelView,
+ actTexelView,
+ }));
}
- );
+
+ return undefined;
+ };
+
+ this.expectGPUBufferValuesPassCheck(dstBuffer, checkByTextureFormat, {
+ srcByteOffset: 0,
+ type: Uint8Array,
+ typedLength: expectedUint8DataWithPadding.length,
+ });
}
InitializeStencilAspect(
@@ -1349,6 +1376,9 @@ g.test('copy_multisampled_color')
texture can only be 1.
`
)
+ .beforeAllSubcases(t => {
+ t.skipIf(t.isCompatibility, 'multisample textures are not copyable in compatibility mode');
+ })
.fn(t => {
const textureSize = [32, 16, 1] as const;
const kColorFormat = 'rgba8unorm';
@@ -1537,6 +1567,9 @@ g.test('copy_multisampled_depth')
texture can only be 1.
`
)
+ .beforeAllSubcases(t => {
+ t.skipIf(t.isCompatibility, 'multisample textures are not copyable in compatibility mode');
+ })
.fn(t => {
const textureSize = [32, 16, 1] as const;
const kDepthFormat = 'depth24plus';
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/command_buffer/image_copy.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/command_buffer/image_copy.spec.ts
index 4eebc3d611..cff2bd50d5 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/command_buffer/image_copy.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/command_buffer/image_copy.spec.ts
@@ -23,8 +23,9 @@ export const description = `writeTexture + copyBufferToTexture + copyTextureToBu
* copy_with_no_image_or_slice_padding_and_undefined_values: test that when copying a single row we can set any bytesPerRow value and when copying a single\
slice we can set rowsPerImage to 0. Also test setting offset, rowsPerImage, mipLevel, origin, origin.{x,y,z} to undefined.
+Note: more coverage of memory synchronization for different read and write texture methods are in same_subresource.spec.ts.
+
* TODO:
- - add another initMethod which renders the texture [3]
- test copyT2B with buffer size not divisible by 4 (not done because expectContents 4-byte alignment)
- Convert the float32 values in initialData into the ones compatible to the depth aspect of
depthFormats when depth16unorm is supported by the browsers in
@@ -86,7 +87,7 @@ type InitMethod = 'WriteTexture' | 'CopyB2T';
* - PartialCopyT2B: do CopyT2B to check that the part of the texture we copied to with InitMethod
* matches the data we were copying and that we don't overwrite any data in the target buffer that
* we're not supposed to - that's primarily for testing CopyT2B functionality.
- * - FullCopyT2B: do CopyT2B on the whole texture and check wether the part we copied to matches
+ * - FullCopyT2B: do CopyT2B on the whole texture and check whether the part we copied to matches
* the data we were copying and that the nothing else was modified - that's primarily for testing
* WriteTexture and CopyB2T.
*
@@ -1132,6 +1133,10 @@ class ImageCopyTest extends TextureTestMixin(GPUTest) {
copySize
);
+ const use2DArray = this.isCompatibility && inputTexture.depthOrArrayLayers > 1;
+ const [textureType, layerCode] = use2DArray
+ ? ['texture_2d_array', ', baseArrayLayer']
+ : ['texture_2d', ''];
const renderPipeline = this.device.createRenderPipeline({
layout: 'auto',
vertex: {
@@ -1154,10 +1159,11 @@ class ImageCopyTest extends TextureTestMixin(GPUTest) {
fragment: {
module: this.device.createShaderModule({
code: `
- @group(0) @binding(0) var inputTexture: texture_2d<f32>;
+ @group(0) @binding(0) var inputTexture: ${textureType}<f32>;
+ @group(0) @binding(1) var<uniform> baseArrayLayer: u32;
@fragment fn main(@builtin(position) fragcoord : vec4<f32>) ->
@builtin(frag_depth) f32 {
- var depthValue : vec4<f32> = textureLoad(inputTexture, vec2<i32>(fragcoord.xy), 0);
+ var depthValue : vec4<f32> = textureLoad(inputTexture, vec2<i32>(fragcoord.xy)${layerCode}, 0);
return depthValue.x;
}`,
}),
@@ -1200,19 +1206,26 @@ class ImageCopyTest extends TextureTestMixin(GPUTest) {
});
renderPass.setPipeline(renderPipeline);
+ const uniformBufferEntry = use2DArray
+ ? [this.createUniformBufferAndBindGroupEntryForBaseArrayLayer(z)]
+ : [];
+
const bindGroup = this.device.createBindGroup({
layout: renderPipeline.getBindGroupLayout(0),
entries: [
{
binding: 0,
resource: inputTexture.createView({
- dimension: '2d',
- baseArrayLayer: z,
- arrayLayerCount: 1,
+ dimension: use2DArray ? '2d-array' : '2d',
+ ...(!use2DArray && {
+ baseArrayLayer: z,
+ arrayLayerCount: 1,
+ }),
baseMipLevel: 0,
mipLevelCount: 1,
}),
},
+ ...uniformBufferEntry,
],
});
renderPass.setBindGroup(0, bindGroup);
@@ -1223,6 +1236,23 @@ class ImageCopyTest extends TextureTestMixin(GPUTest) {
this.queue.submit([encoder.finish()]);
}
+ createUniformBufferAndBindGroupEntryForBaseArrayLayer(z: number) {
+ const buffer = this.device.createBuffer({
+ usage: GPUBufferUsage.UNIFORM,
+ size: 4,
+ mappedAtCreation: true,
+ });
+ this.trackForCleanup(buffer);
+ new Uint32Array(buffer.getMappedRange()).set([z]);
+ buffer.unmap();
+ return {
+ binding: 1,
+ resource: {
+ buffer,
+ },
+ };
+ }
+
DoCopyTextureToBufferWithDepthAspectTest(
format: DepthStencilFormat,
copySize: readonly [number, number, number],
@@ -1328,8 +1358,6 @@ class ImageCopyTest extends TextureTestMixin(GPUTest) {
/**
* This is a helper function used for filtering test parameters
- *
- * [3]: Modify this after introducing tests with rendering.
*/
function formatCanBeTested({ format }: { format: ColorTextureFormat }): boolean {
return kTextureFormatInfo[format].color.copyDst && kTextureFormatInfo[format].color.copySrc;
@@ -1491,6 +1519,12 @@ works for every format with 2d and 2d-array textures.
offset + bytesInCopyExtentPerRow { ==, > } bytesPerRow
offset > bytesInACompleteCopyImage
+ Covers spceial cases for OpenGL Compat:
+ offset % 4 > 0 while:
+ - padding bytes at end of each row/layer: bytesPerRow % 256 > 0 || rowsPerImage > copyDepth
+ - rows/layers are compact: bytesPerRow % 256 == 0 && rowsPerImage == copyDepth
+ - padding bytes at front and end of the same 4-byte word: format == 'r8snorm' && copyWidth <= 2
+
TODO: Cover the special code paths for 3D textures in D3D12.
TODO: Make a variant for depth-stencil formats.
`
@@ -1505,6 +1539,18 @@ works for every format with 2d and 2d-array textures.
.beginSubcases()
.combineWithParams(kOffsetsAndSizesParams.offsetsAndPaddings)
.combine('copyDepth', kOffsetsAndSizesParams.copyDepth) // 2d and 2d-array textures
+ .combine('copyWidth', [3, 1, 2, 127, 128, 255, 256] as const) // copyWidth === 3 is the default. Others covers special cases for r8snorm and rg8snorm on compatiblity mode.
+ .filter(({ format, copyWidth }) => {
+ switch (format) {
+ case 'r8snorm':
+ case 'rg8snorm':
+ return true;
+ default:
+ // Restrict test parameters to save run time.
+ return copyWidth === 3;
+ }
+ })
+ .combine('rowsPerImageEqualsCopyHeight', [true, false] as const)
.unless(p => p.dimension === '1d' && p.copyDepth !== 1)
)
.beforeAllSubcases(t => {
@@ -1521,25 +1567,43 @@ works for every format with 2d and 2d-array textures.
dimension,
initMethod,
checkMethod,
+ copyWidth,
+ rowsPerImageEqualsCopyHeight,
} = t.params;
+
+ // Skip test cases designed for special cases coverage on compatibility mode to save run time.
+ if (!(t.isCompatibility && (format === 'r8snorm' || format === 'rg8snorm'))) {
+ if (rowsPerImageEqualsCopyHeight === false) {
+ t.skip(
+ 'rowsPerImageEqualsCopyHeight === false is only for r8snorm and rg8snorm on compatibility mode'
+ );
+ }
+
+ if (copyWidth !== 3) {
+ t.skip('copyWidth !== 3 is only for r8snorm and rg8snorm on compatibility mode');
+ }
+ }
+
const info = kTextureFormatInfo[format];
const offset = offsetInBlocks * info.color.bytes;
+ const copyHeight = 3;
const copySize = {
- width: 3 * info.blockWidth,
- height: 3 * info.blockHeight,
+ width: copyWidth * info.blockWidth,
+ height: copyHeight * info.blockHeight,
depthOrArrayLayers: copyDepth,
};
let textureHeight = 4 * info.blockHeight;
- let rowsPerImage = 3;
- const bytesPerRow = 256;
+ let rowsPerImage = rowsPerImageEqualsCopyHeight ? copyHeight : copyHeight + 1;
+ const bytesPerRow = align(copyWidth * info.color.bytes, 256);
if (dimension === '1d') {
copySize.height = 1;
textureHeight = info.blockHeight;
rowsPerImage = 1;
}
- const textureSize = [4 * info.blockWidth, textureHeight, copyDepth] as const;
+ // Add textureWidth by 1 to make sure we are doing a partial copy.
+ const textureSize = [(copyWidth + 1) * info.blockWidth, textureHeight, copyDepth] as const;
const minDataSize = dataBytesForCopyOrFail({
layout: { offset, bytesPerRow, rowsPerImage },
@@ -1549,7 +1613,7 @@ works for every format with 2d and 2d-array textures.
});
const dataSize = minDataSize + dataPaddingInBytes;
- // We're copying a (3 x 3 x copyDepth) (in texel blocks) part of a (4 x 4 x copyDepth)
+ // We're copying a (copyWidth x 3 x copyDepth) (in texel blocks) part of a ((copyWidth + 1) x 4 x copyDepth)
// (in texel blocks) texture with no origin.
t.uploadTextureAndVerifyCopy({
textureDataLayout: { offset, bytesPerRow, rowsPerImage },
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/command_buffer/queries/occlusionQuery.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/command_buffer/queries/occlusionQuery.spec.ts
index 39b7a377fe..c4b80b7f4f 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/command_buffer/queries/occlusionQuery.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/command_buffer/queries/occlusionQuery.spec.ts
@@ -695,7 +695,7 @@ g.test('occlusion_query,scissor')
const expectPassed = !occluded;
t.expect(
!!passed === expectPassed,
- `queryIndex: ${queryIndex}, scissorCase: ${scissorCase}, was: ${!!passed}, expected: ${expectPassed}, ${name}`
+ `queryIndex: ${queryIndex}, scissorCase: ${scissorCase}, was: ${!!passed}, expected: ${expectPassed}`
);
}
);
@@ -739,7 +739,7 @@ g.test('occlusion_query,depth')
const expectPassed = queryIndex % 2 === 0;
t.expect(
!!passed === expectPassed,
- `queryIndex: ${queryIndex}, was: ${!!passed}, expected: ${expectPassed}, ${name}`
+ `queryIndex: ${queryIndex}, was: ${!!passed}, expected: ${expectPassed}`
);
}
);
@@ -783,7 +783,7 @@ g.test('occlusion_query,stencil')
const expectPassed = queryIndex % 2 === 0;
t.expect(
!!passed === expectPassed,
- `queryIndex: ${queryIndex}, was: ${!!passed}, expected: ${expectPassed}, ${name}`
+ `queryIndex: ${queryIndex}, was: ${!!passed}, expected: ${expectPassed}`
);
}
);
@@ -851,7 +851,7 @@ g.test('occlusion_query,sample_mask')
const expectPassed = !!(sampleMask & drawMask);
t.expect(
!!passed === expectPassed,
- `queryIndex: ${queryIndex}, was: ${!!passed}, expected: ${expectPassed}, ${name}`
+ `queryIndex: ${queryIndex}, was: ${!!passed}, expected: ${expectPassed}`
);
}
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/memory_sync/texture/readonly_depth_stencil.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/memory_sync/texture/readonly_depth_stencil.spec.ts
new file mode 100644
index 0000000000..3c381f1c1f
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/memory_sync/texture/readonly_depth_stencil.spec.ts
@@ -0,0 +1,329 @@
+export const description = `
+Memory synchronization tests for depth-stencil attachments in a single pass, with checks for readonlyness.
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { kDepthStencilFormats, kTextureFormatInfo } from '../../../../format_info.js';
+import { GPUTest } from '../../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('sampling_while_testing')
+ .desc(
+ `Tests concurrent sampling and testing of readonly depth-stencil attachments in a render pass.
+ - Test for all depth-stencil formats.
+ - Test for all valid combinations of depth/stencilReadOnly.
+
+In particular this test checks that a non-readonly aspect can be rendered to, and used for depth/stencil
+testing while the other one is used for sampling.
+ `
+ )
+ .params(p =>
+ p
+ .combine('format', kDepthStencilFormats) //
+ .combine('depthReadOnly', [true, false, undefined])
+ .combine('stencilReadOnly', [true, false, undefined])
+ .filter(p => {
+ const info = kTextureFormatInfo[p.format];
+ const depthMatch = (info.depth === undefined) === (p.depthReadOnly === undefined);
+ const stencilMatch = (info.stencil === undefined) === (p.stencilReadOnly === undefined);
+ return depthMatch && stencilMatch;
+ })
+ )
+ .beforeAllSubcases(t => {
+ const { format } = t.params;
+ const formatInfo = kTextureFormatInfo[format];
+ const hasDepth = formatInfo.depth !== undefined;
+ const hasStencil = formatInfo.stencil !== undefined;
+
+ t.selectDeviceForTextureFormatOrSkipTestCase(t.params.format);
+ t.skipIf(
+ t.isCompatibility && hasDepth && hasStencil,
+ 'compatibility mode does not support different TEXTURE_BINDING views of the same texture in a single draw calls'
+ );
+ })
+ .fn(t => {
+ const { format, depthReadOnly, stencilReadOnly } = t.params;
+ const formatInfo = kTextureFormatInfo[format];
+ const hasDepth = formatInfo.depth !== undefined;
+ const hasStencil = formatInfo.stencil !== undefined;
+
+ // The 3x3 depth stencil texture used for the tests.
+ const ds = t.device.createTexture({
+ label: 'testTexture',
+ size: [3, 3],
+ format,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
+ });
+ t.trackForCleanup(ds);
+
+ // Fill the texture along the X axis with stencil values 1, 2, 3 and along the Y axis depth
+ // values 0.1, 0.2, 0.3. The depth value is written using @builtin(frag_depth) while the
+ // stencil is written using stencil operation and modifying the stencilReference.
+ const initModule = t.device.createShaderModule({
+ code: `
+ @vertex fn vs(
+ @builtin(instance_index) x : u32, @builtin(vertex_index) y : u32
+ ) -> @builtin(position) vec4f {
+ let texcoord = (vec2f(f32(x), f32(y)) + vec2f(0.5)) / 3;
+ return vec4f((texcoord * 2) - vec2f(1.0), 0, 1);
+ }
+ @fragment fn fs_with_depth(@builtin(position) pos : vec4f) -> @builtin(frag_depth) f32 {
+ return (pos.y + 0.5) / 10;
+ }
+ @fragment fn fs_no_depth() {
+ }
+ `,
+ });
+ const initPipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ label: 'initPipeline',
+ vertex: { module: initModule },
+ fragment: {
+ module: initModule,
+ targets: [],
+ entryPoint: hasDepth ? 'fs_with_depth' : 'fs_no_depth',
+ },
+ depthStencil: {
+ format,
+ ...(hasDepth && {
+ depthWriteEnabled: true,
+ depthCompare: 'always',
+ }),
+ ...(hasStencil && {
+ stencilBack: { compare: 'always', passOp: 'replace' },
+ stencilFront: { compare: 'always', passOp: 'replace' },
+ }),
+ },
+ primitive: { topology: 'point-list' },
+ });
+
+ const encoder = t.device.createCommandEncoder();
+
+ const initPass = encoder.beginRenderPass({
+ colorAttachments: [],
+ depthStencilAttachment: {
+ view: ds.createView(),
+ ...(hasDepth && {
+ depthStoreOp: 'store',
+ depthLoadOp: 'clear',
+ depthClearValue: 0,
+ }),
+ ...(hasStencil && {
+ stencilStoreOp: 'store',
+ stencilLoadOp: 'clear',
+ stencilClearValue: 0,
+ }),
+ },
+ });
+ initPass.setPipeline(initPipeline);
+ for (let i = 0; i < 3; i++) {
+ initPass.setStencilReference(i + 1);
+ // Draw 3 points (Y = 0, 1, 2) at X = instance_index = i.
+ initPass.draw(3, 1, 0, i);
+ }
+ initPass.end();
+
+ // Perform the actual test:
+ // - The shader outputs depth 0.15 and stencil 2 (via stencilReference).
+ // - Test that the fragdepth / stencilref must be <= to what's in the depth-stencil attachment.
+ // -> Fragments that have depth 0.1 or stencil 1 are tested out.
+ // - Test that sampling the depth / stencil (when possible) is <= 0.2 for depth, <= 2 for stencil
+ // -> Fragments that have depth 0.3 or stencil 3 are discarded if that aspect is readonly.
+ // - Write the depth / increment the stencil if the aspect is not readonly.
+ // -> After the test, fragments that passed will have non-readonly aspects updated.
+ const kFragDepth = 0.15;
+ const kStencilRef = 2;
+ const testAndCheckModule = t.device.createShaderModule({
+ code: `
+ @group(0) @binding(0) var depthTex : texture_2d<f32>;
+ @group(0) @binding(1) var stencilTex : texture_2d<u32>;
+
+ @vertex fn full_quad_vs(@builtin(vertex_index) id : u32) -> @builtin(position) vec4f {
+ let pos = array(vec2f(-3, -1), vec2(3, -1), vec2(0, 2));
+ return vec4f(pos[id], ${kFragDepth}, 1.0);
+ }
+
+ @fragment fn test_texture(@builtin(position) pos : vec4f) {
+ let texel = vec2u(floor(pos.xy));
+ if ${!!stencilReadOnly} && textureLoad(stencilTex, texel, 0).r > 2 {
+ discard;
+ }
+ if ${!!depthReadOnly} && textureLoad(depthTex, texel, 0).r > 0.21 {
+ discard;
+ }
+ }
+
+ @fragment fn check_texture(@builtin(position) pos : vec4f) -> @location(0) u32 {
+ let texel = vec2u(floor(pos.xy));
+
+ // The current values in the framebuffer.
+ let initStencil = texel.x + 1;
+ let initDepth = f32(texel.y + 1) / 10.0;
+
+ // Expected results of the test_texture step.
+ let stencilTestPasses = !${hasStencil} || ${kStencilRef} <= initStencil;
+ let depthTestPasses = !${hasDepth} || ${kFragDepth} <= initDepth;
+ let fsDiscards = (${!!stencilReadOnly} && initStencil > 2) ||
+ (${!!depthReadOnly} && initDepth > 0.21);
+
+ // Compute the values that should be in the framebuffer.
+ var stencil = initStencil;
+ var depth = initDepth;
+
+ // When the fragments aren't discarded, fragment output operations happen.
+ if depthTestPasses && stencilTestPasses && !fsDiscards {
+ if ${!stencilReadOnly} {
+ stencil += 1;
+ }
+ if ${!depthReadOnly} {
+ depth = ${kFragDepth};
+ }
+ }
+
+ if ${hasStencil} && textureLoad(stencilTex, texel, 0).r != stencil {
+ return 0;
+ }
+ if ${hasDepth} && abs(textureLoad(depthTex, texel, 0).r - depth) > 0.01 {
+ return 0;
+ }
+ return 1;
+ }
+ `,
+ });
+ const testPipeline = t.device.createRenderPipeline({
+ label: 'testPipeline',
+ layout: 'auto',
+ vertex: { module: testAndCheckModule },
+ fragment: { module: testAndCheckModule, entryPoint: 'test_texture', targets: [] },
+ depthStencil: {
+ format,
+ ...(hasDepth && {
+ depthCompare: 'less-equal',
+ depthWriteEnabled: !depthReadOnly,
+ }),
+ ...(hasStencil && {
+ stencilBack: {
+ compare: 'less-equal',
+ passOp: stencilReadOnly ? 'keep' : 'increment-clamp',
+ },
+ stencilFront: {
+ compare: 'less-equal',
+ passOp: stencilReadOnly ? 'keep' : 'increment-clamp',
+ },
+ }),
+ },
+ primitive: { topology: 'triangle-list' },
+ });
+
+ // Make fake stencil or depth textures to put in the bindgroup if the aspect is not readonly.
+ const fakeStencil = t.device.createTexture({
+ label: 'fakeStencil',
+ format: 'r32uint',
+ size: [1, 1],
+ usage: GPUTextureUsage.TEXTURE_BINDING,
+ });
+ t.trackForCleanup(fakeStencil);
+ const fakeDepth = t.device.createTexture({
+ label: 'fakeDepth',
+ format: 'r32float',
+ size: [1, 1],
+ usage: GPUTextureUsage.TEXTURE_BINDING,
+ });
+ t.trackForCleanup(fakeDepth);
+ const stencilView = stencilReadOnly
+ ? ds.createView({ aspect: 'stencil-only' })
+ : fakeStencil.createView();
+ const depthView = depthReadOnly
+ ? ds.createView({ aspect: 'depth-only' })
+ : fakeDepth.createView();
+ const testBindGroup = t.device.createBindGroup({
+ layout: testPipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: depthView },
+ { binding: 1, resource: stencilView },
+ ],
+ });
+
+ // Run the test.
+ const testPass = encoder.beginRenderPass({
+ colorAttachments: [],
+ depthStencilAttachment: {
+ view: ds.createView(),
+ ...(hasDepth &&
+ (depthReadOnly
+ ? { depthReadOnly: true }
+ : {
+ depthStoreOp: 'store',
+ depthLoadOp: 'load',
+ })),
+ ...(hasStencil &&
+ (stencilReadOnly
+ ? { stencilReadOnly: true }
+ : {
+ stencilStoreOp: 'store',
+ stencilLoadOp: 'load',
+ })),
+ },
+ });
+ testPass.setPipeline(testPipeline);
+ testPass.setStencilReference(kStencilRef);
+ testPass.setBindGroup(0, testBindGroup);
+ testPass.draw(3);
+ testPass.end();
+
+ // Check that the contents of the textures are what we expect. See the shader module for the
+ // computation of what's expected, it writes a 1 on success, 0 otherwise.
+ const checkPipeline = t.device.createRenderPipeline({
+ label: 'checkPipeline',
+ layout: 'auto',
+ vertex: { module: testAndCheckModule },
+ fragment: {
+ module: testAndCheckModule,
+ entryPoint: 'check_texture',
+ targets: [{ format: 'r32uint' }],
+ },
+ primitive: { topology: 'triangle-list' },
+ });
+ const checkBindGroup = t.device.createBindGroup({
+ layout: checkPipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: hasDepth ? ds.createView({ aspect: 'depth-only' }) : fakeDepth.createView(),
+ },
+ {
+ binding: 1,
+ resource: hasStencil
+ ? ds.createView({ aspect: 'stencil-only' })
+ : fakeStencil.createView(),
+ },
+ ],
+ });
+
+ const resultTexture = t.device.createTexture({
+ label: 'resultTexture',
+ format: 'r32uint',
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
+ size: [3, 3],
+ });
+ const checkPass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: resultTexture.createView(),
+ loadOp: 'clear',
+ clearValue: [0, 0, 0, 0],
+ storeOp: 'store',
+ },
+ ],
+ });
+ checkPass.setPipeline(checkPipeline);
+ checkPass.setBindGroup(0, checkBindGroup);
+ checkPass.draw(3);
+ checkPass.end();
+
+ t.queue.submit([encoder.finish()]);
+
+ // The check texture should be full of success (a.k.a. 1)!
+ t.expectSingleColor(resultTexture, resultTexture.format, { size: [3, 3, 1], exp: { R: 1 } });
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/reflection.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/reflection.spec.ts
index e9f7b9726c..7fb1a230cc 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/reflection.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/reflection.spec.ts
@@ -8,26 +8,38 @@ import { GPUTest } from '../../gpu_test.js';
export const g = makeTestGroup(GPUTest);
+function* extractValuePropertyKeys(obj: { [k: string]: unknown }) {
+ for (const key in obj) {
+ if (typeof obj[key] !== 'function') {
+ yield key;
+ }
+ }
+}
+
+const kBufferSubcases: readonly {
+ size: number;
+ usage: GPUBufferUsageFlags;
+ label?: string;
+ invalid?: boolean;
+}[] = [
+ { size: 4, usage: GPUConst.BufferUsage.VERTEX },
+ {
+ size: 16,
+ usage:
+ GPUConst.BufferUsage.STORAGE | GPUConst.BufferUsage.COPY_SRC | GPUConst.BufferUsage.UNIFORM,
+ },
+ { size: 32, usage: GPUConst.BufferUsage.MAP_READ | GPUConst.BufferUsage.COPY_DST },
+ { size: 40, usage: GPUConst.BufferUsage.INDEX, label: 'some label' },
+ {
+ size: 32,
+ usage: GPUConst.BufferUsage.MAP_READ | GPUConst.BufferUsage.MAP_WRITE,
+ invalid: true,
+ },
+] as const;
+
g.test('buffer_reflection_attributes')
.desc(`For every buffer attribute, the corresponding descriptor value is carried over.`)
- .paramsSubcasesOnly(u =>
- u.combine('descriptor', [
- { size: 4, usage: GPUConst.BufferUsage.VERTEX },
- {
- size: 16,
- usage:
- GPUConst.BufferUsage.STORAGE |
- GPUConst.BufferUsage.COPY_SRC |
- GPUConst.BufferUsage.UNIFORM,
- },
- { size: 32, usage: GPUConst.BufferUsage.MAP_READ | GPUConst.BufferUsage.COPY_DST },
- {
- size: 32,
- usage: GPUConst.BufferUsage.MAP_READ | GPUConst.BufferUsage.MAP_WRITE,
- invalid: true,
- },
- ] as const)
- )
+ .paramsSubcasesOnly(u => u.combine('descriptor', kBufferSubcases))
.fn(t => {
const { descriptor } = t.params;
@@ -39,53 +51,102 @@ g.test('buffer_reflection_attributes')
}, descriptor.invalid === true);
});
-g.test('texture_reflection_attributes')
- .desc(`For every texture attribute, the corresponding descriptor value is carried over.`)
+g.test('buffer_creation_from_reflection')
+ .desc(
+ `
+ Check that you can create a buffer from a buffer's reflection.
+ This check is to insure that as WebGPU develops this path doesn't
+ suddenly break because of new reflection.
+ `
+ )
.paramsSubcasesOnly(u =>
- u.combine('descriptor', [
- {
- size: { width: 4, height: 4 },
- format: 'rgba8unorm',
- usage: GPUConst.TextureUsage.TEXTURE_BINDING,
- },
- {
- size: { width: 8, height: 8, depthOrArrayLayers: 8 },
- format: 'bgra8unorm',
- usage: GPUConst.TextureUsage.RENDER_ATTACHMENT | GPUConst.TextureUsage.COPY_SRC,
- },
- {
- size: [4, 4],
- format: 'rgba8unorm',
- usage: GPUConst.TextureUsage.TEXTURE_BINDING,
- mipLevelCount: 2,
- },
- {
- size: [16, 16, 16],
- format: 'rgba8unorm',
- usage: GPUConst.TextureUsage.TEXTURE_BINDING,
- dimension: '3d',
- },
- {
- size: [32],
- format: 'rgba8unorm',
- usage: GPUConst.TextureUsage.TEXTURE_BINDING,
- dimension: '1d',
- },
- {
- size: { width: 4, height: 4 },
- format: 'rgba8unorm',
- usage: GPUConst.TextureUsage.RENDER_ATTACHMENT,
- sampleCount: 4,
- },
- {
- size: { width: 4, height: 4 },
- format: 'rgba8unorm',
- usage: GPUConst.TextureUsage.TEXTURE_BINDING,
- sampleCount: 4,
- invalid: true,
- },
- ] as const)
+ u.combine('descriptor', kBufferSubcases).filter(p => !p.descriptor.invalid)
)
+
+ .fn(t => {
+ const { descriptor } = t.params;
+
+ const buffer = t.device.createBuffer(descriptor);
+ t.trackForCleanup(buffer);
+ const buffer2 = t.device.createBuffer(buffer);
+ t.trackForCleanup(buffer2);
+
+ const bufferAsObject = buffer as unknown as { [k: string]: unknown };
+ const buffer2AsObject = buffer2 as unknown as { [k: string]: unknown };
+ const keys = [...extractValuePropertyKeys(bufferAsObject)];
+
+ // Sanity check
+ t.expect(keys.includes('size'));
+ t.expect(keys.includes('usage'));
+ t.expect(keys.includes('label'));
+
+ for (const key of keys) {
+ t.expect(bufferAsObject[key] === buffer2AsObject[key], key);
+ }
+ });
+
+const kTextureSubcases: readonly {
+ size: GPUExtent3D;
+ format: GPUTextureFormat;
+ usage: GPUTextureUsageFlags;
+ mipLevelCount?: number;
+ label?: string;
+ dimension?: GPUTextureDimension;
+ sampleCount?: number;
+ invalid?: boolean;
+}[] = [
+ {
+ size: { width: 4, height: 4 },
+ format: 'rgba8unorm',
+ usage: GPUConst.TextureUsage.TEXTURE_BINDING,
+ },
+ {
+ size: { width: 4, height: 4 },
+ format: 'rgba8unorm',
+ usage: GPUConst.TextureUsage.TEXTURE_BINDING,
+ label: 'some label',
+ },
+ {
+ size: { width: 8, height: 8, depthOrArrayLayers: 8 },
+ format: 'bgra8unorm',
+ usage: GPUConst.TextureUsage.RENDER_ATTACHMENT | GPUConst.TextureUsage.COPY_SRC,
+ },
+ {
+ size: [4, 4],
+ format: 'rgba8unorm',
+ usage: GPUConst.TextureUsage.TEXTURE_BINDING,
+ mipLevelCount: 2,
+ },
+ {
+ size: [16, 16, 16],
+ format: 'rgba8unorm',
+ usage: GPUConst.TextureUsage.TEXTURE_BINDING,
+ dimension: '3d',
+ },
+ {
+ size: [32],
+ format: 'rgba8unorm',
+ usage: GPUConst.TextureUsage.TEXTURE_BINDING,
+ dimension: '1d',
+ },
+ {
+ size: { width: 4, height: 4 },
+ format: 'rgba8unorm',
+ usage: GPUConst.TextureUsage.RENDER_ATTACHMENT,
+ sampleCount: 4,
+ },
+ {
+ size: { width: 4, height: 4 },
+ format: 'rgba8unorm',
+ usage: GPUConst.TextureUsage.TEXTURE_BINDING,
+ sampleCount: 4,
+ invalid: true,
+ },
+] as const;
+
+g.test('texture_reflection_attributes')
+ .desc(`For every texture attribute, the corresponding descriptor value is carried over.`)
+ .paramsSubcasesOnly(u => u.combine('descriptor', kTextureSubcases))
.fn(t => {
const { descriptor } = t.params;
@@ -116,18 +177,77 @@ g.test('texture_reflection_attributes')
}, descriptor.invalid === true);
});
-g.test('query_set_reflection_attributes')
- .desc(`For every queue attribute, the corresponding descriptor value is carried over.`)
+interface TextureWithSize extends GPUTexture {
+ size: GPUExtent3D;
+}
+
+g.test('texture_creation_from_reflection')
+ .desc(
+ `
+ Check that you can create a texture from a texture's reflection.
+ This check is to insure that as WebGPU develops this path doesn't
+ suddenly break because of new reflection.
+ `
+ )
.paramsSubcasesOnly(u =>
- u.combine('descriptor', [
- { type: 'occlusion', count: 4 },
- { type: 'occlusion', count: 16 },
- { type: 'occlusion', count: 8193, invalid: true },
- ] as const)
+ u.combine('descriptor', kTextureSubcases).filter(p => !p.descriptor.invalid)
)
.fn(t => {
const { descriptor } = t.params;
+ const texture = t.device.createTexture(descriptor);
+ t.trackForCleanup(texture);
+ const textureWithSize = texture as TextureWithSize;
+ textureWithSize.size = [texture.width, texture.height, texture.depthOrArrayLayers];
+ const texture2 = t.device.createTexture(textureWithSize);
+ t.trackForCleanup(texture2);
+
+ const textureAsObject = texture as unknown as { [k: string]: unknown };
+ const texture2AsObject = texture2 as unknown as { [k: string]: unknown };
+ const keys = [...extractValuePropertyKeys(textureAsObject)].filter(k => k !== 'size');
+
+ // Sanity check
+ t.expect(keys.includes('format'));
+ t.expect(keys.includes('usage'));
+ t.expect(keys.includes('label'));
+
+ for (const key of keys) {
+ t.expect(textureAsObject[key] === texture2AsObject[key], key);
+ }
+
+ // MAINTENANCE_TODO: Check this if it is made possible by a spec change.
+ //
+ // texture3 = t.device.createTexture({
+ // ...texture,
+ // size: [texture.width, texture.height, texture.depthOrArrayLayers],
+ // });
+ //
+ // and this
+ //
+ // texture3 = t.device.createTexture({
+ // size: [texture.width, texture.height, texture.depthOrArrayLayers],
+ // ...texture,
+ // });
+ });
+
+const kQuerySetSubcases: readonly {
+ type: GPUQueryType;
+ count: number;
+ label?: string;
+ invalid?: boolean;
+}[] = [
+ { type: 'occlusion', count: 4 },
+ { type: 'occlusion', count: 16 },
+ { type: 'occlusion', count: 32, label: 'some label' },
+ { type: 'occlusion', count: 8193, invalid: true },
+] as const;
+
+g.test('query_set_reflection_attributes')
+ .desc(`For every queue attribute, the corresponding descriptor value is carried over.`)
+ .paramsSubcasesOnly(u => u.combine('descriptor', kQuerySetSubcases))
+ .fn(t => {
+ const { descriptor } = t.params;
+
t.expectValidationError(() => {
const querySet = t.device.createQuerySet(descriptor);
@@ -135,3 +255,36 @@ g.test('query_set_reflection_attributes')
t.expect(querySet.count === descriptor.count);
}, descriptor.invalid === true);
});
+
+g.test('query_set_creation_from_reflection')
+ .desc(
+ `
+ Check that you can create a queryset from a queryset's reflection.
+ This check is to insure that as WebGPU develops this path doesn't
+ suddenly break because of new reflection.
+ `
+ )
+ .paramsSubcasesOnly(u =>
+ u.combine('descriptor', kQuerySetSubcases).filter(p => !p.descriptor.invalid)
+ )
+ .fn(t => {
+ const { descriptor } = t.params;
+
+ const querySet = t.device.createQuerySet(descriptor);
+ t.trackForCleanup(querySet);
+ const querySet2 = t.device.createQuerySet(querySet);
+ t.trackForCleanup(querySet2);
+
+ const querySetAsObject = querySet as unknown as { [k: string]: unknown };
+ const querySet2AsObject = querySet2 as unknown as { [k: string]: unknown };
+ const keys = [...extractValuePropertyKeys(querySetAsObject)];
+
+ // Sanity check
+ t.expect(keys.includes('type'));
+ t.expect(keys.includes('count'));
+ t.expect(keys.includes('label'));
+
+ for (const key of keys) {
+ t.expect(querySetAsObject[key] === querySet2AsObject[key], key);
+ }
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/render_pipeline/sample_mask.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/render_pipeline/sample_mask.spec.ts
index 00069b777f..b28e1b381c 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/render_pipeline/sample_mask.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/render_pipeline/sample_mask.spec.ts
@@ -6,6 +6,8 @@ Also tested:
- The positions of samples in the standard sample patterns.
- Per-sample interpolation sampling: @interpolate(perspective, sample).
+TODO: Test sample_mask as an input.
+
TODO: add a test without a 0th color attachment (sparse color attachment), with different color attachments and alpha value output.
The cross-platform behavior is unknown. could be any of:
- coverage is always 100%
@@ -19,7 +21,7 @@ import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { assert, range } from '../../../../common/util/util.js';
import { GPUTest, TextureTestMixin } from '../../../gpu_test.js';
import { checkElementsPassPredicate, checkElementsEqual } from '../../../util/check_contents.js';
-import { TypeF32, TypeU32 } from '../../../util/conversion.js';
+import { Type } from '../../../util/conversion.js';
import { TexelView } from '../../../util/texture/texel_view.js';
const kColors = [
@@ -435,8 +437,8 @@ class F extends TextureTestMixin(GPUTest) {
sampleMask: number,
fragmentShaderOutputMask: number
) {
- const buffer = this.copySinglePixelTextureToBufferUsingComputePass(
- TypeF32, // correspond to 'rgba8unorm' format
+ const buffer = this.copy2DTextureToBufferUsingComputePass(
+ Type.f32, // correspond to 'rgba8unorm' format
4,
texture.createView(),
sampleCount
@@ -459,10 +461,10 @@ class F extends TextureTestMixin(GPUTest) {
sampleMask: number,
fragmentShaderOutputMask: number
) {
- const buffer = this.copySinglePixelTextureToBufferUsingComputePass(
+ const buffer = this.copy2DTextureToBufferUsingComputePass(
// Use f32 as the scalar type for depth (depth24plus, depth32float)
// Use u32 as the scalar type for stencil (stencil8)
- aspect === 'depth-only' ? TypeF32 : TypeU32,
+ aspect === 'depth-only' ? Type.f32 : Type.u32,
1,
depthStencilTexture.createView({ aspect }),
sampleCount
@@ -702,8 +704,8 @@ color' <= color.
2
);
- const colorBuffer = t.copySinglePixelTextureToBufferUsingComputePass(
- TypeF32, // correspond to 'rgba8unorm' format
+ const colorBuffer = t.copy2DTextureToBufferUsingComputePass(
+ Type.f32, // correspond to 'rgba8unorm' format
4,
color.createView(),
sampleCount
@@ -714,8 +716,8 @@ color' <= color.
});
colorResultPromises.push(colorResult);
- const depthBuffer = t.copySinglePixelTextureToBufferUsingComputePass(
- TypeF32, // correspond to 'depth24plus-stencil8' format
+ const depthBuffer = t.copy2DTextureToBufferUsingComputePass(
+ Type.f32, // correspond to 'depth24plus-stencil8' format
1,
depthStencil.createView({ aspect: 'depth-only' }),
sampleCount
@@ -726,8 +728,8 @@ color' <= color.
});
depthResultPromises.push(depthResult);
- const stencilBuffer = t.copySinglePixelTextureToBufferUsingComputePass(
- TypeU32, // correspond to 'depth24plus-stencil8' format
+ const stencilBuffer = t.copy2DTextureToBufferUsingComputePass(
+ Type.u32, // correspond to 'depth24plus-stencil8' format
1,
depthStencil.createView({ aspect: 'stencil-only' }),
sampleCount
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/3d_texture_slices.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/3d_texture_slices.spec.ts
new file mode 100644
index 0000000000..98f51d3dff
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/3d_texture_slices.spec.ts
@@ -0,0 +1,363 @@
+export const description = `
+Test rendering to 3d texture slices.
+- Render to same slice on different render pass, different textures, or texture [1, 1, N]'s different mip levels
+- Render to different slices at mip levels on same texture in render pass
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { kTextureFormatInfo } from '../../../format_info.js';
+import { GPUTest } from '../../../gpu_test.js';
+import { kBytesPerRowAlignment } from '../../../util/texture/layout.js';
+
+const kSize = 4;
+const kFormat = 'rgba8unorm' as const;
+
+class F extends GPUTest {
+ createShaderModule(attachmentCount: number = 1): GPUShaderModule {
+ let locations = '';
+ let outputs = '';
+ for (let i = 0; i < attachmentCount; i++) {
+ locations = locations + `@location(${i}) color${i} : vec4f, \n`;
+ outputs = outputs + `output.color${i} = vec4f(0.0, 1.0, 0.0, 1.0);\n`;
+ }
+
+ return this.device.createShaderModule({
+ code: `
+ struct Output {
+ ${locations}
+ }
+
+ @vertex
+ fn main_vs(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4<f32> {
+ var pos : array<vec2<f32>, 3> = array<vec2<f32>, 3>(
+ // Triangle is slightly extended so its edge doesn't cut through pixel centers.
+ vec2<f32>(-1.0, 1.01),
+ vec2<f32>(1.01, -1.0),
+ vec2<f32>(-1.0, -1.0));
+ return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
+ }
+
+ @fragment
+ fn main_fs() -> Output {
+ var output : Output;
+ ${outputs}
+ return output;
+ }
+ `,
+ });
+ }
+
+ getBufferSizeAndOffset(
+ attachmentWidth: number,
+ attachmentHeight: number,
+ attachmentCount: number
+ ): { bufferSize: number; bufferOffset: number } {
+ const bufferSize =
+ (attachmentCount * attachmentHeight - 1) * kBytesPerRowAlignment + attachmentWidth * 4;
+ const bufferOffset = attachmentCount > 1 ? attachmentHeight * kBytesPerRowAlignment : 0;
+ return { bufferSize, bufferOffset };
+ }
+
+ checkAttachmentResult(
+ attachmentWidth: number,
+ attachmentHeight: number,
+ attachmentCount: number,
+ buffer: GPUBuffer
+ ) {
+ const { bufferSize, bufferOffset } = this.getBufferSizeAndOffset(
+ attachmentWidth,
+ attachmentHeight,
+ attachmentCount
+ );
+ const expectedData = new Uint8Array(bufferSize);
+ for (let i = 0; i < attachmentCount; i++) {
+ for (let j = 0; j < attachmentHeight; j++) {
+ for (let k = 0; k < attachmentWidth; k++) {
+ expectedData[i * bufferOffset + j * 256 + k * 4] = k <= j ? 0x00 : 0xff;
+ expectedData[i * bufferOffset + j * 256 + k * 4 + 1] = k <= j ? 0xff : 0x00;
+ expectedData[i * bufferOffset + j * 256 + k * 4 + 2] = 0x00;
+ expectedData[i * bufferOffset + j * 256 + k * 4 + 3] = 0xff;
+ }
+ }
+ }
+
+ this.expectGPUBufferValuesEqual(buffer, expectedData);
+ }
+}
+
+export const g = makeTestGroup(F);
+
+g.test('one_color_attachment,mip_levels')
+ .desc(
+ `
+ Render to a 3d texture slice with mip levels.
+ `
+ )
+ .params(u => u.combine('mipLevel', [0, 1, 2]).combine('depthSlice', [0, 1]))
+ .fn(t => {
+ const { mipLevel, depthSlice } = t.params;
+
+ const texture = t.device.createTexture({
+ size: [kSize << mipLevel, kSize << mipLevel, 2 << mipLevel],
+ dimension: '3d',
+ format: kFormat,
+ mipLevelCount: mipLevel + 1,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
+ });
+
+ const { bufferSize } = t.getBufferSizeAndOffset(kSize, kSize, 1);
+
+ const buffer = t.device.createBuffer({
+ size: bufferSize,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
+ });
+
+ const module = t.createShaderModule();
+
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: { module },
+ fragment: {
+ module,
+ targets: [{ format: kFormat }],
+ },
+ primitive: { topology: 'triangle-list' },
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: texture.createView({
+ baseMipLevel: mipLevel,
+ mipLevelCount: 1,
+ }),
+ depthSlice,
+ clearValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store',
+ },
+ ],
+ });
+ pass.setPipeline(pipeline);
+ pass.draw(3);
+ pass.end();
+ encoder.copyTextureToBuffer(
+ { texture, mipLevel, origin: { x: 0, y: 0, z: depthSlice } },
+ { buffer, bytesPerRow: 256 },
+ { width: kSize, height: kSize, depthOrArrayLayers: 1 }
+ );
+ t.device.queue.submit([encoder.finish()]);
+
+ t.checkAttachmentResult(kSize, kSize, 1, buffer);
+ });
+
+g.test('multiple_color_attachments,same_mip_level')
+ .desc(
+ `
+ Render to the different slices of 3d texture in multiple color attachments.
+ - Same 3d texture with different slices at same mip level
+ - Different 3d textures with same slice at same mip level
+ `
+ )
+ .params(u =>
+ u
+ .combine('sameTexture', [true, false])
+ .beginSubcases()
+ .combine('samePass', [true, false])
+ .combine('mipLevel', [0, 1])
+ )
+ .fn(t => {
+ const { sameTexture, samePass, mipLevel } = t.params;
+
+ const formatByteCost = kTextureFormatInfo[kFormat].colorRender.byteCost;
+ const maxAttachmentCountPerSample = Math.trunc(
+ t.device.limits.maxColorAttachmentBytesPerSample / formatByteCost
+ );
+ const attachmentCount = Math.min(
+ maxAttachmentCountPerSample,
+ t.device.limits.maxColorAttachments
+ );
+
+ const descriptor = {
+ size: [kSize << mipLevel, kSize << mipLevel, (1 << attachmentCount) << mipLevel],
+ dimension: '3d',
+ format: kFormat,
+ mipLevelCount: mipLevel + 1,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
+ } as const;
+
+ const texture = t.device.createTexture(descriptor);
+
+ const textures: GPUTexture[] = [];
+ const colorAttachments: GPURenderPassColorAttachment[] = [];
+ for (let i = 0; i < attachmentCount; i++) {
+ if (sameTexture) {
+ textures.push(texture);
+ } else {
+ const diffTexture = t.device.createTexture(descriptor);
+ textures.push(diffTexture);
+ }
+
+ const colorAttachment = {
+ view: textures[i].createView({
+ baseMipLevel: mipLevel,
+ mipLevelCount: 1,
+ }),
+ depthSlice: sameTexture ? i : 0,
+ clearValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store',
+ } as const;
+
+ colorAttachments.push(colorAttachment);
+ }
+
+ const encoder = t.device.createCommandEncoder();
+
+ if (samePass) {
+ const module = t.createShaderModule(attachmentCount);
+
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: { module },
+ fragment: {
+ module,
+ targets: new Array<GPUColorTargetState>(attachmentCount).fill({ format: kFormat }),
+ },
+ primitive: { topology: 'triangle-list' },
+ });
+
+ const pass = encoder.beginRenderPass({ colorAttachments });
+ pass.setPipeline(pipeline);
+ pass.draw(3);
+ pass.end();
+ } else {
+ const module = t.createShaderModule();
+
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: { module },
+ fragment: {
+ module,
+ targets: [{ format: kFormat }],
+ },
+ primitive: { topology: 'triangle-list' },
+ });
+
+ for (let i = 0; i < attachmentCount; i++) {
+ const pass = encoder.beginRenderPass({ colorAttachments: [colorAttachments[i]] });
+ pass.setPipeline(pipeline);
+ pass.draw(3);
+ pass.end();
+ }
+ }
+
+ const { bufferSize, bufferOffset } = t.getBufferSizeAndOffset(kSize, kSize, attachmentCount);
+ const buffer = t.device.createBuffer({
+ size: bufferSize,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
+ });
+ for (let i = 0; i < attachmentCount; i++) {
+ encoder.copyTextureToBuffer(
+ {
+ texture: textures[i],
+ mipLevel,
+ origin: { x: 0, y: 0, z: sameTexture ? i : 0 },
+ },
+ { buffer, bytesPerRow: 256, offset: bufferOffset * i },
+ { width: kSize, height: kSize, depthOrArrayLayers: 1 }
+ );
+ }
+
+ t.device.queue.submit([encoder.finish()]);
+
+ t.checkAttachmentResult(kSize, kSize, attachmentCount, buffer);
+ });
+
+g.test('multiple_color_attachments,same_slice_with_diff_mip_levels')
+ .desc(
+ `
+ Render to the same slice of a 3d texture at different mip levels in multiple color attachments.
+ - For texture size with 1x1xN, the same depth slice of different mip levels can be rendered.
+ `
+ )
+ .params(u => u.combine('depthSlice', [0, 1]))
+ .fn(t => {
+ const { depthSlice } = t.params;
+
+ const kBaseSize = 1;
+
+ const formatByteCost = kTextureFormatInfo[kFormat].colorRender.byteCost;
+ const maxAttachmentCountPerSample = Math.trunc(
+ t.device.limits.maxColorAttachmentBytesPerSample / formatByteCost
+ );
+ const attachmentCount = Math.min(
+ maxAttachmentCountPerSample,
+ t.device.limits.maxColorAttachments
+ );
+
+ const module = t.createShaderModule(attachmentCount);
+
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: { module },
+ fragment: {
+ module,
+ targets: new Array<GPUColorTargetState>(attachmentCount).fill({ format: kFormat }),
+ },
+ primitive: { topology: 'triangle-list' },
+ });
+
+ const texture = t.device.createTexture({
+ size: [kBaseSize, kBaseSize, (depthSlice + 1) << attachmentCount],
+ dimension: '3d',
+ format: kFormat,
+ mipLevelCount: attachmentCount,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
+ });
+
+ const colorAttachments: GPURenderPassColorAttachment[] = [];
+ for (let i = 0; i < attachmentCount; i++) {
+ const colorAttachment = {
+ view: texture.createView({
+ baseMipLevel: i,
+ mipLevelCount: 1,
+ }),
+ depthSlice,
+ clearValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store',
+ } as const;
+
+ colorAttachments.push(colorAttachment);
+ }
+
+ const encoder = t.device.createCommandEncoder();
+
+ const pass = encoder.beginRenderPass({ colorAttachments });
+ pass.setPipeline(pipeline);
+ pass.draw(3);
+ pass.end();
+
+ const { bufferSize, bufferOffset } = t.getBufferSizeAndOffset(
+ kBaseSize,
+ kBaseSize,
+ attachmentCount
+ );
+ const buffer = t.device.createBuffer({
+ size: bufferSize,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
+ });
+ for (let i = 0; i < attachmentCount; i++) {
+ encoder.copyTextureToBuffer(
+ { texture, mipLevel: i, origin: { x: 0, y: 0, z: depthSlice } },
+ { buffer, bytesPerRow: 256, offset: bufferOffset * i },
+ { width: kBaseSize, height: kBaseSize, depthOrArrayLayers: 1 }
+ );
+ }
+
+ t.device.queue.submit([encoder.finish()]);
+
+ t.checkAttachmentResult(kBaseSize, kBaseSize, attachmentCount, buffer);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/color_target_state.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/color_target_state.spec.ts
index 1290c6bc99..673e33c2ea 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/color_target_state.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/color_target_state.spec.ts
@@ -11,7 +11,7 @@ import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { assert, TypedArrayBufferView, unreachable } from '../../../../common/util/util.js';
import { kBlendFactors, kBlendOperations } from '../../../capability_info.js';
import { GPUConst } from '../../../constants.js';
-import { kEncodableTextureFormats, kTextureFormatInfo } from '../../../format_info.js';
+import { kRegularTextureFormats, kTextureFormatInfo } from '../../../format_info.js';
import { GPUTest, TextureTestMixin } from '../../../gpu_test.js';
import { clamp } from '../../../util/math.js';
import { TexelView } from '../../../util/texture/texel_view.js';
@@ -165,6 +165,7 @@ g.test('blending,GPUBlendComponent')
.combine('component', ['color', 'alpha'] as const)
.combine('srcFactor', kBlendFactors)
.combine('dstFactor', kBlendFactors)
+ .beginSubcases()
.combine('operation', kBlendOperations)
.filter(t => {
if (t.operation === 'min' || t.operation === 'max') {
@@ -172,7 +173,6 @@ g.test('blending,GPUBlendComponent')
}
return true;
})
- .beginSubcases()
.combine('srcColor', [{ r: 0.11, g: 0.61, b: 0.81, a: 0.44 }])
.combine('dstColor', [
{ r: 0.51, g: 0.22, b: 0.71, a: 0.33 },
@@ -318,9 +318,9 @@ struct Uniform {
);
});
-const kBlendableFormats = kEncodableTextureFormats.filter(f => {
+const kBlendableFormats = kRegularTextureFormats.filter(f => {
const info = kTextureFormatInfo[f];
- return info.renderable && info.sampleType === 'float';
+ return info.colorRender && info.color.type === 'float';
});
g.test('blending,formats')
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/depth.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/depth.spec.ts
index 3b2227db98..a81a7c7812 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/depth.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/depth.spec.ts
@@ -467,13 +467,8 @@ g.test('reverse_depth')
@vertex fn main(
@builtin(vertex_index) VertexIndex : u32,
@builtin(instance_index) InstanceIndex : u32) -> Output {
- // TODO: remove workaround for Tint unary array access broke
- var zv : array<vec2<f32>, 4> = array<vec2<f32>, 4>(
- vec2<f32>(0.2, 0.2),
- vec2<f32>(0.3, 0.3),
- vec2<f32>(-0.1, -0.1),
- vec2<f32>(1.1, 1.1));
- let z : f32 = zv[InstanceIndex].x;
+ let zv = array(0.2, 0.3, -0.1, 1.1);
+ let z = zv[InstanceIndex];
var output : Output;
output.Position = vec4<f32>(0.5, 0.5, z, 1.0);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/depth_bias.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/depth_bias.spec.ts
index 03caff3b25..17e80c5da9 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/depth_bias.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/depth_bias.spec.ts
@@ -304,6 +304,12 @@ g.test('depth_bias')
},
] as const)
)
+ .beforeAllSubcases(t => {
+ t.skipIf(
+ t.isCompatibility && t.params.biasClamp !== 0,
+ 'non zero depthBiasClamp is not supported in compatibility mode'
+ );
+ })
.fn(t => {
t.runDepthBiasTest('depth32float', t.params);
});
@@ -346,6 +352,12 @@ g.test('depth_bias_24bit_format')
},
] as const)
)
+ .beforeAllSubcases(t => {
+ t.skipIf(
+ t.isCompatibility && t.params.biasClamp !== 0,
+ 'non zero depthBiasClamp is not supported in compatibility mode'
+ );
+ })
.fn(t => {
const { format } = t.params;
t.runDepthBiasTestFor24BitFormat(format, t.params);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/depth_clip_clamp.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/depth_clip_clamp.spec.ts
index 65e2e8af1f..1d3b6d8b7a 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/depth_clip_clamp.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/depth_clip_clamp.spec.ts
@@ -4,6 +4,7 @@ depth ranges as well.
`;
import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { assert } from '../../../../common/util/util.js';
import { kDepthStencilFormats, kTextureFormatInfo } from '../../../format_info.js';
import { GPUTest } from '../../../gpu_test.js';
import {
@@ -52,6 +53,7 @@ have unexpected values then get drawn to the color buffer, which is later checke
.fn(async t => {
const { format, unclippedDepth, writeDepth, multisampled } = t.params;
const info = kTextureFormatInfo[format];
+ assert(!!info.depth);
/** Number of depth values to test for both vertex output and frag_depth output. */
const kNumDepthValues = 8;
@@ -222,16 +224,16 @@ have unexpected values then get drawn to the color buffer, which is later checke
: undefined;
const dsActual =
- !multisampled && info.bytesPerBlock
+ !multisampled && info.depth.bytes
? t.device.createBuffer({
- size: kNumTestPoints * info.bytesPerBlock,
+ size: kNumTestPoints * info.depth.bytes,
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
})
: undefined;
const dsExpected =
- !multisampled && info.bytesPerBlock
+ !multisampled && info.depth.bytes
? t.device.createBuffer({
- size: kNumTestPoints * info.bytesPerBlock,
+ size: kNumTestPoints * info.depth.bytes,
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
})
: undefined;
@@ -270,7 +272,9 @@ have unexpected values then get drawn to the color buffer, which is later checke
pass.end();
}
if (dsActual) {
- enc.copyTextureToBuffer({ texture: dsTexture }, { buffer: dsActual }, [kNumTestPoints]);
+ enc.copyTextureToBuffer({ texture: dsTexture, aspect: 'depth-only' }, { buffer: dsActual }, [
+ kNumTestPoints,
+ ]);
}
{
const clearValue = [0, 0, 0, 0]; // Will see this color if the check passed.
@@ -302,7 +306,11 @@ have unexpected values then get drawn to the color buffer, which is later checke
}
enc.copyTextureToBuffer({ texture: checkTexture }, { buffer: checkBuffer }, [kNumTestPoints]);
if (dsExpected) {
- enc.copyTextureToBuffer({ texture: dsTexture }, { buffer: dsExpected }, [kNumTestPoints]);
+ enc.copyTextureToBuffer(
+ { texture: dsTexture, aspect: 'depth-only' },
+ { buffer: dsExpected },
+ [kNumTestPoints]
+ );
}
t.device.queue.submit([enc.finish()]);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/by_copy.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/by_copy.ts
index 2a4ca5e6a4..546db16c7e 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/by_copy.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/by_copy.ts
@@ -1,7 +1,8 @@
import { assert } from '../../../../../common/util/util.js';
import { kTextureFormatInfo, EncodableTextureFormat } from '../../../../format_info.js';
import { virtualMipSize } from '../../../../util/texture/base.js';
-import { CheckContents } from '../texture_zero.spec.js';
+
+import { CheckContents } from './texture_zero_init_test.js';
export const checkContentsByBufferCopy: CheckContents = (
t,
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/by_ds_test.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/by_ds_test.ts
index 8646062452..72ef2909f2 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/by_ds_test.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/by_ds_test.ts
@@ -2,7 +2,8 @@ import { assert } from '../../../../../common/util/util.js';
import { kTextureFormatInfo } from '../../../../format_info.js';
import { GPUTest } from '../../../../gpu_test.js';
import { virtualMipSize } from '../../../../util/texture/base.js';
-import { CheckContents } from '../texture_zero.spec.js';
+
+import { CheckContents } from './texture_zero_init_test.js';
function makeFullscreenVertexModule(device: GPUDevice) {
return device.createShaderModule({
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/by_sampling.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/by_sampling.ts
index 64b4f73b34..fd268be3b9 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/by_sampling.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/by_sampling.ts
@@ -6,7 +6,8 @@ import {
getSingleDataType,
getComponentReadbackTraits,
} from '../../../../util/texture/texel_data.js';
-import { CheckContents } from '../texture_zero.spec.js';
+
+import { CheckContents } from './texture_zero_init_test.js';
export const checkContentsBySampling: CheckContents = (
t,
@@ -41,14 +42,20 @@ export const checkContentsBySampling: CheckContents = (
? componentOrder[0].toLowerCase()
: componentOrder.map(c => c.toLowerCase()).join('') + '[i]';
- const _xd = '_' + params.dimension;
+ const viewDimension =
+ t.isCompatibility && params.dimension === '2d' && texture.depthOrArrayLayers > 1
+ ? '2d-array'
+ : params.dimension;
+ const _xd = `_${viewDimension.replace('-', '_')}`;
const _multisampled = params.sampleCount > 1 ? '_multisampled' : '';
const texelIndexExpression =
- params.dimension === '2d'
+ viewDimension === '2d'
? 'vec2<i32>(GlobalInvocationID.xy)'
- : params.dimension === '3d'
+ : viewDimension === '2d-array'
+ ? 'vec2<i32>(GlobalInvocationID.xy), constants.layer'
+ : viewDimension === '3d'
? 'vec3<i32>(GlobalInvocationID.xyz)'
- : params.dimension === '1d'
+ : viewDimension === '1d'
? 'i32(GlobalInvocationID.x)'
: unreachable();
const computePipeline = t.device.createComputePipeline({
@@ -58,7 +65,8 @@ export const checkContentsBySampling: CheckContents = (
module: t.device.createShaderModule({
code: `
struct Constants {
- level : i32
+ level : i32,
+ layer : i32,
};
@group(0) @binding(0) var<uniform> constants : Constants;
@@ -90,10 +98,10 @@ export const checkContentsBySampling: CheckContents = (
for (const layer of layers) {
const ubo = t.device.createBuffer({
mappedAtCreation: true,
- size: 4,
+ size: 8,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
});
- new Int32Array(ubo.getMappedRange(), 0, 1)[0] = level;
+ new Int32Array(ubo.getMappedRange()).set([level, layer]);
ubo.unmap();
const byteLength =
@@ -104,6 +112,14 @@ export const checkContentsBySampling: CheckContents = (
});
t.trackForCleanup(resultBuffer);
+ const viewDescriptor: GPUTextureViewDescriptor = {
+ ...(!t.isCompatibility && {
+ baseArrayLayer: layer,
+ arrayLayerCount: 1,
+ }),
+ dimension: viewDimension,
+ };
+
const bindGroup = t.device.createBindGroup({
layout: computePipeline.getBindGroupLayout(0),
entries: [
@@ -113,11 +129,7 @@ export const checkContentsBySampling: CheckContents = (
},
{
binding: 1,
- resource: texture.createView({
- baseArrayLayer: layer,
- arrayLayerCount: 1,
- dimension: params.dimension,
- }),
+ resource: texture.createView(viewDescriptor),
},
{
binding: 3,
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/texture_zero_init_test.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/texture_zero_init_test.ts
new file mode 100644
index 0000000000..6ff3ab4c9b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/texture_zero_init_test.ts
@@ -0,0 +1,548 @@
+import { TestCaseRecorder, TestParams } from '../../../../../common/framework/fixture.js';
+import {
+ kUnitCaseParamsBuilder,
+ ParamTypeOf,
+} from '../../../../../common/framework/params_builder.js';
+import { assert, unreachable } from '../../../../../common/util/util.js';
+import { kTextureAspects, kTextureDimensions } from '../../../../capability_info.js';
+import { GPUConst } from '../../../../constants.js';
+import {
+ kTextureFormatInfo,
+ kUncompressedTextureFormats,
+ textureDimensionAndFormatCompatible,
+ UncompressedTextureFormat,
+ EncodableTextureFormat,
+} from '../../../../format_info.js';
+import { GPUTest, GPUTestSubcaseBatchState } from '../../../../gpu_test.js';
+import { virtualMipSize } from '../../../../util/texture/base.js';
+import { createTextureUploadBuffer } from '../../../../util/texture/layout.js';
+import { BeginEndRange, SubresourceRange } from '../../../../util/texture/subresource.js';
+import {
+ PerTexelComponent,
+ kTexelRepresentationInfo,
+} from '../../../../util/texture/texel_data.js';
+
+export enum UninitializeMethod {
+ Creation = 'Creation', // The texture was just created. It is uninitialized.
+ StoreOpClear = 'StoreOpClear', // The texture was rendered to with GPUStoreOp "clear"
+}
+const kUninitializeMethods = Object.keys(UninitializeMethod) as UninitializeMethod[];
+
+export const enum ReadMethod {
+ Sample = 'Sample', // The texture is sampled from
+ CopyToBuffer = 'CopyToBuffer', // The texture is copied to a buffer
+ CopyToTexture = 'CopyToTexture', // The texture is copied to another texture
+ DepthTest = 'DepthTest', // The texture is read as a depth buffer
+ StencilTest = 'StencilTest', // The texture is read as a stencil buffer
+ ColorBlending = 'ColorBlending', // Read the texture by blending as a color attachment
+ Storage = 'Storage', // Read the texture as a storage texture
+}
+
+// Test with these mip level counts
+type MipLevels = 1 | 5;
+const kMipLevelCounts: MipLevels[] = [1, 5];
+
+// For each mip level count, define the mip ranges to leave uninitialized.
+const kUninitializedMipRangesToTest: { [k in MipLevels]: BeginEndRange[] } = {
+ 1: [{ begin: 0, end: 1 }], // Test the only mip
+ 5: [
+ { begin: 0, end: 2 },
+ { begin: 3, end: 4 },
+ ], // Test a range and a single mip
+};
+
+// Test with these sample counts.
+const kSampleCounts: number[] = [1, 4];
+
+// Test with these layer counts.
+type LayerCounts = 1 | 7;
+
+// For each layer count, define the layers to leave uninitialized.
+const kUninitializedLayerRangesToTest: { [k in LayerCounts]: BeginEndRange[] } = {
+ 1: [{ begin: 0, end: 1 }], // Test the only layer
+ 7: [
+ { begin: 2, end: 4 },
+ { begin: 6, end: 7 },
+ ], // Test a range and a single layer
+};
+
+// Enums to abstract over color / depth / stencil values in textures. Depending on the texture format,
+// the data for each value may have a different representation. These enums are converted to a
+// representation such that their values can be compared. ex.) An integer is needed to upload to an
+// unsigned normalized format, but its value is read as a float in the shader.
+export const enum InitializedState {
+ Canary, // Set on initialized subresources. It should stay the same. On discarded resources, we should observe zero.
+ Zero, // We check that uninitialized subresources are in this state when read back.
+}
+
+const initializedStateAsFloat = {
+ [InitializedState.Zero]: 0,
+ [InitializedState.Canary]: 1,
+};
+
+const initializedStateAsUint = {
+ [InitializedState.Zero]: 0,
+ [InitializedState.Canary]: 1,
+};
+
+const initializedStateAsSint = {
+ [InitializedState.Zero]: 0,
+ [InitializedState.Canary]: -1,
+};
+
+function initializedStateAsColor(
+ state: InitializedState,
+ format: GPUTextureFormat
+): [number, number, number, number] {
+ let value;
+ if (format.indexOf('uint') !== -1) {
+ value = initializedStateAsUint[state];
+ } else if (format.indexOf('sint') !== -1) {
+ value = initializedStateAsSint[state];
+ } else {
+ value = initializedStateAsFloat[state];
+ }
+ return [value, value, value, value];
+}
+
+const initializedStateAsDepth = {
+ [InitializedState.Zero]: 0,
+ [InitializedState.Canary]: 0.8,
+};
+
+const initializedStateAsStencil = {
+ [InitializedState.Zero]: 0,
+ [InitializedState.Canary]: 42,
+};
+
+function allAspectsCopyDst(info: (typeof kTextureFormatInfo)[UncompressedTextureFormat]) {
+ return (
+ (!info.color || info.color.copyDst) &&
+ (!info.depth || info.depth.copyDst) &&
+ (!info.stencil || info.stencil.copyDst)
+ );
+}
+
+export function getRequiredTextureUsage(
+ format: UncompressedTextureFormat,
+ sampleCount: number,
+ uninitializeMethod: UninitializeMethod,
+ readMethod: ReadMethod
+): GPUTextureUsageFlags {
+ let usage: GPUTextureUsageFlags = GPUConst.TextureUsage.COPY_DST;
+
+ switch (uninitializeMethod) {
+ case UninitializeMethod.Creation:
+ break;
+ case UninitializeMethod.StoreOpClear:
+ usage |= GPUConst.TextureUsage.RENDER_ATTACHMENT;
+ break;
+ default:
+ unreachable();
+ }
+
+ switch (readMethod) {
+ case ReadMethod.CopyToBuffer:
+ case ReadMethod.CopyToTexture:
+ usage |= GPUConst.TextureUsage.COPY_SRC;
+ break;
+ case ReadMethod.Sample:
+ usage |= GPUConst.TextureUsage.TEXTURE_BINDING;
+ break;
+ case ReadMethod.Storage:
+ usage |= GPUConst.TextureUsage.STORAGE_BINDING;
+ break;
+ case ReadMethod.DepthTest:
+ case ReadMethod.StencilTest:
+ case ReadMethod.ColorBlending:
+ usage |= GPUConst.TextureUsage.RENDER_ATTACHMENT;
+ break;
+ default:
+ unreachable();
+ }
+
+ if (sampleCount > 1) {
+ // Copies to multisampled textures are not allowed. We need OutputAttachment to initialize
+ // canary data in multisampled textures.
+ usage |= GPUConst.TextureUsage.RENDER_ATTACHMENT;
+ }
+
+ const info = kTextureFormatInfo[format];
+ if (!allAspectsCopyDst(info)) {
+ // Copies are not possible. We need OutputAttachment to initialize
+ // canary data.
+ if (info.color) assert(!!info.colorRender, 'not implemented for non-renderable color');
+ usage |= GPUConst.TextureUsage.RENDER_ATTACHMENT;
+ }
+
+ return usage;
+}
+
+export class TextureZeroInitTest extends GPUTest {
+ readonly stateToTexelComponents: { [k in InitializedState]: PerTexelComponent<number> };
+
+ private p: TextureZeroParams;
+ constructor(sharedState: GPUTestSubcaseBatchState, rec: TestCaseRecorder, params: TestParams) {
+ super(sharedState, rec, params);
+ this.p = params as TextureZeroParams;
+
+ const stateToTexelComponents = (state: InitializedState) => {
+ const [R, G, B, A] = initializedStateAsColor(state, this.p.format);
+ return {
+ R,
+ G,
+ B,
+ A,
+ Depth: initializedStateAsDepth[state],
+ Stencil: initializedStateAsStencil[state],
+ };
+ };
+
+ this.stateToTexelComponents = {
+ [InitializedState.Zero]: stateToTexelComponents(InitializedState.Zero),
+ [InitializedState.Canary]: stateToTexelComponents(InitializedState.Canary),
+ };
+ }
+
+ get textureWidth(): number {
+ let width = 1 << this.p.mipLevelCount;
+ if (this.p.nonPowerOfTwo) {
+ width = 2 * width - 1;
+ }
+ return width;
+ }
+
+ get textureHeight(): number {
+ if (this.p.dimension === '1d') {
+ return 1;
+ }
+
+ let height = 1 << this.p.mipLevelCount;
+ if (this.p.nonPowerOfTwo) {
+ height = 2 * height - 1;
+ }
+ return height;
+ }
+
+ get textureDepth(): number {
+ return this.p.dimension === '3d' ? 11 : 1;
+ }
+
+ get textureDepthOrArrayLayers(): number {
+ return this.p.dimension === '2d' ? this.p.layerCount : this.textureDepth;
+ }
+
+ // Used to iterate subresources and check that their uninitialized contents are zero when accessed
+ *iterateUninitializedSubresources(): Generator<SubresourceRange> {
+ for (const mipRange of kUninitializedMipRangesToTest[this.p.mipLevelCount]) {
+ for (const layerRange of kUninitializedLayerRangesToTest[this.p.layerCount]) {
+ yield new SubresourceRange({ mipRange, layerRange });
+ }
+ }
+ }
+
+ // Used to iterate and initialize other subresources not checked for zero-initialization.
+ // Zero-initialization of uninitialized subresources should not have side effects on already
+ // initialized subresources.
+ *iterateInitializedSubresources(): Generator<SubresourceRange> {
+ const uninitialized: boolean[][] = new Array(this.p.mipLevelCount);
+ for (let level = 0; level < uninitialized.length; ++level) {
+ uninitialized[level] = new Array(this.p.layerCount);
+ }
+ for (const subresources of this.iterateUninitializedSubresources()) {
+ for (const { level, layer } of subresources.each()) {
+ uninitialized[level][layer] = true;
+ }
+ }
+ for (let level = 0; level < uninitialized.length; ++level) {
+ for (let layer = 0; layer < uninitialized[level].length; ++layer) {
+ if (!uninitialized[level][layer]) {
+ yield new SubresourceRange({
+ mipRange: { begin: level, count: 1 },
+ layerRange: { begin: layer, count: 1 },
+ });
+ }
+ }
+ }
+ }
+
+ *generateTextureViewDescriptorsForRendering(
+ aspect: GPUTextureAspect,
+ subresourceRange?: SubresourceRange
+ ): Generator<GPUTextureViewDescriptor> {
+ const viewDescriptor: GPUTextureViewDescriptor = {
+ dimension: '2d',
+ aspect,
+ };
+
+ if (subresourceRange === undefined) {
+ return viewDescriptor;
+ }
+
+ for (const { level, layer } of subresourceRange.each()) {
+ yield {
+ ...viewDescriptor,
+ baseMipLevel: level,
+ mipLevelCount: 1,
+ baseArrayLayer: layer,
+ arrayLayerCount: 1,
+ };
+ }
+ }
+
+ private initializeWithStoreOp(
+ state: InitializedState,
+ texture: GPUTexture,
+ subresourceRange?: SubresourceRange
+ ): void {
+ const commandEncoder = this.device.createCommandEncoder();
+ commandEncoder.pushDebugGroup('initializeWithStoreOp');
+
+ for (const viewDescriptor of this.generateTextureViewDescriptorsForRendering(
+ 'all',
+ subresourceRange
+ )) {
+ if (kTextureFormatInfo[this.p.format].color) {
+ commandEncoder
+ .beginRenderPass({
+ colorAttachments: [
+ {
+ view: texture.createView(viewDescriptor),
+ storeOp: 'store',
+ clearValue: initializedStateAsColor(state, this.p.format),
+ loadOp: 'clear',
+ },
+ ],
+ })
+ .end();
+ } else {
+ const depthStencilAttachment: GPURenderPassDepthStencilAttachment = {
+ view: texture.createView(viewDescriptor),
+ };
+ if (kTextureFormatInfo[this.p.format].depth) {
+ depthStencilAttachment.depthClearValue = initializedStateAsDepth[state];
+ depthStencilAttachment.depthLoadOp = 'clear';
+ depthStencilAttachment.depthStoreOp = 'store';
+ }
+ if (kTextureFormatInfo[this.p.format].stencil) {
+ depthStencilAttachment.stencilClearValue = initializedStateAsStencil[state];
+ depthStencilAttachment.stencilLoadOp = 'clear';
+ depthStencilAttachment.stencilStoreOp = 'store';
+ }
+ commandEncoder
+ .beginRenderPass({
+ colorAttachments: [],
+ depthStencilAttachment,
+ })
+ .end();
+ }
+ }
+
+ commandEncoder.popDebugGroup();
+ this.queue.submit([commandEncoder.finish()]);
+ }
+
+ private initializeWithCopy(
+ texture: GPUTexture,
+ state: InitializedState,
+ subresourceRange: SubresourceRange
+ ): void {
+ assert(this.p.format in kTextureFormatInfo);
+ const format = this.p.format as EncodableTextureFormat;
+
+ const firstSubresource = subresourceRange.each().next().value;
+ assert(typeof firstSubresource !== 'undefined');
+
+ const [largestWidth, largestHeight, largestDepth] = virtualMipSize(
+ this.p.dimension,
+ [this.textureWidth, this.textureHeight, this.textureDepth],
+ firstSubresource.level
+ );
+
+ const rep = kTexelRepresentationInfo[format];
+ const texelData = new Uint8Array(rep.pack(rep.encode(this.stateToTexelComponents[state])));
+ const { buffer, bytesPerRow, rowsPerImage } = createTextureUploadBuffer(
+ texelData,
+ this.device,
+ format,
+ this.p.dimension,
+ [largestWidth, largestHeight, largestDepth]
+ );
+
+ const commandEncoder = this.device.createCommandEncoder();
+
+ for (const { level, layer } of subresourceRange.each()) {
+ const [width, height, depth] = virtualMipSize(
+ this.p.dimension,
+ [this.textureWidth, this.textureHeight, this.textureDepth],
+ level
+ );
+
+ commandEncoder.copyBufferToTexture(
+ {
+ buffer,
+ bytesPerRow,
+ rowsPerImage,
+ },
+ { texture, mipLevel: level, origin: { x: 0, y: 0, z: layer } },
+ { width, height, depthOrArrayLayers: depth }
+ );
+ }
+ this.queue.submit([commandEncoder.finish()]);
+ buffer.destroy();
+ }
+
+ initializeTexture(
+ texture: GPUTexture,
+ state: InitializedState,
+ subresourceRange: SubresourceRange
+ ): void {
+ const info = kTextureFormatInfo[this.p.format];
+ if (this.p.sampleCount > 1 || !allAspectsCopyDst(info)) {
+ // Copies to multisampled textures not yet specified.
+ // Use a storeOp for now.
+ if (info.color) assert(!!info.colorRender, 'not implemented for non-renderable color');
+ this.initializeWithStoreOp(state, texture, subresourceRange);
+ } else {
+ this.initializeWithCopy(texture, state, subresourceRange);
+ }
+ }
+
+ discardTexture(texture: GPUTexture, subresourceRange: SubresourceRange): void {
+ const commandEncoder = this.device.createCommandEncoder();
+ commandEncoder.pushDebugGroup('discardTexture');
+
+ for (const desc of this.generateTextureViewDescriptorsForRendering('all', subresourceRange)) {
+ if (kTextureFormatInfo[this.p.format].color) {
+ commandEncoder
+ .beginRenderPass({
+ colorAttachments: [
+ {
+ view: texture.createView(desc),
+ storeOp: 'discard',
+ loadOp: 'load',
+ },
+ ],
+ })
+ .end();
+ } else {
+ const depthStencilAttachment: GPURenderPassDepthStencilAttachment = {
+ view: texture.createView(desc),
+ };
+ if (kTextureFormatInfo[this.p.format].depth) {
+ depthStencilAttachment.depthLoadOp = 'load';
+ depthStencilAttachment.depthStoreOp = 'discard';
+ }
+ if (kTextureFormatInfo[this.p.format].stencil) {
+ depthStencilAttachment.stencilLoadOp = 'load';
+ depthStencilAttachment.stencilStoreOp = 'discard';
+ }
+ commandEncoder
+ .beginRenderPass({
+ colorAttachments: [],
+ depthStencilAttachment,
+ })
+ .end();
+ }
+ }
+
+ commandEncoder.popDebugGroup();
+ this.queue.submit([commandEncoder.finish()]);
+ }
+}
+
+export const kTestParams = kUnitCaseParamsBuilder
+ .combine('dimension', kTextureDimensions)
+ .combine('readMethod', [
+ ReadMethod.CopyToBuffer,
+ ReadMethod.CopyToTexture,
+ ReadMethod.Sample,
+ ReadMethod.DepthTest,
+ ReadMethod.StencilTest,
+ ])
+ // [3] compressed formats
+ .combine('format', kUncompressedTextureFormats)
+ .filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format))
+ .beginSubcases()
+ .combine('aspect', kTextureAspects)
+ .unless(({ readMethod, format, aspect }) => {
+ const info = kTextureFormatInfo[format];
+ return (
+ (readMethod === ReadMethod.DepthTest && (!info.depth || aspect === 'stencil-only')) ||
+ (readMethod === ReadMethod.StencilTest && (!info.stencil || aspect === 'depth-only')) ||
+ (readMethod === ReadMethod.ColorBlending && !info.color) ||
+ // [1]: Test with depth/stencil sampling
+ (readMethod === ReadMethod.Sample && (!!info.depth || !!info.stencil)) ||
+ (aspect === 'depth-only' && !info.depth) ||
+ (aspect === 'stencil-only' && !info.stencil) ||
+ (aspect === 'all' && !!info.depth && !!info.stencil) ||
+ // Cannot copy from a packed depth format.
+ // [2]: Test copying out of the stencil aspect.
+ ((readMethod === ReadMethod.CopyToBuffer || readMethod === ReadMethod.CopyToTexture) &&
+ (format === 'depth24plus' || format === 'depth24plus-stencil8'))
+ );
+ })
+ .combine('mipLevelCount', kMipLevelCounts)
+ // 1D texture can only have a single mip level
+ .unless(p => p.dimension === '1d' && p.mipLevelCount !== 1)
+ .combine('sampleCount', kSampleCounts)
+ .unless(
+ ({ readMethod, sampleCount }) =>
+ // We can only read from multisampled textures by sampling.
+ sampleCount > 1 &&
+ (readMethod === ReadMethod.CopyToBuffer || readMethod === ReadMethod.CopyToTexture)
+ )
+ // Multisampled textures may only have one mip
+ .unless(({ sampleCount, mipLevelCount }) => sampleCount > 1 && mipLevelCount > 1)
+ .combine('uninitializeMethod', kUninitializeMethods)
+ .unless(({ dimension, readMethod, uninitializeMethod, format, sampleCount }) => {
+ const formatInfo = kTextureFormatInfo[format];
+ return (
+ dimension !== '2d' &&
+ (sampleCount > 1 ||
+ !!formatInfo.depth ||
+ !!formatInfo.stencil ||
+ readMethod === ReadMethod.DepthTest ||
+ readMethod === ReadMethod.StencilTest ||
+ readMethod === ReadMethod.ColorBlending ||
+ uninitializeMethod === UninitializeMethod.StoreOpClear)
+ );
+ })
+ .expandWithParams(function* ({ dimension }) {
+ switch (dimension) {
+ case '2d':
+ yield { layerCount: 1 as LayerCounts };
+ yield { layerCount: 7 as LayerCounts };
+ break;
+ case '1d':
+ case '3d':
+ yield { layerCount: 1 as LayerCounts };
+ break;
+ }
+ })
+ // Multisampled 3D / 2D array textures not supported.
+ .unless(({ sampleCount, layerCount }) => sampleCount > 1 && layerCount > 1)
+ .unless(({ format, sampleCount, uninitializeMethod, readMethod }) => {
+ const usage = getRequiredTextureUsage(format, sampleCount, uninitializeMethod, readMethod);
+ const info = kTextureFormatInfo[format];
+
+ return (
+ ((usage & GPUConst.TextureUsage.RENDER_ATTACHMENT) !== 0 &&
+ info.color &&
+ !info.colorRender) ||
+ ((usage & GPUConst.TextureUsage.STORAGE_BINDING) !== 0 && !info.color?.storage) ||
+ (sampleCount > 1 && !info.multisample)
+ );
+ })
+ .combine('nonPowerOfTwo', [false, true])
+ .combine('canaryOnCreation', [false, true]);
+
+type TextureZeroParams = ParamTypeOf<typeof kTestParams>;
+
+export type CheckContents = (
+ t: TextureZeroInitTest,
+ params: TextureZeroParams,
+ texture: GPUTexture,
+ state: InitializedState,
+ subresourceRange: SubresourceRange
+) => void;
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/texture_zero.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/texture_zero.spec.ts
index 3f0baeccbd..ee4c141aef 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/texture_zero.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/texture_zero.spec.ts
@@ -7,550 +7,9 @@ TODO:
- test compressed texture formats [3]
`;
-// MAINTENANCE_TODO: This is a test file, it probably shouldn't export anything.
-// Everything that's exported should be moved to another file.
-
-import { TestCaseRecorder, TestParams } from '../../../../common/framework/fixture.js';
-import {
- kUnitCaseParamsBuilder,
- ParamTypeOf,
-} from '../../../../common/framework/params_builder.js';
import { makeTestGroup } from '../../../../common/framework/test_group.js';
-import { assert, unreachable } from '../../../../common/util/util.js';
-import { kTextureAspects, kTextureDimensions } from '../../../capability_info.js';
-import { GPUConst } from '../../../constants.js';
-import {
- kTextureFormatInfo,
- kUncompressedTextureFormats,
- textureDimensionAndFormatCompatible,
- UncompressedTextureFormat,
- EncodableTextureFormat,
-} from '../../../format_info.js';
-import { GPUTest, GPUTestSubcaseBatchState } from '../../../gpu_test.js';
-import { virtualMipSize } from '../../../util/texture/base.js';
-import { createTextureUploadBuffer } from '../../../util/texture/layout.js';
-import { BeginEndRange, SubresourceRange } from '../../../util/texture/subresource.js';
-import { PerTexelComponent, kTexelRepresentationInfo } from '../../../util/texture/texel_data.js';
-
-export enum UninitializeMethod {
- Creation = 'Creation', // The texture was just created. It is uninitialized.
- StoreOpClear = 'StoreOpClear', // The texture was rendered to with GPUStoreOp "clear"
-}
-const kUninitializeMethods = Object.keys(UninitializeMethod) as UninitializeMethod[];
-
-export const enum ReadMethod {
- Sample = 'Sample', // The texture is sampled from
- CopyToBuffer = 'CopyToBuffer', // The texture is copied to a buffer
- CopyToTexture = 'CopyToTexture', // The texture is copied to another texture
- DepthTest = 'DepthTest', // The texture is read as a depth buffer
- StencilTest = 'StencilTest', // The texture is read as a stencil buffer
- ColorBlending = 'ColorBlending', // Read the texture by blending as a color attachment
- Storage = 'Storage', // Read the texture as a storage texture
-}
-
-// Test with these mip level counts
-type MipLevels = 1 | 5;
-const kMipLevelCounts: MipLevels[] = [1, 5];
-
-// For each mip level count, define the mip ranges to leave uninitialized.
-const kUninitializedMipRangesToTest: { [k in MipLevels]: BeginEndRange[] } = {
- 1: [{ begin: 0, end: 1 }], // Test the only mip
- 5: [
- { begin: 0, end: 2 },
- { begin: 3, end: 4 },
- ], // Test a range and a single mip
-};
-
-// Test with these sample counts.
-const kSampleCounts: number[] = [1, 4];
-
-// Test with these layer counts.
-type LayerCounts = 1 | 7;
-
-// For each layer count, define the layers to leave uninitialized.
-const kUninitializedLayerRangesToTest: { [k in LayerCounts]: BeginEndRange[] } = {
- 1: [{ begin: 0, end: 1 }], // Test the only layer
- 7: [
- { begin: 2, end: 4 },
- { begin: 6, end: 7 },
- ], // Test a range and a single layer
-};
-
-// Enums to abstract over color / depth / stencil values in textures. Depending on the texture format,
-// the data for each value may have a different representation. These enums are converted to a
-// representation such that their values can be compared. ex.) An integer is needed to upload to an
-// unsigned normalized format, but its value is read as a float in the shader.
-export const enum InitializedState {
- Canary, // Set on initialized subresources. It should stay the same. On discarded resources, we should observe zero.
- Zero, // We check that uninitialized subresources are in this state when read back.
-}
-
-const initializedStateAsFloat = {
- [InitializedState.Zero]: 0,
- [InitializedState.Canary]: 1,
-};
-
-const initializedStateAsUint = {
- [InitializedState.Zero]: 0,
- [InitializedState.Canary]: 1,
-};
-
-const initializedStateAsSint = {
- [InitializedState.Zero]: 0,
- [InitializedState.Canary]: -1,
-};
-
-function initializedStateAsColor(
- state: InitializedState,
- format: GPUTextureFormat
-): [number, number, number, number] {
- let value;
- if (format.indexOf('uint') !== -1) {
- value = initializedStateAsUint[state];
- } else if (format.indexOf('sint') !== -1) {
- value = initializedStateAsSint[state];
- } else {
- value = initializedStateAsFloat[state];
- }
- return [value, value, value, value];
-}
-
-const initializedStateAsDepth = {
- [InitializedState.Zero]: 0,
- [InitializedState.Canary]: 0.8,
-};
-
-const initializedStateAsStencil = {
- [InitializedState.Zero]: 0,
- [InitializedState.Canary]: 42,
-};
-
-function getRequiredTextureUsage(
- format: UncompressedTextureFormat,
- sampleCount: number,
- uninitializeMethod: UninitializeMethod,
- readMethod: ReadMethod
-): GPUTextureUsageFlags {
- let usage: GPUTextureUsageFlags = GPUConst.TextureUsage.COPY_DST;
-
- switch (uninitializeMethod) {
- case UninitializeMethod.Creation:
- break;
- case UninitializeMethod.StoreOpClear:
- usage |= GPUConst.TextureUsage.RENDER_ATTACHMENT;
- break;
- default:
- unreachable();
- }
-
- switch (readMethod) {
- case ReadMethod.CopyToBuffer:
- case ReadMethod.CopyToTexture:
- usage |= GPUConst.TextureUsage.COPY_SRC;
- break;
- case ReadMethod.Sample:
- usage |= GPUConst.TextureUsage.TEXTURE_BINDING;
- break;
- case ReadMethod.Storage:
- usage |= GPUConst.TextureUsage.STORAGE_BINDING;
- break;
- case ReadMethod.DepthTest:
- case ReadMethod.StencilTest:
- case ReadMethod.ColorBlending:
- usage |= GPUConst.TextureUsage.RENDER_ATTACHMENT;
- break;
- default:
- unreachable();
- }
-
- if (sampleCount > 1) {
- // Copies to multisampled textures are not allowed. We need OutputAttachment to initialize
- // canary data in multisampled textures.
- usage |= GPUConst.TextureUsage.RENDER_ATTACHMENT;
- }
-
- if (!kTextureFormatInfo[format].copyDst) {
- // Copies are not possible. We need OutputAttachment to initialize
- // canary data.
- assert(kTextureFormatInfo[format].renderable);
- usage |= GPUConst.TextureUsage.RENDER_ATTACHMENT;
- }
-
- return usage;
-}
-
-export class TextureZeroInitTest extends GPUTest {
- readonly stateToTexelComponents: { [k in InitializedState]: PerTexelComponent<number> };
-
- private p: TextureZeroParams;
- constructor(sharedState: GPUTestSubcaseBatchState, rec: TestCaseRecorder, params: TestParams) {
- super(sharedState, rec, params);
- this.p = params as TextureZeroParams;
-
- const stateToTexelComponents = (state: InitializedState) => {
- const [R, G, B, A] = initializedStateAsColor(state, this.p.format);
- return {
- R,
- G,
- B,
- A,
- Depth: initializedStateAsDepth[state],
- Stencil: initializedStateAsStencil[state],
- };
- };
-
- this.stateToTexelComponents = {
- [InitializedState.Zero]: stateToTexelComponents(InitializedState.Zero),
- [InitializedState.Canary]: stateToTexelComponents(InitializedState.Canary),
- };
- }
-
- get textureWidth(): number {
- let width = 1 << this.p.mipLevelCount;
- if (this.p.nonPowerOfTwo) {
- width = 2 * width - 1;
- }
- return width;
- }
-
- get textureHeight(): number {
- if (this.p.dimension === '1d') {
- return 1;
- }
-
- let height = 1 << this.p.mipLevelCount;
- if (this.p.nonPowerOfTwo) {
- height = 2 * height - 1;
- }
- return height;
- }
-
- get textureDepth(): number {
- return this.p.dimension === '3d' ? 11 : 1;
- }
-
- get textureDepthOrArrayLayers(): number {
- return this.p.dimension === '2d' ? this.p.layerCount : this.textureDepth;
- }
-
- // Used to iterate subresources and check that their uninitialized contents are zero when accessed
- *iterateUninitializedSubresources(): Generator<SubresourceRange> {
- for (const mipRange of kUninitializedMipRangesToTest[this.p.mipLevelCount]) {
- for (const layerRange of kUninitializedLayerRangesToTest[this.p.layerCount]) {
- yield new SubresourceRange({ mipRange, layerRange });
- }
- }
- }
-
- // Used to iterate and initialize other subresources not checked for zero-initialization.
- // Zero-initialization of uninitialized subresources should not have side effects on already
- // initialized subresources.
- *iterateInitializedSubresources(): Generator<SubresourceRange> {
- const uninitialized: boolean[][] = new Array(this.p.mipLevelCount);
- for (let level = 0; level < uninitialized.length; ++level) {
- uninitialized[level] = new Array(this.p.layerCount);
- }
- for (const subresources of this.iterateUninitializedSubresources()) {
- for (const { level, layer } of subresources.each()) {
- uninitialized[level][layer] = true;
- }
- }
- for (let level = 0; level < uninitialized.length; ++level) {
- for (let layer = 0; layer < uninitialized[level].length; ++layer) {
- if (!uninitialized[level][layer]) {
- yield new SubresourceRange({
- mipRange: { begin: level, count: 1 },
- layerRange: { begin: layer, count: 1 },
- });
- }
- }
- }
- }
-
- *generateTextureViewDescriptorsForRendering(
- aspect: GPUTextureAspect,
- subresourceRange?: SubresourceRange
- ): Generator<GPUTextureViewDescriptor> {
- const viewDescriptor: GPUTextureViewDescriptor = {
- dimension: '2d',
- aspect,
- };
-
- if (subresourceRange === undefined) {
- return viewDescriptor;
- }
-
- for (const { level, layer } of subresourceRange.each()) {
- yield {
- ...viewDescriptor,
- baseMipLevel: level,
- mipLevelCount: 1,
- baseArrayLayer: layer,
- arrayLayerCount: 1,
- };
- }
- }
-
- private initializeWithStoreOp(
- state: InitializedState,
- texture: GPUTexture,
- subresourceRange?: SubresourceRange
- ): void {
- const commandEncoder = this.device.createCommandEncoder();
- commandEncoder.pushDebugGroup('initializeWithStoreOp');
-
- for (const viewDescriptor of this.generateTextureViewDescriptorsForRendering(
- 'all',
- subresourceRange
- )) {
- if (kTextureFormatInfo[this.p.format].color) {
- commandEncoder
- .beginRenderPass({
- colorAttachments: [
- {
- view: texture.createView(viewDescriptor),
- storeOp: 'store',
- clearValue: initializedStateAsColor(state, this.p.format),
- loadOp: 'clear',
- },
- ],
- })
- .end();
- } else {
- const depthStencilAttachment: GPURenderPassDepthStencilAttachment = {
- view: texture.createView(viewDescriptor),
- };
- if (kTextureFormatInfo[this.p.format].depth) {
- depthStencilAttachment.depthClearValue = initializedStateAsDepth[state];
- depthStencilAttachment.depthLoadOp = 'clear';
- depthStencilAttachment.depthStoreOp = 'store';
- }
- if (kTextureFormatInfo[this.p.format].stencil) {
- depthStencilAttachment.stencilClearValue = initializedStateAsStencil[state];
- depthStencilAttachment.stencilLoadOp = 'clear';
- depthStencilAttachment.stencilStoreOp = 'store';
- }
- commandEncoder
- .beginRenderPass({
- colorAttachments: [],
- depthStencilAttachment,
- })
- .end();
- }
- }
-
- commandEncoder.popDebugGroup();
- this.queue.submit([commandEncoder.finish()]);
- }
-
- private initializeWithCopy(
- texture: GPUTexture,
- state: InitializedState,
- subresourceRange: SubresourceRange
- ): void {
- assert(this.p.format in kTextureFormatInfo);
- const format = this.p.format as EncodableTextureFormat;
-
- const firstSubresource = subresourceRange.each().next().value;
- assert(typeof firstSubresource !== 'undefined');
-
- const [largestWidth, largestHeight, largestDepth] = virtualMipSize(
- this.p.dimension,
- [this.textureWidth, this.textureHeight, this.textureDepth],
- firstSubresource.level
- );
-
- const rep = kTexelRepresentationInfo[format];
- const texelData = new Uint8Array(rep.pack(rep.encode(this.stateToTexelComponents[state])));
- const { buffer, bytesPerRow, rowsPerImage } = createTextureUploadBuffer(
- texelData,
- this.device,
- format,
- this.p.dimension,
- [largestWidth, largestHeight, largestDepth]
- );
-
- const commandEncoder = this.device.createCommandEncoder();
-
- for (const { level, layer } of subresourceRange.each()) {
- const [width, height, depth] = virtualMipSize(
- this.p.dimension,
- [this.textureWidth, this.textureHeight, this.textureDepth],
- level
- );
-
- commandEncoder.copyBufferToTexture(
- {
- buffer,
- bytesPerRow,
- rowsPerImage,
- },
- { texture, mipLevel: level, origin: { x: 0, y: 0, z: layer } },
- { width, height, depthOrArrayLayers: depth }
- );
- }
- this.queue.submit([commandEncoder.finish()]);
- buffer.destroy();
- }
-
- initializeTexture(
- texture: GPUTexture,
- state: InitializedState,
- subresourceRange: SubresourceRange
- ): void {
- if (this.p.sampleCount > 1 || !kTextureFormatInfo[this.p.format].copyDst) {
- // Copies to multisampled textures not yet specified.
- // Use a storeOp for now.
- assert(kTextureFormatInfo[this.p.format].renderable);
- this.initializeWithStoreOp(state, texture, subresourceRange);
- } else {
- this.initializeWithCopy(texture, state, subresourceRange);
- }
- }
-
- discardTexture(texture: GPUTexture, subresourceRange: SubresourceRange): void {
- const commandEncoder = this.device.createCommandEncoder();
- commandEncoder.pushDebugGroup('discardTexture');
-
- for (const desc of this.generateTextureViewDescriptorsForRendering('all', subresourceRange)) {
- if (kTextureFormatInfo[this.p.format].color) {
- commandEncoder
- .beginRenderPass({
- colorAttachments: [
- {
- view: texture.createView(desc),
- storeOp: 'discard',
- loadOp: 'load',
- },
- ],
- })
- .end();
- } else {
- const depthStencilAttachment: GPURenderPassDepthStencilAttachment = {
- view: texture.createView(desc),
- };
- if (kTextureFormatInfo[this.p.format].depth) {
- depthStencilAttachment.depthLoadOp = 'load';
- depthStencilAttachment.depthStoreOp = 'discard';
- }
- if (kTextureFormatInfo[this.p.format].stencil) {
- depthStencilAttachment.stencilLoadOp = 'load';
- depthStencilAttachment.stencilStoreOp = 'discard';
- }
- commandEncoder
- .beginRenderPass({
- colorAttachments: [],
- depthStencilAttachment,
- })
- .end();
- }
- }
-
- commandEncoder.popDebugGroup();
- this.queue.submit([commandEncoder.finish()]);
- }
-}
-
-const kTestParams = kUnitCaseParamsBuilder
- .combine('dimension', kTextureDimensions)
- .combine('readMethod', [
- ReadMethod.CopyToBuffer,
- ReadMethod.CopyToTexture,
- ReadMethod.Sample,
- ReadMethod.DepthTest,
- ReadMethod.StencilTest,
- ])
- // [3] compressed formats
- .combine('format', kUncompressedTextureFormats)
- .filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format))
- .beginSubcases()
- .combine('aspect', kTextureAspects)
- .unless(({ readMethod, format, aspect }) => {
- const info = kTextureFormatInfo[format];
- return (
- (readMethod === ReadMethod.DepthTest && (!info.depth || aspect === 'stencil-only')) ||
- (readMethod === ReadMethod.StencilTest && (!info.stencil || aspect === 'depth-only')) ||
- (readMethod === ReadMethod.ColorBlending && !info.color) ||
- // [1]: Test with depth/stencil sampling
- (readMethod === ReadMethod.Sample && (!!info.depth || !!info.stencil)) ||
- (aspect === 'depth-only' && !info.depth) ||
- (aspect === 'stencil-only' && !info.stencil) ||
- (aspect === 'all' && !!info.depth && !!info.stencil) ||
- // Cannot copy from a packed depth format.
- // [2]: Test copying out of the stencil aspect.
- ((readMethod === ReadMethod.CopyToBuffer || readMethod === ReadMethod.CopyToTexture) &&
- (format === 'depth24plus' || format === 'depth24plus-stencil8'))
- );
- })
- .combine('mipLevelCount', kMipLevelCounts)
- // 1D texture can only have a single mip level
- .unless(p => p.dimension === '1d' && p.mipLevelCount !== 1)
- .combine('sampleCount', kSampleCounts)
- .unless(
- ({ readMethod, sampleCount }) =>
- // We can only read from multisampled textures by sampling.
- sampleCount > 1 &&
- (readMethod === ReadMethod.CopyToBuffer || readMethod === ReadMethod.CopyToTexture)
- )
- // Multisampled textures may only have one mip
- .unless(({ sampleCount, mipLevelCount }) => sampleCount > 1 && mipLevelCount > 1)
- .combine('uninitializeMethod', kUninitializeMethods)
- .unless(({ dimension, readMethod, uninitializeMethod, format, sampleCount }) => {
- const formatInfo = kTextureFormatInfo[format];
- return (
- dimension !== '2d' &&
- (sampleCount > 1 ||
- !!formatInfo.depth ||
- !!formatInfo.stencil ||
- readMethod === ReadMethod.DepthTest ||
- readMethod === ReadMethod.StencilTest ||
- readMethod === ReadMethod.ColorBlending ||
- uninitializeMethod === UninitializeMethod.StoreOpClear)
- );
- })
- .expandWithParams(function* ({ dimension }) {
- switch (dimension) {
- case '2d':
- yield { layerCount: 1 as LayerCounts };
- yield { layerCount: 7 as LayerCounts };
- break;
- case '1d':
- case '3d':
- yield { layerCount: 1 as LayerCounts };
- break;
- }
- })
- // Multisampled 3D / 2D array textures not supported.
- .unless(({ sampleCount, layerCount }) => sampleCount > 1 && layerCount > 1)
- .unless(({ format, sampleCount, uninitializeMethod, readMethod }) => {
- const usage = getRequiredTextureUsage(format, sampleCount, uninitializeMethod, readMethod);
- const info = kTextureFormatInfo[format];
-
- return (
- ((usage & GPUConst.TextureUsage.RENDER_ATTACHMENT) !== 0 && !info.renderable) ||
- ((usage & GPUConst.TextureUsage.STORAGE_BINDING) !== 0 && !info.color?.storage) ||
- (sampleCount > 1 && !info.multisample)
- );
- })
- .combine('nonPowerOfTwo', [false, true])
- .combine('canaryOnCreation', [false, true])
- .filter(({ canaryOnCreation, format }) => {
- // We can only initialize the texture if it's encodable or renderable.
- const canInitialize = format in kTextureFormatInfo || kTextureFormatInfo[format].renderable;
-
- // Filter out cases where we want canary values but can't initialize.
- return !canaryOnCreation || canInitialize;
- });
-
-type TextureZeroParams = ParamTypeOf<typeof kTestParams>;
-
-export type CheckContents = (
- t: TextureZeroInitTest,
- params: TextureZeroParams,
- texture: GPUTexture,
- state: InitializedState,
- subresourceRange: SubresourceRange
-) => void;
+import { unreachable } from '../../../../common/util/util.js';
+import { kTextureFormatInfo } from '../../../format_info.js';
import { checkContentsByBufferCopy, checkContentsByTextureCopy } from './check_texture/by_copy.js';
import {
@@ -558,6 +17,15 @@ import {
checkContentsByStencilTest,
} from './check_texture/by_ds_test.js';
import { checkContentsBySampling } from './check_texture/by_sampling.js';
+import {
+ getRequiredTextureUsage,
+ ReadMethod,
+ CheckContents,
+ TextureZeroInitTest,
+ kTestParams,
+ UninitializeMethod,
+ InitializedState,
+} from './check_texture/texture_zero_init_test.js';
const checkContentsImpl: { [k in ReadMethod]: CheckContents } = {
Sample: checkContentsBySampling,
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/shader_module/compilation_info.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/shader_module/compilation_info.spec.ts
index 93fa4575c4..3382dabc37 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/shader_module/compilation_info.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/shader_module/compilation_info.spec.ts
@@ -64,6 +64,17 @@ const kInvalidShaderSources = [
return unknown(0.0, 0.0, 0.0, 1.0);
}`,
},
+ {
+ valid: false,
+ name: 'unicode-multi-byte-characters',
+ _errorLine: 1,
+ // This shader is simplistic enough to always result in the same error position.
+ // Generally, various backends may choose to report the error at different positions within the
+ // line, so it's difficult to meaningfully validate them.
+ _errorLinePos: 19,
+ _code: `/*🐈🐈🐈🐈🐈🐈🐈*/?
+// Expected Error: invalid character found`,
+ },
];
const kAllShaderSources = [...kValidShaderSources, ...kInvalidShaderSources];
@@ -174,7 +185,7 @@ g.test('line_number_and_position')
.combine('sourceMapName', kSourceMapsKeys)
)
.fn(async t => {
- const { _code, _errorLine, sourceMapName } = t.params;
+ const { _code, _errorLine, _errorLinePos, sourceMapName } = t.params;
const shaderModule = t.expectGPUError('validation', () => {
const sourceMap = kSourceMaps[sourceMapName];
@@ -191,14 +202,22 @@ g.test('line_number_and_position')
// If a line is reported, it should point at the correct line (1-based).
t.expect(
(message.lineNum === 0) === (message.linePos === 0),
- "GPUCompilationMessages that don't report a line number should not report a line position."
+ `Got message.lineNum ${message.lineNum}, .linePos ${message.linePos}, but GPUCompilationMessage should specify both or neither`
);
- if (message.lineNum === 0 || message.lineNum === _errorLine) {
+ if (message.lineNum === 0) {
foundAppropriateError = true;
+ break;
+ }
- // Various backends may choose to report the error at different positions within the line,
- // so it's difficult to meaningfully validate them.
+ if (message.lineNum === _errorLine) {
+ foundAppropriateError = true;
+ if (_errorLinePos !== undefined) {
+ t.expect(
+ message.linePos === _errorLinePos,
+ `Got message.linePos ${message.linePos}, expected ${_errorLinePos}`
+ );
+ }
break;
}
}
@@ -239,10 +258,9 @@ g.test('offset_and_length')
for (const message of info.messages) {
// Any offsets and lengths should reference valid spans of the shader code.
- t.expect(message.offset <= _code.length, 'Message offset should be within the shader source');
t.expect(
- message.offset + message.length <= _code.length,
- 'Message offset and length should be within the shader source'
+ message.offset <= _code.length && message.offset + message.length <= _code.length,
+ 'message.offset and .length should be within the shader source'
);
// If a valid line number and position are given, the offset should point the the same
@@ -255,9 +273,10 @@ g.test('offset_and_length')
lineOffset += 1;
}
+ const expectedOffset = lineOffset + message.linePos - 1;
t.expect(
- message.offset === lineOffset + message.linePos - 1,
- 'lineNum and linePos should point to the same location as offset'
+ message.offset === expectedOffset,
+ `message.lineNum (${message.lineNum}) and .linePos (${message.linePos}) point to a different offset (${lineOffset} + ${message.linePos} - 1 = ${expectedOffset}) than .offset (${message.offset})`
);
}
}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/storage_texture/read_only.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/storage_texture/read_only.spec.ts
new file mode 100644
index 0000000000..978924aabd
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/storage_texture/read_only.spec.ts
@@ -0,0 +1,626 @@
+export const description = `
+Tests for the behavior of read-only storage textures.
+
+TODO:
+- Test mipmap level > 0
+- Test resource usage transitions with read-only storage textures
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { unreachable, assert } from '../../../../common/util/util.js';
+import { Float16Array } from '../../../../external/petamoriken/float16/float16.js';
+import { kTextureDimensions } from '../../../capability_info.js';
+import {
+ ColorTextureFormat,
+ kColorTextureFormats,
+ kTextureFormatInfo,
+} from '../../../format_info.js';
+import { GPUTest } from '../../../gpu_test.js';
+import { kValidShaderStages, TValidShaderStage } from '../../../util/shader.js';
+
+function ComponentCount(format: ColorTextureFormat): number {
+ switch (format) {
+ case 'r32float':
+ case 'r32sint':
+ case 'r32uint':
+ return 1;
+ case 'rg32float':
+ case 'rg32sint':
+ case 'rg32uint':
+ return 2;
+ case 'rgba32float':
+ case 'rgba32sint':
+ case 'rgba32uint':
+ case 'rgba8sint':
+ case 'rgba8uint':
+ case 'rgba8snorm':
+ case 'rgba8unorm':
+ case 'rgba16float':
+ case 'rgba16sint':
+ case 'rgba16uint':
+ case 'bgra8unorm':
+ return 4;
+ default:
+ unreachable();
+ return 0;
+ }
+}
+
+class F extends GPUTest {
+ InitTextureAndGetExpectedOutputBufferData(
+ storageTexture: GPUTexture,
+ format: ColorTextureFormat
+ ): ArrayBuffer {
+ const bytesPerBlock = kTextureFormatInfo[format].color.bytes;
+ assert(bytesPerBlock !== undefined);
+
+ const width = storageTexture.width;
+ const height = storageTexture.height;
+ const depthOrArrayLayers = storageTexture.depthOrArrayLayers;
+
+ const texelData = new ArrayBuffer(bytesPerBlock * width * height * depthOrArrayLayers);
+ const texelTypedDataView = this.GetTypedArrayBufferViewForTexelData(texelData, format);
+ const componentCount = ComponentCount(format);
+ const outputBufferData = new ArrayBuffer(4 * 4 * width * height * depthOrArrayLayers);
+ const outputBufferTypedData = this.GetTypedArrayBufferForOutputBufferData(
+ outputBufferData,
+ format
+ );
+
+ const SetData = (
+ texelValue: number,
+ outputValue: number,
+ texelDataIndex: number,
+ component: number,
+ outputComponent: number = component
+ ) => {
+ const texelComponentIndex = texelDataIndex * componentCount + component;
+ texelTypedDataView[texelComponentIndex] = texelValue;
+ const outputTexelComponentIndex = texelDataIndex * 4 + outputComponent;
+ outputBufferTypedData[outputTexelComponentIndex] = outputValue;
+ };
+ for (let z = 0; z < depthOrArrayLayers; ++z) {
+ for (let y = 0; y < height; ++y) {
+ for (let x = 0; x < width; ++x) {
+ const texelDataIndex = z * width * height + y * width + x;
+ outputBufferTypedData[4 * texelDataIndex] = 0;
+ outputBufferTypedData[4 * texelDataIndex + 1] = 0;
+ outputBufferTypedData[4 * texelDataIndex + 2] = 0;
+ outputBufferTypedData[4 * texelDataIndex + 3] = 1;
+ for (let component = 0; component < componentCount; ++component) {
+ switch (format) {
+ case 'r32uint':
+ case 'rg32uint':
+ case 'rgba16uint':
+ case 'rgba32uint': {
+ const texelValue = 4 * texelDataIndex + component + 1;
+ SetData(texelValue, texelValue, texelDataIndex, component);
+ break;
+ }
+ case 'rgba8uint': {
+ const texelValue = (4 * texelDataIndex + component + 1) % 256;
+ SetData(texelValue, texelValue, texelDataIndex, component);
+ break;
+ }
+ case 'rgba8unorm': {
+ const texelValue = (4 * texelDataIndex + component + 1) % 256;
+ const outputValue = texelValue / 255.0;
+ SetData(texelValue, outputValue, texelDataIndex, component);
+ break;
+ }
+ case 'bgra8unorm': {
+ const texelValue = (4 * texelDataIndex + component + 1) % 256;
+ const outputValue = texelValue / 255.0;
+ // BGRA -> RGBA
+ assert(component < 4);
+ const outputComponent = [2, 1, 0, 3][component];
+ SetData(texelValue, outputValue, texelDataIndex, component, outputComponent);
+ break;
+ }
+ case 'r32sint':
+ case 'rg32sint':
+ case 'rgba16sint':
+ case 'rgba32sint': {
+ const texelValue =
+ (texelDataIndex & 1 ? 1 : -1) * (4 * texelDataIndex + component + 1);
+ SetData(texelValue, texelValue, texelDataIndex, component);
+ break;
+ }
+ case 'rgba8sint': {
+ const texelValue = ((4 * texelDataIndex + component + 1) % 256) - 128;
+ SetData(texelValue, texelValue, texelDataIndex, component);
+ break;
+ }
+ case 'rgba8snorm': {
+ const texelValue = ((4 * texelDataIndex + component + 1) % 256) - 128;
+ const outputValue = Math.max(texelValue / 127.0, -1.0);
+ SetData(texelValue, outputValue, texelDataIndex, component);
+ break;
+ }
+ case 'r32float':
+ case 'rg32float':
+ case 'rgba32float': {
+ const texelValue = (4 * texelDataIndex + component + 1) / 10.0;
+ SetData(texelValue, texelValue, texelDataIndex, component);
+ break;
+ }
+ case 'rgba16float': {
+ const texelValue = (4 * texelDataIndex + component + 1) / 10.0;
+ const f16Array = new Float16Array(1);
+ f16Array[0] = texelValue;
+ SetData(texelValue, f16Array[0], texelDataIndex, component);
+ break;
+ }
+ default:
+ unreachable();
+ break;
+ }
+ }
+ }
+ }
+ }
+ this.queue.writeTexture(
+ {
+ texture: storageTexture,
+ },
+ texelData,
+ {
+ bytesPerRow: bytesPerBlock * width,
+ rowsPerImage: height,
+ },
+ [width, height, depthOrArrayLayers]
+ );
+
+ return outputBufferData;
+ }
+
+ GetTypedArrayBufferForOutputBufferData(arrayBuffer: ArrayBuffer, format: ColorTextureFormat) {
+ switch (kTextureFormatInfo[format].color.type) {
+ case 'uint':
+ return new Uint32Array(arrayBuffer);
+ case 'sint':
+ return new Int32Array(arrayBuffer);
+ case 'float':
+ case 'unfilterable-float':
+ return new Float32Array(arrayBuffer);
+ }
+ }
+
+ GetTypedArrayBufferViewForTexelData(arrayBuffer: ArrayBuffer, format: ColorTextureFormat) {
+ switch (format) {
+ case 'r32uint':
+ case 'rg32uint':
+ case 'rgba32uint':
+ return new Uint32Array(arrayBuffer);
+ case 'rgba8uint':
+ case 'rgba8unorm':
+ case 'bgra8unorm':
+ return new Uint8Array(arrayBuffer);
+ case 'rgba16uint':
+ return new Uint16Array(arrayBuffer);
+ case 'r32sint':
+ case 'rg32sint':
+ case 'rgba32sint':
+ return new Int32Array(arrayBuffer);
+ case 'rgba8sint':
+ case 'rgba8snorm':
+ return new Int8Array(arrayBuffer);
+ case 'rgba16sint':
+ return new Int16Array(arrayBuffer);
+ case 'r32float':
+ case 'rg32float':
+ case 'rgba32float':
+ return new Float32Array(arrayBuffer);
+ case 'rgba16float':
+ return new Float16Array(arrayBuffer);
+ default:
+ unreachable();
+ return new Uint8Array(arrayBuffer);
+ }
+ }
+
+ GetOutputBufferWGSLType(format: ColorTextureFormat) {
+ switch (kTextureFormatInfo[format].color.type) {
+ case 'uint':
+ return 'vec4u';
+ case 'sint':
+ return 'vec4i';
+ case 'float':
+ case 'unfilterable-float':
+ return 'vec4f';
+ default:
+ unreachable();
+ return '';
+ }
+ }
+
+ DoTransform(
+ storageTexture: GPUTexture,
+ shaderStage: TValidShaderStage,
+ format: ColorTextureFormat,
+ outputBuffer: GPUBuffer
+ ) {
+ let declaration = '';
+ switch (storageTexture.dimension) {
+ case '1d':
+ declaration = 'texture_storage_1d';
+ break;
+ case '2d':
+ declaration =
+ storageTexture.depthOrArrayLayers > 1 ? 'texture_storage_2d_array' : 'texture_storage_2d';
+ break;
+ case '3d':
+ declaration = 'texture_storage_3d';
+ break;
+ }
+ const textureDeclaration = `
+ @group(0) @binding(0) var readOnlyTexture: ${declaration}<${format}, read>;
+ `;
+ const bindingResourceDeclaration = `
+ ${textureDeclaration}
+ @group(0) @binding(1)
+ var<storage,read_write> outputBuffer : array<${this.GetOutputBufferWGSLType(format)}>;
+ `;
+
+ const bindGroupEntries = [
+ {
+ binding: 0,
+ resource: storageTexture.createView(),
+ },
+ {
+ binding: 1,
+ resource: {
+ buffer: outputBuffer,
+ },
+ },
+ ];
+
+ const commandEncoder = this.device.createCommandEncoder();
+
+ switch (shaderStage) {
+ case 'compute': {
+ let textureLoadCoord = '';
+ switch (storageTexture.dimension) {
+ case '1d':
+ textureLoadCoord = 'invocationID.x';
+ break;
+ case '2d':
+ textureLoadCoord =
+ storageTexture.depthOrArrayLayers > 1
+ ? `vec2u(invocationID.x, invocationID.y), invocationID.z`
+ : `vec2u(invocationID.x, invocationID.y)`;
+ break;
+ case '3d':
+ textureLoadCoord = 'invocationID';
+ break;
+ }
+
+ const computeShader = `
+ ${bindingResourceDeclaration}
+ @compute
+ @workgroup_size(
+ ${storageTexture.width}, ${storageTexture.height}, ${storageTexture.depthOrArrayLayers})
+ fn main(
+ @builtin(local_invocation_id) invocationID: vec3u,
+ @builtin(local_invocation_index) invocationIndex: u32) {
+ let initialValue = textureLoad(readOnlyTexture, ${textureLoadCoord});
+ outputBuffer[invocationIndex] = initialValue;
+ }`;
+ const computePipeline = this.device.createComputePipeline({
+ compute: {
+ module: this.device.createShaderModule({
+ code: computeShader,
+ }),
+ },
+ layout: 'auto',
+ });
+ const bindGroup = this.device.createBindGroup({
+ layout: computePipeline.getBindGroupLayout(0),
+ entries: bindGroupEntries,
+ });
+
+ const computePassEncoder = commandEncoder.beginComputePass();
+ computePassEncoder.setPipeline(computePipeline);
+ computePassEncoder.setBindGroup(0, bindGroup);
+ computePassEncoder.dispatchWorkgroups(1);
+ computePassEncoder.end();
+ break;
+ }
+ case 'fragment': {
+ let textureLoadCoord = '';
+ switch (storageTexture.dimension) {
+ case '1d':
+ textureLoadCoord = 'textureCoord.x';
+ break;
+ case '2d':
+ textureLoadCoord =
+ storageTexture.depthOrArrayLayers > 1 ? 'textureCoord, z' : 'textureCoord';
+ break;
+ case '3d':
+ textureLoadCoord = 'vec3u(textureCoord, z)';
+ break;
+ }
+
+ const fragmentShader = `
+ ${bindingResourceDeclaration}
+ @fragment
+ fn main(@builtin(position) fragCoord: vec4f) -> @location(0) vec4f {
+ let textureCoord = vec2u(fragCoord.xy);
+ let storageTextureTexelCountPerImage = ${storageTexture.width * storageTexture.height}u;
+ for (var z = 0u; z < ${storageTexture.depthOrArrayLayers}; z++) {
+ let initialValue = textureLoad(readOnlyTexture, ${textureLoadCoord});
+ let outputIndex =
+ storageTextureTexelCountPerImage * z + textureCoord.y * ${storageTexture.width} +
+ textureCoord.x;
+ outputBuffer[outputIndex] = initialValue;
+ }
+ return vec4f(0.0, 1.0, 0.0, 1.0);
+ }`;
+ const vertexShader = `
+ @vertex
+ fn main(@builtin(vertex_index) vertexIndex : u32) -> @builtin(position) vec4f {
+ var pos = array(
+ vec2f(-1.0, -1.0),
+ vec2f(-1.0, 1.0),
+ vec2f( 1.0, -1.0),
+ vec2f(-1.0, 1.0),
+ vec2f( 1.0, -1.0),
+ vec2f( 1.0, 1.0));
+ return vec4f(pos[vertexIndex], 0.0, 1.0);
+ }
+ `;
+ const renderPipeline = this.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: this.device.createShaderModule({
+ code: vertexShader,
+ }),
+ },
+ fragment: {
+ module: this.device.createShaderModule({
+ code: fragmentShader,
+ }),
+ targets: [
+ {
+ format: 'rgba8unorm',
+ },
+ ],
+ },
+ primitive: {
+ topology: 'triangle-list',
+ },
+ });
+
+ const bindGroup = this.device.createBindGroup({
+ layout: renderPipeline.getBindGroupLayout(0),
+ entries: bindGroupEntries,
+ });
+
+ const placeholderColorTexture = this.device.createTexture({
+ size: [storageTexture.width, storageTexture.height, 1],
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ format: 'rgba8unorm',
+ });
+ this.trackForCleanup(placeholderColorTexture);
+
+ const renderPassEncoder = commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: placeholderColorTexture.createView(),
+ loadOp: 'clear',
+ clearValue: { r: 0, g: 0, b: 0, a: 0 },
+ storeOp: 'store',
+ },
+ ],
+ });
+ renderPassEncoder.setPipeline(renderPipeline);
+ renderPassEncoder.setBindGroup(0, bindGroup);
+ renderPassEncoder.draw(6);
+ renderPassEncoder.end();
+ break;
+ }
+ case 'vertex': {
+ // For each texel location (coordX, coordY), draw one point at (coordX + 0.5, coordY + 0.5)
+ // in the storageTexture.width * storageTexture.height grid, and save all the texel values
+ // at (coordX, coordY, z) (z >= 0 && z < storageTexture.depthOrArrayLayers) into the
+ // corresponding vertex shader outputs.
+ let vertexOutputs = '';
+ for (let layer = 0; layer < storageTexture.depthOrArrayLayers; ++layer) {
+ vertexOutputs = vertexOutputs.concat(
+ `
+ @location(${layer + 1}) @interpolate(flat)
+ vertex_out${layer}: ${this.GetOutputBufferWGSLType(format)},`
+ );
+ }
+
+ let loadFromTextureWGSL = '';
+ switch (storageTexture.dimension) {
+ case '1d':
+ loadFromTextureWGSL = `
+ output.vertex_out0 = textureLoad(readOnlyTexture, coordX);`;
+ break;
+ case '2d':
+ if (storageTexture.depthOrArrayLayers === 1) {
+ loadFromTextureWGSL = `
+ output.vertex_out0 = textureLoad(readOnlyTexture, vec2u(coordX, coordY));`;
+ } else {
+ for (let z = 0; z < storageTexture.depthOrArrayLayers; ++z) {
+ loadFromTextureWGSL = loadFromTextureWGSL.concat(`
+ output.vertex_out${z} =
+ textureLoad(readOnlyTexture, vec2u(coordX, coordY), ${z});`);
+ }
+ }
+ break;
+ case '3d':
+ for (let z = 0; z < storageTexture.depthOrArrayLayers; ++z) {
+ loadFromTextureWGSL = loadFromTextureWGSL.concat(`
+ output.vertex_out${z} = textureLoad(readOnlyTexture, vec3u(coordX, coordY, ${z}));`);
+ }
+ break;
+ }
+
+ let outputToBufferWGSL = '';
+ for (let layer = 0; layer < storageTexture.depthOrArrayLayers; ++layer) {
+ outputToBufferWGSL = outputToBufferWGSL.concat(
+ `
+ let outputIndex${layer} =
+ storageTextureTexelCountPerImage * ${layer}u +
+ fragmentInput.tex_coord.y * ${storageTexture.width}u + fragmentInput.tex_coord.x;
+ outputBuffer[outputIndex${layer}] = fragmentInput.vertex_out${layer};`
+ );
+ }
+
+ const shader = `
+ ${bindingResourceDeclaration}
+ struct VertexOutput {
+ @builtin(position) my_pos: vec4f,
+ @location(0) @interpolate(flat) tex_coord: vec2u,
+ ${vertexOutputs}
+ }
+ @vertex
+ fn vs_main(@builtin(vertex_index) vertexIndex : u32) -> VertexOutput {
+ var output : VertexOutput;
+ let coordX = vertexIndex % ${storageTexture.width}u;
+ let coordY = vertexIndex / ${storageTexture.width}u;
+ // Each vertex in the mesh take an even step along X axis from -1.0 to 1.0.
+ let posXStep = f32(${2.0 / storageTexture.width});
+ // As well as along Y axis.
+ let posYStep = f32(${2.0 / storageTexture.height});
+ // And the vertex located in the middle of the step, i.e. with a bias of 0.5 step.
+ let outputPosX = -1.0 + posXStep * 0.5 + posXStep * f32(coordX);
+ let outputPosY = -1.0 + posYStep * 0.5 + posYStep * f32(coordY);
+ output.my_pos = vec4f(outputPosX, outputPosY, 0.0, 1.0);
+ output.tex_coord = vec2u(coordX, coordY);
+ ${loadFromTextureWGSL}
+ return output;
+ }
+ @fragment
+ fn fs_main(fragmentInput : VertexOutput) -> @location(0) vec4f {
+ let storageTextureTexelCountPerImage = ${storageTexture.width * storageTexture.height}u;
+ ${outputToBufferWGSL}
+ return vec4f(0.0, 1.0, 0.0, 1.0);
+ }
+ `;
+
+ const renderPipeline = this.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: this.device.createShaderModule({
+ code: shader,
+ }),
+ },
+ fragment: {
+ module: this.device.createShaderModule({
+ code: shader,
+ }),
+ targets: [
+ {
+ format: 'rgba8unorm',
+ },
+ ],
+ },
+ primitive: {
+ topology: 'point-list',
+ },
+ });
+
+ const bindGroup = this.device.createBindGroup({
+ layout: renderPipeline.getBindGroupLayout(0),
+ entries: bindGroupEntries,
+ });
+
+ const placeholderColorTexture = this.device.createTexture({
+ size: [storageTexture.width, storageTexture.height, 1],
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ format: 'rgba8unorm',
+ });
+ this.trackForCleanup(placeholderColorTexture);
+
+ const renderPassEncoder = commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: placeholderColorTexture.createView(),
+ loadOp: 'clear',
+ clearValue: { r: 0, g: 0, b: 0, a: 0 },
+ storeOp: 'store',
+ },
+ ],
+ });
+ renderPassEncoder.setPipeline(renderPipeline);
+ renderPassEncoder.setBindGroup(0, bindGroup);
+ renderPassEncoder.draw(storageTexture.width * storageTexture.height);
+ renderPassEncoder.end();
+ break;
+ }
+ }
+
+ this.queue.submit([commandEncoder.finish()]);
+ }
+}
+
+export const g = makeTestGroup(F);
+
+g.test('basic')
+ .desc(
+ `The basic functionality tests for read-only storage textures. In the test we read data from
+ the read-only storage texture, write the data into an output storage buffer, and check if the
+ data in the output storage buffer is exactly what we expect.`
+ )
+ .params(u =>
+ u
+ .combine('format', kColorTextureFormats)
+ .filter(
+ p => p.format === 'bgra8unorm' || kTextureFormatInfo[p.format].color?.storage === true
+ )
+ .combine('shaderStage', kValidShaderStages)
+ .combine('dimension', kTextureDimensions)
+ .combine('depthOrArrayLayers', [1, 2] as const)
+ .unless(p => p.dimension === '1d' && p.depthOrArrayLayers > 1)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.format === 'bgra8unorm') {
+ t.selectDeviceOrSkipTestCase('bgra8unorm-storage');
+ }
+ if (t.isCompatibility) {
+ t.skipIfTextureFormatNotUsableAsStorageTexture(t.params.format);
+ }
+ })
+ .fn(t => {
+ const { format, shaderStage, dimension, depthOrArrayLayers } = t.params;
+
+ const kWidth = 8;
+ const height = dimension === '1d' ? 1 : 8;
+ const storageTexture = t.device.createTexture({
+ format,
+ dimension,
+ size: [kWidth, height, depthOrArrayLayers],
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST | GPUTextureUsage.STORAGE_BINDING,
+ });
+ t.trackForCleanup(storageTexture);
+
+ const expectedData = t.InitTextureAndGetExpectedOutputBufferData(storageTexture, format);
+
+ const outputBuffer = t.device.createBuffer({
+ size: 4 * 4 * kWidth * height * depthOrArrayLayers,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE,
+ });
+ t.trackForCleanup(outputBuffer);
+
+ t.DoTransform(storageTexture, shaderStage, format, outputBuffer);
+
+ switch (kTextureFormatInfo[format].color.type) {
+ case 'uint':
+ t.expectGPUBufferValuesEqual(outputBuffer, new Uint32Array(expectedData));
+ break;
+ case 'sint':
+ t.expectGPUBufferValuesEqual(outputBuffer, new Int32Array(expectedData));
+ break;
+ case 'float':
+ case 'unfilterable-float':
+ t.expectGPUBufferValuesEqual(outputBuffer, new Float32Array(expectedData));
+ break;
+ default:
+ unreachable();
+ break;
+ }
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/storage_texture/read_write.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/storage_texture/read_write.spec.ts
new file mode 100644
index 0000000000..9eb04b2b45
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/storage_texture/read_write.spec.ts
@@ -0,0 +1,385 @@
+export const description = `
+Tests for the behavior of read-write storage textures.
+
+TODO:
+- Test resource usage transitions with read-write storage textures
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { assert, unreachable } from '../../../../common/util/util.js';
+import { kTextureDimensions } from '../../../capability_info.js';
+import { kColorTextureFormats, kTextureFormatInfo } from '../../../format_info.js';
+import { GPUTest } from '../../../gpu_test.js';
+import { align } from '../../../util/math.js';
+
+const kShaderStagesForReadWriteStorageTexture = ['fragment', 'compute'] as const;
+type ShaderStageForReadWriteStorageTexture =
+ (typeof kShaderStagesForReadWriteStorageTexture)[number];
+
+class F extends GPUTest {
+ GetInitialData(storageTexture: GPUTexture): ArrayBuffer {
+ const format = storageTexture.format;
+ const bytesPerBlock = kTextureFormatInfo[format].bytesPerBlock;
+ assert(bytesPerBlock !== undefined);
+
+ const width = storageTexture.width;
+ const height = storageTexture.height;
+ const depthOrArrayLayers = storageTexture.depthOrArrayLayers;
+ const initialData = new ArrayBuffer(bytesPerBlock * width * height * depthOrArrayLayers);
+ const initialTypedData = this.GetTypedArrayBuffer(initialData, format);
+ for (let z = 0; z < depthOrArrayLayers; ++z) {
+ for (let y = 0; y < height; ++y) {
+ for (let x = 0; x < width; ++x) {
+ const index = z * width * height + y * width + x;
+ switch (format) {
+ case 'r32sint':
+ initialTypedData[index] = (index & 1 ? 1 : -1) * (2 * index + 1);
+ break;
+ case 'r32uint':
+ initialTypedData[index] = 2 * index + 1;
+ break;
+ case 'r32float':
+ initialTypedData[index] = (2 * index + 1) / 10.0;
+ break;
+ }
+ }
+ }
+ }
+ return initialData;
+ }
+
+ GetTypedArrayBuffer(arrayBuffer: ArrayBuffer, format: GPUTextureFormat) {
+ switch (format) {
+ case 'r32sint':
+ return new Int32Array(arrayBuffer);
+ case 'r32uint':
+ return new Uint32Array(arrayBuffer);
+ case 'r32float':
+ return new Float32Array(arrayBuffer);
+ default:
+ unreachable();
+ return new Uint8Array(arrayBuffer);
+ }
+ }
+
+ GetExpectedData(
+ shaderStage: ShaderStageForReadWriteStorageTexture,
+ storageTexture: GPUTexture,
+ initialData: ArrayBuffer
+ ): ArrayBuffer {
+ const format = storageTexture.format;
+ const bytesPerBlock = kTextureFormatInfo[format].bytesPerBlock;
+ assert(bytesPerBlock !== undefined);
+
+ const width = storageTexture.width;
+ const height = storageTexture.height;
+ const depthOrArrayLayers = storageTexture.depthOrArrayLayers;
+ const bytesPerRowAlignment = align(bytesPerBlock * width, 256);
+ const itemsPerRow = bytesPerRowAlignment / bytesPerBlock;
+
+ const expectedData = new ArrayBuffer(
+ bytesPerRowAlignment * (height * depthOrArrayLayers - 1) + bytesPerBlock * width
+ );
+ const expectedTypedData = this.GetTypedArrayBuffer(expectedData, format);
+ const initialTypedData = this.GetTypedArrayBuffer(initialData, format);
+ for (let z = 0; z < depthOrArrayLayers; ++z) {
+ for (let y = 0; y < height; ++y) {
+ for (let x = 0; x < width; ++x) {
+ const expectedIndex = z * itemsPerRow * height + y * itemsPerRow + x;
+ switch (shaderStage) {
+ case 'compute': {
+ // In the compute shader we flip the texture along the diagonal.
+ const initialIndex =
+ (depthOrArrayLayers - 1 - z) * width * height +
+ (height - 1 - y) * width +
+ (width - 1 - x);
+ expectedTypedData[expectedIndex] = initialTypedData[initialIndex];
+ break;
+ }
+ case 'fragment': {
+ // In the fragment shader we double the original texel value of the read-write storage
+ // texture.
+ const initialIndex = z * width * height + y * width + x;
+ expectedTypedData[expectedIndex] = initialTypedData[initialIndex] * 2;
+ break;
+ }
+ }
+ }
+ }
+ }
+ return expectedData;
+ }
+
+ RecordCommandsToTransform(
+ device: GPUDevice,
+ shaderStage: ShaderStageForReadWriteStorageTexture,
+ commandEncoder: GPUCommandEncoder,
+ rwTexture: GPUTexture
+ ) {
+ let declaration = '';
+ switch (rwTexture.dimension) {
+ case '1d':
+ declaration = 'texture_storage_1d';
+ break;
+ case '2d':
+ declaration =
+ rwTexture.depthOrArrayLayers > 1 ? 'texture_storage_2d_array' : 'texture_storage_2d';
+ break;
+ case '3d':
+ declaration = 'texture_storage_3d';
+ break;
+ }
+ const textureDeclaration = `
+ @group(0) @binding(0) var rwTexture: ${declaration}<${rwTexture.format}, read_write>;
+ `;
+
+ switch (shaderStage) {
+ case 'fragment': {
+ const vertexShader = `
+ @vertex
+ fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4f {
+ var pos = array(
+ vec2f(-1.0, -1.0),
+ vec2f(-1.0, 1.0),
+ vec2f( 1.0, -1.0),
+ vec2f(-1.0, 1.0),
+ vec2f( 1.0, -1.0),
+ vec2f( 1.0, 1.0));
+ return vec4f(pos[VertexIndex], 0.0, 1.0);
+ }
+ `;
+ let textureLoadStoreCoord = '';
+ switch (rwTexture.dimension) {
+ case '1d':
+ textureLoadStoreCoord = 'textureCoord.x';
+ break;
+ case '2d':
+ textureLoadStoreCoord =
+ rwTexture.depthOrArrayLayers > 1 ? 'textureCoord, z' : 'textureCoord';
+ break;
+ case '3d':
+ textureLoadStoreCoord = 'vec3u(textureCoord, z)';
+ break;
+ }
+ const fragmentShader = `
+ ${textureDeclaration}
+ @fragment
+ fn main(@builtin(position) fragCoord: vec4f) -> @location(0) vec4f {
+ let textureCoord = vec2u(fragCoord.xy);
+
+ for (var z = 0u; z < ${rwTexture.depthOrArrayLayers}; z++) {
+ let initialValue = textureLoad(rwTexture, ${textureLoadStoreCoord});
+ let outputValue = initialValue * 2;
+ textureStore(rwTexture, ${textureLoadStoreCoord}, outputValue);
+ }
+
+ return vec4f(0.0, 1.0, 0.0, 1.0);
+ }
+ `;
+ const renderPipeline = device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: device.createShaderModule({
+ code: vertexShader,
+ }),
+ },
+ fragment: {
+ module: device.createShaderModule({
+ code: fragmentShader,
+ }),
+ targets: [
+ {
+ format: 'rgba8unorm',
+ },
+ ],
+ },
+ primitive: {
+ topology: 'triangle-list',
+ },
+ });
+
+ const bindGroup = device.createBindGroup({
+ layout: renderPipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: rwTexture.createView(),
+ },
+ ],
+ });
+
+ const placeholderColorTexture = device.createTexture({
+ size: [rwTexture.width, rwTexture.height, 1],
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ format: 'rgba8unorm',
+ });
+ this.trackForCleanup(placeholderColorTexture);
+
+ const renderPassEncoder = commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: placeholderColorTexture.createView(),
+ loadOp: 'clear',
+ clearValue: { r: 0, g: 0, b: 0, a: 0 },
+ storeOp: 'store',
+ },
+ ],
+ });
+ renderPassEncoder.setPipeline(renderPipeline);
+ renderPassEncoder.setBindGroup(0, bindGroup);
+ renderPassEncoder.draw(6);
+ renderPassEncoder.end();
+ break;
+ }
+ case 'compute': {
+ let textureLoadCoord = '';
+ let textureStoreCoord = '';
+ switch (rwTexture.dimension) {
+ case '1d':
+ textureLoadCoord = 'dimension - 1u - invocationID.x';
+ textureStoreCoord = 'invocationID.x';
+ break;
+ case '2d':
+ textureLoadCoord =
+ rwTexture.depthOrArrayLayers > 1
+ ? `vec2u(dimension.x - 1u - invocationID.x, dimension.y - 1u - invocationID.y),
+ textureNumLayers(rwTexture) - 1u - invocationID.z`
+ : `vec2u(dimension.x - 1u - invocationID.x, dimension.y - 1u - invocationID.y)`;
+ textureStoreCoord =
+ rwTexture.depthOrArrayLayers > 1
+ ? 'invocationID.xy, invocationID.z'
+ : 'invocationID.xy';
+ break;
+ case '3d':
+ textureLoadCoord = `
+ vec3u(dimension.x - 1u - invocationID.x, dimension.y - 1u - invocationID.y,
+ dimension.z - 1u - invocationID.z)`;
+ textureStoreCoord = 'invocationID';
+ break;
+ }
+
+ const computeShader = `
+ ${textureDeclaration}
+ @compute
+ @workgroup_size(${rwTexture.width}, ${rwTexture.height}, ${rwTexture.depthOrArrayLayers})
+ fn main(@builtin(local_invocation_id) invocationID: vec3u) {
+ let dimension = textureDimensions(rwTexture);
+
+ let initialValue = textureLoad(rwTexture, ${textureLoadCoord});
+ textureBarrier();
+
+ textureStore(rwTexture, ${textureStoreCoord}, initialValue);
+ }`;
+
+ const computePipeline = device.createComputePipeline({
+ compute: {
+ module: device.createShaderModule({
+ code: computeShader,
+ }),
+ },
+ layout: 'auto',
+ });
+ const bindGroup = device.createBindGroup({
+ layout: computePipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: rwTexture.createView(),
+ },
+ ],
+ });
+ const computePassEncoder = commandEncoder.beginComputePass();
+ computePassEncoder.setPipeline(computePipeline);
+ computePassEncoder.setBindGroup(0, bindGroup);
+ computePassEncoder.dispatchWorkgroups(1);
+ computePassEncoder.end();
+ break;
+ }
+ }
+ }
+}
+
+export const g = makeTestGroup(F);
+
+g.test('basic')
+ .desc(
+ `The basic functionality tests for read-write storage textures. In the test we read data from
+ the read-write storage texture, do transforms and write the data back to the read-write storage
+ texture. textureBarrier() is also called in the tests using compute pipelines.`
+ )
+ .params(u =>
+ u
+ .combine('format', kColorTextureFormats)
+ .filter(p => kTextureFormatInfo[p.format].color?.readWriteStorage === true)
+ .combine('shaderStage', kShaderStagesForReadWriteStorageTexture)
+ .combine('textureDimension', kTextureDimensions)
+ .combine('depthOrArrayLayers', [1, 2] as const)
+ .unless(p => p.textureDimension === '1d' && p.depthOrArrayLayers > 1)
+ )
+ .beforeAllSubcases(t => {
+ t.skipIfTextureFormatNotUsableAsStorageTexture(t.params.format);
+ })
+ .fn(t => {
+ const { format, shaderStage, textureDimension, depthOrArrayLayers } = t.params;
+
+ // In compatibility mode the lowest maxComputeInvocationsPerWorkgroup is 128 vs non-compat which is 256
+ // So in non-compat we get 16 * 8 * 2, vs compat where we get 8 * 8 * 2
+ const kWidth = t.isCompatibility ? 8 : 16;
+ const height = textureDimension === '1d' ? 1 : 8;
+ const textureSize = [kWidth, height, depthOrArrayLayers] as const;
+ const storageTexture = t.device.createTexture({
+ format,
+ dimension: textureDimension,
+ size: textureSize,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST | GPUTextureUsage.STORAGE_BINDING,
+ });
+ t.trackForCleanup(storageTexture);
+
+ const bytesPerBlock = kTextureFormatInfo[format].bytesPerBlock;
+ const initialData = t.GetInitialData(storageTexture);
+ t.queue.writeTexture(
+ { texture: storageTexture },
+ initialData,
+ {
+ bytesPerRow: bytesPerBlock * kWidth,
+ rowsPerImage: height,
+ },
+ textureSize
+ );
+
+ const commandEncoder = t.device.createCommandEncoder();
+
+ t.RecordCommandsToTransform(t.device, shaderStage, commandEncoder, storageTexture);
+
+ const expectedData = t.GetExpectedData(shaderStage, storageTexture, initialData);
+ const readbackBuffer = t.device.createBuffer({
+ size: expectedData.byteLength,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
+ });
+ t.trackForCleanup(readbackBuffer);
+ const bytesPerRow = align(bytesPerBlock * kWidth, 256);
+ commandEncoder.copyTextureToBuffer(
+ {
+ texture: storageTexture,
+ },
+ {
+ buffer: readbackBuffer,
+ bytesPerRow,
+ rowsPerImage: height,
+ },
+ textureSize
+ );
+ t.queue.submit([commandEncoder.finish()]);
+
+ switch (format) {
+ case 'r32sint':
+ t.expectGPUBufferValuesEqual(readbackBuffer, new Int32Array(expectedData));
+ break;
+ case 'r32uint':
+ t.expectGPUBufferValuesEqual(readbackBuffer, new Uint32Array(expectedData));
+ break;
+ case 'r32float':
+ t.expectGPUBufferValuesEqual(readbackBuffer, new Float32Array(expectedData));
+ break;
+ }
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/texture_view/format_reinterpretation.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/texture_view/format_reinterpretation.spec.ts
index c032415327..f7fb49818e 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/texture_view/format_reinterpretation.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/texture_view/format_reinterpretation.spec.ts
@@ -100,12 +100,15 @@ g.test('texture_binding')
.combine('format', kRegularTextureFormats)
.combine('viewFormat', kRegularTextureFormats)
.filter(
- ({ format, viewFormat }) => format !== viewFormat && viewCompatible(format, viewFormat)
+ ({ format, viewFormat }) =>
+ format !== viewFormat && viewCompatible(false, format, viewFormat)
)
)
.beforeAllSubcases(t => {
const { format, viewFormat } = t.params;
t.skipIfTextureFormatNotSupported(format, viewFormat);
+ // Compatibility mode does not support format reinterpretation.
+ t.skipIf(t.isCompatibility);
})
.fn(t => {
const { format, viewFormat } = t.params;
@@ -200,13 +203,16 @@ in view format and match in base format.`
.combine('format', kRenderableColorTextureFormats)
.combine('viewFormat', kRenderableColorTextureFormats)
.filter(
- ({ format, viewFormat }) => format !== viewFormat && viewCompatible(format, viewFormat)
+ ({ format, viewFormat }) =>
+ format !== viewFormat && viewCompatible(false, format, viewFormat)
)
.combine('sampleCount', [1, 4])
)
.beforeAllSubcases(t => {
const { format, viewFormat } = t.params;
t.skipIfTextureFormatNotSupported(format, viewFormat);
+ // Compatibility mode does not support format reinterpretation.
+ t.skipIf(t.isCompatibility);
})
.fn(t => {
const { format, viewFormat, sampleCount } = t.params;
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/texture_view/write.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/texture_view/write.spec.ts
index 0340121334..b4ce6f4cec 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/texture_view/write.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/texture_view/write.spec.ts
@@ -1,6 +1,9 @@
export const description = `
Test the result of writing textures through texture views with various options.
+Reads value from a shader array, writes the value via various write methods.
+Check the texture result with the expected texel view.
+
All x= every possible view write method: {
- storage write {fragment, compute}
- render pass store
@@ -13,20 +16,358 @@ TODO: Write helper for this if not already available (see resource_init, buffer_
`;
import { makeTestGroup } from '../../../../common/framework/test_group.js';
-import { GPUTest } from '../../../gpu_test.js';
+import { unreachable } from '../../../../common/util/util.js';
+import {
+ kRegularTextureFormats,
+ kTextureFormatInfo,
+ RegularTextureFormat,
+} from '../../../format_info.js';
+import { GPUTest, TextureTestMixin } from '../../../gpu_test.js';
+import { kFullscreenQuadVertexShaderCode } from '../../../util/shader.js';
+import { TexelView } from '../../../util/texture/texel_view.js';
+
+export const g = makeTestGroup(TextureTestMixin(GPUTest));
+
+const kTextureViewWriteMethods = [
+ 'storage-write-fragment',
+ 'storage-write-compute',
+ 'render-pass-store',
+ 'render-pass-resolve',
+] as const;
+type TextureViewWriteMethod = (typeof kTextureViewWriteMethods)[number];
+
+// Src color values to read from a shader array.
+const kColorsFloat = [
+ { R: 1.0, G: 0.0, B: 0.0, A: 0.8 },
+ { R: 0.0, G: 1.0, B: 0.0, A: 0.7 },
+ { R: 0.0, G: 0.0, B: 0.0, A: 0.6 },
+ { R: 0.0, G: 0.0, B: 0.0, A: 0.5 },
+ { R: 1.0, G: 1.0, B: 1.0, A: 0.4 },
+ { R: 0.7, G: 0.0, B: 0.0, A: 0.3 },
+ { R: 0.0, G: 0.8, B: 0.0, A: 0.2 },
+ { R: 0.0, G: 0.0, B: 0.9, A: 0.1 },
+ { R: 0.1, G: 0.2, B: 0.0, A: 0.3 },
+ { R: 0.4, G: 0.3, B: 0.6, A: 0.8 },
+];
+
+function FloatToIntColor(c: number) {
+ return Math.floor(c * 100);
+}
+
+const kColorsInt = kColorsFloat.map(c => {
+ return {
+ R: FloatToIntColor(c.R),
+ G: FloatToIntColor(c.G),
+ B: FloatToIntColor(c.B),
+ A: FloatToIntColor(c.A),
+ };
+});
-export const g = makeTestGroup(GPUTest);
+const kTextureSize = 16;
+
+function writeTextureAndGetExpectedTexelView(
+ t: GPUTest,
+ method: TextureViewWriteMethod,
+ view: GPUTextureView,
+ format: RegularTextureFormat,
+ sampleCount: number
+) {
+ const info = kTextureFormatInfo[format];
+ const isFloatType = info.color.type === 'float' || info.color.type === 'unfilterable-float';
+ const kColors = isFloatType ? kColorsFloat : kColorsInt;
+ const expectedTexelView = TexelView.fromTexelsAsColors(
+ format,
+ coords => {
+ const pixelPos = coords.y * kTextureSize + coords.x;
+ return kColors[pixelPos % kColors.length];
+ },
+ { clampToFormatRange: true }
+ );
+ const vecType = isFloatType ? 'vec4f' : info.color.type === 'sint' ? 'vec4i' : 'vec4u';
+ const kColorArrayShaderString = `array<${vecType}, ${kColors.length}>(
+ ${kColors.map(t => `${vecType}(${t.R}, ${t.G}, ${t.B}, ${t.A}) `).join(',')}
+ )`;
+
+ switch (method) {
+ case 'storage-write-compute':
+ {
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code: `
+ @group(0) @binding(0) var dst: texture_storage_2d<${format}, write>;
+ @compute @workgroup_size(1, 1) fn main(
+ @builtin(global_invocation_id) global_id: vec3<u32>,
+ ) {
+ const src = ${kColorArrayShaderString};
+ let coord = vec2u(global_id.xy);
+ let idx = coord.x + coord.y * ${kTextureSize};
+ textureStore(dst, coord, src[idx % ${kColors.length}]);
+ }`,
+ }),
+ entryPoint: 'main',
+ },
+ });
+ const commandEncoder = t.device.createCommandEncoder();
+ const pass = commandEncoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(
+ 0,
+ t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: view,
+ },
+ ],
+ })
+ );
+ pass.dispatchWorkgroups(kTextureSize, kTextureSize);
+ pass.end();
+ t.device.queue.submit([commandEncoder.finish()]);
+ }
+ break;
+
+ case 'storage-write-fragment':
+ {
+ // Create a placeholder color attachment texture,
+ // The size of which equals that of format texture we are testing,
+ // so that we have the same number of fragments and texels.
+ const kPlaceholderTextureFormat = 'rgba8unorm';
+ const placeholderTexture = t.trackForCleanup(
+ t.device.createTexture({
+ format: kPlaceholderTextureFormat,
+ size: [kTextureSize, kTextureSize],
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ })
+ );
+
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: kFullscreenQuadVertexShaderCode,
+ }),
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: `
+ @group(0) @binding(0) var dst: texture_storage_2d<${format}, write>;
+ @fragment fn main(
+ @builtin(position) fragCoord: vec4<f32>,
+ ) {
+ const src = ${kColorArrayShaderString};
+ let coord = vec2u(fragCoord.xy);
+ let idx = coord.x + coord.y * ${kTextureSize};
+ textureStore(dst, coord, src[idx % ${kColors.length}]);
+ }`,
+ }),
+ // Set writeMask to 0 as the fragment shader has no output.
+ targets: [
+ {
+ format: kPlaceholderTextureFormat,
+ writeMask: 0,
+ },
+ ],
+ },
+ });
+ const commandEncoder = t.device.createCommandEncoder();
+ const pass = commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: placeholderTexture.createView(),
+ loadOp: 'clear',
+ storeOp: 'discard',
+ },
+ ],
+ });
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(
+ 0,
+ t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: view,
+ },
+ ],
+ })
+ );
+ pass.draw(6);
+ pass.end();
+ t.device.queue.submit([commandEncoder.finish()]);
+ }
+ break;
+
+ case 'render-pass-store':
+ case 'render-pass-resolve':
+ {
+ // Create a placeholder color attachment texture for the store target when tesing texture is used as resolve target.
+ const targetView =
+ method === 'render-pass-store'
+ ? view
+ : t
+ .trackForCleanup(
+ t.device.createTexture({
+ format,
+ size: [kTextureSize, kTextureSize],
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ sampleCount: 4,
+ })
+ )
+ .createView();
+ const resolveView = method === 'render-pass-store' ? undefined : view;
+ const multisampleCount = method === 'render-pass-store' ? sampleCount : 4;
+
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: kFullscreenQuadVertexShaderCode,
+ }),
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: `
+ @fragment fn main(
+ @builtin(position) fragCoord: vec4<f32>,
+ ) -> @location(0) ${vecType} {
+ const src = ${kColorArrayShaderString};
+ let coord = vec2u(fragCoord.xy);
+ let idx = coord.x + coord.y * ${kTextureSize};
+ return src[idx % ${kColors.length}];
+ }`,
+ }),
+ targets: [
+ {
+ format,
+ },
+ ],
+ },
+ multisample: {
+ count: multisampleCount,
+ },
+ });
+ const commandEncoder = t.device.createCommandEncoder();
+ const pass = commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: targetView,
+ resolveTarget: resolveView,
+ loadOp: 'clear',
+ storeOp: 'store',
+ },
+ ],
+ });
+ pass.setPipeline(pipeline);
+ pass.draw(6);
+ pass.end();
+ t.device.queue.submit([commandEncoder.finish()]);
+ }
+ break;
+ default:
+ unreachable();
+ }
+
+ return expectedTexelView;
+}
g.test('format')
.desc(
`Views of every allowed format.
+Read values from color array in the shader, and write it to the texture view via different write methods.
+
- x= every texture format
- x= sampleCount {1, 4} if valid
- x= every possible view write method (see above)
+
+TODO: Test sampleCount > 1 for 'render-pass-store' after extending copySinglePixelTextureToBufferUsingComputePass
+ to read multiple pixels from multisampled textures. [1]
+TODO: Test rgb10a2uint when TexelRepresentation.numericRange is made per-component. [2]
`
)
- .unimplemented();
+ .params(u =>
+ u //
+ .combine('method', kTextureViewWriteMethods)
+ .combine('format', kRegularTextureFormats)
+ .combine('sampleCount', [1, 4])
+ .filter(({ format, method, sampleCount }) => {
+ const info = kTextureFormatInfo[format];
+
+ if (sampleCount > 1 && !info.multisample) {
+ return false;
+ }
+
+ // [2]
+ if (format === 'rgb10a2uint') {
+ return false;
+ }
+
+ switch (method) {
+ case 'storage-write-compute':
+ case 'storage-write-fragment':
+ return info.color?.storage && sampleCount === 1;
+ case 'render-pass-store':
+ // [1]
+ if (sampleCount > 1) {
+ return false;
+ }
+ return !!info.colorRender;
+ case 'render-pass-resolve':
+ return !!info.colorRender?.resolve && sampleCount === 1;
+ }
+ return true;
+ })
+ )
+ .beforeAllSubcases(t => {
+ const { format, method } = t.params;
+ t.skipIfTextureFormatNotSupported(format);
+
+ switch (method) {
+ case 'storage-write-compute':
+ case 'storage-write-fragment':
+ // Still need to filter again for compat mode.
+ t.skipIfTextureFormatNotUsableAsStorageTexture(format);
+ break;
+ }
+ })
+ .fn(t => {
+ const { format, method, sampleCount } = t.params;
+
+ const usage =
+ GPUTextureUsage.COPY_SRC |
+ (method.includes('storage')
+ ? GPUTextureUsage.STORAGE_BINDING
+ : GPUTextureUsage.RENDER_ATTACHMENT);
+
+ const texture = t.trackForCleanup(
+ t.device.createTexture({
+ format,
+ usage,
+ size: [kTextureSize, kTextureSize],
+ sampleCount,
+ })
+ );
+
+ const view = texture.createView();
+ const expectedTexelView = writeTextureAndGetExpectedTexelView(
+ t,
+ method,
+ view,
+ format,
+ sampleCount
+ );
+
+ // [1] Use copySinglePixelTextureToBufferUsingComputePass to check multisampled texture.
+ t.expectTexelViewComparisonIsOkInTexture({ texture }, expectedTexelView, [
+ kTextureSize,
+ kTextureSize,
+ ]);
+ });
g.test('dimension')
.desc(
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/buffer/mapping.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/buffer/mapping.spec.ts
index 58d7f2767a..250918f2c9 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/buffer/mapping.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/buffer/mapping.spec.ts
@@ -27,8 +27,10 @@ class F extends ValidationTest {
this.expectValidationError(() => {
p = buffer.mapAsync(mode, offset, size);
}, expectation.validationError);
+
let caught = false;
let rejectedEarly = false;
+ let microtaskBRan = false;
// If mapAsync rejected early, microtask A will run before B.
// If not, B will run before A.
p!.catch(() => {
@@ -38,20 +40,31 @@ class F extends ValidationTest {
queueMicrotask(() => {
// Microtask B
rejectedEarly = caught;
+ microtaskBRan = true;
});
- try {
- // This await will always complete after microtasks A and B are both done.
- await p!;
- assert(expectation.rejectName === null, 'mapAsync unexpectedly passed');
- } catch (ex) {
- assert(ex instanceof Error, 'mapAsync rejected with non-error');
- assert(typeof ex.stack === 'string', 'mapAsync rejected without a stack');
- assert(expectation.rejectName === ex.name, `mapAsync rejected unexpectedly with: ${ex}`);
- assert(
- expectation.earlyRejection === rejectedEarly,
- 'mapAsync rejected at an unexpected timing'
- );
- }
+
+ // These handlers should always run after microtasks A and B are both done.
+ await p!.then(
+ () => {
+ unreachable('mapAsync unexpectedly passed');
+ },
+ ex => {
+ const suffix = `\n Rejection: ${ex}`;
+
+ this.expect(microtaskBRan, 'scheduling problem?: microtaskB has not run yet' + suffix);
+ assert(ex instanceof Error, 'mapAsync rejected with non-error' + suffix);
+ this.expect(typeof ex.stack === 'string', 'mapAsync rejected without a stack' + suffix);
+ this.expect(
+ expectation.rejectName === ex.name,
+ 'mapAsync rejected with wrong exception name' + suffix
+ );
+ if (expectation.earlyRejection) {
+ this.expect(rejectedEarly, 'expected early mapAsync rejection, got deferred' + suffix);
+ } else {
+ this.expect(!rejectedEarly, 'expected deferred mapAsync rejection, got early' + suffix);
+ }
+ }
+ );
}
}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/features/query_types.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/features/query_types.spec.ts
index 8016252b1e..9d6ab1cdce 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/features/query_types.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/features/query_types.spec.ts
@@ -43,11 +43,13 @@ g.test('createQuerySet')
});
});
-g.test('writeTimestamp')
+g.test('timestamp')
.desc(
`
Tests that writing a timestamp throws a type error exception if the features don't contain
'timestamp-query'.
+
+ TODO: writeTimestamp test is disabled since it's removed from the spec for now.
`
)
.params(u => u.combine('featureContainsTimestampQuery', [false, true]))
@@ -66,11 +68,53 @@ g.test('writeTimestamp')
const querySet = t.device.createQuerySet({
type: featureContainsTimestampQuery ? 'timestamp' : 'occlusion',
- count: 1,
+ count: 2,
});
- const encoder = t.createEncoder('non-pass');
- t.shouldThrow(featureContainsTimestampQuery ? false : 'TypeError', () => {
- encoder.encoder.writeTimestamp(querySet, 0);
- });
+ {
+ let expected = featureContainsTimestampQuery ? false : 'TypeError';
+ // writeTimestamp no longer exists and this should always TypeError.
+ expected = 'TypeError';
+
+ const encoder = t.createEncoder('non-pass');
+ t.shouldThrow(expected, () => {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (encoder.encoder as any).writeTimestamp(querySet, 0);
+ });
+ encoder.finish();
+ }
+
+ {
+ const encoder = t.createEncoder('non-pass');
+ encoder.encoder
+ .beginComputePass({
+ timestampWrites: { querySet, beginningOfPassWriteIndex: 0, endOfPassWriteIndex: 1 },
+ })
+ .end();
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, !featureContainsTimestampQuery);
+ }
+
+ {
+ const encoder = t.createEncoder('non-pass');
+ const view = t
+ .trackForCleanup(
+ t.device.createTexture({
+ size: [16, 16, 1],
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ })
+ )
+ .createView();
+ encoder.encoder
+ .beginRenderPass({
+ colorAttachments: [{ view, loadOp: 'clear', storeOp: 'discard' }],
+ timestampWrites: { querySet, beginningOfPassWriteIndex: 0, endOfPassWriteIndex: 1 },
+ })
+ .end();
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, !featureContainsTimestampQuery);
+ }
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/limit_utils.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/limit_utils.ts
index fee2ea716e..2daed5c57c 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/limit_utils.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/limit_utils.ts
@@ -45,32 +45,47 @@ export function getPipelineTypeForBindingCombination(bindingCombination: Binding
}
}
-function getBindGroupIndex(bindGroupTest: BindGroupTest, i: number) {
+function getBindGroupIndex(bindGroupTest: BindGroupTest, numBindGroups: number, i: number) {
switch (bindGroupTest) {
case 'sameGroup':
return 0;
case 'differentGroups':
- return i % 3;
+ return i % numBindGroups;
+ }
+}
+
+function getBindingIndex(bindGroupTest: BindGroupTest, numBindGroups: number, i: number) {
+ switch (bindGroupTest) {
+ case 'sameGroup':
+ return i;
+ case 'differentGroups':
+ return (i / numBindGroups) | 0;
}
}
function getWGSLBindings(
- order: ReorderOrder,
- bindGroupTest: BindGroupTest,
- storageDefinitionWGSLSnippetFn: (i: number, j: number) => string,
+ {
+ order,
+ bindGroupTest,
+ storageDefinitionWGSLSnippetFn,
+ numBindGroups,
+ }: {
+ order: ReorderOrder;
+ bindGroupTest: BindGroupTest;
+ storageDefinitionWGSLSnippetFn: (i: number, j: number) => string;
+ numBindGroups: number;
+ },
numBindings: number,
id: number
) {
return reorder(
order,
- range(
- numBindings,
- i =>
- `@group(${getBindGroupIndex(
- bindGroupTest,
- i
- )}) @binding(${i}) ${storageDefinitionWGSLSnippetFn(i, id)};`
- )
+ range(numBindings, i => {
+ const groupNdx = getBindGroupIndex(bindGroupTest, numBindGroups, i);
+ const bindingNdx = getBindingIndex(bindGroupTest, numBindGroups, i);
+ const storageWGSL = storageDefinitionWGSLSnippetFn(i, id);
+ return `@group(${groupNdx}) @binding(${bindingNdx}) ${storageWGSL};`;
+ })
).join('\n ');
}
@@ -80,15 +95,22 @@ export function getPerStageWGSLForBindingCombinationImpl(
bindGroupTest: BindGroupTest,
storageDefinitionWGSLSnippetFn: (i: number, j: number) => string,
bodyFn: (numBindings: number, set: number) => string,
+ numBindGroups: number,
numBindings: number,
extraWGSL = ''
) {
+ const bindingParams = {
+ order,
+ bindGroupTest,
+ storageDefinitionWGSLSnippetFn,
+ numBindGroups,
+ };
switch (bindingCombination) {
case 'vertex':
return `
${extraWGSL}
- ${getWGSLBindings(order, bindGroupTest, storageDefinitionWGSLSnippetFn, numBindings, 0)}
+ ${getWGSLBindings(bindingParams, numBindings, 0)}
@vertex fn mainVS() -> @builtin(position) vec4f {
${bodyFn(numBindings, 0)}
@@ -99,7 +121,7 @@ export function getPerStageWGSLForBindingCombinationImpl(
return `
${extraWGSL}
- ${getWGSLBindings(order, bindGroupTest, storageDefinitionWGSLSnippetFn, numBindings, 0)}
+ ${getWGSLBindings(bindingParams, numBindings, 0)}
@vertex fn mainVS() -> @builtin(position) vec4f {
return vec4f(0);
@@ -113,9 +135,9 @@ export function getPerStageWGSLForBindingCombinationImpl(
return `
${extraWGSL}
- ${getWGSLBindings(order, bindGroupTest, storageDefinitionWGSLSnippetFn, numBindings, 0)}
+ ${getWGSLBindings(bindingParams, numBindings, 0)}
- ${getWGSLBindings(order, bindGroupTest, storageDefinitionWGSLSnippetFn, numBindings - 1, 1)}
+ ${getWGSLBindings(bindingParams, numBindings - 1, 1)}
@vertex fn mainVS() -> @builtin(position) vec4f {
${bodyFn(numBindings, 0)}
@@ -131,9 +153,9 @@ export function getPerStageWGSLForBindingCombinationImpl(
return `
${extraWGSL}
- ${getWGSLBindings(order, bindGroupTest, storageDefinitionWGSLSnippetFn, numBindings - 1, 0)}
+ ${getWGSLBindings(bindingParams, numBindings - 1, 0)}
- ${getWGSLBindings(order, bindGroupTest, storageDefinitionWGSLSnippetFn, numBindings, 1)}
+ ${getWGSLBindings(bindingParams, numBindings, 1)}
@vertex fn mainVS() -> @builtin(position) vec4f {
${bodyFn(numBindings - 1, 0)}
@@ -148,8 +170,7 @@ export function getPerStageWGSLForBindingCombinationImpl(
case 'compute':
return `
${extraWGSL}
- ${getWGSLBindings(order, bindGroupTest, storageDefinitionWGSLSnippetFn, numBindings, 0)}
- @group(3) @binding(0) var<storage, read_write> d: f32;
+ ${getWGSLBindings(bindingParams, numBindings, 0)}
@compute @workgroup_size(1) fn main() {
${bodyFn(numBindings, 0)}
}
@@ -164,6 +185,7 @@ export function getPerStageWGSLForBindingCombination(
bindGroupTest: BindGroupTest,
storageDefinitionWGSLSnippetFn: (i: number, j: number) => string,
usageWGSLSnippetFn: (i: number, j: number) => string,
+ maxBindGroups: number,
numBindings: number,
extraWGSL = ''
) {
@@ -174,6 +196,7 @@ export function getPerStageWGSLForBindingCombination(
storageDefinitionWGSLSnippetFn,
(numBindings: number, set: number) =>
`${range(numBindings, i => usageWGSLSnippetFn(i, set)).join('\n ')}`,
+ maxBindGroups,
numBindings,
extraWGSL
);
@@ -185,6 +208,7 @@ export function getPerStageWGSLForBindingCombinationStorageTextures(
bindGroupTest: BindGroupTest,
storageDefinitionWGSLSnippetFn: (i: number, j: number) => string,
usageWGSLSnippetFn: (i: number, j: number) => string,
+ numBindGroups: number,
numBindings: number,
extraWGSL = ''
) {
@@ -195,6 +219,7 @@ export function getPerStageWGSLForBindingCombinationStorageTextures(
storageDefinitionWGSLSnippetFn,
(numBindings: number, set: number) =>
`${range(numBindings, i => usageWGSLSnippetFn(i, set)).join('\n ')}`,
+ numBindGroups,
numBindings,
extraWGSL
);
@@ -854,7 +879,7 @@ export class LimitTestsImpl extends GPUTestBase {
/**
* Creates an GPURenderCommandsMixin setup with some initial state.
*/
- _getGPURenderCommandsMixin(encoderType: RenderEncoderType) {
+ #getGPURenderCommandsMixin(encoderType: RenderEncoderType) {
const { device } = this;
switch (encoderType) {
@@ -895,7 +920,7 @@ export class LimitTestsImpl extends GPUTestBase {
});
const encoder = device.createCommandEncoder();
- const mixin = encoder.beginRenderPass({
+ const passEncoder = encoder.beginRenderPass({
colorAttachments: [
{
view: texture.createView(),
@@ -906,10 +931,10 @@ export class LimitTestsImpl extends GPUTestBase {
});
return {
- mixin,
+ passEncoder,
bindGroup,
prep() {
- mixin.end();
+ passEncoder.end();
},
test() {
encoder.finish();
@@ -946,16 +971,16 @@ export class LimitTestsImpl extends GPUTestBase {
],
});
- const mixin = device.createRenderBundleEncoder({
+ const passEncoder = device.createRenderBundleEncoder({
colorFormats: ['rgba8unorm'],
});
return {
- mixin,
+ passEncoder,
bindGroup,
prep() {},
test() {
- mixin.finish();
+ passEncoder.finish();
},
};
break;
@@ -964,17 +989,23 @@ export class LimitTestsImpl extends GPUTestBase {
}
/**
- * Tests a method on GPURenderCommandsMixin
- * The function will be called with the mixin.
+ * Test a method on GPURenderCommandsMixin or GPUBindingCommandsMixin
+ * The function will be called with the passEncoder.
*/
- async testGPURenderCommandsMixin(
+ async testGPURenderAndBindingCommandsMixin(
encoderType: RenderEncoderType,
- fn: ({ mixin }: { mixin: GPURenderCommandsMixin }) => void,
+ fn: ({
+ passEncoder,
+ bindGroup,
+ }: {
+ passEncoder: GPURenderCommandsMixin & GPUBindingCommandsMixin;
+ bindGroup: GPUBindGroup;
+ }) => void,
shouldError: boolean,
msg = ''
) {
- const { mixin, prep, test } = this._getGPURenderCommandsMixin(encoderType);
- fn({ mixin });
+ const { passEncoder, prep, test, bindGroup } = this.#getGPURenderCommandsMixin(encoderType);
+ fn({ passEncoder, bindGroup });
prep();
await this.expectValidationError(test, shouldError, msg);
@@ -983,7 +1014,7 @@ export class LimitTestsImpl extends GPUTestBase {
/**
* Creates GPUBindingCommandsMixin setup with some initial state.
*/
- _getGPUBindingCommandsMixin(encoderType: EncoderType) {
+ #getGPUBindingCommandsMixin(encoderType: EncoderType) {
const { device } = this;
switch (encoderType) {
@@ -1016,12 +1047,12 @@ export class LimitTestsImpl extends GPUTestBase {
});
const encoder = device.createCommandEncoder();
- const mixin = encoder.beginComputePass();
+ const passEncoder = encoder.beginComputePass();
return {
- mixin,
+ passEncoder,
bindGroup,
prep() {
- mixin.end();
+ passEncoder.end();
},
test() {
encoder.finish();
@@ -1030,24 +1061,24 @@ export class LimitTestsImpl extends GPUTestBase {
break;
}
case 'render':
- return this._getGPURenderCommandsMixin('render');
+ return this.#getGPURenderCommandsMixin('render');
case 'renderBundle':
- return this._getGPURenderCommandsMixin('renderBundle');
+ return this.#getGPURenderCommandsMixin('renderBundle');
}
}
/**
* Tests a method on GPUBindingCommandsMixin
- * The function pass will be called with the mixin and a bindGroup
+ * The function pass will be called with the passEncoder and a bindGroup
*/
async testGPUBindingCommandsMixin(
encoderType: EncoderType,
- fn: ({ bindGroup }: { mixin: GPUBindingCommandsMixin; bindGroup: GPUBindGroup }) => void,
+ fn: ({ bindGroup }: { passEncoder: GPUBindingCommandsMixin; bindGroup: GPUBindGroup }) => void,
shouldError: boolean,
msg = ''
) {
- const { mixin, bindGroup, prep, test } = this._getGPUBindingCommandsMixin(encoderType);
- fn({ mixin, bindGroup });
+ const { passEncoder, bindGroup, prep, test } = this.#getGPUBindingCommandsMixin(encoderType);
+ fn({ passEncoder, bindGroup });
prep();
await this.expectValidationError(test, shouldError, msg);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxBindGroups.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxBindGroups.spec.ts
index 334b49cc90..166b40ff2c 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxBindGroups.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxBindGroups.spec.ts
@@ -1,4 +1,4 @@
-import { range } from '../../../../../common/util/util.js';
+import { assert } from '../../../../../common/util/util.js';
import {
kCreatePipelineTypes,
@@ -10,30 +10,152 @@ import {
const limit = 'maxBindGroups';
export const { g, description } = makeLimitTestGroup(limit);
+type BindingLayout = {
+ buffer?: GPUBufferBindingLayout;
+ sampler?: GPUSamplerBindingLayout;
+ texture?: GPUTextureBindingLayout;
+ storageTexture?: GPUStorageTextureBindingLayout;
+ externalTexture?: GPUExternalTextureBindingLayout;
+};
+
+type LimitToBindingLayout = {
+ name: keyof GPUSupportedLimits;
+ entry: BindingLayout;
+};
+
+const kLimitToBindingLayout: readonly LimitToBindingLayout[] = [
+ {
+ name: 'maxSampledTexturesPerShaderStage',
+ entry: {
+ texture: {},
+ },
+ },
+ {
+ name: 'maxSamplersPerShaderStage',
+ entry: {
+ sampler: {},
+ },
+ },
+ {
+ name: 'maxUniformBuffersPerShaderStage',
+ entry: {
+ buffer: {},
+ },
+ },
+ {
+ name: 'maxStorageBuffersPerShaderStage',
+ entry: {
+ buffer: {
+ type: 'read-only-storage',
+ },
+ },
+ },
+ {
+ name: 'maxStorageTexturesPerShaderStage',
+ entry: {
+ storageTexture: {
+ access: 'write-only',
+ format: 'rgba8unorm',
+ viewDimension: '2d',
+ },
+ },
+ },
+] as const;
+
+/**
+ * Yields all possible binding layout entries for a stage.
+ */
+function* getBindingLayoutEntriesForStage(device: GPUDevice) {
+ for (const { name, entry } of kLimitToBindingLayout) {
+ const limit = device.limits[name] as number;
+ for (let i = 0; i < limit; ++i) {
+ yield entry;
+ }
+ }
+}
+
+/**
+ * Yields all of the possible BindingLayoutEntryAndVisibility entries for a render pipeline
+ */
+function* getBindingLayoutEntriesForRenderPipeline(
+ device: GPUDevice
+): Generator<GPUBindGroupLayoutEntry> {
+ const visibilities = [GPUShaderStage.VERTEX, GPUShaderStage.FRAGMENT];
+ for (const visibility of visibilities) {
+ for (const bindEntryResourceType of getBindingLayoutEntriesForStage(device)) {
+ const entry: GPUBindGroupLayoutEntry = {
+ binding: 0,
+ visibility,
+ ...bindEntryResourceType,
+ };
+ yield entry;
+ }
+ }
+}
+
+/**
+ * Returns the total possible bindings per render pipeline
+ */
+function getTotalPossibleBindingsPerRenderPipeline(device: GPUDevice) {
+ const totalPossibleBindingsPerStage =
+ device.limits.maxSampledTexturesPerShaderStage +
+ device.limits.maxSamplersPerShaderStage +
+ device.limits.maxUniformBuffersPerShaderStage +
+ device.limits.maxStorageBuffersPerShaderStage +
+ device.limits.maxStorageTexturesPerShaderStage;
+ return totalPossibleBindingsPerStage * 2;
+}
+
+/**
+ * Yields count GPUBindGroupLayoutEntries
+ */
+function* getBindingLayoutEntries(
+ device: GPUDevice,
+ count: number
+): Generator<GPUBindGroupLayoutEntry> {
+ assert(count < getTotalPossibleBindingsPerRenderPipeline(device));
+ const iter = getBindingLayoutEntriesForRenderPipeline(device);
+ for (; count > 0; --count) {
+ yield iter.next().value;
+ }
+}
+
g.test('createPipelineLayout,at_over')
.desc(`Test using createPipelineLayout at and over ${limit} limit`)
.params(kMaximumLimitBaseParams)
.fn(async t => {
const { limitTest, testValueName } = t.params;
+
await t.testDeviceWithRequestedMaximumLimits(
limitTest,
testValueName,
- async ({ device, testValue, shouldError }) => {
- const bindGroupLayouts = range(testValue, _i =>
- device.createBindGroupLayout({
- entries: [
- {
- binding: 0,
- visibility: GPUShaderStage.VERTEX,
- buffer: {},
- },
- ],
- })
+ async ({ device, testValue, shouldError, actualLimit }) => {
+ const totalPossibleBindingsPerPipeline = getTotalPossibleBindingsPerRenderPipeline(device);
+ // Not sure what to do if we ever hit this but I think it's better to assert than silently skip.
+ assert(
+ testValue < totalPossibleBindingsPerPipeline,
+ `not enough possible bindings(${totalPossibleBindingsPerPipeline}) to test ${testValue} bindGroups`
);
- await t.expectValidationError(() => {
- device.createPipelineLayout({ bindGroupLayouts });
- }, shouldError);
+ const bindingDescriptions: string[] = [];
+ const bindGroupLayouts = [...getBindingLayoutEntries(device, testValue)].map(entry => {
+ bindingDescriptions.push(
+ `${JSON.stringify(entry)} // group(${bindingDescriptions.length})`
+ );
+ return device.createBindGroupLayout({
+ entries: [entry],
+ });
+ });
+
+ await t.expectValidationError(
+ () => {
+ device.createPipelineLayout({ bindGroupLayouts });
+ },
+ shouldError,
+ `testing ${testValue} bindGroups on maxBindGroups = ${actualLimit} with \n${bindingDescriptions.join(
+ '\n'
+ )}`
+ );
}
);
});
@@ -76,8 +198,8 @@ g.test('setBindGroup,at_over')
const lastIndex = testValue - 1;
await t.testGPUBindingCommandsMixin(
encoderType,
- ({ mixin, bindGroup }) => {
- mixin.setBindGroup(lastIndex, bindGroup);
+ ({ passEncoder, bindGroup }) => {
+ passEncoder.setBindGroup(lastIndex, bindGroup);
},
shouldError,
`shouldError: ${shouldError}, actualLimit: ${actualLimit}, testValue: ${lastIndex}`
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxBindGroupsPlusVertexBuffers.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxBindGroupsPlusVertexBuffers.spec.ts
new file mode 100644
index 0000000000..d75ef0ce7d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxBindGroupsPlusVertexBuffers.spec.ts
@@ -0,0 +1,301 @@
+import {
+ kRenderEncoderTypes,
+ kMaximumLimitBaseParams,
+ makeLimitTestGroup,
+ LimitsRequest,
+} from './limit_utils.js';
+
+const kVertexBufferBindGroupPreferences = ['vertexBuffers', 'bindGroups'] as const;
+type VertexBufferBindGroupPreference = (typeof kVertexBufferBindGroupPreferences)[number];
+
+const kLayoutTypes = ['auto', 'explicit'] as const;
+type LayoutType = (typeof kLayoutTypes)[number];
+
+/**
+ * Given testValue, choose more vertex buffers or more bind groups based on preference
+ */
+function getNumBindGroupsAndNumVertexBuffers(
+ device: GPUDevice,
+ preference: VertexBufferBindGroupPreference,
+ testValue: number
+) {
+ switch (preference) {
+ case 'bindGroups': {
+ const numBindGroups = Math.min(testValue, device.limits.maxBindGroups);
+ const numVertexBuffers = Math.max(0, testValue - numBindGroups);
+ return { numVertexBuffers, numBindGroups };
+ }
+ case 'vertexBuffers': {
+ const numVertexBuffers = Math.min(testValue, device.limits.maxVertexBuffers);
+ const numBindGroups = Math.max(0, testValue - numVertexBuffers);
+ return { numVertexBuffers, numBindGroups };
+ }
+ }
+}
+
+function createLayout(device: GPUDevice, layoutType: LayoutType, numBindGroups: number) {
+ switch (layoutType) {
+ case 'auto':
+ return 'auto';
+ case 'explicit': {
+ const bindGroupLayouts = new Array(numBindGroups);
+ if (numBindGroups > 0) {
+ bindGroupLayouts.fill(device.createBindGroupLayout({ entries: [] }));
+ bindGroupLayouts[numBindGroups - 1] = device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.VERTEX,
+ buffer: {},
+ },
+ ],
+ });
+ }
+ return device.createPipelineLayout({ bindGroupLayouts });
+ }
+ }
+}
+
+/**
+ * Generate a render pipeline that can be used to test maxBindGroupsPlusVertexBuffers
+ */
+function getPipelineDescriptor(
+ device: GPUDevice,
+ preference: VertexBufferBindGroupPreference,
+ testValue: number,
+ layoutType: LayoutType
+) {
+ // Get the numVertexBuffers and numBindGroups we could use given testValue as a total.
+ // We will only use 1 of each but we'll use the last index.
+ const { numVertexBuffers, numBindGroups } = getNumBindGroupsAndNumVertexBuffers(
+ device,
+ preference,
+ testValue
+ );
+
+ const layout = createLayout(device, layoutType, numBindGroups);
+
+ const [bindGroupDecl, bindGroupUsage] =
+ numBindGroups === 0
+ ? ['', '']
+ : [`@group(${numBindGroups - 1}) @binding(0) var<uniform> u: f32;`, `_ = u;`];
+
+ const [attribDecl, attribUsage] =
+ numVertexBuffers === 0
+ ? ['', '']
+ : ['@location(0) v: vec4f', `_ = v; // will use vertex buffer ${numVertexBuffers - 1}`];
+
+ const code = `
+ ${bindGroupDecl}
+
+ @vertex fn vs(${attribDecl}) -> @builtin(position) vec4f {
+ ${bindGroupUsage}
+ ${attribUsage}
+ return vec4f(0);
+ }
+
+ @fragment fn fs() -> @location(0) vec4f {
+ return vec4f(0);
+ }
+ `;
+
+ const module = device.createShaderModule({ code });
+ const buffers = new Array<GPUVertexBufferLayout | null>(numVertexBuffers);
+ if (numVertexBuffers > 0) {
+ buffers[numVertexBuffers - 1] = {
+ arrayStride: 16,
+ attributes: [{ shaderLocation: 0, offset: 0, format: 'float32' }],
+ };
+ }
+
+ return {
+ code,
+ descriptor: {
+ layout,
+ vertex: {
+ module,
+ buffers,
+ },
+ fragment: {
+ module,
+ targets: [{ format: 'rgba8unorm' }],
+ },
+ } as const,
+ };
+}
+
+const kExtraLimits: LimitsRequest = {
+ maxBindGroups: 'adapterLimit',
+ maxVertexBuffers: 'adapterLimit',
+};
+
+const limit = 'maxBindGroupsPlusVertexBuffers';
+export const { g, description } = makeLimitTestGroup(limit);
+
+g.test('createRenderPipeline,at_over')
+ .desc(`Test using at and over ${limit} limit in createRenderPipeline(Async).`)
+ .params(
+ kMaximumLimitBaseParams
+ .combine('async', [false, true])
+ .beginSubcases()
+ .combine('preference', kVertexBufferBindGroupPreferences)
+ .combine('layoutType', kLayoutTypes)
+ )
+ .fn(async t => {
+ const { limitTest, testValueName, async, preference, layoutType } = t.params;
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, shouldError, actualLimit }) => {
+ const maxUsableBindGroupsPlusVertexBuffers =
+ device.limits.maxBindGroups + device.limits.maxVertexBuffers;
+ t.skipIf(
+ maxUsableBindGroupsPlusVertexBuffers < actualLimit,
+ `can not test because the max usable bindGroups + vertexBuffers (${maxUsableBindGroupsPlusVertexBuffers}) is < maxBindGroupsAndVertexBuffers (${actualLimit})`
+ );
+ t.skipIf(
+ maxUsableBindGroupsPlusVertexBuffers === actualLimit && testValue > actualLimit,
+ `can not test because the max usable bindGroups + vertexBuffers (${maxUsableBindGroupsPlusVertexBuffers}) === maxBindGroupsAndVertexBuffers (${actualLimit})
+ but the testValue (${testValue}) > maxBindGroupsAndVertexBuffers (${actualLimit})`
+ );
+
+ const { code, descriptor } = getPipelineDescriptor(
+ device,
+ preference,
+ testValue,
+ layoutType
+ );
+
+ await t.testCreateRenderPipeline(
+ descriptor,
+ async,
+ shouldError,
+ `testValue: ${testValue}, actualLimit: ${actualLimit}, shouldError: ${shouldError}\n${code}`
+ );
+ },
+ kExtraLimits
+ );
+ });
+
+g.test('draw,at_over')
+ .desc(`Test using at and over ${limit} limit draw/drawIndexed/drawIndirect/drawIndexedIndirect`)
+ .params(
+ kMaximumLimitBaseParams
+ .combine('encoderType', kRenderEncoderTypes)
+ .beginSubcases()
+ .combine('preference', kVertexBufferBindGroupPreferences)
+ .combine('drawType', ['draw', 'drawIndexed', 'drawIndirect', 'drawIndexedIndirect'] as const)
+ )
+ .fn(async t => {
+ const { limitTest, testValueName, encoderType, drawType, preference } = t.params;
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, shouldError, actualLimit }) => {
+ const maxUsableVertexBuffers = Math.min(
+ device.limits.maxVertexBuffers,
+ device.limits.maxVertexAttributes
+ );
+ const maxUsableBindGroupsPlusVertexBuffers =
+ device.limits.maxBindGroups + maxUsableVertexBuffers;
+ t.skipIf(
+ maxUsableBindGroupsPlusVertexBuffers < actualLimit,
+ `can not test because the max usable bindGroups + vertexBuffers (${maxUsableBindGroupsPlusVertexBuffers}) is < maxBindGroupsAndVertexBuffers (${actualLimit})`
+ );
+ t.skipIf(
+ maxUsableBindGroupsPlusVertexBuffers === actualLimit && testValue > actualLimit,
+ `can not test because the max usable bindGroups + vertexBuffers (${maxUsableBindGroupsPlusVertexBuffers}) === maxBindGroupsAndVertexBuffers (${actualLimit})
+ but the testValue (${testValue}) > maxBindGroupsAndVertexBuffers (${actualLimit})`
+ );
+
+ // Get the numVertexBuffers and numBindGroups we could use given testValue as a total.
+ // We will only use 1 of each but we'll use the last index.
+ const { numVertexBuffers, numBindGroups } = getNumBindGroupsAndNumVertexBuffers(
+ device,
+ preference,
+ testValue
+ );
+
+ const module = device.createShaderModule({
+ code: `
+ @vertex fn vs() -> @builtin(position) vec4f {
+ return vec4f(0);
+ }
+ @fragment fn fs() -> @location(0) vec4f {
+ return vec4f(0);
+ }
+ `,
+ });
+ const pipeline = device.createRenderPipeline({
+ layout: 'auto',
+ vertex: { module },
+ fragment: { module, targets: [{ format: 'rgba8unorm' }] },
+ });
+
+ const vertexBuffer = device.createBuffer({
+ size: 16,
+ usage: GPUBufferUsage.VERTEX,
+ });
+ t.trackForCleanup(vertexBuffer);
+
+ await t.testGPURenderAndBindingCommandsMixin(
+ encoderType,
+ ({ bindGroup, passEncoder }) => {
+ // Set the last vertex buffer and clear it. This should have no effect
+ // unless there is a bug in bookkeeping in the implementation.
+ passEncoder.setVertexBuffer(device.limits.maxVertexBuffers - 1, vertexBuffer);
+ passEncoder.setVertexBuffer(device.limits.maxVertexBuffers - 1, null);
+
+ // Set the last bindGroup and clear it. This should have no effect
+ // unless there is a bug in bookkeeping in the implementation.
+ passEncoder.setBindGroup(device.limits.maxBindGroups - 1, bindGroup);
+ passEncoder.setBindGroup(device.limits.maxBindGroups - 1, null);
+
+ if (numVertexBuffers) {
+ passEncoder.setVertexBuffer(numVertexBuffers - 1, vertexBuffer);
+ }
+
+ if (numBindGroups) {
+ passEncoder.setBindGroup(numBindGroups - 1, bindGroup);
+ }
+
+ passEncoder.setPipeline(pipeline);
+
+ const indirectBuffer = device.createBuffer({
+ size: 20,
+ usage: GPUBufferUsage.INDIRECT,
+ });
+ t.trackForCleanup(indirectBuffer);
+
+ const indexBuffer = device.createBuffer({
+ size: 4,
+ usage: GPUBufferUsage.INDEX,
+ });
+ t.trackForCleanup(indexBuffer);
+
+ switch (drawType) {
+ case 'draw':
+ passEncoder.draw(0);
+ break;
+ case 'drawIndexed':
+ passEncoder.setIndexBuffer(indexBuffer, 'uint16');
+ passEncoder.drawIndexed(0);
+ break;
+ case 'drawIndirect':
+ passEncoder.drawIndirect(indirectBuffer, 0);
+ break;
+ case 'drawIndexedIndirect':
+ passEncoder.setIndexBuffer(indexBuffer, 'uint16');
+ passEncoder.drawIndexedIndirect(indirectBuffer, 0);
+ break;
+ }
+ },
+ shouldError,
+ `testValue: ${testValue}, actualLimit: ${actualLimit}, shouldError: ${shouldError}`
+ );
+
+ vertexBuffer.destroy();
+ },
+ kExtraLimits
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupStorageSize.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupStorageSize.spec.ts
index cb26e18ebe..8a9176983d 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupStorageSize.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupStorageSize.spec.ts
@@ -12,7 +12,8 @@ import {
const limit = 'maxComputeWorkgroupStorageSize';
export const { g, description } = makeLimitTestGroup(limit);
-const kSmallestWorkgroupVarSize = 4;
+// Each var is roundUp(16, SizeOf(T))
+const kSmallestWorkgroupVarSize = 16;
const wgslF16Types = {
f16: { alignOf: 2, sizeOf: 2, requireF16: true },
@@ -71,7 +72,9 @@ function getModuleForWorkgroupStorageSize(device: GPUDevice, wgslType: WGSLType,
const { sizeOf, alignOf, requireF16 } = wgslTypes[wgslType];
const unitSize = align(sizeOf, alignOf);
const units = Math.floor(size / unitSize);
- const extra = (size - units * unitSize) / kSmallestWorkgroupVarSize;
+ const sizeUsed = align(units * unitSize, 16);
+ const sizeLeft = size - sizeUsed;
+ const extra = Math.floor(sizeLeft / kSmallestWorkgroupVarSize);
const code =
(requireF16 ? 'enable f16;\n' : '') +
@@ -89,7 +92,7 @@ function getModuleForWorkgroupStorageSize(device: GPUDevice, wgslType: WGSLType,
b: vec2f,
};
var<workgroup> d0: array<${wgslType}, ${units}>;
- ${extra ? `var<workgroup> d1: array<f32, ${extra}>;` : ''}
+ ${extra ? `var<workgroup> d1: array<vec4<f32>, ${extra}>;` : ''}
@compute @workgroup_size(1) fn main() {
_ = d0;
${extra ? '_ = d1;' : ''}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxInterStageShaderComponents.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxInterStageShaderComponents.spec.ts
index eeb9eb0faf..409dc72724 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxInterStageShaderComponents.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxInterStageShaderComponents.spec.ts
@@ -111,8 +111,12 @@ g.test('createRenderPipeline,at_over')
.combine('sampleMaskOut', [false, true])
)
.beforeAllSubcases(t => {
- if (t.isCompatibility && (t.params.sampleMaskIn || t.params.sampleMaskOut)) {
- t.skip('sample_mask not supported in compatibility mode');
+ if (t.isCompatibility) {
+ t.skipIf(
+ t.params.sampleMaskIn || t.params.sampleMaskOut,
+ 'sample_mask not supported in compatibility mode'
+ );
+ t.skipIf(t.params.sampleIndex, 'sample_index not supported in compatibility mode');
}
})
.fn(async t => {
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxSampledTexturesPerShaderStage.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxSampledTexturesPerShaderStage.spec.ts
index cd90d9d907..57e602c40a 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxSampledTexturesPerShaderStage.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxSampledTexturesPerShaderStage.spec.ts
@@ -3,6 +3,7 @@ import {
reorder,
kReorderOrderKeys,
ReorderOrder,
+ assert,
} from '../../../../../common/util/util.js';
import { kShaderStageCombinationsWithStage } from '../../../../capability_info.js';
@@ -13,8 +14,14 @@ import {
kBindingCombinations,
getPipelineTypeForBindingCombination,
getPerStageWGSLForBindingCombination,
+ LimitsRequest,
} from './limit_utils.js';
+const kExtraLimits: LimitsRequest = {
+ maxBindingsPerBindGroup: 'adapterLimit',
+ maxBindGroups: 'adapterLimit',
+};
+
const limit = 'maxSampledTexturesPerShaderStage';
export const { g, description } = makeLimitTestGroup(limit);
@@ -43,6 +50,9 @@ g.test('createBindGroupLayout,at_over')
Note: We also test order to make sure the implementation isn't just looking
at just the last entry.
+
+ Note: It's also possible the maxBindingsPerBindGroup is lower than
+ ${limit} in which case skip the test since we can not hit the limit.
`
)
.params(
@@ -56,11 +66,17 @@ g.test('createBindGroupLayout,at_over')
limitTest,
testValueName,
async ({ device, testValue, shouldError }) => {
+ t.skipIf(
+ t.adapter.limits.maxBindingsPerBindGroup < testValue,
+ `maxBindingsPerBindGroup = ${t.adapter.limits.maxBindingsPerBindGroup} which is less than ${testValue}`
+ );
+
await t.expectValidationError(
() => createBindGroupLayout(device, visibility, order, testValue),
shouldError
);
- }
+ },
+ kExtraLimits
);
});
@@ -83,18 +99,30 @@ g.test('createPipelineLayout,at_over')
await t.testDeviceWithRequestedMaximumLimits(
limitTest,
testValueName,
- async ({ device, testValue, shouldError }) => {
- const kNumGroups = 3;
+ async ({ device, testValue, shouldError, actualLimit }) => {
+ const maxBindingsPerBindGroup = Math.min(
+ t.device.limits.maxBindingsPerBindGroup,
+ actualLimit
+ );
+ const kNumGroups = Math.ceil(testValue / maxBindingsPerBindGroup);
+
+ // Not sure what to do in this case but best we get notified if it happens.
+ assert(kNumGroups <= t.device.limits.maxBindGroups);
+
const bindGroupLayouts = range(kNumGroups, i => {
- const minInGroup = Math.floor(testValue / kNumGroups);
- const numInGroup = i ? minInGroup : testValue - minInGroup * (kNumGroups - 1);
+ const numInGroup = Math.min(
+ testValue - i * maxBindingsPerBindGroup,
+ maxBindingsPerBindGroup
+ );
return createBindGroupLayout(device, visibility, order, numInGroup);
});
+
await t.expectValidationError(
() => device.createPipelineLayout({ bindGroupLayouts }),
shouldError
);
- }
+ },
+ kExtraLimits
);
});
@@ -122,16 +150,21 @@ g.test('createPipeline,at_over')
limitTest,
testValueName,
async ({ device, testValue, actualLimit, shouldError }) => {
+ t.skipIf(
+ bindGroupTest === 'sameGroup' && testValue > device.limits.maxBindingsPerBindGroup,
+ `can not test ${testValue} bindings in same group because maxBindingsPerBindGroup = ${device.limits.maxBindingsPerBindGroup}`
+ );
+
const code = getPerStageWGSLForBindingCombination(
bindingCombination,
order,
bindGroupTest,
(i, j) => `var u${j}_${i}: texture_2d<f32>`,
(i, j) => `_ = textureLoad(u${j}_${i}, vec2u(0), 0);`,
+ device.limits.maxBindGroups,
testValue
);
const module = device.createShaderModule({ code });
-
await t.testCreatePipeline(
pipelineType,
async,
@@ -139,6 +172,7 @@ g.test('createPipeline,at_over')
shouldError,
`actualLimit: ${actualLimit}, testValue: ${testValue}\n:${code}`
);
- }
+ },
+ kExtraLimits
);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxSamplersPerShaderStage.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxSamplersPerShaderStage.spec.ts
index 3103d423c9..892c1f498d 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxSamplersPerShaderStage.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxSamplersPerShaderStage.spec.ts
@@ -3,6 +3,7 @@ import {
reorder,
kReorderOrderKeys,
ReorderOrder,
+ assert,
} from '../../../../../common/util/util.js';
import { kShaderStageCombinationsWithStage } from '../../../../capability_info.js';
@@ -13,8 +14,14 @@ import {
kBindingCombinations,
getPipelineTypeForBindingCombination,
getPerStageWGSLForBindingCombination,
+ LimitsRequest,
} from './limit_utils.js';
+const kExtraLimits: LimitsRequest = {
+ maxBindingsPerBindGroup: 'adapterLimit',
+ maxBindGroups: 'adapterLimit',
+};
+
const limit = 'maxSamplersPerShaderStage';
export const { g, description } = makeLimitTestGroup(limit);
@@ -56,11 +63,17 @@ g.test('createBindGroupLayout,at_over')
limitTest,
testValueName,
async ({ device, testValue, shouldError }) => {
+ t.skipIf(
+ t.adapter.limits.maxBindingsPerBindGroup < testValue,
+ `maxBindingsPerBindGroup = ${t.adapter.limits.maxBindingsPerBindGroup} which is less than ${testValue}`
+ );
+
await t.expectValidationError(
() => createBindGroupLayout(device, visibility, order, testValue),
shouldError
);
- }
+ },
+ kExtraLimits
);
});
@@ -83,18 +96,29 @@ g.test('createPipelineLayout,at_over')
await t.testDeviceWithRequestedMaximumLimits(
limitTest,
testValueName,
- async ({ device, testValue, shouldError }) => {
- const kNumGroups = 3;
+ async ({ device, testValue, shouldError, actualLimit }) => {
+ const maxBindingsPerBindGroup = Math.min(
+ t.device.limits.maxBindingsPerBindGroup,
+ actualLimit
+ );
+ const kNumGroups = Math.ceil(testValue / maxBindingsPerBindGroup);
+
+ // Not sure what to do in this case but best we get notified if it happens.
+ assert(kNumGroups <= t.device.limits.maxBindGroups);
+
const bindGroupLayouts = range(kNumGroups, i => {
- const minInGroup = Math.floor(testValue / kNumGroups);
- const numInGroup = i ? minInGroup : testValue - minInGroup * (kNumGroups - 1);
+ const numInGroup = Math.min(
+ testValue - i * maxBindingsPerBindGroup,
+ maxBindingsPerBindGroup
+ );
return createBindGroupLayout(device, visibility, order, numInGroup);
});
await t.expectValidationError(
() => device.createPipelineLayout({ bindGroupLayouts }),
shouldError
);
- }
+ },
+ kExtraLimits
);
});
@@ -122,14 +146,27 @@ g.test('createPipeline,at_over')
limitTest,
testValueName,
async ({ device, testValue, actualLimit, shouldError }) => {
+ t.skipIf(
+ bindGroupTest === 'sameGroup' && testValue > device.limits.maxBindingsPerBindGroup,
+ `can not test ${testValue} bindings in same group because maxBindingsPerBindGroup = ${device.limits.maxBindingsPerBindGroup}`
+ );
+
+ // If this was false the texture binding would overlap the sampler bindings.
+ assert(testValue < device.limits.maxBindGroups * device.limits.maxBindingsPerBindGroup);
+
+ // Put the texture on the last possible binding.
+ const groupNdx = device.limits.maxBindGroups - 1;
+ const bindingNdx = device.limits.maxBindingsPerBindGroup - 1;
+
const code = getPerStageWGSLForBindingCombination(
bindingCombination,
order,
bindGroupTest,
(i, j) => `var u${j}_${i}: sampler`,
(i, j) => `_ = textureGather(0, tex, u${j}_${i}, vec2f(0));`,
+ device.limits.maxBindGroups,
testValue,
- '@group(3) @binding(1) var tex: texture_2d<f32>;'
+ `@group(${groupNdx}) @binding(${bindingNdx}) var tex: texture_2d<f32>;`
);
const module = device.createShaderModule({ code });
@@ -140,6 +177,7 @@ g.test('createPipeline,at_over')
shouldError,
`actualLimit: ${actualLimit}, testValue: ${testValue}\n:${code}`
);
- }
+ },
+ kExtraLimits
);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxStorageBuffersPerShaderStage.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxStorageBuffersPerShaderStage.spec.ts
index 5dfff78907..ee7c3a0246 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxStorageBuffersPerShaderStage.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxStorageBuffersPerShaderStage.spec.ts
@@ -3,7 +3,9 @@ import {
reorder,
kReorderOrderKeys,
ReorderOrder,
+ assert,
} from '../../../../../common/util/util.js';
+import { kShaderStageCombinationsWithStage } from '../../../../capability_info.js';
import { GPUConst } from '../../../../constants.js';
import {
@@ -13,8 +15,14 @@ import {
kBindingCombinations,
getPipelineTypeForBindingCombination,
getPerStageWGSLForBindingCombination,
+ LimitsRequest,
} from './limit_utils.js';
+const kExtraLimits: LimitsRequest = {
+ maxBindingsPerBindGroup: 'adapterLimit',
+ maxBindGroups: 'adapterLimit',
+};
+
const limit = 'maxStorageBuffersPerShaderStage';
export const { g, description } = makeLimitTestGroup(limit);
@@ -48,34 +56,31 @@ g.test('createBindGroupLayout,at_over')
)
.params(
kMaximumLimitBaseParams
- .combine('visibility', [
- GPUConst.ShaderStage.VERTEX,
- GPUConst.ShaderStage.FRAGMENT,
- GPUConst.ShaderStage.VERTEX | GPUConst.ShaderStage.FRAGMENT,
- GPUConst.ShaderStage.COMPUTE,
- GPUConst.ShaderStage.VERTEX | GPUConst.ShaderStage.COMPUTE,
- GPUConst.ShaderStage.FRAGMENT | GPUConst.ShaderStage.COMPUTE,
- GPUConst.ShaderStage.VERTEX | GPUConst.ShaderStage.FRAGMENT | GPUConst.ShaderStage.COMPUTE,
- ])
+ .combine('visibility', kShaderStageCombinationsWithStage)
.combine('type', ['storage', 'read-only-storage'] as GPUBufferBindingType[])
.combine('order', kReorderOrderKeys)
+ .filter(
+ ({ visibility, type }) =>
+ (visibility & GPUConst.ShaderStage.VERTEX) === 0 || type !== 'storage'
+ )
)
.fn(async t => {
const { limitTest, testValueName, visibility, order, type } = t.params;
- if (visibility & GPUConst.ShaderStage.VERTEX && type === 'storage') {
- // vertex stage does not support storage buffers
- return;
- }
-
await t.testDeviceWithRequestedMaximumLimits(
limitTest,
testValueName,
async ({ device, testValue, shouldError }) => {
+ t.skipIf(
+ t.adapter.limits.maxBindingsPerBindGroup < testValue,
+ `maxBindingsPerBindGroup = ${t.adapter.limits.maxBindingsPerBindGroup} which is less than ${testValue}`
+ );
+
await t.expectValidationError(() => {
createBindGroupLayout(device, visibility, type, order, testValue);
}, shouldError);
- }
+ },
+ kExtraLimits
);
});
@@ -90,41 +95,44 @@ g.test('createPipelineLayout,at_over')
)
.params(
kMaximumLimitBaseParams
- .combine('visibility', [
- GPUConst.ShaderStage.VERTEX,
- GPUConst.ShaderStage.FRAGMENT,
- GPUConst.ShaderStage.VERTEX | GPUConst.ShaderStage.FRAGMENT,
- GPUConst.ShaderStage.COMPUTE,
- GPUConst.ShaderStage.VERTEX | GPUConst.ShaderStage.COMPUTE,
- GPUConst.ShaderStage.FRAGMENT | GPUConst.ShaderStage.COMPUTE,
- GPUConst.ShaderStage.VERTEX | GPUConst.ShaderStage.FRAGMENT | GPUConst.ShaderStage.COMPUTE,
- ])
+ .combine('visibility', kShaderStageCombinationsWithStage)
.combine('type', ['storage', 'read-only-storage'] as GPUBufferBindingType[])
.combine('order', kReorderOrderKeys)
+ .filter(
+ ({ visibility, type }) =>
+ (visibility & GPUConst.ShaderStage.VERTEX) === 0 || type !== 'storage'
+ )
)
.fn(async t => {
const { limitTest, testValueName, visibility, order, type } = t.params;
- if (visibility & GPUConst.ShaderStage.VERTEX && type === 'storage') {
- // vertex stage does not support storage buffers
- return;
- }
-
await t.testDeviceWithRequestedMaximumLimits(
limitTest,
testValueName,
- async ({ device, testValue, shouldError }) => {
- const kNumGroups = 3;
+ async ({ device, testValue, shouldError, actualLimit }) => {
+ const maxBindingsPerBindGroup = Math.min(
+ t.device.limits.maxBindingsPerBindGroup,
+ actualLimit
+ );
+ const kNumGroups = Math.ceil(testValue / maxBindingsPerBindGroup);
+
+ // Not sure what to do in this case but best we get notified if it happens.
+ assert(kNumGroups <= t.device.limits.maxBindGroups);
+
const bindGroupLayouts = range(kNumGroups, i => {
- const minInGroup = Math.floor(testValue / kNumGroups);
- const numInGroup = i ? minInGroup : testValue - minInGroup * (kNumGroups - 1);
+ const numInGroup = Math.min(
+ testValue - i * maxBindingsPerBindGroup,
+ maxBindingsPerBindGroup
+ );
return createBindGroupLayout(device, visibility, type, order, numInGroup);
});
+
await t.expectValidationError(
() => device.createPipelineLayout({ bindGroupLayouts }),
shouldError
);
- }
+ },
+ kExtraLimits
);
});
@@ -152,12 +160,18 @@ g.test('createPipeline,at_over')
limitTest,
testValueName,
async ({ device, testValue, actualLimit, shouldError }) => {
+ t.skipIf(
+ bindGroupTest === 'sameGroup' && testValue > device.limits.maxBindingsPerBindGroup,
+ `can not test ${testValue} bindings in same group because maxBindingsPerBindGroup = ${device.limits.maxBindingsPerBindGroup}`
+ );
+
const code = getPerStageWGSLForBindingCombination(
bindingCombination,
order,
bindGroupTest,
(i, j) => `var<storage> u${j}_${i}: f32`,
(i, j) => `_ = u${j}_${i};`,
+ device.limits.maxBindGroups,
testValue
);
const module = device.createShaderModule({ code });
@@ -169,6 +183,7 @@ g.test('createPipeline,at_over')
shouldError,
`actualLimit: ${actualLimit}, testValue: ${testValue}\n:${code}`
);
- }
+ },
+ kExtraLimits
);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxStorageTexturesPerShaderStage.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxStorageTexturesPerShaderStage.spec.ts
index dee6069b44..8af61f51fc 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxStorageTexturesPerShaderStage.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxStorageTexturesPerShaderStage.spec.ts
@@ -3,6 +3,7 @@ import {
reorder,
ReorderOrder,
kReorderOrderKeys,
+ assert,
} from '../../../../../common/util/util.js';
import { GPUConst } from '../../../../constants.js';
@@ -13,8 +14,14 @@ import {
getPerStageWGSLForBindingCombinationStorageTextures,
getPipelineTypeForBindingCombination,
BindingCombination,
+ LimitsRequest,
} from './limit_utils.js';
+const kExtraLimits: LimitsRequest = {
+ maxBindingsPerBindGroup: 'adapterLimit',
+ maxBindGroups: 'adapterLimit',
+};
+
const limit = 'maxStorageTexturesPerShaderStage';
export const { g, description } = makeLimitTestGroup(limit);
@@ -60,11 +67,17 @@ g.test('createBindGroupLayout,at_over')
limitTest,
testValueName,
async ({ device, testValue, shouldError }) => {
+ t.skipIf(
+ t.adapter.limits.maxBindingsPerBindGroup < testValue,
+ `maxBindingsPerBindGroup = ${t.adapter.limits.maxBindingsPerBindGroup} which is less than ${testValue}`
+ );
+
await t.expectValidationError(
() => createBindGroupLayout(device, visibility, order, testValue),
shouldError
);
- }
+ },
+ kExtraLimits
);
});
@@ -91,18 +104,30 @@ g.test('createPipelineLayout,at_over')
await t.testDeviceWithRequestedMaximumLimits(
limitTest,
testValueName,
- async ({ device, testValue, shouldError }) => {
- const kNumGroups = 3;
+ async ({ device, testValue, shouldError, actualLimit }) => {
+ const maxBindingsPerBindGroup = Math.min(
+ t.device.limits.maxBindingsPerBindGroup,
+ actualLimit
+ );
+ const kNumGroups = Math.ceil(testValue / maxBindingsPerBindGroup);
+
+ // Not sure what to do in this case but best we get notified if it happens.
+ assert(kNumGroups <= t.device.limits.maxBindGroups);
+
const bindGroupLayouts = range(kNumGroups, i => {
- const minInGroup = Math.floor(testValue / kNumGroups);
- const numInGroup = i ? minInGroup : testValue - minInGroup * (kNumGroups - 1);
+ const numInGroup = Math.min(
+ testValue - i * maxBindingsPerBindGroup,
+ maxBindingsPerBindGroup
+ );
return createBindGroupLayout(device, visibility, order, numInGroup);
});
+
await t.expectValidationError(
() => device.createPipelineLayout({ bindGroupLayouts }),
shouldError
);
- }
+ },
+ kExtraLimits
);
});
@@ -130,6 +155,11 @@ g.test('createPipeline,at_over')
limitTest,
testValueName,
async ({ device, testValue, actualLimit, shouldError }) => {
+ t.skipIf(
+ bindGroupTest === 'sameGroup' && testValue > device.limits.maxBindingsPerBindGroup,
+ `can not test ${testValue} bindings in same group because maxBindingsPerBindGroup = ${device.limits.maxBindingsPerBindGroup}`
+ );
+
if (bindingCombination === 'fragment') {
return;
}
@@ -140,6 +170,7 @@ g.test('createPipeline,at_over')
bindGroupTest,
(i, j) => `var u${j}_${i}: texture_storage_2d<rgba8unorm, write>`,
(i, j) => `textureStore(u${j}_${i}, vec2u(0), vec4f(1));`,
+ device.limits.maxBindGroups,
testValue
);
const module = device.createShaderModule({ code });
@@ -151,6 +182,7 @@ g.test('createPipeline,at_over')
shouldError,
`actualLimit: ${actualLimit}, testValue: ${testValue}\n:${code}`
);
- }
+ },
+ kExtraLimits
);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxUniformBuffersPerShaderStage.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxUniformBuffersPerShaderStage.spec.ts
index 7e55078f16..64de1a71f6 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxUniformBuffersPerShaderStage.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxUniformBuffersPerShaderStage.spec.ts
@@ -3,6 +3,7 @@ import {
reorder,
kReorderOrderKeys,
ReorderOrder,
+ assert,
} from '../../../../../common/util/util.js';
import { kShaderStageCombinationsWithStage } from '../../../../capability_info.js';
@@ -13,8 +14,14 @@ import {
kBindingCombinations,
getPipelineTypeForBindingCombination,
getPerStageWGSLForBindingCombination,
+ LimitsRequest,
} from './limit_utils.js';
+const kExtraLimits: LimitsRequest = {
+ maxBindingsPerBindGroup: 'adapterLimit',
+ maxBindGroups: 'adapterLimit',
+};
+
const limit = 'maxUniformBuffersPerShaderStage';
export const { g, description } = makeLimitTestGroup(limit);
@@ -56,11 +63,17 @@ g.test('createBindGroupLayout,at_over')
limitTest,
testValueName,
async ({ device, testValue, shouldError }) => {
+ t.skipIf(
+ t.adapter.limits.maxBindingsPerBindGroup < testValue,
+ `maxBindingsPerBindGroup = ${t.adapter.limits.maxBindingsPerBindGroup} which is less than ${testValue}`
+ );
+
await t.expectValidationError(
() => createBindGroupLayout(device, visibility, order, testValue),
shouldError
);
- }
+ },
+ kExtraLimits
);
});
@@ -83,18 +96,30 @@ g.test('createPipelineLayout,at_over')
await t.testDeviceWithRequestedMaximumLimits(
limitTest,
testValueName,
- async ({ device, testValue, shouldError }) => {
- const kNumGroups = 3;
+ async ({ device, testValue, shouldError, actualLimit }) => {
+ const maxBindingsPerBindGroup = Math.min(
+ t.device.limits.maxBindingsPerBindGroup,
+ actualLimit
+ );
+ const kNumGroups = Math.ceil(testValue / maxBindingsPerBindGroup);
+
+ // Not sure what to do in this case but best we get notified if it happens.
+ assert(kNumGroups <= t.device.limits.maxBindGroups);
+
const bindGroupLayouts = range(kNumGroups, i => {
- const minInGroup = Math.floor(testValue / kNumGroups);
- const numInGroup = i ? minInGroup : testValue - minInGroup * (kNumGroups - 1);
+ const numInGroup = Math.min(
+ testValue - i * maxBindingsPerBindGroup,
+ maxBindingsPerBindGroup
+ );
return createBindGroupLayout(device, visibility, order, numInGroup);
});
+
await t.expectValidationError(
() => device.createPipelineLayout({ bindGroupLayouts }),
shouldError
);
- }
+ },
+ kExtraLimits
);
});
@@ -122,12 +147,18 @@ g.test('createPipeline,at_over')
limitTest,
testValueName,
async ({ device, testValue, actualLimit, shouldError }) => {
+ t.skipIf(
+ bindGroupTest === 'sameGroup' && testValue > device.limits.maxBindingsPerBindGroup,
+ `can not test ${testValue} bindings in same group because maxBindingsPerBindGroup = ${device.limits.maxBindingsPerBindGroup}`
+ );
+
const code = getPerStageWGSLForBindingCombination(
bindingCombination,
order,
bindGroupTest,
(i, j) => `var<uniform> u${j}_${i}: f32`,
(i, j) => `_ = u${j}_${i};`,
+ device.limits.maxBindGroups,
testValue
);
const module = device.createShaderModule({ code });
@@ -139,6 +170,7 @@ g.test('createPipeline,at_over')
shouldError,
`actualLimit: ${actualLimit}, testValue: ${testValue}\n:${code}`
);
- }
+ },
+ kExtraLimits
);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxVertexBuffers.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxVertexBuffers.spec.ts
index 7f760fe9b6..51cb44d55b 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxVertexBuffers.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxVertexBuffers.spec.ts
@@ -1,41 +1,23 @@
-import { range } from '../../../../../common/util/util.js';
-
import { kRenderEncoderTypes, kMaximumLimitBaseParams, makeLimitTestGroup } from './limit_utils.js';
-const kPipelineTypes = ['withoutLocations', 'withLocations'] as const;
-type PipelineType = (typeof kPipelineTypes)[number];
+function getPipelineDescriptor(device: GPUDevice, testValue: number): GPURenderPipelineDescriptor {
+ const module = device.createShaderModule({
+ code: `
+ @vertex fn vs(@location(0) p: vec4f) -> @builtin(position) vec4f {
+ return p;
+ }`,
+ });
+ const buffers = new Array<GPUVertexBufferLayout>(testValue);
+ buffers[testValue - 1] = {
+ arrayStride: 16,
+ attributes: [{ shaderLocation: 0, offset: 0, format: 'float32' }],
+ };
-function getPipelineDescriptor(
- device: GPUDevice,
- pipelineType: PipelineType,
- testValue: number
-): GPURenderPipelineDescriptor {
- const code =
- pipelineType === 'withLocations'
- ? `
- struct VSInput {
- ${range(testValue, i => `@location(${i}) p${i}: f32,`).join('\n')}
- }
- @vertex fn vs(v: VSInput) -> @builtin(position) vec4f {
- let x = ${range(testValue, i => `v.p${i}`).join(' + ')};
- return vec4f(x, 0, 0, 1);
- }
- `
- : `
- @vertex fn vs() -> @builtin(position) vec4f {
- return vec4f(0);
- }
- `;
- const module = device.createShaderModule({ code });
return {
layout: 'auto',
vertex: {
module,
- entryPoint: 'vs',
- buffers: range(testValue, i => ({
- arrayStride: 32,
- attributes: [{ shaderLocation: i, offset: 0, format: 'float32' }],
- })),
+ buffers,
},
};
}
@@ -45,18 +27,22 @@ export const { g, description } = makeLimitTestGroup(limit);
g.test('createRenderPipeline,at_over')
.desc(`Test using at and over ${limit} limit in createRenderPipeline(Async)`)
- .params(
- kMaximumLimitBaseParams.combine('async', [false, true]).combine('pipelineType', kPipelineTypes)
- )
+ .params(kMaximumLimitBaseParams.combine('async', [false, true]))
.fn(async t => {
- const { limitTest, testValueName, async, pipelineType } = t.params;
+ const { limitTest, testValueName, async } = t.params;
await t.testDeviceWithRequestedMaximumLimits(
limitTest,
testValueName,
- async ({ device, testValue, shouldError }) => {
- const pipelineDescriptor = getPipelineDescriptor(device, pipelineType, testValue);
+ async ({ device, testValue, shouldError, actualLimit }) => {
+ const pipelineDescriptor = getPipelineDescriptor(device, testValue);
+ const lastIndex = testValue - 1;
- await t.testCreateRenderPipeline(pipelineDescriptor, async, shouldError);
+ await t.testCreateRenderPipeline(
+ pipelineDescriptor,
+ async,
+ shouldError,
+ `lastIndex: ${lastIndex}, actualLimit: ${actualLimit}, shouldError: ${shouldError}`
+ );
}
);
});
@@ -77,10 +63,10 @@ g.test('setVertexBuffer,at_over')
usage: GPUBufferUsage.VERTEX,
});
- await t.testGPURenderCommandsMixin(
+ await t.testGPURenderAndBindingCommandsMixin(
encoderType,
- ({ mixin }) => {
- mixin.setVertexBuffer(lastIndex, buffer);
+ ({ passEncoder }) => {
+ passEncoder.setVertexBuffer(lastIndex, buffer);
},
shouldError,
`lastIndex: ${lastIndex}, actualLimit: ${actualLimit}, shouldError: ${shouldError}`
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/compute_pipeline.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/compute_pipeline.spec.ts
index 3a0a51b363..790f25897a 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/compute_pipeline.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/compute_pipeline.spec.ts
@@ -5,9 +5,16 @@ Note: entry point matching tests are in shader_module/entry_point.spec.ts
`;
import { makeTestGroup } from '../../../common/framework/test_group.js';
+import { keysOf } from '../../../common/util/data_tables.js';
import { kValue } from '../../util/constants.js';
import { TShaderStage, getShaderWithEntryPoint } from '../../util/shader.js';
+import {
+ kAPIResources,
+ getWGSLShaderForResource,
+ getAPIBindGroupLayoutForResource,
+ doResourcesMatch,
+} from './utils.js';
import { ValidationTest } from './validation_test.js';
class F extends ValidationTest {
@@ -690,3 +697,46 @@ Tests calling createComputePipeline(Async) validation for overridable constants
testFn(maxVec4Count + 1, 0, false);
testFn(0, maxMat4Count + 1, false);
});
+
+g.test('resource_compatibility')
+ .desc(
+ 'Tests validation of resource (bind group) compatibility between pipeline layout and WGSL shader'
+ )
+ .params(u =>
+ u //
+ .combine('apiResource', keysOf(kAPIResources))
+ .beginSubcases()
+ .combine('isAsync', [true, false] as const)
+ .combine('wgslResource', keysOf(kAPIResources))
+ )
+ .fn(t => {
+ const apiResource = kAPIResources[t.params.apiResource];
+ const wgslResource = kAPIResources[t.params.wgslResource];
+ t.skipIf(
+ wgslResource.storageTexture !== undefined &&
+ wgslResource.storageTexture.access !== 'write-only' &&
+ !t.hasLanguageFeature('readonly_and_readwrite_storage_textures'),
+ 'Storage textures require language feature'
+ );
+
+ const layout = t.device.createPipelineLayout({
+ bindGroupLayouts: [
+ getAPIBindGroupLayoutForResource(t.device, GPUShaderStage.COMPUTE, apiResource),
+ ],
+ });
+
+ const descriptor = {
+ layout,
+ compute: {
+ module: t.device.createShaderModule({
+ code: getWGSLShaderForResource('compute', wgslResource),
+ }),
+ entryPoint: 'main',
+ },
+ };
+ t.doCreateComputePipelineTest(
+ t.params.isAsync,
+ doResourcesMatch(apiResource, wgslResource),
+ descriptor
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createBindGroup.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createBindGroup.spec.ts
index ddd0f8b39f..b2d1939a4f 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createBindGroup.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createBindGroup.spec.ts
@@ -8,6 +8,7 @@ import { makeTestGroup } from '../../../common/framework/test_group.js';
import { assert, makeValueTestVariant, unreachable } from '../../../common/util/util.js';
import {
allBindingEntries,
+ BindableResource,
bindingTypeInfo,
bufferBindingEntries,
bufferBindingTypeInfo,
@@ -106,7 +107,7 @@ g.test('binding_must_contain_resource_defined_in_layout')
.desc(
'Test that only compatible resource types specified in the BindGroupLayout are allowed for each entry.'
)
- .paramsSubcasesOnly(u =>
+ .params(u =>
u //
.combine('resourceType', kBindableResources)
.combine('entry', allBindingEntries(false))
@@ -121,6 +122,17 @@ g.test('binding_must_contain_resource_defined_in_layout')
const resource = t.getBindingResource(resourceType);
+ const IsStorageTextureResourceType = (resourceType: BindableResource) => {
+ switch (resourceType) {
+ case 'readonlyStorageTex':
+ case 'readwriteStorageTex':
+ case 'writeonlyStorageTex':
+ return true;
+ default:
+ return false;
+ }
+ };
+
let resourceBindingIsCompatible;
switch (info.resource) {
// Either type of sampler may be bound to a filtering sampler binding.
@@ -131,6 +143,11 @@ g.test('binding_must_contain_resource_defined_in_layout')
case 'nonFiltSamp':
resourceBindingIsCompatible = resourceType === 'nonFiltSamp';
break;
+ case 'readonlyStorageTex':
+ case 'readwriteStorageTex':
+ case 'writeonlyStorageTex':
+ resourceBindingIsCompatible = IsStorageTextureResourceType(resourceType);
+ break;
default:
resourceBindingIsCompatible = info.resource === resourceType;
break;
@@ -166,7 +183,7 @@ g.test('texture_binding_must_have_correct_usage')
const descriptor = {
size: { width: 16, height: 16, depthOrArrayLayers: 1 },
- format: 'rgba8unorm' as const,
+ format: 'r32float' as const,
usage: appliedUsage,
sampleCount: info.resource === 'sampledTexMS' ? 4 : 1,
};
@@ -313,6 +330,19 @@ g.test('texture_must_have_correct_dimension')
});
t.skipIfTextureViewDimensionNotSupported(viewDimension, dimension);
+ if (t.isCompatibility && texture.dimension === '2d') {
+ if (depthOrArrayLayers === 1) {
+ t.skipIf(
+ viewDimension !== '2d',
+ '1 layer 2d textures default to textureBindingViewDimension: "2d" in compat mode'
+ );
+ } else {
+ t.skipIf(
+ viewDimension !== '2d-array',
+ '> 1 layer 2d textures default to textureBindingViewDimension "2d-array" in compat mode'
+ );
+ }
+ }
const shouldError = viewDimension !== dimension;
const textureView = texture.createView({ dimension });
@@ -526,9 +556,7 @@ g.test('buffer,resource_state')
g.test('texture,resource_state')
.desc('Test bind group creation with various texture resource states')
.paramsSubcasesOnly(u =>
- u
- .combine('state', kResourceStates)
- .combine('entry', sampledAndStorageBindingEntries(true, 'rgba8unorm'))
+ u.combine('state', kResourceStates).combine('entry', sampledAndStorageBindingEntries(true))
)
.fn(t => {
const { state, entry } = t.params;
@@ -548,10 +576,11 @@ g.test('texture,resource_state')
const usage = entry.texture?.multisampled
? info.usage | GPUConst.TextureUsage.RENDER_ATTACHMENT
: info.usage;
+ const format = entry.storageTexture !== undefined ? 'r32float' : 'rgba8unorm';
const texture = t.createTextureWithState(state, {
usage,
size: [1, 1],
- format: 'rgba8unorm',
+ format,
sampleCount: entry.texture?.multisampled ? 4 : 1,
});
@@ -626,7 +655,9 @@ g.test('binding_resources,device_mismatch')
{ buffer: { type: 'storage' } },
{ sampler: { type: 'filtering' } },
{ texture: { multisampled: false } },
- { storageTexture: { access: 'write-only', format: 'rgba8unorm' } },
+ { storageTexture: { access: 'write-only', format: 'r32float' } },
+ { storageTexture: { access: 'read-only', format: 'r32float' } },
+ { storageTexture: { access: 'read-write', format: 'r32float' } },
] as const)
.beginSubcases()
.combineWithParams([
@@ -784,6 +815,10 @@ g.test('storage_texture,format')
.combine('storageTextureFormat', kStorageTextureFormats)
.combine('resourceFormat', kStorageTextureFormats)
)
+ .beforeAllSubcases(t => {
+ const { storageTextureFormat, resourceFormat } = t.params;
+ t.skipIfTextureFormatNotUsableAsStorageTexture(storageTextureFormat, resourceFormat);
+ })
.fn(t => {
const { storageTextureFormat, resourceFormat } = t.params;
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createBindGroupLayout.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createBindGroupLayout.spec.ts
index a50247aa13..b09adc2af1 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createBindGroupLayout.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createBindGroupLayout.spec.ts
@@ -163,8 +163,10 @@ g.test('visibility,VERTEX_shader_stage_storage_texture_access')
.fn(t => {
const { shaderStage, access } = t.params;
+ const appliedAccess = access ?? 'write-only';
const success = !(
- (access ?? 'write-only') === 'write-only' && shaderStage & GPUShaderStage.VERTEX
+ // If visibility includes VERETX, storageTexture.access must be "read-only"
+ (shaderStage & GPUShaderStage.VERTEX && appliedAccess !== 'read-only')
);
t.expectValidationError(() => {
@@ -173,7 +175,7 @@ g.test('visibility,VERTEX_shader_stage_storage_texture_access')
{
binding: 0,
visibility: shaderStage,
- storageTexture: { access, format: 'rgba8unorm' },
+ storageTexture: { access, format: 'r32uint' },
},
],
});
@@ -436,29 +438,36 @@ g.test('storage_texture,layout_dimension')
g.test('storage_texture,formats')
.desc(
`
- Test that a validation error is generated if the format doesn't support the storage usage.
+ Test that a validation error is generated if the format doesn't support the storage usage. A
+ validation error is also generated if the format doesn't support the 'read-write' storage access
+ when the storage access is 'read-write'.
`
)
- .params(u => u.combine('format', kAllTextureFormats))
+ .params(u =>
+ u //
+ .combine('format', kAllTextureFormats) //
+ .combine('access', kStorageTextureAccessValues)
+ )
.beforeAllSubcases(t => {
t.selectDeviceForTextureFormatOrSkipTestCase(t.params.format);
+ t.skipIfTextureFormatNotUsableAsStorageTexture(t.params.format);
})
.fn(t => {
- const { format } = t.params;
+ const { format, access } = t.params;
const info = kTextureFormatInfo[format];
- t.expectValidationError(
- () => {
- t.device.createBindGroupLayout({
- entries: [
- {
- binding: 0,
- visibility: GPUShaderStage.COMPUTE,
- storageTexture: { format },
- },
- ],
- });
- },
- !info.color?.storage
- );
+ const success =
+ info.color?.storage && !(access === 'read-write' && !info.color?.readWriteStorage);
+
+ t.expectValidationError(() => {
+ t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.COMPUTE,
+ storageTexture: { format, access },
+ },
+ ],
+ });
+ }, !success);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createTexture.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createTexture.spec.ts
index a9fe352b74..fc1c8b86b2 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createTexture.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createTexture.spec.ts
@@ -6,7 +6,7 @@ import { assert, makeValueTestVariant } from '../../../common/util/util.js';
import { kTextureDimensions, kTextureUsages } from '../../capability_info.js';
import { GPUConst } from '../../constants.js';
import {
- kTextureFormats,
+ kAllTextureFormats,
kTextureFormatInfo,
kCompressedTextureFormats,
kUncompressedTextureFormats,
@@ -15,6 +15,7 @@ import {
filterFormatsByFeature,
viewCompatible,
textureDimensionAndFormatCompatible,
+ isTextureFormatUsableAsStorageFormat,
} from '../../format_info.js';
import { maxMipLevelCount } from '../../util/texture/base.js';
@@ -103,7 +104,9 @@ g.test('dimension_type_and_format_compatibility')
`Test every dimension type on every format. Note that compressed formats and depth/stencil formats are not valid for 1D/3D dimension types.`
)
.params(u =>
- u.combine('dimension', [undefined, ...kTextureDimensions]).combine('format', kTextureFormats)
+ u //
+ .combine('dimension', [undefined, ...kTextureDimensions])
+ .combine('format', kAllTextureFormats)
)
.beforeAllSubcases(t => {
const { format } = t.params;
@@ -135,7 +138,7 @@ g.test('mipLevelCount,format')
.params(u =>
u
.combine('dimension', [undefined, ...kTextureDimensions])
- .combine('format', kTextureFormats)
+ .combine('format', kAllTextureFormats)
.beginSubcases()
.combine('mipLevelCount', [1, 2, 3, 6, 7])
// Filter out incompatible dimension type and format combinations.
@@ -270,7 +273,7 @@ g.test('sampleCount,various_sampleCount_with_all_formats')
.params(u =>
u
.combine('dimension', [undefined, '2d'] as const)
- .combine('format', kTextureFormats)
+ .combine('format', kAllTextureFormats)
.beginSubcases()
.combine('sampleCount', [0, 1, 2, 4, 8, 16, 32, 256])
)
@@ -296,7 +299,7 @@ g.test('sampleCount,various_sampleCount_with_all_formats')
usage,
};
- const success = sampleCount === 1 || (sampleCount === 4 && info.multisample && info.renderable);
+ const success = sampleCount === 1 || (sampleCount === 4 && info.multisample);
t.expectValidationError(() => {
t.device.createTexture(descriptor);
@@ -317,7 +320,7 @@ g.test('sampleCount,valid_sampleCount_with_other_parameter_varies')
.params(u =>
u
.combine('dimension', [undefined, ...kTextureDimensions])
- .combine('format', kTextureFormats)
+ .combine('format', kAllTextureFormats)
.beginSubcases()
.combine('sampleCount', [1, 4])
.combine('arrayLayerCount', [1, 2])
@@ -372,8 +375,12 @@ g.test('sampleCount,valid_sampleCount_with_other_parameter_varies')
usage,
};
+ const satisfyWithStorageUsageRequirement =
+ (usage & GPUConst.TextureUsage.STORAGE_BINDING) === 0 ||
+ isTextureFormatUsableAsStorageFormat(format, t.isCompatibility);
+
const success =
- sampleCount === 1 ||
+ (sampleCount === 1 && satisfyWithStorageUsageRequirement) ||
(sampleCount === 4 &&
(dimension === '2d' || dimension === undefined) &&
kTextureFormatInfo[format].multisample &&
@@ -589,6 +596,7 @@ g.test('texture_size,2d_texture,compressed_format')
u
.combine('dimension', [undefined, '2d'] as const)
.combine('format', kCompressedTextureFormats)
+ .beginSubcases()
.expand('sizeVariant', p => {
const { blockWidth, blockHeight } = kTextureFormatInfo[p.format];
return [
@@ -1026,7 +1034,7 @@ g.test('texture_usage')
.params(u =>
u
.combine('dimension', [undefined, ...kTextureDimensions])
- .combine('format', kTextureFormats)
+ .combine('format', kAllTextureFormats)
.beginSubcases()
// If usage0 and usage1 are the same, then the usage being test is a single usage. Otherwise, it is a combined usage.
.combine('usage0', kTextureUsages)
@@ -1058,12 +1066,13 @@ g.test('texture_usage')
// Note that we unconditionally test copy usages for all formats. We don't check copySrc/copyDst in kTextureFormatInfo in capability_info.js
// if (!info.copySrc && (usage & GPUTextureUsage.COPY_SRC) !== 0) success = false;
// if (!info.copyDst && (usage & GPUTextureUsage.COPY_DST) !== 0) success = false;
- if (!info.color?.storage && (usage & GPUTextureUsage.STORAGE_BINDING) !== 0) success = false;
- if (
- (!info.renderable || appliedDimension !== '2d') &&
- (usage & GPUTextureUsage.RENDER_ATTACHMENT) !== 0
- )
- success = false;
+ if (usage & GPUTextureUsage.STORAGE_BINDING) {
+ if (!isTextureFormatUsableAsStorageFormat(format, t.isCompatibility)) success = false;
+ }
+ if (usage & GPUTextureUsage.RENDER_ATTACHMENT) {
+ if (appliedDimension === '1d') success = false;
+ if (info.color && !info.colorRender) success = false;
+ }
t.expectValidationError(() => {
t.device.createTexture(descriptor);
@@ -1080,10 +1089,10 @@ g.test('viewFormats')
.combine('viewFormatFeature', kFeaturesForFormats)
.beginSubcases()
.expand('format', ({ formatFeature }) =>
- filterFormatsByFeature(formatFeature, kTextureFormats)
+ filterFormatsByFeature(formatFeature, kAllTextureFormats)
)
.expand('viewFormat', ({ viewFormatFeature }) =>
- filterFormatsByFeature(viewFormatFeature, kTextureFormats)
+ filterFormatsByFeature(viewFormatFeature, kAllTextureFormats)
)
)
.beforeAllSubcases(t => {
@@ -1096,7 +1105,7 @@ g.test('viewFormats')
t.skipIfTextureFormatNotSupported(format, viewFormat);
- const compatible = viewCompatible(format, viewFormat);
+ const compatible = viewCompatible(t.isCompatibility, format, viewFormat);
// Test the viewFormat in the list.
t.expectValidationError(() => {
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createView.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createView.spec.ts
index e4871c5d80..8949ea1eeb 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createView.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createView.spec.ts
@@ -10,7 +10,7 @@ import {
} from '../../capability_info.js';
import {
kTextureFormatInfo,
- kTextureFormats,
+ kAllTextureFormats,
kFeaturesForFormats,
filterFormatsByFeature,
viewCompatible,
@@ -39,10 +39,10 @@ g.test('format')
.combine('viewFormatFeature', kFeaturesForFormats)
.beginSubcases()
.expand('textureFormat', ({ textureFormatFeature }) =>
- filterFormatsByFeature(textureFormatFeature, kTextureFormats)
+ filterFormatsByFeature(textureFormatFeature, kAllTextureFormats)
)
.expand('viewFormat', ({ viewFormatFeature }) =>
- filterFormatsByFeature(viewFormatFeature, [undefined, ...kTextureFormats])
+ filterFormatsByFeature(viewFormatFeature, [undefined, ...kAllTextureFormats])
)
.combine('useViewFormatList', [false, true])
)
@@ -56,7 +56,8 @@ g.test('format')
t.skipIfTextureFormatNotSupported(textureFormat, viewFormat);
- const compatible = viewFormat === undefined || viewCompatible(textureFormat, viewFormat);
+ const compatible =
+ viewFormat === undefined || viewCompatible(t.isCompatibility, textureFormat, viewFormat);
const texture = t.device.createTexture({
format: textureFormat,
@@ -123,7 +124,7 @@ g.test('aspect')
)
.params(u =>
u //
- .combine('format', kTextureFormats)
+ .combine('format', kAllTextureFormats)
.combine('aspect', kTextureAspects)
)
.beforeAllSubcases(t => {
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/cmds/copyTextureToTexture.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/cmds/copyTextureToTexture.spec.ts
index f1c3d91e29..75f4a092fc 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/cmds/copyTextureToTexture.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/cmds/copyTextureToTexture.spec.ts
@@ -6,7 +6,7 @@ import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { kTextureUsages, kTextureDimensions } from '../../../../capability_info.js';
import {
kTextureFormatInfo,
- kTextureFormats,
+ kAllTextureFormats,
kCompressedTextureFormats,
kDepthStencilFormats,
kFeaturesForFormats,
@@ -255,6 +255,12 @@ Test that textures in copyTextureToTexture must have the same sample count.
.combine('srcSampleCount', [1, 4])
.combine('dstSampleCount', [1, 4])
)
+ .beforeAllSubcases(t => {
+ t.skipIf(
+ t.isCompatibility && (t.params.srcSampleCount !== 1 || t.params.dstSampleCount !== 1),
+ 'multisample textures are not copyable in compatibility mode'
+ );
+ })
.fn(t => {
const { srcSampleCount, dstSampleCount } = t.params;
@@ -307,6 +313,9 @@ TODO: Check the source and destination constraints separately.
.expand('copyWidth', p => [32 - Math.max(p.srcCopyOrigin.x, p.dstCopyOrigin.x), 16])
.expand('copyHeight', p => [16 - Math.max(p.srcCopyOrigin.y, p.dstCopyOrigin.y), 8])
)
+ .beforeAllSubcases(t => {
+ t.skipIf(t.isCompatibility, 'multisample textures are not copyable in compatibility mode');
+ })
.fn(t => {
const { srcCopyOrigin, dstCopyOrigin, copyWidth, copyHeight } = t.params;
@@ -351,10 +360,10 @@ Test the formats of textures in copyTextureToTexture must be copy-compatible.
.combine('dstFormatFeature', kFeaturesForFormats)
.beginSubcases()
.expand('srcFormat', ({ srcFormatFeature }) =>
- filterFormatsByFeature(srcFormatFeature, kTextureFormats)
+ filterFormatsByFeature(srcFormatFeature, kAllTextureFormats)
)
.expand('dstFormat', ({ dstFormatFeature }) =>
- filterFormatsByFeature(dstFormatFeature, kTextureFormats)
+ filterFormatsByFeature(dstFormatFeature, kAllTextureFormats)
)
)
.beforeAllSubcases(t => {
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/createRenderBundleEncoder.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/createRenderBundleEncoder.spec.ts
index 2eaa9b43fd..b4beeb8fbe 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/createRenderBundleEncoder.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/createRenderBundleEncoder.spec.ts
@@ -196,10 +196,7 @@ g.test('valid_texture_formats')
g.test('depth_stencil_readonly')
.desc(
`
- Tests that createRenderBundleEncoder validation of depthReadOnly and stencilReadOnly
- - With depth-only formats
- - With stencil-only formats
- - With depth-stencil-combined formats
+ Test that allow combinations of depth-stencil format, depthReadOnly and stencilReadOnly are allowed.
`
)
.params(u =>
@@ -215,44 +212,9 @@ g.test('depth_stencil_readonly')
})
.fn(t => {
const { depthStencilFormat, depthReadOnly, stencilReadOnly } = t.params;
-
- let shouldError = false;
- if (
- kTextureFormatInfo[depthStencilFormat].depth &&
- kTextureFormatInfo[depthStencilFormat].stencil &&
- depthReadOnly !== stencilReadOnly
- ) {
- shouldError = true;
- }
-
- t.expectValidationError(() => {
- t.device.createRenderBundleEncoder({
- colorFormats: [],
- depthStencilFormat,
- depthReadOnly,
- stencilReadOnly,
- });
- }, shouldError);
- });
-
-g.test('depth_stencil_readonly_with_undefined_depth')
- .desc(
- `
- Tests that createRenderBundleEncoder validation of depthReadOnly and stencilReadOnly is ignored
- if there is no depthStencilFormat set.
- `
- )
- .params(u =>
- u //
- .beginSubcases()
- .combine('depthReadOnly', [false, true])
- .combine('stencilReadOnly', [false, true])
- )
- .fn(t => {
- const { depthReadOnly, stencilReadOnly } = t.params;
-
t.device.createRenderBundleEncoder({
- colorFormats: ['bgra8unorm'],
+ colorFormats: [],
+ depthStencilFormat,
depthReadOnly,
stencilReadOnly,
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/encoder_open_state.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/encoder_open_state.spec.ts
index 0d56222eed..815ca5a198 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/encoder_open_state.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/encoder_open_state.spec.ts
@@ -57,7 +57,10 @@ class F extends ValidationTest {
export const g = makeTestGroup(F);
-type EncoderCommands = keyof Omit<GPUCommandEncoder, '__brand' | 'label' | 'finish'>;
+// MAINTENANCE_TODO: Remove writeTimestamp from here once it's (hopefully) added back to the spec.
+type EncoderCommands =
+ | keyof Omit<GPUCommandEncoder, '__brand' | 'label' | 'finish'>
+ | 'writeTimestamp';
const kEncoderCommandInfo: {
readonly [k in EncoderCommands]: {};
} = {
@@ -146,6 +149,8 @@ g.test('non_pass_commands')
`
Test that functions of GPUCommandEncoder generate a validation error if the encoder is already
finished.
+
+ TODO: writeTimestamp is removed from the spec so it's skipped if it TypeErrors.
`
)
.params(u =>
@@ -260,8 +265,11 @@ g.test('non_pass_commands')
}
break;
case 'writeTimestamp':
- {
- encoder.writeTimestamp(querySet, 0);
+ try {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (encoder as any).writeTimestamp(querySet, 0);
+ } catch (ex) {
+ t.skipIf(ex instanceof TypeError, 'writeTimestamp is actually not available');
}
break;
case 'resolveQuerySet':
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/programmable/pipeline_bind_group_compat.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/programmable/pipeline_bind_group_compat.spec.ts
index 163c20c311..8da3be33de 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/programmable/pipeline_bind_group_compat.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/programmable/pipeline_bind_group_compat.spec.ts
@@ -32,13 +32,32 @@ type ComputeCmd = (typeof kComputeCmds)[number];
const kRenderCmds = ['draw', 'drawIndexed', 'drawIndirect', 'drawIndexedIndirect'] as const;
type RenderCmd = (typeof kRenderCmds)[number];
+const kPipelineTypes = ['auto0', 'explicit'] as const;
+type PipelineType = (typeof kPipelineTypes)[number];
+const kBindingTypes = ['auto0', 'auto1', 'explicit'] as const;
+type BindingType = (typeof kBindingTypes)[number];
+
+const kEmptyBindGroup0Ndx = 0;
+const kEmptyBindGroup1Ndx = 1;
+const kNonEmptyBindGroup0Ndx = 2;
+const kNonEmptyBindGroup1Ndx = 3;
+
+// Swaps 2 array elements in place.
+function swapArrayElements<T>(array: T[], ndx1: number, ndx2: number) {
+ const t = array[ndx1];
+ array[ndx1] = array[ndx2];
+ array[ndx2] = t;
+}
+
// Test resource type compatibility in pipeline and bind group
// [1]: Need to add externalTexture
const kResourceTypes: ValidBindableResource[] = [
'uniformBuf',
'filtSamp',
'sampledTex',
- 'storageTex',
+ 'readonlyStorageTex',
+ 'writeonlyStorageTex',
+ 'readwriteStorageTex',
];
function getTestCmds(
@@ -75,7 +94,17 @@ class F extends ValidationTest {
if (entry.buffer !== undefined) return 'uniformBuf';
if (entry.sampler !== undefined) return 'filtSamp';
if (entry.texture !== undefined) return 'sampledTex';
- if (entry.storageTexture !== undefined) return 'storageTex';
+ if (entry.storageTexture !== undefined) {
+ switch (entry.storageTexture.access) {
+ case undefined:
+ case 'write-only':
+ return 'writeonlyStorageTex';
+ case 'read-only':
+ return 'readonlyStorageTex';
+ case 'read-write':
+ return 'readwriteStorageTex';
+ }
+ }
unreachable();
}
@@ -208,8 +237,14 @@ class F extends ValidationTest {
case 'sampledTex':
entry.texture = {}; // default sampleType: float
break;
- case 'storageTex':
- entry.storageTexture = { access: 'write-only', format: 'rgba8unorm' };
+ case 'readonlyStorageTex':
+ entry.storageTexture = { access: 'read-only', format: 'r32float' };
+ break;
+ case 'writeonlyStorageTex':
+ entry.storageTexture = { access: 'write-only', format: 'r32float' };
+ break;
+ case 'readwriteStorageTex':
+ entry.storageTexture = { access: 'read-write', format: 'r32float' };
break;
}
@@ -259,6 +294,135 @@ class F extends ValidationTest {
validateFinish(success);
}
+
+ runDefaultLayoutBindingTest<T extends GPURenderPipeline | GPUComputePipeline>({
+ visibility,
+ empty,
+ pipelineType,
+ bindingType,
+ swap,
+ success,
+ makePipelinesFn,
+ doCommandFn,
+ }: {
+ visibility: number;
+ empty: boolean;
+ pipelineType: PipelineType;
+ bindingType: BindingType;
+ swap: boolean;
+ success: boolean;
+ makePipelinesFn: (t: F, explicitPipelineLayout: GPUPipelineLayout) => T[];
+ doCommandFn: (params: {
+ t: F;
+ encoder: GPUCommandEncoder;
+ pipeline: T;
+ emptyBindGroups: GPUBindGroup[];
+ nonEmptyBindGroups: GPUBindGroup[];
+ }) => void;
+ }) {
+ const { device } = this;
+ const explicitEmptyBindGroupLayout = device.createBindGroupLayout({
+ entries: [],
+ });
+ const explicitBindGroupLayout = device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility,
+ buffer: {},
+ },
+ ],
+ });
+ const explicitPipelineLayout = device.createPipelineLayout({
+ bindGroupLayouts: [
+ explicitEmptyBindGroupLayout,
+ explicitEmptyBindGroupLayout,
+ explicitBindGroupLayout,
+ explicitBindGroupLayout,
+ ],
+ });
+
+ const [pipelineAuto0, pipelineAuto1, pipelineExplicit] = makePipelinesFn(
+ this,
+ explicitPipelineLayout
+ );
+
+ const buffer = device.createBuffer({
+ size: 16,
+ usage: GPUBufferUsage.UNIFORM,
+ });
+ this.trackForCleanup(buffer);
+
+ let emptyBindGroupLayouts;
+ let nonEmptyBindGroupLayouts;
+ const pipeline = pipelineType === 'auto0' ? pipelineAuto0 : pipelineExplicit;
+
+ // Gets a bindGroupLayout either from the explicit layout passed in
+ // or one of the 2 `layout: 'auto'` pipelines.
+ const getBindGroupLayout = (
+ explicitBindGroupLayout: GPUBindGroupLayout,
+ bindGroupIndex: number
+ ) =>
+ bindingType === 'explicit'
+ ? explicitBindGroupLayout
+ : bindingType === 'auto0'
+ ? pipelineAuto0.getBindGroupLayout(bindGroupIndex)
+ : pipelineAuto1.getBindGroupLayout(bindGroupIndex);
+
+ if (empty) {
+ // Testing empty:
+ // - emptyBindGroupLayout comes from a possibly incompatible place.
+ // - nonEmptyBindGroupLayout comes from the pipeline we'll render/compute with.
+ emptyBindGroupLayouts = [
+ getBindGroupLayout(explicitEmptyBindGroupLayout, kEmptyBindGroup0Ndx),
+ getBindGroupLayout(explicitEmptyBindGroupLayout, kEmptyBindGroup1Ndx),
+ ];
+ if (swap) {
+ swapArrayElements(emptyBindGroupLayouts, 0, 1);
+ }
+ nonEmptyBindGroupLayouts = [
+ pipeline.getBindGroupLayout(kNonEmptyBindGroup0Ndx),
+ pipeline.getBindGroupLayout(kNonEmptyBindGroup1Ndx),
+ ];
+ } else {
+ // Testing non-empty:
+ // - nonEmptyBindGroupLayout comes from a possibly incompatible place.
+ // - emptyBindGroupLayout comes from the pipeline we'll render/compute with.
+ nonEmptyBindGroupLayouts = [
+ getBindGroupLayout(explicitBindGroupLayout, kNonEmptyBindGroup0Ndx),
+ getBindGroupLayout(explicitBindGroupLayout, kNonEmptyBindGroup1Ndx),
+ ];
+ if (swap) {
+ swapArrayElements(nonEmptyBindGroupLayouts, 0, 1);
+ }
+ emptyBindGroupLayouts = [
+ pipeline.getBindGroupLayout(kEmptyBindGroup0Ndx),
+ pipeline.getBindGroupLayout(kEmptyBindGroup1Ndx),
+ ];
+ }
+
+ const emptyBindGroups = emptyBindGroupLayouts.map(layout =>
+ device.createBindGroup({
+ layout,
+ entries: [],
+ })
+ );
+
+ const nonEmptyBindGroups = nonEmptyBindGroupLayouts.map(layout =>
+ device.createBindGroup({
+ layout,
+ entries: [{ binding: 0, resource: { buffer } }],
+ })
+ );
+
+ const encoder = device.createCommandEncoder();
+
+ doCommandFn({ t: this, encoder, pipeline, emptyBindGroups, nonEmptyBindGroups });
+
+ this.expectValidationError(() => {
+ encoder.finish();
+ }, !success);
+ }
}
export const g = makeTestGroup(F);
@@ -775,3 +939,174 @@ g.test('empty_bind_group_layouts_requires_empty_bind_groups,render_pass')
encoder.finish();
}, !success);
});
+
+// pipelineType specifies which pipeline to try to render/compute with
+// auto0 = the first `layout: 'auto'` pipeline
+// explicit = a pipeline crated with an explicit pipeline layout using explicit bind group layouts
+//
+// bindingType specifies where to get bindGroupLayouts to use to create bindGroups
+// auto0 = the first `layout: 'auto'` pipeline
+// auto1 = the second `layout: 'auto'` pipeline
+// explicit = a pipeline crated with an explicit pipeline layout using explicit bind group layouts
+//
+// swap specifies to swap the bindgroups we're testing. We test 2 of each type, 2 empty bindgroups and
+// 2 non-empty bindgroups. The 2 empty bindgroups, when swapped should still be compatible. Similarly
+// the 2 non-empty bindgroups, when swapped, should still be compatible.
+const kPipelineTypesAndBindingTypeParams = [
+ { pipelineType: 'auto0', bindingType: 'auto0', swap: false, _success: true },
+ { pipelineType: 'explicit', bindingType: 'explicit', swap: false, _success: true },
+ { pipelineType: 'explicit', bindingType: 'auto0', swap: false, _success: false },
+ { pipelineType: 'auto0', bindingType: 'explicit', swap: false, _success: false },
+ { pipelineType: 'auto0', bindingType: 'auto1', swap: false, _success: false },
+ { pipelineType: 'auto0', bindingType: 'auto0', swap: true, _success: true },
+] as const;
+
+g.test('default_bind_group_layouts_never_match,compute_pass')
+ .desc(
+ `
+ Test that bind groups created with default bind group layouts never match other layouts, including empty bind groups.
+
+ * Test that a pipeline with an explicit layout can not be used with a bindGroup from an auto layout
+ * Test that a pipeline with an auto layout can not be used with a bindGroup from an explicit layout
+ * Test that an auto layout from one pipeline can not be used with an auto layout from a different pipeline.
+ * Test matching bindgroup layouts on the same default layout pipeline are compatible. In other words if
+ you only define group(2) then group(0)'s empty layout and group(1)'s empty layout should be compatible.
+ Similarly if group(2) and group(3) have the same types of resources they should be compatible.
+ `
+ )
+ .params(u =>
+ u
+ .combineWithParams(kPipelineTypesAndBindingTypeParams)
+ .combine('empty', [false, true])
+ .combine('computeCommand', ['dispatchIndirect', 'dispatch'] as const)
+ )
+ .fn(t => {
+ const { pipelineType, bindingType, swap, _success: success, computeCommand, empty } = t.params;
+
+ t.runDefaultLayoutBindingTest<GPUComputePipeline>({
+ visibility: GPUShaderStage.COMPUTE,
+ empty,
+ pipelineType,
+ bindingType,
+ swap,
+ success,
+ makePipelinesFn: (t, explicitPipelineLayout) => {
+ return (['auto', 'auto', explicitPipelineLayout] as const).map<GPUComputePipeline>(layout =>
+ t.device.createComputePipeline({
+ layout,
+ compute: {
+ module: t.device.createShaderModule({
+ code: `
+ @group(2) @binding(0) var<uniform> u1: vec4f;
+ @group(3) @binding(0) var<uniform> u2: vec4f;
+ @compute @workgroup_size(2) fn main() { _ = u1; _ = u2; }
+ `,
+ }),
+ entryPoint: 'main',
+ },
+ })
+ );
+ },
+ doCommandFn: ({ t, encoder, pipeline, emptyBindGroups, nonEmptyBindGroups }) => {
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(kEmptyBindGroup0Ndx, emptyBindGroups[0]);
+ pass.setBindGroup(kEmptyBindGroup1Ndx, emptyBindGroups[1]);
+ pass.setBindGroup(kNonEmptyBindGroup0Ndx, nonEmptyBindGroups[0]);
+ pass.setBindGroup(kNonEmptyBindGroup1Ndx, nonEmptyBindGroups[1]);
+ t.doCompute(pass, computeCommand, true);
+ pass.end();
+ },
+ });
+ });
+
+g.test('default_bind_group_layouts_never_match,render_pass')
+ .desc(
+ `
+ Test that bind groups created with default bind group layouts never match other layouts, including empty bind groups.
+
+ * Test that a pipeline with an explicit layout can not be used with a bindGroup from an auto layout
+ * Test that a pipeline with an auto layout can not be used with a bindGroup from an explicit layout
+ * Test that an auto layout from one pipeline can not be used with an auto layout from a different pipeline.
+ * Test matching bindgroup layouts on the same default layout pipeline are compatible. In other words if
+ you only define group(2) then group(0)'s empty layout and group(1)'s empty layout should be compatible.
+ Similarly if group(2) and group(3) have the same types of resources they should be compatible.
+ `
+ )
+ .params(u =>
+ u
+ .combineWithParams(kPipelineTypesAndBindingTypeParams)
+ .combine('empty', [false, true])
+ .combine('renderCommand', [
+ 'draw',
+ 'drawIndexed',
+ 'drawIndirect',
+ 'drawIndexedIndirect',
+ ] as const)
+ )
+ .fn(t => {
+ const { pipelineType, bindingType, swap, _success: success, renderCommand, empty } = t.params;
+
+ t.runDefaultLayoutBindingTest<GPURenderPipeline>({
+ visibility: GPUShaderStage.VERTEX,
+ empty,
+ pipelineType,
+ bindingType,
+ swap,
+ success,
+ makePipelinesFn: (t, explicitPipelineLayout) => {
+ return (['auto', 'auto', explicitPipelineLayout] as const).map<GPURenderPipeline>(
+ layout => {
+ const colorFormat = 'rgba8unorm';
+ return t.device.createRenderPipeline({
+ layout,
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+ @group(2) @binding(0) var<uniform> u1: vec4f;
+ @group(3) @binding(0) var<uniform> u2: vec4f;
+ @vertex fn main() -> @builtin(position) vec4f { return u1 + u2; }
+ `,
+ }),
+ entryPoint: 'main',
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: `@fragment fn main() {}`,
+ }),
+ entryPoint: 'main',
+ targets: [{ format: colorFormat, writeMask: 0 }],
+ },
+ });
+ }
+ );
+ },
+ doCommandFn: ({ t, encoder, pipeline, emptyBindGroups, nonEmptyBindGroups }) => {
+ const attachmentTexture = t.device.createTexture({
+ format: 'rgba8unorm',
+ size: { width: 16, height: 16, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ });
+ t.trackForCleanup(attachmentTexture);
+
+ const renderPass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: attachmentTexture.createView(),
+ clearValue: [0, 0, 0, 0],
+ loadOp: 'clear',
+ storeOp: 'store',
+ },
+ ],
+ });
+
+ renderPass.setPipeline(pipeline);
+ renderPass.setBindGroup(kEmptyBindGroup0Ndx, emptyBindGroups[0]);
+ renderPass.setBindGroup(kEmptyBindGroup1Ndx, emptyBindGroups[1]);
+ renderPass.setBindGroup(kNonEmptyBindGroup0Ndx, nonEmptyBindGroups[0]);
+ renderPass.setBindGroup(kNonEmptyBindGroup1Ndx, nonEmptyBindGroups[1]);
+ t.doRender(renderPass, renderCommand, true);
+ renderPass.end();
+ },
+ });
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/queries/general.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/queries/general.spec.ts
index 0ed2352bfd..8c9c4bb551 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/queries/general.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/queries/general.spec.ts
@@ -68,14 +68,16 @@ Tests that begin occlusion query with query index:
encoder.validateFinish(t.params.queryIndex < 2);
});
-g.test('timestamp_query,query_type_and_index')
+g.test('writeTimestamp,query_type_and_index')
.desc(
`
Tests that write timestamp to all types of query set on all possible encoders:
- type {occlusion, timestamp}
- queryIndex {in, out of} range for GPUQuerySet
- x= {non-pass} encoder
- `
+
+TODO: writeTimestamp is removed from the spec so it's skipped if it TypeErrors.
+`
)
.params(u =>
u
@@ -101,16 +103,23 @@ Tests that write timestamp to all types of query set on all possible encoders:
const querySet = createQuerySetWithType(t, type, count);
const encoder = t.createEncoder('non-pass');
- encoder.encoder.writeTimestamp(querySet, queryIndex);
+ try {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (encoder.encoder as any).writeTimestamp(querySet, queryIndex);
+ } catch (ex) {
+ t.skipIf(ex instanceof TypeError, 'writeTimestamp is actually not available');
+ }
encoder.validateFinish(type === 'timestamp' && queryIndex < count);
});
-g.test('timestamp_query,invalid_query_set')
+g.test('writeTimestamp,invalid_query_set')
.desc(
`
Tests that write timestamp to a invalid query set that failed during creation:
- x= {non-pass} encoder
- `
+
+TODO: writeTimestamp is removed from the spec so it's skipped if it TypeErrors.
+`
)
.paramsSubcasesOnly(u => u.combine('querySetState', ['valid', 'invalid'] as const))
.beforeAllSubcases(t => {
@@ -125,12 +134,22 @@ Tests that write timestamp to a invalid query set that failed during creation:
});
const encoder = t.createEncoder('non-pass');
- encoder.encoder.writeTimestamp(querySet, 0);
+ try {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (encoder.encoder as any).writeTimestamp(querySet, 0);
+ } catch (ex) {
+ t.skipIf(ex instanceof TypeError, 'writeTimestamp is actually not available');
+ }
encoder.validateFinish(querySetState !== 'invalid');
});
-g.test('timestamp_query,device_mismatch')
- .desc('Tests writeTimestamp cannot be called with a query set created from another device')
+g.test('writeTimestamp,device_mismatch')
+ .desc(
+ `Tests writeTimestamp cannot be called with a query set created from another device
+
+ TODO: writeTimestamp is removed from the spec so it's skipped if it TypeErrors.
+ `
+ )
.paramsSubcasesOnly(u => u.combine('mismatched', [true, false]))
.beforeAllSubcases(t => {
t.selectDeviceForQueryTypeOrSkipTestCase('timestamp');
@@ -147,6 +166,11 @@ g.test('timestamp_query,device_mismatch')
t.trackForCleanup(querySet);
const encoder = t.createEncoder('non-pass');
- encoder.encoder.writeTimestamp(querySet, 0);
+ try {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (encoder.encoder as any).writeTimestamp(querySet, 0);
+ } catch (ex) {
+ t.skipIf(ex instanceof TypeError, 'writeTimestamp is actually not available');
+ }
encoder.validateFinish(!mismatched);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/render_bundle.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/render_bundle.spec.ts
index 883b634446..35bdc99583 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/render_bundle.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/render_bundle.spec.ts
@@ -3,7 +3,7 @@ Tests execution of render bundles.
`;
import { makeTestGroup } from '../../../../common/framework/test_group.js';
-import { kDepthStencilFormats, kTextureFormatInfo } from '../../../format_info.js';
+import { kDepthStencilFormats } from '../../../format_info.js';
import { ValidationTest } from '../validation_test.js';
export const g = makeTestGroup(ValidationTest);
@@ -170,18 +170,6 @@ g.test('depth_stencil_readonly_mismatch')
.combine('bundleStencilReadOnly', [false, true])
.combine('passDepthReadOnly', [false, true])
.combine('passStencilReadOnly', [false, true])
- .filter(p => {
- // For combined depth/stencil formats the depth and stencil read only state must match
- // in order to create a valid render bundle or render pass.
- const depthStencilInfo = kTextureFormatInfo[p.depthStencilFormat];
- if (depthStencilInfo.depth && depthStencilInfo.stencil) {
- return (
- p.passDepthReadOnly === p.passStencilReadOnly &&
- p.bundleDepthReadOnly === p.bundleStencilReadOnly
- );
- }
- return true;
- })
)
.beforeAllSubcases(t => {
t.selectDeviceForTextureFormatOrSkipTestCase(t.params.depthStencilFormat);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/error_scope.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/error_scope.spec.ts
index cb5581fed6..8a8369dc3b 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/error_scope.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/error_scope.spec.ts
@@ -26,7 +26,17 @@ class ErrorScopeTests extends Fixture {
const gpu = getGPU(this.rec);
const adapter = await gpu.requestAdapter();
assert(adapter !== null);
- const device = await adapter.requestDevice();
+
+ // We need to max out the adapter limits related to texture dimensions to more reliably cause an
+ // OOM error when asked for it, so set that on the device now.
+ const device = this.trackForCleanup(
+ await adapter.requestDevice({
+ requiredLimits: {
+ maxTextureDimension2D: adapter.limits.maxTextureDimension2D,
+ maxTextureArrayLayers: adapter.limits.maxTextureArrayLayers,
+ },
+ })
+ );
assert(device !== null);
this._device = device;
}
@@ -146,7 +156,7 @@ Tests that popping an empty error scope stack should reject.
)
.fn(t => {
const promise = t.device.popErrorScope();
- t.shouldReject('OperationError', promise);
+ t.shouldReject('OperationError', promise, { allowMissingStack: true });
});
g.test('parent_scope')
@@ -250,7 +260,7 @@ Tests that sibling error scopes need to be balanced.
{
// Trying to pop an additional non-existing scope should reject.
const promise = t.device.popErrorScope();
- t.shouldReject('OperationError', promise);
+ t.shouldReject('OperationError', promise, { allowMissingStack: true });
}
const errors = await Promise.all(promises);
@@ -286,6 +296,6 @@ Tests that nested error scopes need to be balanced.
{
// Trying to pop an additional non-existing scope should reject.
const promise = t.device.popErrorScope();
- t.shouldReject('OperationError', promise);
+ t.shouldReject('OperationError', promise, { allowMissingStack: true });
}
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/gpu_external_texture_expiration.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/gpu_external_texture_expiration.spec.ts
index 7d77329920..20ea4897e6 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/gpu_external_texture_expiration.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/gpu_external_texture_expiration.spec.ts
@@ -86,10 +86,7 @@ g.test('import_multiple_times_in_same_task_scope')
sourceType === 'VideoFrame'
? await getVideoFrameFromVideoElement(t, videoElement)
: videoElement;
- externalTexture = t.device.importExternalTexture({
- /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
- source: source as any,
- });
+ externalTexture = t.device.importExternalTexture({ source });
bindGroup = t.device.createBindGroup({
layout: t.getDefaultBindGroupLayout(),
@@ -99,10 +96,7 @@ g.test('import_multiple_times_in_same_task_scope')
t.submitCommandBuffer(bindGroup, true);
// Import again in the same task scope should return same object.
- const mayBeTheSameExternalTexture = t.device.importExternalTexture({
- /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
- source: source as any,
- });
+ const mayBeTheSameExternalTexture = t.device.importExternalTexture({ source });
if (externalTexture === mayBeTheSameExternalTexture) {
t.submitCommandBuffer(bindGroup, true);
@@ -142,10 +136,7 @@ g.test('import_and_use_in_different_microtask')
// Import GPUExternalTexture
queueMicrotask(() => {
- externalTexture = t.device.importExternalTexture({
- /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
- source: source as any,
- });
+ externalTexture = t.device.importExternalTexture({ source });
});
// Submit GPUExternalTexture
@@ -182,10 +173,7 @@ g.test('import_and_use_in_different_task')
sourceType === 'VideoFrame'
? await getVideoFrameFromVideoElement(t, videoElement)
: videoElement;
- externalTexture = t.device.importExternalTexture({
- /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
- source: source as any,
- });
+ externalTexture = t.device.importExternalTexture({ source });
bindGroup = t.device.createBindGroup({
layout: t.getDefaultBindGroupLayout(),
@@ -218,10 +206,7 @@ g.test('use_import_to_refresh')
let source: HTMLVideoElement | VideoFrame;
await startPlayingAndWaitForVideo(videoElement, () => {
source = videoElement;
- externalTexture = t.device.importExternalTexture({
- /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
- source: source as any,
- });
+ externalTexture = t.device.importExternalTexture({ source });
bindGroup = t.device.createBindGroup({
layout: t.getDefaultBindGroupLayout(),
@@ -232,10 +217,7 @@ g.test('use_import_to_refresh')
});
await waitForNextTask(() => {
- const mayBeTheSameExternalTexture = t.device.importExternalTexture({
- /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
- source: source as any,
- });
+ const mayBeTheSameExternalTexture = t.device.importExternalTexture({ source });
if (externalTexture === mayBeTheSameExternalTexture) {
// ImportExternalTexture should refresh expired GPUExternalTexture.
@@ -264,10 +246,7 @@ g.test('webcodec_video_frame_close_expire_immediately')
let externalTexture: GPUExternalTexture;
await startPlayingAndWaitForVideo(videoElement, async () => {
const source = await getVideoFrameFromVideoElement(t, videoElement);
- externalTexture = t.device.importExternalTexture({
- /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
- source: source as any,
- });
+ externalTexture = t.device.importExternalTexture({ source });
bindGroup = t.device.createBindGroup({
layout: t.getDefaultBindGroupLayout(),
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/image_copy.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/image_copy.ts
index 686a5ee1cf..0290046675 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/image_copy.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/image_copy.ts
@@ -255,9 +255,9 @@ export function formatCopyableWithMethod({ format, method }: WithFormatAndMethod
return supportedAspects.length > 0;
}
if (method === 'CopyT2B') {
- return info.copySrc;
+ return info.color.copySrc;
} else {
- return info.copyDst;
+ return info.color.copyDst;
}
}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/texture_related.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/texture_related.spec.ts
index a0fe38e8e3..cbc36b1431 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/texture_related.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/texture_related.spec.ts
@@ -440,7 +440,7 @@ Test that the copy size must be aligned to the texture's format's block size.
const texture = t.createAlignedTexture(format, size, origin, dimension);
const bytesPerRow = align(
- Math.max(1, Math.ceil(size.width / info.blockWidth)) * info.bytesPerBlock,
+ Math.max(1, Math.ceil(size.width / info.blockWidth)) * info.color.bytes,
256
);
const rowsPerImage = Math.ceil(size.height / info.blockHeight);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/layout_shader_compat.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/layout_shader_compat.spec.ts
index 986fc42296..2b5e609c55 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/layout_shader_compat.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/layout_shader_compat.spec.ts
@@ -1,14 +1,293 @@
export const description = `
TODO:
- interface matching between pipeline layout and shader
- - x= {compute, vertex, fragment, vertex+fragment}, visibilities
- x= bind group index values, binding index values, multiple bindings
- - x= types of bindings
- - x= {equal, superset, subset}
+ - x= {superset, subset}
`;
import { makeTestGroup } from '../../../common/framework/test_group.js';
+import {
+ kShaderStageCombinations,
+ kShaderStages,
+ ValidBindableResource,
+} from '../../capability_info.js';
+import { GPUConst } from '../../constants.js';
import { ValidationTest } from './validation_test.js';
-export const g = makeTestGroup(ValidationTest);
+type BindableResourceType = ValidBindableResource | 'readonlyStorageBuf';
+const kBindableResources = [
+ 'uniformBuf',
+ 'storageBuf',
+ 'readonlyStorageBuf',
+ 'filtSamp',
+ 'nonFiltSamp',
+ 'compareSamp',
+ 'sampledTex',
+ 'sampledTexMS',
+ 'readonlyStorageTex',
+ 'writeonlyStorageTex',
+ 'readwriteStorageTex',
+] as const;
+
+const bindGroupLayoutEntryContents = {
+ compareSamp: {
+ sampler: {
+ type: 'comparison',
+ },
+ },
+ filtSamp: {
+ sampler: {
+ type: 'filtering',
+ },
+ },
+ nonFiltSamp: {
+ sampler: {
+ type: 'non-filtering',
+ },
+ },
+ sampledTex: {
+ texture: {
+ sampleType: 'unfilterable-float',
+ },
+ },
+ sampledTexMS: {
+ texture: {
+ sampleType: 'unfilterable-float',
+ multisampled: true,
+ },
+ },
+ storageBuf: {
+ buffer: {
+ type: 'storage',
+ },
+ },
+ readonlyStorageBuf: {
+ buffer: {
+ type: 'read-only-storage',
+ },
+ },
+ uniformBuf: {
+ buffer: {
+ type: 'uniform',
+ },
+ },
+ readonlyStorageTex: {
+ storageTexture: {
+ format: 'r32float',
+ access: 'read-only',
+ },
+ },
+ writeonlyStorageTex: {
+ storageTexture: {
+ format: 'r32float',
+ access: 'write-only',
+ },
+ },
+ readwriteStorageTex: {
+ storageTexture: {
+ format: 'r32float',
+ access: 'read-write',
+ },
+ },
+} as const;
+
+class F extends ValidationTest {
+ createPipelineLayout(
+ bindingInPipelineLayout: BindableResourceType,
+ visibility: number
+ ): GPUPipelineLayout {
+ return this.device.createPipelineLayout({
+ bindGroupLayouts: [
+ this.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility,
+ ...bindGroupLayoutEntryContents[bindingInPipelineLayout],
+ },
+ ],
+ }),
+ ],
+ });
+ }
+
+ GetBindableResourceShaderDeclaration(bindableResource: BindableResourceType): string {
+ switch (bindableResource) {
+ case 'compareSamp':
+ return 'var tmp : sampler_comparison';
+ case 'filtSamp':
+ case 'nonFiltSamp':
+ return 'var tmp : sampler';
+ case 'sampledTex':
+ return 'var tmp : texture_2d<f32>';
+ case 'sampledTexMS':
+ return 'var tmp : texture_multisampled_2d<f32>';
+ case 'storageBuf':
+ return 'var<storage, read_write> tmp : vec4u';
+ case 'readonlyStorageBuf':
+ return 'var<storage, read> tmp : vec4u';
+ case 'uniformBuf':
+ return 'var<uniform> tmp : vec4u;';
+ case 'readonlyStorageTex':
+ return 'var tmp : texture_storage_2d<r32float, read>';
+ case 'writeonlyStorageTex':
+ return 'var tmp : texture_storage_2d<r32float, write>';
+ case 'readwriteStorageTex':
+ return 'var tmp : texture_storage_2d<r32float, read_write>';
+ }
+ }
+}
+
+const BindingResourceCompatibleWithShaderStages = function (
+ bindingResource: BindableResourceType,
+ shaderStages: number
+): boolean {
+ if ((shaderStages & GPUConst.ShaderStage.VERTEX) > 0) {
+ switch (bindingResource) {
+ case 'writeonlyStorageTex':
+ case 'readwriteStorageTex':
+ case 'storageBuf':
+ return false;
+ default:
+ break;
+ }
+ }
+ return true;
+};
+
+export const g = makeTestGroup(F);
+
+g.test('pipeline_layout_shader_exact_match')
+ .desc(
+ `
+ Test that the binding type in the pipeline layout must match the related declaration in shader.
+ Note that read-write storage textures in the pipeline layout can match write-only storage textures
+ in the shader.
+ `
+ )
+ .params(u =>
+ u
+ .combine('bindingInPipelineLayout', kBindableResources)
+ .combine('bindingInShader', kBindableResources)
+ .beginSubcases()
+ .combine('pipelineLayoutVisibility', kShaderStageCombinations)
+ .combine('shaderStageWithBinding', kShaderStages)
+ .combine('isBindingStaticallyUsed', [true, false] as const)
+ .unless(
+ p =>
+ // We don't test using non-filtering sampler in shader because it has the same declaration
+ // as filtering sampler.
+ p.bindingInShader === 'nonFiltSamp' ||
+ !BindingResourceCompatibleWithShaderStages(
+ p.bindingInPipelineLayout,
+ p.pipelineLayoutVisibility
+ ) ||
+ !BindingResourceCompatibleWithShaderStages(p.bindingInShader, p.shaderStageWithBinding)
+ )
+ )
+ .fn(t => {
+ const {
+ bindingInPipelineLayout,
+ bindingInShader,
+ pipelineLayoutVisibility,
+ shaderStageWithBinding,
+ isBindingStaticallyUsed,
+ } = t.params;
+
+ const layout = t.createPipelineLayout(bindingInPipelineLayout, pipelineLayoutVisibility);
+ const bindResourceDeclaration = `@group(0) @binding(0) ${t.GetBindableResourceShaderDeclaration(
+ bindingInShader
+ )}`;
+ const staticallyUseBinding = isBindingStaticallyUsed ? '_ = tmp; ' : '';
+ const isAsync = false;
+
+ let success = true;
+ if (isBindingStaticallyUsed) {
+ success = bindingInPipelineLayout === bindingInShader;
+
+ // Filtering and non-filtering both have the same shader declaration.
+ success ||= bindingInPipelineLayout === 'nonFiltSamp' && bindingInShader === 'filtSamp';
+
+ // Promoting storage textures that are read-write in the layout can be readonly in the shader.
+ success ||=
+ bindingInPipelineLayout === 'readwriteStorageTex' &&
+ bindingInShader === 'writeonlyStorageTex';
+
+ // The shader using the resource must be included in the visibility in the layout.
+ success &&= (pipelineLayoutVisibility & shaderStageWithBinding) > 0;
+ }
+
+ switch (shaderStageWithBinding) {
+ case GPUConst.ShaderStage.COMPUTE: {
+ const computeShader = `
+ ${bindResourceDeclaration};
+ @compute @workgroup_size(1)
+ fn main() {
+ ${staticallyUseBinding}
+ }
+ `;
+ t.doCreateComputePipelineTest(isAsync, success, {
+ layout,
+ compute: {
+ module: t.device.createShaderModule({
+ code: computeShader,
+ }),
+ },
+ });
+ break;
+ }
+ case GPUConst.ShaderStage.VERTEX: {
+ const vertexShader = `
+ ${bindResourceDeclaration};
+ @vertex
+ fn main() -> @builtin(position) vec4f {
+ ${staticallyUseBinding}
+ return vec4f();
+ }
+ `;
+ t.doCreateRenderPipelineTest(isAsync, success, {
+ layout,
+ vertex: {
+ module: t.device.createShaderModule({
+ code: vertexShader,
+ }),
+ },
+ });
+ break;
+ }
+ case GPUConst.ShaderStage.FRAGMENT: {
+ const fragmentShader = `
+ ${bindResourceDeclaration};
+ @fragment
+ fn main() -> @location(0) vec4f {
+ ${staticallyUseBinding}
+ return vec4f();
+ }
+ `;
+ t.doCreateRenderPipelineTest(isAsync, success, {
+ layout,
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+ @vertex
+ fn main() -> @builtin(position) vec4f {
+ return vec4f();
+ }`,
+ }),
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: fragmentShader,
+ }),
+ targets: [
+ {
+ format: 'rgba8unorm',
+ },
+ ],
+ },
+ });
+ break;
+ }
+ }
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/queue/copyToTexture/CopyExternalImageToTexture.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/queue/copyToTexture/CopyExternalImageToTexture.spec.ts
index 4ac240d66e..34d7ef1c83 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/queue/copyToTexture/CopyExternalImageToTexture.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/queue/copyToTexture/CopyExternalImageToTexture.spec.ts
@@ -14,7 +14,7 @@ import { raceWithRejectOnTimeout, unreachable, assert } from '../../../../../com
import { kTextureUsages } from '../../../../capability_info.js';
import {
kTextureFormatInfo,
- kTextureFormats,
+ kAllTextureFormats,
kValidTextureFormatsForCopyE2T,
} from '../../../../format_info.js';
import { kResourceStates } from '../../../../gpu_test.js';
@@ -669,7 +669,7 @@ g.test('destination_texture,format')
)
.params(u =>
u
- .combine('format', kTextureFormats)
+ .combine('format', kAllTextureFormats)
.beginSubcases()
.combine('copySize', [
{ width: 0, height: 0, depthOrArrayLayers: 0 },
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/queue/destroyed/query_set.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/queue/destroyed/query_set.spec.ts
index 1d8adab7e8..987a88830d 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/queue/destroyed/query_set.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/queue/destroyed/query_set.spec.ts
@@ -24,11 +24,13 @@ Tests that use a destroyed query set in occlusion query on render pass encoder.
encoder.validateFinishAndSubmitGivenState(t.params.querySetState);
});
-g.test('writeTimestamp')
+g.test('timestamps')
.desc(
`
-Tests that use a destroyed query set in writeTimestamp on {non-pass, compute, render} encoder.
+Tests that use a destroyed query set in timestamp query on {non-pass, compute, render} encoder.
- x= {destroyed, not destroyed (control case)}
+
+ TODO: writeTimestamp is removed from the spec so it's skipped if it TypeErrors.
`
)
.params(u => u.beginSubcases().combine('querySetState', ['valid', 'destroyed'] as const))
@@ -39,9 +41,50 @@ Tests that use a destroyed query set in writeTimestamp on {non-pass, compute, re
count: 2,
});
- const encoder = t.createEncoder('non-pass');
- encoder.encoder.writeTimestamp(querySet, 0);
- encoder.validateFinishAndSubmitGivenState(t.params.querySetState);
+ {
+ const encoder = t.createEncoder('non-pass');
+ try {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (encoder.encoder as any).writeTimestamp(querySet, 0);
+ } catch (ex) {
+ t.skipIf(ex instanceof TypeError, 'writeTimestamp is actually not available');
+ }
+ encoder.validateFinishAndSubmitGivenState(t.params.querySetState);
+ }
+
+ {
+ const encoder = t.createEncoder('non-pass');
+ encoder.encoder
+ .beginComputePass({
+ timestampWrites: { querySet, beginningOfPassWriteIndex: 0 },
+ })
+ .end();
+ encoder.validateFinishAndSubmitGivenState(t.params.querySetState);
+ }
+
+ {
+ const texture = t.trackForCleanup(
+ t.device.createTexture({
+ size: [1, 1, 1],
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ })
+ );
+ const encoder = t.createEncoder('non-pass');
+ encoder.encoder
+ .beginRenderPass({
+ colorAttachments: [
+ {
+ view: texture.createView(),
+ loadOp: 'load',
+ storeOp: 'store',
+ },
+ ],
+ timestampWrites: { querySet, beginningOfPassWriteIndex: 0 },
+ })
+ .end();
+ encoder.validateFinishAndSubmitGivenState(t.params.querySetState);
+ }
});
g.test('resolveQuerySet')
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/queue/destroyed/texture.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/queue/destroyed/texture.spec.ts
index 42036bd881..8e73cbf67b 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/queue/destroyed/texture.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/queue/destroyed/texture.spec.ts
@@ -165,15 +165,16 @@ Tests that using a destroyed texture referenced by a bindGroup set with setBindG
u
.combine('destroyed', [false, true] as const)
.combine('encoderType', ['compute pass', 'render pass', 'render bundle'] as const)
+ .combine('bindingType', ['texture', 'storageTexture'] as const)
)
.fn(t => {
- const { destroyed, encoderType } = t.params;
+ const { destroyed, encoderType, bindingType } = t.params;
const { device } = t;
const texture = t.trackForCleanup(
t.device.createTexture({
size: [1, 1, 1],
format: 'rgba8unorm',
- usage: GPUTextureUsage.TEXTURE_BINDING,
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING,
})
);
@@ -182,7 +183,9 @@ Tests that using a destroyed texture referenced by a bindGroup set with setBindG
{
binding: 0,
visibility: GPUShaderStage.COMPUTE,
- texture: {},
+ [bindingType]: {
+ format: texture.format,
+ },
},
],
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pass/attachment_compatibility.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pass/attachment_compatibility.spec.ts
index c0ab23b91c..241d576e39 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pass/attachment_compatibility.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pass/attachment_compatibility.spec.ts
@@ -551,13 +551,6 @@ Test that the depth stencil read only state in render passes or bundles is compa
.filter(p => {
if (p.format) {
const depthStencilInfo = kTextureFormatInfo[p.format];
- // For combined depth/stencil formats the depth and stencil read only state must match
- // in order to create a valid render bundle or render pass.
- if (depthStencilInfo.depth && depthStencilInfo.stencil) {
- if (p.depthReadOnly !== p.stencilReadOnly) {
- return false;
- }
- }
// If the format has no depth aspect, the depthReadOnly, depthWriteEnabled of the pipeline must not be true
// in order to create a valid render pipeline.
if (!depthStencilInfo.depth && p.depthWriteEnabled) {
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pass/render_pass_descriptor.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pass/render_pass_descriptor.spec.ts
index 9713beea52..0b471c5f6d 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pass/render_pass_descriptor.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pass/render_pass_descriptor.spec.ts
@@ -20,6 +20,7 @@ class F extends ValidationTest {
createTexture(
options: {
format?: GPUTextureFormat;
+ dimension?: GPUTextureDimension;
width?: number;
height?: number;
arrayLayerCount?: number;
@@ -30,6 +31,7 @@ class F extends ValidationTest {
): GPUTexture {
const {
format = 'rgba8unorm',
+ dimension = '2d',
width = 16,
height = 16,
arrayLayerCount = 1,
@@ -41,6 +43,7 @@ class F extends ValidationTest {
return this.device.createTexture({
size: { width, height, depthOrArrayLayers: arrayLayerCount },
format,
+ dimension,
mipLevelCount,
sampleCount,
usage,
@@ -90,6 +93,7 @@ class F extends ValidationTest {
}
export const g = makeTestGroup(F);
+const kArrayLayerCount = 10;
g.test('attachments,one_color_attachment')
.desc(`Test that a render pass works with only one color attachment.`)
@@ -278,6 +282,184 @@ g.test('color_attachments,limits,maxColorAttachmentBytesPerSample,unaligned')
t.tryRenderPass(success, { colorAttachments });
});
+g.test('color_attachments,depthSlice,definedness')
+ .desc(
+ `
+ Test that depthSlice must be undefined for 2d color attachments and defined for 3d color attachments."
+ - The special value '0xFFFFFFFF' is not treated as 'undefined'.
+ `
+ )
+ .params(u =>
+ u
+ .combine('dimension', ['2d', '3d'] as GPUTextureDimension[])
+ .beginSubcases()
+ .combine('depthSlice', [undefined, 0, 0xffffffff])
+ )
+ .fn(t => {
+ const { dimension, depthSlice } = t.params;
+ const texture = t.createTexture({ dimension });
+
+ const colorAttachment = t.getColorAttachment(texture);
+ if (depthSlice !== undefined) {
+ colorAttachment.depthSlice = depthSlice;
+ }
+
+ const descriptor: GPURenderPassDescriptor = {
+ colorAttachments: [colorAttachment],
+ };
+
+ const success =
+ (dimension === '2d' && depthSlice === undefined) || (dimension === '3d' && depthSlice === 0);
+
+ t.tryRenderPass(success, descriptor);
+ });
+
+g.test('color_attachments,depthSlice,bound_check')
+ .desc(
+ `
+ Test that depthSlice must be less than the depthOrArrayLayers of 3d texture's subresource at mip levels.
+ - Check depth bounds with 3d texture size [16, 1, 10], which has 5 mip levels with depth [10, 5, 2, 1, 1]
+ for testing more mip level size computation.
+ - Failed if depthSlice >= the depth of each mip level.
+ `
+ )
+ .params(u =>
+ u
+ .combine('mipLevel', [0, 1, 2, 3, 4])
+ .beginSubcases()
+ .expand('depthSlice', ({ mipLevel }) => {
+ const depthAtMipLevel = Math.max(kArrayLayerCount >> mipLevel, 1);
+ // Use Set() to exclude duplicates when the depthAtMipLevel is 1 and 2
+ return [...new Set([0, 1, depthAtMipLevel - 1, depthAtMipLevel])];
+ })
+ )
+ .fn(t => {
+ const { mipLevel, depthSlice } = t.params;
+
+ const texture = t.createTexture({
+ dimension: '3d',
+ width: 16,
+ height: 1,
+ arrayLayerCount: kArrayLayerCount,
+ mipLevelCount: mipLevel + 1,
+ });
+
+ const viewDescriptor: GPUTextureViewDescriptor = {
+ baseMipLevel: mipLevel,
+ mipLevelCount: 1,
+ baseArrayLayer: 0,
+ arrayLayerCount: 1,
+ };
+
+ const colorAttachment = t.getColorAttachment(texture, viewDescriptor);
+ colorAttachment.depthSlice = depthSlice;
+
+ const passDescriptor: GPURenderPassDescriptor = {
+ colorAttachments: [colorAttachment],
+ };
+
+ const success = depthSlice < Math.max(kArrayLayerCount >> mipLevel, 1);
+
+ t.tryRenderPass(success, passDescriptor);
+ });
+
+g.test('color_attachments,depthSlice,overlaps,same_miplevel')
+ .desc(
+ `
+ Test that the depth slices of 3d color attachments have no overlaps for same texture in a render
+ pass.
+ - Succeed if the depth slices are different, or from different textures, or on different render
+ passes.
+ - Fail if same depth slice from same texture's same mip level is overwritten in a render pass.
+ `
+ )
+ .params(u =>
+ u
+ .combine('sameDepthSlice', [true, false])
+ .beginSubcases()
+ .combine('sameTexture', [true, false])
+ .combine('samePass', [true, false])
+ )
+ .fn(t => {
+ const { sameDepthSlice, sameTexture, samePass } = t.params;
+ const arrayLayerCount = 4;
+
+ const texDescriptor = {
+ dimension: '3d' as GPUTextureDimension,
+ arrayLayerCount,
+ };
+ const texture = t.createTexture(texDescriptor);
+
+ const colorAttachments = [];
+ for (let i = 0; i < arrayLayerCount; i++) {
+ const colorAttachment = t.getColorAttachment(
+ sameTexture ? texture : t.createTexture(texDescriptor)
+ );
+ colorAttachment.depthSlice = sameDepthSlice ? 0 : i;
+ colorAttachments.push(colorAttachment);
+ }
+
+ const encoder = t.createEncoder('non-pass');
+ if (samePass) {
+ const pass = encoder.encoder.beginRenderPass({ colorAttachments });
+ pass.end();
+ } else {
+ for (let i = 0; i < arrayLayerCount; i++) {
+ const pass = encoder.encoder.beginRenderPass({ colorAttachments: [colorAttachments[i]] });
+ pass.end();
+ }
+ }
+
+ const success = !sameDepthSlice || !sameTexture || !samePass;
+
+ encoder.validateFinish(success);
+ });
+
+g.test('color_attachments,depthSlice,overlaps,diff_miplevel')
+ .desc(
+ `
+ Test that the same depth slice from different mip levels of a 3d texture with size [1, 1, N] can
+ be set in a render pass's color attachments.
+ `
+ )
+ .params(u => u.combine('sameMipLevel', [true, false]))
+ .fn(t => {
+ const { sameMipLevel } = t.params;
+ const mipLevelCount = 4;
+
+ const texDescriptor = {
+ dimension: '3d' as GPUTextureDimension,
+ width: 1,
+ height: 1,
+ arrayLayerCount: 1 << mipLevelCount,
+ mipLevelCount,
+ };
+ const texture = t.createTexture(texDescriptor);
+
+ const viewDescriptor: GPUTextureViewDescriptor = {
+ baseMipLevel: 0,
+ mipLevelCount: 1,
+ baseArrayLayer: 0,
+ arrayLayerCount: 1,
+ };
+
+ const colorAttachments = [];
+ for (let i = 0; i < mipLevelCount; i++) {
+ if (!sameMipLevel) {
+ viewDescriptor.baseMipLevel = i;
+ }
+ const colorAttachment = t.getColorAttachment(texture, viewDescriptor);
+ colorAttachment.depthSlice = 0;
+ colorAttachments.push(colorAttachment);
+ }
+
+ const encoder = t.createEncoder('non-pass');
+ const pass = encoder.encoder.beginRenderPass({ colorAttachments });
+ pass.end();
+
+ encoder.validateFinish(!sameMipLevel);
+ });
+
g.test('attachments,same_size')
.desc(
`
@@ -909,10 +1091,8 @@ g.test('depth_stencil_attachment,loadOp_storeOp_match_depthReadOnly_stencilReadO
const hasDepth = info.depth;
const hasStencil = info.stencil;
- const goodAspectCombo =
- (hasDepth && hasStencil ? !depthReadOnly === !stencilReadOnly : true) &&
- (hasDepthSettings ? hasDepth : true) &&
- (hasStencilSettings ? hasStencil : true);
+ const goodAspectSettingsPresent =
+ (hasDepthSettings ? hasDepth : true) && (hasStencilSettings ? hasStencil : true);
const hasBothDepthOps = !!depthLoadOp && !!depthStoreOp;
const hasBothStencilOps = !!stencilLoadOp && !!stencilStoreOp;
@@ -923,7 +1103,7 @@ g.test('depth_stencil_attachment,loadOp_storeOp_match_depthReadOnly_stencilReadO
const goodStencilCombo =
hasStencil && !stencilReadOnly ? hasBothStencilOps : hasNeitherStencilOps;
- const shouldError = !goodAspectCombo || !goodDepthCombo || !goodStencilCombo;
+ const shouldError = !goodAspectSettingsPresent || !goodDepthCombo || !goodStencilCombo;
t.expectValidationError(() => {
encoder.finish();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/common.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/common.ts
index 93b0932042..e0316a5517 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/common.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/common.ts
@@ -1,4 +1,4 @@
-import { kTextureFormatInfo } from '../../../format_info.js';
+import { ColorTextureFormat, kTextureFormatInfo } from '../../../format_info.js';
import {
getFragmentShaderCodeWithOutput,
getPlainTypeInfo,
@@ -6,12 +6,14 @@ import {
} from '../../../util/shader.js';
import { ValidationTest } from '../validation_test.js';
+type ColorTargetState = GPUColorTargetState & { format: ColorTextureFormat };
+
const values = [0, 1, 0, 1];
export class CreateRenderPipelineValidationTest extends ValidationTest {
getDescriptor(
options: {
primitive?: GPUPrimitiveState;
- targets?: GPUColorTargetState[];
+ targets?: ColorTargetState[];
multisample?: GPUMultisampleState;
depthStencil?: GPUDepthStencilState;
fragmentShaderCode?: string;
@@ -19,17 +21,16 @@ export class CreateRenderPipelineValidationTest extends ValidationTest {
fragmentConstants?: Record<string, GPUPipelineConstantValue>;
} = {}
): GPURenderPipelineDescriptor {
- const defaultTargets: GPUColorTargetState[] = [{ format: 'rgba8unorm' }];
const {
primitive = {},
- targets = defaultTargets,
+ targets = [{ format: 'rgba8unorm' }] as const,
multisample = {},
depthStencil,
fragmentShaderCode = getFragmentShaderCodeWithOutput([
{
values,
plainType: getPlainTypeInfo(
- kTextureFormatInfo[targets[0] ? targets[0].format : 'rgba8unorm'].sampleType
+ kTextureFormatInfo[targets[0] ? targets[0].format : 'rgba8unorm'].color.type
),
componentCount: 4,
},
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/depth_stencil_state.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/depth_stencil_state.spec.ts
index eaaf78af66..403f463943 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/depth_stencil_state.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/depth_stencil_state.spec.ts
@@ -5,7 +5,11 @@ This test dedicatedly tests validation of GPUDepthStencilState of createRenderPi
import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { unreachable } from '../../../../common/util/util.js';
import { kCompareFunctions, kStencilOperations } from '../../../capability_info.js';
-import { kTextureFormats, kTextureFormatInfo, kDepthStencilFormats } from '../../../format_info.js';
+import {
+ kAllTextureFormats,
+ kTextureFormatInfo,
+ kDepthStencilFormats,
+} from '../../../format_info.js';
import { getFragmentShaderCodeWithOutput } from '../../../util/shader.js';
import { CreateRenderPipelineValidationTest } from './common.js';
@@ -14,7 +18,11 @@ export const g = makeTestGroup(CreateRenderPipelineValidationTest);
g.test('format')
.desc(`The texture format in depthStencilState must be a depth/stencil format.`)
- .params(u => u.combine('isAsync', [false, true]).combine('format', kTextureFormats))
+ .params(u =>
+ u //
+ .combine('isAsync', [false, true])
+ .combine('format', kAllTextureFormats)
+ )
.beforeAllSubcases(t => {
const { format } = t.params;
const info = kTextureFormatInfo[format];
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/fragment_state.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/fragment_state.spec.ts
index 0206431eee..c01c2ba9ef 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/fragment_state.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/fragment_state.spec.ts
@@ -10,15 +10,17 @@ import {
kMaxColorAttachmentsToTest,
} from '../../../capability_info.js';
import {
- kTextureFormats,
+ kAllTextureFormats,
kRenderableColorTextureFormats,
kTextureFormatInfo,
computeBytesPerSampleFromFormats,
+ kColorTextureFormats,
} from '../../../format_info.js';
import {
getFragmentShaderCodeWithOutput,
getPlainTypeInfo,
kDefaultFragmentShaderCode,
+ kDefaultVertexShaderCode,
} from '../../../util/shader.js';
import { kTexelRepresentationInfo } from '../../../util/texture/texel_data.js';
@@ -49,9 +51,60 @@ g.test('color_target_exists')
t.doCreateRenderPipelineTest(isAsync, false, badDescriptor);
});
+g.test('targets_format_is_color_format')
+ .desc(
+ `Tests that color target state format must be a color format, regardless of how the
+ fragment shader writes to it.`
+ )
+ .params(u =>
+ u
+ // Test all non-color texture formats, plus 'rgba8unorm' as a control case.
+ .combine('format', kAllTextureFormats)
+ .filter(({ format }) => {
+ return format === 'rgba8unorm' || !kTextureFormatInfo[format].color;
+ })
+ .combine('isAsync', [false, true])
+ .beginSubcases()
+ .combine('fragOutType', ['f32', 'u32', 'i32'] as const)
+ )
+ .beforeAllSubcases(t => {
+ const { format } = t.params;
+ const info = kTextureFormatInfo[format];
+ t.skipIfTextureFormatNotSupported(t.params.format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+ })
+ .fn(t => {
+ const { isAsync, format, fragOutType } = t.params;
+
+ const fragmentShaderCode = getFragmentShaderCodeWithOutput([
+ { values, plainType: fragOutType, componentCount: 4 },
+ ]);
+
+ const success = format === 'rgba8unorm' && fragOutType === 'f32';
+ t.doCreateRenderPipelineTest(isAsync, success, {
+ vertex: {
+ module: t.device.createShaderModule({ code: kDefaultVertexShaderCode }),
+ entryPoint: 'main',
+ },
+ fragment: {
+ module: t.device.createShaderModule({ code: fragmentShaderCode }),
+ entryPoint: 'main',
+ targets: [{ format }],
+ },
+ layout: 'auto',
+ });
+ });
+
g.test('targets_format_renderable')
- .desc(`Tests that color target state format must have RENDER_ATTACHMENT capability.`)
- .params(u => u.combine('isAsync', [false, true]).combine('format', kTextureFormats))
+ .desc(
+ `Tests that color target state format must have RENDER_ATTACHMENT capability
+ (tests only color formats).`
+ )
+ .params(u =>
+ u //
+ .combine('isAsync', [false, true])
+ .combine('format', kColorTextureFormats)
+ )
.beforeAllSubcases(t => {
const { format } = t.params;
const info = kTextureFormatInfo[format];
@@ -158,24 +211,12 @@ g.test('limits,maxColorAttachmentBytesPerSample,unaligned')
// become 4 and 4+4+8+16+1 > 32. Re-ordering this so the R8Unorm's are at the end, however
// is allowed: 4+8+16+1+1 < 32.
{
- formats: [
- 'r8unorm',
- 'r32float',
- 'rgba8unorm',
- 'rgba32float',
- 'r8unorm',
- ] as GPUTextureFormat[],
+ formats: ['r8unorm', 'r32float', 'rgba8unorm', 'rgba32float', 'r8unorm'],
},
{
- formats: [
- 'r32float',
- 'rgba8unorm',
- 'rgba32float',
- 'r8unorm',
- 'r8unorm',
- ] as GPUTextureFormat[],
+ formats: ['r32float', 'rgba8unorm', 'rgba32float', 'r8unorm', 'r8unorm'],
},
- ])
+ ] as const)
.beginSubcases()
.combine('isAsync', [false, true])
)
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/inter_stage.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/inter_stage.spec.ts
index 91aabb0ab8..db65ba8bf4 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/inter_stage.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/inter_stage.spec.ts
@@ -3,7 +3,7 @@ Interface matching between vertex and fragment shader validation for createRende
`;
import { makeTestGroup } from '../../../../common/framework/test_group.js';
-import { assert, range } from '../../../../common/util/util.js';
+import { range } from '../../../../common/util/util.js';
import { CreateRenderPipelineValidationTest } from './common.js';
@@ -97,8 +97,18 @@ g.test('location,mismatch')
});
g.test('location,superset')
- .desc(`TODO: implement after spec is settled: https://github.com/gpuweb/gpuweb/issues/2038`)
- .unimplemented();
+ .desc(`Tests that validation should succeed when vertex output is superset of fragment input`)
+ .params(u => u.combine('isAsync', [false, true]))
+ .fn(t => {
+ const { isAsync } = t.params;
+
+ const descriptor = t.getDescriptorWithStates(
+ t.getVertexStateWithOutputs(['@location(0) vout0: f32', '@location(1) vout1: f32']),
+ t.getFragmentStateWithInputs(['@location(1) fin1: f32'])
+ );
+
+ t.doCreateRenderPipelineTest(isAsync, true, descriptor);
+ });
g.test('location,subset')
.desc(`Tests that validation should fail when vertex output is a subset of fragment input.`)
@@ -159,20 +169,27 @@ g.test('interpolation_type')
{ output: '@interpolate(linear)', input: '@interpolate(perspective)' },
{ output: '@interpolate(flat)', input: '@interpolate(perspective)' },
{ output: '@interpolate(linear)', input: '@interpolate(flat)' },
- { output: '@interpolate(linear, center)', input: '@interpolate(linear, center)' },
+ {
+ output: '@interpolate(linear, center)',
+ input: '@interpolate(linear, center)',
+ _compat_success: false,
+ },
])
)
.fn(t => {
- const { isAsync, output, input, _success } = t.params;
+ const { isAsync, output, input, _success, _compat_success } = t.params;
const descriptor = t.getDescriptorWithStates(
t.getVertexStateWithOutputs([`@location(0) ${output} vout0: f32`]),
t.getFragmentStateWithInputs([`@location(0) ${input} fin0: f32`])
);
- t.doCreateRenderPipelineTest(isAsync, _success ?? output === input, descriptor);
- });
+ const shouldSucceed =
+ (_success ?? output === input) && (!t.isCompatibility || _compat_success !== false);
+ t.doCreateRenderPipelineTest(isAsync, shouldSucceed, descriptor);
+ });
+1;
g.test('interpolation_sampling')
.desc(
`Tests that validation should fail when interpolation sampling of vertex output and fragment input at the same location doesn't match.`
@@ -186,7 +203,12 @@ g.test('interpolation_sampling')
input: '@interpolate(perspective, center)',
_success: true,
},
- { output: '@interpolate(linear, center)', input: '@interpolate(linear)', _success: true },
+ {
+ output: '@interpolate(linear, center)',
+ input: '@interpolate(linear)',
+ _success: true,
+ _compat_success: false,
+ },
{ output: '@interpolate(flat)', input: '@interpolate(flat)' },
{ output: '@interpolate(perspective)', input: '@interpolate(perspective, sample)' },
{ output: '@interpolate(perspective, center)', input: '@interpolate(perspective, sample)' },
@@ -198,14 +220,17 @@ g.test('interpolation_sampling')
])
)
.fn(t => {
- const { isAsync, output, input, _success } = t.params;
+ const { isAsync, output, input, _success, _compat_success } = t.params;
const descriptor = t.getDescriptorWithStates(
t.getVertexStateWithOutputs([`@location(0) ${output} vout0: f32`]),
t.getFragmentStateWithInputs([`@location(0) ${input} fin0: f32`])
);
- t.doCreateRenderPipelineTest(isAsync, _success ?? output === input, descriptor);
+ const shouldSucceed =
+ (_success ?? output === input) && (!t.isCompatibility || _compat_success !== false);
+
+ t.doCreateRenderPipelineTest(isAsync, shouldSucceed, descriptor);
});
g.test('max_shader_variable_location')
@@ -251,9 +276,6 @@ g.test('max_components_count,output')
const numVec4 = Math.floor(numScalarComponents / 4);
const numTrailingScalars = numScalarComponents % 4;
- const numUserDefinedInterStageVariables = numTrailingScalars > 0 ? numVec4 + 1 : numVec4;
-
- assert(numUserDefinedInterStageVariables <= t.device.limits.maxInterStageShaderVariables);
const outputs = range(numVec4, i => `@location(${i}) vout${i}: vec4<f32>`);
const inputs = range(numVec4, i => `@location(${i}) fin${i}: vec4<f32>`);
@@ -280,23 +302,23 @@ g.test('max_components_count,input')
.params(u =>
u.combine('isAsync', [false, true]).combineWithParams([
// Number of user-defined input scalar components in test shader = device.limits.maxInterStageShaderComponents + numScalarDelta.
- { numScalarDelta: 0, useExtraBuiltinInputs: false, _success: true },
- { numScalarDelta: 1, useExtraBuiltinInputs: false, _success: false },
- { numScalarDelta: 0, useExtraBuiltinInputs: true, _success: false },
- { numScalarDelta: -3, useExtraBuiltinInputs: true, _success: true },
- { numScalarDelta: -2, useExtraBuiltinInputs: true, _success: false },
+ { numScalarDelta: 0, useExtraBuiltinInputs: false },
+ { numScalarDelta: 1, useExtraBuiltinInputs: false },
+ { numScalarDelta: 0, useExtraBuiltinInputs: true },
+ { numScalarDelta: -3, useExtraBuiltinInputs: true },
+ { numScalarDelta: -2, useExtraBuiltinInputs: true },
] as const)
)
.fn(t => {
- const { isAsync, numScalarDelta, useExtraBuiltinInputs, _success } = t.params;
+ const { isAsync, numScalarDelta, useExtraBuiltinInputs } = t.params;
const numScalarComponents = t.device.limits.maxInterStageShaderComponents + numScalarDelta;
+ const numExtraComponents = useExtraBuiltinInputs ? (t.isCompatibility ? 2 : 3) : 0;
+ const numUsedComponents = numScalarComponents + numExtraComponents;
+ const success = numUsedComponents <= t.device.limits.maxInterStageShaderComponents;
const numVec4 = Math.floor(numScalarComponents / 4);
const numTrailingScalars = numScalarComponents % 4;
- const numUserDefinedInterStageVariables = numTrailingScalars > 0 ? numVec4 + 1 : numVec4;
-
- assert(numUserDefinedInterStageVariables <= t.device.limits.maxInterStageShaderVariables);
const outputs = range(numVec4, i => `@location(${i}) vout${i}: vec4<f32>`);
const inputs = range(numVec4, i => `@location(${i}) fin${i}: vec4<f32>`);
@@ -310,9 +332,11 @@ g.test('max_components_count,input')
if (useExtraBuiltinInputs) {
inputs.push(
'@builtin(front_facing) front_facing_in: bool',
- '@builtin(sample_index) sample_index_in: u32',
'@builtin(sample_mask) sample_mask_in: u32'
);
+ if (!t.isCompatibility) {
+ inputs.push('@builtin(sample_index) sample_index_in: u32');
+ }
}
const descriptor = t.getDescriptorWithStates(
@@ -320,5 +344,5 @@ g.test('max_components_count,input')
t.getFragmentStateWithInputs(inputs, true)
);
- t.doCreateRenderPipelineTest(isAsync, _success, descriptor);
+ t.doCreateRenderPipelineTest(isAsync, success, descriptor);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/misc.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/misc.spec.ts
index 1e3ccf5637..adb091a236 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/misc.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/misc.spec.ts
@@ -96,3 +96,37 @@ g.test('pipeline_layout,device_mismatch')
t.doCreateRenderPipelineTest(isAsync, !mismatched, descriptor);
});
+
+g.test('external_texture')
+ .desc('Tests createRenderPipeline() with an external_texture')
+ .fn(t => {
+ const shader = t.device.createShaderModule({
+ code: `
+ @vertex
+ fn vertexMain() -> @builtin(position) vec4f {
+ return vec4f(1);
+ }
+
+ @group(0) @binding(0) var myTexture: texture_external;
+
+ @fragment
+ fn fragmentMain() -> @location(0) vec4f {
+ let result = textureLoad(myTexture, vec2u(1, 1));
+ return vec4f(1);
+ }
+ `,
+ });
+
+ const descriptor: GPURenderPipelineDescriptor = {
+ layout: 'auto',
+ vertex: {
+ module: shader,
+ },
+ fragment: {
+ module: shader,
+ targets: [{ format: 'rgba8unorm' }],
+ },
+ };
+
+ t.doCreateRenderPipelineTest(false, true, descriptor);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/resource_compatibility.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/resource_compatibility.spec.ts
new file mode 100644
index 0000000000..5d6bc8d125
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/resource_compatibility.spec.ts
@@ -0,0 +1,95 @@
+export const description = `
+Tests for resource compatibilty between pipeline layout and shader modules
+ `;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import {
+ kAPIResources,
+ getWGSLShaderForResource,
+ getAPIBindGroupLayoutForResource,
+ doResourcesMatch,
+} from '../utils.js';
+
+import { CreateRenderPipelineValidationTest } from './common.js';
+
+export const g = makeTestGroup(CreateRenderPipelineValidationTest);
+
+g.test('resource_compatibility')
+ .desc(
+ 'Tests validation of resource (bind group) compatibility between pipeline layout and WGSL shader'
+ )
+ .params(u =>
+ u //
+ .combine('stage', ['vertex', 'fragment'] as const)
+ .combine('apiResource', keysOf(kAPIResources))
+ .filter(t => {
+ const res = kAPIResources[t.apiResource];
+ if (t.stage === 'vertex') {
+ if (res.buffer && res.buffer.type === 'storage') {
+ return false;
+ }
+ if (res.storageTexture && res.storageTexture.access !== 'read-only') {
+ return false;
+ }
+ }
+ return true;
+ })
+ .beginSubcases()
+ .combine('isAsync', [true, false] as const)
+ .combine('wgslResource', keysOf(kAPIResources))
+ )
+ .fn(t => {
+ const apiResource = kAPIResources[t.params.apiResource];
+ const wgslResource = kAPIResources[t.params.wgslResource];
+ t.skipIf(
+ wgslResource.storageTexture !== undefined &&
+ wgslResource.storageTexture.access !== 'write-only' &&
+ !t.hasLanguageFeature('readonly_and_readwrite_storage_textures'),
+ 'Storage textures require language feature'
+ );
+ const emptyVS = `
+@vertex
+fn main() -> @builtin(position) vec4f {
+ return vec4f();
+}
+`;
+ const emptyFS = `
+@fragment
+fn main() -> @location(0) vec4f {
+ return vec4f();
+}
+`;
+
+ const code = getWGSLShaderForResource(t.params.stage, wgslResource);
+ const vsCode = t.params.stage === 'vertex' ? code : emptyVS;
+ const fsCode = t.params.stage === 'fragment' ? code : emptyFS;
+ const gpuStage: GPUShaderStageFlags =
+ t.params.stage === 'vertex' ? GPUShaderStage.VERTEX : GPUShaderStage.FRAGMENT;
+ const layout = t.device.createPipelineLayout({
+ bindGroupLayouts: [getAPIBindGroupLayoutForResource(t.device, gpuStage, apiResource)],
+ });
+
+ const descriptor = {
+ layout,
+ vertex: {
+ module: t.device.createShaderModule({
+ code: vsCode,
+ }),
+ entryPoint: 'main',
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: fsCode,
+ }),
+ entryPoint: 'main',
+ targets: [{ format: 'rgba8unorm' }] as const,
+ },
+ };
+
+ t.doCreateRenderPipelineTest(
+ t.params.isAsync,
+ doResourcesMatch(apiResource, wgslResource),
+ descriptor
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/resource_usages/texture/in_pass_encoder.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/resource_usages/texture/in_pass_encoder.spec.ts
index d316f26c06..5114cbb266 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/resource_usages/texture/in_pass_encoder.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/resource_usages/texture/in_pass_encoder.spec.ts
@@ -13,11 +13,18 @@ import {
} from '../../../../format_info.js';
import { ValidationTest } from '../../validation_test.js';
-type TextureBindingType = 'sampled-texture' | 'multisampled-texture' | 'writeonly-storage-texture';
+type TextureBindingType =
+ | 'sampled-texture'
+ | 'multisampled-texture'
+ | 'writeonly-storage-texture'
+ | 'readonly-storage-texture'
+ | 'readwrite-storage-texture';
const kTextureBindingTypes = [
'sampled-texture',
'multisampled-texture',
'writeonly-storage-texture',
+ 'readonly-storage-texture',
+ 'readwrite-storage-texture',
] as const;
const SIZE = 32;
@@ -39,7 +46,7 @@ class TextureUsageTracking extends ValidationTest {
arrayLayerCount = 1,
mipLevelCount = 1,
sampleCount = 1,
- format = 'rgba8unorm',
+ format = 'r32float',
usage = GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
} = options;
@@ -75,6 +82,14 @@ class TextureUsageTracking extends ValidationTest {
assert(format !== undefined);
entry = { storageTexture: { access: 'write-only', format, viewDimension } };
break;
+ case 'readonly-storage-texture':
+ assert(format !== undefined);
+ entry = { storageTexture: { access: 'read-only', format, viewDimension } };
+ break;
+ case 'readwrite-storage-texture':
+ assert(format !== undefined);
+ entry = { storageTexture: { access: 'read-write', format, viewDimension } };
+ break;
}
return this.device.createBindGroupLayout({
@@ -107,7 +122,7 @@ class TextureUsageTracking extends ValidationTest {
depthStencilFormat?: GPUTextureFormat
) {
const bundleEncoder = this.device.createRenderBundleEncoder({
- colorFormats: ['rgba8unorm'],
+ colorFormats: ['r32float'],
depthStencilFormat,
});
bundleEncoder.setBindGroup(binding, bindGroup);
@@ -129,16 +144,21 @@ class TextureUsageTracking extends ValidationTest {
}
/**
- * Create two bind groups. Resource usages conflict between these two bind groups. But resource
- * usage inside each bind group doesn't conflict.
+ * Create two bind groups with one texture view.
*/
- makeConflictingBindGroups() {
+ makeTwoBindGroupsWithOneTextureView(usage1: TextureBindingType, usage2: TextureBindingType) {
const view = this.createTexture({
usage: GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING,
}).createView();
const bindGroupLayouts = [
- this.createBindGroupLayout(0, 'sampled-texture', '2d'),
- this.createBindGroupLayout(0, 'writeonly-storage-texture', '2d', { format: 'rgba8unorm' }),
+ this.createBindGroupLayout(0, usage1, '2d', {
+ sampleType: 'unfilterable-float',
+ format: 'r32float',
+ }),
+ this.createBindGroupLayout(0, usage2, '2d', {
+ sampleType: 'unfilterable-float',
+ format: 'r32float',
+ }),
];
return {
bindGroupLayouts,
@@ -155,14 +175,21 @@ class TextureUsageTracking extends ValidationTest {
};
}
- testValidationScope(compute: boolean): {
+ testValidationScope(
+ compute: boolean,
+ usage1: TextureBindingType,
+ usage2: TextureBindingType
+ ): {
bindGroup0: GPUBindGroup;
bindGroup1: GPUBindGroup;
encoder: GPUCommandEncoder;
pass: GPURenderPassEncoder | GPUComputePassEncoder;
pipeline: GPURenderPipeline | GPUComputePipeline;
} {
- const { bindGroupLayouts, bindGroups } = this.makeConflictingBindGroups();
+ const { bindGroupLayouts, bindGroups } = this.makeTwoBindGroupsWithOneTextureView(
+ usage1,
+ usage2
+ );
const encoder = this.device.createCommandEncoder();
const pass = compute
@@ -175,7 +202,7 @@ class TextureUsageTracking extends ValidationTest {
});
const pipeline = compute
? this.createNoOpComputePipeline(pipelineLayout)
- : this.createNoOpRenderPipeline(pipelineLayout);
+ : this.createNoOpRenderPipeline(pipelineLayout, 'r32float');
return {
bindGroup0: bindGroups[0],
bindGroup1: bindGroups[1],
@@ -237,6 +264,8 @@ g.test('subresources_and_binding_types_combination_for_color')
[
{ _usageOK: true, type0: 'sampled-texture', type1: 'sampled-texture' },
{ _usageOK: false, type0: 'sampled-texture', type1: 'writeonly-storage-texture' },
+ { _usageOK: true, type0: 'sampled-texture', type1: 'readonly-storage-texture' },
+ { _usageOK: false, type0: 'sampled-texture', type1: 'readwrite-storage-texture' },
{ _usageOK: false, type0: 'sampled-texture', type1: 'render-target' },
// Race condition upon multiple writable storage texture is valid.
// For p.compute === true, fails at pass.dispatch because aliasing exists.
@@ -245,7 +274,34 @@ g.test('subresources_and_binding_types_combination_for_color')
type0: 'writeonly-storage-texture',
type1: 'writeonly-storage-texture',
},
+ {
+ _usageOK: true,
+ type0: 'readonly-storage-texture',
+ type1: 'readonly-storage-texture',
+ },
+ {
+ _usageOK: !p.compute,
+ type0: 'readwrite-storage-texture',
+ type1: 'readwrite-storage-texture',
+ },
+ {
+ _usageOK: false,
+ type0: 'readonly-storage-texture',
+ type1: 'writeonly-storage-texture',
+ },
+ {
+ _usageOK: false,
+ type0: 'readonly-storage-texture',
+ type1: 'readwrite-storage-texture',
+ },
+ {
+ _usageOK: false,
+ type0: 'writeonly-storage-texture',
+ type1: 'readwrite-storage-texture',
+ },
+ { _usageOK: false, type0: 'readonly-storage-texture', type1: 'render-target' },
{ _usageOK: false, type0: 'writeonly-storage-texture', type1: 'render-target' },
+ { _usageOK: false, type0: 'readwrite-storage-texture', type1: 'render-target' },
{ _usageOK: false, type0: 'render-target', type1: 'render-target' },
] as const
)
@@ -428,6 +484,11 @@ g.test('subresources_and_binding_types_combination_for_color')
_resourceSuccess,
} = t.params;
+ t.skipIf(
+ t.isCompatibility,
+ 'multiple views of the same texture in a single draw/dispatch are not supported in compat, nor are sub ranges of layers'
+ );
+
const texture = t.createTexture({
arrayLayerCount: TOTAL_LAYERS,
mipLevelCount: TOTAL_LEVELS,
@@ -497,9 +558,13 @@ g.test('subresources_and_binding_types_combination_for_color')
const bgls: GPUBindGroupLayout[] = [];
// Create bind groups. Set bind groups in pass directly or set bind groups in bundle.
- const storageTextureFormat0 = type0 === 'sampled-texture' ? undefined : 'rgba8unorm';
+ const storageTextureFormat0 = type0 === 'sampled-texture' ? undefined : 'r32float';
+ const sampleType0 = type0 === 'sampled-texture' ? 'unfilterable-float' : undefined;
- const bgl0 = t.createBindGroupLayout(0, type0, dimension0, { format: storageTextureFormat0 });
+ const bgl0 = t.createBindGroupLayout(0, type0, dimension0, {
+ format: storageTextureFormat0,
+ sampleType: sampleType0,
+ });
const bindGroup0 = t.device.createBindGroup({
layout: bgl0,
entries: [{ binding: 0, resource: view0 }],
@@ -513,10 +578,11 @@ g.test('subresources_and_binding_types_combination_for_color')
pass.setBindGroup(0, bindGroup0);
}
if (type1 !== 'render-target') {
- const storageTextureFormat1 = type1 === 'sampled-texture' ? undefined : 'rgba8unorm';
-
+ const storageTextureFormat1 = type1 === 'sampled-texture' ? undefined : 'r32float';
+ const sampleType1 = type1 === 'sampled-texture' ? 'unfilterable-float' : undefined;
const bgl1 = t.createBindGroupLayout(1, type1, dimension1, {
format: storageTextureFormat1,
+ sampleType: sampleType1,
});
const bindGroup1 = t.device.createBindGroup({
layout: bgl1,
@@ -653,6 +719,8 @@ g.test('subresources_and_binding_types_combination_for_aspect')
_usageSuccess,
} = t.params;
+ t.skipIf(t.isCompatibility, 'sub ranges of layers are not supported in compat mode');
+
const texture = t.createTexture({
arrayLayerCount: TOTAL_LAYERS,
mipLevelCount: TOTAL_LEVELS,
@@ -782,9 +850,21 @@ g.test('shader_stages_and_visibility,storage_write')
GPUConst.ShaderStage.COMPUTE,
])
.combine('writeVisibility', [0, GPUConst.ShaderStage.FRAGMENT, GPUConst.ShaderStage.COMPUTE])
+ .combine('readEntry', [
+ { texture: { sampleType: 'unfilterable-float' } },
+ { storageTexture: { access: 'read-only', format: 'r32float' } },
+ ] as const)
+ .combine('storageWriteAccess', ['write-only', 'read-write'] as const)
)
.fn(t => {
- const { compute, readVisibility, writeVisibility, secondUseConflicts } = t.params;
+ const {
+ compute,
+ readEntry,
+ storageWriteAccess,
+ readVisibility,
+ writeVisibility,
+ secondUseConflicts,
+ } = t.params;
const usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING;
const view = t.createTexture({ usage }).createView();
@@ -792,11 +872,11 @@ g.test('shader_stages_and_visibility,storage_write')
const bgl = t.device.createBindGroupLayout({
entries: [
- { binding: 0, visibility: readVisibility, texture: {} },
+ { binding: 0, visibility: readVisibility, ...readEntry },
{
binding: 1,
visibility: writeVisibility,
- storageTexture: { access: 'write-only', format: 'rgba8unorm' },
+ storageTexture: { access: storageWriteAccess, format: 'r32float' },
},
],
});
@@ -851,19 +931,23 @@ g.test('shader_stages_and_visibility,attachment_write')
GPUConst.ShaderStage.FRAGMENT,
GPUConst.ShaderStage.COMPUTE,
])
+ .combine('readEntry', [
+ { texture: { sampleType: 'unfilterable-float' } },
+ { storageTexture: { access: 'read-only', format: 'r32float' } },
+ ] as const)
)
.fn(t => {
- const { readVisibility, secondUseConflicts } = t.params;
+ const { readVisibility, readEntry, secondUseConflicts } = t.params;
- // writeonly-storage-texture binding type is not supported in vertex stage. So, this test
- // uses writeonly-storage-texture binding as writable binding upon the same subresource if
- // vertex stage is not included. Otherwise, it uses output attachment instead.
- const usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT;
+ const usage =
+ GPUTextureUsage.TEXTURE_BINDING |
+ GPUTextureUsage.RENDER_ATTACHMENT |
+ GPUTextureUsage.STORAGE_BINDING;
const view = t.createTexture({ usage }).createView();
const view2 = secondUseConflicts ? view : t.createTexture({ usage }).createView();
const bgl = t.device.createBindGroupLayout({
- entries: [{ binding: 0, visibility: readVisibility, texture: {} }],
+ entries: [{ binding: 0, visibility: readVisibility, ...readEntry }],
});
const bindGroup = t.device.createBindGroup({
layout: bgl,
@@ -898,8 +982,10 @@ g.test('replaced_binding')
.combine('compute', [false, true])
.combine('callDrawOrDispatch', [false, true])
.combine('entry', [
- { texture: {} },
- { storageTexture: { access: 'write-only', format: 'rgba8unorm' } },
+ { texture: { sampleType: 'unfilterable-float' } },
+ { storageTexture: { access: 'read-only', format: 'r32float' } },
+ { storageTexture: { access: 'write-only', format: 'r32float' } },
+ { storageTexture: { access: 'read-write', format: 'r32float' } },
] as const)
)
.fn(t => {
@@ -912,7 +998,11 @@ g.test('replaced_binding')
// Create bindGroup0. It has two bindings. These two bindings use different views/subresources.
const bglEntries0: GPUBindGroupLayoutEntry[] = [
- { binding: 0, visibility: GPUShaderStage.FRAGMENT, texture: {} },
+ {
+ binding: 0,
+ visibility: GPUShaderStage.FRAGMENT,
+ texture: { sampleType: 'unfilterable-float' },
+ },
{
binding: 1,
visibility: GPUShaderStage.FRAGMENT,
@@ -930,7 +1020,9 @@ g.test('replaced_binding')
// Create bindGroup1. It has one binding, which use the same view/subresource of a binding in
// bindGroup0. So it may or may not conflicts with that binding in bindGroup0.
- const bindGroup1 = t.createBindGroup(0, sampledStorageView, 'sampled-texture', '2d', undefined);
+ const bindGroup1 = t.createBindGroup(0, sampledStorageView, 'sampled-texture', '2d', {
+ sampleType: 'unfilterable-float',
+ });
const encoder = t.device.createCommandEncoder();
const pass = compute
@@ -941,7 +1033,9 @@ g.test('replaced_binding')
// But bindings in bindGroup0 should be validated too.
pass.setBindGroup(0, bindGroup0);
if (callDrawOrDispatch) {
- const pipeline = compute ? t.createNoOpComputePipeline() : t.createNoOpRenderPipeline();
+ const pipeline = compute
+ ? t.createNoOpComputePipeline()
+ : t.createNoOpRenderPipeline('auto', 'r32float');
t.setPipeline(pass, pipeline);
t.issueDrawOrDispatch(pass);
}
@@ -951,7 +1045,9 @@ g.test('replaced_binding')
// MAINTENANCE_TODO: If the Compatible Usage List
// (https://gpuweb.github.io/gpuweb/#compatible-usage-list) gets programmatically defined in
// capability_info, use it here, instead of this logic, for clarity.
- let success = entry.storageTexture?.access !== 'write-only';
+ let success =
+ entry.storageTexture?.access !== 'write-only' &&
+ entry.storageTexture?.access !== 'read-write';
// Replaced bindings should not be validated in compute pass, because validation only occurs
// inside dispatchWorkgroups() which only looks at the current resource usages.
success ||= compute;
@@ -981,7 +1077,9 @@ g.test('bindings_in_bundle')
case 'multisampled-texture':
case 'sampled-texture':
return 'TEXTURE_BINDING' as const;
+ case 'readonly-storage-texture':
case 'writeonly-storage-texture':
+ case 'readwrite-storage-texture':
return 'STORAGE_BINDING' as const;
case 'render-target':
return 'RENDER_ATTACHMENT' as const;
@@ -1033,17 +1131,17 @@ g.test('bindings_in_bundle')
const bindGroups: GPUBindGroup[] = [];
if (type0 !== 'render-target') {
- const binding0TexFormat = type0 === 'sampled-texture' ? undefined : 'rgba8unorm';
+ const binding0TexFormat = type0 === 'sampled-texture' ? undefined : 'r32float';
bindGroups[0] = t.createBindGroup(0, view, type0, '2d', {
format: binding0TexFormat,
- sampleType: _sampleCount && 'unfilterable-float',
+ sampleType: 'unfilterable-float',
});
}
if (type1 !== 'render-target') {
- const binding1TexFormat = type1 === 'sampled-texture' ? undefined : 'rgba8unorm';
+ const binding1TexFormat = type1 === 'sampled-texture' ? undefined : 'r32float';
bindGroups[1] = t.createBindGroup(1, view, type1, '2d', {
format: binding1TexFormat,
- sampleType: _sampleCount && 'unfilterable-float',
+ sampleType: 'unfilterable-float',
});
}
@@ -1062,7 +1160,7 @@ g.test('bindings_in_bundle')
// 'render-target').
if (bindingsInBundle[i]) {
const bundleEncoder = t.device.createRenderBundleEncoder({
- colorFormats: ['rgba8unorm'],
+ colorFormats: ['r32float'],
});
bundleEncoder.setBindGroup(i, bindGroups[i]);
const bundleInPass = bundleEncoder.finish();
@@ -1078,6 +1176,7 @@ g.test('bindings_in_bundle')
switch (t) {
case 'sampled-texture':
case 'multisampled-texture':
+ case 'readonly-storage-texture':
return true;
default:
return false;
@@ -1089,7 +1188,8 @@ g.test('bindings_in_bundle')
success = true;
}
- if (type0 === 'writeonly-storage-texture' && type1 === 'writeonly-storage-texture') {
+ // Writable storage textures (write-only and read-write storage textures) cannot be aliased.
+ if (type0 === type1) {
success = true;
}
@@ -1110,6 +1210,8 @@ g.test('unused_bindings_in_pipeline')
.params(u =>
u
.combine('compute', [false, true])
+ .combine('readOnlyUsage', ['sampled-texture', 'readonly-storage-texture'] as const)
+ .combine('writableUsage', ['writeonly-storage-texture', 'readwrite-storage-texture'] as const)
.combine('useBindGroup0', [false, true])
.combine('useBindGroup1', [false, true])
.combine('setBindGroupsOrder', ['common', 'reversed'] as const)
@@ -1119,41 +1221,49 @@ g.test('unused_bindings_in_pipeline')
.fn(t => {
const {
compute,
+ readOnlyUsage,
+ writableUsage,
useBindGroup0,
useBindGroup1,
setBindGroupsOrder,
setPipeline,
callDrawOrDispatch,
} = t.params;
+ if (writableUsage === 'readwrite-storage-texture') {
+ t.skipIfLanguageFeatureNotSupported('readonly_and_readwrite_storage_textures');
+ }
+
const view = t
.createTexture({ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING })
.createView();
- const bindGroup0 = t.createBindGroup(0, view, 'sampled-texture', '2d', {
- format: 'rgba8unorm',
+ const bindGroup0 = t.createBindGroup(0, view, readOnlyUsage, '2d', {
+ sampleType: 'unfilterable-float',
+ format: 'r32float',
});
- const bindGroup1 = t.createBindGroup(0, view, 'writeonly-storage-texture', '2d', {
- format: 'rgba8unorm',
+ const bindGroup1 = t.createBindGroup(0, view, writableUsage, '2d', {
+ format: 'r32float',
});
+ const writeAccess = writableUsage === 'writeonly-storage-texture' ? 'write' : 'read_write';
const wgslVertex = `@vertex fn main() -> @builtin(position) vec4<f32> {
return vec4<f32>();
}`;
const wgslFragment = pp`
${pp._if(useBindGroup0)}
- @group(0) @binding(0) var image0 : texture_storage_2d<rgba8unorm, write>;
+ @group(0) @binding(0) var image0 : texture_storage_2d<r32float, ${writeAccess}>;
${pp._endif}
${pp._if(useBindGroup1)}
- @group(1) @binding(0) var image1 : texture_storage_2d<rgba8unorm, write>;
+ @group(1) @binding(0) var image1 : texture_storage_2d<r32float, ${writeAccess}>;
${pp._endif}
@fragment fn main() {}
`;
const wgslCompute = pp`
${pp._if(useBindGroup0)}
- @group(0) @binding(0) var image0 : texture_storage_2d<rgba8unorm, write>;
+ @group(0) @binding(0) var image0 : texture_storage_2d<r32float, ${writeAccess}>;
${pp._endif}
${pp._if(useBindGroup1)}
- @group(1) @binding(0) var image1 : texture_storage_2d<rgba8unorm, write>;
+ @group(1) @binding(0) var image1 : texture_storage_2d<r32float, ${writeAccess}>;
${pp._endif}
@compute @workgroup_size(1) fn main() {}
`;
@@ -1181,7 +1291,7 @@ g.test('unused_bindings_in_pipeline')
code: wgslFragment,
}),
entryPoint: 'main',
- targets: [{ format: 'rgba8unorm', writeMask: 0 }],
+ targets: [{ format: 'r32float', writeMask: 0 }],
},
primitive: { topology: 'triangle-list' },
});
@@ -1237,14 +1347,28 @@ g.test('scope,dispatch')
.params(u =>
u
.combine('dispatch', ['none', 'direct', 'indirect'])
+ .expandWithParams(
+ p =>
+ [
+ { usage1: 'sampled-texture', usage2: 'writeonly-storage-texture' },
+ { usage1: 'sampled-texture', usage2: 'readwrite-storage-texture' },
+ { usage1: 'readonly-storage-texture', usage2: 'writeonly-storage-texture' },
+ { usage1: 'readonly-storage-texture', usage2: 'readwrite-storage-texture' },
+ { usage1: 'writeonly-storage-texture', usage2: 'readwrite-storage-texture' },
+ ] as const
+ )
.beginSubcases()
.expand('setBindGroup0', p => (p.dispatch ? [true] : [false, true]))
.expand('setBindGroup1', p => (p.dispatch ? [true] : [false, true]))
)
.fn(t => {
- const { dispatch, setBindGroup0, setBindGroup1 } = t.params;
+ const { dispatch, usage1, usage2, setBindGroup0, setBindGroup1 } = t.params;
- const { bindGroup0, bindGroup1, encoder, pass, pipeline } = t.testValidationScope(true);
+ const { bindGroup0, bindGroup1, encoder, pass, pipeline } = t.testValidationScope(
+ true,
+ usage1,
+ usage2
+ );
assert(pass instanceof GPUComputePassEncoder);
t.setPipeline(pass, pipeline);
@@ -1284,11 +1408,21 @@ g.test('scope,basic,render')
u //
.combine('setBindGroup0', [false, true])
.combine('setBindGroup1', [false, true])
+ .expandWithParams(
+ p =>
+ [
+ { usage1: 'sampled-texture', usage2: 'writeonly-storage-texture' },
+ { usage1: 'sampled-texture', usage2: 'readwrite-storage-texture' },
+ { usage1: 'readonly-storage-texture', usage2: 'writeonly-storage-texture' },
+ { usage1: 'readonly-storage-texture', usage2: 'readwrite-storage-texture' },
+ { usage1: 'writeonly-storage-texture', usage2: 'readwrite-storage-texture' },
+ ] as const
+ )
)
.fn(t => {
- const { setBindGroup0, setBindGroup1 } = t.params;
+ const { setBindGroup0, setBindGroup1, usage1, usage2 } = t.params;
- const { bindGroup0, bindGroup1, encoder, pass } = t.testValidationScope(false);
+ const { bindGroup0, bindGroup1, encoder, pass } = t.testValidationScope(false, usage1, usage2);
assert(pass instanceof GPURenderPassEncoder);
if (setBindGroup0) pass.setBindGroup(0, bindGroup0);
@@ -1308,11 +1442,22 @@ g.test('scope,pass_boundary,compute')
boundary in between. This should always be valid.
`
)
- .paramsSubcasesOnly(u => u.combine('splitPass', [false, true]))
+ .paramsSubcasesOnly(u =>
+ u.combine('splitPass', [false, true]).expandWithParams(
+ p =>
+ [
+ { usage1: 'sampled-texture', usage2: 'writeonly-storage-texture' },
+ { usage1: 'sampled-texture', usage2: 'readwrite-storage-texture' },
+ { usage1: 'readonly-storage-texture', usage2: 'writeonly-storage-texture' },
+ { usage1: 'readonly-storage-texture', usage2: 'readwrite-storage-texture' },
+ { usage1: 'writeonly-storage-texture', usage2: 'readwrite-storage-texture' },
+ ] as const
+ )
+ )
.fn(t => {
- const { splitPass } = t.params;
+ const { splitPass, usage1, usage2 } = t.params;
- const { bindGroupLayouts, bindGroups } = t.makeConflictingBindGroups();
+ const { bindGroupLayouts, bindGroups } = t.makeTwoBindGroupsWithOneTextureView(usage1, usage2);
const encoder = t.device.createCommandEncoder();
@@ -1355,23 +1500,35 @@ g.test('scope,pass_boundary,render')
u //
.combine('splitPass', [false, true])
.combine('draw', [false, true])
+ .expandWithParams(
+ p =>
+ [
+ { usage1: 'sampled-texture', usage2: 'writeonly-storage-texture' },
+ { usage1: 'sampled-texture', usage2: 'readwrite-storage-texture' },
+ { usage1: 'readonly-storage-texture', usage2: 'writeonly-storage-texture' },
+ { usage1: 'readonly-storage-texture', usage2: 'readwrite-storage-texture' },
+ { usage1: 'writeonly-storage-texture', usage2: 'readwrite-storage-texture' },
+ ] as const
+ )
)
.fn(t => {
- const { splitPass, draw } = t.params;
+ const { splitPass, draw, usage1, usage2 } = t.params;
- const { bindGroupLayouts, bindGroups } = t.makeConflictingBindGroups();
+ const { bindGroupLayouts, bindGroups } = t.makeTwoBindGroupsWithOneTextureView(usage1, usage2);
const encoder = t.device.createCommandEncoder();
const pipelineUsingBG0 = t.createNoOpRenderPipeline(
t.device.createPipelineLayout({
bindGroupLayouts: [bindGroupLayouts[0]],
- })
+ }),
+ 'r32float'
);
const pipelineUsingBG1 = t.createNoOpRenderPipeline(
t.device.createPipelineLayout({
bindGroupLayouts: [bindGroupLayouts[1]],
- })
+ }),
+ 'r32float'
);
const attachment = t.createTexture().createView();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/resource_usages/texture/in_render_common.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/resource_usages/texture/in_render_common.spec.ts
index 0c41098556..120aadfb07 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/resource_usages/texture/in_render_common.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/resource_usages/texture/in_render_common.spec.ts
@@ -6,6 +6,21 @@ import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { assert, unreachable } from '../../../../../common/util/util.js';
import { ValidationTest } from '../../validation_test.js';
+export type TextureBindingType =
+ | 'sampled-texture'
+ | 'writeonly-storage-texture'
+ | 'readonly-storage-texture'
+ | 'readwrite-storage-texture';
+export const kTextureBindingTypes = [
+ 'sampled-texture',
+ 'writeonly-storage-texture',
+ 'readonly-storage-texture',
+ 'readwrite-storage-texture',
+] as const;
+export function IsReadOnlyTextureBindingType(t: TextureBindingType): boolean {
+ return t === 'sampled-texture' || t === 'readonly-storage-texture';
+}
+
class F extends ValidationTest {
getColorAttachment(
texture: GPUTexture,
@@ -23,21 +38,35 @@ class F extends ValidationTest {
createBindGroupForTest(
textureView: GPUTextureView,
- textureUsage: 'texture' | 'storage',
- sampleType: 'float' | 'depth' | 'uint'
+ textureUsage: TextureBindingType,
+ sampleType: 'unfilterable-float' | 'depth' | 'uint'
) {
const bindGroupLayoutEntry: GPUBindGroupLayoutEntry = {
binding: 0,
visibility: GPUShaderStage.FRAGMENT,
};
switch (textureUsage) {
- case 'texture':
+ case 'sampled-texture':
bindGroupLayoutEntry.texture = { viewDimension: '2d-array', sampleType };
break;
- case 'storage':
+ case 'readonly-storage-texture':
+ bindGroupLayoutEntry.storageTexture = {
+ access: 'read-only',
+ format: 'r32float',
+ viewDimension: '2d-array',
+ };
+ break;
+ case 'readwrite-storage-texture':
+ bindGroupLayoutEntry.storageTexture = {
+ access: 'read-write',
+ format: 'r32float',
+ viewDimension: '2d-array',
+ };
+ break;
+ case 'writeonly-storage-texture':
bindGroupLayoutEntry.storageTexture = {
access: 'write-only',
- format: 'rgba8unorm',
+ format: 'r32float',
viewDimension: '2d-array',
};
break;
@@ -89,7 +118,7 @@ g.test('subresources,color_attachments')
const { layer0, level0, layer1, level1, inSamePass } = t.params;
const texture = t.device.createTexture({
- format: 'rgba8unorm',
+ format: 'r32float',
usage: GPUTextureUsage.RENDER_ATTACHMENT,
size: [kTextureSize, kTextureSize, kTextureLayers],
mipLevelCount: kTextureLevels,
@@ -152,8 +181,8 @@ g.test('subresources,color_attachment_and_bind_group')
{ bgLayer: 1, bgLayerCount: 1 },
{ bgLayer: 1, bgLayerCount: 2 },
])
- .combine('bgUsage', ['texture', 'storage'] as const)
- .unless(t => t.bgUsage === 'storage' && t.bgLevelCount > 1)
+ .combine('bgUsage', kTextureBindingTypes)
+ .unless(t => t.bgUsage !== 'sampled-texture' && t.bgLevelCount > 1)
.combine('inSamePass', [true, false])
)
.fn(t => {
@@ -169,7 +198,7 @@ g.test('subresources,color_attachment_and_bind_group')
} = t.params;
const texture = t.device.createTexture({
- format: 'rgba8unorm',
+ format: 'r32float',
usage:
GPUTextureUsage.RENDER_ATTACHMENT |
GPUTextureUsage.TEXTURE_BINDING |
@@ -184,7 +213,7 @@ g.test('subresources,color_attachment_and_bind_group')
baseMipLevel: bgLevel,
mipLevelCount: bgLevelCount,
});
- const bindGroup = t.createBindGroupForTest(bindGroupView, bgUsage, 'float');
+ const bindGroup = t.createBindGroupForTest(bindGroupView, bgUsage, 'unfilterable-float');
const colorAttachment = t.getColorAttachment(texture, {
dimension: '2d',
@@ -205,7 +234,7 @@ g.test('subresources,color_attachment_and_bind_group')
renderPass.end();
const texture2 = t.device.createTexture({
- format: 'rgba8unorm',
+ format: 'r32float',
usage: GPUTextureUsage.RENDER_ATTACHMENT,
size: [kTextureSize, kTextureSize, 1],
mipLevelCount: 1,
@@ -261,7 +290,8 @@ g.test('subresources,depth_stencil_attachment_and_bind_group')
{ bgLayer: 1, bgLayerCount: 2 },
])
.beginSubcases()
- .combine('dsReadOnly', [true, false])
+ .combine('depthReadOnly', [true, false])
+ .combine('stencilReadOnly', [true, false])
.combine('bgAspect', ['depth-only', 'stencil-only'] as const)
.combine('inSamePass', [true, false])
)
@@ -273,7 +303,8 @@ g.test('subresources,depth_stencil_attachment_and_bind_group')
bgLevelCount,
bgLayer,
bgLayerCount,
- dsReadOnly,
+ depthReadOnly,
+ stencilReadOnly,
bgAspect,
inSamePass,
} = t.params;
@@ -293,7 +324,7 @@ g.test('subresources,depth_stencil_attachment_and_bind_group')
aspect: bgAspect,
});
const sampleType = bgAspect === 'depth-only' ? 'depth' : 'uint';
- const bindGroup = t.createBindGroupForTest(bindGroupView, 'texture', sampleType);
+ const bindGroup = t.createBindGroupForTest(bindGroupView, 'sampled-texture', sampleType);
const attachmentView = texture.createView({
dimension: '2d',
@@ -304,12 +335,12 @@ g.test('subresources,depth_stencil_attachment_and_bind_group')
});
const depthStencilAttachment: GPURenderPassDepthStencilAttachment = {
view: attachmentView,
- depthReadOnly: dsReadOnly,
- depthLoadOp: dsReadOnly ? undefined : 'load',
- depthStoreOp: dsReadOnly ? undefined : 'store',
- stencilReadOnly: dsReadOnly,
- stencilLoadOp: dsReadOnly ? undefined : 'load',
- stencilStoreOp: dsReadOnly ? undefined : 'store',
+ depthReadOnly,
+ depthLoadOp: depthReadOnly ? undefined : 'load',
+ depthStoreOp: depthReadOnly ? undefined : 'store',
+ stencilReadOnly,
+ stencilLoadOp: stencilReadOnly ? undefined : 'load',
+ stencilStoreOp: stencilReadOnly ? undefined : 'store',
};
const encoder = t.device.createCommandEncoder();
@@ -350,8 +381,11 @@ g.test('subresources,depth_stencil_attachment_and_bind_group')
bgLayer + bgLayerCount - 1
);
const isNotOverlapped = isMipLevelNotOverlapped || isArrayLayerNotOverlapped;
+ const readonly =
+ (bgAspect === 'stencil-only' && stencilReadOnly) ||
+ (bgAspect === 'depth-only' && depthReadOnly);
- const success = !inSamePass || isNotOverlapped || dsReadOnly;
+ const success = !inSamePass || isNotOverlapped || readonly;
t.expectValidationError(() => {
encoder.finish();
}, !success);
@@ -388,20 +422,21 @@ g.test('subresources,multiple_bind_groups')
{ base: 1, count: 1 },
{ base: 1, count: 2 },
])
- .combine('bgUsage0', ['texture', 'storage'] as const)
- .combine('bgUsage1', ['texture', 'storage'] as const)
+ .combine('bgUsage0', kTextureBindingTypes)
+ .combine('bgUsage1', kTextureBindingTypes)
.unless(
t =>
- (t.bgUsage0 === 'storage' && t.bg0Levels.count > 1) ||
- (t.bgUsage1 === 'storage' && t.bg1Levels.count > 1)
+ (t.bgUsage0 !== 'sampled-texture' && t.bg0Levels.count > 1) ||
+ (t.bgUsage1 !== 'sampled-texture' && t.bg1Levels.count > 1)
)
+ .beginSubcases()
.combine('inSamePass', [true, false])
)
.fn(t => {
const { bg0Levels, bg0Layers, bg1Levels, bg1Layers, bgUsage0, bgUsage1, inSamePass } = t.params;
const texture = t.device.createTexture({
- format: 'rgba8unorm',
+ format: 'r32float',
usage: GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING,
size: [kTextureSize, kTextureSize, kTextureLayers],
mipLevelCount: kTextureLevels,
@@ -420,11 +455,11 @@ g.test('subresources,multiple_bind_groups')
baseMipLevel: bg1Levels.base,
mipLevelCount: bg1Levels.count,
});
- const bindGroup0 = t.createBindGroupForTest(bg0, bgUsage0, 'float');
- const bindGroup1 = t.createBindGroupForTest(bg1, bgUsage1, 'float');
+ const bindGroup0 = t.createBindGroupForTest(bg0, bgUsage0, 'unfilterable-float');
+ const bindGroup1 = t.createBindGroupForTest(bg1, bgUsage1, 'unfilterable-float');
const colorTexture = t.device.createTexture({
- format: 'rgba8unorm',
+ format: 'r32float',
usage: GPUTextureUsage.RENDER_ATTACHMENT,
size: [kTextureSize, kTextureSize, 1],
mipLevelCount: 1,
@@ -449,6 +484,8 @@ g.test('subresources,multiple_bind_groups')
renderPass2.end();
}
+ const bothReadOnly =
+ IsReadOnlyTextureBindingType(bgUsage0) && IsReadOnlyTextureBindingType(bgUsage1);
const isMipLevelNotOverlapped = t.isRangeNotOverlapped(
bg0Levels.base,
bg0Levels.base + bg0Levels.count - 1,
@@ -463,7 +500,7 @@ g.test('subresources,multiple_bind_groups')
);
const isNotOverlapped = isMipLevelNotOverlapped || isArrayLayerNotOverlapped;
- const success = !inSamePass || isNotOverlapped || bgUsage0 === bgUsage1;
+ const success = !inSamePass || bothReadOnly || isNotOverlapped || bgUsage0 === bgUsage1;
t.expectValidationError(() => {
encoder.finish();
}, !success);
@@ -531,8 +568,8 @@ g.test('subresources,depth_stencil_texture_in_bind_groups')
const sampleType0 = aspect0 === 'depth-only' ? 'depth' : 'uint';
const sampleType1 = aspect1 === 'depth-only' ? 'depth' : 'uint';
- const bindGroup0 = t.createBindGroupForTest(bindGroupView0, 'texture', sampleType0);
- const bindGroup1 = t.createBindGroupForTest(bindGroupView1, 'texture', sampleType1);
+ const bindGroup0 = t.createBindGroupForTest(bindGroupView0, 'sampled-texture', sampleType0);
+ const bindGroup1 = t.createBindGroupForTest(bindGroupView1, 'sampled-texture', sampleType1);
const colorTexture = t.device.createTexture({
format: 'rgba8unorm',
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/resource_usages/texture/in_render_misc.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/resource_usages/texture/in_render_misc.spec.ts
index 1b80a2f73e..db18a0140f 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/resource_usages/texture/in_render_misc.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/resource_usages/texture/in_render_misc.spec.ts
@@ -5,11 +5,16 @@ Texture Usages Validation Tests on All Kinds of WebGPU Subresource Usage Scopes.
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { unreachable } from '../../../../../common/util/util.js';
import { ValidationTest } from '../../validation_test.js';
+import {
+ TextureBindingType,
+ kTextureBindingTypes,
+ IsReadOnlyTextureBindingType,
+} from '../texture/in_render_common.spec.js';
class F extends ValidationTest {
createBindGroupLayoutForTest(
- textureUsage: 'texture' | 'storage',
- sampleType: 'float' | 'depth' | 'uint',
+ textureUsage: TextureBindingType,
+ sampleType: 'unfilterable-float' | 'depth' | 'uint',
visibility: GPUShaderStage['FRAGMENT'] | GPUShaderStage['COMPUTE'] = GPUShaderStage['FRAGMENT']
): GPUBindGroupLayout {
const bindGroupLayoutEntry: GPUBindGroupLayoutEntry = {
@@ -18,13 +23,27 @@ class F extends ValidationTest {
};
switch (textureUsage) {
- case 'texture':
+ case 'sampled-texture':
bindGroupLayoutEntry.texture = { viewDimension: '2d-array', sampleType };
break;
- case 'storage':
+ case 'readonly-storage-texture':
+ bindGroupLayoutEntry.storageTexture = {
+ access: 'read-only',
+ format: 'r32float',
+ viewDimension: '2d-array',
+ };
+ break;
+ case 'writeonly-storage-texture':
bindGroupLayoutEntry.storageTexture = {
access: 'write-only',
- format: 'rgba8unorm',
+ format: 'r32float',
+ viewDimension: '2d-array',
+ };
+ break;
+ case 'readwrite-storage-texture':
+ bindGroupLayoutEntry.storageTexture = {
+ access: 'read-write',
+ format: 'r32float',
viewDimension: '2d-array',
};
break;
@@ -39,8 +58,8 @@ class F extends ValidationTest {
createBindGroupForTest(
textureView: GPUTextureView,
- textureUsage: 'texture' | 'storage',
- sampleType: 'float' | 'depth' | 'uint',
+ textureUsage: TextureBindingType,
+ sampleType: 'unfilterable-float' | 'depth' | 'uint',
visibility: GPUShaderStage['FRAGMENT'] | GPUShaderStage['COMPUTE'] = GPUShaderStage['FRAGMENT']
) {
return this.device.createBindGroup({
@@ -64,20 +83,16 @@ g.test('subresources,set_bind_group_on_same_index_color_texture')
)
.params(u =>
u
- .combineWithParams([
- { useDifferentTextureAsTexture2: true, baseLayer2: 0, view2Binding: 'texture' },
- { useDifferentTextureAsTexture2: false, baseLayer2: 0, view2Binding: 'texture' },
- { useDifferentTextureAsTexture2: false, baseLayer2: 1, view2Binding: 'texture' },
- { useDifferentTextureAsTexture2: false, baseLayer2: 0, view2Binding: 'storage' },
- { useDifferentTextureAsTexture2: false, baseLayer2: 1, view2Binding: 'storage' },
- ] as const)
- .combine('hasConflict', [true, false])
+ .combine('useDifferentTextureAsTexture2', [true, false])
+ .combine('baseLayer2', [0, 1] as const)
+ .combine('view1Binding', kTextureBindingTypes)
+ .combine('view2Binding', kTextureBindingTypes)
)
.fn(t => {
- const { useDifferentTextureAsTexture2, baseLayer2, view2Binding, hasConflict } = t.params;
+ const { useDifferentTextureAsTexture2, baseLayer2, view1Binding, view2Binding } = t.params;
const texture0 = t.device.createTexture({
- format: 'rgba8unorm',
+ format: 'r32float',
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING,
size: [kTextureSize, kTextureSize, kTextureLayers],
});
@@ -87,19 +102,12 @@ g.test('subresources,set_bind_group_on_same_index_color_texture')
baseArrayLayer: 0,
arrayLayerCount: 1,
});
- const bindGroup0 = t.createBindGroupForTest(textureView0, view2Binding, 'float');
-
- // In one renderPassEncoder it is an error to set both bindGroup0 and bindGroup1.
- const view1Binding = hasConflict
- ? view2Binding === 'texture'
- ? 'storage'
- : 'texture'
- : view2Binding;
- const bindGroup1 = t.createBindGroupForTest(textureView0, view1Binding, 'float');
+ const bindGroup0 = t.createBindGroupForTest(textureView0, view1Binding, 'unfilterable-float');
+ const bindGroup1 = t.createBindGroupForTest(textureView0, view2Binding, 'unfilterable-float');
const texture2 = useDifferentTextureAsTexture2
? t.device.createTexture({
- format: 'rgba8unorm',
+ format: 'r32float',
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING,
size: [kTextureSize, kTextureSize, kTextureLayers],
})
@@ -110,10 +118,14 @@ g.test('subresources,set_bind_group_on_same_index_color_texture')
arrayLayerCount: kTextureLayers - baseLayer2,
});
// There should be no conflict between bindGroup0 and validBindGroup2.
- const validBindGroup2 = t.createBindGroupForTest(textureView2, view2Binding, 'float');
+ const validBindGroup2 = t.createBindGroupForTest(
+ textureView2,
+ view2Binding,
+ 'unfilterable-float'
+ );
- const colorTexture = t.device.createTexture({
- format: 'rgba8unorm',
+ const unusedColorTexture = t.device.createTexture({
+ format: 'r32float',
usage: GPUTextureUsage.RENDER_ATTACHMENT,
size: [kTextureSize, kTextureSize, 1],
});
@@ -121,7 +133,7 @@ g.test('subresources,set_bind_group_on_same_index_color_texture')
const renderPassEncoder = encoder.beginRenderPass({
colorAttachments: [
{
- view: colorTexture.createView(),
+ view: unusedColorTexture.createView(),
loadOp: 'load',
storeOp: 'store',
},
@@ -132,9 +144,12 @@ g.test('subresources,set_bind_group_on_same_index_color_texture')
renderPassEncoder.setBindGroup(1, validBindGroup2);
renderPassEncoder.end();
+ const noConflict =
+ (IsReadOnlyTextureBindingType(view1Binding) && IsReadOnlyTextureBindingType(view2Binding)) ||
+ view1Binding === view2Binding;
t.expectValidationError(() => {
encoder.finish();
- }, hasConflict);
+ }, !noConflict);
});
g.test('subresources,set_bind_group_on_same_index_depth_stencil_texture')
@@ -155,6 +170,9 @@ g.test('subresources,set_bind_group_on_same_index_depth_stencil_texture')
format: 'depth24plus-stencil8',
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT,
size: [kTextureSize, kTextureSize, 1],
+ ...(t.isCompatibility && {
+ textureBindingViewDimension: '2d-array',
+ }),
});
const conflictedToNonReadOnlyAttachmentBindGroup = t.createBindGroupForTest(
@@ -162,21 +180,24 @@ g.test('subresources,set_bind_group_on_same_index_depth_stencil_texture')
dimension: '2d-array',
aspect: bindAspect,
}),
- 'texture',
+ 'sampled-texture',
bindAspect === 'depth-only' ? 'depth' : 'uint'
);
const colorTexture = t.device.createTexture({
- format: 'rgba8unorm',
+ format: 'r32float',
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING,
size: [kTextureSize, kTextureSize, 1],
+ ...(t.isCompatibility && {
+ textureBindingViewDimension: '2d-array',
+ }),
});
const validBindGroup = t.createBindGroupForTest(
colorTexture.createView({
dimension: '2d-array',
}),
- 'texture',
- 'float'
+ 'sampled-texture',
+ 'unfilterable-float'
);
const encoder = t.device.createCommandEncoder();
@@ -204,12 +225,24 @@ g.test('subresources,set_unused_bind_group')
used in the same render or compute pass encoder, its list of internal usages within one usage
scope can only be a compatible usage list.`
)
- .params(u => u.combine('inRenderPass', [true, false]).combine('hasConflict', [true, false]))
+ .params(u =>
+ u
+ .combine('inRenderPass', [true, false])
+ .combine('textureUsage0', kTextureBindingTypes)
+ .combine('textureUsage1', kTextureBindingTypes)
+ )
.fn(t => {
- const { inRenderPass, hasConflict } = t.params;
+ const { inRenderPass, textureUsage0, textureUsage1 } = t.params;
+
+ if (
+ textureUsage0 === 'readwrite-storage-texture' ||
+ textureUsage1 === 'readwrite-storage-texture'
+ ) {
+ t.skipIfLanguageFeatureNotSupported('readonly_and_readwrite_storage_textures');
+ }
const texture0 = t.device.createTexture({
- format: 'rgba8unorm',
+ format: 'r32float',
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING,
size: [kTextureSize, kTextureSize, kTextureLayers],
});
@@ -221,40 +254,85 @@ g.test('subresources,set_unused_bind_group')
});
const visibility = inRenderPass ? GPUShaderStage.FRAGMENT : GPUShaderStage.COMPUTE;
// bindGroup0 is used by the pipelines, and bindGroup1 is not used by the pipelines.
- const textureUsage0 = inRenderPass ? 'texture' : 'storage';
- const textureUsage1 = hasConflict ? (inRenderPass ? 'storage' : 'texture') : textureUsage0;
- const bindGroup0 = t.createBindGroupForTest(textureView0, textureUsage0, 'float', visibility);
- const bindGroup1 = t.createBindGroupForTest(textureView0, textureUsage1, 'float', visibility);
+ const bindGroup0 = t.createBindGroupForTest(
+ textureView0,
+ textureUsage0,
+ 'unfilterable-float',
+ visibility
+ );
+ const bindGroup1 = t.createBindGroupForTest(
+ textureView0,
+ textureUsage1,
+ 'unfilterable-float',
+ visibility
+ );
const encoder = t.device.createCommandEncoder();
const colorTexture = t.device.createTexture({
- format: 'rgba8unorm',
+ format: 'r32float',
usage: GPUTextureUsage.RENDER_ATTACHMENT,
size: [kTextureSize, kTextureSize, 1],
});
- const pipelineLayout = t.device.createPipelineLayout({
- bindGroupLayouts: [t.createBindGroupLayoutForTest(textureUsage0, 'float', visibility)],
- });
if (inRenderPass) {
+ let fragmentShader = '';
+ switch (textureUsage0) {
+ case 'sampled-texture':
+ fragmentShader = `
+ @group(0) @binding(0) var texture0 : texture_2d_array<f32>;
+ @fragment fn main()
+ -> @location(0) vec4<f32> {
+ return textureLoad(texture0, vec2<i32>(), 0, 0);
+ }
+ `;
+ break;
+ case `readonly-storage-texture`:
+ fragmentShader = `
+ @group(0) @binding(0) var texture0 : texture_storage_2d_array<r32float, read>;
+ @fragment fn main()
+ -> @location(0) vec4<f32> {
+ return textureLoad(texture0, vec2<i32>(), 0);
+ }
+ `;
+ break;
+ case `writeonly-storage-texture`:
+ fragmentShader = `
+ @group(0) @binding(0) var texture0 : texture_storage_2d_array<r32float, write>;
+ @fragment fn main()
+ -> @location(0) vec4<f32> {
+ textureStore(texture0, vec2i(), 0, vec4f(1, 0, 0, 1));
+ return vec4f(0, 0, 0, 1);
+ }
+ `;
+ break;
+ case `readwrite-storage-texture`:
+ fragmentShader = `
+ @group(0) @binding(0) var texture0 : texture_storage_2d_array<r32float, read_write>;
+ @fragment fn main()
+ -> @location(0) vec4<f32> {
+ let color = textureLoad(texture0, vec2i(), 0);
+ textureStore(texture0, vec2i(), 0, vec4f(1, 0, 0, 1));
+ return color;
+ }
+ `;
+ break;
+ }
+
const renderPipeline = t.device.createRenderPipeline({
- layout: pipelineLayout,
+ layout: t.device.createPipelineLayout({
+ bindGroupLayouts: [
+ t.createBindGroupLayoutForTest(textureUsage0, 'unfilterable-float', visibility),
+ ],
+ }),
vertex: {
module: t.device.createShaderModule({
code: t.getNoOpShaderCode('VERTEX'),
}),
- entryPoint: 'main',
},
fragment: {
module: t.device.createShaderModule({
- code: `
- @group(0) @binding(0) var texture0 : texture_2d_array<f32>;
- @fragment fn main()
- -> @location(0) vec4<f32> {
- return textureLoad(texture0, vec2<i32>(), 0, 0);
- }`,
+ code: fragmentShader,
}),
- entryPoint: 'main',
- targets: [{ format: 'rgba8unorm' }],
+ targets: [{ format: 'r32float' }],
},
});
@@ -273,29 +351,97 @@ g.test('subresources,set_unused_bind_group')
renderPassEncoder.draw(1);
renderPassEncoder.end();
} else {
+ let computeShader = '';
+ switch (textureUsage0) {
+ case 'sampled-texture':
+ computeShader = `
+ @group(0) @binding(0) var texture0 : texture_2d_array<f32>;
+ @group(1) @binding(0) var writableStorage : texture_storage_2d_array<r32float, write>;
+ @compute @workgroup_size(1) fn main() {
+ let value = textureLoad(texture0, vec2i(), 0, 0);
+ textureStore(writableStorage, vec2i(), 0, value);
+ }
+ `;
+ break;
+ case `readonly-storage-texture`:
+ computeShader = `
+ @group(0) @binding(0) var texture0 : texture_storage_2d_array<r32float, read>;
+ @group(1) @binding(0) var writableStorage : texture_storage_2d_array<r32float, write>;
+ @compute @workgroup_size(1) fn main() {
+ let value = textureLoad(texture0, vec2<i32>(), 0);
+ textureStore(writableStorage, vec2i(), 0, value);
+ }
+ `;
+ break;
+ case `writeonly-storage-texture`:
+ computeShader = `
+ @group(0) @binding(0) var texture0 : texture_storage_2d_array<r32float, write>;
+ @group(1) @binding(0) var writableStorage : texture_storage_2d_array<r32float, write>;
+ @compute @workgroup_size(1) fn main() {
+ textureStore(texture0, vec2i(), 0, vec4f(1, 0, 0, 1));
+ textureStore(writableStorage, vec2i(), 0, vec4f(1, 0, 0, 1));
+ }
+ `;
+ break;
+ case `readwrite-storage-texture`:
+ computeShader = `
+ @group(0) @binding(0) var texture0 : texture_storage_2d_array<r32float, read_write>;
+ @group(1) @binding(0) var writableStorage : texture_storage_2d_array<r32float, write>;
+ @compute @workgroup_size(1) fn main() {
+ let color = textureLoad(texture0, vec2i(), 0);
+ textureStore(texture0, vec2i(), 0, vec4f(1, 0, 0, 1));
+ textureStore(writableStorage, vec2i(), 0, color);
+ }
+ `;
+ break;
+ }
+
+ const pipelineLayout = t.device.createPipelineLayout({
+ bindGroupLayouts: [
+ t.createBindGroupLayoutForTest(textureUsage0, 'unfilterable-float', visibility),
+ t.createBindGroupLayoutForTest(
+ 'writeonly-storage-texture',
+ 'unfilterable-float',
+ visibility
+ ),
+ ],
+ });
const computePipeline = t.device.createComputePipeline({
layout: pipelineLayout,
compute: {
module: t.device.createShaderModule({
- code: `
- @group(0) @binding(0) var texture0 : texture_storage_2d_array<rgba8unorm, write>;
- @compute @workgroup_size(1)
- fn main() {
- textureStore(texture0, vec2<i32>(), 0, vec4<f32>());
- }`,
+ code: computeShader,
}),
- entryPoint: 'main',
},
});
+
+ const writableStorageTexture = t.device.createTexture({
+ format: 'r32float',
+ usage: GPUTextureUsage.STORAGE_BINDING,
+ size: [kTextureSize, kTextureSize, 1],
+ });
+ const writableStorageTextureView = writableStorageTexture.createView({
+ dimension: '2d-array',
+ baseArrayLayer: 0,
+ arrayLayerCount: 1,
+ });
+ const writableStorageTextureBindGroup = t.createBindGroupForTest(
+ writableStorageTextureView,
+ 'writeonly-storage-texture',
+ 'unfilterable-float',
+ visibility
+ );
+
const computePassEncoder = encoder.beginComputePass();
computePassEncoder.setBindGroup(0, bindGroup0);
- computePassEncoder.setBindGroup(1, bindGroup1);
+ computePassEncoder.setBindGroup(1, writableStorageTextureBindGroup);
+ computePassEncoder.setBindGroup(2, bindGroup1);
computePassEncoder.setPipeline(computePipeline);
computePassEncoder.dispatchWorkgroups(1);
computePassEncoder.end();
}
- // In WebGPU SPEC (Chapter 3.4.5, Synchronization):
+ // In WebGPU SPEC (https://gpuweb.github.io/gpuweb/#programming-model-synchronization):
// This specification defines the following usage scopes:
// - In a compute pass, each dispatch command (dispatchWorkgroups() or
// dispatchWorkgroupsIndirect()) is one usage scope. A subresource is "used" in the usage
@@ -306,7 +452,11 @@ g.test('subresources,set_unused_bind_group')
// referenced by any (state-setting or non-state-setting) command. For example, in
// setBindGroup(index, bindGroup, dynamicOffsets), every subresource in bindGroup is "used" in
// the render pass’s usage scope.
- const success = !inRenderPass || !hasConflict;
+ const success =
+ !inRenderPass ||
+ (IsReadOnlyTextureBindingType(textureUsage0) &&
+ IsReadOnlyTextureBindingType(textureUsage1)) ||
+ textureUsage0 === textureUsage1;
t.expectValidationError(() => {
encoder.finish();
}, !success);
@@ -324,16 +474,14 @@ g.test('subresources,texture_usages_in_copy_and_render_pass')
.combine('usage0', [
'copy-src',
'copy-dst',
- 'texture',
- 'storage',
'color-attachment',
+ ...kTextureBindingTypes,
] as const)
.combine('usage1', [
'copy-src',
'copy-dst',
- 'texture',
- 'storage',
'color-attachment',
+ ...kTextureBindingTypes,
] as const)
.filter(
({ usage0, usage1 }) =>
@@ -347,7 +495,7 @@ g.test('subresources,texture_usages_in_copy_and_render_pass')
const { usage0, usage1 } = t.params;
const texture = t.device.createTexture({
- format: 'rgba8unorm',
+ format: 'r32float',
usage:
GPUTextureUsage.COPY_SRC |
GPUTextureUsage.COPY_DST |
@@ -355,11 +503,14 @@ g.test('subresources,texture_usages_in_copy_and_render_pass')
GPUTextureUsage.STORAGE_BINDING |
GPUTextureUsage.RENDER_ATTACHMENT,
size: [kTextureSize, kTextureSize, 1],
+ ...(t.isCompatibility && {
+ textureBindingViewDimension: '2d-array',
+ }),
});
const UseTextureOnCommandEncoder = (
texture: GPUTexture,
- usage: 'copy-src' | 'copy-dst' | 'texture' | 'storage' | 'color-attachment',
+ usage: 'copy-src' | 'copy-dst' | 'color-attachment' | TextureBindingType,
encoder: GPUCommandEncoder
) => {
switch (usage) {
@@ -386,10 +537,12 @@ g.test('subresources,texture_usages_in_copy_and_render_pass')
renderPassEncoder.end();
break;
}
- case 'texture':
- case 'storage': {
+ case 'sampled-texture':
+ case 'readonly-storage-texture':
+ case 'writeonly-storage-texture':
+ case 'readwrite-storage-texture': {
const colorTexture = t.device.createTexture({
- format: 'rgba8unorm',
+ format: 'r32float',
usage: GPUTextureUsage.RENDER_ATTACHMENT,
size: [kTextureSize, kTextureSize, 1],
});
@@ -403,7 +556,7 @@ g.test('subresources,texture_usages_in_copy_and_render_pass')
dimension: '2d-array',
}),
usage,
- 'float'
+ 'unfilterable-float'
);
renderPassEncoder.setBindGroup(0, bindGroup);
renderPassEncoder.end();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/shader_module/entry_point.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/shader_module/entry_point.spec.ts
index 1a8da470a4..c956dc3021 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/shader_module/entry_point.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/shader_module/entry_point.spec.ts
@@ -3,6 +3,7 @@ This tests entry point validation of compute/render pipelines and their shader m
The entryPoint in shader module include standard "main" and others.
The entryPoint assigned in descriptor include:
+- Undefined with matching entry point for stage
- Matching case (control case)
- Empty string
- Mistyping
@@ -10,7 +11,6 @@ The entryPoint assigned in descriptor include:
- Unicode entrypoints and their ASCIIfied version
TODO:
-- Test unicode normalization (gpuweb/gpuweb#1160)
- Fine-tune test cases to reduce number by removing trivially similar cases
`;
@@ -43,23 +43,52 @@ const kEntryPointTestCases = [
g.test('compute')
.desc(
`
-Tests calling createComputePipeline(Async) with valid vertex stage shader and different entryPoints,
+Tests calling createComputePipeline(Async) with valid compute stage shader and different entryPoints,
and check that the APIs only accept matching entryPoint.
`
)
- .params(u => u.combine('isAsync', [true, false]).combineWithParams(kEntryPointTestCases))
+ .params(u =>
+ u
+ .combine('isAsync', [true, false])
+ .combine('shaderModuleStage', ['compute', 'vertex', 'fragment'] as const)
+ .beginSubcases()
+ .combine('provideEntryPoint', [true, false])
+ .combine('extraEntryPoint', [true, false])
+ .combineWithParams(kEntryPointTestCases)
+ )
.fn(t => {
- const { isAsync, shaderModuleEntryPoint, stageEntryPoint } = t.params;
+ const {
+ isAsync,
+ provideEntryPoint,
+ extraEntryPoint,
+ shaderModuleStage,
+ shaderModuleEntryPoint,
+ stageEntryPoint,
+ } = t.params;
+ const entryPoint = provideEntryPoint ? stageEntryPoint : undefined;
+ let code = getShaderWithEntryPoint(shaderModuleStage, shaderModuleEntryPoint);
+ if (extraEntryPoint) {
+ code += ` ${getShaderWithEntryPoint(shaderModuleStage, 'extra')}`;
+ }
const descriptor: GPUComputePipelineDescriptor = {
layout: 'auto',
compute: {
module: t.device.createShaderModule({
- code: getShaderWithEntryPoint('compute', shaderModuleEntryPoint),
+ code,
}),
- entryPoint: stageEntryPoint,
+ entryPoint,
},
};
- const _success = shaderModuleEntryPoint === stageEntryPoint;
+ let _success = true;
+ if (shaderModuleStage !== 'compute') {
+ _success = false;
+ }
+ if (!provideEntryPoint && extraEntryPoint) {
+ _success = false;
+ }
+ if (shaderModuleEntryPoint !== stageEntryPoint && provideEntryPoint) {
+ _success = false;
+ }
t.doCreateComputePipelineTest(isAsync, _success, descriptor);
});
@@ -70,19 +99,46 @@ Tests calling createRenderPipeline(Async) with valid vertex stage shader and dif
and check that the APIs only accept matching entryPoint.
`
)
- .params(u => u.combine('isAsync', [true, false]).combineWithParams(kEntryPointTestCases))
+ .params(u =>
+ u
+ .combine('isAsync', [true, false])
+ .combine('shaderModuleStage', ['compute', 'vertex', 'fragment'] as const)
+ .beginSubcases()
+ .combine('provideEntryPoint', [true, false])
+ .combine('extraEntryPoint', [true, false])
+ .combineWithParams(kEntryPointTestCases)
+ )
.fn(t => {
- const { isAsync, shaderModuleEntryPoint, stageEntryPoint } = t.params;
+ const {
+ isAsync,
+ provideEntryPoint,
+ extraEntryPoint,
+ shaderModuleStage,
+ shaderModuleEntryPoint,
+ stageEntryPoint,
+ } = t.params;
+ const entryPoint = provideEntryPoint ? stageEntryPoint : undefined;
+ let code = getShaderWithEntryPoint(shaderModuleStage, shaderModuleEntryPoint);
+ if (extraEntryPoint) {
+ code += ` ${getShaderWithEntryPoint(shaderModuleStage, 'extra')}`;
+ }
const descriptor: GPURenderPipelineDescriptor = {
layout: 'auto',
vertex: {
- module: t.device.createShaderModule({
- code: getShaderWithEntryPoint('vertex', shaderModuleEntryPoint),
- }),
- entryPoint: stageEntryPoint,
+ module: t.device.createShaderModule({ code }),
+ entryPoint,
},
};
- const _success = shaderModuleEntryPoint === stageEntryPoint;
+ let _success = true;
+ if (shaderModuleStage !== 'vertex') {
+ _success = false;
+ }
+ if (!provideEntryPoint && extraEntryPoint) {
+ _success = false;
+ }
+ if (shaderModuleEntryPoint !== stageEntryPoint && provideEntryPoint) {
+ _success = false;
+ }
t.doCreateRenderPipelineTest(isAsync, _success, descriptor);
});
@@ -93,25 +149,155 @@ Tests calling createRenderPipeline(Async) with valid fragment stage shader and d
and check that the APIs only accept matching entryPoint.
`
)
- .params(u => u.combine('isAsync', [true, false]).combineWithParams(kEntryPointTestCases))
+ .params(u =>
+ u
+ .combine('isAsync', [true, false])
+ .combine('shaderModuleStage', ['compute', 'vertex', 'fragment'] as const)
+ .beginSubcases()
+ .combine('provideEntryPoint', [true, false])
+ .combine('extraEntryPoint', [true, false])
+ .combineWithParams(kEntryPointTestCases)
+ )
.fn(t => {
- const { isAsync, shaderModuleEntryPoint, stageEntryPoint } = t.params;
+ const {
+ isAsync,
+ provideEntryPoint,
+ extraEntryPoint,
+ shaderModuleStage,
+ shaderModuleEntryPoint,
+ stageEntryPoint,
+ } = t.params;
+ const entryPoint = provideEntryPoint ? stageEntryPoint : undefined;
+ let code = getShaderWithEntryPoint(shaderModuleStage, shaderModuleEntryPoint);
+ if (extraEntryPoint) {
+ code += ` ${getShaderWithEntryPoint(shaderModuleStage, 'extra')}`;
+ }
const descriptor: GPURenderPipelineDescriptor = {
layout: 'auto',
vertex: {
module: t.device.createShaderModule({
code: kDefaultVertexShaderCode,
}),
- entryPoint: 'main',
},
fragment: {
module: t.device.createShaderModule({
- code: getShaderWithEntryPoint('fragment', shaderModuleEntryPoint),
+ code,
}),
- entryPoint: stageEntryPoint,
+ entryPoint,
targets: [{ format: 'rgba8unorm' }],
},
};
- const _success = shaderModuleEntryPoint === stageEntryPoint;
+ let _success = true;
+ if (shaderModuleStage !== 'fragment') {
+ _success = false;
+ }
+ if (!provideEntryPoint && extraEntryPoint) {
+ _success = false;
+ }
+ if (shaderModuleEntryPoint !== stageEntryPoint && provideEntryPoint) {
+ _success = false;
+ }
t.doCreateRenderPipelineTest(isAsync, _success, descriptor);
});
+
+g.test('compute_undefined_entry_point_and_extra_stage')
+ .desc(
+ `
+Tests calling createComputePipeline(Async) with compute stage shader and
+an undefined entryPoint is valid if there's an extra shader stage.
+`
+ )
+ .params(u =>
+ u
+ .combine('isAsync', [true, false])
+ .combine('extraShaderModuleStage', ['compute', 'vertex', 'fragment'] as const)
+ )
+ .fn(t => {
+ const { isAsync, extraShaderModuleStage } = t.params;
+ const code = `
+ ${getShaderWithEntryPoint('compute', 'main')}
+ ${getShaderWithEntryPoint(extraShaderModuleStage, 'extra')}
+ `;
+ const descriptor: GPUComputePipelineDescriptor = {
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code,
+ }),
+ entryPoint: undefined,
+ },
+ };
+
+ const success = extraShaderModuleStage !== 'compute';
+ t.doCreateComputePipelineTest(isAsync, success, descriptor);
+ });
+
+g.test('vertex_undefined_entry_point_and_extra_stage')
+ .desc(
+ `
+Tests calling createRenderPipeline(Async) with vertex stage shader and
+an undefined entryPoint is valid if there's an extra shader stage.
+`
+ )
+ .params(u =>
+ u
+ .combine('isAsync', [true, false])
+ .combine('extraShaderModuleStage', ['compute', 'vertex', 'fragment'] as const)
+ )
+ .fn(t => {
+ const { isAsync, extraShaderModuleStage } = t.params;
+ const code = `
+ ${getShaderWithEntryPoint('vertex', 'main')}
+ ${getShaderWithEntryPoint(extraShaderModuleStage, 'extra')}
+ `;
+ const descriptor: GPURenderPipelineDescriptor = {
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code,
+ }),
+ entryPoint: undefined,
+ },
+ };
+
+ const success = extraShaderModuleStage !== 'vertex';
+ t.doCreateRenderPipelineTest(isAsync, success, descriptor);
+ });
+
+g.test('fragment_undefined_entry_point_and_extra_stage')
+ .desc(
+ `
+Tests calling createRenderPipeline(Async) with fragment stage shader and
+an undefined entryPoint is valid if there's an extra shader stage.
+`
+ )
+ .params(u =>
+ u
+ .combine('isAsync', [true, false])
+ .combine('extraShaderModuleStage', ['compute', 'vertex', 'fragment'] as const)
+ )
+ .fn(t => {
+ const { isAsync, extraShaderModuleStage } = t.params;
+ const code = `
+ ${getShaderWithEntryPoint('fragment', 'main')}
+ ${getShaderWithEntryPoint(extraShaderModuleStage, 'extra')}
+ `;
+ const descriptor: GPURenderPipelineDescriptor = {
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: kDefaultVertexShaderCode,
+ }),
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code,
+ }),
+ entryPoint: undefined,
+ targets: [{ format: 'rgba8unorm' }],
+ },
+ };
+
+ const success = extraShaderModuleStage !== 'fragment';
+ t.doCreateRenderPipelineTest(isAsync, success, descriptor);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/state/device_lost/destroy.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/state/device_lost/destroy.spec.ts
index df03427a0a..42f069ee77 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/state/device_lost/destroy.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/state/device_lost/destroy.spec.ts
@@ -704,10 +704,7 @@ Tests import external texture on destroyed device. Tests valid combinations of:
entries: [
{
binding: 0,
- resource: t.device.importExternalTexture({
- /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
- source: source as any,
- }),
+ resource: t.device.importExternalTexture({ source }),
},
],
});
@@ -899,7 +896,12 @@ Tests encoding and finishing a writeTimestamp command on destroyed device.
const { type, stage, awaitLost } = t.params;
const querySet = t.device.createQuerySet({ type, count: 2 });
await t.executeCommandsAfterDestroy(stage, awaitLost, 'non-pass', maker => {
- maker.encoder.writeTimestamp(querySet, 0);
+ try {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (maker.encoder as any).writeTimestamp(querySet, 0);
+ } catch (ex) {
+ t.skipIf(ex instanceof TypeError, 'writeTimestamp is actually not available');
+ }
return maker;
});
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/texture/bgra8unorm_storage.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/texture/bgra8unorm_storage.spec.ts
index 80872fd5d3..0ae600eb49 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/texture/bgra8unorm_storage.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/texture/bgra8unorm_storage.spec.ts
@@ -81,37 +81,6 @@ validation cases where this feature is not enabled, which are skipped here.
});
});
-g.test('create_shader_module_with_bgra8unorm_storage')
- .desc(
- `
-Test that it is valid to declare the format of a storage texture as bgra8unorm in a shader module if
-the feature bgra8unorm-storage is enabled.
-`
- )
- .beforeAllSubcases(t => {
- t.selectDeviceOrSkipTestCase('bgra8unorm-storage');
- })
- .params(u => u.combine('shaderType', ['fragment', 'compute'] as const))
- .fn(t => {
- const { shaderType } = t.params;
-
- t.testCreateShaderModuleWithBGRA8UnormStorage(shaderType, true);
- });
-
-g.test('create_shader_module_without_bgra8unorm_storage')
- .desc(
- `
-Test that it is invalid to declare the format of a storage texture as bgra8unorm in a shader module
-if the feature bgra8unorm-storage is not enabled.
-`
- )
- .params(u => u.combine('shaderType', ['fragment', 'compute'] as const))
- .fn(t => {
- const { shaderType } = t.params;
-
- t.testCreateShaderModuleWithBGRA8UnormStorage(shaderType, false);
- });
-
g.test('configure_storage_usage_on_canvas_context_without_bgra8unorm_storage')
.desc(
`
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/utils.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/utils.ts
new file mode 100644
index 0000000000..e6ee87f797
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/utils.ts
@@ -0,0 +1,275 @@
+interface Resource {
+ readonly buffer?: GPUBufferBindingLayout;
+ readonly sampler?: GPUSamplerBindingLayout;
+ readonly texture?: GPUTextureBindingLayout;
+ readonly storageTexture?: GPUStorageTextureBindingLayout;
+ readonly externalTexture?: GPUExternalTextureBindingLayout;
+ readonly code: string;
+ readonly staticUse?: string;
+}
+
+/**
+ * Returns an array of possible resources
+ */
+function generateResources(): Resource[] {
+ const resources: Resource[] = [
+ // Buffers
+ {
+ buffer: { type: 'uniform' },
+ code: `var<uniform> res : array<vec4u, 16>`,
+ staticUse: `res[0]`,
+ },
+ {
+ buffer: { type: 'storage' },
+ code: `var<storage, read_write> res : array<vec4u>`,
+ staticUse: `res[0]`,
+ },
+ {
+ buffer: { type: 'read-only-storage' },
+ code: `var<storage> res : array<vec4u>`,
+ staticUse: `res[0]`,
+ },
+
+ // Samplers
+ {
+ sampler: { type: 'filtering' },
+ code: `var res : sampler`,
+ },
+ {
+ sampler: { type: 'non-filtering' },
+ code: `var res : sampler`,
+ },
+ {
+ sampler: { type: 'comparison' },
+ code: `var res : sampler_comparison`,
+ },
+ // Multisampled textures
+ {
+ texture: { sampleType: 'depth', viewDimension: '2d', multisampled: true },
+ code: `var res : texture_depth_multisampled_2d`,
+ },
+ {
+ texture: { sampleType: 'unfilterable-float', viewDimension: '2d', multisampled: true },
+ code: `var res : texture_multisampled_2d<f32>`,
+ },
+ {
+ texture: { sampleType: 'sint', viewDimension: '2d', multisampled: true },
+ code: `var res : texture_multisampled_2d<i32>`,
+ },
+ {
+ texture: { sampleType: 'uint', viewDimension: '2d', multisampled: true },
+ code: `var res : texture_multisampled_2d<u32>`,
+ },
+ ];
+
+ // Sampled textures
+ const sampleDims: GPUTextureViewDimension[] = [
+ '1d',
+ '2d',
+ '2d-array',
+ '3d',
+ 'cube',
+ 'cube-array',
+ ];
+ const sampleTypes: GPUTextureSampleType[] = ['float', 'unfilterable-float', 'sint', 'uint'];
+ const sampleWGSL = ['f32', 'f32', 'i32', 'u32'];
+ for (const dim of sampleDims) {
+ let i = 0;
+ for (const type of sampleTypes) {
+ resources.push({
+ texture: { sampleType: type, viewDimension: dim, multisampled: false },
+ code: `var res : texture_${dim.replace('-', '_')}<${sampleWGSL[i++]}>`,
+ });
+ }
+ }
+
+ // Depth textures
+ const depthDims: GPUTextureViewDimension[] = ['2d', '2d-array', 'cube', 'cube-array'];
+ for (const dim of depthDims) {
+ resources.push({
+ texture: { sampleType: 'depth', viewDimension: dim, multisampled: false },
+ code: `var res : texture_depth_${dim.replace('-', '_')}`,
+ });
+ }
+
+ // Storage textures
+ // Only cover r32uint, r32sint, and r32float here for ease of testing.
+ const storageDims: GPUTextureViewDimension[] = ['1d', '2d', '2d-array', '3d'];
+ const formats: GPUTextureFormat[] = ['r32float', 'r32sint', 'r32uint'];
+ const accesses: GPUStorageTextureAccess[] = ['write-only', 'read-only', 'read-write'];
+ for (const dim of storageDims) {
+ for (const format of formats) {
+ for (const access of accesses) {
+ resources.push({
+ storageTexture: { access, format, viewDimension: dim },
+ code: `var res : texture_storage_${dim.replace('-', '_')}<${format},${access
+ .replace('-only', '')
+ .replace('-', '_')}>`,
+ });
+ }
+ }
+ }
+
+ return resources;
+}
+
+/**
+ * Returns a string suitable as a Record key.
+ */
+function resourceKey(res: Resource): string {
+ if (res.buffer) {
+ return `${res.buffer.type}_buffer`;
+ }
+ if (res.sampler) {
+ return `${res.sampler.type}_sampler`;
+ }
+ if (res.texture) {
+ return `texture_${res.texture.sampleType}_${res.texture.viewDimension}_${res.texture.multisampled}`;
+ }
+ if (res.storageTexture) {
+ return `storage_texture_${res.storageTexture.viewDimension}_${res.storageTexture.format}_${res.storageTexture.access}`;
+ }
+ if (res.externalTexture) {
+ return `external_texture`;
+ }
+ return ``;
+}
+
+/**
+ * Resource array converted to a Record for nicer test parameterization names.
+ */
+export const kAPIResources: Record<string, Resource> = Object.fromEntries(
+ generateResources().map(x => [resourceKey(x), x])
+) as Record<string, Resource>;
+
+/**
+ * Generates a shader of the specified stage using the specified resource at binding (0,0).
+ */
+export function getWGSLShaderForResource(stage: string, resource: Resource): string {
+ let code = `@group(0) @binding(0) ${resource.code};\n`;
+
+ code += `@${stage}`;
+ if (stage === 'compute') {
+ code += `@workgroup_size(1)`;
+ }
+
+ let retTy = '';
+ let retVal = '';
+ if (stage === 'vertex') {
+ retTy = ' -> @builtin(position) vec4f';
+ retVal = 'return vec4f();';
+ } else if (stage === 'fragment') {
+ retTy = ' -> @location(0) vec4f';
+ retVal = 'return vec4f();';
+ }
+ code += `
+fn main() ${retTy} {
+ _ = ${resource.staticUse ?? 'res'};
+ ${retVal}
+}
+`;
+
+ return code;
+}
+
+/**
+ * Generates a bind group layout for for the given resource at binding 0.
+ */
+export function getAPIBindGroupLayoutForResource(
+ device: GPUDevice,
+ stage: GPUShaderStageFlags,
+ resource: Resource
+): GPUBindGroupLayout {
+ const entry: GPUBindGroupLayoutEntry = {
+ binding: 0,
+ visibility: stage,
+ };
+ if (resource.buffer) {
+ entry.buffer = resource.buffer;
+ }
+ if (resource.sampler) {
+ entry.sampler = resource.sampler;
+ }
+ if (resource.texture) {
+ entry.texture = resource.texture;
+ }
+ if (resource.storageTexture) {
+ entry.storageTexture = resource.storageTexture;
+ }
+ if (resource.externalTexture) {
+ entry.externalTexture = resource.externalTexture;
+ }
+
+ const entries: GPUBindGroupLayoutEntry[] = [entry];
+ return device.createBindGroupLayout({ entries });
+}
+
+/**
+ * Returns true if the sample types are compatible.
+ */
+function doSampleTypesMatch(api: GPUTextureSampleType, wgsl: GPUTextureSampleType): boolean {
+ if (api === 'float' || api === 'unfilterable-float') {
+ return wgsl === 'float' || wgsl === 'unfilterable-float';
+ }
+ return api === wgsl;
+}
+
+/**
+ * Returns true if the access modes are compatible.
+ */
+function doAccessesMatch(api: GPUStorageTextureAccess, wgsl: GPUStorageTextureAccess): boolean {
+ if (api === 'read-write') {
+ return wgsl === 'read-write' || wgsl === 'write-only';
+ }
+ return api === wgsl;
+}
+
+/**
+ * Returns true if the resources are compatible.
+ */
+export function doResourcesMatch(api: Resource, wgsl: Resource): boolean {
+ if (api.buffer) {
+ if (!wgsl.buffer) {
+ return false;
+ }
+ return api.buffer.type === wgsl.buffer.type;
+ }
+ if (api.sampler) {
+ if (!wgsl.sampler) {
+ return false;
+ }
+ return (
+ api.sampler.type === wgsl.sampler.type ||
+ (api.sampler.type !== 'comparison' && wgsl.sampler.type !== 'comparison')
+ );
+ }
+ if (api.texture) {
+ if (!wgsl.texture) {
+ return false;
+ }
+ const aType = api.texture.sampleType as GPUTextureSampleType;
+ const wType = wgsl.texture.sampleType as GPUTextureSampleType;
+ return (
+ doSampleTypesMatch(aType, wType) &&
+ api.texture.viewDimension === wgsl.texture.viewDimension &&
+ api.texture.multisampled === wgsl.texture.multisampled
+ );
+ }
+ if (api.storageTexture) {
+ if (!wgsl.storageTexture) {
+ return false;
+ }
+ const aAccess = api.storageTexture.access as GPUStorageTextureAccess;
+ const wAccess = wgsl.storageTexture.access as GPUStorageTextureAccess;
+ return (
+ doAccessesMatch(aAccess, wAccess) &&
+ api.storageTexture.format === wgsl.storageTexture.format &&
+ api.storageTexture.viewDimension === wgsl.storageTexture.viewDimension
+ );
+ }
+ if (api.externalTexture) {
+ return wgsl.externalTexture !== undefined;
+ }
+
+ return false;
+}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/validation_test.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/validation_test.ts
index 7ee5b9f7c1..6e6802d95f 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/validation_test.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/validation_test.ts
@@ -152,11 +152,11 @@ export class ValidationTest extends GPUTest {
}
/** Return an arbitrarily-configured GPUTexture with the `STORAGE_BINDING` usage. */
- getStorageTexture(): GPUTexture {
+ getStorageTexture(format: GPUTextureFormat): GPUTexture {
return this.trackForCleanup(
this.device.createTexture({
size: { width: 16, height: 16, depthOrArrayLayers: 1 },
- format: 'rgba8unorm',
+ format,
usage: GPUTextureUsage.STORAGE_BINDING,
})
);
@@ -220,8 +220,10 @@ export class ValidationTest extends GPUTest {
return this.getSampledTexture(1).createView();
case 'sampledTexMS':
return this.getSampledTexture(4).createView();
- case 'storageTex':
- return this.getStorageTexture().createView();
+ case 'readonlyStorageTex':
+ case 'writeonlyStorageTex':
+ case 'readwriteStorageTex':
+ return this.getStorageTexture('r32float').createView();
}
}
@@ -255,10 +257,10 @@ export class ValidationTest extends GPUTest {
}
/** Return an arbitrarily-configured GPUTexture with the `STORAGE` usage from mismatched device. */
- getDeviceMismatchedStorageTexture(): GPUTexture {
+ getDeviceMismatchedStorageTexture(format: GPUTextureFormat): GPUTexture {
return this.getDeviceMismatchedTexture({
size: { width: 4, height: 4, depthOrArrayLayers: 1 },
- format: 'rgba8unorm',
+ format,
usage: GPUTextureUsage.STORAGE_BINDING,
});
}
@@ -289,8 +291,10 @@ export class ValidationTest extends GPUTest {
return this.getDeviceMismatchedSampledTexture(1).createView();
case 'sampledTexMS':
return this.getDeviceMismatchedSampledTexture(4).createView();
- case 'storageTex':
- return this.getDeviceMismatchedStorageTexture().createView();
+ case 'readonlyStorageTex':
+ case 'writeonlyStorageTex':
+ case 'readwriteStorageTex':
+ return this.getDeviceMismatchedStorageTexture('r32float').createView();
}
}
@@ -317,7 +321,8 @@ export class ValidationTest extends GPUTest {
/** Return a GPURenderPipeline with default options and no-op vertex and fragment shaders. */
createNoOpRenderPipeline(
- layout: GPUPipelineLayout | GPUAutoLayoutMode = 'auto'
+ layout: GPUPipelineLayout | GPUAutoLayoutMode = 'auto',
+ colorFormat: GPUTextureFormat = 'rgba8unorm'
): GPURenderPipeline {
return this.device.createRenderPipeline({
layout,
@@ -332,7 +337,7 @@ export class ValidationTest extends GPUTest {
code: this.getNoOpShaderCode('FRAGMENT'),
}),
entryPoint: 'main',
- targets: [{ format: 'rgba8unorm', writeMask: 0 }],
+ targets: [{ format: colorFormat, writeMask: 0 }],
},
primitive: { topology: 'triangle-list' },
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/capability_info.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/capability_info.ts
index d65313c006..d26d93ca2e 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/capability_info.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/capability_info.ts
@@ -322,7 +322,9 @@ export type PerStageBindingLimitClass =
| 'storageBuf'
| 'sampler'
| 'sampledTex'
- | 'storageTex';
+ | 'readonlyStorageTex'
+ | 'writeonlyStorageTex'
+ | 'readwriteStorageTex';
/**
* Classes of `PerPipelineLayout` binding limits. Two bindings with the same class
* count toward the same `PerPipelineLayout` limit(s) in the spec (if any).
@@ -337,7 +339,9 @@ export type ValidBindableResource =
| 'compareSamp'
| 'sampledTex'
| 'sampledTexMS'
- | 'storageTex';
+ | 'readonlyStorageTex'
+ | 'writeonlyStorageTex'
+ | 'readwriteStorageTex';
type ErrorBindableResource = 'errorBuf' | 'errorSamp' | 'errorTex';
/**
@@ -353,7 +357,9 @@ export const kBindableResources = [
'compareSamp',
'sampledTex',
'sampledTexMS',
- 'storageTex',
+ 'readonlyStorageTex',
+ 'writeonlyStorageTex',
+ 'readwriteStorageTex',
'errorBuf',
'errorSamp',
'errorTex',
@@ -376,11 +382,13 @@ export const kPerStageBindingLimits: {
};
} =
/* prettier-ignore */ {
- 'uniformBuf': { class: 'uniformBuf', maxLimit: 'maxUniformBuffersPerShaderStage', },
- 'storageBuf': { class: 'storageBuf', maxLimit: 'maxStorageBuffersPerShaderStage', },
- 'sampler': { class: 'sampler', maxLimit: 'maxSamplersPerShaderStage', },
- 'sampledTex': { class: 'sampledTex', maxLimit: 'maxSampledTexturesPerShaderStage', },
- 'storageTex': { class: 'storageTex', maxLimit: 'maxStorageTexturesPerShaderStage', },
+ 'uniformBuf': { class: 'uniformBuf', maxLimit: 'maxUniformBuffersPerShaderStage', },
+ 'storageBuf': { class: 'storageBuf', maxLimit: 'maxStorageBuffersPerShaderStage', },
+ 'sampler': { class: 'sampler', maxLimit: 'maxSamplersPerShaderStage', },
+ 'sampledTex': { class: 'sampledTex', maxLimit: 'maxSampledTexturesPerShaderStage', },
+ 'readonlyStorageTex': { class: 'readonlyStorageTex', maxLimit: 'maxStorageTexturesPerShaderStage', },
+ 'writeonlyStorageTex': { class: 'writeonlyStorageTex', maxLimit: 'maxStorageTexturesPerShaderStage', },
+ 'readwriteStorageTex': { class: 'readwriteStorageTex', maxLimit: 'maxStorageTexturesPerShaderStage', },
};
/**
@@ -398,11 +406,13 @@ export const kPerPipelineBindingLimits: {
};
} =
/* prettier-ignore */ {
- 'uniformBuf': { class: 'uniformBuf', maxDynamicLimit: 'maxDynamicUniformBuffersPerPipelineLayout', },
- 'storageBuf': { class: 'storageBuf', maxDynamicLimit: 'maxDynamicStorageBuffersPerPipelineLayout', },
- 'sampler': { class: 'sampler', maxDynamicLimit: '', },
- 'sampledTex': { class: 'sampledTex', maxDynamicLimit: '', },
- 'storageTex': { class: 'storageTex', maxDynamicLimit: '', },
+ 'uniformBuf': { class: 'uniformBuf', maxDynamicLimit: 'maxDynamicUniformBuffersPerPipelineLayout', },
+ 'storageBuf': { class: 'storageBuf', maxDynamicLimit: 'maxDynamicStorageBuffersPerPipelineLayout', },
+ 'sampler': { class: 'sampler', maxDynamicLimit: '', },
+ 'sampledTex': { class: 'sampledTex', maxDynamicLimit: '', },
+ 'readonlyStorageTex': { class: 'readonlyStorageTex', maxDynamicLimit: '', },
+ 'writeonlyStorageTex': { class: 'writeonlyStorageTex', maxDynamicLimit: '', },
+ 'readwriteStorageTex': { class: 'readwriteStorageTex', maxDynamicLimit: '', },
};
interface BindingKindInfo {
@@ -416,14 +426,16 @@ const kBindingKind: {
readonly [k in ValidBindableResource]: BindingKindInfo;
} =
/* prettier-ignore */ {
- uniformBuf: { resource: 'uniformBuf', perStageLimitClass: kPerStageBindingLimits.uniformBuf, perPipelineLimitClass: kPerPipelineBindingLimits.uniformBuf, },
- storageBuf: { resource: 'storageBuf', perStageLimitClass: kPerStageBindingLimits.storageBuf, perPipelineLimitClass: kPerPipelineBindingLimits.storageBuf, },
- filtSamp: { resource: 'filtSamp', perStageLimitClass: kPerStageBindingLimits.sampler, perPipelineLimitClass: kPerPipelineBindingLimits.sampler, },
- nonFiltSamp: { resource: 'nonFiltSamp', perStageLimitClass: kPerStageBindingLimits.sampler, perPipelineLimitClass: kPerPipelineBindingLimits.sampler, },
- compareSamp: { resource: 'compareSamp', perStageLimitClass: kPerStageBindingLimits.sampler, perPipelineLimitClass: kPerPipelineBindingLimits.sampler, },
- sampledTex: { resource: 'sampledTex', perStageLimitClass: kPerStageBindingLimits.sampledTex, perPipelineLimitClass: kPerPipelineBindingLimits.sampledTex, },
- sampledTexMS: { resource: 'sampledTexMS', perStageLimitClass: kPerStageBindingLimits.sampledTex, perPipelineLimitClass: kPerPipelineBindingLimits.sampledTex, },
- storageTex: { resource: 'storageTex', perStageLimitClass: kPerStageBindingLimits.storageTex, perPipelineLimitClass: kPerPipelineBindingLimits.storageTex, },
+ uniformBuf: { resource: 'uniformBuf', perStageLimitClass: kPerStageBindingLimits.uniformBuf, perPipelineLimitClass: kPerPipelineBindingLimits.uniformBuf, },
+ storageBuf: { resource: 'storageBuf', perStageLimitClass: kPerStageBindingLimits.storageBuf, perPipelineLimitClass: kPerPipelineBindingLimits.storageBuf, },
+ filtSamp: { resource: 'filtSamp', perStageLimitClass: kPerStageBindingLimits.sampler, perPipelineLimitClass: kPerPipelineBindingLimits.sampler, },
+ nonFiltSamp: { resource: 'nonFiltSamp', perStageLimitClass: kPerStageBindingLimits.sampler, perPipelineLimitClass: kPerPipelineBindingLimits.sampler, },
+ compareSamp: { resource: 'compareSamp', perStageLimitClass: kPerStageBindingLimits.sampler, perPipelineLimitClass: kPerPipelineBindingLimits.sampler, },
+ sampledTex: { resource: 'sampledTex', perStageLimitClass: kPerStageBindingLimits.sampledTex, perPipelineLimitClass: kPerPipelineBindingLimits.sampledTex, },
+ sampledTexMS: { resource: 'sampledTexMS', perStageLimitClass: kPerStageBindingLimits.sampledTex, perPipelineLimitClass: kPerPipelineBindingLimits.sampledTex, },
+ readonlyStorageTex: { resource: 'readonlyStorageTex', perStageLimitClass: kPerStageBindingLimits.readonlyStorageTex, perPipelineLimitClass: kPerPipelineBindingLimits.readonlyStorageTex, },
+ writeonlyStorageTex: { resource: 'writeonlyStorageTex', perStageLimitClass: kPerStageBindingLimits.writeonlyStorageTex, perPipelineLimitClass: kPerPipelineBindingLimits.writeonlyStorageTex, },
+ readwriteStorageTex: { resource: 'readwriteStorageTex', perStageLimitClass: kPerStageBindingLimits.readwriteStorageTex, perPipelineLimitClass: kPerPipelineBindingLimits.readwriteStorageTex, },
};
// Binding type info
@@ -483,14 +495,30 @@ assertTypeTrue<TypeEqual<GPUTextureSampleType, (typeof kTextureSampleTypes)[numb
/** Binding type info (including class limits) for the specified GPUStorageTextureBindingLayout. */
export function storageTextureBindingTypeInfo(d: GPUStorageTextureBindingLayout) {
- return {
- usage: GPUConst.TextureUsage.STORAGE_BINDING,
- ...kBindingKind.storageTex,
- ...kValidStagesStorageWrite,
- };
+ switch (d.access) {
+ case undefined:
+ case 'write-only':
+ return {
+ usage: GPUConst.TextureUsage.STORAGE_BINDING,
+ ...kBindingKind.writeonlyStorageTex,
+ ...kValidStagesStorageWrite,
+ };
+ case 'read-only':
+ return {
+ usage: GPUConst.TextureUsage.STORAGE_BINDING,
+ ...kBindingKind.readonlyStorageTex,
+ ...kValidStagesAll,
+ };
+ case 'read-write':
+ return {
+ usage: GPUConst.TextureUsage.STORAGE_BINDING,
+ ...kBindingKind.readwriteStorageTex,
+ ...kValidStagesStorageWrite,
+ };
+ }
}
/** List of all GPUStorageTextureAccess values. */
-export const kStorageTextureAccessValues = ['write-only'] as const;
+export const kStorageTextureAccessValues = ['read-only', 'read-write', 'write-only'] as const;
assertTypeTrue<TypeEqual<GPUStorageTextureAccess, (typeof kStorageTextureAccessValues)[number]>>();
/** GPUBindGroupLayoutEntry, but only the "union" fields, not the common fields. */
@@ -539,8 +567,10 @@ export function samplerBindingEntries(includeUndefined: boolean): readonly BGLEn
*/
export function textureBindingEntries(includeUndefined: boolean): readonly BGLEntry[] {
return [
- ...(includeUndefined ? [{ texture: { multisampled: undefined } }] : []),
- { texture: { multisampled: false } },
+ ...(includeUndefined
+ ? [{ texture: { multisampled: undefined, sampleType: 'unfilterable-float' } } as const]
+ : []),
+ { texture: { multisampled: false, sampleType: 'unfilterable-float' } },
{ texture: { multisampled: true, sampleType: 'unfilterable-float' } },
] as const;
}
@@ -549,19 +579,17 @@ export function textureBindingEntries(includeUndefined: boolean): readonly BGLEn
*
* Note: Generates different `access` options, but not `format` or `viewDimension` options.
*/
-export function storageTextureBindingEntries(format: GPUTextureFormat): readonly BGLEntry[] {
- return [{ storageTexture: { access: 'write-only', format } }] as const;
-}
-/** Generate a list of possible texture-or-storageTexture-typed BGLEntry values. */
-export function sampledAndStorageBindingEntries(
- includeUndefined: boolean,
- storageTextureFormat: GPUTextureFormat = 'rgba8unorm'
-): readonly BGLEntry[] {
+export function storageTextureBindingEntries(): readonly BGLEntry[] {
return [
- ...textureBindingEntries(includeUndefined),
- ...storageTextureBindingEntries(storageTextureFormat),
+ { storageTexture: { access: 'write-only', format: 'r32float' } },
+ { storageTexture: { access: 'read-only', format: 'r32float' } },
+ { storageTexture: { access: 'read-write', format: 'r32float' } },
] as const;
}
+/** Generate a list of possible texture-or-storageTexture-typed BGLEntry values. */
+export function sampledAndStorageBindingEntries(includeUndefined: boolean): readonly BGLEntry[] {
+ return [...textureBindingEntries(includeUndefined), ...storageTextureBindingEntries()] as const;
+}
/**
* Generate a list of possible BGLEntry values of every type, but not variants with different:
* - buffer.hasDynamicOffset
@@ -569,14 +597,11 @@ export function sampledAndStorageBindingEntries(
* - texture.viewDimension
* - storageTexture.viewDimension
*/
-export function allBindingEntries(
- includeUndefined: boolean,
- storageTextureFormat: GPUTextureFormat = 'rgba8unorm'
-): readonly BGLEntry[] {
+export function allBindingEntries(includeUndefined: boolean): readonly BGLEntry[] {
return [
...bufferBindingEntries(includeUndefined),
...samplerBindingEntries(includeUndefined),
- ...sampledAndStorageBindingEntries(includeUndefined, storageTextureFormat),
+ ...sampledAndStorageBindingEntries(includeUndefined),
] as const;
}
@@ -689,7 +714,7 @@ const [kLimitInfoKeys, kLimitInfoDefaults, kLimitInfoData] =
'maxVertexAttributes': [ , 16, 16, ],
'maxVertexBufferArrayStride': [ , 2048, 2048, ],
'maxInterStageShaderComponents': [ , 60, 60, ],
- 'maxInterStageShaderVariables': [ , 16, 16, ],
+ 'maxInterStageShaderVariables': [ , 16, 15, ],
'maxColorAttachments': [ , 8, 4, ],
'maxColorAttachmentBytesPerSample': [ , 32, 32, ],
@@ -790,3 +815,13 @@ export const kFeatureNameInfo: {
};
/** List of all GPUFeatureName values. */
export const kFeatureNames = keysOf(kFeatureNameInfo);
+
+/** List of all known WGSL language features */
+export const kKnownWGSLLanguageFeatures = [
+ 'readonly_and_readwrite_storage_textures',
+ 'packed_4x8_integer_dot_product',
+ 'unrestricted_pointer_parameters',
+ 'pointer_composite_access',
+] as const;
+
+export type WGSLLanguageFeature = (typeof kKnownWGSLLanguageFeatures)[number];
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/createBindGroup.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/createBindGroup.spec.ts
new file mode 100644
index 0000000000..b48fa80422
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/createBindGroup.spec.ts
@@ -0,0 +1,178 @@
+export const description = `
+Tests that, in compat mode, the dimension of a view is compatible with a texture's textureBindingViewDimension.
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { kTextureDimensions, kTextureViewDimensions } from '../../../capability_info.js';
+import {
+ effectiveViewDimensionForTexture,
+ getTextureDimensionFromView,
+} from '../../../util/texture/base.js';
+import { CompatibilityTest } from '../../compatibility_test.js';
+
+export const g = makeTestGroup(CompatibilityTest);
+
+function isTextureBindingViewDimensionCompatibleWithDimension(
+ dimension: GPUTextureDimension = '2d',
+ textureBindingViewDimension: GPUTextureViewDimension = '2d'
+) {
+ return getTextureDimensionFromView(textureBindingViewDimension) === dimension;
+}
+
+function isValidViewDimensionForDimension(
+ dimension: GPUTextureDimension | undefined,
+ depthOrArrayLayers: number,
+ viewDimension: GPUTextureViewDimension | undefined
+) {
+ if (viewDimension === undefined) {
+ return true;
+ }
+
+ switch (dimension) {
+ case '1d':
+ return viewDimension === '1d';
+ case '2d':
+ case undefined:
+ switch (viewDimension) {
+ case undefined:
+ case '2d':
+ case '2d-array':
+ return true;
+ case 'cube':
+ return depthOrArrayLayers === 6;
+ case 'cube-array':
+ return depthOrArrayLayers % 6 === 0;
+ default:
+ return false;
+ }
+ break;
+ case '3d':
+ return viewDimension === '3d';
+ }
+}
+
+function isValidDimensionForDepthOrArrayLayers(
+ dimension: GPUTextureDimension | undefined,
+ depthOrArrayLayers: number
+) {
+ switch (dimension) {
+ case '1d':
+ return depthOrArrayLayers === 1;
+ default:
+ return true;
+ }
+}
+
+function isValidViewDimensionForDepthOrArrayLayers(
+ viewDimension: GPUTextureViewDimension | undefined,
+ depthOrArrayLayers: number
+) {
+ switch (viewDimension) {
+ case '2d':
+ return depthOrArrayLayers === 1;
+ case 'cube':
+ return depthOrArrayLayers === 6;
+ case 'cube-array':
+ return depthOrArrayLayers % 6 === 0;
+ default:
+ return true;
+ }
+ return viewDimension === 'cube';
+}
+
+function getEffectiveTextureBindingViewDimension(
+ dimension: GPUTextureDimension | undefined,
+ depthOrArrayLayers: number,
+ textureBindingViewDimension: GPUTextureViewDimension | undefined
+) {
+ if (textureBindingViewDimension) {
+ return textureBindingViewDimension;
+ }
+
+ switch (dimension) {
+ case '1d':
+ return '1d';
+ case '2d':
+ case undefined:
+ return depthOrArrayLayers > 1 ? '2d-array' : '2d';
+ break;
+ case '3d':
+ return '3d';
+ }
+}
+
+g.test('viewDimension_matches_textureBindingViewDimension')
+ .desc(
+ `
+ Tests that, in compat mode, the dimension of a view is compatible with a texture's textureBindingViewDimension
+ when used as a TEXTURE_BINDING.
+ `
+ )
+ .params(u =>
+ u //
+ .combine('dimension', [...kTextureDimensions, undefined])
+ .combine('textureBindingViewDimension', [...kTextureViewDimensions, undefined])
+ .combine('viewDimension', [...kTextureViewDimensions, undefined])
+ .combine('depthOrArrayLayers', [1, 2, 6])
+ .filter(
+ ({ dimension, textureBindingViewDimension, depthOrArrayLayers, viewDimension }) =>
+ textureBindingViewDimension !== 'cube-array' &&
+ viewDimension !== 'cube-array' &&
+ isTextureBindingViewDimensionCompatibleWithDimension(
+ dimension,
+ textureBindingViewDimension
+ ) &&
+ isValidViewDimensionForDimension(dimension, depthOrArrayLayers, viewDimension) &&
+ isValidViewDimensionForDepthOrArrayLayers(
+ textureBindingViewDimension,
+ depthOrArrayLayers
+ ) &&
+ isValidDimensionForDepthOrArrayLayers(dimension, depthOrArrayLayers)
+ )
+ )
+ .fn(t => {
+ const { dimension, textureBindingViewDimension, viewDimension, depthOrArrayLayers } = t.params;
+
+ const texture = t.device.createTexture({
+ size: [1, 1, depthOrArrayLayers],
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.TEXTURE_BINDING,
+ ...(dimension && { dimension }),
+ ...(textureBindingViewDimension && { textureBindingViewDimension }),
+ } as GPUTextureDescriptor); // MAINTENANCE_TODO: remove cast once textureBindingViewDimension is added to IDL
+ t.trackForCleanup(texture);
+
+ const effectiveTextureBindingViewDimension = getEffectiveTextureBindingViewDimension(
+ dimension,
+ texture.depthOrArrayLayers,
+ textureBindingViewDimension
+ );
+
+ const effectiveViewDimension = getEffectiveTextureBindingViewDimension(
+ dimension,
+ texture.depthOrArrayLayers,
+ viewDimension
+ );
+
+ const layout = t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.COMPUTE,
+ texture: {
+ viewDimension: effectiveViewDimensionForTexture(texture, viewDimension),
+ },
+ },
+ ],
+ });
+
+ const resource = texture.createView({ dimension: viewDimension });
+ const shouldError = effectiveTextureBindingViewDimension !== effectiveViewDimension;
+
+ t.expectValidationError(() => {
+ t.device.createBindGroup({
+ layout,
+ entries: [{ binding: 0, resource }],
+ });
+ }, shouldError);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/createBindGroupLayout.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/createBindGroupLayout.spec.ts
new file mode 100644
index 0000000000..f05af2860e
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/createBindGroupLayout.spec.ts
@@ -0,0 +1,34 @@
+export const description = `
+Tests that, in compat mode, you can not create a bind group layout with unsupported storage texture formats.
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { kCompatModeUnsupportedStorageTextureFormats } from '../../../format_info.js';
+import { CompatibilityTest } from '../../compatibility_test.js';
+
+export const g = makeTestGroup(CompatibilityTest);
+
+g.test('unsupportedStorageTextureFormats')
+ .desc(
+ `
+ Tests that, in compat mode, you can not create a bind group layout with unsupported storage texture formats.
+ `
+ )
+ .params(u => u.combine('format', kCompatModeUnsupportedStorageTextureFormats))
+ .fn(t => {
+ const { format } = t.params;
+
+ t.expectValidationError(() => {
+ t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.COMPUTE,
+ storageTexture: {
+ format,
+ },
+ },
+ ],
+ });
+ }, true);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/encoding/cmds/copyTextureToBuffer.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/encoding/cmds/copyTextureToBuffer.spec.ts
index a9af7795b3..e7d5164c45 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/encoding/cmds/copyTextureToBuffer.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/encoding/cmds/copyTextureToBuffer.spec.ts
@@ -19,16 +19,17 @@ g.test('compressed')
.fn(t => {
const { format } = t.params;
- const { blockWidth, blockHeight, bytesPerBlock } = kTextureFormatInfo[format];
+ const info = kTextureFormatInfo[format];
+ const textureSize = [info.blockWidth, info.blockHeight, 1];
const texture = t.device.createTexture({
- size: [blockWidth, blockHeight, 1],
+ size: textureSize,
format,
usage: GPUTextureUsage.COPY_SRC,
});
t.trackForCleanup(texture);
- const bytesPerRow = align(bytesPerBlock, 256);
+ const bytesPerRow = align(info.color.bytes, 256);
const buffer = t.device.createBuffer({
size: bytesPerRow,
@@ -37,7 +38,7 @@ g.test('compressed')
t.trackForCleanup(buffer);
const encoder = t.device.createCommandEncoder();
- encoder.copyTextureToBuffer({ texture }, { buffer, bytesPerRow }, [blockWidth, blockHeight, 1]);
+ encoder.copyTextureToBuffer({ texture }, { buffer, bytesPerRow }, textureSize);
t.expectGPUError('validation', () => {
encoder.finish();
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/encoding/cmds/copyTextureToTexture.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/encoding/cmds/copyTextureToTexture.spec.ts
new file mode 100644
index 0000000000..cd551f9b63
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/encoding/cmds/copyTextureToTexture.spec.ts
@@ -0,0 +1,94 @@
+export const description = `
+Tests limitations of copyTextureToTextures in compat mode.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import {
+ kAllTextureFormats,
+ kCompressedTextureFormats,
+ kTextureFormatInfo,
+} from '../../../../../format_info.js';
+import { CompatibilityTest } from '../../../../compatibility_test.js';
+
+export const g = makeTestGroup(CompatibilityTest);
+
+g.test('compressed')
+ .desc(`Tests that you can not call copyTextureToTexture with compressed textures in compat mode.`)
+ .params(u => u.combine('format', kCompressedTextureFormats))
+ .beforeAllSubcases(t => {
+ const { format } = t.params;
+ t.selectDeviceOrSkipTestCase([kTextureFormatInfo[format].feature]);
+ })
+ .fn(t => {
+ const { format } = t.params;
+
+ const { blockWidth, blockHeight } = kTextureFormatInfo[format];
+
+ const srcTexture = t.device.createTexture({
+ size: [blockWidth, blockHeight, 1],
+ format,
+ usage: GPUTextureUsage.COPY_SRC,
+ });
+ t.trackForCleanup(srcTexture);
+
+ const dstTexture = t.device.createTexture({
+ size: [blockWidth, blockHeight, 1],
+ format,
+ usage: GPUTextureUsage.COPY_DST,
+ });
+ t.trackForCleanup(dstTexture);
+
+ const encoder = t.device.createCommandEncoder();
+ encoder.copyTextureToTexture({ texture: srcTexture }, { texture: dstTexture }, [
+ blockWidth,
+ blockHeight,
+ 1,
+ ]);
+ t.expectGPUError('validation', () => {
+ encoder.finish();
+ });
+ });
+
+g.test('multisample')
+ .desc(`Test that you can not call copyTextureToTexture with multisample textures in compat mode.`)
+ .params(u =>
+ u
+ .beginSubcases()
+ .combine('format', kAllTextureFormats)
+ .filter(({ format }) => {
+ const info = kTextureFormatInfo[format];
+ return info.multisample && !info.feature;
+ })
+ )
+ .fn(t => {
+ const { format } = t.params;
+ const { blockWidth, blockHeight } = kTextureFormatInfo[format];
+
+ t.skipIfTextureFormatNotSupported(format as GPUTextureFormat);
+
+ const srcTexture = t.device.createTexture({
+ size: [blockWidth, blockHeight, 1],
+ format,
+ sampleCount: 4,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
+ });
+ t.trackForCleanup(srcTexture);
+
+ const dstTexture = t.device.createTexture({
+ size: [blockWidth, blockHeight, 1],
+ format,
+ sampleCount: 4,
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
+ });
+ t.trackForCleanup(dstTexture);
+
+ const encoder = t.device.createCommandEncoder();
+ encoder.copyTextureToTexture({ texture: srcTexture }, { texture: dstTexture }, [
+ blockWidth,
+ blockHeight,
+ 1,
+ ]);
+ t.expectGPUError('validation', () => {
+ encoder.finish();
+ });
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/render_pipeline/depth_stencil_state.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/render_pipeline/depth_stencil_state.spec.ts
new file mode 100644
index 0000000000..66045c2b8a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/render_pipeline/depth_stencil_state.spec.ts
@@ -0,0 +1,53 @@
+export const description = `
+Tests that depthBiasClamp must be zero in compat mode.
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { CompatibilityTest } from '../../../compatibility_test.js';
+
+export const g = makeTestGroup(CompatibilityTest);
+
+g.test('depthBiasClamp')
+ .desc('Tests that depthBiasClamp must be zero in compat mode.')
+ .params(u =>
+ u //
+ .combine('depthBiasClamp', [undefined, 0, 0.1, 1])
+ .combine('async', [false, true] as const)
+ )
+ .fn(t => {
+ const { depthBiasClamp, async } = t.params;
+
+ const module = t.device.createShaderModule({
+ code: `
+ @vertex fn vs() -> @builtin(position) vec4f {
+ return vec4f(0);
+ }
+
+ @fragment fn fs() -> @location(0) vec4f {
+ return vec4f(0);
+ }
+ `,
+ });
+
+ const pipelineDescriptor: GPURenderPipelineDescriptor = {
+ layout: 'auto',
+ vertex: {
+ module,
+ entryPoint: 'vs',
+ },
+ fragment: {
+ module,
+ entryPoint: 'fs',
+ targets: [{ format: 'rgba8unorm' }],
+ },
+ depthStencil: {
+ format: 'depth24plus',
+ depthWriteEnabled: true,
+ depthCompare: 'always',
+ ...(depthBiasClamp !== undefined && { depthBiasClamp }),
+ },
+ };
+
+ const success = !depthBiasClamp;
+ t.doCreateRenderPipelineTest(async, success, pipelineDescriptor);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/render_pipeline/shader_module.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/render_pipeline/shader_module.spec.ts
index abe2b063e7..56da7d355e 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/render_pipeline/shader_module.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/render_pipeline/shader_module.spec.ts
@@ -3,6 +3,7 @@ Tests limitations of createRenderPipeline related to shader modules in compat mo
`;
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { kCompatModeUnsupportedStorageTextureFormats } from '../../../../format_info.js';
import { CompatibilityTest } from '../../../compatibility_test.js';
export const g = makeTestGroup(CompatibilityTest);
@@ -72,3 +73,209 @@ Tests that you can not create a render pipeline with a shader module that uses s
!isValid
);
});
+
+g.test('sample_index')
+ .desc(
+ `
+Tests that you can not create a render pipeline with a shader module that uses sample_index in compat mode.
+
+- Test that a pipeline with a shader that uses sample_index fails.
+- Test that a pipeline that references a module that has a shader that uses sample_index
+ but the pipeline does not reference that shader succeeds.
+ `
+ )
+ .params(u =>
+ u.combine('entryPoint', ['fsWithoutSampleIndexUsage', 'fsWithSampleIndexUsage'] as const)
+ )
+ .fn(t => {
+ const { entryPoint } = t.params;
+
+ const module = t.device.createShaderModule({
+ code: `
+ @vertex fn vs() -> @builtin(position) vec4f {
+ return vec4f(1);
+ }
+ @fragment fn fsWithoutSampleIndexUsage() -> @location(0) vec4f {
+ return vec4f(0);
+ }
+ @fragment fn fsWithSampleIndexUsage(@builtin(sample_index) sampleIndex: u32) -> @location(0) vec4f {
+ _ = sampleIndex;
+ return vec4f(0);
+ }
+ `,
+ });
+
+ const pipelineDescriptor: GPURenderPipelineDescriptor = {
+ layout: 'auto',
+ vertex: {
+ module,
+ entryPoint: 'vs',
+ },
+ fragment: {
+ module,
+ entryPoint,
+ targets: [
+ {
+ format: 'rgba8unorm',
+ },
+ ],
+ },
+ multisample: {
+ count: 4,
+ },
+ };
+
+ const isValid = entryPoint === 'fsWithoutSampleIndexUsage';
+ t.expectGPUError(
+ 'validation',
+ () => t.device.createRenderPipeline(pipelineDescriptor),
+ !isValid
+ );
+ });
+
+g.test('interpolate')
+ .desc(
+ `
+Tests that you can not create a render pipeline with a shader module that uses interpolate(linear) nor interpolate(...,sample) in compat mode.
+
+- Test that a pipeline with a shader that uses interpolate(linear) or interpolate(sample) fails.
+- Test that a pipeline that references a module that has a shader that uses interpolate(linear/sample)
+ but the pipeline does not reference that shader succeeds.
+ `
+ )
+ .params(u =>
+ u
+ .combine('interpolate', [
+ '',
+ '@interpolate(linear)',
+ '@interpolate(linear, sample)',
+ '@interpolate(perspective, sample)',
+ ] as const)
+ .combine('entryPoint', [
+ 'fsWithoutInterpolationUsage',
+ 'fsWithInterpolationUsage1',
+ 'fsWithInterpolationUsage2',
+ 'fsWithInterpolationUsage3',
+ ] as const)
+ )
+ .fn(t => {
+ const { entryPoint, interpolate } = t.params;
+
+ const module = t.device.createShaderModule({
+ code: `
+ struct Vertex {
+ @builtin(position) pos: vec4f,
+ @location(0) ${interpolate} color : vec4f,
+ };
+ @vertex fn vs() -> Vertex {
+ var v: Vertex;
+ v.pos = vec4f(1);
+ v.color = vec4f(1);
+ return v;
+ }
+ @fragment fn fsWithoutInterpolationUsage() -> @location(0) vec4f {
+ return vec4f(1);
+ }
+ @fragment fn fsWithInterpolationUsage1(v: Vertex) -> @location(0) vec4f {
+ return vec4f(1);
+ }
+ @fragment fn fsWithInterpolationUsage2(v: Vertex) -> @location(0) vec4f {
+ return v.pos;
+ }
+ @fragment fn fsWithInterpolationUsage3(v: Vertex) -> @location(0) vec4f {
+ return v.color;
+ }
+ `,
+ });
+
+ const pipelineDescriptor: GPURenderPipelineDescriptor = {
+ layout: 'auto',
+ vertex: {
+ module,
+ entryPoint: 'vs',
+ },
+ fragment: {
+ module,
+ entryPoint,
+ targets: [
+ {
+ format: 'rgba8unorm',
+ },
+ ],
+ },
+ };
+
+ const isValid = entryPoint === 'fsWithoutInterpolationUsage' || interpolate === '';
+ t.expectGPUError(
+ 'validation',
+ () => t.device.createRenderPipeline(pipelineDescriptor),
+ !isValid
+ );
+ });
+
+g.test('unsupportedStorageTextureFormats,computePipeline')
+ .desc(
+ `
+Tests that you can not create a compute pipeline with unsupported storage texture formats in compat mode.
+ `
+ )
+ .params(u =>
+ u //
+ .combine('format', kCompatModeUnsupportedStorageTextureFormats)
+ .combine('async', [false, true] as const)
+ )
+ .fn(t => {
+ const { format, async } = t.params;
+
+ const module = t.device.createShaderModule({
+ code: `
+ @group(0) @binding(0) var s: texture_storage_2d<${format}, read>;
+ @compute @workgroup_size(1) fn cs() {
+ _ = textureLoad(s, vec2u(0));
+ }
+ `,
+ });
+
+ const pipelineDescriptor: GPUComputePipelineDescriptor = {
+ layout: 'auto',
+ compute: {
+ module,
+ entryPoint: 'cs',
+ },
+ };
+ t.doCreateComputePipelineTest(async, false, pipelineDescriptor);
+ });
+
+g.test('unsupportedStorageTextureFormats,renderPipeline')
+ .desc(
+ `
+Tests that you can not create a render pipeline with unsupported storage texture formats in compat mode.
+ `
+ )
+ .params(u =>
+ u //
+ .combine('format', kCompatModeUnsupportedStorageTextureFormats)
+ .combine('async', [false, true] as const)
+ )
+ .fn(t => {
+ const { format, async } = t.params;
+
+ const module = t.device.createShaderModule({
+ code: `
+ @group(0) @binding(0) var s: texture_storage_2d<${format}, read>;
+ @vertex fn vs() -> @builtin(position) vec4f {
+ _ = textureLoad(s, vec2u(0));
+ return vec4f(0);
+ }
+ `,
+ });
+
+ const pipelineDescriptor: GPURenderPipelineDescriptor = {
+ layout: 'auto',
+ vertex: {
+ module,
+ entryPoint: 'vs',
+ },
+ };
+ t.doCreateRenderPipelineTest(async, false, pipelineDescriptor);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/texture/createTexture.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/texture/createTexture.spec.ts
index 9f0d353268..58dcd41ec7 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/texture/createTexture.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/texture/createTexture.spec.ts
@@ -1,8 +1,13 @@
+/* eslint-disable prettier/prettier */
export const description = `
Tests that you can not use bgra8unorm-srgb in compat mode.
+Tests that textureBindingViewDimension must compatible with texture dimension
`;
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { kTextureDimensions, kTextureViewDimensions } from '../../../../capability_info.js';
+import { kColorTextureFormats, kCompatModeUnsupportedStorageTextureFormats, kTextureFormatInfo } from '../../../../format_info.js';
+import { getTextureDimensionFromView } from '../../../../util/texture/base.js';
import { CompatibilityTest } from '../../../compatibility_test.js';
export const g = makeTestGroup(CompatibilityTest);
@@ -39,3 +44,129 @@ g.test('unsupportedTextureViewFormats')
true
);
});
+
+g.test('invalidTextureBindingViewDimension')
+ .desc(
+ `Tests that you can not specify a textureBindingViewDimension that is incompatible with the texture's dimension.`
+ )
+ .params(u =>
+ u //
+ .combine('dimension', kTextureDimensions)
+ .combine('textureBindingViewDimension', kTextureViewDimensions)
+ )
+ .fn(t => {
+ const { dimension, textureBindingViewDimension } = t.params;
+ const depthOrArrayLayers = textureBindingViewDimension === '1d' || textureBindingViewDimension === '2d' ? 1 : 6;
+ const shouldError = getTextureDimensionFromView(textureBindingViewDimension) !== dimension;
+ t.expectGPUError(
+ 'validation',
+ () => {
+ const texture = t.device.createTexture({
+ size: [1, 1, depthOrArrayLayers],
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.TEXTURE_BINDING,
+ dimension,
+ textureBindingViewDimension,
+ } as GPUTextureDescriptor); // MAINTENANCE_TODO: remove cast once textureBindingViewDimension is added to IDL
+ t.trackForCleanup(texture);
+ },
+ shouldError
+ );
+ });
+
+g.test('depthOrArrayLayers_incompatible_with_textureBindingViewDimension')
+ .desc(
+ `Tests
+ * if textureBindingViewDimension is '2d' then depthOrArrayLayers must be 1
+ * if textureBindingViewDimension is 'cube' then depthOrArrayLayers must be 6
+ `
+ )
+ .params(u =>
+ u //
+ .combine('textureBindingViewDimension', ['2d', 'cube'])
+ .combine('depthOrArrayLayers', [1, 3, 6, 12])
+ )
+ .fn(t => {
+ const { textureBindingViewDimension, depthOrArrayLayers } = t.params;
+ const shouldError =
+ (textureBindingViewDimension === '2d' && depthOrArrayLayers !== 1) ||
+ (textureBindingViewDimension === 'cube' && depthOrArrayLayers !== 6);
+ t.expectGPUError(
+ 'validation',
+ () => {
+ const texture = t.device.createTexture({
+ size: [1, 1, depthOrArrayLayers],
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.TEXTURE_BINDING,
+ textureBindingViewDimension,
+ } as GPUTextureDescriptor); // MAINTENANCE_TODO: remove cast once textureBindingViewDimension is added to IDL
+ t.trackForCleanup(texture);
+ },
+ shouldError
+ );
+ });
+
+g.test('format_reinterpretation')
+ .desc(
+ `
+ Tests that you can not request different view formats when creating a texture.
+ For example, rgba8unorm can not be viewed as rgba8unorm-srgb
+ `
+ )
+ .params(u =>
+ u //
+ .combine('format', kColorTextureFormats as GPUTextureFormat[])
+ .filter(
+ ({ format }) =>
+ !!kTextureFormatInfo[format].baseFormat &&
+ kTextureFormatInfo[format].baseFormat !== format
+ )
+ )
+ .beforeAllSubcases(t => {
+ const info = kTextureFormatInfo[t.params.format];
+ t.skipIfTextureFormatNotSupported(t.params.format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+ })
+ .fn(t => {
+ const { format } = t.params;
+ const info = kTextureFormatInfo[format];
+
+ const formatPairs = [
+ { format, viewFormats: [info.baseFormat!] },
+ { format: info.baseFormat!, viewFormats: [format] },
+ { format, viewFormats: [format, info.baseFormat!] },
+ { format: info.baseFormat!, viewFormats: [format, info.baseFormat!] },
+ ];
+ for (const { format, viewFormats } of formatPairs) {
+ t.expectGPUError(
+ 'validation',
+ () => {
+ const texture = t.device.createTexture({
+ size: [info.blockWidth, info.blockHeight],
+ format,
+ viewFormats,
+ usage: GPUTextureUsage.TEXTURE_BINDING,
+ });
+ t.trackForCleanup(texture);
+ },
+ true
+ );
+ }
+ });
+
+g.test('unsupportedStorageTextureFormats')
+ .desc(`Tests that you can not create unsupported storage texture formats in compat mode.`)
+ .params(u => u.combine('format', kCompatModeUnsupportedStorageTextureFormats))
+ .fn(t => {
+ const { format } = t.params;
+ t.expectGPUError(
+ 'validation',
+ () =>
+ t.device.createTexture({
+ size: [1, 1, 1],
+ format,
+ usage: GPUTextureUsage.STORAGE_BINDING,
+ }),
+ true
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/constants.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/constants.ts
index c7a28cb837..60cdd63674 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/constants.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/constants.ts
@@ -60,3 +60,8 @@ export const GPUConst = {
export const kMaxUnsignedLongValue = 4294967295;
export const kMaxUnsignedLongLongValue = Number.MAX_SAFE_INTEGER;
+
+export const kInterpolationSampling = ['center', 'centroid', 'sample'] as const;
+export const kInterpolationType = ['perspective', 'linear', 'flat'] as const;
+export type InterpolationType = (typeof kInterpolationType)[number];
+export type InterpolationSampling = (typeof kInterpolationSampling)[number];
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/format_info.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/format_info.ts
index 566027714f..be1320d3cd 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/format_info.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/format_info.ts
@@ -1,5 +1,5 @@
import { keysOf } from '../common/util/data_tables.js';
-import { assert } from '../common/util/util.js';
+import { assert, unreachable } from '../common/util/util.js';
import { align } from './util/math.js';
import { ImageCopyType } from './util/texture/layout.js';
@@ -13,25 +13,26 @@ import { ImageCopyType } from './util/texture/layout.js';
* `formatTableWithDefaults`. This ensures keys are never missing, always explicitly `undefined`.
*
* All top-level keys must be defined here, or they won't be exposed at all.
+ * Documentation is also written here; this makes it propagate through to the end types.
*/
const kFormatUniversalDefaults = {
+ /** Texel block width. */
blockWidth: undefined,
+ /** Texel block height. */
blockHeight: undefined,
color: undefined,
depth: undefined,
stencil: undefined,
colorRender: undefined,
+ /** Whether the format can be used in a multisample texture. */
multisample: undefined,
+ /** Optional feature required to use this format, or `undefined` if none. */
feature: undefined,
+ /** The base format for srgb formats. Specified on both srgb and equivalent non-srgb formats. */
baseFormat: undefined,
- sampleType: undefined,
- copySrc: undefined,
- copyDst: undefined,
+ /** @deprecated Use `.color.bytes`, `.depth.bytes`, or `.stencil.bytes`. */
bytesPerBlock: undefined,
- renderable: false,
- renderTargetPixelByteCost: undefined,
- renderTargetComponentAlignment: undefined,
// IMPORTANT:
// Add new top-level keys both here and in TextureFormatInfo_TypeCheck.
@@ -67,382 +68,506 @@ function formatTableWithDefaults<Defaults extends {}, Table extends { readonly [
/** "plain color formats", plus rgb9e5ufloat. */
const kRegularTextureFormatInfo = formatTableWithDefaults({
- defaults: { blockWidth: 1, blockHeight: 1, copySrc: true, copyDst: true },
+ defaults: { blockWidth: 1, blockHeight: 1 },
table: {
// plain, 8 bits per component
r8unorm: {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 1 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 1,
+ },
colorRender: { blend: true, resolve: true, byteCost: 1, alignment: 1 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
r8snorm: {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 1 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 1,
+ },
multisample: false,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
r8uint: {
- color: { type: 'uint', copySrc: true, copyDst: true, storage: false, bytes: 1 },
+ color: {
+ type: 'uint',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 1,
+ },
colorRender: { blend: false, resolve: false, byteCost: 1, alignment: 1 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
r8sint: {
- color: { type: 'sint', copySrc: true, copyDst: true, storage: false, bytes: 1 },
+ color: {
+ type: 'sint',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 1,
+ },
colorRender: { blend: false, resolve: false, byteCost: 1, alignment: 1 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rg8unorm: {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 2 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 2,
+ },
colorRender: { blend: true, resolve: true, byteCost: 2, alignment: 1 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rg8snorm: {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 2 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 2,
+ },
multisample: false,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rg8uint: {
- color: { type: 'uint', copySrc: true, copyDst: true, storage: false, bytes: 2 },
+ color: {
+ type: 'uint',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 2,
+ },
colorRender: { blend: false, resolve: false, byteCost: 2, alignment: 1 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rg8sint: {
- color: { type: 'sint', copySrc: true, copyDst: true, storage: false, bytes: 2 },
+ color: {
+ type: 'sint',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 2,
+ },
colorRender: { blend: false, resolve: false, byteCost: 2, alignment: 1 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rgba8unorm: {
- color: { type: 'float', copySrc: true, copyDst: true, storage: true, bytes: 4 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: true,
+ readWriteStorage: false,
+ bytes: 4,
+ },
colorRender: { blend: true, resolve: true, byteCost: 8, alignment: 1 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
baseFormat: 'rgba8unorm',
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'rgba8unorm-srgb': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 4 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 4,
+ },
colorRender: { blend: true, resolve: true, byteCost: 8, alignment: 1 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
baseFormat: 'rgba8unorm',
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rgba8snorm: {
- color: { type: 'float', copySrc: true, copyDst: true, storage: true, bytes: 4 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: true,
+ readWriteStorage: false,
+ bytes: 4,
+ },
multisample: false,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rgba8uint: {
- color: { type: 'uint', copySrc: true, copyDst: true, storage: true, bytes: 4 },
+ color: {
+ type: 'uint',
+ copySrc: true,
+ copyDst: true,
+ storage: true,
+ readWriteStorage: false,
+ bytes: 4,
+ },
colorRender: { blend: false, resolve: false, byteCost: 4, alignment: 1 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rgba8sint: {
- color: { type: 'sint', copySrc: true, copyDst: true, storage: true, bytes: 4 },
+ color: {
+ type: 'sint',
+ copySrc: true,
+ copyDst: true,
+ storage: true,
+ readWriteStorage: false,
+ bytes: 4,
+ },
colorRender: { blend: false, resolve: false, byteCost: 4, alignment: 1 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
bgra8unorm: {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 4 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 4,
+ },
colorRender: { blend: true, resolve: true, byteCost: 8, alignment: 1 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
baseFormat: 'bgra8unorm',
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'bgra8unorm-srgb': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 4 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 4,
+ },
colorRender: { blend: true, resolve: true, byteCost: 8, alignment: 1 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
baseFormat: 'bgra8unorm',
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
// plain, 16 bits per component
r16uint: {
- color: { type: 'uint', copySrc: true, copyDst: true, storage: false, bytes: 2 },
+ color: {
+ type: 'uint',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 2,
+ },
colorRender: { blend: false, resolve: false, byteCost: 2, alignment: 2 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
r16sint: {
- color: { type: 'sint', copySrc: true, copyDst: true, storage: false, bytes: 2 },
+ color: {
+ type: 'sint',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 2,
+ },
colorRender: { blend: false, resolve: false, byteCost: 2, alignment: 2 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
r16float: {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 2 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 2,
+ },
colorRender: { blend: true, resolve: true, byteCost: 2, alignment: 2 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rg16uint: {
- color: { type: 'uint', copySrc: true, copyDst: true, storage: false, bytes: 4 },
+ color: {
+ type: 'uint',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 4,
+ },
colorRender: { blend: false, resolve: false, byteCost: 4, alignment: 2 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rg16sint: {
- color: { type: 'sint', copySrc: true, copyDst: true, storage: false, bytes: 4 },
+ color: {
+ type: 'sint',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 4,
+ },
colorRender: { blend: false, resolve: false, byteCost: 4, alignment: 2 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rg16float: {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 4 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 4,
+ },
colorRender: { blend: true, resolve: true, byteCost: 4, alignment: 2 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rgba16uint: {
- color: { type: 'uint', copySrc: true, copyDst: true, storage: true, bytes: 8 },
+ color: {
+ type: 'uint',
+ copySrc: true,
+ copyDst: true,
+ storage: true,
+ readWriteStorage: false,
+ bytes: 8,
+ },
colorRender: { blend: false, resolve: false, byteCost: 8, alignment: 2 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rgba16sint: {
- color: { type: 'sint', copySrc: true, copyDst: true, storage: true, bytes: 8 },
+ color: {
+ type: 'sint',
+ copySrc: true,
+ copyDst: true,
+ storage: true,
+ readWriteStorage: false,
+ bytes: 8,
+ },
colorRender: { blend: false, resolve: false, byteCost: 8, alignment: 2 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rgba16float: {
- color: { type: 'float', copySrc: true, copyDst: true, storage: true, bytes: 8 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: true,
+ readWriteStorage: false,
+ bytes: 8,
+ },
colorRender: { blend: true, resolve: true, byteCost: 8, alignment: 2 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
// plain, 32 bits per component
r32uint: {
- color: { type: 'uint', copySrc: true, copyDst: true, storage: true, bytes: 4 },
+ color: {
+ type: 'uint',
+ copySrc: true,
+ copyDst: true,
+ storage: true,
+ readWriteStorage: true,
+ bytes: 4,
+ },
colorRender: { blend: false, resolve: false, byteCost: 4, alignment: 4 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: false,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
r32sint: {
- color: { type: 'sint', copySrc: true, copyDst: true, storage: true, bytes: 4 },
+ color: {
+ type: 'sint',
+ copySrc: true,
+ copyDst: true,
+ storage: true,
+ readWriteStorage: true,
+ bytes: 4,
+ },
colorRender: { blend: false, resolve: false, byteCost: 4, alignment: 4 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: false,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
r32float: {
- color: { type: 'unfilterable-float', copySrc: true, copyDst: true, storage: true, bytes: 4 },
+ color: {
+ type: 'unfilterable-float',
+ copySrc: true,
+ copyDst: true,
+ storage: true,
+ readWriteStorage: true,
+ bytes: 4,
+ },
colorRender: { blend: false, resolve: false, byteCost: 4, alignment: 4 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rg32uint: {
- color: { type: 'uint', copySrc: true, copyDst: true, storage: true, bytes: 8 },
+ color: {
+ type: 'uint',
+ copySrc: true,
+ copyDst: true,
+ storage: true,
+ readWriteStorage: false,
+ bytes: 8,
+ },
colorRender: { blend: false, resolve: false, byteCost: 8, alignment: 4 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: false,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rg32sint: {
- color: { type: 'sint', copySrc: true, copyDst: true, storage: true, bytes: 8 },
+ color: {
+ type: 'sint',
+ copySrc: true,
+ copyDst: true,
+ storage: true,
+ readWriteStorage: false,
+ bytes: 8,
+ },
colorRender: { blend: false, resolve: false, byteCost: 8, alignment: 4 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: false,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rg32float: {
- color: { type: 'unfilterable-float', copySrc: true, copyDst: true, storage: true, bytes: 8 },
+ color: {
+ type: 'unfilterable-float',
+ copySrc: true,
+ copyDst: true,
+ storage: true,
+ readWriteStorage: false,
+ bytes: 8,
+ },
colorRender: { blend: false, resolve: false, byteCost: 8, alignment: 4 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: false,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rgba32uint: {
- color: { type: 'uint', copySrc: true, copyDst: true, storage: true, bytes: 16 },
+ color: {
+ type: 'uint',
+ copySrc: true,
+ copyDst: true,
+ storage: true,
+ readWriteStorage: false,
+ bytes: 16,
+ },
colorRender: { blend: false, resolve: false, byteCost: 16, alignment: 4 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: false,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rgba32sint: {
- color: { type: 'sint', copySrc: true, copyDst: true, storage: true, bytes: 16 },
+ color: {
+ type: 'sint',
+ copySrc: true,
+ copyDst: true,
+ storage: true,
+ readWriteStorage: false,
+ bytes: 16,
+ },
colorRender: { blend: false, resolve: false, byteCost: 16, alignment: 4 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: false,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rgba32float: {
- color: { type: 'unfilterable-float', copySrc: true, copyDst: true, storage: true, bytes: 16 },
+ color: {
+ type: 'unfilterable-float',
+ copySrc: true,
+ copyDst: true,
+ storage: true,
+ readWriteStorage: false,
+ bytes: 16,
+ },
colorRender: { blend: false, resolve: false, byteCost: 16, alignment: 4 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: false,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
// plain, mixed component width, 32 bits per texel
rgb10a2uint: {
- color: { type: 'uint', copySrc: true, copyDst: true, storage: false, bytes: 4 },
+ color: {
+ type: 'uint',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 4,
+ },
colorRender: { blend: false, resolve: false, byteCost: 8, alignment: 4 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rgb10a2unorm: {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 4 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 4,
+ },
colorRender: { blend: true, resolve: true, byteCost: 8, alignment: 4 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rg11b10ufloat: {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 4 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 4,
+ },
multisample: false,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
- renderTargetPixelByteCost: 8,
- renderTargetComponentAlignment: 4,
},
// packed
rgb9e5ufloat: {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 4 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 4,
+ },
multisample: false,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
},
@@ -452,24 +577,39 @@ const kRegularTextureFormatInfo = formatTableWithDefaults({
// because one aspect can be sized and one can be unsized. This should be cleaned up, but is kept
// this way during a migration phase.
const kSizedDepthStencilFormatInfo = formatTableWithDefaults({
- defaults: { blockWidth: 1, blockHeight: 1, multisample: true, copySrc: true, renderable: true },
+ defaults: { blockWidth: 1, blockHeight: 1, multisample: true },
table: {
stencil8: {
- stencil: { type: 'uint', copySrc: true, copyDst: true, storage: false, bytes: 1 },
- sampleType: 'uint',
- copyDst: true,
+ stencil: {
+ type: 'uint',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 1,
+ },
bytesPerBlock: 1,
},
depth16unorm: {
- depth: { type: 'depth', copySrc: true, copyDst: true, storage: false, bytes: 2 },
- sampleType: 'depth',
- copyDst: true,
+ depth: {
+ type: 'depth',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 2,
+ },
bytesPerBlock: 2,
},
depth32float: {
- depth: { type: 'depth', copySrc: true, copyDst: false, storage: false, bytes: 4 },
- sampleType: 'depth',
- copyDst: false,
+ depth: {
+ type: 'depth',
+ copySrc: true,
+ copyDst: false,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 4,
+ },
bytesPerBlock: 4,
},
},
@@ -478,28 +618,51 @@ const kUnsizedDepthStencilFormatInfo = formatTableWithDefaults({
defaults: { blockWidth: 1, blockHeight: 1, multisample: true },
table: {
depth24plus: {
- depth: { type: 'depth', copySrc: false, copyDst: false, storage: false, bytes: undefined },
- copySrc: false,
- copyDst: false,
- sampleType: 'depth',
- renderable: true,
+ depth: {
+ type: 'depth',
+ copySrc: false,
+ copyDst: false,
+ storage: false,
+ readWriteStorage: false,
+ bytes: undefined,
+ },
},
'depth24plus-stencil8': {
- depth: { type: 'depth', copySrc: false, copyDst: false, storage: false, bytes: undefined },
- stencil: { type: 'uint', copySrc: true, copyDst: true, storage: false, bytes: 1 },
- copySrc: false,
- copyDst: false,
- sampleType: 'depth',
- renderable: true,
+ depth: {
+ type: 'depth',
+ copySrc: false,
+ copyDst: false,
+ storage: false,
+ readWriteStorage: false,
+ bytes: undefined,
+ },
+ stencil: {
+ type: 'uint',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 1,
+ },
},
'depth32float-stencil8': {
- depth: { type: 'depth', copySrc: true, copyDst: false, storage: false, bytes: 4 },
- stencil: { type: 'uint', copySrc: true, copyDst: true, storage: false, bytes: 1 },
+ depth: {
+ type: 'depth',
+ copySrc: true,
+ copyDst: false,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 4,
+ },
+ stencil: {
+ type: 'uint',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 1,
+ },
feature: 'depth32float-stencil8',
- copySrc: false,
- copyDst: false,
- sampleType: 'depth',
- renderable: true,
},
},
} as const);
@@ -510,78 +673,173 @@ const kBCTextureFormatInfo = formatTableWithDefaults({
blockHeight: 4,
multisample: false,
feature: 'texture-compression-bc',
- sampleType: 'float',
- copySrc: true,
- copyDst: true,
},
table: {
'bc1-rgba-unorm': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 8 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 8,
+ },
baseFormat: 'bc1-rgba-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'bc1-rgba-unorm-srgb': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 8 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 8,
+ },
baseFormat: 'bc1-rgba-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'bc2-rgba-unorm': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'bc2-rgba-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'bc2-rgba-unorm-srgb': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'bc2-rgba-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'bc3-rgba-unorm': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'bc3-rgba-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'bc3-rgba-unorm-srgb': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'bc3-rgba-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'bc4-r-unorm': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 8 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 8,
+ },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'bc4-r-snorm': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 8 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 8,
+ },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'bc5-rg-unorm': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'bc5-rg-snorm': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'bc6h-rgb-ufloat': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'bc6h-rgb-float': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'bc7-rgba-unorm': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'bc7-rgba-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'bc7-rgba-unorm-srgb': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'bc7-rgba-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
@@ -594,59 +852,126 @@ const kETC2TextureFormatInfo = formatTableWithDefaults({
blockHeight: 4,
multisample: false,
feature: 'texture-compression-etc2',
- sampleType: 'float',
- copySrc: true,
- copyDst: true,
},
table: {
'etc2-rgb8unorm': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 8 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 8,
+ },
baseFormat: 'etc2-rgb8unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'etc2-rgb8unorm-srgb': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 8 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 8,
+ },
baseFormat: 'etc2-rgb8unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'etc2-rgb8a1unorm': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 8 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 8,
+ },
baseFormat: 'etc2-rgb8a1unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'etc2-rgb8a1unorm-srgb': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 8 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 8,
+ },
baseFormat: 'etc2-rgb8a1unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'etc2-rgba8unorm': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'etc2-rgba8unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'etc2-rgba8unorm-srgb': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'etc2-rgba8unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'eac-r11unorm': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 8 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 8,
+ },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'eac-r11snorm': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 8 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 8,
+ },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'eac-rg11unorm': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'eac-rg11snorm': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
},
@@ -656,22 +981,33 @@ const kASTCTextureFormatInfo = formatTableWithDefaults({
defaults: {
multisample: false,
feature: 'texture-compression-astc',
- sampleType: 'float',
- copySrc: true,
- copyDst: true,
},
table: {
'astc-4x4-unorm': {
blockWidth: 4,
blockHeight: 4,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-4x4-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'astc-4x4-unorm-srgb': {
blockWidth: 4,
blockHeight: 4,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-4x4-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
@@ -679,14 +1015,28 @@ const kASTCTextureFormatInfo = formatTableWithDefaults({
'astc-5x4-unorm': {
blockWidth: 5,
blockHeight: 4,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-5x4-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'astc-5x4-unorm-srgb': {
blockWidth: 5,
blockHeight: 4,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-5x4-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
@@ -694,14 +1044,28 @@ const kASTCTextureFormatInfo = formatTableWithDefaults({
'astc-5x5-unorm': {
blockWidth: 5,
blockHeight: 5,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-5x5-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'astc-5x5-unorm-srgb': {
blockWidth: 5,
blockHeight: 5,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-5x5-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
@@ -709,14 +1073,28 @@ const kASTCTextureFormatInfo = formatTableWithDefaults({
'astc-6x5-unorm': {
blockWidth: 6,
blockHeight: 5,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-6x5-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'astc-6x5-unorm-srgb': {
blockWidth: 6,
blockHeight: 5,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-6x5-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
@@ -724,14 +1102,28 @@ const kASTCTextureFormatInfo = formatTableWithDefaults({
'astc-6x6-unorm': {
blockWidth: 6,
blockHeight: 6,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-6x6-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'astc-6x6-unorm-srgb': {
blockWidth: 6,
blockHeight: 6,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-6x6-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
@@ -739,14 +1131,28 @@ const kASTCTextureFormatInfo = formatTableWithDefaults({
'astc-8x5-unorm': {
blockWidth: 8,
blockHeight: 5,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-8x5-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'astc-8x5-unorm-srgb': {
blockWidth: 8,
blockHeight: 5,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-8x5-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
@@ -754,14 +1160,28 @@ const kASTCTextureFormatInfo = formatTableWithDefaults({
'astc-8x6-unorm': {
blockWidth: 8,
blockHeight: 6,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-8x6-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'astc-8x6-unorm-srgb': {
blockWidth: 8,
blockHeight: 6,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-8x6-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
@@ -769,14 +1189,28 @@ const kASTCTextureFormatInfo = formatTableWithDefaults({
'astc-8x8-unorm': {
blockWidth: 8,
blockHeight: 8,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-8x8-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'astc-8x8-unorm-srgb': {
blockWidth: 8,
blockHeight: 8,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-8x8-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
@@ -784,14 +1218,28 @@ const kASTCTextureFormatInfo = formatTableWithDefaults({
'astc-10x5-unorm': {
blockWidth: 10,
blockHeight: 5,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-10x5-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'astc-10x5-unorm-srgb': {
blockWidth: 10,
blockHeight: 5,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-10x5-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
@@ -799,14 +1247,28 @@ const kASTCTextureFormatInfo = formatTableWithDefaults({
'astc-10x6-unorm': {
blockWidth: 10,
blockHeight: 6,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-10x6-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'astc-10x6-unorm-srgb': {
blockWidth: 10,
blockHeight: 6,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-10x6-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
@@ -814,14 +1276,28 @@ const kASTCTextureFormatInfo = formatTableWithDefaults({
'astc-10x8-unorm': {
blockWidth: 10,
blockHeight: 8,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-10x8-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'astc-10x8-unorm-srgb': {
blockWidth: 10,
blockHeight: 8,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-10x8-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
@@ -829,14 +1305,28 @@ const kASTCTextureFormatInfo = formatTableWithDefaults({
'astc-10x10-unorm': {
blockWidth: 10,
blockHeight: 10,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-10x10-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'astc-10x10-unorm-srgb': {
blockWidth: 10,
blockHeight: 10,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-10x10-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
@@ -844,14 +1334,28 @@ const kASTCTextureFormatInfo = formatTableWithDefaults({
'astc-12x10-unorm': {
blockWidth: 12,
blockHeight: 10,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-12x10-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'astc-12x10-unorm-srgb': {
blockWidth: 12,
blockHeight: 10,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-12x10-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
@@ -859,14 +1363,28 @@ const kASTCTextureFormatInfo = formatTableWithDefaults({
'astc-12x12-unorm': {
blockWidth: 12,
blockHeight: 12,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-12x12-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'astc-12x12-unorm-srgb': {
blockWidth: 12,
blockHeight: 12,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-12x12-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
@@ -921,13 +1439,6 @@ const kASTCTextureFormatInfo = formatTableWithDefaults({
export const kRenderableColorTextureFormats = kRegularTextureFormats.filter(
v => kColorTextureFormatInfo[v].colorRender
);
-assert(
- kRenderableColorTextureFormats.every(
- f =>
- kAllTextureFormatInfo[f].renderTargetComponentAlignment !== undefined &&
- kAllTextureFormatInfo[f].renderTargetPixelByteCost !== undefined
- )
-);
/** Per-GPUTextureFormat-per-aspect info. */
interface TextureFormatAspectInfo {
@@ -937,6 +1448,8 @@ interface TextureFormatAspectInfo {
copyDst: boolean;
/** Whether the aspect can be used as `STORAGE`. */
storage: boolean;
+ /** Whether the aspect can be used as `STORAGE` with `read-write` storage texture access. */
+ readWriteStorage: boolean;
/** The "texel block copy footprint" of one texel block; `undefined` if the aspect is unsized. */
bytes: number | undefined;
}
@@ -962,34 +1475,15 @@ interface TextureFormatStencilAspectInfo extends TextureFormatAspectInfo {
* This is not actually the type of values in kTextureFormatInfo; that type is fully const
* so that it can be narrowed very precisely at usage sites by the compiler.
* This type exists only as a type check on the inferred type of kTextureFormatInfo.
- * Documentation is also written here, but not actually visible to the IDE.
*/
type TextureFormatInfo_TypeCheck = {
- /** Texel block width. */
blockWidth: number;
- /** Texel block height. */
blockHeight: number;
- /** Whether the format can be used in a multisample texture. */
multisample: boolean;
- /** The base format for srgb formats. Specified on both srgb and equivalent non-srgb formats. */
baseFormat: GPUTextureFormat | undefined;
- /** Optional feature required to use this format, or `undefined` if none. */
feature: GPUFeatureName | undefined;
- /** @deprecated */
- sampleType: GPUTextureSampleType;
- /** @deprecated */
- copySrc: boolean;
- /** @deprecated */
- copyDst: boolean;
- /** @deprecated */
bytesPerBlock: number | undefined;
- /** @deprecated */
- renderable: boolean;
- /** @deprecated */
- renderTargetPixelByteCost: number | undefined;
- /** @deprecated */
- renderTargetComponentAlignment: number | undefined;
// IMPORTANT:
// Add new top-level keys both here and in kUniversalDefaults.
@@ -1043,10 +1537,6 @@ const kTextureFormatInfo_TypeCheck: {
readonly [F in GPUTextureFormat]: TextureFormatInfo_TypeCheck;
} = kTextureFormatInfo;
-/** List of all GPUTextureFormat values. */
-// MAINTENANCE_TODO: dedup with kAllTextureFormats
-export const kTextureFormats: readonly GPUTextureFormat[] = keysOf(kAllTextureFormatInfo);
-
/** Valid GPUTextureFormats for `copyExternalImageToTexture`, by spec. */
export const kValidTextureFormatsForCopyE2T = [
'r8unorm',
@@ -1170,6 +1660,35 @@ export function resolvePerAspectFormat(
}
/**
+ * @returns the sample type of the specified aspect of the specified format.
+ */
+export function sampleTypeForFormatAndAspect(
+ format: GPUTextureFormat,
+ aspect: GPUTextureAspect
+): 'uint' | 'depth' | 'float' | 'sint' | 'unfilterable-float' {
+ const info = kTextureFormatInfo[format];
+ if (info.color) {
+ assert(aspect === 'all', `color format ${format} used with aspect ${aspect}`);
+ return info.color.type;
+ } else if (info.depth && info.stencil) {
+ if (aspect === 'depth-only') {
+ return info.depth.type;
+ } else if (aspect === 'stencil-only') {
+ return info.stencil.type;
+ } else {
+ unreachable(`depth-stencil format ${format} used with aspect ${aspect}`);
+ }
+ } else if (info.depth) {
+ assert(aspect !== 'stencil-only', `depth-only format ${format} used with aspect ${aspect}`);
+ return info.depth.type;
+ } else if (info.stencil) {
+ assert(aspect !== 'depth-only', `stencil-only format ${format} used with aspect ${aspect}`);
+ return info.stencil.type;
+ }
+ unreachable();
+}
+
+/**
* Gets all copyable aspects for copies between texture and buffer for specified depth/stencil format and copy type, by spec.
*/
export function depthStencilFormatCopyableAspects(
@@ -1229,8 +1748,12 @@ export function textureDimensionAndFormatCompatible(
*
* This function may need to be generalized to use `baseFormat` from `kTextureFormatInfo`.
*/
-export function viewCompatible(a: GPUTextureFormat, b: GPUTextureFormat): boolean {
- return a === b || a + '-srgb' === b || b + '-srgb' === a;
+export function viewCompatible(
+ compatibilityMode: boolean,
+ a: GPUTextureFormat,
+ b: GPUTextureFormat
+): boolean {
+ return compatibilityMode ? a === b : a === b || a + '-srgb' === b || b + '-srgb' === a;
}
export function getFeaturesForFormats<T>(
@@ -1250,7 +1773,29 @@ export function isCompressedTextureFormat(format: GPUTextureFormat) {
return format in kCompressedTextureFormatInfo;
}
-export const kFeaturesForFormats = getFeaturesForFormats(kTextureFormats);
+export const kCompatModeUnsupportedStorageTextureFormats: readonly GPUTextureFormat[] = [
+ 'rg32float',
+ 'rg32sint',
+ 'rg32uint',
+] as const;
+
+export function isTextureFormatUsableAsStorageFormat(
+ format: GPUTextureFormat,
+ isCompatibilityMode: boolean
+) {
+ if (isCompatibilityMode) {
+ if (kCompatModeUnsupportedStorageTextureFormats.indexOf(format) >= 0) {
+ return false;
+ }
+ }
+ return !!kTextureFormatInfo[format].color?.storage;
+}
+
+export function isRegularTextureFormat(format: GPUTextureFormat) {
+ return format in kRegularTextureFormatInfo;
+}
+
+export const kFeaturesForFormats = getFeaturesForFormats(kAllTextureFormats);
/**
* Given an array of texture formats return the number of bytes per sample.
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/gpu_test.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/gpu_test.ts
index f9ef1f2f06..fab1844c3c 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/gpu_test.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/gpu_test.ts
@@ -9,6 +9,7 @@ import {
TestParams,
} from '../common/framework/fixture.js';
import { globalTestConfig } from '../common/framework/test_config.js';
+import { getGPU } from '../common/util/navigator_gpu.js';
import {
assert,
makeValueTestVariant,
@@ -20,7 +21,13 @@ import {
unreachable,
} from '../common/util/util.js';
-import { getDefaultLimits, kLimits, kQueryTypeInfo } from './capability_info.js';
+import {
+ getDefaultLimits,
+ kLimits,
+ kQueryTypeInfo,
+ WGSLLanguageFeature,
+} from './capability_info.js';
+import { InterpolationType, InterpolationSampling } from './constants.js';
import {
kTextureFormatInfo,
kEncodableTextureFormats,
@@ -29,6 +36,7 @@ import {
EncodableTextureFormat,
isCompressedTextureFormat,
ColorTextureFormat,
+ isTextureFormatUsableAsStorageFormat,
} from './format_info.js';
import { makeBufferWithContents } from './util/buffer.js';
import { checkElementsEqual, checkElementsBetween } from './util/check_contents.js';
@@ -52,7 +60,7 @@ import {
textureContentIsOKByT2B,
} from './util/texture/texture_ok.js';
import { createTextureFromTexelView, createTextureFromTexelViews } from './util/texture.js';
-import { reifyOrigin3D } from './util/unions.js';
+import { reifyExtent3D, reifyOrigin3D } from './util/unions.js';
const devicePool = new DevicePool();
@@ -245,6 +253,56 @@ export class GPUTestSubcaseBatchState extends SubcaseBatchState {
}
}
}
+
+ skipIfTextureFormatNotUsableAsStorageTexture(...formats: (GPUTextureFormat | undefined)[]) {
+ for (const format of formats) {
+ if (format && !isTextureFormatUsableAsStorageFormat(format, this.isCompatibility)) {
+ this.skip(`Texture with ${format} is not usable as a storage texture`);
+ }
+ }
+ }
+
+ /**
+ * Skips test if the given interpolation type or sampling is not supported.
+ */
+ skipIfInterpolationTypeOrSamplingNotSupported({
+ type,
+ sampling,
+ }: {
+ type?: InterpolationType;
+ sampling?: InterpolationSampling;
+ }) {
+ if (this.isCompatibility) {
+ this.skipIf(
+ type === 'linear',
+ 'interpolation type linear is not supported in compatibility mode'
+ );
+ this.skipIf(
+ sampling === 'sample',
+ 'interpolation type linear is not supported in compatibility mode'
+ );
+ }
+ }
+
+ /** Skips this test case if the `langFeature` is *not* supported. */
+ skipIfLanguageFeatureNotSupported(langFeature: WGSLLanguageFeature) {
+ if (!this.hasLanguageFeature(langFeature)) {
+ this.skip(`WGSL language feature '${langFeature}' is not supported`);
+ }
+ }
+
+ /** Skips this test case if the `langFeature` is supported. */
+ skipIfLanguageFeatureSupported(langFeature: WGSLLanguageFeature) {
+ if (this.hasLanguageFeature(langFeature)) {
+ this.skip(`WGSL language feature '${langFeature}' is supported`);
+ }
+ }
+
+ /** returns true iff the `langFeature` is supported */
+ hasLanguageFeature(langFeature: WGSLLanguageFeature) {
+ const lf = getGPU(this.recorder).wgslLanguageFeatures;
+ return lf !== undefined && lf.has(langFeature);
+ }
}
/**
@@ -420,6 +478,26 @@ export class GPUTestBase extends Fixture<GPUTestSubcaseBatchState> {
}
}
+ /** Skips this test case if the `langFeature` is *not* supported. */
+ skipIfLanguageFeatureNotSupported(langFeature: WGSLLanguageFeature) {
+ if (!this.hasLanguageFeature(langFeature)) {
+ this.skip(`WGSL language feature '${langFeature}' is not supported`);
+ }
+ }
+
+ /** Skips this test case if the `langFeature` is supported. */
+ skipIfLanguageFeatureSupported(langFeature: WGSLLanguageFeature) {
+ if (this.hasLanguageFeature(langFeature)) {
+ this.skip(`WGSL language feature '${langFeature}' is supported`);
+ }
+ }
+
+ /** returns true iff the `langFeature` is supported */
+ hasLanguageFeature(langFeature: WGSLLanguageFeature) {
+ const lf = getGPU(this.rec).wgslLanguageFeatures;
+ return lf !== undefined && lf.has(langFeature);
+ }
+
/**
* Expect a GPUBuffer's contents to pass the provided check.
*
@@ -730,7 +808,8 @@ export class GPUTestBase extends Fixture<GPUTestSubcaseBatchState> {
slice = 0,
layout,
generateWarningOnly = false,
- checkElementsBetweenFn = (act, [a, b]) => checkElementsBetween(act, [i => a[i], i => b[i]]),
+ checkElementsBetweenFn = (act, [a, b]) =>
+ checkElementsBetween(act, [i => a[i] as number, i => b[i] as number]),
}: {
exp: [TypedArrayBufferView, TypedArrayBufferView];
slice?: number;
@@ -757,24 +836,32 @@ export class GPUTestBase extends Fixture<GPUTestSubcaseBatchState> {
/**
* Emulate a texture to buffer copy by using a compute shader
- * to load texture value of a single pixel and write to a storage buffer.
- * For sample count == 1, the buffer contains only one value of the sample.
- * For sample count > 1, the buffer contains (N = sampleCount) values sorted
+ * to load texture values of a subregion of a 2d texture and write to a storage buffer.
+ * For sample count == 1, the buffer contains extent[0] * extent[1] of the sample.
+ * For sample count > 1, the buffer contains extent[0] * extent[1] * (N = sampleCount) values sorted
* in the order of their sample index [0, sampleCount - 1]
*
* This can be useful when the texture to buffer copy is not available to the texture format
* e.g. (depth24plus), or when the texture is multisampled.
*
- * MAINTENANCE_TODO: extend to read multiple pixels with given origin and size.
+ * MAINTENANCE_TODO: extend texture dimension to 1d and 3d.
*
* @returns storage buffer containing the copied value from the texture.
*/
- copySinglePixelTextureToBufferUsingComputePass(
+ copy2DTextureToBufferUsingComputePass(
type: ScalarType,
componentCount: number,
textureView: GPUTextureView,
- sampleCount: number
+ sampleCount: number = 1,
+ extent_: GPUExtent3D = [1, 1, 1],
+ origin_: GPUOrigin3D = [0, 0, 0]
): GPUBuffer {
+ const origin = reifyOrigin3D(origin_);
+ const extent = reifyExtent3D(extent_);
+ const width = extent.width;
+ const height = extent.height;
+ const kWorkgroupSizeX = 8;
+ const kWorkgroupSizeY = 8;
const textureSrcCode =
sampleCount === 1
? `@group(0) @binding(0) var src: texture_2d<${type}>;`
@@ -787,13 +874,24 @@ export class GPUTestBase extends Fixture<GPUTestSubcaseBatchState> {
${textureSrcCode}
@group(0) @binding(1) var<storage, read_write> dst : Buffer;
- @compute @workgroup_size(1) fn main() {
- var coord = vec2<i32>(0, 0);
- for (var sampleIndex = 0; sampleIndex < ${sampleCount};
+ struct Params {
+ origin: vec2u,
+ extent: vec2u,
+ };
+ @group(0) @binding(2) var<uniform> params : Params;
+
+ @compute @workgroup_size(${kWorkgroupSizeX}, ${kWorkgroupSizeY}, 1) fn main(@builtin(global_invocation_id) id : vec3u) {
+ let boundary = params.origin + params.extent;
+ let coord = params.origin + id.xy;
+ if (any(coord >= boundary)) {
+ return;
+ }
+ let offset = (id.x + id.y * params.extent.x) * ${componentCount} * ${sampleCount};
+ for (var sampleIndex = 0u; sampleIndex < ${sampleCount};
sampleIndex = sampleIndex + 1) {
- let o = sampleIndex * ${componentCount};
- let v = textureLoad(src, coord, sampleIndex);
- for (var component = 0; component < ${componentCount}; component = component + 1) {
+ let o = offset + sampleIndex * ${componentCount};
+ let v = textureLoad(src, coord.xy, sampleIndex);
+ for (var component = 0u; component < ${componentCount}; component = component + 1) {
dst.data[o + component] = v[component];
}
}
@@ -810,11 +908,16 @@ export class GPUTestBase extends Fixture<GPUTestSubcaseBatchState> {
});
const storageBuffer = this.device.createBuffer({
- size: sampleCount * type.size * componentCount,
+ size: sampleCount * type.size * componentCount * width * height,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC,
});
this.trackForCleanup(storageBuffer);
+ const uniformBuffer = this.makeBufferWithContents(
+ new Uint32Array([origin.x, origin.y, width, height]),
+ GPUBufferUsage.UNIFORM
+ );
+
const uniformBindGroup = this.device.createBindGroup({
layout: computePipeline.getBindGroupLayout(0),
entries: [
@@ -828,6 +931,12 @@ export class GPUTestBase extends Fixture<GPUTestSubcaseBatchState> {
buffer: storageBuffer,
},
},
+ {
+ binding: 2,
+ resource: {
+ buffer: uniformBuffer,
+ },
+ },
],
});
@@ -835,7 +944,11 @@ export class GPUTestBase extends Fixture<GPUTestSubcaseBatchState> {
const pass = encoder.beginComputePass();
pass.setPipeline(computePipeline);
pass.setBindGroup(0, uniformBindGroup);
- pass.dispatchWorkgroups(1);
+ pass.dispatchWorkgroups(
+ Math.floor((width + kWorkgroupSizeX - 1) / kWorkgroupSizeX),
+ Math.floor((height + kWorkgroupSizeY - 1) / kWorkgroupSizeY),
+ 1
+ );
pass.end();
this.device.queue.submit([encoder.finish()]);
@@ -1081,11 +1194,17 @@ export class GPUTest extends GPUTestBase {
this.mismatchedProvider = await this.sharedState.acquireMismatchedProvider();
}
+ /** GPUAdapter that the device was created from. */
+ get adapter(): GPUAdapter {
+ assert(this.provider !== undefined, 'internal error: DeviceProvider missing');
+ return this.provider.adapter;
+ }
+
/**
* GPUDevice for the test to use.
*/
override get device(): GPUDevice {
- assert(this.provider !== undefined, 'internal error: GPUDevice missing?');
+ assert(this.provider !== undefined, 'internal error: DeviceProvider missing');
return this.provider.device;
}
@@ -1237,8 +1356,10 @@ export interface TextureTestMixinType {
): Generator<Required<GPUOrigin3DDict>>;
}
+type PipelineType = '2d' | '2d-array';
+
type ImageCopyTestResources = {
- pipeline: GPURenderPipeline;
+ pipelineByPipelineType: Map<PipelineType, GPURenderPipeline>;
};
const s_deviceToResourcesMap = new WeakMap<GPUDevice, ImageCopyTestResources>();
@@ -1246,8 +1367,23 @@ const s_deviceToResourcesMap = new WeakMap<GPUDevice, ImageCopyTestResources>();
/**
* Gets a (cached) pipeline to render a texture to an rgba8unorm texture
*/
-function getPipelineToRenderTextureToRGB8UnormTexture(device: GPUDevice) {
+function getPipelineToRenderTextureToRGB8UnormTexture(
+ device: GPUDevice,
+ texture: GPUTexture,
+ isCompatibility: boolean
+) {
if (!s_deviceToResourcesMap.has(device)) {
+ s_deviceToResourcesMap.set(device, {
+ pipelineByPipelineType: new Map<PipelineType, GPURenderPipeline>(),
+ });
+ }
+
+ const { pipelineByPipelineType } = s_deviceToResourcesMap.get(device)!;
+ const pipelineType: PipelineType =
+ isCompatibility && texture.depthOrArrayLayers > 1 ? '2d-array' : '2d';
+ if (!pipelineByPipelineType.get(pipelineType)) {
+ const [textureType, layerCode] =
+ pipelineType === '2d' ? ['texture_2d', ''] : ['texture_2d_array', ', uni.baseArrayLayer'];
const module = device.createShaderModule({
code: `
struct VSOutput {
@@ -1255,6 +1391,10 @@ function getPipelineToRenderTextureToRGB8UnormTexture(device: GPUDevice) {
@location(0) texcoord: vec2f,
};
+ struct Uniforms {
+ baseArrayLayer: u32,
+ };
+
@vertex fn vs(
@builtin(vertex_index) vertexIndex : u32
) -> VSOutput {
@@ -1275,10 +1415,11 @@ function getPipelineToRenderTextureToRGB8UnormTexture(device: GPUDevice) {
}
@group(0) @binding(0) var ourSampler: sampler;
- @group(0) @binding(1) var ourTexture: texture_2d<f32>;
+ @group(0) @binding(1) var ourTexture: ${textureType}<f32>;
+ @group(0) @binding(2) var<uniform> uni: Uniforms;
@fragment fn fs(fsInput: VSOutput) -> @location(0) vec4f {
- return textureSample(ourTexture, ourSampler, fsInput.texcoord);
+ return textureSample(ourTexture, ourSampler, fsInput.texcoord${layerCode});
}
`,
});
@@ -1294,10 +1435,10 @@ function getPipelineToRenderTextureToRGB8UnormTexture(device: GPUDevice) {
targets: [{ format: 'rgba8unorm' }],
},
});
- s_deviceToResourcesMap.set(device, { pipeline });
+ pipelineByPipelineType.set(pipelineType, pipeline);
}
- const { pipeline } = s_deviceToResourcesMap.get(device)!;
- return pipeline;
+ const pipeline = pipelineByPipelineType.get(pipelineType)!;
+ return { pipelineType, pipeline };
}
type LinearCopyParameters = {
@@ -1441,7 +1582,11 @@ export function TextureTestMixin<F extends FixtureClass<GPUTest>>(
// Render every layer of both textures at mipLevel to an rgba8unorm texture
// that matches the size of the mipLevel. After each render, copy the
// result to a buffer and expect the results from both textures to match.
- const pipeline = getPipelineToRenderTextureToRGB8UnormTexture(this.device);
+ const { pipelineType, pipeline } = getPipelineToRenderTextureToRGB8UnormTexture(
+ this.device,
+ actualTexture,
+ this.isCompatibility
+ );
const readbackPromisesPerTexturePerLayer = [actualTexture, expectedTexture].map(
(texture, ndx) => {
const attachmentSize = virtualMipSize('2d', [texture.width, texture.height, 1], mipLevel);
@@ -1457,24 +1602,45 @@ export function TextureTestMixin<F extends FixtureClass<GPUTest>>(
const numLayers = texture.depthOrArrayLayers;
const readbackPromisesPerLayer = [];
+
+ const uniformBuffer = this.device.createBuffer({
+ size: 4,
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
+ });
+ this.trackForCleanup(uniformBuffer);
+
for (let layer = 0; layer < numLayers; ++layer) {
+ const viewDescriptor: GPUTextureViewDescriptor = {
+ baseMipLevel: mipLevel,
+ mipLevelCount: 1,
+ ...(!this.isCompatibility && {
+ baseArrayLayer: layer,
+ arrayLayerCount: 1,
+ }),
+ dimension: pipelineType,
+ };
+
const bindGroup = this.device.createBindGroup({
layout: pipeline.getBindGroupLayout(0),
entries: [
{ binding: 0, resource: sampler },
{
binding: 1,
- resource: texture.createView({
- baseMipLevel: mipLevel,
- mipLevelCount: 1,
- baseArrayLayer: layer,
- arrayLayerCount: 1,
- dimension: '2d',
- }),
+ resource: texture.createView(viewDescriptor),
},
+ ...(pipelineType === '2d-array'
+ ? [
+ {
+ binding: 2,
+ resource: { buffer: uniformBuffer },
+ },
+ ]
+ : []),
],
});
+ this.device.queue.writeBuffer(uniformBuffer, 0, new Uint32Array([layer]));
+
const encoder = this.device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/idl/constructable.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/idl/constructable.spec.ts
new file mode 100644
index 0000000000..8fd9d08d3c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/idl/constructable.spec.ts
@@ -0,0 +1,54 @@
+export const description = `
+Test that constructable WebGPU objects are actually constructable.
+`;
+
+import { makeTestGroup } from './../../common/framework/test_group.js';
+import { IDLTest } from './idl_test.js';
+
+export const g = makeTestGroup(IDLTest);
+
+g.test('gpu_errors')
+ .desc('tests that GPUErrors are constructable')
+ .params(u =>
+ u.combine('errorType', [
+ 'GPUInternalError',
+ 'GPUOutOfMemoryError',
+ 'GPUValidationError',
+ ] as const)
+ )
+ .fn(t => {
+ const { errorType } = t.params;
+ const Ctor = globalThis[errorType];
+ const msg = 'this is a test';
+ const error = new Ctor(msg);
+ t.expect(error.message === msg);
+ });
+
+const pipelineErrorOptions: GPUPipelineErrorInit[] = [
+ { reason: 'validation' },
+ { reason: 'internal' },
+];
+
+g.test('pipeline_errors')
+ .desc('tests that GPUPipelineError is constructable')
+ .params(u =>
+ u //
+ .combine('msg', [undefined, 'some msg'])
+ .combine('options', pipelineErrorOptions)
+ )
+ .fn(t => {
+ const { msg, options } = t.params;
+ const error = new GPUPipelineError(msg, options);
+ const expectedMsg = msg || '';
+ t.expect(error.message === expectedMsg);
+ t.expect(error.reason === options.reason);
+ });
+
+g.test('uncaptured_error_event')
+ .desc('tests that GPUUncapturedErrorEvent is constructable')
+ .fn(t => {
+ const msg = 'this is a test';
+ const error = new GPUValidationError(msg);
+ const event = new GPUUncapturedErrorEvent('uncapturedError', { error });
+ t.expect(event.error === error);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/listing_meta.json b/dom/webgpu/tests/cts/checkout/src/webgpu/listing_meta.json
index f9caeefc6e..6ee25813f4 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/listing_meta.json
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/listing_meta.json
@@ -1,5 +1,5 @@
{
- "_comment": "SEMI AUTO-GENERATED: Please read docs/adding_timing_metadata.md.",
+ "_comment": "SEMI AUTO-GENERATED. This list is NOT exhaustive. Please read docs/adding_timing_metadata.md.",
"webgpu:api,operation,adapter,requestAdapter:requestAdapter:*": { "subcaseMS": 152.083 },
"webgpu:api,operation,adapter,requestAdapter:requestAdapter_no_parameters:*": { "subcaseMS": 384.601 },
"webgpu:api,operation,adapter,requestAdapterInfo:adapter_info:*": { "subcaseMS": 136.601 },
@@ -9,6 +9,7 @@
"webgpu:api,operation,adapter,requestDevice:features,unknown:*": { "subcaseMS": 13.600 },
"webgpu:api,operation,adapter,requestDevice:invalid:*": { "subcaseMS": 27.801 },
"webgpu:api,operation,adapter,requestDevice:limit,better_than_supported:*": { "subcaseMS": 3.614 },
+ "webgpu:api,operation,adapter,requestDevice:limit,out_of_range:*": { "subcaseMS": 1.000 },
"webgpu:api,operation,adapter,requestDevice:limit,worse_than_default:*": { "subcaseMS": 6.711 },
"webgpu:api,operation,adapter,requestDevice:limits,supported:*": { "subcaseMS": 4.579 },
"webgpu:api,operation,adapter,requestDevice:limits,unknown:*": { "subcaseMS": 0.601 },
@@ -93,6 +94,7 @@
"webgpu:api,operation,memory_sync,buffer,single_buffer:two_draws_in_the_same_render_pass:*": { "subcaseMS": 4.925 },
"webgpu:api,operation,memory_sync,buffer,single_buffer:wr:*": { "subcaseMS": 18.296 },
"webgpu:api,operation,memory_sync,buffer,single_buffer:ww:*": { "subcaseMS": 18.802 },
+ "webgpu:api,operation,memory_sync,texture,readonly_depth_stencil:sampling_while_testing:*": { "subcaseMS": 50.000 },
"webgpu:api,operation,memory_sync,texture,same_subresource:rw,single_pass,load_resolve:*": { "subcaseMS": 1.200 },
"webgpu:api,operation,memory_sync,texture,same_subresource:rw,single_pass,load_store:*": { "subcaseMS": 14.200 },
"webgpu:api,operation,memory_sync,texture,same_subresource:rw:*": { "subcaseMS": 10.908 },
@@ -108,8 +110,11 @@
"webgpu:api,operation,pipeline,default_layout:layout:*": { "subcaseMS": 11.500 },
"webgpu:api,operation,queue,writeBuffer:array_types:*": { "subcaseMS": 12.032 },
"webgpu:api,operation,queue,writeBuffer:multiple_writes_at_different_offsets_and_sizes:*": { "subcaseMS": 2.087 },
+ "webgpu:api,operation,reflection:buffer_creation_from_reflection:*": { "subcaseMS": 0.800 },
"webgpu:api,operation,reflection:buffer_reflection_attributes:*": { "subcaseMS": 0.800 },
+ "webgpu:api,operation,reflection:query_set_creation_from_reflection:*": { "subcaseMS": 0.634 },
"webgpu:api,operation,reflection:query_set_reflection_attributes:*": { "subcaseMS": 0.634 },
+ "webgpu:api,operation,reflection:texture_creation_from_reflection:*": { "subcaseMS": 1.829 },
"webgpu:api,operation,reflection:texture_reflection_attributes:*": { "subcaseMS": 1.829 },
"webgpu:api,operation,render_pass,clear_value:layout:*": { "subcaseMS": 1.401 },
"webgpu:api,operation,render_pass,clear_value:loaded:*": { "subcaseMS": 14.300 },
@@ -135,6 +140,9 @@
"webgpu:api,operation,render_pipeline,sample_mask:alpha_to_coverage_mask:*": { "subcaseMS": 68.512 },
"webgpu:api,operation,render_pipeline,sample_mask:fragment_output_mask:*": { "subcaseMS": 6.154 },
"webgpu:api,operation,render_pipeline,vertex_only_render_pipeline:draw_depth_and_stencil_with_vertex_only_pipeline:*": { "subcaseMS": 14.100 },
+ "webgpu:api,operation,rendering,3d_texture_slices:multiple_color_attachments,same_mip_level:*": { "subcaseMS": 69.400 },
+ "webgpu:api,operation,rendering,3d_texture_slices:multiple_color_attachments,same_slice_with_diff_mip_levels:*": { "subcaseMS": 9.800 },
+ "webgpu:api,operation,rendering,3d_texture_slices:one_color_attachment,mip_levels:*": { "subcaseMS": 54.100 },
"webgpu:api,operation,rendering,basic:clear:*": { "subcaseMS": 3.700 },
"webgpu:api,operation,rendering,basic:fullscreen_quad:*": { "subcaseMS": 16.601 },
"webgpu:api,operation,rendering,basic:large_draw:*": { "subcaseMS": 2335.425 },
@@ -194,6 +202,8 @@
"webgpu:api,operation,shader_module,compilation_info:getCompilationInfo_returns:*": { "subcaseMS": 0.284 },
"webgpu:api,operation,shader_module,compilation_info:line_number_and_position:*": { "subcaseMS": 1.867 },
"webgpu:api,operation,shader_module,compilation_info:offset_and_length:*": { "subcaseMS": 1.648 },
+ "webgpu:api,operation,storage_texture,read_only:basic:*": { "subcaseMS": 20.000 },
+ "webgpu:api,operation,storage_texture,read_write:basic:*": { "subcaseMS": 5.000 },
"webgpu:api,operation,texture_view,format_reinterpretation:render_and_resolve_attachment:*": { "subcaseMS": 14.488 },
"webgpu:api,operation,texture_view,format_reinterpretation:texture_binding:*": { "subcaseMS": 17.225 },
"webgpu:api,operation,texture_view,read:aspect:*": { "subcaseMS": 0.601 },
@@ -264,7 +274,7 @@
"webgpu:api,validation,buffer,mapping:unmap,state,mappingPending:*": { "subcaseMS": 22.951 },
"webgpu:api,validation,buffer,mapping:unmap,state,unmapped:*": { "subcaseMS": 74.200 },
"webgpu:api,validation,capability_checks,features,query_types:createQuerySet:*": { "subcaseMS": 10.451 },
- "webgpu:api,validation,capability_checks,features,query_types:writeTimestamp:*": { "subcaseMS": 1.200 },
+ "webgpu:api,validation,capability_checks,features,query_types:timestamp:*": { "subcaseMS": 1.200 },
"webgpu:api,validation,capability_checks,features,texture_formats:canvas_configuration:*": { "subcaseMS": 4.339 },
"webgpu:api,validation,capability_checks,features,texture_formats:canvas_configuration_view_formats:*": { "subcaseMS": 4.522 },
"webgpu:api,validation,capability_checks,features,texture_formats:check_capability_guarantees:*": { "subcaseMS": 55.901 },
@@ -277,6 +287,8 @@
"webgpu:api,validation,capability_checks,limits,maxBindGroups:createPipelineLayout,at_over:*": { "subcaseMS": 9.310 },
"webgpu:api,validation,capability_checks,limits,maxBindGroups:setBindGroup,at_over:*": { "subcaseMS": 9.984 },
"webgpu:api,validation,capability_checks,limits,maxBindGroups:validate,maxBindGroupsPlusVertexBuffers:*": { "subcaseMS": 11.200 },
+ "webgpu:api,validation,capability_checks,limits,maxBindGroupsPlusVertexBuffers:createRenderPipeline,at_over:*": { "subcaseMS": 11.200 },
+ "webgpu:api,validation,capability_checks,limits,maxBindGroupsPlusVertexBuffers:draw,at_over:*": { "subcaseMS": 11.200 },
"webgpu:api,validation,capability_checks,limits,maxBindingsPerBindGroup:createBindGroupLayout,at_over:*": { "subcaseMS": 12.441 },
"webgpu:api,validation,capability_checks,limits,maxBindingsPerBindGroup:createPipeline,at_over:*": { "subcaseMS": 11.179 },
"webgpu:api,validation,capability_checks,limits,maxBindingsPerBindGroup:validate:*": { "subcaseMS": 12.401 },
@@ -356,6 +368,7 @@
"webgpu:api,validation,compute_pipeline:overrides,workgroup_size,limits:*": { "subcaseMS": 14.751 },
"webgpu:api,validation,compute_pipeline:overrides,workgroup_size:*": { "subcaseMS": 6.376 },
"webgpu:api,validation,compute_pipeline:pipeline_layout,device_mismatch:*": { "subcaseMS": 1.175 },
+ "webgpu:api,validation,compute_pipeline:resource_compatibility:*": { "subcaseMS": 1.175 },
"webgpu:api,validation,compute_pipeline:shader_module,compute:*": { "subcaseMS": 6.867 },
"webgpu:api,validation,compute_pipeline:shader_module,device_mismatch:*": { "subcaseMS": 15.350 },
"webgpu:api,validation,compute_pipeline:shader_module,invalid:*": { "subcaseMS": 2.500 },
@@ -516,7 +529,6 @@
"webgpu:api,validation,encoding,createRenderBundleEncoder:attachment_state,limits,maxColorAttachmentBytesPerSample,unaligned:*": { "subcaseMS": 0.750 },
"webgpu:api,validation,encoding,createRenderBundleEncoder:attachment_state,limits,maxColorAttachments:*": { "subcaseMS": 0.145 },
"webgpu:api,validation,encoding,createRenderBundleEncoder:depth_stencil_readonly:*": { "subcaseMS": 1.804 },
- "webgpu:api,validation,encoding,createRenderBundleEncoder:depth_stencil_readonly_with_undefined_depth:*": { "subcaseMS": 14.825 },
"webgpu:api,validation,encoding,createRenderBundleEncoder:valid_texture_formats:*": { "subcaseMS": 2.130 },
"webgpu:api,validation,encoding,encoder_open_state:compute_pass_commands:*": { "subcaseMS": 4.208 },
"webgpu:api,validation,encoding,encoder_open_state:non_pass_commands:*": { "subcaseMS": 26.191 },
@@ -532,6 +544,8 @@
"webgpu:api,validation,encoding,programmable,pipeline_bind_group_compat:bgl_visibility_mismatch:*": { "subcaseMS": 0.608 },
"webgpu:api,validation,encoding,programmable,pipeline_bind_group_compat:bind_groups_and_pipeline_layout_mismatch:*": { "subcaseMS": 1.535 },
"webgpu:api,validation,encoding,programmable,pipeline_bind_group_compat:buffer_binding,render_pipeline:*": { "subcaseMS": 1.734 },
+ "webgpu:api,validation,encoding,programmable,pipeline_bind_group_compat:default_bind_group_layouts_never_match,compute_pass:*": { "subcaseMS": 1.734 },
+ "webgpu:api,validation,encoding,programmable,pipeline_bind_group_compat:default_bind_group_layouts_never_match,render_pass:*": { "subcaseMS": 1.734 },
"webgpu:api,validation,encoding,programmable,pipeline_bind_group_compat:empty_bind_group_layouts_requires_empty_bind_groups,compute_pass:*": { "subcaseMS": 2.325 },
"webgpu:api,validation,encoding,programmable,pipeline_bind_group_compat:empty_bind_group_layouts_requires_empty_bind_groups,render_pass:*": { "subcaseMS": 10.838 },
"webgpu:api,validation,encoding,programmable,pipeline_bind_group_compat:sampler_binding,render_pipeline:*": { "subcaseMS": 10.523 },
@@ -542,9 +556,9 @@
"webgpu:api,validation,encoding,queries,general:occlusion_query,invalid_query_set:*": { "subcaseMS": 1.651 },
"webgpu:api,validation,encoding,queries,general:occlusion_query,query_index:*": { "subcaseMS": 0.500 },
"webgpu:api,validation,encoding,queries,general:occlusion_query,query_type:*": { "subcaseMS": 4.702 },
- "webgpu:api,validation,encoding,queries,general:timestamp_query,device_mismatch:*": { "subcaseMS": 0.101 },
- "webgpu:api,validation,encoding,queries,general:timestamp_query,invalid_query_set:*": { "subcaseMS": 0.101 },
- "webgpu:api,validation,encoding,queries,general:timestamp_query,query_type_and_index:*": { "subcaseMS": 0.301 },
+ "webgpu:api,validation,encoding,queries,general:writeTimestamp,device_mismatch:*": { "subcaseMS": 0.101 },
+ "webgpu:api,validation,encoding,queries,general:writeTimestamp,invalid_query_set:*": { "subcaseMS": 0.101 },
+ "webgpu:api,validation,encoding,queries,general:writeTimestamp,query_type_and_index:*": { "subcaseMS": 0.301 },
"webgpu:api,validation,encoding,queries,resolveQuerySet:destination_buffer_usage:*": { "subcaseMS": 16.050 },
"webgpu:api,validation,encoding,queries,resolveQuerySet:destination_offset_alignment:*": { "subcaseMS": 0.325 },
"webgpu:api,validation,encoding,queries,resolveQuerySet:first_query_and_query_count:*": { "subcaseMS": 0.250 },
@@ -599,6 +613,7 @@
"webgpu:api,validation,image_copy,texture_related:texture,device_mismatch:*": { "subcaseMS": 5.417 },
"webgpu:api,validation,image_copy,texture_related:usage:*": { "subcaseMS": 1.224 },
"webgpu:api,validation,image_copy,texture_related:valid:*": { "subcaseMS": 3.678 },
+ "webgpu:api,validation,layout_shader_compat:pipeline_layout_shader_exact_match:*": { "subcaseMS": 2.000 },
"webgpu:api,validation,query_set,create:count:*": { "subcaseMS": 0.967 },
"webgpu:api,validation,query_set,destroy:invalid_queryset:*": { "subcaseMS": 0.801 },
"webgpu:api,validation,query_set,destroy:twice:*": { "subcaseMS": 0.700 },
@@ -629,7 +644,7 @@
"webgpu:api,validation,queue,destroyed,buffer:writeBuffer:*": { "subcaseMS": 2.151 },
"webgpu:api,validation,queue,destroyed,query_set:beginOcclusionQuery:*": { "subcaseMS": 17.401 },
"webgpu:api,validation,queue,destroyed,query_set:resolveQuerySet:*": { "subcaseMS": 16.401 },
- "webgpu:api,validation,queue,destroyed,query_set:writeTimestamp:*": { "subcaseMS": 0.901 },
+ "webgpu:api,validation,queue,destroyed,query_set:timestamps:*": { "subcaseMS": 0.901 },
"webgpu:api,validation,queue,destroyed,texture:beginRenderPass:*": { "subcaseMS": 0.350 },
"webgpu:api,validation,queue,destroyed,texture:copyBufferToTexture:*": { "subcaseMS": 16.550 },
"webgpu:api,validation,queue,destroyed,texture:copyTextureToBuffer:*": { "subcaseMS": 15.900 },
@@ -663,6 +678,10 @@
"webgpu:api,validation,render_pass,render_pass_descriptor:attachments,one_color_attachment:*": { "subcaseMS": 33.401 },
"webgpu:api,validation,render_pass,render_pass_descriptor:attachments,one_depth_stencil_attachment:*": { "subcaseMS": 15.301 },
"webgpu:api,validation,render_pass,render_pass_descriptor:attachments,same_size:*": { "subcaseMS": 33.400 },
+ "webgpu:api,validation,render_pass,render_pass_descriptor:color_attachments,depthSlice,bound_check:*": { "subcaseMS": 9.400 },
+ "webgpu:api,validation,render_pass,render_pass_descriptor:color_attachments,depthSlice,definedness:*": { "subcaseMS": 5.601 },
+ "webgpu:api,validation,render_pass,render_pass_descriptor:color_attachments,depthSlice,overlaps,diff_miplevel:*": { "subcaseMS": 3.901 },
+ "webgpu:api,validation,render_pass,render_pass_descriptor:color_attachments,depthSlice,overlaps,same_miplevel:*": { "subcaseMS": 6.400 },
"webgpu:api,validation,render_pass,render_pass_descriptor:color_attachments,empty:*": { "subcaseMS": 0.400 },
"webgpu:api,validation,render_pass,render_pass_descriptor:color_attachments,limits,maxColorAttachmentBytesPerSample,aligned:*": { "subcaseMS": 1.825 },
"webgpu:api,validation,render_pass,render_pass_descriptor:color_attachments,limits,maxColorAttachmentBytesPerSample,unaligned:*": { "subcaseMS": 17.151 },
@@ -701,6 +720,7 @@
"webgpu:api,validation,render_pipeline,fragment_state:pipeline_output_targets:*": { "subcaseMS": 0.497 },
"webgpu:api,validation,render_pipeline,fragment_state:targets_blend:*": { "subcaseMS": 1.203 },
"webgpu:api,validation,render_pipeline,fragment_state:targets_format_filterable:*": { "subcaseMS": 2.143 },
+ "webgpu:api,validation,render_pipeline,fragment_state:targets_format_is_color_format:*": { "subcaseMS": 2.000 },
"webgpu:api,validation,render_pipeline,fragment_state:targets_format_renderable:*": { "subcaseMS": 3.339 },
"webgpu:api,validation,render_pipeline,fragment_state:targets_write_mask:*": { "subcaseMS": 12.272 },
"webgpu:api,validation,render_pipeline,inter_stage:interpolation_sampling:*": { "subcaseMS": 3.126 },
@@ -713,6 +733,7 @@
"webgpu:api,validation,render_pipeline,inter_stage:max_shader_variable_location:*": { "subcaseMS": 11.050 },
"webgpu:api,validation,render_pipeline,inter_stage:type:*": { "subcaseMS": 6.170 },
"webgpu:api,validation,render_pipeline,misc:basic:*": { "subcaseMS": 0.901 },
+ "webgpu:api,validation,render_pipeline,misc:external_texture:*": { "subcaseMS": 35.189 },
"webgpu:api,validation,render_pipeline,misc:pipeline_layout,device_mismatch:*": { "subcaseMS": 8.700 },
"webgpu:api,validation,render_pipeline,misc:vertex_state_only:*": { "subcaseMS": 1.125 },
"webgpu:api,validation,render_pipeline,multisample_state:alpha_to_coverage,count:*": { "subcaseMS": 3.200 },
@@ -730,6 +751,7 @@
"webgpu:api,validation,render_pipeline,overrides:value,validation_error,vertex:*": { "subcaseMS": 6.022 },
"webgpu:api,validation,render_pipeline,primitive_state:strip_index_format:*": { "subcaseMS": 5.267 },
"webgpu:api,validation,render_pipeline,primitive_state:unclipped_depth:*": { "subcaseMS": 1.025 },
+ "webgpu:api,validation,render_pipeline,resource_compatibility:resource_compatibility:*": { "subcaseMS": 1.025 },
"webgpu:api,validation,render_pipeline,shader_module:device_mismatch:*": { "subcaseMS": 0.700 },
"webgpu:api,validation,render_pipeline,shader_module:invalid,fragment:*": { "subcaseMS": 5.800 },
"webgpu:api,validation,render_pipeline,shader_module:invalid,vertex:*": { "subcaseMS": 15.151 },
@@ -775,8 +797,11 @@
"webgpu:api,validation,resource_usages,texture,in_render_misc:subresources,set_unused_bind_group:*": { "subcaseMS": 6.200 },
"webgpu:api,validation,resource_usages,texture,in_render_misc:subresources,texture_usages_in_copy_and_render_pass:*": { "subcaseMS": 4.763 },
"webgpu:api,validation,shader_module,entry_point:compute:*": { "subcaseMS": 4.439 },
+ "webgpu:api,validation,shader_module,entry_point:compute_undefined_entry_point_and_extra_stage:*": { "subcaseMS": 17.075 },
"webgpu:api,validation,shader_module,entry_point:fragment:*": { "subcaseMS": 5.865 },
+ "webgpu:api,validation,shader_module,entry_point:fragment_undefined_entry_point_and_extra_stage:*": { "subcaseMS": 16.050 },
"webgpu:api,validation,shader_module,entry_point:vertex:*": { "subcaseMS": 5.803 },
+ "webgpu:api,validation,shader_module,entry_point:vertex_undefined_entry_point_and_extra_stage:*": { "subcaseMS": 15.851 },
"webgpu:api,validation,shader_module,overrides:id_conflict:*": { "subcaseMS": 36.700 },
"webgpu:api,validation,shader_module,overrides:name_conflict:*": { "subcaseMS": 1.500 },
"webgpu:api,validation,state,device_lost,destroy:command,clearBuffer:*": { "subcaseMS": 11.826 },
@@ -815,8 +840,6 @@
"webgpu:api,validation,texture,bgra8unorm_storage:configure_storage_usage_on_canvas_context_with_bgra8unorm_storage:*": { "subcaseMS": 3.230 },
"webgpu:api,validation,texture,bgra8unorm_storage:configure_storage_usage_on_canvas_context_without_bgra8unorm_storage:*": { "subcaseMS": 1.767 },
"webgpu:api,validation,texture,bgra8unorm_storage:create_bind_group_layout:*": { "subcaseMS": 21.500 },
- "webgpu:api,validation,texture,bgra8unorm_storage:create_shader_module_with_bgra8unorm_storage:*": { "subcaseMS": 11.201 },
- "webgpu:api,validation,texture,bgra8unorm_storage:create_shader_module_without_bgra8unorm_storage:*": { "subcaseMS": 1.601 },
"webgpu:api,validation,texture,bgra8unorm_storage:create_texture:*": { "subcaseMS": 22.900 },
"webgpu:api,validation,texture,destroy:base:*": { "subcaseMS": 4.000 },
"webgpu:api,validation,texture,destroy:invalid_texture:*": { "subcaseMS": 27.200 },
@@ -828,14 +851,27 @@
"webgpu:api,validation,texture,rg11b10ufloat_renderable:begin_render_pass_single_sampled:*": { "subcaseMS": 1.200 },
"webgpu:api,validation,texture,rg11b10ufloat_renderable:create_render_pipeline:*": { "subcaseMS": 2.400 },
"webgpu:api,validation,texture,rg11b10ufloat_renderable:create_texture:*": { "subcaseMS": 12.700 },
+ "webgpu:compat,api,validation,createBindGroup:viewDimension_matches_textureBindingViewDimension:*": { "subcaseMS": 6.523 },
+ "webgpu:compat,api,validation,createBindGroupLayout:unsupportedStorageTextureFormats:*": { "subcaseMS": 0.601 },
"webgpu:compat,api,validation,encoding,cmds,copyTextureToBuffer:compressed:*": { "subcaseMS": 202.929 },
+ "webgpu:compat,api,validation,encoding,cmds,copyTextureToTexture:compressed:*": { "subcaseMS": 0.600 },
+ "webgpu:compat,api,validation,encoding,cmds,copyTextureToTexture:multisample:*": { "subcaseMS": 0.600 },
"webgpu:compat,api,validation,encoding,programmable,pipeline_bind_group_compat:twoDifferentTextureViews,compute_pass,unused:*": { "subcaseMS": 1.501 },
"webgpu:compat,api,validation,encoding,programmable,pipeline_bind_group_compat:twoDifferentTextureViews,compute_pass,used:*": { "subcaseMS": 49.405 },
"webgpu:compat,api,validation,encoding,programmable,pipeline_bind_group_compat:twoDifferentTextureViews,render_pass,unused:*": { "subcaseMS": 16.002 },
"webgpu:compat,api,validation,encoding,programmable,pipeline_bind_group_compat:twoDifferentTextureViews,render_pass,used:*": { "subcaseMS": 0.000 },
+ "webgpu:compat,api,validation,render_pipeline,depth_stencil_state:depthBiasClamp:*": { "subcaseMS": 1.604 },
"webgpu:compat,api,validation,render_pipeline,fragment_state:colorState:*": { "subcaseMS": 32.604 },
+ "webgpu:compat,api,validation,render_pipeline,shader_module:interpolate:*": { "subcaseMS": 1.502 },
+ "webgpu:compat,api,validation,render_pipeline,shader_module:sample_index:*": { "subcaseMS": 1.502 },
"webgpu:compat,api,validation,render_pipeline,shader_module:sample_mask:*": { "subcaseMS": 14.801 },
+ "webgpu:compat,api,validation,render_pipeline,shader_module:unsupportedStorageTextureFormats,computePipeline:*": { "subcaseMS": 0.601 },
+ "webgpu:compat,api,validation,render_pipeline,shader_module:unsupportedStorageTextureFormats,renderPipeline:*": { "subcaseMS": 0.601 },
"webgpu:compat,api,validation,render_pipeline,vertex_state:maxVertexAttributesVertexIndexInstanceIndex:*": { "subcaseMS": 3.700 },
+ "webgpu:compat,api,validation,texture,createTexture:depthOrArrayLayers_incompatible_with_textureBindingViewDimension:*": { "subcaseMS": 12.712 },
+ "webgpu:compat,api,validation,texture,createTexture:format_reinterpretation:*": { "subcaseMS": 7.012 },
+ "webgpu:compat,api,validation,texture,createTexture:invalidTextureBindingViewDimension:*": { "subcaseMS": 6.022 },
+ "webgpu:compat,api,validation,texture,createTexture:unsupportedStorageTextureFormats:*": { "subcaseMS": 0.601 },
"webgpu:compat,api,validation,texture,createTexture:unsupportedTextureFormats:*": { "subcaseMS": 0.700 },
"webgpu:compat,api,validation,texture,createTexture:unsupportedTextureViewFormats:*": { "subcaseMS": 0.601 },
"webgpu:compat,api,validation,texture,cubeArray:cube_array:*": { "subcaseMS": 13.701 },
@@ -862,6 +898,30 @@
"webgpu:idl,constants,flags:ShaderStage,values:*": { "subcaseMS": 0.034 },
"webgpu:idl,constants,flags:TextureUsage,count:*": { "subcaseMS": 0.101 },
"webgpu:idl,constants,flags:TextureUsage,values:*": { "subcaseMS": 0.040 },
+ "webgpu:idl,constructable:gpu_errors:*": { "subcaseMS": 0.101 },
+ "webgpu:idl,constructable:pipeline_errors:*": { "subcaseMS": 0.101 },
+ "webgpu:idl,constructable:uncaptured_error_event:*": { "subcaseMS": 0.101 },
+ "webgpu:shader,execution,expression,access,array,index:abstract_scalar:*": { "subcaseMS": 235.962 },
+ "webgpu:shader,execution,expression,access,array,index:bool:*": { "subcaseMS": 663.038 },
+ "webgpu:shader,execution,expression,access,array,index:concrete_scalar:*": { "subcaseMS": 1439.796 },
+ "webgpu:shader,execution,expression,access,array,index:runtime_sized:*": { "subcaseMS": 830.024 },
+ "webgpu:shader,execution,expression,access,array,index:vector:*": { "subcaseMS": 2137.684 },
+ "webgpu:shader,execution,expression,access,matrix,index:abstract_float_column:*": { "subcaseMS": 14.643 },
+ "webgpu:shader,execution,expression,access,matrix,index:abstract_float_element:*": { "subcaseMS": 587.333 },
+ "webgpu:shader,execution,expression,access,matrix,index:concrete_float_column:*": { "subcaseMS": 4690.055 },
+ "webgpu:shader,execution,expression,access,matrix,index:concrete_float_element:*": { "subcaseMS": 6855.570 },
+ "webgpu:shader,execution,expression,access,structure,index:buffer:*": { "subcaseMS": 325.082 },
+ "webgpu:shader,execution,expression,access,structure,index:buffer_align:*": { "subcaseMS": 84.911 },
+ "webgpu:shader,execution,expression,access,structure,index:buffer_pointer:*": { "subcaseMS": 307.453 },
+ "webgpu:shader,execution,expression,access,structure,index:buffer_size:*": { "subcaseMS": 45.423 },
+ "webgpu:shader,execution,expression,access,structure,index:const:*": { "subcaseMS": 211.459 },
+ "webgpu:shader,execution,expression,access,structure,index:const_nested:*": { "subcaseMS": 214.727 },
+ "webgpu:shader,execution,expression,access,structure,index:let:*": { "subcaseMS": 201.392 },
+ "webgpu:shader,execution,expression,access,structure,index:param:*": { "subcaseMS": 215.826 },
+ "webgpu:shader,execution,expression,access,vector,components:abstract_scalar:*": { "subcaseMS": 533.768 },
+ "webgpu:shader,execution,expression,access,vector,components:concrete_scalar:*": { "subcaseMS": 11636.652 },
+ "webgpu:shader,execution,expression,access,vector,index:abstract_scalar:*": { "subcaseMS": 215.404 },
+ "webgpu:shader,execution,expression,access,vector,index:concrete_scalar:*": { "subcaseMS": 1707.582 },
"webgpu:shader,execution,expression,binary,af_addition:scalar:*": { "subcaseMS": 815.300 },
"webgpu:shader,execution,expression,binary,af_addition:scalar_vector:*": { "subcaseMS": 1803.434 },
"webgpu:shader,execution,expression,binary,af_addition:vector:*": { "subcaseMS": 719.600 },
@@ -877,7 +937,12 @@
"webgpu:shader,execution,expression,binary,af_division:vector:*": { "subcaseMS": 237.134 },
"webgpu:shader,execution,expression,binary,af_division:vector_scalar:*": { "subcaseMS": 580.000 },
"webgpu:shader,execution,expression,binary,af_matrix_addition:matrix:*": { "subcaseMS": 11169.534 },
+ "webgpu:shader,execution,expression,binary,af_matrix_matrix_multiplication:matrix_matrix:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,binary,af_matrix_scalar_multiplication:matrix_scalar:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,binary,af_matrix_scalar_multiplication:scalar_matrix:*": { "subcaseMS": 0.000 },
"webgpu:shader,execution,expression,binary,af_matrix_subtraction:matrix:*": { "subcaseMS": 14060.956 },
+ "webgpu:shader,execution,expression,binary,af_matrix_vector_multiplication:matrix_vector:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,binary,af_matrix_vector_multiplication:vector_matrix:*": { "subcaseMS": 0.000 },
"webgpu:shader,execution,expression,binary,af_multiplication:scalar:*": { "subcaseMS": 777.901 },
"webgpu:shader,execution,expression,binary,af_multiplication:scalar_vector:*": { "subcaseMS": 2025.534 },
"webgpu:shader,execution,expression,binary,af_multiplication:vector:*": { "subcaseMS": 710.667 },
@@ -890,6 +955,27 @@
"webgpu:shader,execution,expression,binary,af_subtraction:scalar_vector:*": { "subcaseMS": 2336.534 },
"webgpu:shader,execution,expression,binary,af_subtraction:vector:*": { "subcaseMS": 764.201 },
"webgpu:shader,execution,expression,binary,af_subtraction:vector_scalar:*": { "subcaseMS": 2437.701 },
+ "webgpu:shader,execution,expression,binary,ai_arithmetic:addition:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,binary,ai_arithmetic:addition_scalar_vector:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,binary,ai_arithmetic:addition_vector_scalar:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,binary,ai_arithmetic:division:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,binary,ai_arithmetic:division_scalar_vector:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,binary,ai_arithmetic:division_vector_scalar:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,binary,ai_arithmetic:multiplication:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,binary,ai_arithmetic:multiplication_scalar_vector:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,binary,ai_arithmetic:multiplication_vector_scalar:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,binary,ai_arithmetic:remainder:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,binary,ai_arithmetic:remainder_scalar_vector:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,binary,ai_arithmetic:remainder_vector_scalar:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,binary,ai_arithmetic:subtraction:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,binary,ai_arithmetic:subtraction_scalar_vector:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,binary,ai_arithmetic:subtraction_vector_scalar:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,binary,ai_comparison:equals:*": { "subcaseMS": 338.609 },
+ "webgpu:shader,execution,expression,binary,ai_comparison:greater_equals:*": { "subcaseMS": 219.452 },
+ "webgpu:shader,execution,expression,binary,ai_comparison:greater_than:*": { "subcaseMS": 232.750 },
+ "webgpu:shader,execution,expression,binary,ai_comparison:less_equals:*": { "subcaseMS": 228.676 },
+ "webgpu:shader,execution,expression,binary,ai_comparison:less_than:*": { "subcaseMS": 245.506 },
+ "webgpu:shader,execution,expression,binary,ai_comparison:not_equals:*": { "subcaseMS": 222.561 },
"webgpu:shader,execution,expression,binary,bitwise:bitwise_and:*": { "subcaseMS": 20.982 },
"webgpu:shader,execution,expression,binary,bitwise:bitwise_and_compound:*": { "subcaseMS": 22.513 },
"webgpu:shader,execution,expression,binary,bitwise:bitwise_exclusive_or:*": { "subcaseMS": 21.294 },
@@ -1066,16 +1152,16 @@
"webgpu:shader,execution,expression,binary,u32_comparison:less_equals:*": { "subcaseMS": 7.844 },
"webgpu:shader,execution,expression,binary,u32_comparison:less_than:*": { "subcaseMS": 6.700 },
"webgpu:shader,execution,expression,binary,u32_comparison:not_equals:*": { "subcaseMS": 6.850 },
- "webgpu:shader,execution,expression,call,builtin,abs:abstract_float:*": { "subcaseMS": 464.126 },
+ "webgpu:shader,execution,expression,call,builtin,abs:abstract_float:*": { "subcaseMS": 13489.454 },
"webgpu:shader,execution,expression,call,builtin,abs:abstract_int:*": { "subcaseMS": 16.810 },
"webgpu:shader,execution,expression,call,builtin,abs:f16:*": { "subcaseMS": 22.910 },
"webgpu:shader,execution,expression,call,builtin,abs:f32:*": { "subcaseMS": 9.844 },
"webgpu:shader,execution,expression,call,builtin,abs:i32:*": { "subcaseMS": 7.088 },
"webgpu:shader,execution,expression,call,builtin,abs:u32:*": { "subcaseMS": 7.513 },
- "webgpu:shader,execution,expression,call,builtin,acos:abstract_float:*": { "subcaseMS": 15.505 },
+ "webgpu:shader,execution,expression,call,builtin,acos:abstract_float:*": { "subcaseMS": 12032.378 },
"webgpu:shader,execution,expression,call,builtin,acos:f16:*": { "subcaseMS": 26.005 },
"webgpu:shader,execution,expression,call,builtin,acos:f32:*": { "subcaseMS": 33.063 },
- "webgpu:shader,execution,expression,call,builtin,acosh:abstract_float:*": { "subcaseMS": 17.210 },
+ "webgpu:shader,execution,expression,call,builtin,acosh:abstract_float:*": { "subcaseMS": 12832.129 },
"webgpu:shader,execution,expression,call,builtin,acosh:f16:*": { "subcaseMS": 140.494 },
"webgpu:shader,execution,expression,call,builtin,acosh:f32:*": { "subcaseMS": 12.588 },
"webgpu:shader,execution,expression,call,builtin,all:bool:*": { "subcaseMS": 6.938 },
@@ -1085,19 +1171,19 @@
"webgpu:shader,execution,expression,call,builtin,arrayLength:read_only:*": { "subcaseMS": 4.500 },
"webgpu:shader,execution,expression,call,builtin,arrayLength:single_element:*": { "subcaseMS": 6.569 },
"webgpu:shader,execution,expression,call,builtin,arrayLength:struct_member:*": { "subcaseMS": 6.819 },
- "webgpu:shader,execution,expression,call,builtin,asin:abstract_float:*": { "subcaseMS": 16.606 },
+ "webgpu:shader,execution,expression,call,builtin,asin:abstract_float:*": { "subcaseMS": 12414.721 },
"webgpu:shader,execution,expression,call,builtin,asin:f16:*": { "subcaseMS": 6.708 },
"webgpu:shader,execution,expression,call,builtin,asin:f32:*": { "subcaseMS": 33.969 },
- "webgpu:shader,execution,expression,call,builtin,asinh:abstract_float:*": { "subcaseMS": 23.305 },
+ "webgpu:shader,execution,expression,call,builtin,asinh:abstract_float:*": { "subcaseMS": 13136.027 },
"webgpu:shader,execution,expression,call,builtin,asinh:f16:*": { "subcaseMS": 59.538 },
"webgpu:shader,execution,expression,call,builtin,asinh:f32:*": { "subcaseMS": 9.731 },
- "webgpu:shader,execution,expression,call,builtin,atan2:abstract_float:*": { "subcaseMS": 24.705 },
+ "webgpu:shader,execution,expression,call,builtin,atan2:abstract_float:*": { "subcaseMS": 6683.345 },
"webgpu:shader,execution,expression,call,builtin,atan2:f16:*": { "subcaseMS": 32.506 },
"webgpu:shader,execution,expression,call,builtin,atan2:f32:*": { "subcaseMS": 25.938 },
- "webgpu:shader,execution,expression,call,builtin,atan:abstract_float:*": { "subcaseMS": 32.408 },
+ "webgpu:shader,execution,expression,call,builtin,atan:abstract_float:*": { "subcaseMS": 12036.687 },
"webgpu:shader,execution,expression,call,builtin,atan:f16:*": { "subcaseMS": 21.106 },
"webgpu:shader,execution,expression,call,builtin,atan:f32:*": { "subcaseMS": 10.251 },
- "webgpu:shader,execution,expression,call,builtin,atanh:abstract_float:*": { "subcaseMS": 16.807 },
+ "webgpu:shader,execution,expression,call,builtin,atanh:abstract_float:*": { "subcaseMS": 12956.533 },
"webgpu:shader,execution,expression,call,builtin,atanh:f16:*": { "subcaseMS": 81.619 },
"webgpu:shader,execution,expression,call,builtin,atanh:f32:*": { "subcaseMS": 12.332 },
"webgpu:shader,execution,expression,call,builtin,atomics,atomicAdd:add_storage:*": { "subcaseMS": 6.482 },
@@ -1128,6 +1214,14 @@
"webgpu:shader,execution,expression,call,builtin,atomics,atomicSub:sub_workgroup:*": { "subcaseMS": 7.238 },
"webgpu:shader,execution,expression,call,builtin,atomics,atomicXor:xor_storage:*": { "subcaseMS": 6.807 },
"webgpu:shader,execution,expression,call,builtin,atomics,atomicXor:xor_workgroup:*": { "subcaseMS": 7.821 },
+ "webgpu:shader,execution,expression,call,builtin,bitcast:af_to_f32:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,builtin,bitcast:af_to_i32:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,builtin,bitcast:af_to_u32:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,builtin,bitcast:af_to_vec2f16:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,builtin,bitcast:ai_to_f32:*": { "subcaseMS": 960.104 },
+ "webgpu:shader,execution,expression,call,builtin,bitcast:ai_to_i32:*": { "subcaseMS": 741.443 },
+ "webgpu:shader,execution,expression,call,builtin,bitcast:ai_to_u32:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,builtin,bitcast:ai_to_vec2h:*": { "subcaseMS": 170.902 },
"webgpu:shader,execution,expression,call,builtin,bitcast:f16_to_f16:*": { "subcaseMS": 21.112 },
"webgpu:shader,execution,expression,call,builtin,bitcast:f32_to_f32:*": { "subcaseMS": 8.625 },
"webgpu:shader,execution,expression,call,builtin,bitcast:f32_to_i32:*": { "subcaseMS": 8.175 },
@@ -1141,6 +1235,8 @@
"webgpu:shader,execution,expression,call,builtin,bitcast:u32_to_i32:*": { "subcaseMS": 6.982 },
"webgpu:shader,execution,expression,call,builtin,bitcast:u32_to_u32:*": { "subcaseMS": 6.907 },
"webgpu:shader,execution,expression,call,builtin,bitcast:u32_to_vec2h:*": { "subcaseMS": 22.210 },
+ "webgpu:shader,execution,expression,call,builtin,bitcast:vec2af_to_vec4f16:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,builtin,bitcast:vec2ai_to_vec4h:*": { "subcaseMS": 304.357 },
"webgpu:shader,execution,expression,call,builtin,bitcast:vec2f_to_vec4h:*": { "subcaseMS": 24.015 },
"webgpu:shader,execution,expression,call,builtin,bitcast:vec2h_to_f32:*": { "subcaseMS": 21.412 },
"webgpu:shader,execution,expression,call,builtin,bitcast:vec2h_to_i32:*": { "subcaseMS": 38.312 },
@@ -1150,19 +1246,19 @@
"webgpu:shader,execution,expression,call,builtin,bitcast:vec4h_to_vec2f:*": { "subcaseMS": 22.812 },
"webgpu:shader,execution,expression,call,builtin,bitcast:vec4h_to_vec2i:*": { "subcaseMS": 20.915 },
"webgpu:shader,execution,expression,call,builtin,bitcast:vec4h_to_vec2u:*": { "subcaseMS": 29.514 },
- "webgpu:shader,execution,expression,call,builtin,ceil:abstract_float:*": { "subcaseMS": 23.611 },
+ "webgpu:shader,execution,expression,call,builtin,ceil:abstract_float:*": { "subcaseMS": 15217.441 },
"webgpu:shader,execution,expression,call,builtin,ceil:f16:*": { "subcaseMS": 29.209 },
"webgpu:shader,execution,expression,call,builtin,ceil:f32:*": { "subcaseMS": 11.132 },
- "webgpu:shader,execution,expression,call,builtin,clamp:abstract_float:*": { "subcaseMS": 11800.350 },
+ "webgpu:shader,execution,expression,call,builtin,clamp:abstract_float:*": { "subcaseMS": 121937.540 },
"webgpu:shader,execution,expression,call,builtin,clamp:abstract_int:*": { "subcaseMS": 18.104 },
"webgpu:shader,execution,expression,call,builtin,clamp:f16:*": { "subcaseMS": 32.809 },
"webgpu:shader,execution,expression,call,builtin,clamp:f32:*": { "subcaseMS": 159.926 },
"webgpu:shader,execution,expression,call,builtin,clamp:i32:*": { "subcaseMS": 54.200 },
"webgpu:shader,execution,expression,call,builtin,clamp:u32:*": { "subcaseMS": 272.419 },
- "webgpu:shader,execution,expression,call,builtin,cos:abstract_float:*": { "subcaseMS": 16.706 },
+ "webgpu:shader,execution,expression,call,builtin,cos:abstract_float:*": { "subcaseMS": 17109.500 },
"webgpu:shader,execution,expression,call,builtin,cos:f16:*": { "subcaseMS": 23.905 },
"webgpu:shader,execution,expression,call,builtin,cos:f32:*": { "subcaseMS": 25.275 },
- "webgpu:shader,execution,expression,call,builtin,cosh:abstract_float:*": { "subcaseMS": 22.909 },
+ "webgpu:shader,execution,expression,call,builtin,cosh:abstract_float:*": { "subcaseMS": 11033.538 },
"webgpu:shader,execution,expression,call,builtin,cosh:f16:*": { "subcaseMS": 58.475 },
"webgpu:shader,execution,expression,call,builtin,cosh:f32:*": { "subcaseMS": 9.694 },
"webgpu:shader,execution,expression,call,builtin,countLeadingZeros:i32:*": { "subcaseMS": 7.494 },
@@ -1171,16 +1267,19 @@
"webgpu:shader,execution,expression,call,builtin,countOneBits:u32:*": { "subcaseMS": 8.644 },
"webgpu:shader,execution,expression,call,builtin,countTrailingZeros:i32:*": { "subcaseMS": 7.844 },
"webgpu:shader,execution,expression,call,builtin,countTrailingZeros:u32:*": { "subcaseMS": 7.851 },
- "webgpu:shader,execution,expression,call,builtin,cross:abstract_float:*": { "subcaseMS": 3.002 },
+ "webgpu:shader,execution,expression,call,builtin,cross:abstract_float:*": { "subcaseMS": 60020.496 },
"webgpu:shader,execution,expression,call,builtin,cross:f16:*": { "subcaseMS": 115.503 },
"webgpu:shader,execution,expression,call,builtin,cross:f32:*": { "subcaseMS": 664.926 },
- "webgpu:shader,execution,expression,call,builtin,degrees:abstract_float:*": { "subcaseMS": 533.052 },
+ "webgpu:shader,execution,expression,call,builtin,degrees:abstract_float:*": { "subcaseMS": 12611.693 },
"webgpu:shader,execution,expression,call,builtin,degrees:f16:*": { "subcaseMS": 29.308 },
"webgpu:shader,execution,expression,call,builtin,degrees:f32:*": { "subcaseMS": 79.525 },
- "webgpu:shader,execution,expression,call,builtin,determinant:abstract_float:*": { "subcaseMS": 15.306 },
+ "webgpu:shader,execution,expression,call,builtin,determinant:abstract_float:*": { "subcaseMS": 1785.618 },
"webgpu:shader,execution,expression,call,builtin,determinant:f16:*": { "subcaseMS": 37.192 },
"webgpu:shader,execution,expression,call,builtin,determinant:f32:*": { "subcaseMS": 10.742 },
- "webgpu:shader,execution,expression,call,builtin,distance:abstract_float:*": { "subcaseMS": 14.503 },
+ "webgpu:shader,execution,expression,call,builtin,distance:abstract_float:*": { "subcaseMS": 10152.221 },
+ "webgpu:shader,execution,expression,call,builtin,distance:abstract_float_vec2:*": { "subcaseMS": 2896.823 },
+ "webgpu:shader,execution,expression,call,builtin,distance:abstract_float_vec3:*": { "subcaseMS": 3191.871 },
+ "webgpu:shader,execution,expression,call,builtin,distance:abstract_float_vec4:*": { "subcaseMS": 3250.958 },
"webgpu:shader,execution,expression,call,builtin,distance:f16:*": { "subcaseMS": 6675.626 },
"webgpu:shader,execution,expression,call,builtin,distance:f16_vec2:*": { "subcaseMS": 78.300 },
"webgpu:shader,execution,expression,call,builtin,distance:f16_vec3:*": { "subcaseMS": 47.925 },
@@ -1189,31 +1288,43 @@
"webgpu:shader,execution,expression,call,builtin,distance:f32_vec2:*": { "subcaseMS": 9.826 },
"webgpu:shader,execution,expression,call,builtin,distance:f32_vec3:*": { "subcaseMS": 10.901 },
"webgpu:shader,execution,expression,call,builtin,distance:f32_vec4:*": { "subcaseMS": 12.700 },
- "webgpu:shader,execution,expression,call,builtin,dot:abstract_float:*": { "subcaseMS": 8.902 },
- "webgpu:shader,execution,expression,call,builtin,dot:abstract_int:*": { "subcaseMS": 2.902 },
+ "webgpu:shader,execution,expression,call,builtin,dot4I8Packed:basic:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,execution,expression,call,builtin,dot4U8Packed:basic:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,execution,expression,call,builtin,dot:abstract_float_vec2:*": { "subcaseMS": 3042.966 },
+ "webgpu:shader,execution,expression,call,builtin,dot:abstract_float_vec3:*": { "subcaseMS": 980.205 },
+ "webgpu:shader,execution,expression,call,builtin,dot:abstract_float_vec4:*": { "subcaseMS": 1036.933 },
+ "webgpu:shader,execution,expression,call,builtin,dot:abstract_int_vec2:*": { "subcaseMS": 2570.488 },
+ "webgpu:shader,execution,expression,call,builtin,dot:abstract_int_vec3:*": { "subcaseMS": 1848.038 },
+ "webgpu:shader,execution,expression,call,builtin,dot:abstract_int_vec4:*": { "subcaseMS": 1742.054 },
"webgpu:shader,execution,expression,call,builtin,dot:f16_vec2:*": { "subcaseMS": 981.225 },
"webgpu:shader,execution,expression,call,builtin,dot:f16_vec3:*": { "subcaseMS": 50.350 },
"webgpu:shader,execution,expression,call,builtin,dot:f16_vec4:*": { "subcaseMS": 52.250 },
"webgpu:shader,execution,expression,call,builtin,dot:f32_vec2:*": { "subcaseMS": 210.350 },
"webgpu:shader,execution,expression,call,builtin,dot:f32_vec3:*": { "subcaseMS": 11.176 },
"webgpu:shader,execution,expression,call,builtin,dot:f32_vec4:*": { "subcaseMS": 11.876 },
- "webgpu:shader,execution,expression,call,builtin,dot:i32:*": { "subcaseMS": 3.103 },
- "webgpu:shader,execution,expression,call,builtin,dot:u32:*": { "subcaseMS": 3.101 },
+ "webgpu:shader,execution,expression,call,builtin,dot:i32_vec2:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,builtin,dot:i32_vec3:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,builtin,dot:i32_vec4:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,builtin,dot:u32_vec2:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,builtin,dot:u32_vec3:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,builtin,dot:u32_vec4:*": { "subcaseMS": 0.000 },
"webgpu:shader,execution,expression,call,builtin,dpdx:f32:*": { "subcaseMS": 22.804 },
"webgpu:shader,execution,expression,call,builtin,dpdxCoarse:f32:*": { "subcaseMS": 22.404 },
"webgpu:shader,execution,expression,call,builtin,dpdxFine:f32:*": { "subcaseMS": 17.708 },
"webgpu:shader,execution,expression,call,builtin,dpdy:f32:*": { "subcaseMS": 17.006 },
"webgpu:shader,execution,expression,call,builtin,dpdyCoarse:f32:*": { "subcaseMS": 17.909 },
"webgpu:shader,execution,expression,call,builtin,dpdyFine:f32:*": { "subcaseMS": 16.806 },
- "webgpu:shader,execution,expression,call,builtin,exp2:abstract_float:*": { "subcaseMS": 22.705 },
+ "webgpu:shader,execution,expression,call,builtin,exp2:abstract_float:*": { "subcaseMS": 17938.514 },
"webgpu:shader,execution,expression,call,builtin,exp2:f16:*": { "subcaseMS": 79.501 },
"webgpu:shader,execution,expression,call,builtin,exp2:f32:*": { "subcaseMS": 12.169 },
- "webgpu:shader,execution,expression,call,builtin,exp:abstract_float:*": { "subcaseMS": 17.210 },
+ "webgpu:shader,execution,expression,call,builtin,exp:abstract_float:*": { "subcaseMS": 18734.085 },
"webgpu:shader,execution,expression,call,builtin,exp:f16:*": { "subcaseMS": 135.363 },
"webgpu:shader,execution,expression,call,builtin,exp:f32:*": { "subcaseMS": 12.557 },
"webgpu:shader,execution,expression,call,builtin,extractBits:i32:*": { "subcaseMS": 8.125 },
"webgpu:shader,execution,expression,call,builtin,extractBits:u32:*": { "subcaseMS": 7.838 },
- "webgpu:shader,execution,expression,call,builtin,faceForward:abstract_float:*": { "subcaseMS": 120.702 },
+ "webgpu:shader,execution,expression,call,builtin,faceForward:abstract_float_vec2:*": { "subcaseMS": 4753.524 },
+ "webgpu:shader,execution,expression,call,builtin,faceForward:abstract_float_vec3:*": { "subcaseMS": 4697.114 },
+ "webgpu:shader,execution,expression,call,builtin,faceForward:abstract_float_vec4:*": { "subcaseMS": 3417.393 },
"webgpu:shader,execution,expression,call,builtin,faceForward:f16_vec2:*": { "subcaseMS": 485.775 },
"webgpu:shader,execution,expression,call,builtin,faceForward:f16_vec3:*": { "subcaseMS": 560.225 },
"webgpu:shader,execution,expression,call,builtin,faceForward:f16_vec4:*": { "subcaseMS": 670.325 },
@@ -1224,15 +1335,23 @@
"webgpu:shader,execution,expression,call,builtin,firstLeadingBit:u32:*": { "subcaseMS": 9.363 },
"webgpu:shader,execution,expression,call,builtin,firstTrailingBit:i32:*": { "subcaseMS": 8.132 },
"webgpu:shader,execution,expression,call,builtin,firstTrailingBit:u32:*": { "subcaseMS": 9.047 },
- "webgpu:shader,execution,expression,call,builtin,floor:abstract_float:*": { "subcaseMS": 34.108 },
+ "webgpu:shader,execution,expression,call,builtin,floor:abstract_float:*": { "subcaseMS": 20061.136 },
"webgpu:shader,execution,expression,call,builtin,floor:f16:*": { "subcaseMS": 30.708 },
"webgpu:shader,execution,expression,call,builtin,floor:f32:*": { "subcaseMS": 10.119 },
- "webgpu:shader,execution,expression,call,builtin,fma:abstract_float:*": { "subcaseMS": 18.208 },
+ "webgpu:shader,execution,expression,call,builtin,fma:abstract_float:*": { "subcaseMS": 148432.980 },
"webgpu:shader,execution,expression,call,builtin,fma:f16:*": { "subcaseMS": 485.857 },
"webgpu:shader,execution,expression,call,builtin,fma:f32:*": { "subcaseMS": 80.388 },
- "webgpu:shader,execution,expression,call,builtin,fract:abstract_float:*": { "subcaseMS": 17.408 },
+ "webgpu:shader,execution,expression,call,builtin,fract:abstract_float:*": { "subcaseMS": 8575.078 },
"webgpu:shader,execution,expression,call,builtin,fract:f16:*": { "subcaseMS": 46.500 },
"webgpu:shader,execution,expression,call,builtin,fract:f32:*": { "subcaseMS": 12.269 },
+ "webgpu:shader,execution,expression,call,builtin,frexp:abstract_float_exp:*": { "subcaseMS": 383.892 },
+ "webgpu:shader,execution,expression,call,builtin,frexp:abstract_float_fract:*": { "subcaseMS": 934.813 },
+ "webgpu:shader,execution,expression,call,builtin,frexp:abstract_float_vec2_exp:*": { "subcaseMS": 805.237 },
+ "webgpu:shader,execution,expression,call,builtin,frexp:abstract_float_vec2_fract:*": { "subcaseMS": 1657.028 },
+ "webgpu:shader,execution,expression,call,builtin,frexp:abstract_float_vec3_exp:*": { "subcaseMS": 1287.254 },
+ "webgpu:shader,execution,expression,call,builtin,frexp:abstract_float_vec3_fract:*": { "subcaseMS": 2943.004 },
+ "webgpu:shader,execution,expression,call,builtin,frexp:abstract_float_vec4_exp:*": { "subcaseMS": 1905.417 },
+ "webgpu:shader,execution,expression,call,builtin,frexp:abstract_float_vec4_fract:*": { "subcaseMS": 4122.700 },
"webgpu:shader,execution,expression,call,builtin,frexp:f16_exp:*": { "subcaseMS": 8.503 },
"webgpu:shader,execution,expression,call,builtin,frexp:f16_fract:*": { "subcaseMS": 17.900 },
"webgpu:shader,execution,expression,call,builtin,frexp:f16_vec2_exp:*": { "subcaseMS": 1.801 },
@@ -1253,13 +1372,16 @@
"webgpu:shader,execution,expression,call,builtin,fwidthCoarse:f32:*": { "subcaseMS": 17.110 },
"webgpu:shader,execution,expression,call,builtin,fwidthFine:f32:*": { "subcaseMS": 16.906 },
"webgpu:shader,execution,expression,call,builtin,insertBits:integer:*": { "subcaseMS": 9.569 },
- "webgpu:shader,execution,expression,call,builtin,inversesqrt:abstract_float:*": { "subcaseMS": 24.310 },
+ "webgpu:shader,execution,expression,call,builtin,inversesqrt:abstract_float:*": { "subcaseMS": 19408.045 },
"webgpu:shader,execution,expression,call,builtin,inversesqrt:f16:*": { "subcaseMS": 21.411 },
"webgpu:shader,execution,expression,call,builtin,inversesqrt:f32:*": { "subcaseMS": 50.125 },
- "webgpu:shader,execution,expression,call,builtin,ldexp:abstract_float:*": { "subcaseMS": 142.805 },
+ "webgpu:shader,execution,expression,call,builtin,ldexp:abstract_float:*": { "subcaseMS": 87.640 },
"webgpu:shader,execution,expression,call,builtin,ldexp:f16:*": { "subcaseMS": 271.038 },
"webgpu:shader,execution,expression,call,builtin,ldexp:f32:*": { "subcaseMS": 161.250 },
- "webgpu:shader,execution,expression,call,builtin,length:abstract_float:*": { "subcaseMS": 31.303 },
+ "webgpu:shader,execution,expression,call,builtin,length:abstract_float:*": { "subcaseMS": 377.202 },
+ "webgpu:shader,execution,expression,call,builtin,length:abstract_float_vec2:*": { "subcaseMS": 2253.267 },
+ "webgpu:shader,execution,expression,call,builtin,length:abstract_float_vec3:*": { "subcaseMS": 1989.791 },
+ "webgpu:shader,execution,expression,call,builtin,length:abstract_float_vec4:*": { "subcaseMS": 1756.180 },
"webgpu:shader,execution,expression,call,builtin,length:f16:*": { "subcaseMS": 490.450 },
"webgpu:shader,execution,expression,call,builtin,length:f16_vec2:*": { "subcaseMS": 33.551 },
"webgpu:shader,execution,expression,call,builtin,length:f16_vec3:*": { "subcaseMS": 79.301 },
@@ -1268,28 +1390,28 @@
"webgpu:shader,execution,expression,call,builtin,length:f32_vec2:*": { "subcaseMS": 9.751 },
"webgpu:shader,execution,expression,call,builtin,length:f32_vec3:*": { "subcaseMS": 10.825 },
"webgpu:shader,execution,expression,call,builtin,length:f32_vec4:*": { "subcaseMS": 9.476 },
- "webgpu:shader,execution,expression,call,builtin,log2:abstract_float:*": { "subcaseMS": 23.607 },
+ "webgpu:shader,execution,expression,call,builtin,log2:abstract_float:*": { "subcaseMS": 59147.901 },
"webgpu:shader,execution,expression,call,builtin,log2:f16:*": { "subcaseMS": 9.404 },
"webgpu:shader,execution,expression,call,builtin,log2:f32:*": { "subcaseMS": 27.838 },
- "webgpu:shader,execution,expression,call,builtin,log:abstract_float:*": { "subcaseMS": 17.911 },
+ "webgpu:shader,execution,expression,call,builtin,log:abstract_float:*": { "subcaseMS": 63419.245 },
"webgpu:shader,execution,expression,call,builtin,log:f16:*": { "subcaseMS": 8.603 },
"webgpu:shader,execution,expression,call,builtin,log:f32:*": { "subcaseMS": 26.725 },
- "webgpu:shader,execution,expression,call,builtin,max:abstract_float:*": { "subcaseMS": 2810.001 },
+ "webgpu:shader,execution,expression,call,builtin,max:abstract_float:*": { "subcaseMS": 31421.439 },
"webgpu:shader,execution,expression,call,builtin,max:abstract_int:*": { "subcaseMS": 33.508 },
"webgpu:shader,execution,expression,call,builtin,max:f16:*": { "subcaseMS": 37.404 },
"webgpu:shader,execution,expression,call,builtin,max:f32:*": { "subcaseMS": 300.619 },
"webgpu:shader,execution,expression,call,builtin,max:i32:*": { "subcaseMS": 7.350 },
"webgpu:shader,execution,expression,call,builtin,max:u32:*": { "subcaseMS": 6.700 },
- "webgpu:shader,execution,expression,call,builtin,min:abstract_float:*": { "subcaseMS": 3054.101 },
+ "webgpu:shader,execution,expression,call,builtin,min:abstract_float:*": { "subcaseMS": 36353.551 },
"webgpu:shader,execution,expression,call,builtin,min:abstract_int:*": { "subcaseMS": 19.806 },
"webgpu:shader,execution,expression,call,builtin,min:f16:*": { "subcaseMS": 8.006 },
"webgpu:shader,execution,expression,call,builtin,min:f32:*": { "subcaseMS": 298.463 },
"webgpu:shader,execution,expression,call,builtin,min:i32:*": { "subcaseMS": 7.825 },
"webgpu:shader,execution,expression,call,builtin,min:u32:*": { "subcaseMS": 6.932 },
- "webgpu:shader,execution,expression,call,builtin,mix:abstract_float_matching:*": { "subcaseMS": 215.206 },
- "webgpu:shader,execution,expression,call,builtin,mix:abstract_float_nonmatching_vec2:*": { "subcaseMS": 14.601 },
- "webgpu:shader,execution,expression,call,builtin,mix:abstract_float_nonmatching_vec3:*": { "subcaseMS": 18.302 },
- "webgpu:shader,execution,expression,call,builtin,mix:abstract_float_nonmatching_vec4:*": { "subcaseMS": 12.602 },
+ "webgpu:shader,execution,expression,call,builtin,mix:abstract_float_matching:*": { "subcaseMS": 23421.613 },
+ "webgpu:shader,execution,expression,call,builtin,mix:abstract_float_nonmatching_vec2:*": { "subcaseMS": 8447.128 },
+ "webgpu:shader,execution,expression,call,builtin,mix:abstract_float_nonmatching_vec3:*": { "subcaseMS": 8537.399 },
+ "webgpu:shader,execution,expression,call,builtin,mix:abstract_float_nonmatching_vec4:*": { "subcaseMS": 11860.514 },
"webgpu:shader,execution,expression,call,builtin,mix:f16_matching:*": { "subcaseMS": 321.700 },
"webgpu:shader,execution,expression,call,builtin,mix:f16_nonmatching_vec2:*": { "subcaseMS": 653.851 },
"webgpu:shader,execution,expression,call,builtin,mix:f16_nonmatching_vec3:*": { "subcaseMS": 832.076 },
@@ -1322,7 +1444,9 @@
"webgpu:shader,execution,expression,call,builtin,modf:f32_vec4_fract:*": { "subcaseMS": 147.876 },
"webgpu:shader,execution,expression,call,builtin,modf:f32_vec4_whole:*": { "subcaseMS": 134.576 },
"webgpu:shader,execution,expression,call,builtin,modf:f32_whole:*": { "subcaseMS": 94.025 },
- "webgpu:shader,execution,expression,call,builtin,normalize:abstract_float:*": { "subcaseMS": 28.508 },
+ "webgpu:shader,execution,expression,call,builtin,normalize:abstract_float_vec2:*": { "subcaseMS": 3621.170 },
+ "webgpu:shader,execution,expression,call,builtin,normalize:abstract_float_vec3:*": { "subcaseMS": 6170.292 },
+ "webgpu:shader,execution,expression,call,builtin,normalize:abstract_float_vec4:*": { "subcaseMS": 7460.960 },
"webgpu:shader,execution,expression,call,builtin,normalize:f16_vec2:*": { "subcaseMS": 635.100 },
"webgpu:shader,execution,expression,call,builtin,normalize:f16_vec3:*": { "subcaseMS": 112.501 },
"webgpu:shader,execution,expression,call,builtin,normalize:f16_vec4:*": { "subcaseMS": 210.526 },
@@ -1334,21 +1458,29 @@
"webgpu:shader,execution,expression,call,builtin,pack2x16unorm:pack:*": { "subcaseMS": 9.525 },
"webgpu:shader,execution,expression,call,builtin,pack4x8snorm:pack:*": { "subcaseMS": 14.751 },
"webgpu:shader,execution,expression,call,builtin,pack4x8unorm:pack:*": { "subcaseMS": 14.575 },
- "webgpu:shader,execution,expression,call,builtin,pow:abstract_float:*": { "subcaseMS": 23.106 },
+ "webgpu:shader,execution,expression,call,builtin,pack4xI8:basic:*": { "subcaseMS": 64.702 },
+ "webgpu:shader,execution,expression,call,builtin,pack4xI8Clamp:basic:*": { "subcaseMS": 92.602 },
+ "webgpu:shader,execution,expression,call,builtin,pack4xU8:basic:*": { "subcaseMS": 166.600 },
+ "webgpu:shader,execution,expression,call,builtin,pack4xU8Clamp:basic:*": { "subcaseMS": 62.802 },
+ "webgpu:shader,execution,expression,call,builtin,pow:abstract_float:*": { "subcaseMS": 30535.000 },
"webgpu:shader,execution,expression,call,builtin,pow:f16:*": { "subcaseMS": 816.063 },
"webgpu:shader,execution,expression,call,builtin,pow:f32:*": { "subcaseMS": 151.269 },
"webgpu:shader,execution,expression,call,builtin,quantizeToF16:f32:*": { "subcaseMS": 11.063 },
- "webgpu:shader,execution,expression,call,builtin,radians:abstract_float:*": { "subcaseMS": 492.827 },
+ "webgpu:shader,execution,expression,call,builtin,radians:abstract_float:*": { "subcaseMS": 12268.988 },
"webgpu:shader,execution,expression,call,builtin,radians:f16:*": { "subcaseMS": 18.707 },
"webgpu:shader,execution,expression,call,builtin,radians:f32:*": { "subcaseMS": 74.432 },
- "webgpu:shader,execution,expression,call,builtin,reflect:abstract_float:*": { "subcaseMS": 47.108 },
+ "webgpu:shader,execution,expression,call,builtin,reflect:abstract_float_vec2:*": { "subcaseMS": 5636.961 },
+ "webgpu:shader,execution,expression,call,builtin,reflect:abstract_float_vec3:*": { "subcaseMS": 10753.506 },
+ "webgpu:shader,execution,expression,call,builtin,reflect:abstract_float_vec4:*": { "subcaseMS": 13283.920 },
"webgpu:shader,execution,expression,call,builtin,reflect:f16_vec2:*": { "subcaseMS": 76.975 },
"webgpu:shader,execution,expression,call,builtin,reflect:f16_vec3:*": { "subcaseMS": 69.451 },
"webgpu:shader,execution,expression,call,builtin,reflect:f16_vec4:*": { "subcaseMS": 79.826 },
"webgpu:shader,execution,expression,call,builtin,reflect:f32_vec2:*": { "subcaseMS": 1182.226 },
"webgpu:shader,execution,expression,call,builtin,reflect:f32_vec3:*": { "subcaseMS": 56.326 },
"webgpu:shader,execution,expression,call,builtin,reflect:f32_vec4:*": { "subcaseMS": 65.250 },
- "webgpu:shader,execution,expression,call,builtin,refract:abstract_float:*": { "subcaseMS": 114.404 },
+ "webgpu:shader,execution,expression,call,builtin,refract:abstract_float_vec2:*": { "subcaseMS": 2981.912 },
+ "webgpu:shader,execution,expression,call,builtin,refract:abstract_float_vec3:*": { "subcaseMS": 3440.181 },
+ "webgpu:shader,execution,expression,call,builtin,refract:abstract_float_vec4:*": { "subcaseMS": 5284.328 },
"webgpu:shader,execution,expression,call,builtin,refract:f16_vec2:*": { "subcaseMS": 536.225 },
"webgpu:shader,execution,expression,call,builtin,refract:f16_vec3:*": { "subcaseMS": 627.450 },
"webgpu:shader,execution,expression,call,builtin,refract:f16_vec4:*": { "subcaseMS": 699.801 },
@@ -1357,46 +1489,46 @@
"webgpu:shader,execution,expression,call,builtin,refract:f32_vec4:*": { "subcaseMS": 610.150 },
"webgpu:shader,execution,expression,call,builtin,reverseBits:i32:*": { "subcaseMS": 9.594 },
"webgpu:shader,execution,expression,call,builtin,reverseBits:u32:*": { "subcaseMS": 7.969 },
- "webgpu:shader,execution,expression,call,builtin,round:abstract_float:*": { "subcaseMS": 19.408 },
+ "webgpu:shader,execution,expression,call,builtin,round:abstract_float:*": { "subcaseMS": 15818.921 },
"webgpu:shader,execution,expression,call,builtin,round:f16:*": { "subcaseMS": 30.509 },
"webgpu:shader,execution,expression,call,builtin,round:f32:*": { "subcaseMS": 12.407 },
- "webgpu:shader,execution,expression,call,builtin,saturate:abstract_float:*": { "subcaseMS": 527.425 },
+ "webgpu:shader,execution,expression,call,builtin,saturate:abstract_float:*": { "subcaseMS": 15450.768 },
"webgpu:shader,execution,expression,call,builtin,saturate:f16:*": { "subcaseMS": 23.407 },
"webgpu:shader,execution,expression,call,builtin,saturate:f32:*": { "subcaseMS": 116.275 },
"webgpu:shader,execution,expression,call,builtin,select:scalar:*": { "subcaseMS": 6.882 },
"webgpu:shader,execution,expression,call,builtin,select:vector:*": { "subcaseMS": 7.096 },
- "webgpu:shader,execution,expression,call,builtin,sign:abstract_float:*": { "subcaseMS": 412.925 },
+ "webgpu:shader,execution,expression,call,builtin,sign:abstract_float:*": { "subcaseMS": 17131.997 },
"webgpu:shader,execution,expression,call,builtin,sign:abstract_int:*": { "subcaseMS": 25.806 },
"webgpu:shader,execution,expression,call,builtin,sign:f16:*": { "subcaseMS": 25.103 },
"webgpu:shader,execution,expression,call,builtin,sign:f32:*": { "subcaseMS": 8.188 },
"webgpu:shader,execution,expression,call,builtin,sign:i32:*": { "subcaseMS": 10.225 },
- "webgpu:shader,execution,expression,call,builtin,sin:abstract_float:*": { "subcaseMS": 19.206 },
+ "webgpu:shader,execution,expression,call,builtin,sin:abstract_float:*": { "subcaseMS": 17098.512 },
"webgpu:shader,execution,expression,call,builtin,sin:f16:*": { "subcaseMS": 8.707 },
"webgpu:shader,execution,expression,call,builtin,sin:f32:*": { "subcaseMS": 26.826 },
- "webgpu:shader,execution,expression,call,builtin,sinh:abstract_float:*": { "subcaseMS": 22.009 },
+ "webgpu:shader,execution,expression,call,builtin,sinh:abstract_float:*": { "subcaseMS": 11721.224 },
"webgpu:shader,execution,expression,call,builtin,sinh:f16:*": { "subcaseMS": 58.288 },
"webgpu:shader,execution,expression,call,builtin,sinh:f32:*": { "subcaseMS": 11.038 },
- "webgpu:shader,execution,expression,call,builtin,smoothstep:abstract_float:*": { "subcaseMS": 23.807 },
+ "webgpu:shader,execution,expression,call,builtin,smoothstep:abstract_float:*": { "subcaseMS": 73577.621 },
"webgpu:shader,execution,expression,call,builtin,smoothstep:f16:*": { "subcaseMS": 616.457 },
"webgpu:shader,execution,expression,call,builtin,smoothstep:f32:*": { "subcaseMS": 88.063 },
- "webgpu:shader,execution,expression,call,builtin,sqrt:abstract_float:*": { "subcaseMS": 19.004 },
+ "webgpu:shader,execution,expression,call,builtin,sqrt:abstract_float:*": { "subcaseMS": 1545.649 },
"webgpu:shader,execution,expression,call,builtin,sqrt:f16:*": { "subcaseMS": 22.908 },
"webgpu:shader,execution,expression,call,builtin,sqrt:f32:*": { "subcaseMS": 10.813 },
- "webgpu:shader,execution,expression,call,builtin,step:abstract_float:*": { "subcaseMS": 19.104 },
+ "webgpu:shader,execution,expression,call,builtin,step:abstract_float:*": { "subcaseMS": 92.310 },
"webgpu:shader,execution,expression,call,builtin,step:f16:*": { "subcaseMS": 32.508 },
"webgpu:shader,execution,expression,call,builtin,step:f32:*": { "subcaseMS": 291.363 },
"webgpu:shader,execution,expression,call,builtin,storageBarrier:barrier:*": { "subcaseMS": 0.801 },
"webgpu:shader,execution,expression,call,builtin,storageBarrier:stage:*": { "subcaseMS": 2.402 },
- "webgpu:shader,execution,expression,call,builtin,tan:abstract_float:*": { "subcaseMS": 31.007 },
+ "webgpu:shader,execution,expression,call,builtin,tan:abstract_float:*": { "subcaseMS": 17043.428 },
"webgpu:shader,execution,expression,call,builtin,tan:f16:*": { "subcaseMS": 116.157 },
"webgpu:shader,execution,expression,call,builtin,tan:f32:*": { "subcaseMS": 13.532 },
- "webgpu:shader,execution,expression,call,builtin,tanh:abstract_float:*": { "subcaseMS": 18.406 },
+ "webgpu:shader,execution,expression,call,builtin,tanh:abstract_float:*": { "subcaseMS": 13578.577 },
"webgpu:shader,execution,expression,call,builtin,tanh:f16:*": { "subcaseMS": 75.982 },
"webgpu:shader,execution,expression,call,builtin,tanh:f32:*": { "subcaseMS": 32.719 },
- "webgpu:shader,execution,expression,call,builtin,textureDimension:depth:*": { "subcaseMS": 20.801 },
- "webgpu:shader,execution,expression,call,builtin,textureDimension:external:*": { "subcaseMS": 1.700 },
- "webgpu:shader,execution,expression,call,builtin,textureDimension:sampled:*": { "subcaseMS": 16.506 },
- "webgpu:shader,execution,expression,call,builtin,textureDimension:storage:*": { "subcaseMS": 25.907 },
+ "webgpu:shader,execution,expression,call,builtin,textureDimensions:depth:*": { "subcaseMS": 20.801 },
+ "webgpu:shader,execution,expression,call,builtin,textureDimensions:external:*": { "subcaseMS": 1.700 },
+ "webgpu:shader,execution,expression,call,builtin,textureDimensions:sampled_and_multisampled:*": { "subcaseMS": 16.506 },
+ "webgpu:shader,execution,expression,call,builtin,textureDimensions:storage:*": { "subcaseMS": 25.907 },
"webgpu:shader,execution,expression,call,builtin,textureGather:depth_2d_coords:*": { "subcaseMS": 11.601 },
"webgpu:shader,execution,expression,call,builtin,textureGather:depth_3d_coords:*": { "subcaseMS": 2.200 },
"webgpu:shader,execution,expression,call,builtin,textureGather:depth_array_2d_coords:*": { "subcaseMS": 23.801 },
@@ -1423,7 +1555,6 @@
"webgpu:shader,execution,expression,call,builtin,textureNumLevels:sampled:*": { "subcaseMS": 6.201 },
"webgpu:shader,execution,expression,call,builtin,textureNumSamples:depth:*": { "subcaseMS": 1.101 },
"webgpu:shader,execution,expression,call,builtin,textureNumSamples:sampled:*": { "subcaseMS": 6.600 },
- "webgpu:shader,execution,expression,call,builtin,textureSample:control_flow:*": { "subcaseMS": 2.801 },
"webgpu:shader,execution,expression,call,builtin,textureSample:depth_2d_coords:*": { "subcaseMS": 12.301 },
"webgpu:shader,execution,expression,call,builtin,textureSample:depth_3d_coords:*": { "subcaseMS": 2.101 },
"webgpu:shader,execution,expression,call,builtin,textureSample:depth_array_2d_coords:*": { "subcaseMS": 92.601 },
@@ -1433,19 +1564,14 @@
"webgpu:shader,execution,expression,call,builtin,textureSample:sampled_3d_coords:*": { "subcaseMS": 36.002 },
"webgpu:shader,execution,expression,call,builtin,textureSample:sampled_array_2d_coords:*": { "subcaseMS": 92.500 },
"webgpu:shader,execution,expression,call,builtin,textureSample:sampled_array_3d_coords:*": { "subcaseMS": 20.200 },
- "webgpu:shader,execution,expression,call,builtin,textureSample:stage:*": { "subcaseMS": 3.000 },
"webgpu:shader,execution,expression,call,builtin,textureSampleBias:arrayed_2d_coords:*": { "subcaseMS": 585.100 },
"webgpu:shader,execution,expression,call,builtin,textureSampleBias:arrayed_3d_coords:*": { "subcaseMS": 121.600 },
- "webgpu:shader,execution,expression,call,builtin,textureSampleBias:control_flow:*": { "subcaseMS": 2.502 },
"webgpu:shader,execution,expression,call,builtin,textureSampleBias:sampled_2d_coords:*": { "subcaseMS": 48.601 },
"webgpu:shader,execution,expression,call,builtin,textureSampleBias:sampled_3d_coords:*": { "subcaseMS": 133.600 },
- "webgpu:shader,execution,expression,call,builtin,textureSampleBias:stage:*": { "subcaseMS": 2.803 },
"webgpu:shader,execution,expression,call,builtin,textureSampleCompare:2d_coords:*": { "subcaseMS": 24.000 },
"webgpu:shader,execution,expression,call,builtin,textureSampleCompare:3d_coords:*": { "subcaseMS": 9.000 },
"webgpu:shader,execution,expression,call,builtin,textureSampleCompare:arrayed_2d_coords:*": { "subcaseMS": 295.601 },
"webgpu:shader,execution,expression,call,builtin,textureSampleCompare:arrayed_3d_coords:*": { "subcaseMS": 60.301 },
- "webgpu:shader,execution,expression,call,builtin,textureSampleCompare:control_flow:*": { "subcaseMS": 2.702 },
- "webgpu:shader,execution,expression,call,builtin,textureSampleCompare:stage:*": { "subcaseMS": 7.701 },
"webgpu:shader,execution,expression,call,builtin,textureSampleCompareLevel:2d_coords:*": { "subcaseMS": 30.401 },
"webgpu:shader,execution,expression,call,builtin,textureSampleCompareLevel:3d_coords:*": { "subcaseMS": 10.301 },
"webgpu:shader,execution,expression,call,builtin,textureSampleCompareLevel:arrayed_2d_coords:*": { "subcaseMS": 705.100 },
@@ -1467,10 +1593,10 @@
"webgpu:shader,execution,expression,call,builtin,textureStore:store_2d_coords:*": { "subcaseMS": 28.809 },
"webgpu:shader,execution,expression,call,builtin,textureStore:store_3d_coords:*": { "subcaseMS": 37.206 },
"webgpu:shader,execution,expression,call,builtin,textureStore:store_array_2d_coords:*": { "subcaseMS": 98.804 },
- "webgpu:shader,execution,expression,call,builtin,transpose:abstract_float:*": { "subcaseMS": 755.012 },
+ "webgpu:shader,execution,expression,call,builtin,transpose:abstract_float:*": { "subcaseMS": 64537.678 },
"webgpu:shader,execution,expression,call,builtin,transpose:f16:*": { "subcaseMS": 33.311 },
"webgpu:shader,execution,expression,call,builtin,transpose:f32:*": { "subcaseMS": 75.887 },
- "webgpu:shader,execution,expression,call,builtin,trunc:abstract_float:*": { "subcaseMS": 455.726 },
+ "webgpu:shader,execution,expression,call,builtin,trunc:abstract_float:*": { "subcaseMS": 12197.517 },
"webgpu:shader,execution,expression,call,builtin,trunc:f16:*": { "subcaseMS": 120.204 },
"webgpu:shader,execution,expression,call,builtin,trunc:f32:*": { "subcaseMS": 48.544 },
"webgpu:shader,execution,expression,call,builtin,unpack2x16float:unpack:*": { "subcaseMS": 11.651 },
@@ -1478,12 +1604,54 @@
"webgpu:shader,execution,expression,call,builtin,unpack2x16unorm:unpack:*": { "subcaseMS": 8.701 },
"webgpu:shader,execution,expression,call,builtin,unpack4x8snorm:unpack:*": { "subcaseMS": 12.275 },
"webgpu:shader,execution,expression,call,builtin,unpack4x8unorm:unpack:*": { "subcaseMS": 11.776 },
+ "webgpu:shader,execution,expression,call,builtin,unpack4xI8:basic:*": { "subcaseMS": 76.901 },
+ "webgpu:shader,execution,expression,call,builtin,unpack4xU8:basic:*": { "subcaseMS": 78.501 },
"webgpu:shader,execution,expression,call,builtin,workgroupBarrier:barrier:*": { "subcaseMS": 0.701 },
"webgpu:shader,execution,expression,call,builtin,workgroupBarrier:stage:*": { "subcaseMS": 1.801 },
+ "webgpu:shader,execution,expression,call,builtin,workgroupUniformLoad:types:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,execution,expression,call,user,ptr_params:array_length:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,user,ptr_params:atomic_ptr_to_element:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,user,ptr_params:mixed_ptr_parameters:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,user,ptr_params:read_full_object:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,user,ptr_params:read_ptr_to_element:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,user,ptr_params:read_ptr_to_member:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,user,ptr_params:write_full_object:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,user,ptr_params:write_ptr_to_element:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,user,ptr_params:write_ptr_to_member:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,constructor,non_zero:abstract_array_elements:*": { "subcaseMS": 115.862 },
+ "webgpu:shader,execution,expression,constructor,non_zero:abstract_matrix_column_vectors:*": { "subcaseMS": 3363.820 },
+ "webgpu:shader,execution,expression,constructor,non_zero:abstract_matrix_elements:*": { "subcaseMS": 35.110 },
+ "webgpu:shader,execution,expression,constructor,non_zero:abstract_vector_elements:*": { "subcaseMS": 52.760 },
+ "webgpu:shader,execution,expression,constructor,non_zero:abstract_vector_mix:*": { "subcaseMS": 29.135 },
+ "webgpu:shader,execution,expression,constructor,non_zero:abstract_vector_splat:*": { "subcaseMS": 70.635 },
+ "webgpu:shader,execution,expression,constructor,non_zero:concrete_array_elements:*": { "subcaseMS": 1578.242 },
+ "webgpu:shader,execution,expression,constructor,non_zero:concrete_matrix_column_vectors:*": { "subcaseMS": 1125.607 },
+ "webgpu:shader,execution,expression,constructor,non_zero:concrete_matrix_elements:*": { "subcaseMS": 2352.444 },
+ "webgpu:shader,execution,expression,constructor,non_zero:concrete_vector_elements:*": { "subcaseMS": 2697.119 },
+ "webgpu:shader,execution,expression,constructor,non_zero:concrete_vector_mix:*": { "subcaseMS": 4056.031 },
+ "webgpu:shader,execution,expression,constructor,non_zero:concrete_vector_splat:*": { "subcaseMS": 10222.094 },
+ "webgpu:shader,execution,expression,constructor,non_zero:matrix_identity:*": { "subcaseMS": 1137.176 },
+ "webgpu:shader,execution,expression,constructor,non_zero:scalar_identity:*": { "subcaseMS": 3153.723 },
+ "webgpu:shader,execution,expression,constructor,non_zero:structure:*": { "subcaseMS": 0.303 },
+ "webgpu:shader,execution,expression,constructor,non_zero:vector_identity:*": { "subcaseMS": 2274.048 },
+ "webgpu:shader,execution,expression,constructor,zero_value:array:*": { "subcaseMS": 500.400 },
+ "webgpu:shader,execution,expression,constructor,zero_value:matrix:*": { "subcaseMS": 205.881 },
+ "webgpu:shader,execution,expression,constructor,zero_value:scalar:*": { "subcaseMS": 39.484 },
+ "webgpu:shader,execution,expression,constructor,zero_value:structure:*": { "subcaseMS": 0.650 },
+ "webgpu:shader,execution,expression,constructor,zero_value:vector:*": { "subcaseMS": 159.975 },
+ "webgpu:shader,execution,expression,precedence:precedence:*": { "subcaseMS": 531.048 },
+ "webgpu:shader,execution,expression,unary,address_of_and_indirection:deref:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,unary,address_of_and_indirection:deref_index:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,unary,address_of_and_indirection:deref_member:*": { "subcaseMS": 0.000 },
"webgpu:shader,execution,expression,unary,af_arithmetic:negation:*": { "subcaseMS": 2165.950 },
"webgpu:shader,execution,expression,unary,af_assignment:abstract:*": { "subcaseMS": 788.400 },
"webgpu:shader,execution,expression,unary,af_assignment:f16:*": { "subcaseMS": 1.000 },
"webgpu:shader,execution,expression,unary,af_assignment:f32:*": { "subcaseMS": 42.000 },
+ "webgpu:shader,execution,expression,unary,ai_arithmetic:negation:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,unary,ai_assignment:abstract:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,unary,ai_assignment:i32:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,unary,ai_assignment:u32:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,unary,ai_complement:complement:*": { "subcaseMS": 0.000 },
"webgpu:shader,execution,expression,unary,bool_conversion:bool:*": { "subcaseMS": 8.357 },
"webgpu:shader,execution,expression,unary,bool_conversion:f16:*": { "subcaseMS": 44.794 },
"webgpu:shader,execution,expression,unary,bool_conversion:f32:*": { "subcaseMS": 41.276 },
@@ -1491,6 +1659,9 @@
"webgpu:shader,execution,expression,unary,bool_conversion:u32:*": { "subcaseMS": 7.401 },
"webgpu:shader,execution,expression,unary,bool_logical:negation:*": { "subcaseMS": 6.413 },
"webgpu:shader,execution,expression,unary,f16_arithmetic:negation:*": { "subcaseMS": 117.604 },
+ "webgpu:shader,execution,expression,unary,f16_conversion:abstract_float:*": { "subcaseMS": 416.757 },
+ "webgpu:shader,execution,expression,unary,f16_conversion:abstract_float_mat:*": { "subcaseMS": 1142.700 },
+ "webgpu:shader,execution,expression,unary,f16_conversion:abstract_int:*": { "subcaseMS": 50.186 },
"webgpu:shader,execution,expression,unary,f16_conversion:bool:*": { "subcaseMS": 34.694 },
"webgpu:shader,execution,expression,unary,f16_conversion:f16:*": { "subcaseMS": 36.013 },
"webgpu:shader,execution,expression,unary,f16_conversion:f16_mat:*": { "subcaseMS": 47.109 },
@@ -1508,12 +1679,15 @@
"webgpu:shader,execution,expression,unary,f32_conversion:u32:*": { "subcaseMS": 7.132 },
"webgpu:shader,execution,expression,unary,i32_arithmetic:negation:*": { "subcaseMS": 7.244 },
"webgpu:shader,execution,expression,unary,i32_complement:i32_complement:*": { "subcaseMS": 9.075 },
+ "webgpu:shader,execution,expression,unary,i32_conversion:abstract_float:*": { "subcaseMS": 4.333 },
+ "webgpu:shader,execution,expression,unary,i32_conversion:abstract_int:*": { "subcaseMS": 167.273 },
"webgpu:shader,execution,expression,unary,i32_conversion:bool:*": { "subcaseMS": 6.457 },
"webgpu:shader,execution,expression,unary,i32_conversion:f16:*": { "subcaseMS": 44.363 },
"webgpu:shader,execution,expression,unary,i32_conversion:f32:*": { "subcaseMS": 8.275 },
"webgpu:shader,execution,expression,unary,i32_conversion:i32:*": { "subcaseMS": 7.707 },
"webgpu:shader,execution,expression,unary,i32_conversion:u32:*": { "subcaseMS": 6.969 },
"webgpu:shader,execution,expression,unary,u32_complement:u32_complement:*": { "subcaseMS": 7.632 },
+ "webgpu:shader,execution,expression,unary,u32_conversion:abstract_float:*": { "subcaseMS": 68.918 },
"webgpu:shader,execution,expression,unary,u32_conversion:abstract_int:*": { "subcaseMS": 20.406 },
"webgpu:shader,execution,expression,unary,u32_conversion:bool:*": { "subcaseMS": 7.713 },
"webgpu:shader,execution,expression,unary,u32_conversion:f16:*": { "subcaseMS": 34.251 },
@@ -1521,6 +1695,10 @@
"webgpu:shader,execution,expression,unary,u32_conversion:i32:*": { "subcaseMS": 8.319 },
"webgpu:shader,execution,expression,unary,u32_conversion:u32:*": { "subcaseMS": 7.057 },
"webgpu:shader,execution,float_parse:valid:*": { "subcaseMS": 6.801 },
+ "webgpu:shader,execution,flow_control,call:arg_eval:*": { "subcaseMS": 40.386 },
+ "webgpu:shader,execution,flow_control,call:arg_eval_logical_and:*": { "subcaseMS": 43.760 },
+ "webgpu:shader,execution,flow_control,call:arg_eval_logical_or:*": { "subcaseMS": 48.924 },
+ "webgpu:shader,execution,flow_control,call:arg_eval_pointers:*": { "subcaseMS": 147.050 },
"webgpu:shader,execution,flow_control,call:call_basic:*": { "subcaseMS": 4.901 },
"webgpu:shader,execution,flow_control,call:call_nested:*": { "subcaseMS": 5.500 },
"webgpu:shader,execution,flow_control,call:call_repeated:*": { "subcaseMS": 10.851 },
@@ -1570,6 +1748,8 @@
"webgpu:shader,execution,flow_control,for:for_continue:*": { "subcaseMS": 10.601 },
"webgpu:shader,execution,flow_control,for:for_continuing:*": { "subcaseMS": 5.000 },
"webgpu:shader,execution,flow_control,for:for_initalizer:*": { "subcaseMS": 7.751 },
+ "webgpu:shader,execution,flow_control,for:for_logical_and_condition:*": { "subcaseMS": 46.477 },
+ "webgpu:shader,execution,flow_control,for:for_logical_or_condition:*": { "subcaseMS": 48.615 },
"webgpu:shader,execution,flow_control,for:nested_for_break:*": { "subcaseMS": 5.901 },
"webgpu:shader,execution,flow_control,for:nested_for_continue:*": { "subcaseMS": 12.851 },
"webgpu:shader,execution,flow_control,if:else_if:*": { "subcaseMS": 7.950 },
@@ -1577,6 +1757,8 @@
"webgpu:shader,execution,flow_control,if:if_true:*": { "subcaseMS": 4.850 },
"webgpu:shader,execution,flow_control,if:nested_if_else:*": { "subcaseMS": 11.650 },
"webgpu:shader,execution,flow_control,loop:loop_break:*": { "subcaseMS": 6.000 },
+ "webgpu:shader,execution,flow_control,loop:loop_break_if_logical_and_condition:*": { "subcaseMS": 6.827 },
+ "webgpu:shader,execution,flow_control,loop:loop_break_if_logical_or_condition:*": { "subcaseMS": 5.846 },
"webgpu:shader,execution,flow_control,loop:loop_continue:*": { "subcaseMS": 11.200 },
"webgpu:shader,execution,flow_control,loop:loop_continuing_basic:*": { "subcaseMS": 12.450 },
"webgpu:shader,execution,flow_control,loop:nested_loops:*": { "subcaseMS": 12.900 },
@@ -1591,13 +1773,18 @@
"webgpu:shader,execution,flow_control,switch:switch:*": { "subcaseMS": 12.750 },
"webgpu:shader,execution,flow_control,switch:switch_default:*": { "subcaseMS": 5.400 },
"webgpu:shader,execution,flow_control,switch:switch_default_only:*": { "subcaseMS": 12.550 },
+ "webgpu:shader,execution,flow_control,switch:switch_inside_loop_with_continue:*": { "subcaseMS": 0.000 },
"webgpu:shader,execution,flow_control,switch:switch_multiple_case:*": { "subcaseMS": 5.550 },
"webgpu:shader,execution,flow_control,switch:switch_multiple_case_default:*": { "subcaseMS": 12.000 },
"webgpu:shader,execution,flow_control,while:while_basic:*": { "subcaseMS": 5.951 },
"webgpu:shader,execution,flow_control,while:while_break:*": { "subcaseMS": 12.450 },
"webgpu:shader,execution,flow_control,while:while_continue:*": { "subcaseMS": 5.650 },
+ "webgpu:shader,execution,flow_control,while:while_logical_and_condition:*": { "subcaseMS": 55.574 },
+ "webgpu:shader,execution,flow_control,while:while_logical_or_condition:*": { "subcaseMS": 49.961 },
"webgpu:shader,execution,flow_control,while:while_nested_break:*": { "subcaseMS": 12.701 },
"webgpu:shader,execution,flow_control,while:while_nested_continue:*": { "subcaseMS": 5.450 },
+ "webgpu:shader,execution,memory_layout:read_layout:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,memory_layout:write_layout:*": { "subcaseMS": 0.000 },
"webgpu:shader,execution,memory_model,adjacent:f16:*": { "subcaseMS": 23.625 },
"webgpu:shader,execution,memory_model,atomicity:atomicity:*": { "subcaseMS": 77.201 },
"webgpu:shader,execution,memory_model,barrier:workgroup_barrier_load_store:*": { "subcaseMS": 65.850 },
@@ -1608,6 +1795,7 @@
"webgpu:shader,execution,memory_model,coherence:corw2:*": { "subcaseMS": 244.384 },
"webgpu:shader,execution,memory_model,coherence:cowr:*": { "subcaseMS": 250.484 },
"webgpu:shader,execution,memory_model,coherence:coww:*": { "subcaseMS": 245.850 },
+ "webgpu:shader,execution,memory_model,texture_intra_invocation_coherence:texture_intra_invocation_coherence:*": { "subcaseMS": 0.000 },
"webgpu:shader,execution,memory_model,weak:2_plus_2_write:*": { "subcaseMS": 185.150 },
"webgpu:shader,execution,memory_model,weak:load_buffer:*": { "subcaseMS": 184.900 },
"webgpu:shader,execution,memory_model,weak:message_passing:*": { "subcaseMS": 196.550 },
@@ -1625,9 +1813,17 @@
"webgpu:shader,execution,robust_access:linear_memory:*": { "subcaseMS": 5.293 },
"webgpu:shader,execution,robust_access_vertex:vertex_buffer_access:*": { "subcaseMS": 6.487 },
"webgpu:shader,execution,shader_io,compute_builtins:inputs:*": { "subcaseMS": 19.342 },
+ "webgpu:shader,execution,shader_io,fragment_builtins:inputs,front_facing:*": { "subcaseMS": 1.001 },
+ "webgpu:shader,execution,shader_io,fragment_builtins:inputs,interStage,centroid:*": { "subcaseMS": 1.001 },
+ "webgpu:shader,execution,shader_io,fragment_builtins:inputs,interStage:*": { "subcaseMS": 1.001 },
+ "webgpu:shader,execution,shader_io,fragment_builtins:inputs,position:*": { "subcaseMS": 1.001 },
+ "webgpu:shader,execution,shader_io,fragment_builtins:inputs,sample_index:*": { "subcaseMS": 1.001 },
+ "webgpu:shader,execution,shader_io,fragment_builtins:inputs,sample_mask:*": { "subcaseMS": 1.001 },
"webgpu:shader,execution,shader_io,shared_structs:shared_between_stages:*": { "subcaseMS": 9.601 },
"webgpu:shader,execution,shader_io,shared_structs:shared_with_buffer:*": { "subcaseMS": 20.701 },
"webgpu:shader,execution,shader_io,shared_structs:shared_with_non_entry_point_function:*": { "subcaseMS": 6.801 },
+ "webgpu:shader,execution,shader_io,user_io:passthrough:*": { "subcaseMS": 373.385 },
+ "webgpu:shader,execution,shader_io,workgroup_size:workgroup_size:*": { "subcaseMS": 0.000 },
"webgpu:shader,execution,shadow:builtin:*": { "subcaseMS": 4.700 },
"webgpu:shader,execution,shadow:declaration:*": { "subcaseMS": 9.700 },
"webgpu:shader,execution,shadow:for_loop:*": { "subcaseMS": 17.201 },
@@ -1635,6 +1831,15 @@
"webgpu:shader,execution,shadow:loop:*": { "subcaseMS": 4.901 },
"webgpu:shader,execution,shadow:switch:*": { "subcaseMS": 4.601 },
"webgpu:shader,execution,shadow:while:*": { "subcaseMS": 7.400 },
+ "webgpu:shader,execution,stage:basic_compute:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,execution,stage:basic_render:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,execution,statement,compound:decl:*": { "subcaseMS": 29.767 },
+ "webgpu:shader,execution,statement,discard:all:*": { "subcaseMS": 36.094 },
+ "webgpu:shader,execution,statement,discard:derivatives:*": { "subcaseMS": 15.287 },
+ "webgpu:shader,execution,statement,discard:function_call:*": { "subcaseMS": 11.744 },
+ "webgpu:shader,execution,statement,discard:loop:*": { "subcaseMS": 11.821 },
+ "webgpu:shader,execution,statement,discard:three_quarters:*": { "subcaseMS": 34.735 },
+ "webgpu:shader,execution,statement,discard:uniform_read_loop:*": { "subcaseMS": 13.095 },
"webgpu:shader,execution,statement,increment_decrement:frexp_exp_increment:*": { "subcaseMS": 4.700 },
"webgpu:shader,execution,statement,increment_decrement:scalar_i32_decrement:*": { "subcaseMS": 20.301 },
"webgpu:shader,execution,statement,increment_decrement:scalar_i32_decrement_underflow:*": { "subcaseMS": 4.900 },
@@ -1658,12 +1863,33 @@
"webgpu:shader,validation,const_assert,const_assert:constant_expression_logical_or_no_assert:*": { "subcaseMS": 1.373 },
"webgpu:shader,validation,const_assert,const_assert:constant_expression_no_assert:*": { "subcaseMS": 1.655 },
"webgpu:shader,validation,const_assert,const_assert:evaluation_stage:*": { "subcaseMS": 3.367 },
+ "webgpu:shader,validation,decl,compound_statement:decl_conflict:*": { "subcaseMS": 5.225 },
+ "webgpu:shader,validation,decl,compound_statement:decl_use:*": { "subcaseMS": 0.625 },
+ "webgpu:shader,validation,decl,const:function_scope:*": { "subcaseMS": 2.088 },
+ "webgpu:shader,validation,decl,const:initializer:*": { "subcaseMS": 0.768 },
"webgpu:shader,validation,decl,const:no_direct_recursion:*": { "subcaseMS": 0.951 },
"webgpu:shader,validation,decl,const:no_indirect_recursion:*": { "subcaseMS": 0.950 },
"webgpu:shader,validation,decl,const:no_indirect_recursion_via_array_size:*": { "subcaseMS": 2.601 },
"webgpu:shader,validation,decl,const:no_indirect_recursion_via_struct_attribute:*": { "subcaseMS": 1.034 },
+ "webgpu:shader,validation,decl,const:type:*": { "subcaseMS": 10.651 },
+ "webgpu:shader,validation,decl,context_dependent_resolution:attribute_names:*": { "subcaseMS": 533.132 },
+ "webgpu:shader,validation,decl,context_dependent_resolution:builtin_value_names:*": { "subcaseMS": 25.538 },
+ "webgpu:shader,validation,decl,context_dependent_resolution:diagnostic_rule_names:*": { "subcaseMS": 6.860 },
+ "webgpu:shader,validation,decl,context_dependent_resolution:diagnostic_severity_names:*": { "subcaseMS": 6.252 },
+ "webgpu:shader,validation,decl,context_dependent_resolution:enable_names:*": { "subcaseMS": 2.226 },
+ "webgpu:shader,validation,decl,context_dependent_resolution:interpolation_sampling_names:*": { "subcaseMS": 4.971 },
+ "webgpu:shader,validation,decl,context_dependent_resolution:interpolation_type_names:*": { "subcaseMS": 4.687 },
+ "webgpu:shader,validation,decl,context_dependent_resolution:language_names:*": { "subcaseMS": 4.920 },
+ "webgpu:shader,validation,decl,context_dependent_resolution:swizzle_names:*": { "subcaseMS": 27.579 },
+ "webgpu:shader,validation,decl,let:initializer:*": { "subcaseMS": 0.706 },
+ "webgpu:shader,validation,decl,let:module_scope:*": { "subcaseMS": 0.619 },
+ "webgpu:shader,validation,decl,let:type:*": { "subcaseMS": 122.199 },
+ "webgpu:shader,validation,decl,override:function_scope:*": { "subcaseMS": 1.003 },
+ "webgpu:shader,validation,decl,override:id:*": { "subcaseMS": 69.432 },
+ "webgpu:shader,validation,decl,override:initializer:*": { "subcaseMS": 4.810 },
"webgpu:shader,validation,decl,override:no_direct_recursion:*": { "subcaseMS": 1.000 },
"webgpu:shader,validation,decl,override:no_indirect_recursion:*": { "subcaseMS": 0.951 },
+ "webgpu:shader,validation,decl,override:type:*": { "subcaseMS": 12.518 },
"webgpu:shader,validation,decl,ptr_spelling:let_ptr_explicit_type_matches_var:*": { "subcaseMS": 1.500 },
"webgpu:shader,validation,decl,ptr_spelling:let_ptr_reads:*": { "subcaseMS": 1.216 },
"webgpu:shader,validation,decl,ptr_spelling:let_ptr_writes:*": { "subcaseMS": 1.250 },
@@ -1671,32 +1897,76 @@
"webgpu:shader,validation,decl,ptr_spelling:ptr_bad_store_type:*": { "subcaseMS": 0.967 },
"webgpu:shader,validation,decl,ptr_spelling:ptr_handle_space_invalid:*": { "subcaseMS": 1.000 },
"webgpu:shader,validation,decl,ptr_spelling:ptr_not_instantiable:*": { "subcaseMS": 1.310 },
+ "webgpu:shader,validation,decl,var:binding_collision_unused_helper:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,decl,var:binding_collisions:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,decl,var:binding_point_on_function_var:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,decl,var:binding_point_on_non_resources:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,decl,var:binding_point_on_resources:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,decl,var:function_addrspace_at_module_scope:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,decl,var:function_scope_types:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,decl,var:handle_initializer:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,decl,var:initializer_kind:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,decl,var:module_scope_initializers:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,decl,var:module_scope_types:*": { "subcaseMS": 1.000 },
"webgpu:shader,validation,decl,var_access_mode:explicit_access_mode:*": { "subcaseMS": 1.373 },
"webgpu:shader,validation,decl,var_access_mode:implicit_access_mode:*": { "subcaseMS": 1.000 },
"webgpu:shader,validation,decl,var_access_mode:read_access:*": { "subcaseMS": 1.177 },
"webgpu:shader,validation,decl,var_access_mode:write_access:*": { "subcaseMS": 1.154 },
"webgpu:shader,validation,expression,access,vector:vector:*": { "subcaseMS": 1.407 },
+ "webgpu:shader,validation,expression,binary,add_sub_mul:invalid_type_with_itself:*": { "subcaseMS": 68.949 },
+ "webgpu:shader,validation,expression,binary,add_sub_mul:scalar_vector:*": { "subcaseMS": 1811.699 },
+ "webgpu:shader,validation,expression,binary,add_sub_mul:scalar_vector_out_of_range:*": { "subcaseMS": 0.719 },
+ "webgpu:shader,validation,expression,binary,and_or_xor:invalid_types:*": { "subcaseMS": 24.069 },
+ "webgpu:shader,validation,expression,binary,and_or_xor:scalar_vector:*": { "subcaseMS": 666.807 },
+ "webgpu:shader,validation,expression,binary,bitwise_shift:invalid_types:*": { "subcaseMS": 22.058 },
+ "webgpu:shader,validation,expression,binary,bitwise_shift:scalar_vector:*": { "subcaseMS": 525.052 },
"webgpu:shader,validation,expression,binary,bitwise_shift:shift_left_concrete:*": { "subcaseMS": 1.216 },
- "webgpu:shader,validation,expression,binary,bitwise_shift:shift_left_vec_size_mismatch:*": { "subcaseMS": 1.367 },
"webgpu:shader,validation,expression,binary,bitwise_shift:shift_right_concrete:*": { "subcaseMS": 1.237 },
- "webgpu:shader,validation,expression,binary,bitwise_shift:shift_right_vec_size_mismatch:*": { "subcaseMS": 1.334 },
+ "webgpu:shader,validation,expression,binary,comparison:invalid_types:*": { "subcaseMS": 39.526 },
+ "webgpu:shader,validation,expression,binary,comparison:scalar_vector:*": { "subcaseMS": 1598.064 },
+ "webgpu:shader,validation,expression,binary,div_rem:invalid_type_with_itself:*": { "subcaseMS": 38.059 },
+ "webgpu:shader,validation,expression,binary,div_rem:scalar_vector:*": { "subcaseMS": 743.721 },
+ "webgpu:shader,validation,expression,binary,div_rem:scalar_vector_out_of_range:*": { "subcaseMS": 650.727 },
+ "webgpu:shader,validation,expression,call,builtin,abs:parameters:*": { "subcaseMS": 10.133 },
"webgpu:shader,validation,expression,call,builtin,abs:values:*": { "subcaseMS": 0.391 },
"webgpu:shader,validation,expression,call,builtin,acos:integer_argument:*": { "subcaseMS": 1.512 },
+ "webgpu:shader,validation,expression,call,builtin,acos:parameters:*": { "subcaseMS": 44.578 },
"webgpu:shader,validation,expression,call,builtin,acos:values:*": { "subcaseMS": 0.342 },
"webgpu:shader,validation,expression,call,builtin,acosh:integer_argument:*": { "subcaseMS": 1.234 },
+ "webgpu:shader,validation,expression,call,builtin,acosh:parameters:*": { "subcaseMS": 152.403 },
"webgpu:shader,validation,expression,call,builtin,acosh:values:*": { "subcaseMS": 0.217 },
+ "webgpu:shader,validation,expression,call,builtin,all:argument_types:*": { "subcaseMS": 58740.580 },
+ "webgpu:shader,validation,expression,call,builtin,all:arguments:*": { "subcaseMS": 80483.389 },
+ "webgpu:shader,validation,expression,call,builtin,all:must_use:*": { "subcaseMS": 7564.378 },
+ "webgpu:shader,validation,expression,call,builtin,any:argument_types:*": { "subcaseMS": 160136.896 },
+ "webgpu:shader,validation,expression,call,builtin,any:arguments:*": { "subcaseMS": 50268.983 },
+ "webgpu:shader,validation,expression,call,builtin,any:must_use:*": { "subcaseMS": 7467.652 },
+ "webgpu:shader,validation,expression,call,builtin,arrayLength:access_mode:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,expression,call,builtin,arrayLength:bool_type:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,expression,call,builtin,arrayLength:type:*": { "subcaseMS": 0.000 },
"webgpu:shader,validation,expression,call,builtin,asin:integer_argument:*": { "subcaseMS": 0.878 },
+ "webgpu:shader,validation,expression,call,builtin,asin:parameters:*": { "subcaseMS": 20072.777 },
"webgpu:shader,validation,expression,call,builtin,asin:values:*": { "subcaseMS": 0.359 },
"webgpu:shader,validation,expression,call,builtin,asinh:integer_argument:*": { "subcaseMS": 1.267 },
+ "webgpu:shader,validation,expression,call,builtin,asinh:parameters:*": { "subcaseMS": 17189.159 },
"webgpu:shader,validation,expression,call,builtin,asinh:values:*": { "subcaseMS": 0.372 },
- "webgpu:shader,validation,expression,call,builtin,atan2:integer_argument_x:*": { "subcaseMS": 0.912 },
- "webgpu:shader,validation,expression,call,builtin,atan2:integer_argument_y:*": { "subcaseMS": 0.867 },
+ "webgpu:shader,validation,expression,call,builtin,atan2:invalid_argument_x:*": { "subcaseMS": 6011.564 },
+ "webgpu:shader,validation,expression,call,builtin,atan2:invalid_argument_y:*": { "subcaseMS": 24242.032 },
+ "webgpu:shader,validation,expression,call,builtin,atan2:must_use:*": { "subcaseMS": 242.807 },
+ "webgpu:shader,validation,expression,call,builtin,atan2:parameters:*": { "subcaseMS": 1360.903 },
"webgpu:shader,validation,expression,call,builtin,atan2:values:*": { "subcaseMS": 0.359 },
"webgpu:shader,validation,expression,call,builtin,atan:integer_argument:*": { "subcaseMS": 1.545 },
+ "webgpu:shader,validation,expression,call,builtin,atan:parameters:*": { "subcaseMS": 14928.226 },
"webgpu:shader,validation,expression,call,builtin,atan:values:*": { "subcaseMS": 0.335 },
"webgpu:shader,validation,expression,call,builtin,atanh:integer_argument:*": { "subcaseMS": 0.912 },
+ "webgpu:shader,validation,expression,call,builtin,atanh:parameters:*": { "subcaseMS": 19071.799 },
"webgpu:shader,validation,expression,call,builtin,atanh:values:*": { "subcaseMS": 0.231 },
+ "webgpu:shader,validation,expression,call,builtin,atomics:atomic_parameterization:*": { "subcaseMS": 1.346 },
+ "webgpu:shader,validation,expression,call,builtin,atomics:data_parameters:*": { "subcaseMS": 38.382 },
+ "webgpu:shader,validation,expression,call,builtin,atomics:return_types:*": { "subcaseMS": 28.021 },
"webgpu:shader,validation,expression,call,builtin,atomics:stage:*": { "subcaseMS": 1.346 },
+ "webgpu:shader,validation,expression,call,builtin,barriers:no_return_value:*": { "subcaseMS": 1.500 },
+ "webgpu:shader,validation,expression,call,builtin,barriers:only_in_compute:*": { "subcaseMS": 1.500 },
"webgpu:shader,validation,expression,call,builtin,bitcast:bad_const_to_f16:*": { "subcaseMS": 0.753 },
"webgpu:shader,validation,expression,call,builtin,bitcast:bad_const_to_f32:*": { "subcaseMS": 0.844 },
"webgpu:shader,validation,expression,call,builtin,bitcast:bad_to_f16:*": { "subcaseMS": 8.518 },
@@ -1708,52 +1978,279 @@
"webgpu:shader,validation,expression,call,builtin,ceil:integer_argument:*": { "subcaseMS": 1.456 },
"webgpu:shader,validation,expression,call,builtin,ceil:values:*": { "subcaseMS": 1.539 },
"webgpu:shader,validation,expression,call,builtin,clamp:values:*": { "subcaseMS": 0.377 },
+ "webgpu:shader,validation,expression,call,builtin,cos:args:*": { "subcaseMS": 4.445 },
"webgpu:shader,validation,expression,call,builtin,cos:integer_argument:*": { "subcaseMS": 1.601 },
+ "webgpu:shader,validation,expression,call,builtin,cos:must_use:*": { "subcaseMS": 0.526 },
"webgpu:shader,validation,expression,call,builtin,cos:values:*": { "subcaseMS": 0.338 },
- "webgpu:shader,validation,expression,call,builtin,cosh:integer_argument:*": { "subcaseMS": 0.889 },
+ "webgpu:shader,validation,expression,call,builtin,cosh:args:*": { "subcaseMS": 41.832 },
+ "webgpu:shader,validation,expression,call,builtin,cosh:must_use:*": { "subcaseMS": 5.658 },
"webgpu:shader,validation,expression,call,builtin,cosh:values:*": { "subcaseMS": 0.272 },
+ "webgpu:shader,validation,expression,call,builtin,countLeadingZeros:arguments:*": { "subcaseMS": 77.173 },
+ "webgpu:shader,validation,expression,call,builtin,countLeadingZeros:float_argument:*": { "subcaseMS": 64.191 },
+ "webgpu:shader,validation,expression,call,builtin,countLeadingZeros:must_use:*": { "subcaseMS": 4.120 },
+ "webgpu:shader,validation,expression,call,builtin,countLeadingZeros:values:*": { "subcaseMS": 3153.457 },
+ "webgpu:shader,validation,expression,call,builtin,countOneBits:arguments:*": { "subcaseMS": 66.449 },
+ "webgpu:shader,validation,expression,call,builtin,countOneBits:float_argument:*": { "subcaseMS": 44.219 },
+ "webgpu:shader,validation,expression,call,builtin,countOneBits:must_use:*": { "subcaseMS": 3.284 },
+ "webgpu:shader,validation,expression,call,builtin,countOneBits:values:*": { "subcaseMS": 3771.859 },
+ "webgpu:shader,validation,expression,call,builtin,countTrailingZeros:arguments:*": { "subcaseMS": 70.424 },
+ "webgpu:shader,validation,expression,call,builtin,countTrailingZeros:float_argument:*": { "subcaseMS": 46.181 },
+ "webgpu:shader,validation,expression,call,builtin,countTrailingZeros:must_use:*": { "subcaseMS": 3.934 },
+ "webgpu:shader,validation,expression,call,builtin,countTrailingZeros:values:*": { "subcaseMS": 3125.847 },
+ "webgpu:shader,validation,expression,call,builtin,cross:args:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,cross:must_use:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,cross:values:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,degrees:args:*": { "subcaseMS": 4.949 },
"webgpu:shader,validation,expression,call,builtin,degrees:integer_argument:*": { "subcaseMS": 1.311 },
+ "webgpu:shader,validation,expression,call,builtin,degrees:must_use:*": { "subcaseMS": 1.406 },
"webgpu:shader,validation,expression,call,builtin,degrees:values:*": { "subcaseMS": 0.303 },
- "webgpu:shader,validation,expression,call,builtin,exp2:integer_argument:*": { "subcaseMS": 0.967 },
+ "webgpu:shader,validation,expression,call,builtin,derivatives:invalid_argument_types:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,derivatives:only_in_fragment:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,determinant:args:*": { "subcaseMS": 15.538 },
+ "webgpu:shader,validation,expression,call,builtin,determinant:matrix_args:*": { "subcaseMS": 193.897 },
+ "webgpu:shader,validation,expression,call,builtin,determinant:must_use:*": { "subcaseMS": 2.856 },
+ "webgpu:shader,validation,expression,call,builtin,distance:args:*": { "subcaseMS": 84.655 },
+ "webgpu:shader,validation,expression,call,builtin,distance:must_use:*": { "subcaseMS": 6.757 },
+ "webgpu:shader,validation,expression,call,builtin,distance:values:*": { "subcaseMS": 9849.553 },
+ "webgpu:shader,validation,expression,call,builtin,dot4I8Packed:args:*": { "subcaseMS": 48.785 },
+ "webgpu:shader,validation,expression,call,builtin,dot4I8Packed:must_use:*": { "subcaseMS": 0.300 },
+ "webgpu:shader,validation,expression,call,builtin,dot4I8Packed:supported:*": { "subcaseMS": 1.100 },
+ "webgpu:shader,validation,expression,call,builtin,dot4I8Packed:unsupported:*": { "subcaseMS": 7.250 },
+ "webgpu:shader,validation,expression,call,builtin,dot4U8Packed:args:*": { "subcaseMS": 45.347 },
+ "webgpu:shader,validation,expression,call,builtin,dot4U8Packed:must_use:*": { "subcaseMS": 0.100 },
+ "webgpu:shader,validation,expression,call,builtin,dot4U8Packed:supported:*": { "subcaseMS": 0.401 },
+ "webgpu:shader,validation,expression,call,builtin,dot4U8Packed:unsupported:*": { "subcaseMS": 3.200 },
+ "webgpu:shader,validation,expression,call,builtin,exp2:args:*": { "subcaseMS": 45.200 },
+ "webgpu:shader,validation,expression,call,builtin,exp2:must_use:*": { "subcaseMS": 6.346 },
"webgpu:shader,validation,expression,call,builtin,exp2:values:*": { "subcaseMS": 0.410 },
- "webgpu:shader,validation,expression,call,builtin,exp:integer_argument:*": { "subcaseMS": 1.356 },
+ "webgpu:shader,validation,expression,call,builtin,exp:args:*": { "subcaseMS": 44.946 },
+ "webgpu:shader,validation,expression,call,builtin,exp:must_use:*": { "subcaseMS": 6.140 },
"webgpu:shader,validation,expression,call,builtin,exp:values:*": { "subcaseMS": 0.311 },
- "webgpu:shader,validation,expression,call,builtin,inverseSqrt:integer_argument:*": { "subcaseMS": 1.356 },
+ "webgpu:shader,validation,expression,call,builtin,extractBits:count_offset:*": { "subcaseMS": 131.573 },
+ "webgpu:shader,validation,expression,call,builtin,extractBits:must_use:*": { "subcaseMS": 4.630 },
+ "webgpu:shader,validation,expression,call,builtin,extractBits:typed_arguments:*": { "subcaseMS": 7928.233 },
+ "webgpu:shader,validation,expression,call,builtin,extractBits:values:*": { "subcaseMS": 28802.248 },
+ "webgpu:shader,validation,expression,call,builtin,faceForward:args:*": { "subcaseMS": 105.517 },
+ "webgpu:shader,validation,expression,call,builtin,faceForward:must_use:*": { "subcaseMS": 5.412 },
+ "webgpu:shader,validation,expression,call,builtin,faceForward:values:*": { "subcaseMS": 96662.064 },
+ "webgpu:shader,validation,expression,call,builtin,firstLeadingBit:arguments:*": { "subcaseMS": 210.892 },
+ "webgpu:shader,validation,expression,call,builtin,firstLeadingBit:float_argument:*": { "subcaseMS": 29.714 },
+ "webgpu:shader,validation,expression,call,builtin,firstLeadingBit:must_use:*": { "subcaseMS": 2.214 },
+ "webgpu:shader,validation,expression,call,builtin,firstLeadingBit:values:*": { "subcaseMS": 4227.758 },
+ "webgpu:shader,validation,expression,call,builtin,firstTrailingBit:arguments:*": { "subcaseMS": 65.356 },
+ "webgpu:shader,validation,expression,call,builtin,firstTrailingBit:float_argument:*": { "subcaseMS": 41.249 },
+ "webgpu:shader,validation,expression,call,builtin,firstTrailingBit:must_use:*": { "subcaseMS": 1.982 },
+ "webgpu:shader,validation,expression,call,builtin,firstTrailingBit:values:*": { "subcaseMS": 4203.191 },
+ "webgpu:shader,validation,expression,call,builtin,floor:args:*": { "subcaseMS": 4.221 },
+ "webgpu:shader,validation,expression,call,builtin,floor:integer_argument:*": { "subcaseMS": 48.400 },
+ "webgpu:shader,validation,expression,call,builtin,floor:must_use:*": { "subcaseMS": 0.170 },
+ "webgpu:shader,validation,expression,call,builtin,floor:values:*": { "subcaseMS": 29.668 },
+ "webgpu:shader,validation,expression,call,builtin,fract:args:*": { "subcaseMS": 45.422 },
+ "webgpu:shader,validation,expression,call,builtin,fract:must_use:*": { "subcaseMS": 5.547 },
+ "webgpu:shader,validation,expression,call,builtin,fract:values:*": { "subcaseMS": 4441.607 },
+ "webgpu:shader,validation,expression,call,builtin,frexp:args:*": { "subcaseMS": 43.518 },
+ "webgpu:shader,validation,expression,call,builtin,frexp:must_use:*": { "subcaseMS": 6.130 },
+ "webgpu:shader,validation,expression,call,builtin,frexp:values:*": { "subcaseMS": 4741.981 },
+ "webgpu:shader,validation,expression,call,builtin,insertBits:count_offset:*": { "subcaseMS": 11904.035 },
+ "webgpu:shader,validation,expression,call,builtin,insertBits:mismatched:*": { "subcaseMS": 659.718 },
+ "webgpu:shader,validation,expression,call,builtin,insertBits:must_use:*": { "subcaseMS": 4.243 },
+ "webgpu:shader,validation,expression,call,builtin,insertBits:typed_arguments:*": { "subcaseMS": 3025.440 },
+ "webgpu:shader,validation,expression,call,builtin,insertBits:values:*": { "subcaseMS": 98566.796 },
+ "webgpu:shader,validation,expression,call,builtin,inverseSqrt:args:*": { "subcaseMS": 41.941 },
+ "webgpu:shader,validation,expression,call,builtin,inverseSqrt:must_use:*": { "subcaseMS": 5.614 },
"webgpu:shader,validation,expression,call,builtin,inverseSqrt:values:*": { "subcaseMS": 0.315 },
"webgpu:shader,validation,expression,call,builtin,length:integer_argument:*": { "subcaseMS": 2.011 },
"webgpu:shader,validation,expression,call,builtin,length:scalar:*": { "subcaseMS": 0.245 },
"webgpu:shader,validation,expression,call,builtin,length:vec2:*": { "subcaseMS": 0.319 },
"webgpu:shader,validation,expression,call,builtin,length:vec3:*": { "subcaseMS": 1.401 },
"webgpu:shader,validation,expression,call,builtin,length:vec4:*": { "subcaseMS": 1.301 },
+ "webgpu:shader,validation,expression,call,builtin,log2:args:*": { "subcaseMS": 4.528 },
"webgpu:shader,validation,expression,call,builtin,log2:integer_argument:*": { "subcaseMS": 1.034 },
+ "webgpu:shader,validation,expression,call,builtin,log2:must_use:*": { "subcaseMS": 1.149 },
"webgpu:shader,validation,expression,call,builtin,log2:values:*": { "subcaseMS": 0.398 },
+ "webgpu:shader,validation,expression,call,builtin,log:args:*": { "subcaseMS": 5.197 },
"webgpu:shader,validation,expression,call,builtin,log:integer_argument:*": { "subcaseMS": 1.134 },
+ "webgpu:shader,validation,expression,call,builtin,log:must_use:*": { "subcaseMS": 1597.590 },
"webgpu:shader,validation,expression,call,builtin,log:values:*": { "subcaseMS": 0.291 },
+ "webgpu:shader,validation,expression,call,builtin,max:args:*": { "subcaseMS": 77.529 },
+ "webgpu:shader,validation,expression,call,builtin,max:must_use:*": { "subcaseMS": 8.286 },
+ "webgpu:shader,validation,expression,call,builtin,max:values:*": { "subcaseMS": 260241.074 },
+ "webgpu:shader,validation,expression,call,builtin,min:args:*": { "subcaseMS": 67.260 },
+ "webgpu:shader,validation,expression,call,builtin,min:must_use:*": { "subcaseMS": 3.899 },
+ "webgpu:shader,validation,expression,call,builtin,min:values:*": { "subcaseMS": 246733.594 },
"webgpu:shader,validation,expression,call,builtin,modf:integer_argument:*": { "subcaseMS": 1.089 },
"webgpu:shader,validation,expression,call,builtin,modf:values:*": { "subcaseMS": 1.866 },
+ "webgpu:shader,validation,expression,call,builtin,normalize:args:*": { "subcaseMS": 25.416 },
+ "webgpu:shader,validation,expression,call,builtin,normalize:invalid_argument:*": { "subcaseMS": 3.258 },
+ "webgpu:shader,validation,expression,call,builtin,normalize:must_use:*": { "subcaseMS": 5.626 },
+ "webgpu:shader,validation,expression,call,builtin,normalize:values:*": { "subcaseMS": 3241.079 },
+ "webgpu:shader,validation,expression,call,builtin,pack4xI8:args:*": { "subcaseMS": 36.226 },
+ "webgpu:shader,validation,expression,call,builtin,pack4xI8:must_use:*": { "subcaseMS": 6.500 },
+ "webgpu:shader,validation,expression,call,builtin,pack4xI8:supported:*": { "subcaseMS": 113.501 },
+ "webgpu:shader,validation,expression,call,builtin,pack4xI8:unsupported:*": { "subcaseMS": 739.400 },
+ "webgpu:shader,validation,expression,call,builtin,pack4xI8Clamp:args:*": { "subcaseMS": 21.994 },
+ "webgpu:shader,validation,expression,call,builtin,pack4xI8Clamp:must_use:*": { "subcaseMS": 34.301 },
+ "webgpu:shader,validation,expression,call,builtin,pack4xI8Clamp:supported:*": { "subcaseMS": 100.450 },
+ "webgpu:shader,validation,expression,call,builtin,pack4xI8Clamp:unsupported:*": { "subcaseMS": 751.101 },
+ "webgpu:shader,validation,expression,call,builtin,pack4xU8:args:*": { "subcaseMS": 24.783 },
+ "webgpu:shader,validation,expression,call,builtin,pack4xU8:must_use:*": { "subcaseMS": 5.300 },
+ "webgpu:shader,validation,expression,call,builtin,pack4xU8:supported:*": { "subcaseMS": 449.800 },
+ "webgpu:shader,validation,expression,call,builtin,pack4xU8:unsupported:*": { "subcaseMS": 773.702 },
+ "webgpu:shader,validation,expression,call,builtin,pack4xU8Clamp:args:*": { "subcaseMS": 26.118 },
+ "webgpu:shader,validation,expression,call,builtin,pack4xU8Clamp:must_use:*": { "subcaseMS": 32.600 },
+ "webgpu:shader,validation,expression,call,builtin,pack4xU8Clamp:supported:*": { "subcaseMS": 134.750 },
+ "webgpu:shader,validation,expression,call,builtin,pack4xU8Clamp:unsupported:*": { "subcaseMS": 570.500 },
+ "webgpu:shader,validation,expression,call,builtin,quantizeToF16:args:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,quantizeToF16:must_use:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,quantizeToF16:values:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,radians:args:*": { "subcaseMS": 4.561 },
"webgpu:shader,validation,expression,call,builtin,radians:integer_argument:*": { "subcaseMS": 1.811 },
+ "webgpu:shader,validation,expression,call,builtin,radians:must_use:*": { "subcaseMS": 0.757 },
"webgpu:shader,validation,expression,call,builtin,radians:values:*": { "subcaseMS": 0.382 },
+ "webgpu:shader,validation,expression,call,builtin,reflect:args:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,reflect:must_use:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,reflect:values:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,reverseBits:arguments:*": { "subcaseMS": 77.380 },
+ "webgpu:shader,validation,expression,call,builtin,reverseBits:float_argument:*": { "subcaseMS": 28.806 },
+ "webgpu:shader,validation,expression,call,builtin,reverseBits:must_use:*": { "subcaseMS": 2.063 },
+ "webgpu:shader,validation,expression,call,builtin,reverseBits:values:*": { "subcaseMS": 3140.778 },
"webgpu:shader,validation,expression,call,builtin,round:integer_argument:*": { "subcaseMS": 1.834 },
"webgpu:shader,validation,expression,call,builtin,round:values:*": { "subcaseMS": 0.382 },
"webgpu:shader,validation,expression,call,builtin,saturate:integer_argument:*": { "subcaseMS": 1.878 },
"webgpu:shader,validation,expression,call,builtin,saturate:values:*": { "subcaseMS": 0.317 },
- "webgpu:shader,validation,expression,call,builtin,sign:unsigned_integer_argument:*": { "subcaseMS": 1.120 },
+ "webgpu:shader,validation,expression,call,builtin,select:argument_types_1_and_2:*": { "subcaseMS": 101642.926 },
+ "webgpu:shader,validation,expression,call,builtin,select:argument_types_3:*": { "subcaseMS": 148.474 },
+ "webgpu:shader,validation,expression,call,builtin,select:arguments:*": { "subcaseMS": 66398.067 },
+ "webgpu:shader,validation,expression,call,builtin,select:must_use:*": { "subcaseMS": 4.312 },
+ "webgpu:shader,validation,expression,call,builtin,sign:args:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,sign:must_use:*": { "subcaseMS": 1.000 },
"webgpu:shader,validation,expression,call,builtin,sign:values:*": { "subcaseMS": 0.343 },
+ "webgpu:shader,validation,expression,call,builtin,sin:args:*": { "subcaseMS": 4.443 },
"webgpu:shader,validation,expression,call,builtin,sin:integer_argument:*": { "subcaseMS": 1.189 },
+ "webgpu:shader,validation,expression,call,builtin,sin:must_use:*": { "subcaseMS": 0.588 },
"webgpu:shader,validation,expression,call,builtin,sin:values:*": { "subcaseMS": 0.349 },
- "webgpu:shader,validation,expression,call,builtin,sinh:integer_argument:*": { "subcaseMS": 1.078 },
+ "webgpu:shader,validation,expression,call,builtin,sinh:args:*": { "subcaseMS": 42.542 },
+ "webgpu:shader,validation,expression,call,builtin,sinh:must_use:*": { "subcaseMS": 5.980 },
"webgpu:shader,validation,expression,call,builtin,sinh:values:*": { "subcaseMS": 0.357 },
+ "webgpu:shader,validation,expression,call,builtin,smoothstep:argument_types:*": { "subcaseMS": 69163.359 },
+ "webgpu:shader,validation,expression,call,builtin,smoothstep:arguments:*": { "subcaseMS": 131.134 },
+ "webgpu:shader,validation,expression,call,builtin,smoothstep:values:*": { "subcaseMS": 81643.500 },
+ "webgpu:shader,validation,expression,call,builtin,sqrt:args:*": { "subcaseMS": 5.398 },
"webgpu:shader,validation,expression,call,builtin,sqrt:integer_argument:*": { "subcaseMS": 1.356 },
+ "webgpu:shader,validation,expression,call,builtin,sqrt:must_use:*": { "subcaseMS": 1.286 },
"webgpu:shader,validation,expression,call,builtin,sqrt:values:*": { "subcaseMS": 0.302 },
- "webgpu:shader,validation,expression,call,builtin,tan:integer_argument:*": { "subcaseMS": 1.734 },
+ "webgpu:shader,validation,expression,call,builtin,step:args:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,step:must_use:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,step:values:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,tan:args:*": { "subcaseMS": 43.560 },
+ "webgpu:shader,validation,expression,call,builtin,tan:must_use:*": { "subcaseMS": 5.401 },
"webgpu:shader,validation,expression,call,builtin,tan:values:*": { "subcaseMS": 0.350 },
+ "webgpu:shader,validation,expression,call,builtin,tanh:args:*": { "subcaseMS": 43.571 },
+ "webgpu:shader,validation,expression,call,builtin,tanh:must_use:*": { "subcaseMS": 6.270 },
+ "webgpu:shader,validation,expression,call,builtin,tanh:values:*": { "subcaseMS": 4590.491 },
+ "webgpu:shader,validation,expression,call,builtin,textureGather:array_index_argument:*": { "subcaseMS": 1.123 },
+ "webgpu:shader,validation,expression,call,builtin,textureGather:component_argument,non_const:*": { "subcaseMS": 1.731 },
+ "webgpu:shader,validation,expression,call,builtin,textureGather:component_argument:*": { "subcaseMS": 1.321 },
+ "webgpu:shader,validation,expression,call,builtin,textureGather:coords_argument:*": { "subcaseMS": 1.352 },
+ "webgpu:shader,validation,expression,call,builtin,textureGather:offset_argument,non_const:*": { "subcaseMS": 1.231 },
+ "webgpu:shader,validation,expression,call,builtin,textureGather:offset_argument:*": { "subcaseMS": 1.534 },
+ "webgpu:shader,validation,expression,call,builtin,textureGatherCompare:array_index_argument:*": { "subcaseMS": 1.932 },
+ "webgpu:shader,validation,expression,call,builtin,textureGatherCompare:coords_argument:*": { "subcaseMS": 1.764 },
+ "webgpu:shader,validation,expression,call,builtin,textureGatherCompare:depth_ref_argument:*": { "subcaseMS": 1.753 },
+ "webgpu:shader,validation,expression,call,builtin,textureGatherCompare:offset_argument,non_const:*": { "subcaseMS": 1.534 },
+ "webgpu:shader,validation,expression,call,builtin,textureGatherCompare:offset_argument:*": { "subcaseMS": 1.243 },
+ "webgpu:shader,validation,expression,call,builtin,textureLoad:array_index_argument,non_storage:*": { "subcaseMS": 1.358 },
+ "webgpu:shader,validation,expression,call,builtin,textureLoad:array_index_argument,storage:*": { "subcaseMS": 1.906 },
+ "webgpu:shader,validation,expression,call,builtin,textureLoad:coords_argument,non_storage:*": { "subcaseMS": 1.717 },
+ "webgpu:shader,validation,expression,call,builtin,textureLoad:coords_argument,storage:*": { "subcaseMS": 1.750 },
+ "webgpu:shader,validation,expression,call,builtin,textureLoad:level_argument,non_storage:*": { "subcaseMS": 1.113 },
+ "webgpu:shader,validation,expression,call,builtin,textureLoad:sample_index_argument,non_storage:*": { "subcaseMS": 1.395 },
+ "webgpu:shader,validation,expression,call,builtin,textureSample:array_index_argument:*": { "subcaseMS": 1.888 },
+ "webgpu:shader,validation,expression,call,builtin,textureSample:coords_argument:*": { "subcaseMS": 1.342 },
+ "webgpu:shader,validation,expression,call,builtin,textureSample:offset_argument,non_const:*": { "subcaseMS": 1.604 },
+ "webgpu:shader,validation,expression,call,builtin,textureSample:offset_argument:*": { "subcaseMS": 1.401 },
+ "webgpu:shader,validation,expression,call,builtin,textureSample:only_in_fragment:*": { "subcaseMS": 1.121 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleBaseClampToEdge:coords_argument:*": { "subcaseMS": 239.356 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleBias:array_index_argument:*": { "subcaseMS": 1.630 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleBias:bias_argument:*": { "subcaseMS": 1.102 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleBias:coords_argument:*": { "subcaseMS": 1.938 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleBias:offset_argument,non_const:*": { "subcaseMS": 1.985 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleBias:offset_argument:*": { "subcaseMS": 1.081 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleBias:only_in_fragment:*": { "subcaseMS": 1.181 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleCompare:array_index_argument:*": { "subcaseMS": 1.932 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleCompare:coords_argument:*": { "subcaseMS": 1.282 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleCompare:depth_ref_argument:*": { "subcaseMS": 1.563 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleCompare:offset_argument,non_const:*": { "subcaseMS": 1.720 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleCompare:offset_argument:*": { "subcaseMS": 1.540 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleCompare:only_in_fragment:*": { "subcaseMS": 1.121 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleCompareLevel:array_index_argument:*": { "subcaseMS": 1.989 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleCompareLevel:coords_argument:*": { "subcaseMS": 1.932 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleCompareLevel:depth_ref_argument:*": { "subcaseMS": 1.578 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleCompareLevel:offset_argument,non_const:*": { "subcaseMS": 1.347 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleCompareLevel:offset_argument:*": { "subcaseMS": 1.619 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleGrad:array_index_argument:*": { "subcaseMS": 1.740 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleGrad:coords_argument:*": { "subcaseMS": 1.802 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleGrad:ddX_argument:*": { "subcaseMS": 1.882 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleGrad:ddY_argument:*": { "subcaseMS": 1.515 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleGrad:offset_argument,non_const:*": { "subcaseMS": 1.987 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleGrad:offset_argument:*": { "subcaseMS": 1.317 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleLevel:array_index_argument:*": { "subcaseMS": 1.888 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleLevel:coords_argument:*": { "subcaseMS": 1.342 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleLevel:level_argument:*": { "subcaseMS": 1.422 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleLevel:offset_argument,non_const:*": { "subcaseMS": 1.604 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleLevel:offset_argument:*": { "subcaseMS": 1.401 },
+ "webgpu:shader,validation,expression,call,builtin,textureStore:array_index_argument:*": { "subcaseMS": 1.240 },
+ "webgpu:shader,validation,expression,call,builtin,textureStore:coords_argument:*": { "subcaseMS": 1.350 },
+ "webgpu:shader,validation,expression,call,builtin,textureStore:value_argument:*": { "subcaseMS": 1.442 },
+ "webgpu:shader,validation,expression,call,builtin,trunc:args:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,trunc:must_use:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,trunc:values:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,unpack4xI8:args:*": { "subcaseMS": 23.923 },
+ "webgpu:shader,validation,expression,call,builtin,unpack4xI8:must_use:*": { "subcaseMS": 35.200 },
+ "webgpu:shader,validation,expression,call,builtin,unpack4xI8:supported:*": { "subcaseMS": 24.150 },
+ "webgpu:shader,validation,expression,call,builtin,unpack4xI8:unsupported:*": { "subcaseMS": 615.301 },
+ "webgpu:shader,validation,expression,call,builtin,unpack4xU8:args:*": { "subcaseMS": 21.830 },
+ "webgpu:shader,validation,expression,call,builtin,unpack4xU8:must_use:*": { "subcaseMS": 32.800 },
+ "webgpu:shader,validation,expression,call,builtin,unpack4xU8:supported:*": { "subcaseMS": 98.501 },
+ "webgpu:shader,validation,expression,call,builtin,unpack4xU8:unsupported:*": { "subcaseMS": 346.801 },
+ "webgpu:shader,validation,expression,call,builtin,workgroupUniformLoad:no_atomics:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,workgroupUniformLoad:only_in_compute:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,overload_resolution:implicit_conversions:*": { "subcaseMS": 519.260 },
+ "webgpu:shader,validation,expression,overload_resolution:overload_resolution:*": { "subcaseMS": 436.603 },
+ "webgpu:shader,validation,expression,precedence:binary_requires_parentheses:*": { "subcaseMS": 149.921 },
+ "webgpu:shader,validation,expression,precedence:mixed_logical_requires_parentheses:*": { "subcaseMS": 6.107 },
+ "webgpu:shader,validation,expression,precedence:other:*": { "subcaseMS": 5.459 },
+ "webgpu:shader,validation,expression,unary,address_of_and_indirection:basic:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,expression,unary,address_of_and_indirection:composite:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,expression,unary,address_of_and_indirection:invalid:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,expression,unary,arithmetic_negation:invalid_types:*": { "subcaseMS": 25.894 },
+ "webgpu:shader,validation,expression,unary,arithmetic_negation:scalar_vector:*": { "subcaseMS": 31.344 },
+ "webgpu:shader,validation,expression,unary,bitwise_complement:invalid_types:*": { "subcaseMS": 7.564 },
+ "webgpu:shader,validation,expression,unary,bitwise_complement:scalar_vector:*": { "subcaseMS": 73.852 },
+ "webgpu:shader,validation,expression,unary,logical_negation:invalid_types:*": { "subcaseMS": 7.100 },
+ "webgpu:shader,validation,expression,unary,logical_negation:scalar_vector:*": { "subcaseMS": 84.680 },
+ "webgpu:shader,validation,extension,pointer_composite_access:deref:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,extension,pointer_composite_access:pointer:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,extension,readonly_and_readwrite_storage_textures:textureBarrier:*": { "subcaseMS": 1.141 },
+ "webgpu:shader,validation,extension,readonly_and_readwrite_storage_textures:var_decl:*": { "subcaseMS": 42.299 },
"webgpu:shader,validation,functions,alias_analysis:aliasing_inside_function:*": { "subcaseMS": 1.200 },
"webgpu:shader,validation,functions,alias_analysis:member_accessors:*": { "subcaseMS": 1.656 },
+ "webgpu:shader,validation,functions,alias_analysis:one_atomic_pointer_one_module_scope:*": { "subcaseMS": 0.000 },
"webgpu:shader,validation,functions,alias_analysis:one_pointer_one_module_scope:*": { "subcaseMS": 1.598 },
"webgpu:shader,validation,functions,alias_analysis:same_pointer_read_and_write:*": { "subcaseMS": 1.301 },
"webgpu:shader,validation,functions,alias_analysis:subcalls:*": { "subcaseMS": 1.673 },
+ "webgpu:shader,validation,functions,alias_analysis:two_atomic_pointers:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,functions,alias_analysis:two_atomic_pointers_to_array_elements:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,functions,alias_analysis:two_atomic_pointers_to_struct_members:*": { "subcaseMS": 0.000 },
"webgpu:shader,validation,functions,alias_analysis:two_pointers:*": { "subcaseMS": 1.537 },
- "webgpu:shader,validation,functions,restrictions:call_arg_types_match_params:*": { "subcaseMS": 1.518 },
+ "webgpu:shader,validation,functions,alias_analysis:two_pointers_to_array_elements:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,functions,alias_analysis:two_pointers_to_array_elements_indirect:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,functions,alias_analysis:two_pointers_to_struct_members:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,functions,alias_analysis:two_pointers_to_struct_members_indirect:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,functions,alias_analysis:workgroup_uniform_load:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,functions,restrictions:call_arg_types_match_1_param:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,functions,restrictions:call_arg_types_match_2_params:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,functions,restrictions:call_arg_types_match_3_params:*": { "subcaseMS": 0.000 },
"webgpu:shader,validation,functions,restrictions:entry_point_call_target:*": { "subcaseMS": 1.734 },
"webgpu:shader,validation,functions,restrictions:function_parameter_matching:*": { "subcaseMS": 1.953 },
"webgpu:shader,validation,functions,restrictions:function_parameter_types:*": { "subcaseMS": 1.520 },
@@ -1774,17 +2271,23 @@
"webgpu:shader,validation,parse,blankspace:bom:*": { "subcaseMS": 1.101 },
"webgpu:shader,validation,parse,blankspace:null_characters:*": { "subcaseMS": 3.217 },
"webgpu:shader,validation,parse,break:placement:*": { "subcaseMS": 1.254 },
+ "webgpu:shader,validation,parse,break_if:non_bool_param:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,parse,break_if:placement:*": { "subcaseMS": 0.000 },
"webgpu:shader,validation,parse,builtin:parse:*": { "subcaseMS": 3.277 },
"webgpu:shader,validation,parse,builtin:placement:*": { "subcaseMS": 1.267 },
"webgpu:shader,validation,parse,comments:comments:*": { "subcaseMS": 1.000 },
"webgpu:shader,validation,parse,comments:line_comment_eof:*": { "subcaseMS": 4.500 },
"webgpu:shader,validation,parse,comments:line_comment_terminators:*": { "subcaseMS": 1.021 },
"webgpu:shader,validation,parse,comments:unterminated_block_comment:*": { "subcaseMS": 8.950 },
+ "webgpu:shader,validation,parse,compound:parse:*": { "subcaseMS": 4.315 },
"webgpu:shader,validation,parse,const:placement:*": { "subcaseMS": 1.167 },
"webgpu:shader,validation,parse,const_assert:parse:*": { "subcaseMS": 1.400 },
+ "webgpu:shader,validation,parse,continuing:placement:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,parse,diagnostic:after_other_directives:*": { "subcaseMS": 1.000 },
"webgpu:shader,validation,parse,diagnostic:conflicting_attribute_different_location:*": { "subcaseMS": 2.257 },
"webgpu:shader,validation,parse,diagnostic:conflicting_attribute_same_location:*": { "subcaseMS": 1.400 },
"webgpu:shader,validation,parse,diagnostic:conflicting_directive:*": { "subcaseMS": 1.244 },
+ "webgpu:shader,validation,parse,diagnostic:diagnostic_scoping:*": { "subcaseMS": 1.244 },
"webgpu:shader,validation,parse,diagnostic:invalid_locations:*": { "subcaseMS": 1.930 },
"webgpu:shader,validation,parse,diagnostic:invalid_severity:*": { "subcaseMS": 1.361 },
"webgpu:shader,validation,parse,diagnostic:valid_locations:*": { "subcaseMS": 1.368 },
@@ -1814,14 +2317,17 @@
"webgpu:shader,validation,parse,must_use:builtin_no_must_use:*": { "subcaseMS": 1.206 },
"webgpu:shader,validation,parse,must_use:call:*": { "subcaseMS": 1.275 },
"webgpu:shader,validation,parse,must_use:declaration:*": { "subcaseMS": 1.523 },
+ "webgpu:shader,validation,parse,must_use:ignore_result_of_non_must_use_that_returns_call_of_must_use:*": { "subcaseMS": 0.000 },
"webgpu:shader,validation,parse,pipeline_stage:compute_parsing:*": { "subcaseMS": 1.000 },
- "webgpu:shader,validation,parse,pipeline_stage:duplicate_compute_on_function:*": { "subcaseMS": 2.651 },
- "webgpu:shader,validation,parse,pipeline_stage:duplicate_fragment_on_function:*": { "subcaseMS": 1.001 },
- "webgpu:shader,validation,parse,pipeline_stage:duplicate_vertex_on_function:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,parse,pipeline_stage:extra_on_compute_function:*": { "subcaseMS": 2.651 },
+ "webgpu:shader,validation,parse,pipeline_stage:extra_on_fragment_function:*": { "subcaseMS": 1.001 },
+ "webgpu:shader,validation,parse,pipeline_stage:extra_on_vertex_function:*": { "subcaseMS": 1.000 },
"webgpu:shader,validation,parse,pipeline_stage:fragment_parsing:*": { "subcaseMS": 2.600 },
"webgpu:shader,validation,parse,pipeline_stage:multiple_entry_points:*": { "subcaseMS": 1.100 },
"webgpu:shader,validation,parse,pipeline_stage:placement:*": { "subcaseMS": 1.388 },
"webgpu:shader,validation,parse,pipeline_stage:vertex_parsing:*": { "subcaseMS": 1.500 },
+ "webgpu:shader,validation,parse,requires:requires:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,parse,requires:wgsl_matches_api:*": { "subcaseMS": 1.000 },
"webgpu:shader,validation,parse,semicolon:after_assignment:*": { "subcaseMS": 1.400 },
"webgpu:shader,validation,parse,semicolon:after_call:*": { "subcaseMS": 1.301 },
"webgpu:shader,validation,parse,semicolon:after_case:*": { "subcaseMS": 1.301 },
@@ -1830,6 +2336,7 @@
"webgpu:shader,validation,parse,semicolon:after_continuing:*": { "subcaseMS": 0.900 },
"webgpu:shader,validation,parse,semicolon:after_default_case:*": { "subcaseMS": 3.100 },
"webgpu:shader,validation,parse,semicolon:after_default_case_break:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,parse,semicolon:after_diagnostic:*": { "subcaseMS": 1.000 },
"webgpu:shader,validation,parse,semicolon:after_discard:*": { "subcaseMS": 4.400 },
"webgpu:shader,validation,parse,semicolon:after_enable:*": { "subcaseMS": 1.301 },
"webgpu:shader,validation,parse,semicolon:after_fn_const_assert:*": { "subcaseMS": 1.400 },
@@ -1848,6 +2355,7 @@
"webgpu:shader,validation,parse,semicolon:after_member:*": { "subcaseMS": 4.801 },
"webgpu:shader,validation,parse,semicolon:after_module_const_decl:*": { "subcaseMS": 1.400 },
"webgpu:shader,validation,parse,semicolon:after_module_var_decl:*": { "subcaseMS": 0.901 },
+ "webgpu:shader,validation,parse,semicolon:after_requires:*": { "subcaseMS": 1.000 },
"webgpu:shader,validation,parse,semicolon:after_return:*": { "subcaseMS": 1.201 },
"webgpu:shader,validation,parse,semicolon:after_struct_decl:*": { "subcaseMS": 1.000 },
"webgpu:shader,validation,parse,semicolon:after_switch:*": { "subcaseMS": 1.101 },
@@ -1861,16 +2369,28 @@
"webgpu:shader,validation,parse,semicolon:function_body_single:*": { "subcaseMS": 0.800 },
"webgpu:shader,validation,parse,semicolon:module_scope_multiple:*": { "subcaseMS": 0.900 },
"webgpu:shader,validation,parse,semicolon:module_scope_single:*": { "subcaseMS": 2.100 },
+ "webgpu:shader,validation,parse,shadow_builtins:function_param:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,parse,shadow_builtins:shadow_hides_access_mode:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,parse,shadow_builtins:shadow_hides_builtin:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,parse,shadow_builtins:shadow_hides_builtin_atomic:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,parse,shadow_builtins:shadow_hides_builtin_atomic_type:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,parse,shadow_builtins:shadow_hides_builtin_barriers:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,parse,shadow_builtins:shadow_hides_builtin_f16:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,parse,shadow_builtins:shadow_hides_builtin_handle_type:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,parse,shadow_builtins:shadow_hides_builtin_texture:*": { "subcaseMS": 0.000 },
"webgpu:shader,validation,parse,source:empty:*": { "subcaseMS": 1.101 },
"webgpu:shader,validation,parse,source:invalid_source:*": { "subcaseMS": 1.100 },
"webgpu:shader,validation,parse,source:valid_source:*": { "subcaseMS": 1.101 },
+ "webgpu:shader,validation,parse,statement_behavior:invalid_functions:*": { "subcaseMS": 1.107 },
+ "webgpu:shader,validation,parse,statement_behavior:invalid_statements:*": { "subcaseMS": 80.699 },
+ "webgpu:shader,validation,parse,statement_behavior:valid_functions:*": { "subcaseMS": 0.978 },
+ "webgpu:shader,validation,parse,statement_behavior:valid_statements:*": { "subcaseMS": 15.781 },
"webgpu:shader,validation,parse,unary_ops:all:*": { "subcaseMS": 1.000 },
"webgpu:shader,validation,parse,var_and_let:initializer_type:*": { "subcaseMS": 0.900 },
"webgpu:shader,validation,parse,var_and_let:var_access_mode_bad_other_template_contents:*": { "subcaseMS": 4.071 },
"webgpu:shader,validation,parse,var_and_let:var_access_mode_bad_template_delim:*": { "subcaseMS": 1.088 },
"webgpu:shader,validation,shader_io,binding:binding:*": { "subcaseMS": 1.240 },
"webgpu:shader,validation,shader_io,binding:binding_f16:*": { "subcaseMS": 0.500 },
- "webgpu:shader,validation,shader_io,binding:binding_without_group:*": { "subcaseMS": 0.901 },
"webgpu:shader,validation,shader_io,builtins:duplicates:*": { "subcaseMS": 1.913 },
"webgpu:shader,validation,shader_io,builtins:missing_vertex_position:*": { "subcaseMS": 0.975 },
"webgpu:shader,validation,shader_io,builtins:nesting:*": { "subcaseMS": 2.700 },
@@ -1884,7 +2404,6 @@
"webgpu:shader,validation,shader_io,entry_point:no_entry_point_provided:*": { "subcaseMS": 0.801 },
"webgpu:shader,validation,shader_io,group:group:*": { "subcaseMS": 1.355 },
"webgpu:shader,validation,shader_io,group:group_f16:*": { "subcaseMS": 0.400 },
- "webgpu:shader,validation,shader_io,group:group_without_binding:*": { "subcaseMS": 1.100 },
"webgpu:shader,validation,shader_io,group_and_binding:binding_attributes:*": { "subcaseMS": 1.280 },
"webgpu:shader,validation,shader_io,group_and_binding:different_entry_points:*": { "subcaseMS": 1.833 },
"webgpu:shader,validation,shader_io,group_and_binding:function_scope:*": { "subcaseMS": 1.000 },
@@ -1905,13 +2424,16 @@
"webgpu:shader,validation,shader_io,invariant:not_valid_on_user_defined_io:*": { "subcaseMS": 1.100 },
"webgpu:shader,validation,shader_io,invariant:parsing:*": { "subcaseMS": 1.438 },
"webgpu:shader,validation,shader_io,invariant:valid_only_with_vertex_position_builtin:*": { "subcaseMS": 1.461 },
+ "webgpu:shader,validation,shader_io,layout_constraints:layout_constraints:*": { "subcaseMS": 1.000 },
"webgpu:shader,validation,shader_io,locations:duplicates:*": { "subcaseMS": 1.906 },
"webgpu:shader,validation,shader_io,locations:location_fp16:*": { "subcaseMS": 0.501 },
"webgpu:shader,validation,shader_io,locations:nesting:*": { "subcaseMS": 0.967 },
+ "webgpu:shader,validation,shader_io,locations:out_of_order:*": { "subcaseMS": 1.296 },
"webgpu:shader,validation,shader_io,locations:stage_inout:*": { "subcaseMS": 1.850 },
"webgpu:shader,validation,shader_io,locations:type:*": { "subcaseMS": 1.332 },
"webgpu:shader,validation,shader_io,locations:validation:*": { "subcaseMS": 1.296 },
"webgpu:shader,validation,shader_io,size:size:*": { "subcaseMS": 1.218 },
+ "webgpu:shader,validation,shader_io,size:size_creation_fixed_footprint:*": { "subcaseMS": 1.000 },
"webgpu:shader,validation,shader_io,size:size_fp16:*": { "subcaseMS": 1.500 },
"webgpu:shader,validation,shader_io,size:size_non_struct:*": { "subcaseMS": 0.929 },
"webgpu:shader,validation,shader_io,workgroup_size:workgroup_size:*": { "subcaseMS": 1.227 },
@@ -1921,6 +2443,8 @@
"webgpu:shader,validation,shader_io,workgroup_size:workgroup_size_function:*": { "subcaseMS": 0.800 },
"webgpu:shader,validation,shader_io,workgroup_size:workgroup_size_var:*": { "subcaseMS": 2.101 },
"webgpu:shader,validation,shader_io,workgroup_size:workgroup_size_vertex_shader:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,types,alias:any_type:*": { "subcaseMS": 42.329 },
+ "webgpu:shader,validation,types,alias:match_non_alias:*": { "subcaseMS": 3.767 },
"webgpu:shader,validation,types,alias:no_direct_recursion:*": { "subcaseMS": 1.450 },
"webgpu:shader,validation,types,alias:no_indirect_recursion:*": { "subcaseMS": 1.000 },
"webgpu:shader,validation,types,alias:no_indirect_recursion_via_array_element:*": { "subcaseMS": 1.050 },
@@ -1931,12 +2455,23 @@
"webgpu:shader,validation,types,alias:no_indirect_recursion_via_struct_attribute:*": { "subcaseMS": 1.584 },
"webgpu:shader,validation,types,alias:no_indirect_recursion_via_struct_member:*": { "subcaseMS": 1.000 },
"webgpu:shader,validation,types,alias:no_indirect_recursion_via_vector_element:*": { "subcaseMS": 1.050 },
+ "webgpu:shader,validation,types,array:invalid:*": { "subcaseMS": 2.470 },
+ "webgpu:shader,validation,types,array:valid:*": { "subcaseMS": 449.293 },
+ "webgpu:shader,validation,types,atomics:address_space:*": { "subcaseMS": 1.050 },
+ "webgpu:shader,validation,types,atomics:invalid_operations:*": { "subcaseMS": 1.050 },
+ "webgpu:shader,validation,types,atomics:type:*": { "subcaseMS": 1.050 },
+ "webgpu:shader,validation,types,matrix:invalid:*": { "subcaseMS": 68.086 },
+ "webgpu:shader,validation,types,matrix:valid:*": { "subcaseMS": 67.784 },
"webgpu:shader,validation,types,struct:no_direct_recursion:*": { "subcaseMS": 0.951 },
"webgpu:shader,validation,types,struct:no_indirect_recursion:*": { "subcaseMS": 0.901 },
"webgpu:shader,validation,types,struct:no_indirect_recursion_via_array_element:*": { "subcaseMS": 0.901 },
"webgpu:shader,validation,types,struct:no_indirect_recursion_via_array_size:*": { "subcaseMS": 0.900 },
"webgpu:shader,validation,types,struct:no_indirect_recursion_via_struct_attribute:*": { "subcaseMS": 1.467 },
"webgpu:shader,validation,types,struct:no_indirect_recursion_via_struct_member_nested_in_alias:*": { "subcaseMS": 0.950 },
+ "webgpu:shader,validation,types,textures:sampled_texture_types:*": { "subcaseMS": 7.634 },
+ "webgpu:shader,validation,types,textures:storage_texture_types:*": { "subcaseMS": 167.510 },
+ "webgpu:shader,validation,types,textures:texel_formats,as_value:*": { "subcaseMS": 0.518 },
+ "webgpu:shader,validation,types,textures:texel_formats:*": { "subcaseMS": 1707.432 },
"webgpu:shader,validation,types,vector:vector:*": { "subcaseMS": 1.295 },
"webgpu:shader,validation,uniformity,uniformity:basics:*": { "subcaseMS": 1.467 },
"webgpu:shader,validation,uniformity,uniformity:binary_expressions:*": { "subcaseMS": 1.758 },
@@ -1948,6 +2483,7 @@
"webgpu:shader,validation,uniformity,uniformity:pointers:*": { "subcaseMS": 1.738 },
"webgpu:shader,validation,uniformity,uniformity:short_circuit_expressions:*": { "subcaseMS": 1.401 },
"webgpu:shader,validation,uniformity,uniformity:unary_expressions:*": { "subcaseMS": 1.279 },
+ "webgpu:util,texture,color_space_conversions:util_matches_2d_canvas:*": { "subcaseMS": 1.001 },
"webgpu:util,texture,texel_data:float_texel_data_in_shader:*": { "subcaseMS": 2.042 },
"webgpu:util,texture,texel_data:sint_texel_data_in_shader:*": { "subcaseMS": 2.573 },
"webgpu:util,texture,texel_data:snorm_texel_data_in_shader:*": { "subcaseMS": 4.645 },
@@ -1994,9 +2530,11 @@
"webgpu:web_platform,copyToTexture,image:from_image:*": { "subcaseMS": 21.869 },
"webgpu:web_platform,copyToTexture,video:copy_from_video:*": { "subcaseMS": 25.101 },
"webgpu:web_platform,external_texture,video:importExternalTexture,compute:*": { "subcaseMS": 36.270 },
- "webgpu:web_platform,external_texture,video:importExternalTexture,sample:*": { "subcaseMS": 33.380 },
- "webgpu:web_platform,external_texture,video:importExternalTexture,sampleWithRotationMetadata:*": { "subcaseMS": 34.968 },
+ "webgpu:web_platform,external_texture,video:importExternalTexture,sample:*": { "subcaseMS": 34.968 },
"webgpu:web_platform,external_texture,video:importExternalTexture,sampleWithVideoFrameWithVisibleRectParam:*": { "subcaseMS": 29.160 },
- "webgpu:web_platform,worker,worker:worker:*": { "subcaseMS": 245.901 },
+ "webgpu:web_platform,external_texture,video:importExternalTexture,sample_non_YUV_video_frame:*": { "subcaseMS": 36.270 },
+ "webgpu:web_platform,worker,worker:dedicated_worker:*": { "subcaseMS": 245.901 },
+ "webgpu:web_platform,worker,worker:service_worker:*": { "subcaseMS": 102.800 },
+ "webgpu:web_platform,worker,worker:shared_worker:*": { "subcaseMS": 26.801 },
"_end": ""
}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/multisample_info.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/multisample_info.ts
new file mode 100644
index 0000000000..f8d247a3de
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/multisample_info.ts
@@ -0,0 +1,75 @@
+/* Data used for multisample tests */
+
+const samplePositionToFragmentPosition = (pos: readonly number[]): readonly number[] =>
+ pos.map(v => v / 16);
+const samplePositionsToFragmentPositions = (
+ positions: readonly (readonly number[])[]
+): readonly (readonly number[])[] => positions.map(samplePositionToFragmentPosition);
+
+// These are sample positions based on a 16x16 grid with 0,0 at the top left.
+// For example 8,8 would be a fragment coordinate of 0.5, 0.5
+// Based on: https://learn.microsoft.com/en-us/windows/win32/api/d3d11/ne-d3d11-d3d11_standard_multisample_quality_levels
+const kMultisamplingTables = new Map<number, readonly (readonly number[])[]>([
+ [1, samplePositionsToFragmentPositions([[8, 8]])],
+ [
+ 2,
+ samplePositionsToFragmentPositions([
+ [4, 4],
+ [12, 12],
+ ]),
+ ],
+ [
+ 4,
+ samplePositionsToFragmentPositions([
+ [6, 2],
+ [14, 6],
+ [2, 10],
+ [10, 14],
+ ]),
+ ],
+ [
+ 8,
+ samplePositionsToFragmentPositions([
+ [9, 5],
+ [7, 11],
+ [13, 9],
+ [5, 3],
+ [3, 13],
+ [1, 7],
+ [11, 15],
+ [15, 1],
+ ]),
+ ],
+ [
+ 16,
+ samplePositionsToFragmentPositions([
+ [9, 9],
+ [7, 5],
+ [5, 10],
+ [12, 7],
+
+ [3, 6],
+ [10, 13],
+ [13, 11],
+ [11, 3],
+
+ [6, 14],
+ [8, 1],
+ [4, 2],
+ [2, 12],
+
+ [0, 8],
+ [15, 4],
+ [14, 15],
+ [1, 0],
+ ]),
+ ],
+]);
+
+/**
+ * For a given sampleCount returns an array of 2d fragment offsets
+ * where each offset is between 0 and 1.
+ */
+export function getMultisampleFragmentOffsets(sampleCount: number) {
+ return kMultisamplingTables.get(sampleCount)!;
+}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/print_environment.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/print_environment.spec.ts
new file mode 100644
index 0000000000..44a34c22cb
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/print_environment.spec.ts
@@ -0,0 +1,70 @@
+export const description = `
+
+Examples of writing CTS tests with various features.
+
+Start here when looking for examples of basic framework usage.
+`;
+
+import { getResourcePath } from '../common/framework/resources.js';
+import { globalTestConfig } from '../common/framework/test_config.js';
+import { makeTestGroup } from '../common/framework/test_group.js';
+import { getDefaultRequestAdapterOptions } from '../common/util/navigator_gpu.js';
+
+import { GPUTest } from './gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+/** console.log is disallowed by WPT. Work around it when we're not in WPT. */
+function consoleLogIfNotWPT(x: unknown) {
+ if (!('step_timeout' in globalThis)) {
+ const cons = console;
+ cons.log(x);
+ }
+}
+
+g.test('info')
+ .desc(
+ `Test which prints what global scope (e.g. worker type) it's running in.
+Typically, tests will check for the presence of the feature they need (like HTMLCanvasElement)
+and skip if it's not available.
+
+Run this test under various configurations to see different results
+(Window, worker scopes, Node, etc.)
+
+NOTE: If your test runtime elides logs when tests pass, you won't see the prints from this test
+in the logs. On non-WPT runtimes, it will also print to the console with console.log.
+WPT disallows console.log and doesn't support logs on passing tests, so this does nothing on WPT.`
+ )
+ .fn(async t => {
+ const adapterInfo = await t.adapter.requestAdapterInfo();
+
+ const info = JSON.stringify(
+ {
+ globalScope: Object.getPrototypeOf(globalThis).constructor.name,
+ globalTestConfig,
+ baseResourcePath: getResourcePath(''),
+ defaultRequestAdapterOptions: getDefaultRequestAdapterOptions(),
+ adapterInfo,
+ userAgent: navigator.userAgent,
+ },
+ // Flatten objects with prototype chains into plain objects, using `for..in`. (Otherwise,
+ // properties from the prototype chain will be ignored and things will print as `{}`.)
+ (_key, value) => {
+ if (value === undefined || value === null) return null;
+ if (typeof value !== 'object') return value;
+
+ const valueObj = value as Record<string, unknown>;
+ return Object.fromEntries(
+ (function* () {
+ for (const key in valueObj) {
+ yield [key, valueObj[key]];
+ }
+ })()
+ );
+ },
+ 2
+ );
+
+ t.info(info);
+ consoleLogIfNotWPT(info);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/array/index.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/array/index.spec.ts
new file mode 100644
index 0000000000..c98cf318a2
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/array/index.spec.ts
@@ -0,0 +1,354 @@
+export const description = `
+Execution Tests for array indexing expressions
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import {
+ False,
+ True,
+ Type,
+ Value,
+ array,
+ f32,
+ scalarTypeOf,
+} from '../../../../../util/conversion.js';
+import { align } from '../../../../../util/math.js';
+import { Case } from '../../case.js';
+import { allInputSources, basicExpressionBuilder, run } from '../../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('concrete_scalar')
+ .specURL('https://www.w3.org/TR/WGSL/#array-access-expr')
+ .desc(`Test indexing of an array of concrete scalars`)
+ .params(u =>
+ u
+ .combine(
+ 'inputSource',
+ // 'uniform' address space requires array stride to be multiple of 16 bytes
+ allInputSources.filter(s => s !== 'uniform')
+ )
+ .combine('elementType', ['i32', 'u32', 'f32', 'f16'] as const)
+ .combine('indexType', ['i32', 'u32'] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.elementType === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const elementType = Type[t.params.elementType];
+ const indexType = Type[t.params.indexType];
+ const cases: Case[] = [
+ {
+ input: [
+ array(
+ /* 0 */ elementType.create(10),
+ /* 1 */ elementType.create(11),
+ /* 2 */ elementType.create(12)
+ ),
+ indexType.create(0),
+ ],
+ expected: elementType.create(10),
+ },
+ {
+ input: [
+ array(
+ /* 0 */ elementType.create(20),
+ /* 1 */ elementType.create(21),
+ /* 2 */ elementType.create(22)
+ ),
+ indexType.create(1),
+ ],
+ expected: elementType.create(21),
+ },
+ {
+ input: [
+ array(
+ /* 0 */ elementType.create(30),
+ /* 1 */ elementType.create(31),
+ /* 2 */ elementType.create(32)
+ ),
+ indexType.create(2),
+ ],
+ expected: elementType.create(32),
+ },
+ ];
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${ops[0]}[${ops[1]}]`),
+ [Type.array(3, elementType), indexType],
+ elementType,
+ t.params,
+ cases
+ );
+ });
+
+g.test('bool')
+ .specURL('https://www.w3.org/TR/WGSL/#array-access-expr')
+ .desc(`Test indexing of an array of booleans`)
+ .params(u =>
+ u
+ .combine(
+ 'inputSource',
+ // 'uniform' address space requires array stride to be multiple of 16 bytes
+ allInputSources.filter(s => s !== 'uniform')
+ )
+ .combine('indexType', ['i32', 'u32'] as const)
+ )
+ .fn(async t => {
+ const indexType = Type[t.params.indexType];
+ const cases: Case[] = [
+ {
+ input: [array(True, False, True), indexType.create(0)],
+ expected: True,
+ },
+ {
+ input: [array(True, False, True), indexType.create(1)],
+ expected: False,
+ },
+ {
+ input: [array(True, False, True), indexType.create(2)],
+ expected: True,
+ },
+ ];
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${ops[0]}[${ops[1]}]`),
+ [Type.array(3, Type.bool), indexType],
+ Type.bool,
+ t.params,
+ cases
+ );
+ });
+
+g.test('abstract_scalar')
+ .specURL('https://www.w3.org/TR/WGSL/#array-access-expr')
+ .desc(`Test indexing of an array of scalars`)
+ .params(u =>
+ u
+ .combine('elementType', ['abstract-int', 'abstract-float'] as const)
+ .combine('indexType', ['i32', 'u32'] as const)
+ )
+ .fn(async t => {
+ const elementType = Type[t.params.elementType];
+ const indexType = Type[t.params.indexType];
+ const cases: Case[] = [
+ {
+ input: [
+ array(
+ /* 0 */ elementType.create(0x10_00000000),
+ /* 1 */ elementType.create(0x11_00000000),
+ /* 2 */ elementType.create(0x12_00000000)
+ ),
+ indexType.create(0),
+ ],
+ expected: f32(0x10),
+ },
+ {
+ input: [
+ array(
+ /* 0 */ elementType.create(0x20_00000000),
+ /* 1 */ elementType.create(0x21_00000000),
+ /* 2 */ elementType.create(0x22_00000000)
+ ),
+ indexType.create(1),
+ ],
+ expected: f32(0x21),
+ },
+ {
+ input: [
+ array(
+ /* 0 */ elementType.create(0x30_00000000),
+ /* 1 */ elementType.create(0x31_00000000),
+ /* 2 */ elementType.create(0x32_00000000)
+ ),
+ indexType.create(2),
+ ],
+ expected: f32(0x32),
+ },
+ ];
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${ops[0]}[${ops[1]}] / 0x100000000`),
+ [Type.array(3, elementType), indexType],
+ Type.f32,
+ { inputSource: 'const' },
+ cases
+ );
+ });
+
+g.test('runtime_sized')
+ .specURL('https://www.w3.org/TR/WGSL/#array-access-expr')
+ .desc(`Test indexing of a runtime sized array`)
+ .params(u =>
+ u
+ .combine('elementType', [
+ 'i32',
+ 'u32',
+ 'f32',
+ 'f16',
+ 'vec4i',
+ 'vec2u',
+ 'vec3f',
+ 'vec2h',
+ ] as const)
+ .combine('indexType', ['i32', 'u32'] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(Type[t.params.elementType]).kind === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const elementType = Type[t.params.elementType];
+ const valueArrayType = Type.array(0, elementType);
+ const indexType = Type[t.params.indexType];
+ const indexArrayType = Type.array(0, indexType);
+
+ const wgsl = `
+${scalarTypeOf(elementType).kind === 'f16' ? 'enable f16;' : ''}
+
+@group(0) @binding(0) var<storage, read> input_values : ${valueArrayType};
+@group(0) @binding(1) var<storage, read> input_indices : ${indexArrayType};
+@group(0) @binding(2) var<storage, read_write> output : ${valueArrayType};
+
+@compute @workgroup_size(16)
+fn main(@builtin(local_invocation_index) invocation_id : u32) {
+ let index = input_indices[invocation_id];
+ output[invocation_id] = input_values[index];
+}
+`;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main',
+ },
+ });
+
+ const values = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53];
+ const indices = [9, 0, 14, 10, 12, 4, 15, 3, 5, 6, 11, 2, 8, 13, 7, 1];
+
+ const inputValues = values.map(i => elementType.create(i));
+ const inputIndices = indices.map(i => indexType.create(i));
+ const expected = indices.map(i => inputValues[i]);
+
+ const bufferSize = (arr: Value[]) => {
+ let offset = 0;
+ let alignment = 0;
+ for (const value of arr) {
+ alignment = Math.max(alignment, value.type.alignment);
+ offset = align(offset, value.type.alignment) + value.type.size;
+ }
+ return align(offset, alignment);
+ };
+
+ const toArray = (arr: Value[]) => {
+ const array = new Uint8Array(bufferSize(arr));
+ let offset = 0;
+ for (const value of arr) {
+ offset = align(offset, value.type.alignment);
+ value.copyTo(array, offset);
+ offset += value.type.size;
+ }
+ return array;
+ };
+
+ const inputArrayBuffer = t.makeBufferWithContents(toArray(inputValues), GPUBufferUsage.STORAGE);
+ const inputIndexBuffer = t.makeBufferWithContents(
+ toArray(inputIndices),
+ GPUBufferUsage.STORAGE
+ );
+ const outputBuffer = t.device.createBuffer({
+ size: bufferSize(expected),
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ });
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: { buffer: inputArrayBuffer } },
+ { binding: 1, resource: { buffer: inputIndexBuffer } },
+ { binding: 2, resource: { buffer: outputBuffer } },
+ ],
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(1);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ t.expectGPUBufferValuesEqual(outputBuffer, toArray(expected));
+ });
+
+g.test('vector')
+ .specURL('https://www.w3.org/TR/WGSL/#array-access-expr')
+ .desc(`Test indexing of an array of vectors`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .expand('elementType', t =>
+ t.inputSource === 'uniform'
+ ? (['vec4i', 'vec4u', 'vec4f'] as const)
+ : (['vec4i', 'vec4u', 'vec4f', 'vec4h'] as const)
+ )
+ .combine('indexType', ['i32', 'u32'] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.elementType === 'vec4h') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const elementType = Type[t.params.elementType];
+ const indexType = Type[t.params.indexType];
+ const cases: Case[] = [
+ {
+ input: [
+ array(
+ /* 0 */ elementType.create([0x10, 0x11, 0x12, 0x13]),
+ /* 1 */ elementType.create([0x14, 0x15, 0x16, 0x17]),
+ /* 2 */ elementType.create([0x18, 0x19, 0x1a, 0x1b])
+ ),
+ indexType.create(0),
+ ],
+ expected: elementType.create([0x10, 0x11, 0x12, 0x13]),
+ },
+ {
+ input: [
+ array(
+ /* 0 */ elementType.create([0x20, 0x21, 0x22, 0x23]),
+ /* 1 */ elementType.create([0x24, 0x25, 0x26, 0x27]),
+ /* 2 */ elementType.create([0x28, 0x29, 0x2a, 0x2b])
+ ),
+ indexType.create(1),
+ ],
+ expected: elementType.create([0x24, 0x25, 0x26, 0x27]),
+ },
+ {
+ input: [
+ array(
+ /* 0 */ elementType.create([0x30, 0x31, 0x32, 0x33]),
+ /* 1 */ elementType.create([0x34, 0x35, 0x36, 0x37]),
+ /* 2 */ elementType.create([0x38, 0x39, 0x3a, 0x3b])
+ ),
+ indexType.create(2),
+ ],
+ expected: elementType.create([0x38, 0x39, 0x3a, 0x3b]),
+ },
+ ];
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${ops[0]}[${ops[1]}]`),
+ [Type.array(3, elementType), indexType],
+ elementType,
+ t.params,
+ cases
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/matrix/index.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/matrix/index.spec.ts
new file mode 100644
index 0000000000..f6fd05b46f
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/matrix/index.spec.ts
@@ -0,0 +1,200 @@
+export const description = `
+Execution Tests for matrix indexing expressions
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import {
+ MatrixValue,
+ ScalarValue,
+ Type,
+ abstractFloat,
+ f32,
+ vec,
+} from '../../../../../util/conversion.js';
+import { Case } from '../../case.js';
+import { allInputSources, basicExpressionBuilder, run } from '../../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('concrete_float_column')
+ .specURL('https://www.w3.org/TR/WGSL/#matrix-access-expr')
+ .desc(`Test indexing a column vector from a concrete matrix`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('elementType', ['f32', 'f16'] as const)
+ .combine('indexType', ['i32', 'u32'] as const)
+ .combine('columns', [2, 3, 4] as const)
+ .combine('rows', [2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.elementType === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const elementType = Type[t.params.elementType];
+ const indexType = Type[t.params.indexType];
+ const matrixType = Type.mat(t.params.columns, t.params.rows, elementType);
+ const columnType = Type.vec(t.params.rows, elementType);
+ const elements: ScalarValue[][] = [];
+ for (let c = 0; c < t.params.columns; c++) {
+ const column: ScalarValue[] = [];
+ for (let r = 0; r < t.params.rows; r++) {
+ column.push(elementType.create((c + 1) * 10 + (r + 1)));
+ }
+ elements.push(column);
+ }
+ const vector = new MatrixValue(elements);
+ const cases: Case[] = [];
+ for (let c = 0; c < t.params.columns; c++) {
+ cases.push({
+ input: [vector, indexType.create(c)],
+ expected: vec(...elements[c]),
+ });
+ }
+
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${ops[0]}[${ops[1]}]`),
+ [matrixType, indexType],
+ columnType,
+ t.params,
+ cases
+ );
+ });
+
+g.test('concrete_float_element')
+ .specURL('https://www.w3.org/TR/WGSL/#matrix-access-expr')
+ .desc(`Test indexing a single element from a concrete matrix`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('elementType', ['f32', 'f16'] as const)
+ .combine('indexType', ['i32', 'u32'] as const)
+ .combine('columns', [2, 3, 4] as const)
+ .combine('rows', [2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.elementType === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const elementType = Type[t.params.elementType];
+ const indexType = Type[t.params.indexType];
+ const matrixType = Type.mat(t.params.columns, t.params.rows, elementType);
+ const columnValues: ScalarValue[][] = [];
+ for (let c = 0; c < t.params.columns; c++) {
+ const column: ScalarValue[] = [];
+ for (let r = 0; r < t.params.rows; r++) {
+ column.push(elementType.create((c + 1) * 10 + (r + 1)));
+ }
+ columnValues.push(column);
+ }
+ const matrix = new MatrixValue(columnValues);
+ const cases: Case[] = [];
+ for (let c = 0; c < t.params.columns; c++) {
+ for (let r = 0; r < t.params.rows; r++) {
+ cases.push({
+ input: [matrix, indexType.create(c), indexType.create(r)],
+ expected: columnValues[c][r],
+ });
+ }
+ }
+
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${ops[0]}[${ops[1]}][${ops[2]}]`),
+ [matrixType, indexType, indexType],
+ elementType,
+ t.params,
+ cases
+ );
+ });
+
+g.test('abstract_float_column')
+ .specURL('https://www.w3.org/TR/WGSL/#matrix-access-expr')
+ .desc(`Test indexing a column vector from a abstract-float matrix`)
+ .params(u =>
+ u
+ .combine('indexType', ['i32', 'u32'] as const)
+ .combine('columns', [2, 3, 4] as const)
+ .combine('rows', [2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const indexType = Type[t.params.indexType];
+ const matrixType = Type.mat(t.params.columns, t.params.rows, Type.abstractFloat);
+ const vecfColumnType = Type.vec(t.params.rows, Type.f32);
+ const values: number[][] = [];
+ for (let c = 0; c < t.params.columns; c++) {
+ const column: number[] = [];
+ for (let r = 0; r < t.params.rows; r++) {
+ column.push((c + 1) * 10 + (r + 1));
+ }
+ values.push(column);
+ }
+ const matrix = new MatrixValue(
+ values.map(column => column.map(v => abstractFloat(v * 0x100000000)))
+ );
+ const cases: Case[] = [];
+ for (let c = 0; c < t.params.columns; c++) {
+ cases.push({
+ input: [matrix, indexType.create(c)],
+ expected: vec(...values[c].map(v => f32(v))),
+ });
+ }
+
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${ops[0]}[${ops[1]}] / 0x100000000`),
+ [matrixType, indexType],
+ vecfColumnType,
+ { inputSource: 'const' },
+ cases
+ );
+ });
+
+g.test('abstract_float_element')
+ .specURL('https://www.w3.org/TR/WGSL/#matrix-access-expr')
+ .desc(`Test indexing a single element from a abstract-float matrix`)
+ .params(u =>
+ u
+ .combine('indexType', ['i32', 'u32'] as const)
+ .combine('columns', [2, 3, 4] as const)
+ .combine('rows', [2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const indexType = Type[t.params.indexType];
+ const matrixType = Type.mat(t.params.columns, t.params.rows, Type.abstractFloat);
+ const values: number[][] = [];
+ for (let c = 0; c < t.params.columns; c++) {
+ const column: number[] = [];
+ for (let r = 0; r < t.params.rows; r++) {
+ column.push((c + 1) * 10 + (r + 1));
+ }
+ values.push(column);
+ }
+ const matrix = new MatrixValue(
+ values.map(column => column.map(v => abstractFloat(v * 0x100000000)))
+ );
+ const cases: Case[] = [];
+ for (let c = 0; c < t.params.columns; c++) {
+ for (let r = 0; r < t.params.rows; r++) {
+ cases.push({
+ input: [matrix, indexType.create(c), indexType.create(r)],
+ expected: f32(values[c][r]),
+ });
+ }
+ }
+
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${ops[0]}[${ops[1]}][${ops[2]}] / 0x100000000`),
+ [matrixType, indexType, indexType],
+ Type.f32,
+ { inputSource: 'const' },
+ cases
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/structure/index.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/structure/index.spec.ts
new file mode 100644
index 0000000000..ae56d11b6b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/structure/index.spec.ts
@@ -0,0 +1,508 @@
+export const description = `
+Execution Tests for structure member accessing expressions
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { ScalarKind, Type, Value, u32 } from '../../../../../util/conversion.js';
+import { align } from '../../../../../util/math.js';
+import { toComparator } from '../../expectation.js';
+import { InputSource, structLayout, structStride } from '../../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+const kMemberTypes = [
+ ['bool'],
+ ['u32'],
+ ['vec3f'],
+ ['i32', 'u32'],
+ ['i32', 'f16', 'vec4i', 'mat3x2f'],
+ ['bool', 'u32', 'f16', 'vec3f', 'vec2i'],
+ ['i32', 'u32', 'f32', 'f16', 'vec3f', 'vec4i'],
+] as readonly ScalarKind[][];
+
+const kMemberTypesNoBool = kMemberTypes.filter(tys => !tys.includes('bool'));
+
+async function run(
+ t: GPUTest,
+ wgsl: string,
+ expected: Value[],
+ input: Value[] | ArrayBufferLike | null,
+ inputSource: InputSource
+) {
+ const outputBufferSize = structStride(
+ expected.map(v => v.type),
+ 'storage_rw'
+ );
+
+ const outputBuffer = t.device.createBuffer({
+ size: outputBufferSize,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE,
+ });
+
+ const bindGroupEntries: GPUBindGroupEntry[] = [
+ {
+ binding: 0,
+ resource: { buffer: outputBuffer },
+ },
+ ];
+
+ if (input !== null) {
+ let inputData: Uint8Array;
+ if (input instanceof Array) {
+ const inputTypes = input.map(v => v.type);
+ const inputBufferSize = structStride(inputTypes, inputSource);
+ inputData = new Uint8Array(inputBufferSize);
+ structLayout(inputTypes, inputSource, m => {
+ input[m.index].copyTo(inputData, m.offset);
+ });
+ } else {
+ inputData = new Uint8Array(input);
+ }
+
+ const inputBuffer = t.makeBufferWithContents(
+ inputData,
+ GPUBufferUsage.COPY_SRC |
+ (inputSource === 'uniform' ? GPUBufferUsage.UNIFORM : GPUBufferUsage.STORAGE)
+ );
+
+ bindGroupEntries.push({
+ binding: 1,
+ resource: { buffer: inputBuffer },
+ });
+ }
+
+ // build the shader module
+ const module = t.device.createShaderModule({ code: wgsl });
+
+ // build the pipeline
+ const pipeline = await t.device.createComputePipelineAsync({
+ layout: 'auto',
+ compute: { module, entryPoint: 'main' },
+ });
+
+ // build the bind group
+ const group = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: bindGroupEntries,
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, group);
+ pass.dispatchWorkgroups(1);
+ pass.end();
+
+ t.queue.submit([encoder.finish()]);
+
+ const checkExpectation = (outputData: Uint8Array) => {
+ // The list of expectation failures
+ const errs: string[] = [];
+
+ let offset = 0;
+ for (let i = 0; i < expected.length; i++) {
+ offset = align(offset, expected[i].type.alignment);
+ const got = expected[i].type.read(outputData, offset);
+ const cmp = toComparator(expected[i]).compare(got);
+ if (!cmp.matched) {
+ errs.push(`result ${i}:)
+ returned: ${cmp.got}
+ expected: ${cmp.expected}`);
+ }
+ offset += expected[i].type.size;
+ }
+
+ return errs.length > 0 ? new Error(errs.join('\n\n')) : undefined;
+ };
+
+ t.expectGPUBufferValuesPassCheck(outputBuffer, checkExpectation, {
+ type: Uint8Array,
+ typedLength: outputBufferSize,
+ });
+}
+
+g.test('buffer')
+ .specURL('https://www.w3.org/TR/WGSL/#struct-access-expr')
+ .desc(`Test accessing of a value structure in a storage or uniform buffer`)
+ .params(u =>
+ u
+ .combine('member_types', kMemberTypesNoBool)
+ .combine('inputSource', ['uniform', 'storage'] as const)
+ .beginSubcases()
+ .expand('member_index', t => t.member_types.map((_, i) => i))
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.member_types.includes('f16')) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const values = t.params.member_types.map((ty, i) => Type[ty].create(i));
+ const expected = values[t.params.member_index];
+
+ await run(
+ t,
+ `
+${t.params.member_types.includes('f16') ? 'enable f16;' : ''}
+
+@group(0) @binding(0) var<storage, read_write> output : ${expected.type};
+@group(0) @binding(1) var<${t.params.inputSource}> input : MyStruct;
+
+struct MyStruct {
+ ${t.params.member_types.map((ty, i) => ` member_${i} : ${ty},`).join('\n')}
+};
+
+@workgroup_size(1) @compute
+fn main() {
+ output = input.member_${t.params.member_index};
+}
+`,
+ /* expected */ [expected],
+ /* input */ values,
+ /* inputSource */ t.params.inputSource === 'uniform' ? 'uniform' : 'storage_r'
+ );
+ });
+
+g.test('buffer_align')
+ .specURL('https://www.w3.org/TR/WGSL/#struct-access-expr')
+ .desc(
+ `Test accessing of a value structure in a storage buffer that has members using the @align attribute`
+ )
+ .params(u =>
+ u
+ .beginSubcases()
+ .combine('member_index', [0, 1, 2] as const)
+ .combine('alignments', [
+ [1, 1, 1],
+ [4, 4, 4],
+ [4, 8, 16],
+ [8, 4, 16],
+ [8, 16, 4],
+ ] as const)
+ )
+ .fn(async t => {
+ const memberTypes = ['i32', 'u32', 'f32'] as const;
+ const values = memberTypes.map((ty, i) => Type[ty].create(i));
+ const expected = values[t.params.member_index];
+ const input = new Uint8Array(64);
+ let offset = 4; // pre : i32
+ for (let i = 0; i < 3; i++) {
+ offset = align(offset, t.params.alignments[i]);
+ values[i].copyTo(input, offset);
+ offset += values[i].type.size;
+ }
+ await run(
+ t,
+ `
+@group(0) @binding(0) var<storage, read_write> output : ${expected.type};
+@group(0) @binding(1) var<storage> input : MyStruct;
+
+struct MyStruct {
+ pre : i32,
+ @align(${t.params.alignments[0]}) member_0 : ${memberTypes[0]},
+ @align(${t.params.alignments[1]}) member_1 : ${memberTypes[1]},
+ @align(${t.params.alignments[2]}) member_2 : ${memberTypes[2]},
+ post : i32,
+};
+
+@workgroup_size(1) @compute
+fn main() {
+output = input.member_${t.params.member_index};
+}
+`,
+ /* expected */ [expected],
+ /* input */ input,
+ /* inputSource */ 'storage_r'
+ );
+ });
+
+g.test('buffer_size')
+ .specURL('https://www.w3.org/TR/WGSL/#struct-access-expr')
+ .desc(
+ `Test accessing of a value structure in a storage buffer that has members using the @size attribute`
+ )
+ .params(u =>
+ u
+ .beginSubcases()
+ .combine('member_index', [0, 1, 2] as const)
+ .combine('sizes', [
+ [4, 4, 4],
+ [4, 8, 16],
+ [8, 4, 16],
+ [8, 16, 4],
+ ] as const)
+ )
+ .fn(async t => {
+ const memberTypes = ['i32', 'u32', 'f32'] as const;
+ const values = memberTypes.map((ty, i) => Type[ty].create(i));
+ const expected = values[t.params.member_index];
+ const input = new Uint8Array(64);
+ let offset = 4; // pre : i32
+ for (let i = 0; i < 3; i++) {
+ offset = align(offset, values[i].type.alignment);
+ values[i].copyTo(input, offset);
+ offset += t.params.sizes[i];
+ }
+ await run(
+ t,
+ `
+@group(0) @binding(0) var<storage, read_write> output : ${expected.type};
+@group(0) @binding(1) var<storage> input : MyStruct;
+
+struct MyStruct {
+ pre : i32,
+ @size(${t.params.sizes[0]}) member_0 : ${memberTypes[0]},
+ @size(${t.params.sizes[1]}) member_1 : ${memberTypes[1]},
+ @size(${t.params.sizes[2]}) member_2 : ${memberTypes[2]},
+ post : i32,
+};
+
+@workgroup_size(1) @compute
+fn main() {
+output = input.member_${t.params.member_index};
+}
+`,
+ /* expected */ [expected],
+ /* input */ input,
+ /* inputSource */ 'storage_r'
+ );
+ });
+
+g.test('buffer_pointer')
+ .specURL('https://www.w3.org/TR/WGSL/#struct-access-expr')
+ .desc(`Test accessing of a value structure via a pointer to a storage or uniform buffer`)
+ .params(u =>
+ u
+ .combine('member_types', kMemberTypesNoBool)
+ .combine('inputSource', ['uniform', 'storage'] as const)
+ .beginSubcases()
+ .expand('member_index', t => t.member_types.map((_, i) => i))
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.member_types.includes('f16')) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const values = t.params.member_types.map((ty, i) => Type[ty].create(i));
+ const expected = values[t.params.member_index];
+
+ await run(
+ t,
+ `
+${t.params.member_types.includes('f16') ? 'enable f16;' : ''}
+
+@group(0) @binding(0) var<storage, read_write> output : ${expected.type};
+@group(0) @binding(1) var<${t.params.inputSource}> input : MyStruct;
+
+struct MyStruct {
+ ${t.params.member_types.map((ty, i) => ` member_${i} : ${ty},`).join('\n')}
+};
+
+@workgroup_size(1) @compute
+fn main() {
+ let ptr = &input;
+ output = (*ptr).member_${t.params.member_index};
+}
+`,
+ /* expected */ [expected],
+ /* input */ values,
+ /* inputSource */ t.params.inputSource === 'uniform' ? 'uniform' : 'storage_r'
+ );
+ });
+
+g.test('let')
+ .specURL('https://www.w3.org/TR/WGSL/#struct-access-expr')
+ .desc(`Test accessing of a let structure`)
+ .params(u =>
+ u
+ .combine('member_types', kMemberTypes)
+ .beginSubcases()
+ .expand('member_index', t => t.member_types.map((_, i) => i))
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.member_types.includes('f16')) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const memberType = Type[t.params.member_types[t.params.member_index]];
+ const values = t.params.member_types.map((ty, i) => Type[ty].create(i));
+ const expected =
+ memberType === Type.bool
+ ? u32(values[t.params.member_index].value === true ? 1 : 0)
+ : values[t.params.member_index];
+
+ await run(
+ t,
+ `
+${t.params.member_types.includes('f16') ? 'enable f16;' : ''}
+
+@group(0) @binding(0) var<storage, read_write> output : ${expected.type};
+
+struct MyStruct {
+ ${t.params.member_types.map((ty, i) => ` member_${i} : ${ty},`).join('\n')}
+};
+
+@workgroup_size(1) @compute
+fn main() {
+ let s = MyStruct(${values.map(v => v.wgsl()).join(', ')});
+ let v = s.member_${t.params.member_index};
+ output = ${memberType === Type.bool ? `select(0u, 1u, v)` : 'v'};
+}
+`,
+ /* expected */ [expected],
+ /* input */ null,
+ /* inputSource */ 'const'
+ );
+ });
+
+g.test('param')
+ .specURL('https://www.w3.org/TR/WGSL/#struct-access-expr')
+ .desc(`Test accessing of a parameter structure`)
+ .params(u =>
+ u
+ .combine('member_types', kMemberTypes)
+ .beginSubcases()
+ .expand('member_index', t => t.member_types.map((_, i) => i))
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.member_types.includes('f16')) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const memberType = Type[t.params.member_types[t.params.member_index]];
+ const values = t.params.member_types.map((ty, i) => Type[ty].create(i));
+ const expected =
+ memberType === Type.bool
+ ? u32(values[t.params.member_index].value === true ? 1 : 0)
+ : values[t.params.member_index];
+
+ await run(
+ t,
+ `
+${t.params.member_types.includes('f16') ? 'enable f16;' : ''}
+
+@group(0) @binding(0) var<storage, read_write> output : ${expected.type};
+
+struct MyStruct {
+ ${t.params.member_types.map((ty, i) => ` member_${i} : ${ty},`).join('\n')}
+};
+
+fn f(s : MyStruct) -> ${t.params.member_types[t.params.member_index]} {
+ return s.member_${t.params.member_index};
+}
+
+@workgroup_size(1) @compute
+fn main() {
+ let v = f(MyStruct(${values.map(v => v.wgsl()).join(', ')}));
+ output = ${memberType === Type.bool ? `select(0u, 1u, v)` : 'v'};
+}
+`,
+ /* expected */ [expected],
+ /* input */ null,
+ /* inputSource */ 'const'
+ );
+ });
+
+g.test('const')
+ .specURL('https://www.w3.org/TR/WGSL/#struct-access-expr')
+ .desc(`Test accessing of a const value structure`)
+ .params(u =>
+ u
+ .combine('member_types', kMemberTypes)
+ .beginSubcases()
+ .expand('member_index', t => t.member_types.map((_, i) => i))
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.member_types.includes('f16')) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const memberType = Type[t.params.member_types[t.params.member_index]];
+ const values = t.params.member_types.map((ty, i) => Type[ty].create(i));
+ const expected =
+ memberType === Type.bool
+ ? u32(values[t.params.member_index].value === true ? 1 : 0)
+ : values[t.params.member_index];
+
+ await run(
+ t,
+ `
+${t.params.member_types.includes('f16') ? 'enable f16;' : ''}
+
+@group(0) @binding(0) var<storage, read_write> output : ${expected.type};
+
+struct MyStruct {
+ ${t.params.member_types.map((ty, i) => ` member_${i} : ${ty},`).join('\n')}
+};
+
+const S = MyStruct(${values.map(v => v.wgsl()).join(', ')});
+
+@workgroup_size(1) @compute
+fn main() {
+ let v = S.member_${t.params.member_index};
+ output = ${memberType === Type.bool ? `select(0u, 1u, v)` : 'v'};
+}
+`,
+ /* expected */ [expected],
+ /* input */ null,
+ /* inputSource */ 'const'
+ );
+ });
+
+g.test('const_nested')
+ .specURL('https://www.w3.org/TR/WGSL/#struct-access-expr')
+ .desc(`Test accessing of a const value structure nested in another structure`)
+ .params(u =>
+ u
+ .combine('member_types', kMemberTypes)
+ .beginSubcases()
+ .expand('member_index', t => t.member_types.map((_, i) => i))
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.member_types.includes('f16')) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const memberType = Type[t.params.member_types[t.params.member_index]];
+ const values = t.params.member_types.map((ty, i) => Type[ty].create(i));
+ const expected =
+ memberType === Type.bool
+ ? u32(values[t.params.member_index].value === true ? 1 : 0)
+ : values[t.params.member_index];
+
+ await run(
+ t,
+ `
+${t.params.member_types.includes('f16') ? 'enable f16;' : ''}
+
+@group(0) @binding(0) var<storage, read_write> output : ${expected.type};
+
+struct MyStruct {
+ ${t.params.member_types.map((ty, i) => ` member_${i} : ${ty},`).join('\n')}
+};
+
+struct Outer {
+ pre : i32,
+ inner : MyStruct,
+ post : i32,
+}
+
+const S = Outer(10, MyStruct(${values.map(v => v.wgsl()).join(', ')}), 20);
+
+@workgroup_size(1) @compute
+fn main() {
+ let v = S.inner.member_${t.params.member_index};
+ output = ${memberType === Type.bool ? `select(0u, 1u, v)` : 'v'};
+}
+`,
+ /* expected */ [expected],
+ /* input */ null,
+ /* inputSource */ 'const'
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/vector/components.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/vector/components.spec.ts
new file mode 100644
index 0000000000..0528337087
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/vector/components.spec.ts
@@ -0,0 +1,118 @@
+export const description = `
+Execution Tests for vector component selection expressions
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { ScalarValue, Type, VectorValue, f32 } from '../../../../../util/conversion.js';
+import { allInputSources, basicExpressionBuilder, run } from '../../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+/** @returns the full permutation of component indices used to component select a vector of width 'n' */
+function indices(n: number) {
+ const out: number[][] = [];
+ for (let width = 1; width < n; width++) {
+ let generate = (swizzle: number[]) => {
+ out.push(swizzle);
+ };
+ for (let i = 0; i < width; i++) {
+ const next = generate;
+ generate = (swizzle: number[]) => {
+ for (let j = 0; j < width; j++) {
+ next([...swizzle, j]);
+ }
+ };
+ }
+ generate([]);
+ }
+ return out;
+}
+
+g.test('concrete_scalar')
+ .specURL('https://www.w3.org/TR/WGSL/#vector-access-expr')
+ .desc(`Test vector component selection of concrete vectors`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('elementType', ['i32', 'u32', 'f32', 'f16', 'bool'] as const)
+ .combine('width', [2, 3, 4] as const)
+ .combine('components', ['rgba', 'xyzw'] as const)
+ .beginSubcases()
+ .expand('indices', u => indices(u.width))
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.elementType === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const elementType = Type[t.params.elementType];
+ const vectorType = Type.vec(t.params.width, elementType);
+ const elementValues =
+ t.params.elementType === 'bool' ? (i: number) => i & 1 : (i: number) => (i + 1) * 10;
+ const elements: ScalarValue[] = [];
+ for (let i = 0; i < t.params.width; i++) {
+ elements.push(elementType.create(elementValues(i)));
+ }
+ const result = (() => {
+ if (t.params.indices.length === 1) {
+ return { type: elementType, value: elementType.create(elementValues(0)) };
+ } else {
+ const vec = Type.vec(t.params.indices.length, elementType);
+ return { type: vec, value: vec.create(t.params.indices.map(i => elementValues(i))) };
+ }
+ })();
+
+ const components = t.params.indices.map(i => t.params.components[i]).join('');
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${ops[0]}.${components}`),
+ [vectorType],
+ result.type,
+ t.params,
+ [{ input: [new VectorValue(elements)], expected: result.value }]
+ );
+ });
+
+g.test('abstract_scalar')
+ .specURL('https://www.w3.org/TR/WGSL/#vector-access-expr')
+ .desc(`Test vector component selection of abstract numeric vectors`)
+ .params(u =>
+ u
+ .combine('elementType', ['abstract-int', 'abstract-float'] as const)
+ .combine('width', [2, 3, 4] as const)
+ .combine('components', ['rgba', 'xyzw'] as const)
+ .beginSubcases()
+ .expand('indices', u => indices(u.width))
+ )
+ .fn(async t => {
+ const elementType = Type[t.params.elementType];
+ const vectorType = Type.vec(t.params.width, elementType);
+ const elementValues = (i: number) => (i + 1) * 0x100000000;
+ const elements: ScalarValue[] = [];
+ for (let i = 0; i < t.params.width; i++) {
+ elements.push(elementType.create(elementValues(i)));
+ }
+ const result = (() => {
+ if (t.params.indices.length === 1) {
+ return { type: Type.f32, value: f32(elementValues(0) / 0x100000000) };
+ } else {
+ const vec = Type.vec(t.params.indices.length, Type.f32);
+ return {
+ type: vec,
+ value: vec.create(t.params.indices.map(i => elementValues(i) / 0x100000000)),
+ };
+ }
+ })();
+
+ const components = t.params.indices.map(i => t.params.components[i]).join('');
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${ops[0]}.${components} / 0x100000000`),
+ [vectorType],
+ result.type,
+ { inputSource: 'const' },
+ [{ input: [new VectorValue(elements)], expected: result.value }]
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/vector/index.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/vector/index.spec.ts
new file mode 100644
index 0000000000..28fbd0e86c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/vector/index.spec.ts
@@ -0,0 +1,87 @@
+export const description = `
+Execution Tests for vector indexing expressions
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { ScalarValue, Type, VectorValue, f32 } from '../../../../../util/conversion.js';
+import { Case } from '../../case.js';
+import { allInputSources, basicExpressionBuilder, run } from '../../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('concrete_scalar')
+ .specURL('https://www.w3.org/TR/WGSL/#vector-access-expr')
+ .desc(`Test indexing of concrete vectors`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('elementType', ['i32', 'u32', 'f32', 'f16', 'bool'] as const)
+ .combine('indexType', ['i32', 'u32'] as const)
+ .combine('width', [2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.elementType === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const elementType = Type[t.params.elementType];
+ const indexType = Type[t.params.indexType];
+ const vectorType = Type.vec(t.params.width, elementType);
+ const elements: ScalarValue[] = [];
+ for (let i = 0; i < t.params.width; i++) {
+ if (t.params.elementType === 'bool') {
+ elements.push(elementType.create(i & 1));
+ } else {
+ elements.push(elementType.create((i + 1) * 10));
+ }
+ }
+ const vector = new VectorValue(elements);
+ const cases: Case[] = [];
+ for (let i = 0; i < t.params.width; i++) {
+ cases.push({ input: [vector, indexType.create(i)], expected: elements[i] });
+ }
+
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${ops[0]}[${ops[1]}]`),
+ [vectorType, indexType],
+ elementType,
+ t.params,
+ cases
+ );
+ });
+
+g.test('abstract_scalar')
+ .specURL('https://www.w3.org/TR/WGSL/#vector-access-expr')
+ .desc(`Test indexing of abstract numeric vectors`)
+ .params(u =>
+ u
+ .combine('elementType', ['abstract-int', 'abstract-float'] as const)
+ .combine('indexType', ['i32', 'u32'] as const)
+ .combine('width', [2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const elementType = Type[t.params.elementType];
+ const indexType = Type[t.params.indexType];
+ const vectorType = Type.vec(t.params.width, elementType);
+ const elements: ScalarValue[] = [];
+ for (let i = 0; i < t.params.width; i++) {
+ elements.push(elementType.create((i + 1) * 0x100000000));
+ }
+ const vector = new VectorValue(elements);
+ const cases: Case[] = [];
+ for (let i = 0; i < t.params.width; i++) {
+ cases.push({ input: [vector, indexType.create(i)], expected: f32(i + 1) });
+ }
+
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${ops[0]}[${ops[1]}] / 0x100000000`),
+ [vectorType, indexType],
+ Type.f32,
+ { inputSource: 'const' },
+ cases
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_addition.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_addition.cache.ts
new file mode 100644
index 0000000000..f5e9d3b481
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_addition.cache.ts
@@ -0,0 +1,54 @@
+import { FP, FPVector } from '../../../../util/floating_point.js';
+import { sparseScalarF64Range, sparseVectorF64Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+const additionVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
+ return FP.abstract.toVector(v.map(e => FP.abstract.additionInterval(e, s)));
+};
+
+const additionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
+ return FP.abstract.toVector(v.map(e => FP.abstract.additionInterval(s, e)));
+};
+
+const scalar_cases = {
+ ['scalar']: () => {
+ return FP.abstract.generateScalarPairToIntervalCases(
+ sparseScalarF64Range(),
+ sparseScalarF64Range(),
+ 'finite',
+ FP.abstract.additionInterval
+ );
+ },
+};
+
+const vector_scalar_cases = ([2, 3, 4] as const)
+ .map(dim => ({
+ [`vec${dim}_scalar`]: () => {
+ return FP.abstract.generateVectorScalarToVectorCases(
+ sparseVectorF64Range(dim),
+ sparseScalarF64Range(),
+ 'finite',
+ additionVectorScalarInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const scalar_vector_cases = ([2, 3, 4] as const)
+ .map(dim => ({
+ [`scalar_vec${dim}`]: () => {
+ return FP.abstract.generateScalarVectorToVectorCases(
+ sparseScalarF64Range(),
+ sparseVectorF64Range(dim),
+ 'finite',
+ additionScalarVectorInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/af_addition', {
+ ...scalar_cases,
+ ...vector_scalar_cases,
+ ...scalar_vector_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_addition.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_addition.spec.ts
index 0f703f0889..52a07ff328 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_addition.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_addition.spec.ts
@@ -1,70 +1,17 @@
export const description = `
-Execution Tests for non-matrix AbstractFloat addition expression
+Execution Tests for non-matrix abstract-float addition expression
`;
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeAbstractFloat, TypeVec } from '../../../../util/conversion.js';
-import { FP, FPVector } from '../../../../util/floating_point.js';
-import { sparseF64Range, sparseVectorF64Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { onlyConstInputSource, run } from '../expression.js';
-import { abstractBinary } from './binary.js';
-
-const additionVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
- return FP.abstract.toVector(v.map(e => FP.abstract.additionInterval(e, s)));
-};
-
-const additionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
- return FP.abstract.toVector(v.map(e => FP.abstract.additionInterval(s, e)));
-};
+import { d } from './af_addition.cache.js';
+import { abstractFloatBinary } from './binary.js';
export const g = makeTestGroup(GPUTest);
-const scalar_cases = {
- ['scalar']: () => {
- return FP.abstract.generateScalarPairToIntervalCases(
- sparseF64Range(),
- sparseF64Range(),
- 'finite',
- FP.abstract.additionInterval
- );
- },
-};
-
-const vector_scalar_cases = ([2, 3, 4] as const)
- .map(dim => ({
- [`vec${dim}_scalar`]: () => {
- return FP.abstract.generateVectorScalarToVectorCases(
- sparseVectorF64Range(dim),
- sparseF64Range(),
- 'finite',
- additionVectorScalarInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const scalar_vector_cases = ([2, 3, 4] as const)
- .map(dim => ({
- [`scalar_vec${dim}`]: () => {
- return FP.abstract.generateScalarVectorToVectorCases(
- sparseF64Range(),
- sparseVectorF64Range(dim),
- 'finite',
- additionScalarVectorInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/af_addition', {
- ...scalar_cases,
- ...vector_scalar_cases,
- ...scalar_vector_cases,
-});
-
g.test('scalar')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -78,9 +25,9 @@ Accuracy: Correctly rounded
const cases = await d.get('scalar');
await run(
t,
- abstractBinary('+'),
- [TypeAbstractFloat, TypeAbstractFloat],
- TypeAbstractFloat,
+ abstractFloatBinary('+'),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
t.params,
cases
);
@@ -101,9 +48,9 @@ Accuracy: Correctly rounded
const cases = await d.get('scalar'); // Using vectorize to generate vector cases based on scalar cases
await run(
t,
- abstractBinary('+'),
- [TypeAbstractFloat, TypeAbstractFloat],
- TypeAbstractFloat,
+ abstractFloatBinary('+'),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
t.params,
cases
);
@@ -123,9 +70,9 @@ Accuracy: Correctly rounded
const cases = await d.get(`vec${dim}_scalar`);
await run(
t,
- abstractBinary('+'),
- [TypeVec(dim, TypeAbstractFloat), TypeAbstractFloat],
- TypeVec(dim, TypeAbstractFloat),
+ abstractFloatBinary('+'),
+ [Type.vec(dim, Type.abstractFloat), Type.abstractFloat],
+ Type.vec(dim, Type.abstractFloat),
t.params,
cases
);
@@ -145,9 +92,9 @@ Accuracy: Correctly rounded
const cases = await d.get(`scalar_vec${dim}`);
await run(
t,
- abstractBinary('+'),
- [TypeAbstractFloat, TypeVec(dim, TypeAbstractFloat)],
- TypeVec(dim, TypeAbstractFloat),
+ abstractFloatBinary('+'),
+ [Type.abstractFloat, Type.vec(dim, Type.abstractFloat)],
+ Type.vec(dim, Type.abstractFloat),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_comparison.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_comparison.cache.ts
new file mode 100644
index 0000000000..e000266401
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_comparison.cache.ts
@@ -0,0 +1,90 @@
+import { anyOf } from '../../../../util/compare.js';
+import { abstractFloat, bool, ScalarValue } from '../../../../util/conversion.js';
+import { flushSubnormalNumberF64, vectorF64Range } from '../../../../util/math.js';
+import { Case } from '../case.js';
+import { makeCaseCache } from '../case_cache.js';
+
+/**
+ * @returns a test case for the provided left hand & right hand values and truth function.
+ * Handles quantization and subnormals.
+ */
+function makeCase(
+ lhs: number,
+ rhs: number,
+ truthFunc: (lhs: ScalarValue, rhs: ScalarValue) => boolean
+): Case {
+ // Subnormal float values may be flushed at any time.
+ // https://www.w3.org/TR/WGSL/#floating-point-evaluation
+ const af_lhs = abstractFloat(lhs);
+ const af_rhs = abstractFloat(rhs);
+ const lhs_options = new Set([af_lhs, abstractFloat(flushSubnormalNumberF64(lhs))]);
+ const rhs_options = new Set([af_rhs, abstractFloat(flushSubnormalNumberF64(rhs))]);
+ const expected: Array<ScalarValue> = [];
+ lhs_options.forEach(l => {
+ rhs_options.forEach(r => {
+ const result = bool(truthFunc(l, r));
+ if (!expected.includes(result)) {
+ expected.push(result);
+ }
+ });
+ });
+
+ return { input: [af_lhs, af_rhs], expected: anyOf(...expected) };
+}
+
+export const d = makeCaseCache('binary/af_logical', {
+ equals: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) === (rhs.value as number);
+ };
+
+ return vectorF64Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ not_equals: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) !== (rhs.value as number);
+ };
+
+ return vectorF64Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ less_than: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) < (rhs.value as number);
+ };
+
+ return vectorF64Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ less_equals: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) <= (rhs.value as number);
+ };
+
+ return vectorF64Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ greater_than: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) > (rhs.value as number);
+ };
+
+ return vectorF64Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ greater_equals: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) >= (rhs.value as number);
+ };
+
+ return vectorF64Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_comparison.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_comparison.spec.ts
index 5b8b1637b9..3941e12539 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_comparison.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_comparison.spec.ts
@@ -1,110 +1,17 @@
export const description = `
-Execution Tests for the AbstractFloat comparison operations
+Execution Tests for the abstract-float comparison operations
`;
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { anyOf } from '../../../../util/compare.js';
-import {
- abstractFloat,
- bool,
- Scalar,
- TypeAbstractFloat,
- TypeBool,
-} from '../../../../util/conversion.js';
-import { flushSubnormalNumberF64, vectorF64Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
-import { allInputSources, Case, run } from '../expression.js';
+import { Type } from '../../../../util/conversion.js';
+import { allInputSources, run } from '../expression.js';
+import { d } from './af_comparison.cache.js';
import { binary } from './binary.js';
export const g = makeTestGroup(GPUTest);
-/**
- * @returns a test case for the provided left hand & right hand values and truth function.
- * Handles quantization and subnormals.
- */
-function makeCase(
- lhs: number,
- rhs: number,
- truthFunc: (lhs: Scalar, rhs: Scalar) => boolean
-): Case {
- // Subnormal float values may be flushed at any time.
- // https://www.w3.org/TR/WGSL/#floating-point-evaluation
- const af_lhs = abstractFloat(lhs);
- const af_rhs = abstractFloat(rhs);
- const lhs_options = new Set([af_lhs, abstractFloat(flushSubnormalNumberF64(lhs))]);
- const rhs_options = new Set([af_rhs, abstractFloat(flushSubnormalNumberF64(rhs))]);
- const expected: Array<Scalar> = [];
- lhs_options.forEach(l => {
- rhs_options.forEach(r => {
- const result = bool(truthFunc(l, r));
- if (!expected.includes(result)) {
- expected.push(result);
- }
- });
- });
-
- return { input: [af_lhs, af_rhs], expected: anyOf(...expected) };
-}
-
-export const d = makeCaseCache('binary/af_logical', {
- equals: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) === (rhs.value as number);
- };
-
- return vectorF64Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- not_equals: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) !== (rhs.value as number);
- };
-
- return vectorF64Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- less_than: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) < (rhs.value as number);
- };
-
- return vectorF64Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- less_equals: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) <= (rhs.value as number);
- };
-
- return vectorF64Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- greater_than: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) > (rhs.value as number);
- };
-
- return vectorF64Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- greater_equals: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) >= (rhs.value as number);
- };
-
- return vectorF64Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
-});
-
g.test('equals')
.specURL('https://www.w3.org/TR/WGSL/#comparison-expr')
.desc(
@@ -120,7 +27,14 @@ Accuracy: Correct result
)
.fn(async t => {
const cases = await d.get('equals');
- await run(t, binary('=='), [TypeAbstractFloat, TypeAbstractFloat], TypeBool, t.params, cases);
+ await run(
+ t,
+ binary('=='),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.bool,
+ t.params,
+ cases
+ );
});
g.test('not_equals')
@@ -138,7 +52,14 @@ Accuracy: Correct result
)
.fn(async t => {
const cases = await d.get('not_equals');
- await run(t, binary('!='), [TypeAbstractFloat, TypeAbstractFloat], TypeBool, t.params, cases);
+ await run(
+ t,
+ binary('!='),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.bool,
+ t.params,
+ cases
+ );
});
g.test('less_than')
@@ -156,7 +77,7 @@ Accuracy: Correct result
)
.fn(async t => {
const cases = await d.get('less_than');
- await run(t, binary('<'), [TypeAbstractFloat, TypeAbstractFloat], TypeBool, t.params, cases);
+ await run(t, binary('<'), [Type.abstractFloat, Type.abstractFloat], Type.bool, t.params, cases);
});
g.test('less_equals')
@@ -174,7 +95,14 @@ Accuracy: Correct result
)
.fn(async t => {
const cases = await d.get('less_equals');
- await run(t, binary('<='), [TypeAbstractFloat, TypeAbstractFloat], TypeBool, t.params, cases);
+ await run(
+ t,
+ binary('<='),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.bool,
+ t.params,
+ cases
+ );
});
g.test('greater_than')
@@ -192,7 +120,7 @@ Accuracy: Correct result
)
.fn(async t => {
const cases = await d.get('greater_than');
- await run(t, binary('>'), [TypeAbstractFloat, TypeAbstractFloat], TypeBool, t.params, cases);
+ await run(t, binary('>'), [Type.abstractFloat, Type.abstractFloat], Type.bool, t.params, cases);
});
g.test('greater_equals')
@@ -210,5 +138,12 @@ Accuracy: Correct result
)
.fn(async t => {
const cases = await d.get('greater_equals');
- await run(t, binary('>='), [TypeAbstractFloat, TypeAbstractFloat], TypeBool, t.params, cases);
+ await run(
+ t,
+ binary('>='),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.bool,
+ t.params,
+ cases
+ );
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_division.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_division.cache.ts
new file mode 100644
index 0000000000..ff2fb6a578
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_division.cache.ts
@@ -0,0 +1,57 @@
+import { FP, FPVector } from '../../../../util/floating_point.js';
+import { sparseScalarF64Range, sparseVectorF64Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+const divisionVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
+ // division has an ulp accuracy, so abstract is only expected to be as accurate as f32
+ return FP.abstract.toVector(v.map(e => FP.f32.divisionInterval(e, s)));
+};
+
+const divisionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
+ // division has an ulp accuracy, so abstract is only expected to be as accurate as f32
+ return FP.abstract.toVector(v.map(e => FP.f32.divisionInterval(s, e)));
+};
+
+const scalar_cases = {
+ ['scalar']: () => {
+ return FP.abstract.generateScalarPairToIntervalCases(
+ sparseScalarF64Range(),
+ sparseScalarF64Range(),
+ 'finite',
+ // division has an ulp accuracy, so abstract is only expected to be as accurate as f32
+ FP.f32.divisionInterval
+ );
+ },
+};
+
+const vector_scalar_cases = ([2, 3, 4] as const)
+ .map(dim => ({
+ [`vec${dim}_scalar`]: () => {
+ return FP.abstract.generateVectorScalarToVectorCases(
+ sparseVectorF64Range(dim),
+ sparseScalarF64Range(),
+ 'finite',
+ divisionVectorScalarInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const scalar_vector_cases = ([2, 3, 4] as const)
+ .map(dim => ({
+ [`scalar_vec${dim}`]: () => {
+ return FP.abstract.generateScalarVectorToVectorCases(
+ sparseScalarF64Range(),
+ sparseVectorF64Range(dim),
+ 'finite',
+ divisionScalarVectorInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/af_division', {
+ ...scalar_cases,
+ ...vector_scalar_cases,
+ ...scalar_vector_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_division.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_division.spec.ts
index 4c1765d203..0ebe30b6cc 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_division.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_division.spec.ts
@@ -1,70 +1,17 @@
export const description = `
-Execution Tests for non-matrix AbstractFloat division expression
+Execution Tests for non-matrix abstract-float division expression
`;
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeAbstractFloat, TypeVec } from '../../../../util/conversion.js';
-import { FP, FPVector } from '../../../../util/floating_point.js';
-import { sparseF64Range, sparseVectorF64Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { onlyConstInputSource, run } from '../expression.js';
-import { abstractBinary } from './binary.js';
-
-const divisionVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
- return FP.abstract.toVector(v.map(e => FP.abstract.divisionInterval(e, s)));
-};
-
-const divisionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
- return FP.abstract.toVector(v.map(e => FP.abstract.divisionInterval(s, e)));
-};
+import { d } from './af_division.cache.js';
+import { abstractFloatBinary } from './binary.js';
export const g = makeTestGroup(GPUTest);
-const scalar_cases = {
- ['scalar']: () => {
- return FP.abstract.generateScalarPairToIntervalCases(
- sparseF64Range(),
- sparseF64Range(),
- 'finite',
- FP.abstract.divisionInterval
- );
- },
-};
-
-const vector_scalar_cases = ([2, 3, 4] as const)
- .map(dim => ({
- [`vec${dim}_scalar`]: () => {
- return FP.abstract.generateVectorScalarToVectorCases(
- sparseVectorF64Range(dim),
- sparseF64Range(),
- 'finite',
- divisionVectorScalarInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const scalar_vector_cases = ([2, 3, 4] as const)
- .map(dim => ({
- [`scalar_vec${dim}`]: () => {
- return FP.abstract.generateScalarVectorToVectorCases(
- sparseF64Range(),
- sparseVectorF64Range(dim),
- 'finite',
- divisionScalarVectorInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/af_division', {
- ...scalar_cases,
- ...vector_scalar_cases,
- ...scalar_vector_cases,
-});
-
g.test('scalar')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -78,9 +25,9 @@ Accuracy: 2.5 ULP for |y| in the range [2^-126, 2^126]
const cases = await d.get('scalar');
await run(
t,
- abstractBinary('/'),
- [TypeAbstractFloat, TypeAbstractFloat],
- TypeAbstractFloat,
+ abstractFloatBinary('/'),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
t.params,
cases
);
@@ -101,9 +48,9 @@ Accuracy: 2.5 ULP for |y| in the range [2^-126, 2^126]
const cases = await d.get('scalar'); // Using vectorize to generate vector cases based on scalar cases
await run(
t,
- abstractBinary('/'),
- [TypeAbstractFloat, TypeAbstractFloat],
- TypeAbstractFloat,
+ abstractFloatBinary('/'),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
t.params,
cases
);
@@ -123,9 +70,9 @@ Accuracy: Correctly rounded
const cases = await d.get(`vec${dim}_scalar`);
await run(
t,
- abstractBinary('/'),
- [TypeVec(dim, TypeAbstractFloat), TypeAbstractFloat],
- TypeVec(dim, TypeAbstractFloat),
+ abstractFloatBinary('/'),
+ [Type.vec(dim, Type.abstractFloat), Type.abstractFloat],
+ Type.vec(dim, Type.abstractFloat),
t.params,
cases
);
@@ -145,9 +92,9 @@ Accuracy: Correctly rounded
const cases = await d.get(`scalar_vec${dim}`);
await run(
t,
- abstractBinary('/'),
- [TypeAbstractFloat, TypeVec(dim, TypeAbstractFloat)],
- TypeVec(dim, TypeAbstractFloat),
+ abstractFloatBinary('/'),
+ [Type.abstractFloat, Type.vec(dim, Type.abstractFloat)],
+ Type.vec(dim, Type.abstractFloat),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_addition.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_addition.cache.ts
new file mode 100644
index 0000000000..e89250f57b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_addition.cache.ts
@@ -0,0 +1,26 @@
+import { FP } from '../../../../util/floating_point.js';
+import { sparseMatrixF64Range } from '../../../../util/math.js';
+import { selectNCases } from '../case.js';
+import { makeCaseCache } from '../case_cache.js';
+
+// Cases: matCxR
+const mat_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).map(rows => ({
+ [`mat${cols}x${rows}`]: () => {
+ return selectNCases(
+ 'binary/af_matrix_addition',
+ 50,
+ FP.abstract.generateMatrixPairToMatrixCases(
+ sparseMatrixF64Range(cols, rows),
+ sparseMatrixF64Range(cols, rows),
+ 'finite',
+ FP.abstract.additionMatrixMatrixInterval
+ )
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/af_matrix_addition', mat_cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_addition.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_addition.spec.ts
index 86bddec894..49c746c53e 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_addition.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_addition.spec.ts
@@ -1,37 +1,17 @@
export const description = `
-Execution Tests for matrix AbstractFloat addition expressions
+Execution Tests for matrix abstract-float addition expressions
`;
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeAbstractFloat, TypeMat } from '../../../../util/conversion.js';
-import { FP } from '../../../../util/floating_point.js';
-import { sparseMatrixF64Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { onlyConstInputSource, run } from '../expression.js';
-import { abstractBinary } from './binary.js';
+import { d } from './af_matrix_addition.cache.js';
+import { abstractFloatBinary } from './binary.js';
export const g = makeTestGroup(GPUTest);
-// Cases: matCxR
-const mat_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).map(rows => ({
- [`mat${cols}x${rows}`]: () => {
- return FP.abstract.generateMatrixPairToMatrixCases(
- sparseMatrixF64Range(cols, rows),
- sparseMatrixF64Range(cols, rows),
- 'finite',
- FP.abstract.additionMatrixMatrixInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/af_matrix_addition', mat_cases);
-
g.test('matrix')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -52,9 +32,9 @@ Accuracy: Correctly rounded
const cases = await d.get(`mat${cols}x${rows}`);
await run(
t,
- abstractBinary('+'),
- [TypeMat(cols, rows, TypeAbstractFloat), TypeMat(cols, rows, TypeAbstractFloat)],
- TypeMat(cols, rows, TypeAbstractFloat),
+ abstractFloatBinary('+'),
+ [Type.mat(cols, rows, Type.abstractFloat), Type.mat(cols, rows, Type.abstractFloat)],
+ Type.mat(cols, rows, Type.abstractFloat),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_matrix_multiplication.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_matrix_multiplication.cache.ts
new file mode 100644
index 0000000000..78512fbd0d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_matrix_multiplication.cache.ts
@@ -0,0 +1,29 @@
+import { FP } from '../../../../util/floating_point.js';
+import { sparseMatrixF64Range } from '../../../../util/math.js';
+import { selectNCases } from '../case.js';
+import { makeCaseCache } from '../case_cache.js';
+
+// Cases: matKxR_matCxK
+const mat_mat_cases = ([2, 3, 4] as const)
+ .flatMap(k =>
+ ([2, 3, 4] as const).flatMap(cols =>
+ ([2, 3, 4] as const).map(rows => ({
+ [`mat${k}x${rows}_mat${cols}x${k}`]: () => {
+ return selectNCases(
+ 'binary/af_matrix_matrix_multiplication',
+ 10,
+ FP.abstract.generateMatrixPairToMatrixCases(
+ sparseMatrixF64Range(k, rows),
+ sparseMatrixF64Range(cols, k),
+ 'finite',
+ // Matrix-matrix multiplication has an inherited accuracy, so abstract is only expected to be as accurate as f32
+ FP.f32.multiplicationMatrixMatrixInterval
+ )
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/af_matrix_matrix_multiplication', mat_mat_cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_matrix_multiplication.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_matrix_multiplication.spec.ts
new file mode 100644
index 0000000000..9320fb1226
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_matrix_multiplication.spec.ts
@@ -0,0 +1,45 @@
+export const description = `
+Execution Tests for matrix-matrix AbstractFloat multiplication expression
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../gpu_test.js';
+import { Type } from '../../../../util/conversion.js';
+import { onlyConstInputSource, run } from '../expression.js';
+
+import { d } from './af_matrix_matrix_multiplication.cache.js';
+import { abstractFloatBinary } from './binary.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('matrix_matrix')
+ .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
+ .desc(
+ `
+Expression: x * y, where x is a matrix and y is a matrix
+Accuracy: Correctly rounded
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('common_dim', [2, 3, 4] as const)
+ .combine('x_rows', [2, 3, 4] as const)
+ .combine('y_cols', [2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const x_cols = t.params.common_dim;
+ const x_rows = t.params.x_rows;
+ const y_cols = t.params.y_cols;
+ const y_rows = t.params.common_dim;
+
+ const cases = await d.get(`mat${x_cols}x${x_rows}_mat${y_cols}x${y_rows}`);
+ await run(
+ t,
+ abstractFloatBinary('*'),
+ [Type.mat(x_cols, x_rows, Type.abstractFloat), Type.mat(y_cols, y_rows, Type.abstractFloat)],
+ Type.mat(y_cols, x_rows, Type.abstractFloat),
+ t.params,
+ cases
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_scalar_multiplication.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_scalar_multiplication.cache.ts
new file mode 100644
index 0000000000..9106362970
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_scalar_multiplication.cache.ts
@@ -0,0 +1,49 @@
+import { FP } from '../../../../util/floating_point.js';
+import { sparseMatrixF64Range, sparseScalarF64Range } from '../../../../util/math.js';
+import { selectNCases } from '../case.js';
+import { makeCaseCache } from '../case_cache.js';
+
+// Cases: matCxR_scalar
+const mat_scalar_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).map(rows => ({
+ [`mat${cols}x${rows}_scalar`]: () => {
+ return selectNCases(
+ 'binary/af_matrix_scalar_multiplication_mat_scalar',
+ 50,
+ FP.abstract.generateMatrixScalarToMatrixCases(
+ sparseMatrixF64Range(cols, rows),
+ sparseScalarF64Range(),
+ 'finite',
+ FP.abstract.multiplicationMatrixScalarInterval
+ )
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: scalar_matCxR
+const scalar_mat_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).map(rows => ({
+ [`scalar_mat${cols}x${rows}`]: () => {
+ return selectNCases(
+ 'binary/af_matrix_scalar_multiplication_scalar_mat',
+ 50,
+ FP.abstract.generateScalarMatrixToMatrixCases(
+ sparseScalarF64Range(),
+ sparseMatrixF64Range(cols, rows),
+ 'finite',
+ FP.abstract.multiplicationScalarMatrixInterval
+ )
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/af_matrix_scalar_multiplication', {
+ ...mat_scalar_cases,
+ ...scalar_mat_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_scalar_multiplication.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_scalar_multiplication.spec.ts
new file mode 100644
index 0000000000..c6faabbc84
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_scalar_multiplication.spec.ts
@@ -0,0 +1,69 @@
+export const description = `
+Execution Tests for matrix-scalar and scalar-matrix AbstractFloat multiplication expression
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../gpu_test.js';
+import { Type } from '../../../../util/conversion.js';
+import { onlyConstInputSource, run } from '../expression.js';
+
+import { d } from './af_matrix_scalar_multiplication.cache.js';
+import { abstractFloatBinary } from './binary.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('matrix_scalar')
+ .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
+ .desc(
+ `
+Expression: x * y, where x is a matrix and y is a scalar
+Accuracy: Correctly rounded
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('cols', [2, 3, 4] as const)
+ .combine('rows', [2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cols = t.params.cols;
+ const rows = t.params.rows;
+ const cases = await d.get(`mat${cols}x${rows}_scalar`);
+ await run(
+ t,
+ abstractFloatBinary('*'),
+ [Type.mat(cols, rows, Type.abstractFloat), Type.abstractFloat],
+ Type.mat(cols, rows, Type.abstractFloat),
+ t.params,
+ cases
+ );
+ });
+
+g.test('scalar_matrix')
+ .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
+ .desc(
+ `
+Expression: x * y, where x is a scalar and y is a matrix
+Accuracy: Correctly rounded
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('cols', [2, 3, 4] as const)
+ .combine('rows', [2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cols = t.params.cols;
+ const rows = t.params.rows;
+ const cases = await d.get(`scalar_mat${cols}x${rows}`);
+ await run(
+ t,
+ abstractFloatBinary('*'),
+ [Type.abstractFloat, Type.mat(cols, rows, Type.abstractFloat)],
+ Type.mat(cols, rows, Type.abstractFloat),
+ t.params,
+ cases
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_subtraction.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_subtraction.cache.ts
new file mode 100644
index 0000000000..c3e5e856dc
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_subtraction.cache.ts
@@ -0,0 +1,26 @@
+import { FP } from '../../../../util/floating_point.js';
+import { sparseMatrixF64Range } from '../../../../util/math.js';
+import { selectNCases } from '../case.js';
+import { makeCaseCache } from '../case_cache.js';
+
+// Cases: matCxR
+const mat_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).map(rows => ({
+ [`mat${cols}x${rows}`]: () => {
+ return selectNCases(
+ 'binary/af_matrix_subtraction',
+ 50,
+ FP.abstract.generateMatrixPairToMatrixCases(
+ sparseMatrixF64Range(cols, rows),
+ sparseMatrixF64Range(cols, rows),
+ 'finite',
+ FP.abstract.subtractionMatrixMatrixInterval
+ )
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/af_matrix_subtraction', mat_cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_subtraction.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_subtraction.spec.ts
index 849c11611f..9b240fdee9 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_subtraction.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_subtraction.spec.ts
@@ -1,37 +1,17 @@
export const description = `
-Execution Tests for matrix AbstractFloat subtraction expression
+Execution Tests for matrix abstract-float subtraction expression
`;
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeAbstractFloat, TypeMat } from '../../../../util/conversion.js';
-import { FP } from '../../../../util/floating_point.js';
-import { sparseMatrixF64Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { onlyConstInputSource, run } from '../expression.js';
-import { abstractBinary } from './binary.js';
+import { d } from './af_matrix_subtraction.cache.js';
+import { abstractFloatBinary } from './binary.js';
export const g = makeTestGroup(GPUTest);
-// Cases: matCxR
-const mat_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).map(rows => ({
- [`mat${cols}x${rows}`]: () => {
- return FP.abstract.generateMatrixPairToMatrixCases(
- sparseMatrixF64Range(cols, rows),
- sparseMatrixF64Range(cols, rows),
- 'finite',
- FP.abstract.subtractionMatrixMatrixInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/af_matrix_subtraction', mat_cases);
-
g.test('matrix')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -52,9 +32,9 @@ Accuracy: Correctly rounded
const cases = await d.get(`mat${cols}x${rows}`);
await run(
t,
- abstractBinary('-'),
- [TypeMat(cols, rows, TypeAbstractFloat), TypeMat(cols, rows, TypeAbstractFloat)],
- TypeMat(cols, rows, TypeAbstractFloat),
+ abstractFloatBinary('-'),
+ [Type.mat(cols, rows, Type.abstractFloat), Type.mat(cols, rows, Type.abstractFloat)],
+ Type.mat(cols, rows, Type.abstractFloat),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_vector_multiplication.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_vector_multiplication.cache.ts
new file mode 100644
index 0000000000..4578eb0ce4
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_vector_multiplication.cache.ts
@@ -0,0 +1,51 @@
+import { FP } from '../../../../util/floating_point.js';
+import { sparseMatrixF64Range, sparseVectorF64Range } from '../../../../util/math.js';
+import { selectNCases } from '../case.js';
+import { makeCaseCache } from '../case_cache.js';
+
+// Cases: matCxR_vecC
+const mat_vec_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).map(rows => ({
+ [`mat${cols}x${rows}_vec${cols}`]: () => {
+ return selectNCases(
+ 'binary/af_matrix_vector_multiplication_mat_vec',
+ 50,
+ FP.abstract.generateMatrixVectorToVectorCases(
+ sparseMatrixF64Range(cols, rows),
+ sparseVectorF64Range(cols),
+ 'finite',
+ // Matrix-vector multiplication has an inherited accuracy, so abstract is only expected to be as accurate as f32
+ FP.f32.multiplicationMatrixVectorInterval
+ )
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: vecR_matCxR
+const vec_mat_cases = ([2, 3, 4] as const)
+ .flatMap(rows =>
+ ([2, 3, 4] as const).map(cols => ({
+ [`vec${rows}_mat${cols}x${rows}`]: () => {
+ return selectNCases(
+ 'binary/af_matrix_vector_multiplication_vec_mat',
+ 50,
+ FP.abstract.generateVectorMatrixToVectorCases(
+ sparseVectorF64Range(rows),
+ sparseMatrixF64Range(cols, rows),
+ 'finite',
+ // Vector-matrix multiplication has an inherited accuracy, so abstract is only expected to be as accurate as f32
+ FP.f32.multiplicationVectorMatrixInterval
+ )
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/af_matrix_vector_multiplication', {
+ ...mat_vec_cases,
+ ...vec_mat_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_vector_multiplication.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_vector_multiplication.spec.ts
new file mode 100644
index 0000000000..5db78f8369
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_vector_multiplication.spec.ts
@@ -0,0 +1,69 @@
+export const description = `
+Execution Tests for matrix-vector and vector-matrix AbstractFloat multiplication expression
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../gpu_test.js';
+import { Type } from '../../../../util/conversion.js';
+import { onlyConstInputSource, run } from '../expression.js';
+
+import { d } from './af_matrix_vector_multiplication.cache.js';
+import { abstractFloatBinary } from './binary.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('matrix_vector')
+ .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
+ .desc(
+ `
+Expression: x * y, where x is a matrix and y is a vector
+Accuracy: Correctly rounded
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('cols', [2, 3, 4] as const)
+ .combine('rows', [2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cols = t.params.cols;
+ const rows = t.params.rows;
+ const cases = await d.get(`mat${cols}x${rows}_vec${cols}`);
+ await run(
+ t,
+ abstractFloatBinary('*'),
+ [Type.mat(cols, rows, Type.abstractFloat), Type.vec(cols, Type.abstractFloat)],
+ Type.vec(rows, Type.abstractFloat),
+ t.params,
+ cases
+ );
+ });
+
+g.test('vector_matrix')
+ .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
+ .desc(
+ `
+Expression: x * y, where x is a vector and y is is a matrix
+Accuracy: Correctly rounded
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('cols', [2, 3, 4] as const)
+ .combine('rows', [2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cols = t.params.cols;
+ const rows = t.params.rows;
+ const cases = await d.get(`vec${rows}_mat${cols}x${rows}`);
+ await run(
+ t,
+ abstractFloatBinary('*'),
+ [Type.vec(rows, Type.abstractFloat), Type.mat(cols, rows, Type.abstractFloat)],
+ Type.vec(cols, Type.abstractFloat),
+ t.params,
+ cases
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_multiplication.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_multiplication.cache.ts
new file mode 100644
index 0000000000..e111682cd2
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_multiplication.cache.ts
@@ -0,0 +1,54 @@
+import { FP, FPVector } from '../../../../util/floating_point.js';
+import { sparseScalarF64Range, sparseVectorF64Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+const multiplicationVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
+ return FP.abstract.toVector(v.map(e => FP.abstract.multiplicationInterval(e, s)));
+};
+
+const multiplicationScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
+ return FP.abstract.toVector(v.map(e => FP.abstract.multiplicationInterval(s, e)));
+};
+
+const scalar_cases = {
+ ['scalar']: () => {
+ return FP.abstract.generateScalarPairToIntervalCases(
+ sparseScalarF64Range(),
+ sparseScalarF64Range(),
+ 'finite',
+ FP.abstract.multiplicationInterval
+ );
+ },
+};
+
+const vector_scalar_cases = ([2, 3, 4] as const)
+ .map(dim => ({
+ [`vec${dim}_scalar`]: () => {
+ return FP.abstract.generateVectorScalarToVectorCases(
+ sparseVectorF64Range(dim),
+ sparseScalarF64Range(),
+ 'finite',
+ multiplicationVectorScalarInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const scalar_vector_cases = ([2, 3, 4] as const)
+ .map(dim => ({
+ [`scalar_vec${dim}`]: () => {
+ return FP.abstract.generateScalarVectorToVectorCases(
+ sparseScalarF64Range(),
+ sparseVectorF64Range(dim),
+ 'finite',
+ multiplicationScalarVectorInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/af_multiplication', {
+ ...scalar_cases,
+ ...vector_scalar_cases,
+ ...scalar_vector_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_multiplication.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_multiplication.spec.ts
index 6b15812703..405de758bd 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_multiplication.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_multiplication.spec.ts
@@ -1,70 +1,17 @@
export const description = `
-Execution Tests for non-matrix AbstractFloat multiplication expression
+Execution Tests for non-matrix abstract-float multiplication expression
`;
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeAbstractFloat, TypeVec } from '../../../../util/conversion.js';
-import { FP, FPVector } from '../../../../util/floating_point.js';
-import { sparseF64Range, sparseVectorF64Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { onlyConstInputSource, run } from '../expression.js';
-import { abstractBinary } from './binary.js';
-
-const multiplicationVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
- return FP.abstract.toVector(v.map(e => FP.abstract.multiplicationInterval(e, s)));
-};
-
-const multiplicationScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
- return FP.abstract.toVector(v.map(e => FP.abstract.multiplicationInterval(s, e)));
-};
+import { d } from './af_multiplication.cache.js';
+import { abstractFloatBinary } from './binary.js';
export const g = makeTestGroup(GPUTest);
-const scalar_cases = {
- ['scalar']: () => {
- return FP.abstract.generateScalarPairToIntervalCases(
- sparseF64Range(),
- sparseF64Range(),
- 'finite',
- FP.abstract.multiplicationInterval
- );
- },
-};
-
-const vector_scalar_cases = ([2, 3, 4] as const)
- .map(dim => ({
- [`vec${dim}_scalar`]: () => {
- return FP.abstract.generateVectorScalarToVectorCases(
- sparseVectorF64Range(dim),
- sparseF64Range(),
- 'finite',
- multiplicationVectorScalarInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const scalar_vector_cases = ([2, 3, 4] as const)
- .map(dim => ({
- [`scalar_vec${dim}`]: () => {
- return FP.abstract.generateScalarVectorToVectorCases(
- sparseF64Range(),
- sparseVectorF64Range(dim),
- 'finite',
- multiplicationScalarVectorInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/af_multiplication', {
- ...scalar_cases,
- ...vector_scalar_cases,
- ...scalar_vector_cases,
-});
-
g.test('scalar')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -78,9 +25,9 @@ Accuracy: Correctly rounded
const cases = await d.get('scalar');
await run(
t,
- abstractBinary('*'),
- [TypeAbstractFloat, TypeAbstractFloat],
- TypeAbstractFloat,
+ abstractFloatBinary('*'),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
t.params,
cases
);
@@ -101,9 +48,9 @@ Accuracy: Correctly rounded
const cases = await d.get('scalar'); // Using vectorize to generate vector cases based on scalar cases
await run(
t,
- abstractBinary('*'),
- [TypeAbstractFloat, TypeAbstractFloat],
- TypeAbstractFloat,
+ abstractFloatBinary('*'),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
t.params,
cases
);
@@ -123,9 +70,9 @@ Accuracy: Correctly rounded
const cases = await d.get(`vec${dim}_scalar`);
await run(
t,
- abstractBinary('*'),
- [TypeVec(dim, TypeAbstractFloat), TypeAbstractFloat],
- TypeVec(dim, TypeAbstractFloat),
+ abstractFloatBinary('*'),
+ [Type.vec(dim, Type.abstractFloat), Type.abstractFloat],
+ Type.vec(dim, Type.abstractFloat),
t.params,
cases
);
@@ -145,9 +92,9 @@ Accuracy: Correctly rounded
const cases = await d.get(`scalar_vec${dim}`);
await run(
t,
- abstractBinary('*'),
- [TypeAbstractFloat, TypeVec(dim, TypeAbstractFloat)],
- TypeVec(dim, TypeAbstractFloat),
+ abstractFloatBinary('*'),
+ [Type.abstractFloat, Type.vec(dim, Type.abstractFloat)],
+ Type.vec(dim, Type.abstractFloat),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_remainder.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_remainder.cache.ts
new file mode 100644
index 0000000000..4817298167
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_remainder.cache.ts
@@ -0,0 +1,57 @@
+import { FP, FPVector } from '../../../../util/floating_point.js';
+import { sparseScalarF64Range, sparseVectorF64Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+const remainderVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
+ // remainder has an inherited accuracy, so abstract is only expected to be as accurate as f32
+ return FP.abstract.toVector(v.map(e => FP.f32.remainderInterval(e, s)));
+};
+
+const remainderScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
+ // remainder has an inherited accuracy, so abstract is only expected to be as accurate as f32
+ return FP.abstract.toVector(v.map(e => FP.f32.remainderInterval(s, e)));
+};
+
+const scalar_cases = {
+ ['scalar']: () => {
+ return FP.abstract.generateScalarPairToIntervalCases(
+ sparseScalarF64Range(),
+ sparseScalarF64Range(),
+ 'finite',
+ // remainder has an inherited accuracy, so abstract is only expected to be as accurate as f32
+ FP.f32.remainderInterval
+ );
+ },
+};
+
+const vector_scalar_cases = ([2, 3, 4] as const)
+ .map(dim => ({
+ [`vec${dim}_scalar`]: () => {
+ return FP.abstract.generateVectorScalarToVectorCases(
+ sparseVectorF64Range(dim),
+ sparseScalarF64Range(),
+ 'finite',
+ remainderVectorScalarInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const scalar_vector_cases = ([2, 3, 4] as const)
+ .map(dim => ({
+ [`scalar_vec${dim}`]: () => {
+ return FP.abstract.generateScalarVectorToVectorCases(
+ sparseScalarF64Range(),
+ sparseVectorF64Range(dim),
+ 'finite',
+ remainderScalarVectorInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/af_remainder', {
+ ...scalar_cases,
+ ...vector_scalar_cases,
+ ...scalar_vector_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_remainder.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_remainder.spec.ts
index b4ce930bdb..d743f85ed6 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_remainder.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_remainder.spec.ts
@@ -4,67 +4,14 @@ Execution Tests for non-matrix abstract float remainder expression
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeAbstractFloat, TypeVec } from '../../../../util/conversion.js';
-import { FP, FPVector } from '../../../../util/floating_point.js';
-import { sparseF64Range, sparseVectorF64Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { onlyConstInputSource, run } from '../expression.js';
-import { abstractBinary } from './binary.js';
-
-const remainderVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
- return FP.abstract.toVector(v.map(e => FP.abstract.remainderInterval(e, s)));
-};
-
-const remainderScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
- return FP.abstract.toVector(v.map(e => FP.abstract.remainderInterval(s, e)));
-};
+import { d } from './af_remainder.cache.js';
+import { abstractFloatBinary } from './binary.js';
export const g = makeTestGroup(GPUTest);
-const scalar_cases = {
- ['scalar']: () => {
- return FP.abstract.generateScalarPairToIntervalCases(
- sparseF64Range(),
- sparseF64Range(),
- 'finite',
- FP.abstract.remainderInterval
- );
- },
-};
-
-const vector_scalar_cases = ([2, 3, 4] as const)
- .map(dim => ({
- [`vec${dim}_scalar`]: () => {
- return FP.abstract.generateVectorScalarToVectorCases(
- sparseVectorF64Range(dim),
- sparseF64Range(),
- 'finite',
- remainderVectorScalarInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const scalar_vector_cases = ([2, 3, 4] as const)
- .map(dim => ({
- [`scalar_vec${dim}`]: () => {
- return FP.abstract.generateScalarVectorToVectorCases(
- sparseF64Range(),
- sparseVectorF64Range(dim),
- 'finite',
- remainderScalarVectorInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/af_remainder', {
- ...scalar_cases,
- ...vector_scalar_cases,
- ...scalar_vector_cases,
-});
-
g.test('scalar')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -78,9 +25,9 @@ Accuracy: Derived from x - y * trunc(x/y)
const cases = await d.get('scalar');
await run(
t,
- abstractBinary('%'),
- [TypeAbstractFloat, TypeAbstractFloat],
- TypeAbstractFloat,
+ abstractFloatBinary('%'),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
t.params,
cases
);
@@ -101,9 +48,9 @@ Accuracy: Derived from x - y * trunc(x/y)
const cases = await d.get('scalar'); // Using vectorize to generate vector cases based on scalar cases
await run(
t,
- abstractBinary('%'),
- [TypeAbstractFloat, TypeAbstractFloat],
- TypeAbstractFloat,
+ abstractFloatBinary('%'),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
t.params,
cases
);
@@ -123,9 +70,9 @@ Accuracy: Correctly rounded
const cases = await d.get(`vec${dim}_scalar`);
await run(
t,
- abstractBinary('%'),
- [TypeVec(dim, TypeAbstractFloat), TypeAbstractFloat],
- TypeVec(dim, TypeAbstractFloat),
+ abstractFloatBinary('%'),
+ [Type.vec(dim, Type.abstractFloat), Type.abstractFloat],
+ Type.vec(dim, Type.abstractFloat),
t.params,
cases
);
@@ -145,9 +92,9 @@ Accuracy: Correctly rounded
const cases = await d.get(`scalar_vec${dim}`);
await run(
t,
- abstractBinary('%'),
- [TypeAbstractFloat, TypeVec(dim, TypeAbstractFloat)],
- TypeVec(dim, TypeAbstractFloat),
+ abstractFloatBinary('%'),
+ [Type.abstractFloat, Type.vec(dim, Type.abstractFloat)],
+ Type.vec(dim, Type.abstractFloat),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_subtraction.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_subtraction.cache.ts
new file mode 100644
index 0000000000..ea5107e143
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_subtraction.cache.ts
@@ -0,0 +1,54 @@
+import { FP, FPVector } from '../../../../util/floating_point.js';
+import { sparseScalarF64Range, sparseVectorF64Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+const subtractionVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
+ return FP.abstract.toVector(v.map(e => FP.abstract.subtractionInterval(e, s)));
+};
+
+const subtractionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
+ return FP.abstract.toVector(v.map(e => FP.abstract.subtractionInterval(s, e)));
+};
+
+const scalar_cases = {
+ ['scalar']: () => {
+ return FP.abstract.generateScalarPairToIntervalCases(
+ sparseScalarF64Range(),
+ sparseScalarF64Range(),
+ 'finite',
+ FP.abstract.subtractionInterval
+ );
+ },
+};
+
+const vector_scalar_cases = ([2, 3, 4] as const)
+ .map(dim => ({
+ [`vec${dim}_scalar`]: () => {
+ return FP.abstract.generateVectorScalarToVectorCases(
+ sparseVectorF64Range(dim),
+ sparseScalarF64Range(),
+ 'finite',
+ subtractionVectorScalarInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const scalar_vector_cases = ([2, 3, 4] as const)
+ .map(dim => ({
+ [`scalar_vec${dim}`]: () => {
+ return FP.abstract.generateScalarVectorToVectorCases(
+ sparseScalarF64Range(),
+ sparseVectorF64Range(dim),
+ 'finite',
+ subtractionScalarVectorInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/af_subtraction', {
+ ...scalar_cases,
+ ...vector_scalar_cases,
+ ...scalar_vector_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_subtraction.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_subtraction.spec.ts
index 00dc66feb9..2874a744da 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_subtraction.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_subtraction.spec.ts
@@ -1,70 +1,17 @@
export const description = `
-Execution Tests for non-matrix AbstractFloat subtraction expression
+Execution Tests for non-matrix abstract-float subtraction expression
`;
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeAbstractFloat, TypeVec } from '../../../../util/conversion.js';
-import { FP, FPVector } from '../../../../util/floating_point.js';
-import { sparseF64Range, sparseVectorF64Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { onlyConstInputSource, run } from '../expression.js';
-import { abstractBinary } from './binary.js';
-
-const subtractionVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
- return FP.abstract.toVector(v.map(e => FP.abstract.subtractionInterval(e, s)));
-};
-
-const subtractionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
- return FP.abstract.toVector(v.map(e => FP.abstract.subtractionInterval(s, e)));
-};
+import { d } from './af_subtraction.cache.js';
+import { abstractFloatBinary } from './binary.js';
export const g = makeTestGroup(GPUTest);
-const scalar_cases = {
- ['scalar']: () => {
- return FP.abstract.generateScalarPairToIntervalCases(
- sparseF64Range(),
- sparseF64Range(),
- 'finite',
- FP.abstract.subtractionInterval
- );
- },
-};
-
-const vector_scalar_cases = ([2, 3, 4] as const)
- .map(dim => ({
- [`vec${dim}_scalar`]: () => {
- return FP.abstract.generateVectorScalarToVectorCases(
- sparseVectorF64Range(dim),
- sparseF64Range(),
- 'finite',
- subtractionVectorScalarInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const scalar_vector_cases = ([2, 3, 4] as const)
- .map(dim => ({
- [`scalar_vec${dim}`]: () => {
- return FP.abstract.generateScalarVectorToVectorCases(
- sparseF64Range(),
- sparseVectorF64Range(dim),
- 'finite',
- subtractionScalarVectorInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/af_subtraction', {
- ...scalar_cases,
- ...vector_scalar_cases,
- ...scalar_vector_cases,
-});
-
g.test('scalar')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -78,9 +25,9 @@ Accuracy: Correctly rounded
const cases = await d.get('scalar');
await run(
t,
- abstractBinary('-'),
- [TypeAbstractFloat, TypeAbstractFloat],
- TypeAbstractFloat,
+ abstractFloatBinary('-'),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
t.params,
cases
);
@@ -101,9 +48,9 @@ Accuracy: Correctly rounded
const cases = await d.get('scalar'); // Using vectorize to generate vector cases based on scalar cases
await run(
t,
- abstractBinary('-'),
- [TypeAbstractFloat, TypeAbstractFloat],
- TypeAbstractFloat,
+ abstractFloatBinary('-'),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
t.params,
cases
);
@@ -123,9 +70,9 @@ Accuracy: Correctly rounded
const cases = await d.get(`vec${dim}_scalar`);
await run(
t,
- abstractBinary('-'),
- [TypeVec(dim, TypeAbstractFloat), TypeAbstractFloat],
- TypeVec(dim, TypeAbstractFloat),
+ abstractFloatBinary('-'),
+ [Type.vec(dim, Type.abstractFloat), Type.abstractFloat],
+ Type.vec(dim, Type.abstractFloat),
t.params,
cases
);
@@ -145,9 +92,9 @@ Accuracy: Correctly rounded
const cases = await d.get(`scalar_vec${dim}`);
await run(
t,
- abstractBinary('-'),
- [TypeAbstractFloat, TypeVec(dim, TypeAbstractFloat)],
- TypeVec(dim, TypeAbstractFloat),
+ abstractFloatBinary('-'),
+ [Type.abstractFloat, Type.vec(dim, Type.abstractFloat)],
+ Type.vec(dim, Type.abstractFloat),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/ai_arithmetic.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/ai_arithmetic.cache.ts
new file mode 100644
index 0000000000..48300e8013
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/ai_arithmetic.cache.ts
@@ -0,0 +1,145 @@
+import { kValue } from '../../../../util/constants.js';
+import { sparseI64Range, vectorI64Range } from '../../../../util/math.js';
+import {
+ generateBinaryToI64Cases,
+ generateI64VectorBinaryToVectorCases,
+ generateVectorI64BinaryToVectorCases,
+} from '../case.js';
+import { makeCaseCache } from '../case_cache.js';
+
+function ai_add(x: bigint, y: bigint): bigint | undefined {
+ const result = x + y;
+ return !kValue.i64.isOOB(result) ? result : undefined;
+}
+
+function ai_div(x: bigint, y: bigint): bigint | undefined {
+ if (y === 0n) return undefined;
+ if (x === kValue.i64.negative.min && y === -1n) return undefined;
+ const result = x / y;
+ return !kValue.i64.isOOB(result) ? result : undefined;
+}
+
+function ai_mul(x: bigint, y: bigint): bigint | undefined {
+ const result = x * y;
+ return !kValue.i64.isOOB(result) ? result : undefined;
+}
+
+function ai_rem(x: bigint, y: bigint): bigint | undefined {
+ if (y === 0n) return undefined;
+ if (x === kValue.i64.negative.min && y === -1n) return undefined;
+ const result = x % y;
+ return !kValue.i64.isOOB(result) ? result : undefined;
+}
+
+function ai_sub(x: bigint, y: bigint): bigint | undefined {
+ const result = x - y;
+ return !kValue.i64.isOOB(result) ? result : undefined;
+}
+
+export const d = makeCaseCache('binary/ai_arithmetic', {
+ addition: () => {
+ return generateBinaryToI64Cases(sparseI64Range(), sparseI64Range(), ai_add);
+ },
+ addition_scalar_vector2: () => {
+ return generateI64VectorBinaryToVectorCases(sparseI64Range(), vectorI64Range(2), ai_add);
+ },
+ addition_scalar_vector3: () => {
+ return generateI64VectorBinaryToVectorCases(sparseI64Range(), vectorI64Range(3), ai_add);
+ },
+ addition_scalar_vector4: () => {
+ return generateI64VectorBinaryToVectorCases(sparseI64Range(), vectorI64Range(4), ai_add);
+ },
+ addition_vector2_scalar: () => {
+ return generateVectorI64BinaryToVectorCases(vectorI64Range(2), sparseI64Range(), ai_add);
+ },
+ addition_vector3_scalar: () => {
+ return generateVectorI64BinaryToVectorCases(vectorI64Range(3), sparseI64Range(), ai_add);
+ },
+ addition_vector4_scalar: () => {
+ return generateVectorI64BinaryToVectorCases(vectorI64Range(4), sparseI64Range(), ai_add);
+ },
+ division: () => {
+ return generateBinaryToI64Cases(sparseI64Range(), sparseI64Range(), ai_div);
+ },
+ division_scalar_vector2: () => {
+ return generateI64VectorBinaryToVectorCases(sparseI64Range(), vectorI64Range(2), ai_div);
+ },
+ division_scalar_vector3: () => {
+ return generateI64VectorBinaryToVectorCases(sparseI64Range(), vectorI64Range(3), ai_div);
+ },
+ division_scalar_vector4: () => {
+ return generateI64VectorBinaryToVectorCases(sparseI64Range(), vectorI64Range(4), ai_div);
+ },
+ division_vector2_scalar: () => {
+ return generateVectorI64BinaryToVectorCases(vectorI64Range(2), sparseI64Range(), ai_div);
+ },
+ division_vector3_scalar: () => {
+ return generateVectorI64BinaryToVectorCases(vectorI64Range(3), sparseI64Range(), ai_div);
+ },
+ division_vector4_scalar: () => {
+ return generateVectorI64BinaryToVectorCases(vectorI64Range(4), sparseI64Range(), ai_div);
+ },
+ multiplication: () => {
+ return generateBinaryToI64Cases(sparseI64Range(), sparseI64Range(), ai_mul);
+ },
+ multiplication_scalar_vector2: () => {
+ return generateI64VectorBinaryToVectorCases(sparseI64Range(), vectorI64Range(2), ai_mul);
+ },
+ multiplication_scalar_vector3: () => {
+ return generateI64VectorBinaryToVectorCases(sparseI64Range(), vectorI64Range(3), ai_mul);
+ },
+ multiplication_scalar_vector4: () => {
+ return generateI64VectorBinaryToVectorCases(sparseI64Range(), vectorI64Range(4), ai_mul);
+ },
+ multiplication_vector2_scalar: () => {
+ return generateVectorI64BinaryToVectorCases(vectorI64Range(2), sparseI64Range(), ai_mul);
+ },
+ multiplication_vector3_scalar: () => {
+ return generateVectorI64BinaryToVectorCases(vectorI64Range(3), sparseI64Range(), ai_mul);
+ },
+ multiplication_vector4_scalar: () => {
+ return generateVectorI64BinaryToVectorCases(vectorI64Range(4), sparseI64Range(), ai_mul);
+ },
+ remainder: () => {
+ return generateBinaryToI64Cases(sparseI64Range(), sparseI64Range(), ai_rem);
+ },
+ remainder_scalar_vector2: () => {
+ return generateI64VectorBinaryToVectorCases(sparseI64Range(), vectorI64Range(2), ai_rem);
+ },
+ remainder_scalar_vector3: () => {
+ return generateI64VectorBinaryToVectorCases(sparseI64Range(), vectorI64Range(3), ai_rem);
+ },
+ remainder_scalar_vector4: () => {
+ return generateI64VectorBinaryToVectorCases(sparseI64Range(), vectorI64Range(4), ai_rem);
+ },
+ remainder_vector2_scalar: () => {
+ return generateVectorI64BinaryToVectorCases(vectorI64Range(2), sparseI64Range(), ai_rem);
+ },
+ remainder_vector3_scalar: () => {
+ return generateVectorI64BinaryToVectorCases(vectorI64Range(3), sparseI64Range(), ai_rem);
+ },
+ remainder_vector4_scalar: () => {
+ return generateVectorI64BinaryToVectorCases(vectorI64Range(4), sparseI64Range(), ai_rem);
+ },
+ subtraction: () => {
+ return generateBinaryToI64Cases(sparseI64Range(), sparseI64Range(), ai_sub);
+ },
+ subtraction_scalar_vector2: () => {
+ return generateI64VectorBinaryToVectorCases(sparseI64Range(), vectorI64Range(2), ai_sub);
+ },
+ subtraction_scalar_vector3: () => {
+ return generateI64VectorBinaryToVectorCases(sparseI64Range(), vectorI64Range(3), ai_sub);
+ },
+ subtraction_scalar_vector4: () => {
+ return generateI64VectorBinaryToVectorCases(sparseI64Range(), vectorI64Range(4), ai_sub);
+ },
+ subtraction_vector2_scalar: () => {
+ return generateVectorI64BinaryToVectorCases(vectorI64Range(2), sparseI64Range(), ai_sub);
+ },
+ subtraction_vector3_scalar: () => {
+ return generateVectorI64BinaryToVectorCases(vectorI64Range(3), sparseI64Range(), ai_sub);
+ },
+ subtraction_vector4_scalar: () => {
+ return generateVectorI64BinaryToVectorCases(vectorI64Range(4), sparseI64Range(), ai_sub);
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/ai_arithmetic.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/ai_arithmetic.spec.ts
new file mode 100644
index 0000000000..ef211af3ed
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/ai_arithmetic.spec.ts
@@ -0,0 +1,303 @@
+export const description = `
+Execution Tests for the abstract int arithmetic binary expression operations
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../gpu_test.js';
+import { Type } from '../../../../util/conversion.js';
+import { onlyConstInputSource, run } from '../expression.js';
+
+import { d } from './ai_arithmetic.cache.js';
+import { abstractIntBinary } from './binary.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('addition')
+ .specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr')
+ .desc(
+ `
+Expression: x + y
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('addition');
+ await run(
+ t,
+ abstractIntBinary('+'),
+ [Type.abstractInt, Type.abstractInt],
+ Type.abstractInt,
+ t.params,
+ cases
+ );
+ });
+
+g.test('addition_scalar_vector')
+ .specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr')
+ .desc(
+ `
+Expression: x + y
+`
+ )
+ .params(u =>
+ u.combine('inputSource', onlyConstInputSource).combine('vectorize_rhs', [2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const vec_size = t.params.vectorize_rhs;
+ const vec_type = Type.vec(vec_size, Type.abstractInt);
+ const cases = await d.get(`addition_scalar_vector${vec_size}`);
+ await run(t, abstractIntBinary('+'), [Type.abstractInt, vec_type], vec_type, t.params, cases);
+ });
+
+g.test('addition_vector_scalar')
+ .specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr')
+ .desc(
+ `
+Expression: x + y
+`
+ )
+ .params(u =>
+ u.combine('inputSource', onlyConstInputSource).combine('vectorize_lhs', [2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const vec_size = t.params.vectorize_lhs;
+ const vec_type = Type.vec(vec_size, Type.abstractInt);
+ const cases = await d.get(`addition_vector${vec_size}_scalar`);
+ await run(t, abstractIntBinary('+'), [vec_type, Type.abstractInt], vec_type, t.params, cases);
+ });
+
+g.test('division')
+ .specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr')
+ .desc(
+ `
+Expression: x / y
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('division');
+ await run(
+ t,
+ abstractIntBinary('/'),
+ [Type.abstractInt, Type.abstractInt],
+ Type.abstractInt,
+ t.params,
+ cases
+ );
+ });
+
+g.test('division_scalar_vector')
+ .specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr')
+ .desc(
+ `
+Expression: x / y
+`
+ )
+ .params(u =>
+ u.combine('inputSource', onlyConstInputSource).combine('vectorize_rhs', [2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const vec_size = t.params.vectorize_rhs;
+ const vec_type = Type.vec(vec_size, Type.abstractInt);
+ const cases = await d.get(`division_scalar_vector${vec_size}`);
+ await run(t, abstractIntBinary('/'), [Type.abstractInt, vec_type], vec_type, t.params, cases);
+ });
+
+g.test('division_vector_scalar')
+ .specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr')
+ .desc(
+ `
+Expression: x / y
+`
+ )
+ .params(u =>
+ u.combine('inputSource', onlyConstInputSource).combine('vectorize_lhs', [2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const vec_size = t.params.vectorize_lhs;
+ const vec_type = Type.vec(vec_size, Type.abstractInt);
+ const cases = await d.get(`division_vector${vec_size}_scalar`);
+ await run(t, abstractIntBinary('/'), [vec_type, Type.abstractInt], vec_type, t.params, cases);
+ });
+
+g.test('multiplication')
+ .specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr')
+ .desc(
+ `
+Expression: x * y
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('multiplication');
+ await run(
+ t,
+ abstractIntBinary('*'),
+ [Type.abstractInt, Type.abstractInt],
+ Type.abstractInt,
+ t.params,
+ cases
+ );
+ });
+
+g.test('multiplication_scalar_vector')
+ .specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr')
+ .desc(
+ `
+Expression: x * y
+`
+ )
+ .params(u =>
+ u.combine('inputSource', onlyConstInputSource).combine('vectorize_rhs', [2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const vec_size = t.params.vectorize_rhs;
+ const vec_type = Type.vec(vec_size, Type.abstractInt);
+ const cases = await d.get(`multiplication_scalar_vector${vec_size}`);
+ await run(t, abstractIntBinary('*'), [Type.abstractInt, vec_type], vec_type, t.params, cases);
+ });
+
+g.test('multiplication_vector_scalar')
+ .specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr')
+ .desc(
+ `
+Expression: x * y
+`
+ )
+ .params(u =>
+ u.combine('inputSource', onlyConstInputSource).combine('vectorize_lhs', [2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const vec_size = t.params.vectorize_lhs;
+ const vec_type = Type.vec(vec_size, Type.abstractInt);
+ const cases = await d.get(`multiplication_vector${vec_size}_scalar`);
+ await run(t, abstractIntBinary('*'), [vec_type, Type.abstractInt], vec_type, t.params, cases);
+ });
+
+g.test('remainder')
+ .specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr')
+ .desc(
+ `
+Expression: x % y
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('remainder');
+ await run(
+ t,
+ abstractIntBinary('%'),
+ [Type.abstractInt, Type.abstractInt],
+ Type.abstractInt,
+ t.params,
+ cases
+ );
+ });
+
+g.test('remainder_scalar_vector')
+ .specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr')
+ .desc(
+ `
+Expression: x % y
+`
+ )
+ .params(u =>
+ u.combine('inputSource', onlyConstInputSource).combine('vectorize_rhs', [2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const vec_size = t.params.vectorize_rhs;
+ const vec_type = Type.vec(vec_size, Type.abstractInt);
+ const cases = await d.get(`remainder_scalar_vector${vec_size}`);
+ await run(t, abstractIntBinary('%'), [Type.abstractInt, vec_type], vec_type, t.params, cases);
+ });
+
+g.test('remainder_vector_scalar')
+ .specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr')
+ .desc(
+ `
+Expression: x % y
+`
+ )
+ .params(u =>
+ u.combine('inputSource', onlyConstInputSource).combine('vectorize_lhs', [2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const vec_size = t.params.vectorize_lhs;
+ const vec_type = Type.vec(vec_size, Type.abstractInt);
+ const cases = await d.get(`remainder_vector${vec_size}_scalar`);
+ await run(t, abstractIntBinary('%'), [vec_type, Type.abstractInt], vec_type, t.params, cases);
+ });
+
+g.test('subtraction')
+ .specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr')
+ .desc(
+ `
+Expression: x - y
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('subtraction');
+ await run(
+ t,
+ abstractIntBinary('-'),
+ [Type.abstractInt, Type.abstractInt],
+ Type.abstractInt,
+ t.params,
+ cases
+ );
+ });
+
+g.test('subtraction_scalar_vector')
+ .specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr')
+ .desc(
+ `
+Expression: x - y
+`
+ )
+ .params(u =>
+ u.combine('inputSource', onlyConstInputSource).combine('vectorize_rhs', [2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const vec_size = t.params.vectorize_rhs;
+ const vec_type = Type.vec(vec_size, Type.abstractInt);
+ const cases = await d.get(`subtraction_scalar_vector${vec_size}`);
+ await run(t, abstractIntBinary('-'), [Type.abstractInt, vec_type], vec_type, t.params, cases);
+ });
+
+g.test('subtraction_vector_scalar')
+ .specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr')
+ .desc(
+ `
+Expression: x - y
+`
+ )
+ .params(u =>
+ u.combine('inputSource', onlyConstInputSource).combine('vectorize_lhs', [2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const vec_size = t.params.vectorize_lhs;
+ const vec_type = Type.vec(vec_size, Type.abstractInt);
+ const cases = await d.get(`subtraction_vector${vec_size}_scalar`);
+ await run(t, abstractIntBinary('-'), [vec_type, Type.abstractInt], vec_type, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/ai_comparison.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/ai_comparison.spec.ts
new file mode 100644
index 0000000000..899e651054
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/ai_comparison.spec.ts
@@ -0,0 +1,124 @@
+export const description = `
+Execution Tests for the abstract-int comparison expressions
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../gpu_test.js';
+import { bool, abstractInt, Type } from '../../../../util/conversion.js';
+import { vectorI64Range } from '../../../../util/math.js';
+import { Case } from '../case.js';
+import { onlyConstInputSource, run } from '../expression.js';
+
+import { binary } from './binary.js';
+
+export const g = makeTestGroup(GPUTest);
+
+/**
+ * @returns a test case for the provided left hand & right hand values and
+ * expected boolean result.
+ */
+function makeCase(lhs: bigint, rhs: bigint, expected_answer: boolean): Case {
+ return { input: [abstractInt(lhs), abstractInt(rhs)], expected: bool(expected_answer) };
+}
+
+g.test('equals')
+ .specURL('https://www.w3.org/TR/WGSL/#comparison-expr')
+ .desc(
+ `
+Expression: x == y
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = vectorI64Range(2).map(v => makeCase(v[0], v[1], v[0] === v[1]));
+ await run(t, binary('=='), [Type.abstractInt, Type.abstractInt], Type.bool, t.params, cases);
+ });
+
+g.test('not_equals')
+ .specURL('https://www.w3.org/TR/WGSL/#comparison-expr')
+ .desc(
+ `
+Expression: x != y
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = vectorI64Range(2).map(v => makeCase(v[0], v[1], v[0] !== v[1]));
+ await run(t, binary('!='), [Type.abstractInt, Type.abstractInt], Type.bool, t.params, cases);
+ });
+
+g.test('less_than')
+ .specURL('https://www.w3.org/TR/WGSL/#comparison-expr')
+ .desc(
+ `
+Expression: x < y
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = vectorI64Range(2).map(v => makeCase(v[0], v[1], v[0] < v[1]));
+ await run(t, binary('<'), [Type.abstractInt, Type.abstractInt], Type.bool, t.params, cases);
+ });
+
+g.test('less_equals')
+ .specURL('https://www.w3.org/TR/WGSL/#comparison-expr')
+ .desc(
+ `
+Expression: x <= y
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = vectorI64Range(2).map(v => makeCase(v[0], v[1], v[0] <= v[1]));
+ await run(t, binary('<='), [Type.abstractInt, Type.abstractInt], Type.bool, t.params, cases);
+ });
+
+g.test('greater_than')
+ .specURL('https://www.w3.org/TR/WGSL/#comparison-expr')
+ .desc(
+ `
+Expression: x > y
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = vectorI64Range(2).map(v => makeCase(v[0], v[1], v[0] > v[1]));
+ await run(t, binary('>'), [Type.abstractInt, Type.abstractInt], Type.bool, t.params, cases);
+ });
+
+g.test('greater_equals')
+ .specURL('https://www.w3.org/TR/WGSL/#comparison-expr')
+ .desc(
+ `
+Expression: x >= y
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = vectorI64Range(2).map(v => makeCase(v[0], v[1], v[0] >= v[1]));
+ await run(t, binary('>='), [Type.abstractInt, Type.abstractInt], Type.bool, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/binary.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/binary.ts
index f0b01b839b..4df0c67d78 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/binary.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/binary.ts
@@ -3,6 +3,7 @@ import {
basicExpressionBuilder,
compoundAssignmentBuilder,
abstractFloatShaderBuilder,
+ abstractIntShaderBuilder,
} from '../expression.js';
/* @returns a ShaderBuilder that evaluates a binary operation */
@@ -16,6 +17,11 @@ export function compoundBinary(op: string): ShaderBuilder {
}
/* @returns a ShaderBuilder that evaluates a binary operation that returns AbstractFloats */
-export function abstractBinary(op: string): ShaderBuilder {
+export function abstractFloatBinary(op: string): ShaderBuilder {
return abstractFloatShaderBuilder(values => `(${values.map(v => `(${v})`).join(op)})`);
}
+
+/* @returns a ShaderBuilder that evaluates a binary operation that returns AbstractFloats */
+export function abstractIntBinary(op: string): ShaderBuilder {
+ return abstractIntShaderBuilder(values => `(${values.map(v => `(${v})`).join(op)})`);
+}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/bitwise.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/bitwise.spec.ts
index 0d8d775352..b6e9a45e9d 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/bitwise.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/bitwise.spec.ts
@@ -3,59 +3,171 @@ Execution Tests for the bitwise binary expression operations
`;
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { assert } from '../../../../../common/util/util.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { i32, scalarType, u32 } from '../../../../util/conversion.js';
-import { allInputSources, run } from '../expression.js';
+import {
+ abstractIntBits,
+ i32Bits,
+ ScalarValue,
+ scalarType,
+ u32Bits,
+} from '../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../expression.js';
-import { binary, compoundBinary } from './binary.js';
+import { abstractIntBinary, binary, compoundBinary } from './binary.js';
export const g = makeTestGroup(GPUTest);
-function makeBitwiseOrCases(inputType: string) {
- const V = inputType === 'i32' ? i32 : u32;
- const cases = [
- // Static patterns
+/**
+ * Collection of functions and values required to implement bitwise tests for a
+ * specific scalar type
+ */
+interface ScalarImpl {
+ // builder is a mostly a wrapper around type builders like 'i32Bits' that
+ // handles the (number|bigint) type check.
+ builder: (bits: bigint | number) => ScalarValue;
+ size: 32 | 64;
+}
+
+const kScalarImpls = {
+ i32: {
+ builder: (bits: bigint | number): ScalarValue => {
+ assert(typeof bits === 'number');
+ return i32Bits(bits);
+ },
+ size: 32,
+ } as ScalarImpl,
+ u32: {
+ builder: (bits: bigint | number): ScalarValue => {
+ assert(typeof bits === 'number');
+ return u32Bits(bits);
+ },
+ size: 32,
+ } as ScalarImpl,
+ 'abstract-int': {
+ builder: (bits: bigint | number): ScalarValue => {
+ assert(typeof bits === 'bigint');
+ return abstractIntBits(bits);
+ },
+ size: 64,
+ } as ScalarImpl,
+};
+
+/** Wrapper for converting from input type strings to the appropriate implementation */
+function scalarImplForInputType(inputType: string): ScalarImpl {
+ assert(inputType === 'i32' || inputType === 'u32' || inputType === 'abstract-int');
+ return kScalarImpls[inputType];
+}
+
+/** Manually calculated bitwise-or cases used a check that the CTS test is correct */
+const kBitwiseOrStaticPatterns = {
+ 32: [
{
- input: [V(0b00000000000000000000000000000000), V(0b00000000000000000000000000000000)],
- expected: V(0b00000000000000000000000000000000),
+ input: [0b00000000000000000000000000000000, 0b00000000000000000000000000000000],
+ expected: 0b00000000000000000000000000000000,
},
{
- input: [V(0b11111111111111111111111111111111), V(0b00000000000000000000000000000000)],
- expected: V(0b11111111111111111111111111111111),
+ input: [0b11111111111111111111111111111111, 0b00000000000000000000000000000000],
+ expected: 0b11111111111111111111111111111111,
},
{
- input: [V(0b00000000000000000000000000000000), V(0b11111111111111111111111111111111)],
- expected: V(0b11111111111111111111111111111111),
+ input: [0b00000000000000000000000000000000, 0b11111111111111111111111111111111],
+ expected: 0b11111111111111111111111111111111,
},
{
- input: [V(0b11111111111111111111111111111111), V(0b11111111111111111111111111111111)],
- expected: V(0b11111111111111111111111111111111),
+ input: [0b11111111111111111111111111111111, 0b11111111111111111111111111111111],
+ expected: 0b11111111111111111111111111111111,
},
{
- input: [V(0b10100100010010100100010010100100), V(0b00000000000000000000000000000000)],
- expected: V(0b10100100010010100100010010100100),
+ input: [0b10100100010010100100010010100100, 0b00000000000000000000000000000000],
+ expected: 0b10100100010010100100010010100100,
},
{
- input: [V(0b00000000000000000000000000000000), V(0b10100100010010100100010010100100)],
- expected: V(0b10100100010010100100010010100100),
+ input: [0b00000000000000000000000000000000, 0b10100100010010100100010010100100],
+ expected: 0b10100100010010100100010010100100,
},
{
- input: [V(0b01010010001001010010001001010010), V(0b10100100010010100100010010100100)],
- expected: V(0b11110110011011110110011011110110),
+ input: [0b01010010001001010010001001010010, 0b10100100010010100100010010100100],
+ expected: 0b11110110011011110110011011110110,
},
- ];
- // Permute all combinations of a single bit being set for the LHS and RHS
- for (let i = 0; i < 32; i++) {
- const lhs = 1 << i;
- for (let j = 0; j < 32; j++) {
- const rhs = 1 << j;
- cases.push({
- input: [V(lhs), V(rhs)],
- expected: V(lhs | rhs),
+ ],
+ 64: [
+ {
+ input: [
+ 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ ],
+ expected: 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ },
+ {
+ input: [
+ 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ ],
+ expected: 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ },
+ {
+ input: [
+ 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ ],
+ expected: 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ },
+ {
+ input: [
+ 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ ],
+ expected: 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ },
+ {
+ input: [
+ 0b1010010001001010010001001010010010100100010010100100010010100100n,
+ 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ ],
+ expected: 0b1010010001001010010001001010010010100100010010100100010010100100n,
+ },
+ {
+ input: [
+ 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ 0b1010010001001010010001001010010010100100010010100100010010100100n,
+ ],
+ expected: 0b1010010001001010010001001010010010100100010010100100010010100100n,
+ },
+ {
+ input: [
+ 0b0101001000100101001000100101001010100100010010100100010010100100n,
+ 0b1010010001001010010001001010010010100100010010100100010010100100n,
+ ],
+ expected: 0b1111011001101111011001101111011010100100010010100100010010100100n,
+ },
+ ],
+};
+
+/** @returns a set of bitwise-or cases for the specific input type */
+function makeBitwiseOrCases(inputType: string) {
+ const impl = scalarImplForInputType(inputType);
+ const indices =
+ impl.size === 64 ? [...Array(impl.size).keys()].map(BigInt) : [...Array(impl.size).keys()];
+
+ return [
+ ...kBitwiseOrStaticPatterns[impl.size].map(c => {
+ return {
+ input: c.input.map(impl.builder),
+ expected: impl.builder(c.expected),
+ };
+ }),
+ // Permute all combinations of a single bit being set for the LHS and RHS
+ ...indices.flatMap(i => {
+ const lhs = typeof i === 'bigint' ? 1n << i : 1 << i;
+ return indices.map(j => {
+ const rhs = typeof j === 'bigint' ? 1n << j : 1 << j;
+ assert(typeof lhs === typeof rhs);
+ const result = typeof lhs === 'bigint' ? lhs | (rhs as bigint) : lhs | (rhs as number);
+ return { input: [impl.builder(lhs), impl.builder(rhs)], expected: impl.builder(result) };
});
- }
- }
- return cases;
+ }),
+ ];
}
g.test('bitwise_or')
@@ -63,22 +175,25 @@ g.test('bitwise_or')
.desc(
`
e1 | e2: T
-T is i32, u32, vecN<i32>, or vecN<u32>
+T is i32, u32, abstractInt, vecN<i32>, vecN<u32>, or vecN<abstractInt>
Bitwise-or. Component-wise when T is a vector.
`
)
.params(u =>
u
- .combine('type', ['i32', 'u32'] as const)
+ .combine('type', ['i32', 'u32', 'abstract-int'] as const)
.combine('inputSource', allInputSources)
.combine('vectorize', [undefined, 2, 3, 4] as const)
)
.fn(async t => {
+ t.skipIf(
+ t.params.type === 'abstract-int' && !onlyConstInputSource.includes(t.params.inputSource)
+ );
const type = scalarType(t.params.type);
const cases = makeBitwiseOrCases(t.params.type);
-
- await run(t, binary('|'), [type, type], type, t.params, cases);
+ const builder = t.params.type === 'abstract-int' ? abstractIntBinary('|') : binary('|');
+ await run(t, builder, [type, type], type, t.params, cases);
});
g.test('bitwise_or_compound')
@@ -104,59 +219,137 @@ Bitwise-or. Component-wise when T is a vector.
await run(t, compoundBinary('|='), [type, type], type, t.params, cases);
});
-function makeBitwiseAndCases(inputType: string) {
- const V = inputType === 'i32' ? i32 : u32;
- const cases = [
- // Static patterns
+/** Manually calculated bitwise-and cases used a check that the CTS test is correct */
+const kBitwiseAndStaticPatterns = {
+ 32: [
{
- input: [V(0b00000000000000000000000000000000), V(0b00000000000000000000000000000000)],
- expected: V(0b00000000000000000000000000000000),
+ input: [0b00000000000000000000000000000000, 0b00000000000000000000000000000000],
+ expected: 0b00000000000000000000000000000000,
},
{
- input: [V(0b11111111111111111111111111111111), V(0b00000000000000000000000000000000)],
- expected: V(0b00000000000000000000000000000000),
+ input: [0b11111111111111111111111111111111, 0b00000000000000000000000000000000],
+ expected: 0b00000000000000000000000000000000,
},
{
- input: [V(0b00000000000000000000000000000000), V(0b11111111111111111111111111111111)],
- expected: V(0b00000000000000000000000000000000),
+ input: [0b00000000000000000000000000000000, 0b11111111111111111111111111111111],
+ expected: 0b00000000000000000000000000000000,
},
{
- input: [V(0b11111111111111111111111111111111), V(0b11111111111111111111111111111111)],
- expected: V(0b11111111111111111111111111111111),
+ input: [0b11111111111111111111111111111111, 0b11111111111111111111111111111111],
+ expected: 0b11111111111111111111111111111111,
},
{
- input: [V(0b10100100010010100100010010100100), V(0b00000000000000000000000000000000)],
- expected: V(0b00000000000000000000000000000000),
+ input: [0b10100100010010100100010010100100, 0b00000000000000000000000000000000],
+ expected: 0b00000000000000000000000000000000,
},
{
- input: [V(0b10100100010010100100010010100100), V(0b11111111111111111111111111111111)],
- expected: V(0b10100100010010100100010010100100),
+ input: [0b10100100010010100100010010100100, 0b11111111111111111111111111111111],
+ expected: 0b10100100010010100100010010100100,
},
{
- input: [V(0b00000000000000000000000000000000), V(0b10100100010010100100010010100100)],
- expected: V(0b00000000000000000000000000000000),
+ input: [0b00000000000000000000000000000000, 0b10100100010010100100010010100100],
+ expected: 0b00000000000000000000000000000000,
},
{
- input: [V(0b11111111111111111111111111111111), V(0b10100100010010100100010010100100)],
- expected: V(0b10100100010010100100010010100100),
+ input: [0b11111111111111111111111111111111, 0b10100100010010100100010010100100],
+ expected: 0b10100100010010100100010010100100,
},
{
- input: [V(0b01010010001001010010001001010010), V(0b01011011101101011011101101011011)],
- expected: V(0b01010010001001010010001001010010),
+ input: [0b01010010001001010010001001010010, 0b01011011101101011011101101011011],
+ expected: 0b01010010001001010010001001010010,
},
- ];
- // Permute all combinations of a single bit being set for the LHS and all but one bit set for the RHS
- for (let i = 0; i < 32; i++) {
- const lhs = 1 << i;
- for (let j = 0; j < 32; j++) {
- const rhs = 0xffffffff ^ (1 << j);
- cases.push({
- input: [V(lhs), V(rhs)],
- expected: V(lhs & rhs),
+ ],
+ 64: [
+ {
+ input: [
+ 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ ],
+ expected: 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ },
+ {
+ input: [
+ 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ ],
+ expected: 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ },
+ {
+ input: [
+ 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ ],
+ expected: 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ },
+ {
+ input: [
+ 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ ],
+ expected: 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ },
+ {
+ input: [
+ 0b1010010001001010010001001010010010100100010010100100010010100100n,
+ 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ ],
+ expected: 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ },
+ {
+ input: [
+ 0b1010010001001010010001001010010010100100010010100100010010100100n,
+ 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ ],
+ expected: 0b1010010001001010010001001010010010100100010010100100010010100100n,
+ },
+ {
+ input: [
+ 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ 0b1010010001001010010001001010010010100100010010100100010010100100n,
+ ],
+ expected: 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ },
+ {
+ input: [
+ 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ 0b1010010001001010010001001010010010100100010010100100010010100100n,
+ ],
+ expected: 0b1010010001001010010001001010010010100100010010100100010010100100n,
+ },
+ {
+ input: [
+ 0b0101001000100101001000100101001001010010001001010010001001010010n,
+ 0b0101101110110101101110110101101101011011101101011011101101011011n,
+ ],
+ expected: 0b0101001000100101001000100101001001010010001001010010001001010010n,
+ },
+ ],
+};
+
+/** @returns a set of bitwise-or cases for the specific input type */
+function makeBitwiseAndCases(inputType: string) {
+ const impl = scalarImplForInputType(inputType);
+ const indices =
+ impl.size === 64 ? [...Array(impl.size).keys()].map(BigInt) : [...Array(impl.size).keys()];
+
+ return [
+ ...kBitwiseAndStaticPatterns[impl.size].map(c => {
+ return {
+ input: c.input.map(impl.builder),
+ expected: impl.builder(c.expected),
+ };
+ }),
+ // Permute all combinations of a single bit being set for the LHS and all but one bit set for the RHS
+ ...indices.flatMap(i => {
+ const lhs = typeof i === 'bigint' ? 1n << i : 1 << i;
+ return indices.map(j => {
+ const rhs = typeof j === 'bigint' ? 0xffffffffffffffffn ^ (1n << j) : 0xffffffff ^ (1 << j);
+ assert(typeof lhs === typeof rhs);
+ const result = typeof lhs === 'bigint' ? lhs & (rhs as bigint) : lhs & (rhs as number);
+ return { input: [impl.builder(lhs), impl.builder(rhs)], expected: impl.builder(result) };
});
- }
- }
- return cases;
+ }),
+ ];
}
g.test('bitwise_and')
@@ -164,21 +357,25 @@ g.test('bitwise_and')
.desc(
`
e1 & e2: T
-T is i32, u32, vecN<i32>, or vecN<u32>
+T is i32, u32, AbstractInt, vecN<i32>, vecN<u32>, or vecN<AbstractInt>
Bitwise-and. Component-wise when T is a vector.
`
)
.params(u =>
u
- .combine('type', ['i32', 'u32'] as const)
+ .combine('type', ['i32', 'u32', 'abstract-int'] as const)
.combine('inputSource', allInputSources)
.combine('vectorize', [undefined, 2, 3, 4] as const)
)
.fn(async t => {
+ t.skipIf(
+ t.params.type === 'abstract-int' && !onlyConstInputSource.includes(t.params.inputSource)
+ );
const type = scalarType(t.params.type);
const cases = makeBitwiseAndCases(t.params.type);
- await run(t, binary('&'), [type, type], type, t.params, cases);
+ const builder = t.params.type === 'abstract-int' ? abstractIntBinary('&') : binary('&');
+ await run(t, builder, [type, type], type, t.params, cases);
});
g.test('bitwise_and_compound')
@@ -203,59 +400,137 @@ Bitwise-and. Component-wise when T is a vector.
await run(t, compoundBinary('&='), [type, type], type, t.params, cases);
});
-function makeBitwiseExcluseOrCases(inputType: string) {
- const V = inputType === 'i32' ? i32 : u32;
- const cases = [
- // Static patterns
+/** Manually calculated bitwise-or cases used a check that the CTS test is correct */
+const kBitwiseExclusiveOrStaticPatterns = {
+ 32: [
{
- input: [V(0b00000000000000000000000000000000), V(0b00000000000000000000000000000000)],
- expected: V(0b00000000000000000000000000000000),
+ input: [0b00000000000000000000000000000000, 0b00000000000000000000000000000000],
+ expected: 0b00000000000000000000000000000000,
},
{
- input: [V(0b11111111111111111111111111111111), V(0b00000000000000000000000000000000)],
- expected: V(0b11111111111111111111111111111111),
+ input: [0b11111111111111111111111111111111, 0b00000000000000000000000000000000],
+ expected: 0b11111111111111111111111111111111,
},
{
- input: [V(0b00000000000000000000000000000000), V(0b11111111111111111111111111111111)],
- expected: V(0b11111111111111111111111111111111),
+ input: [0b00000000000000000000000000000000, 0b11111111111111111111111111111111],
+ expected: 0b11111111111111111111111111111111,
},
{
- input: [V(0b11111111111111111111111111111111), V(0b11111111111111111111111111111111)],
- expected: V(0b00000000000000000000000000000000),
+ input: [0b11111111111111111111111111111111, 0b11111111111111111111111111111111],
+ expected: 0b00000000000000000000000000000000,
},
{
- input: [V(0b10100100010010100100010010100100), V(0b00000000000000000000000000000000)],
- expected: V(0b10100100010010100100010010100100),
+ input: [0b10100100010010100100010010100100, 0b00000000000000000000000000000000],
+ expected: 0b10100100010010100100010010100100,
},
{
- input: [V(0b10100100010010100100010010100100), V(0b11111111111111111111111111111111)],
- expected: V(0b01011011101101011011101101011011),
+ input: [0b10100100010010100100010010100100, 0b11111111111111111111111111111111],
+ expected: 0b01011011101101011011101101011011,
},
{
- input: [V(0b00000000000000000000000000000000), V(0b10100100010010100100010010100100)],
- expected: V(0b10100100010010100100010010100100),
+ input: [0b00000000000000000000000000000000, 0b10100100010010100100010010100100],
+ expected: 0b10100100010010100100010010100100,
},
{
- input: [V(0b11111111111111111111111111111111), V(0b10100100010010100100010010100100)],
- expected: V(0b01011011101101011011101101011011),
+ input: [0b11111111111111111111111111111111, 0b10100100010010100100010010100100],
+ expected: 0b01011011101101011011101101011011,
},
{
- input: [V(0b01010010001001010010001001010010), V(0b01011011101101011011101101011011)],
- expected: V(0b00001001100100001001100100001001),
+ input: [0b01010010001001010010001001010010, 0b01011011101101011011101101011011],
+ expected: 0b00001001100100001001100100001001,
},
- ];
- // Permute all combinations of a single bit being set for the LHS and all but one bit set for the RHS
- for (let i = 0; i < 32; i++) {
- const lhs = 1 << i;
- for (let j = 0; j < 32; j++) {
- const rhs = 0xffffffff ^ (1 << j);
- cases.push({
- input: [V(lhs), V(rhs)],
- expected: V(lhs ^ rhs),
+ ],
+ 64: [
+ {
+ input: [
+ 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ ],
+ expected: 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ },
+ {
+ input: [
+ 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ ],
+ expected: 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ },
+ {
+ input: [
+ 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ ],
+ expected: 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ },
+ {
+ input: [
+ 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ ],
+ expected: 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ },
+ {
+ input: [
+ 0b1010010001001010010001001010010010100100010010100100010010100100n,
+ 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ ],
+ expected: 0b1010010001001010010001001010010010100100010010100100010010100100n,
+ },
+ {
+ input: [
+ 0b1010010001001010010001001010010010100100010010100100010010100100n,
+ 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ ],
+ expected: 0b0101101110110101101110110101101101011011101101011011101101011011n,
+ },
+ {
+ input: [
+ 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ 0b1010010001001010010001001010010010100100010010100100010010100100n,
+ ],
+ expected: 0b1010010001001010010001001010010010100100010010100100010010100100n,
+ },
+ {
+ input: [
+ 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ 0b1010010001001010010001001010010010100100010010100100010010100100n,
+ ],
+ expected: 0b0101101110110101101110110101101101011011101101011011101101011011n,
+ },
+ {
+ input: [
+ 0b0101001000100101001000100101001001010010001001010010001001010010n,
+ 0b0101101110110101101110110101101101011011101101011011101101011011n,
+ ],
+ expected: 0b0000100110010000100110010000100100001001100100001001100100001001n,
+ },
+ ],
+};
+
+/** @returns a set of bitwise-xor cases for the specific input type */
+function makeBitwiseExclusiveOrCases(inputType: string) {
+ const impl = scalarImplForInputType(inputType);
+ const indices =
+ impl.size === 64 ? [...Array(impl.size).keys()].map(BigInt) : [...Array(impl.size).keys()];
+
+ return [
+ ...kBitwiseExclusiveOrStaticPatterns[impl.size].map(c => {
+ return {
+ input: c.input.map(impl.builder),
+ expected: impl.builder(c.expected),
+ };
+ }),
+ // Permute all combinations of a single bit being set for the LHS and all but one bit set for the RHS
+ ...indices.flatMap(i => {
+ const lhs = typeof i === 'bigint' ? 1n << i : 1 << i;
+ return indices.map(j => {
+ const rhs = typeof j === 'bigint' ? 0xffffffffffffffffn ^ (1n << j) : 0xffffffff ^ (1 << j);
+ assert(typeof lhs === typeof rhs);
+ const result = typeof lhs === 'bigint' ? lhs ^ (rhs as bigint) : lhs ^ (rhs as number);
+ return { input: [impl.builder(lhs), impl.builder(rhs)], expected: impl.builder(result) };
});
- }
- }
- return cases;
+ }),
+ ];
}
g.test('bitwise_exclusive_or')
@@ -263,21 +538,25 @@ g.test('bitwise_exclusive_or')
.desc(
`
e1 ^ e2: T
-T is i32, u32, vecN<i32>, or vecN<u32>
+T is i32, u32, abstractInt, vecN<i32>, vecN<u32>, or vecN<abstractInt>
Bitwise-exclusive-or. Component-wise when T is a vector.
`
)
.params(u =>
u
- .combine('type', ['i32', 'u32'] as const)
+ .combine('type', ['i32', 'u32', 'abstract-int'] as const)
.combine('inputSource', allInputSources)
.combine('vectorize', [undefined, 2, 3, 4] as const)
)
.fn(async t => {
+ t.skipIf(
+ t.params.type === 'abstract-int' && !onlyConstInputSource.includes(t.params.inputSource)
+ );
const type = scalarType(t.params.type);
- const cases = makeBitwiseExcluseOrCases(t.params.type);
- await run(t, binary('^'), [type, type], type, t.params, cases);
+ const cases = makeBitwiseExclusiveOrCases(t.params.type);
+ const builder = t.params.type === 'abstract-int' ? abstractIntBinary('^') : binary('^');
+ await run(t, builder, [type, type], type, t.params, cases);
});
g.test('bitwise_exclusive_or_compound')
@@ -298,6 +577,6 @@ Bitwise-exclusive-or. Component-wise when T is a vector.
)
.fn(async t => {
const type = scalarType(t.params.type);
- const cases = makeBitwiseExcluseOrCases(t.params.type);
+ const cases = makeBitwiseExclusiveOrCases(t.params.type);
await run(t, compoundBinary('^='), [type, type], type, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/bitwise_shift.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/bitwise_shift.spec.ts
index 5457b7ceab..e2ed29d3c2 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/bitwise_shift.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/bitwise_shift.spec.ts
@@ -4,8 +4,9 @@ Execution Tests for the bitwise shift binary expression operations
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { i32, scalarType, ScalarType, TypeU32, u32 } from '../../../../util/conversion.js';
-import { allInputSources, CaseList, run } from '../expression.js';
+import { i32, scalarType, ScalarType, Type, u32 } from '../../../../util/conversion.js';
+import { Case } from '../case.js';
+import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
@@ -65,9 +66,9 @@ function is_valid_const_shift_right(e1: number, e1Type: string, e2: number) {
// Returns all cases of shifting e1 left by [0,63]. If `is_const` is true, cases that are
// invalid for const eval are not returned.
-function generate_shift_left_cases(e1: number, e1Type: string, is_const: boolean): CaseList {
+function generate_shift_left_cases(e1: number, e1Type: string, is_const: boolean): Case[] {
const V = e1Type === 'i32' ? i32 : u32;
- const cases: CaseList = [];
+ const cases: Case[] = [];
for (let shift = 0; shift < 64; ++shift) {
const e2 = shift;
if (is_const && !is_valid_const_shift_left(e1, e1Type, e2)) {
@@ -81,9 +82,9 @@ function generate_shift_left_cases(e1: number, e1Type: string, is_const: boolean
// Returns all cases of shifting e1 right by [0,63]. If `is_const` is true, cases that are
// invalid for const eval are not returned.
-function generate_shift_right_cases(e1: number, e1Type: string, is_const: boolean): CaseList {
+function generate_shift_right_cases(e1: number, e1Type: string, is_const: boolean): Case[] {
const V = e1Type === 'i32' ? i32 : u32;
- const cases: CaseList = [];
+ const cases: Case[] = [];
for (let shift = 0; shift < 64; ++shift) {
const e2 = shift;
if (is_const && !is_valid_const_shift_right(e1, e1Type, e2)) {
@@ -107,7 +108,7 @@ function makeShiftLeftConcreteCases(inputType: string, inputSource: string, type
const V = inputType === 'i32' ? i32 : u32;
const is_const = inputSource === 'const';
- const cases: CaseList = [
+ const cases: Case[] = [
{
input: /* */ [V(0b00000000000000000000000000000001), u32(1)],
expected: /**/ V(0b00000000000000000000000000000010),
@@ -193,7 +194,7 @@ Shift left (shifted value is concrete)
.fn(async t => {
const type = scalarType(t.params.type);
const cases = makeShiftLeftConcreteCases(t.params.type, t.params.inputSource, type);
- await run(t, binary('<<'), [type, TypeU32], type, t.params, cases);
+ await run(t, binary('<<'), [type, Type.u32], type, t.params, cases);
});
g.test('shift_left_concrete_compound')
@@ -214,14 +215,14 @@ Shift left (shifted value is concrete)
.fn(async t => {
const type = scalarType(t.params.type);
const cases = makeShiftLeftConcreteCases(t.params.type, t.params.inputSource, type);
- await run(t, compoundBinary('<<='), [type, TypeU32], type, t.params, cases);
+ await run(t, compoundBinary('<<='), [type, Type.u32], type, t.params, cases);
});
function makeShiftRightConcreteCases(inputType: string, inputSource: string, type: ScalarType) {
const V = inputType === 'i32' ? i32 : u32;
const is_const = inputSource === 'const';
- const cases: CaseList = [
+ const cases: Case[] = [
{
input: /* */ [V(0b00000000000000000000000000000001), u32(1)],
expected: /**/ V(0b00000000000000000000000000000000),
@@ -318,7 +319,7 @@ Shift right (shifted value is concrete)
.fn(async t => {
const type = scalarType(t.params.type);
const cases = makeShiftRightConcreteCases(t.params.type, t.params.inputSource, type);
- await run(t, binary('>>'), [type, TypeU32], type, t.params, cases);
+ await run(t, binary('>>'), [type, Type.u32], type, t.params, cases);
});
g.test('shift_right_concrete_compound')
@@ -339,5 +340,5 @@ Shift right (shifted value is concrete)
.fn(async t => {
const type = scalarType(t.params.type);
const cases = makeShiftRightConcreteCases(t.params.type, t.params.inputSource, type);
- await run(t, compoundBinary('>>='), [type, TypeU32], type, t.params, cases);
+ await run(t, compoundBinary('>>='), [type, Type.u32], type, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/bool_logical.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/bool_logical.spec.ts
index e3aa448fe3..0e76f50824 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/bool_logical.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/bool_logical.spec.ts
@@ -4,7 +4,7 @@ Execution Tests for the boolean binary logical expression operations
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { bool, TypeBool } from '../../../../util/conversion.js';
+import { bool, Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
@@ -33,7 +33,7 @@ Logical "and". Component-wise when T is a vector. Evaluates both e1 and e2.
{ input: [bool(true), bool(true)], expected: bool(true) },
];
- await run(t, binary('&'), [TypeBool, TypeBool], TypeBool, t.params, cases);
+ await run(t, binary('&'), [Type.bool, Type.bool], Type.bool, t.params, cases);
});
g.test('and_compound')
@@ -55,7 +55,7 @@ Logical "and". Component-wise when T is a vector. Evaluates both e1 and e2.
{ input: [bool(true), bool(true)], expected: bool(true) },
];
- await run(t, compoundBinary('&='), [TypeBool, TypeBool], TypeBool, t.params, cases);
+ await run(t, compoundBinary('&='), [Type.bool, Type.bool], Type.bool, t.params, cases);
});
g.test('and_short_circuit')
@@ -75,7 +75,7 @@ short_circuiting "and". Yields true if both e1 and e2 are true; evaluates e2 onl
{ input: [bool(true), bool(true)], expected: bool(true) },
];
- await run(t, binary('&&'), [TypeBool, TypeBool], TypeBool, t.params, cases);
+ await run(t, binary('&&'), [Type.bool, Type.bool], Type.bool, t.params, cases);
});
g.test('or')
@@ -97,7 +97,7 @@ Logical "or". Component-wise when T is a vector. Evaluates both e1 and e2.
{ input: [bool(true), bool(true)], expected: bool(true) },
];
- await run(t, binary('|'), [TypeBool, TypeBool], TypeBool, t.params, cases);
+ await run(t, binary('|'), [Type.bool, Type.bool], Type.bool, t.params, cases);
});
g.test('or_compound')
@@ -119,7 +119,7 @@ Logical "or". Component-wise when T is a vector. Evaluates both e1 and e2.
{ input: [bool(true), bool(true)], expected: bool(true) },
];
- await run(t, compoundBinary('|='), [TypeBool, TypeBool], TypeBool, t.params, cases);
+ await run(t, compoundBinary('|='), [Type.bool, Type.bool], Type.bool, t.params, cases);
});
g.test('or_short_circuit')
@@ -139,7 +139,7 @@ short_circuiting "and". Yields true if both e1 and e2 are true; evaluates e2 onl
{ input: [bool(true), bool(true)], expected: bool(true) },
];
- await run(t, binary('||'), [TypeBool, TypeBool], TypeBool, t.params, cases);
+ await run(t, binary('||'), [Type.bool, Type.bool], Type.bool, t.params, cases);
});
g.test('equals')
@@ -161,7 +161,7 @@ Equality. Component-wise when T is a vector.
{ input: [bool(true), bool(true)], expected: bool(true) },
];
- await run(t, binary('=='), [TypeBool, TypeBool], TypeBool, t.params, cases);
+ await run(t, binary('=='), [Type.bool, Type.bool], Type.bool, t.params, cases);
});
g.test('not_equals')
@@ -183,5 +183,5 @@ Equality. Component-wise when T is a vector.
{ input: [bool(true), bool(true)], expected: bool(false) },
];
- await run(t, binary('!='), [TypeBool, TypeBool], TypeBool, t.params, cases);
+ await run(t, binary('!='), [Type.bool, Type.bool], Type.bool, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_addition.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_addition.cache.ts
new file mode 100644
index 0000000000..f179d48a13
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_addition.cache.ts
@@ -0,0 +1,60 @@
+import { FP, FPVector } from '../../../../util/floating_point.js';
+import { sparseScalarF16Range, sparseVectorF16Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+const additionVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
+ return FP.f16.toVector(v.map(e => FP.f16.additionInterval(e, s)));
+};
+
+const additionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
+ return FP.f16.toVector(v.map(e => FP.f16.additionInterval(s, e)));
+};
+
+const scalar_cases = ([true, false] as const)
+ .map(nonConst => ({
+ [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateScalarPairToIntervalCases(
+ sparseScalarF16Range(),
+ sparseScalarF16Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.additionInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const vector_scalar_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateVectorScalarToVectorCases(
+ sparseVectorF16Range(dim),
+ sparseScalarF16Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ additionVectorScalarInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const scalar_vector_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateScalarVectorToVectorCases(
+ sparseScalarF16Range(),
+ sparseVectorF16Range(dim),
+ nonConst ? 'unfiltered' : 'finite',
+ additionScalarVectorInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f16_addition', {
+ ...scalar_cases,
+ ...vector_scalar_cases,
+ ...scalar_vector_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_addition.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_addition.spec.ts
index 8948f90499..d9aa44bba0 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_addition.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_addition.spec.ts
@@ -4,73 +4,14 @@ Execution Tests for non-matrix f16 addition expression
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF16, TypeVec } from '../../../../util/conversion.js';
-import { FP, FPVector } from '../../../../util/floating_point.js';
-import { sparseF16Range, sparseVectorF16Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
-
-const additionVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
- return FP.f16.toVector(v.map(e => FP.f16.additionInterval(e, s)));
-};
-
-const additionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
- return FP.f16.toVector(v.map(e => FP.f16.additionInterval(s, e)));
-};
+import { d } from './f16_addition.cache.js';
export const g = makeTestGroup(GPUTest);
-const scalar_cases = ([true, false] as const)
- .map(nonConst => ({
- [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateScalarPairToIntervalCases(
- sparseF16Range(),
- sparseF16Range(),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.additionInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const vector_scalar_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateVectorScalarToVectorCases(
- sparseVectorF16Range(dim),
- sparseF16Range(),
- nonConst ? 'unfiltered' : 'finite',
- additionVectorScalarInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const scalar_vector_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateScalarVectorToVectorCases(
- sparseF16Range(),
- sparseVectorF16Range(dim),
- nonConst ? 'unfiltered' : 'finite',
- additionScalarVectorInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f16_addition', {
- ...scalar_cases,
- ...vector_scalar_cases,
- ...scalar_vector_cases,
-});
-
g.test('scalar')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -87,7 +28,7 @@ Accuracy: Correctly rounded
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, binary('+'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, binary('+'), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
g.test('vector')
@@ -106,7 +47,7 @@ Accuracy: Correctly rounded
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const' // Using vectorize to generate vector cases based on scalar cases
);
- await run(t, binary('+'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, binary('+'), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
g.test('scalar_compound')
@@ -127,7 +68,7 @@ Accuracy: Correctly rounded
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, compoundBinary('+='), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, compoundBinary('+='), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
g.test('vector_scalar')
@@ -150,8 +91,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('+'),
- [TypeVec(dim, TypeF16), TypeF16],
- TypeVec(dim, TypeF16),
+ [Type.vec(dim, Type.f16), Type.f16],
+ Type.vec(dim, Type.f16),
t.params,
cases
);
@@ -177,8 +118,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('+='),
- [TypeVec(dim, TypeF16), TypeF16],
- TypeVec(dim, TypeF16),
+ [Type.vec(dim, Type.f16), Type.f16],
+ Type.vec(dim, Type.f16),
t.params,
cases
);
@@ -204,8 +145,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('+'),
- [TypeF16, TypeVec(dim, TypeF16)],
- TypeVec(dim, TypeF16),
+ [Type.f16, Type.vec(dim, Type.f16)],
+ Type.vec(dim, Type.f16),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_comparison.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_comparison.cache.ts
new file mode 100644
index 0000000000..c0c0d4f8a4
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_comparison.cache.ts
@@ -0,0 +1,144 @@
+import { anyOf } from '../../../../util/compare.js';
+import { bool, f16, ScalarValue } from '../../../../util/conversion.js';
+import { flushSubnormalNumberF16, vectorF16Range } from '../../../../util/math.js';
+import { Case } from '../case.js';
+import { makeCaseCache } from '../case_cache.js';
+
+/**
+ * @returns a test case for the provided left hand & right hand values and truth function.
+ * Handles quantization and subnormals.
+ */
+function makeCase(
+ lhs: number,
+ rhs: number,
+ truthFunc: (lhs: ScalarValue, rhs: ScalarValue) => boolean
+): Case {
+ // Subnormal float values may be flushed at any time.
+ // https://www.w3.org/TR/WGSL/#floating-point-evaluation
+ const f16_lhs = f16(lhs);
+ const f16_rhs = f16(rhs);
+ const lhs_options = new Set([f16_lhs, f16(flushSubnormalNumberF16(lhs))]);
+ const rhs_options = new Set([f16_rhs, f16(flushSubnormalNumberF16(rhs))]);
+ const expected: Array<ScalarValue> = [];
+ lhs_options.forEach(l => {
+ rhs_options.forEach(r => {
+ const result = bool(truthFunc(l, r));
+ if (!expected.includes(result)) {
+ expected.push(result);
+ }
+ });
+ });
+
+ return { input: [f16_lhs, f16_rhs], expected: anyOf(...expected) };
+}
+
+export const d = makeCaseCache('binary/f16_logical', {
+ equals_non_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) === (rhs.value as number);
+ };
+
+ return vectorF16Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ equals_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) === (rhs.value as number);
+ };
+
+ return vectorF16Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ not_equals_non_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) !== (rhs.value as number);
+ };
+
+ return vectorF16Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ not_equals_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) !== (rhs.value as number);
+ };
+
+ return vectorF16Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ less_than_non_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) < (rhs.value as number);
+ };
+
+ return vectorF16Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ less_than_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) < (rhs.value as number);
+ };
+
+ return vectorF16Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ less_equals_non_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) <= (rhs.value as number);
+ };
+
+ return vectorF16Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ less_equals_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) <= (rhs.value as number);
+ };
+
+ return vectorF16Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ greater_than_non_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) > (rhs.value as number);
+ };
+
+ return vectorF16Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ greater_than_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) > (rhs.value as number);
+ };
+
+ return vectorF16Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ greater_equals_non_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) >= (rhs.value as number);
+ };
+
+ return vectorF16Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ greater_equals_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) >= (rhs.value as number);
+ };
+
+ return vectorF16Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_comparison.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_comparison.spec.ts
index ae7e1675c5..b978cd3c99 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_comparison.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_comparison.spec.ts
@@ -4,155 +4,14 @@ Execution Tests for the f16 comparison operations
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { anyOf } from '../../../../util/compare.js';
-import { bool, f16, Scalar, TypeBool, TypeF16 } from '../../../../util/conversion.js';
-import { flushSubnormalNumberF16, vectorF16Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
-import { allInputSources, Case, run } from '../expression.js';
+import { Type } from '../../../../util/conversion.js';
+import { allInputSources, run } from '../expression.js';
import { binary } from './binary.js';
+import { d } from './f16_comparison.cache.js';
export const g = makeTestGroup(GPUTest);
-/**
- * @returns a test case for the provided left hand & right hand values and truth function.
- * Handles quantization and subnormals.
- */
-function makeCase(
- lhs: number,
- rhs: number,
- truthFunc: (lhs: Scalar, rhs: Scalar) => boolean
-): Case {
- // Subnormal float values may be flushed at any time.
- // https://www.w3.org/TR/WGSL/#floating-point-evaluation
- const f16_lhs = f16(lhs);
- const f16_rhs = f16(rhs);
- const lhs_options = new Set([f16_lhs, f16(flushSubnormalNumberF16(lhs))]);
- const rhs_options = new Set([f16_rhs, f16(flushSubnormalNumberF16(rhs))]);
- const expected: Array<Scalar> = [];
- lhs_options.forEach(l => {
- rhs_options.forEach(r => {
- const result = bool(truthFunc(l, r));
- if (!expected.includes(result)) {
- expected.push(result);
- }
- });
- });
-
- return { input: [f16_lhs, f16_rhs], expected: anyOf(...expected) };
-}
-
-export const d = makeCaseCache('binary/f16_logical', {
- equals_non_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) === (rhs.value as number);
- };
-
- return vectorF16Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- equals_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) === (rhs.value as number);
- };
-
- return vectorF16Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- not_equals_non_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) !== (rhs.value as number);
- };
-
- return vectorF16Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- not_equals_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) !== (rhs.value as number);
- };
-
- return vectorF16Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- less_than_non_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) < (rhs.value as number);
- };
-
- return vectorF16Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- less_than_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) < (rhs.value as number);
- };
-
- return vectorF16Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- less_equals_non_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) <= (rhs.value as number);
- };
-
- return vectorF16Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- less_equals_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) <= (rhs.value as number);
- };
-
- return vectorF16Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- greater_than_non_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) > (rhs.value as number);
- };
-
- return vectorF16Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- greater_than_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) > (rhs.value as number);
- };
-
- return vectorF16Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- greater_equals_non_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) >= (rhs.value as number);
- };
-
- return vectorF16Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- greater_equals_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) >= (rhs.value as number);
- };
-
- return vectorF16Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
-});
-
g.test('equals')
.specURL('https://www.w3.org/TR/WGSL/#comparison-expr')
.desc(
@@ -171,7 +30,7 @@ Accuracy: Correct result
const cases = await d.get(
t.params.inputSource === 'const' ? 'equals_const' : 'equals_non_const'
);
- await run(t, binary('=='), [TypeF16, TypeF16], TypeBool, t.params, cases);
+ await run(t, binary('=='), [Type.f16, Type.f16], Type.bool, t.params, cases);
});
g.test('not_equals')
@@ -192,7 +51,7 @@ Accuracy: Correct result
const cases = await d.get(
t.params.inputSource === 'const' ? 'not_equals_const' : 'not_equals_non_const'
);
- await run(t, binary('!='), [TypeF16, TypeF16], TypeBool, t.params, cases);
+ await run(t, binary('!='), [Type.f16, Type.f16], Type.bool, t.params, cases);
});
g.test('less_than')
@@ -213,7 +72,7 @@ Accuracy: Correct result
const cases = await d.get(
t.params.inputSource === 'const' ? 'less_than_const' : 'less_than_non_const'
);
- await run(t, binary('<'), [TypeF16, TypeF16], TypeBool, t.params, cases);
+ await run(t, binary('<'), [Type.f16, Type.f16], Type.bool, t.params, cases);
});
g.test('less_equals')
@@ -234,7 +93,7 @@ Accuracy: Correct result
const cases = await d.get(
t.params.inputSource === 'const' ? 'less_equals_const' : 'less_equals_non_const'
);
- await run(t, binary('<='), [TypeF16, TypeF16], TypeBool, t.params, cases);
+ await run(t, binary('<='), [Type.f16, Type.f16], Type.bool, t.params, cases);
});
g.test('greater_than')
@@ -255,7 +114,7 @@ Accuracy: Correct result
const cases = await d.get(
t.params.inputSource === 'const' ? 'greater_than_const' : 'greater_than_non_const'
);
- await run(t, binary('>'), [TypeF16, TypeF16], TypeBool, t.params, cases);
+ await run(t, binary('>'), [Type.f16, Type.f16], Type.bool, t.params, cases);
});
g.test('greater_equals')
@@ -276,5 +135,5 @@ Accuracy: Correct result
const cases = await d.get(
t.params.inputSource === 'const' ? 'greater_equals_const' : 'greater_equals_non_const'
);
- await run(t, binary('>='), [TypeF16, TypeF16], TypeBool, t.params, cases);
+ await run(t, binary('>='), [Type.f16, Type.f16], Type.bool, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_division.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_division.cache.ts
new file mode 100644
index 0000000000..95590ca467
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_division.cache.ts
@@ -0,0 +1,60 @@
+import { FP, FPVector } from '../../../../util/floating_point.js';
+import { sparseScalarF16Range, sparseVectorF16Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+const divisionVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
+ return FP.f16.toVector(v.map(e => FP.f16.divisionInterval(e, s)));
+};
+
+const divisionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
+ return FP.f16.toVector(v.map(e => FP.f16.divisionInterval(s, e)));
+};
+
+const scalar_cases = ([true, false] as const)
+ .map(nonConst => ({
+ [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateScalarPairToIntervalCases(
+ sparseScalarF16Range(),
+ sparseScalarF16Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.divisionInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const vector_scalar_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateVectorScalarToVectorCases(
+ sparseVectorF16Range(dim),
+ sparseScalarF16Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ divisionVectorScalarInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const scalar_vector_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateScalarVectorToVectorCases(
+ sparseScalarF16Range(),
+ sparseVectorF16Range(dim),
+ nonConst ? 'unfiltered' : 'finite',
+ divisionScalarVectorInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f16_division', {
+ ...scalar_cases,
+ ...vector_scalar_cases,
+ ...scalar_vector_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_division.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_division.spec.ts
index c3b8fc04db..8a155024db 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_division.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_division.spec.ts
@@ -4,73 +4,14 @@ Execution Tests for non-matrix f16 division expression
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF16, TypeVec } from '../../../../util/conversion.js';
-import { FP, FPVector } from '../../../../util/floating_point.js';
-import { sparseF16Range, sparseVectorF16Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
-
-const divisionVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
- return FP.f16.toVector(v.map(e => FP.f16.divisionInterval(e, s)));
-};
-
-const divisionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
- return FP.f16.toVector(v.map(e => FP.f16.divisionInterval(s, e)));
-};
+import { d } from './f16_division.cache.js';
export const g = makeTestGroup(GPUTest);
-const scalar_cases = ([true, false] as const)
- .map(nonConst => ({
- [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateScalarPairToIntervalCases(
- sparseF16Range(),
- sparseF16Range(),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.divisionInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const vector_scalar_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateVectorScalarToVectorCases(
- sparseVectorF16Range(dim),
- sparseF16Range(),
- nonConst ? 'unfiltered' : 'finite',
- divisionVectorScalarInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const scalar_vector_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateScalarVectorToVectorCases(
- sparseF16Range(),
- sparseVectorF16Range(dim),
- nonConst ? 'unfiltered' : 'finite',
- divisionScalarVectorInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f16_division', {
- ...scalar_cases,
- ...vector_scalar_cases,
- ...scalar_vector_cases,
-});
-
g.test('scalar')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -87,7 +28,7 @@ Accuracy: 2.5 ULP for |y| in the range [2^-126, 2^126]
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, binary('/'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, binary('/'), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
g.test('vector')
@@ -106,7 +47,7 @@ Accuracy: 2.5 ULP for |y| in the range [2^-126, 2^126]
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const' // Using vectorize to generate vector cases based on scalar cases
);
- await run(t, binary('/'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, binary('/'), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
g.test('scalar_compound')
@@ -127,7 +68,7 @@ Accuracy: 2.5 ULP for |y| in the range [2^-126, 2^126]
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, compoundBinary('/='), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, compoundBinary('/='), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
g.test('vector_scalar')
@@ -150,8 +91,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('/'),
- [TypeVec(dim, TypeF16), TypeF16],
- TypeVec(dim, TypeF16),
+ [Type.vec(dim, Type.f16), Type.f16],
+ Type.vec(dim, Type.f16),
t.params,
cases
);
@@ -177,8 +118,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('/='),
- [TypeVec(dim, TypeF16), TypeF16],
- TypeVec(dim, TypeF16),
+ [Type.vec(dim, Type.f16), Type.f16],
+ Type.vec(dim, Type.f16),
t.params,
cases
);
@@ -204,8 +145,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('/'),
- [TypeF16, TypeVec(dim, TypeF16)],
- TypeVec(dim, TypeF16),
+ [Type.f16, Type.vec(dim, Type.f16)],
+ Type.vec(dim, Type.f16),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_addition.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_addition.cache.ts
new file mode 100644
index 0000000000..a670b08b07
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_addition.cache.ts
@@ -0,0 +1,23 @@
+import { FP } from '../../../../util/floating_point.js';
+import { sparseMatrixF16Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+// Cases: matCxR_[non_]const
+const mat_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ ([true, false] as const).map(nonConst => ({
+ [`mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateMatrixPairToMatrixCases(
+ sparseMatrixF16Range(cols, rows),
+ sparseMatrixF16Range(cols, rows),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.additionMatrixMatrixInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f16_matrix_addition', mat_cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_addition.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_addition.spec.ts
index fe64f41503..7c34b0cadd 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_addition.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_addition.spec.ts
@@ -4,36 +4,14 @@ Execution Tests for matrix f16 addition expression
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF16, TypeMat } from '../../../../util/conversion.js';
-import { FP } from '../../../../util/floating_point.js';
-import { sparseMatrixF16Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
+import { d } from './f16_matrix_addition.cache.js';
export const g = makeTestGroup(GPUTest);
-// Cases: matCxR_[non_]const
-const mat_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).flatMap(rows =>
- ([true, false] as const).map(nonConst => ({
- [`mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateMatrixPairToMatrixCases(
- sparseMatrixF16Range(cols, rows),
- sparseMatrixF16Range(cols, rows),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.additionMatrixMatrixInterval
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f16_matrix_addition', mat_cases);
-
g.test('matrix')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -60,8 +38,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('+'),
- [TypeMat(cols, rows, TypeF16), TypeMat(cols, rows, TypeF16)],
- TypeMat(cols, rows, TypeF16),
+ [Type.mat(cols, rows, Type.f16), Type.mat(cols, rows, Type.f16)],
+ Type.mat(cols, rows, Type.f16),
t.params,
cases
);
@@ -93,8 +71,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('+='),
- [TypeMat(cols, rows, TypeF16), TypeMat(cols, rows, TypeF16)],
- TypeMat(cols, rows, TypeF16),
+ [Type.mat(cols, rows, Type.f16), Type.mat(cols, rows, Type.f16)],
+ Type.mat(cols, rows, Type.f16),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_matrix_multiplication.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_matrix_multiplication.cache.ts
new file mode 100644
index 0000000000..a31813abb3
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_matrix_multiplication.cache.ts
@@ -0,0 +1,25 @@
+import { FP } from '../../../../util/floating_point.js';
+import { sparseMatrixF16Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+// Cases: matKxR_matCxK_[non_]const
+const mat_mat_cases = ([2, 3, 4] as const)
+ .flatMap(k =>
+ ([2, 3, 4] as const).flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ ([true, false] as const).map(nonConst => ({
+ [`mat${k}x${rows}_mat${cols}x${k}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateMatrixPairToMatrixCases(
+ sparseMatrixF16Range(k, rows),
+ sparseMatrixF16Range(cols, k),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.multiplicationMatrixMatrixInterval
+ );
+ },
+ }))
+ )
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f16_matrix_matrix_multiplication', mat_mat_cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_matrix_multiplication.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_matrix_multiplication.spec.ts
index 0c8b3e8c51..80ca78f7f5 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_matrix_multiplication.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_matrix_multiplication.spec.ts
@@ -4,38 +4,14 @@ Execution Tests for matrix-matrix f16 multiplication expression
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF16, TypeMat } from '../../../../util/conversion.js';
-import { FP } from '../../../../util/floating_point.js';
-import { sparseMatrixF16Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
+import { d } from './f16_matrix_matrix_multiplication.cache.js';
export const g = makeTestGroup(GPUTest);
-// Cases: matKxR_matCxK_[non_]const
-const mat_mat_cases = ([2, 3, 4] as const)
- .flatMap(k =>
- ([2, 3, 4] as const).flatMap(cols =>
- ([2, 3, 4] as const).flatMap(rows =>
- ([true, false] as const).map(nonConst => ({
- [`mat${k}x${rows}_mat${cols}x${k}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateMatrixPairToMatrixCases(
- sparseMatrixF16Range(k, rows),
- sparseMatrixF16Range(cols, k),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.multiplicationMatrixMatrixInterval
- );
- },
- }))
- )
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f16_matrix_matrix_multiplication', mat_mat_cases);
-
g.test('matrix_matrix')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -68,8 +44,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('*'),
- [TypeMat(x_cols, x_rows, TypeF16), TypeMat(y_cols, y_rows, TypeF16)],
- TypeMat(y_cols, x_rows, TypeF16),
+ [Type.mat(x_cols, x_rows, Type.f16), Type.mat(y_cols, y_rows, Type.f16)],
+ Type.mat(y_cols, x_rows, Type.f16),
t.params,
cases
);
@@ -106,8 +82,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('*='),
- [TypeMat(x_cols, x_rows, TypeF16), TypeMat(y_cols, y_rows, TypeF16)],
- TypeMat(y_cols, x_rows, TypeF16),
+ [Type.mat(x_cols, x_rows, Type.f16), Type.mat(y_cols, y_rows, Type.f16)],
+ Type.mat(y_cols, x_rows, Type.f16),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_scalar_multiplication.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_scalar_multiplication.cache.ts
new file mode 100644
index 0000000000..f902a1c8bc
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_scalar_multiplication.cache.ts
@@ -0,0 +1,44 @@
+import { FP } from '../../../../util/floating_point.js';
+import { sparseMatrixF16Range, sparseScalarF16Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+// Cases: matCxR_scalar_[non_]const
+const mat_scalar_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ ([true, false] as const).map(nonConst => ({
+ [`mat${cols}x${rows}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateMatrixScalarToMatrixCases(
+ sparseMatrixF16Range(cols, rows),
+ sparseScalarF16Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.multiplicationMatrixScalarInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: scalar_matCxR_[non_]const
+const scalar_mat_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ ([true, false] as const).map(nonConst => ({
+ [`scalar_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateScalarMatrixToMatrixCases(
+ sparseScalarF16Range(),
+ sparseMatrixF16Range(cols, rows),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.multiplicationScalarMatrixInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f16_matrix_scalar_multiplication', {
+ ...mat_scalar_cases,
+ ...scalar_mat_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_scalar_multiplication.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_scalar_multiplication.spec.ts
index 29d4700ee6..aa7087738a 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_scalar_multiplication.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_scalar_multiplication.spec.ts
@@ -4,57 +4,14 @@ Execution Tests for matrix-scalar and scalar-matrix f16 multiplication expressio
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF16, TypeMat } from '../../../../util/conversion.js';
-import { FP } from '../../../../util/floating_point.js';
-import { sparseF16Range, sparseMatrixF16Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
+import { d } from './f16_matrix_scalar_multiplication.cache.js';
export const g = makeTestGroup(GPUTest);
-// Cases: matCxR_scalar_[non_]const
-const mat_scalar_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).flatMap(rows =>
- ([true, false] as const).map(nonConst => ({
- [`mat${cols}x${rows}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateMatrixScalarToMatrixCases(
- sparseMatrixF16Range(cols, rows),
- sparseF16Range(),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.multiplicationMatrixScalarInterval
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-// Cases: scalar_matCxR_[non_]const
-const scalar_mat_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).flatMap(rows =>
- ([true, false] as const).map(nonConst => ({
- [`scalar_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateScalarMatrixToMatrixCases(
- sparseF16Range(),
- sparseMatrixF16Range(cols, rows),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.multiplicationScalarMatrixInterval
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f16_matrix_scalar_multiplication', {
- ...mat_scalar_cases,
- ...scalar_mat_cases,
-});
-
g.test('matrix_scalar')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -83,8 +40,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('*'),
- [TypeMat(cols, rows, TypeF16), TypeF16],
- TypeMat(cols, rows, TypeF16),
+ [Type.mat(cols, rows, Type.f16), Type.f16],
+ Type.mat(cols, rows, Type.f16),
t.params,
cases
);
@@ -118,8 +75,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('*='),
- [TypeMat(cols, rows, TypeF16), TypeF16],
- TypeMat(cols, rows, TypeF16),
+ [Type.mat(cols, rows, Type.f16), Type.f16],
+ Type.mat(cols, rows, Type.f16),
t.params,
cases
);
@@ -153,8 +110,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('*'),
- [TypeF16, TypeMat(cols, rows, TypeF16)],
- TypeMat(cols, rows, TypeF16),
+ [Type.f16, Type.mat(cols, rows, Type.f16)],
+ Type.mat(cols, rows, Type.f16),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_subtraction.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_subtraction.cache.ts
new file mode 100644
index 0000000000..a6edcf7fe5
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_subtraction.cache.ts
@@ -0,0 +1,23 @@
+import { FP } from '../../../../util/floating_point.js';
+import { sparseMatrixF16Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+// Cases: matCxR_[non_]const
+const mat_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ ([true, false] as const).map(nonConst => ({
+ [`mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateMatrixPairToMatrixCases(
+ sparseMatrixF16Range(cols, rows),
+ sparseMatrixF16Range(cols, rows),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.subtractionMatrixMatrixInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f16_matrix_subtraction', mat_cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_subtraction.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_subtraction.spec.ts
index 5b5f6ba04e..e8e13d902a 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_subtraction.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_subtraction.spec.ts
@@ -4,36 +4,14 @@ Execution Tests for matrix f16 subtraction expression
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF16, TypeMat } from '../../../../util/conversion.js';
-import { FP } from '../../../../util/floating_point.js';
-import { sparseMatrixF16Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
+import { d } from './f16_matrix_subtraction.cache.js';
export const g = makeTestGroup(GPUTest);
-// Cases: matCxR_[non_]const
-const mat_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).flatMap(rows =>
- ([true, false] as const).map(nonConst => ({
- [`mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateMatrixPairToMatrixCases(
- sparseMatrixF16Range(cols, rows),
- sparseMatrixF16Range(cols, rows),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.subtractionMatrixMatrixInterval
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f16_matrix_subtraction', mat_cases);
-
g.test('matrix')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -60,8 +38,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('-'),
- [TypeMat(cols, rows, TypeF16), TypeMat(cols, rows, TypeF16)],
- TypeMat(cols, rows, TypeF16),
+ [Type.mat(cols, rows, Type.f16), Type.mat(cols, rows, Type.f16)],
+ Type.mat(cols, rows, Type.f16),
t.params,
cases
);
@@ -93,8 +71,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('-='),
- [TypeMat(cols, rows, TypeF16), TypeMat(cols, rows, TypeF16)],
- TypeMat(cols, rows, TypeF16),
+ [Type.mat(cols, rows, Type.f16), Type.mat(cols, rows, Type.f16)],
+ Type.mat(cols, rows, Type.f16),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_vector_multiplication.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_vector_multiplication.cache.ts
new file mode 100644
index 0000000000..7b822386fe
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_vector_multiplication.cache.ts
@@ -0,0 +1,44 @@
+import { FP } from '../../../../util/floating_point.js';
+import { sparseMatrixF16Range, sparseVectorF16Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+// Cases: matCxR_vecC_[non_]const
+const mat_vec_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ ([true, false] as const).map(nonConst => ({
+ [`mat${cols}x${rows}_vec${cols}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateMatrixVectorToVectorCases(
+ sparseMatrixF16Range(cols, rows),
+ sparseVectorF16Range(cols),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.multiplicationMatrixVectorInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: vecR_matCxR_[non_]const
+const vec_mat_cases = ([2, 3, 4] as const)
+ .flatMap(rows =>
+ ([2, 3, 4] as const).flatMap(cols =>
+ ([true, false] as const).map(nonConst => ({
+ [`vec${rows}_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateVectorMatrixToVectorCases(
+ sparseVectorF16Range(rows),
+ sparseMatrixF16Range(cols, rows),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.multiplicationVectorMatrixInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f16_matrix_vector_multiplication', {
+ ...mat_vec_cases,
+ ...vec_mat_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_vector_multiplication.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_vector_multiplication.spec.ts
index 3e916c7fd4..557a7cead8 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_vector_multiplication.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_vector_multiplication.spec.ts
@@ -4,57 +4,14 @@ Execution Tests for matrix-vector and vector-matrix f16 multiplication expressio
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF16, TypeMat, TypeVec } from '../../../../util/conversion.js';
-import { FP } from '../../../../util/floating_point.js';
-import { sparseMatrixF16Range, sparseVectorF16Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
+import { d } from './f16_matrix_vector_multiplication.cache.js';
export const g = makeTestGroup(GPUTest);
-// Cases: matCxR_vecC_[non_]const
-const mat_vec_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).flatMap(rows =>
- ([true, false] as const).map(nonConst => ({
- [`mat${cols}x${rows}_vec${cols}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateMatrixVectorToVectorCases(
- sparseMatrixF16Range(cols, rows),
- sparseVectorF16Range(cols),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.multiplicationMatrixVectorInterval
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-// Cases: vecR_matCxR_[non_]const
-const vec_mat_cases = ([2, 3, 4] as const)
- .flatMap(rows =>
- ([2, 3, 4] as const).flatMap(cols =>
- ([true, false] as const).map(nonConst => ({
- [`vec${rows}_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateVectorMatrixToVectorCases(
- sparseVectorF16Range(rows),
- sparseMatrixF16Range(cols, rows),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.multiplicationVectorMatrixInterval
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f16_matrix_vector_multiplication', {
- ...mat_vec_cases,
- ...vec_mat_cases,
-});
-
g.test('matrix_vector')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -83,8 +40,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('*'),
- [TypeMat(cols, rows, TypeF16), TypeVec(cols, TypeF16)],
- TypeVec(rows, TypeF16),
+ [Type.mat(cols, rows, Type.f16), Type.vec(cols, Type.f16)],
+ Type.vec(rows, Type.f16),
t.params,
cases
);
@@ -118,8 +75,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('*'),
- [TypeVec(rows, TypeF16), TypeMat(cols, rows, TypeF16)],
- TypeVec(cols, TypeF16),
+ [Type.vec(rows, Type.f16), Type.mat(cols, rows, Type.f16)],
+ Type.vec(cols, Type.f16),
t.params,
cases
);
@@ -148,8 +105,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('*='),
- [TypeVec(rows, TypeF16), TypeMat(cols, rows, TypeF16)],
- TypeVec(cols, TypeF16),
+ [Type.vec(rows, Type.f16), Type.mat(cols, rows, Type.f16)],
+ Type.vec(cols, Type.f16),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_multiplication.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_multiplication.cache.ts
new file mode 100644
index 0000000000..a94f55ccf0
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_multiplication.cache.ts
@@ -0,0 +1,60 @@
+import { FP, FPVector } from '../../../../util/floating_point.js';
+import { sparseScalarF16Range, sparseVectorF16Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+const multiplicationVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
+ return FP.f16.toVector(v.map(e => FP.f16.multiplicationInterval(e, s)));
+};
+
+const multiplicationScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
+ return FP.f16.toVector(v.map(e => FP.f16.multiplicationInterval(s, e)));
+};
+
+const scalar_cases = ([true, false] as const)
+ .map(nonConst => ({
+ [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateScalarPairToIntervalCases(
+ sparseScalarF16Range(),
+ sparseScalarF16Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.multiplicationInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const vector_scalar_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateVectorScalarToVectorCases(
+ sparseVectorF16Range(dim),
+ sparseScalarF16Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ multiplicationVectorScalarInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const scalar_vector_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateScalarVectorToVectorCases(
+ sparseScalarF16Range(),
+ sparseVectorF16Range(dim),
+ nonConst ? 'unfiltered' : 'finite',
+ multiplicationScalarVectorInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f16_multiplication', {
+ ...scalar_cases,
+ ...vector_scalar_cases,
+ ...scalar_vector_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_multiplication.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_multiplication.spec.ts
index 10041fbc17..81339d9266 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_multiplication.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_multiplication.spec.ts
@@ -4,73 +4,14 @@ Execution Tests for non-matrix f16 multiplication expression
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF16, TypeVec } from '../../../../util/conversion.js';
-import { FP, FPVector } from '../../../../util/floating_point.js';
-import { sparseF16Range, sparseVectorF16Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
-
-const multiplicationVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
- return FP.f16.toVector(v.map(e => FP.f16.multiplicationInterval(e, s)));
-};
-
-const multiplicationScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
- return FP.f16.toVector(v.map(e => FP.f16.multiplicationInterval(s, e)));
-};
+import { d } from './f16_multiplication.cache.js';
export const g = makeTestGroup(GPUTest);
-const scalar_cases = ([true, false] as const)
- .map(nonConst => ({
- [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateScalarPairToIntervalCases(
- sparseF16Range(),
- sparseF16Range(),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.multiplicationInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const vector_scalar_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateVectorScalarToVectorCases(
- sparseVectorF16Range(dim),
- sparseF16Range(),
- nonConst ? 'unfiltered' : 'finite',
- multiplicationVectorScalarInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const scalar_vector_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateScalarVectorToVectorCases(
- sparseF16Range(),
- sparseVectorF16Range(dim),
- nonConst ? 'unfiltered' : 'finite',
- multiplicationScalarVectorInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f16_multiplication', {
- ...scalar_cases,
- ...vector_scalar_cases,
- ...scalar_vector_cases,
-});
-
g.test('scalar')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -87,7 +28,7 @@ Accuracy: Correctly rounded
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, binary('*'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, binary('*'), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
g.test('vector')
@@ -106,7 +47,7 @@ Accuracy: Correctly rounded
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const' // Using vectorize to generate vector cases based on scalar cases
);
- await run(t, binary('*'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, binary('*'), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
g.test('scalar_compound')
@@ -127,7 +68,7 @@ Accuracy: Correctly rounded
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, compoundBinary('*='), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, compoundBinary('*='), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
g.test('vector_scalar')
@@ -150,8 +91,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('*'),
- [TypeVec(dim, TypeF16), TypeF16],
- TypeVec(dim, TypeF16),
+ [Type.vec(dim, Type.f16), Type.f16],
+ Type.vec(dim, Type.f16),
t.params,
cases
);
@@ -177,8 +118,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('*='),
- [TypeVec(dim, TypeF16), TypeF16],
- TypeVec(dim, TypeF16),
+ [Type.vec(dim, Type.f16), Type.f16],
+ Type.vec(dim, Type.f16),
t.params,
cases
);
@@ -204,8 +145,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('*'),
- [TypeF16, TypeVec(dim, TypeF16)],
- TypeVec(dim, TypeF16),
+ [Type.f16, Type.vec(dim, Type.f16)],
+ Type.vec(dim, Type.f16),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_remainder.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_remainder.cache.ts
new file mode 100644
index 0000000000..2c1cdc0c38
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_remainder.cache.ts
@@ -0,0 +1,60 @@
+import { FP, FPVector } from '../../../../util/floating_point.js';
+import { sparseScalarF16Range, sparseVectorF16Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+const remainderVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
+ return FP.f16.toVector(v.map(e => FP.f16.remainderInterval(e, s)));
+};
+
+const remainderScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
+ return FP.f16.toVector(v.map(e => FP.f16.remainderInterval(s, e)));
+};
+
+const scalar_cases = ([true, false] as const)
+ .map(nonConst => ({
+ [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateScalarPairToIntervalCases(
+ sparseScalarF16Range(),
+ sparseScalarF16Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.remainderInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const vector_scalar_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateVectorScalarToVectorCases(
+ sparseVectorF16Range(dim),
+ sparseScalarF16Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ remainderVectorScalarInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const scalar_vector_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateScalarVectorToVectorCases(
+ sparseScalarF16Range(),
+ sparseVectorF16Range(dim),
+ nonConst ? 'unfiltered' : 'finite',
+ remainderScalarVectorInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f16_remainder', {
+ ...scalar_cases,
+ ...vector_scalar_cases,
+ ...scalar_vector_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_remainder.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_remainder.spec.ts
index 801b84904b..0fe1cc53c6 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_remainder.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_remainder.spec.ts
@@ -4,73 +4,14 @@ Execution Tests for non-matrix f16 remainder expression
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF16, TypeVec } from '../../../../util/conversion.js';
-import { FP, FPVector } from '../../../../util/floating_point.js';
-import { sparseF16Range, sparseVectorF16Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
-
-const remainderVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
- return FP.f16.toVector(v.map(e => FP.f16.remainderInterval(e, s)));
-};
-
-const remainderScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
- return FP.f16.toVector(v.map(e => FP.f16.remainderInterval(s, e)));
-};
+import { d } from './f16_remainder.cache.js';
export const g = makeTestGroup(GPUTest);
-const scalar_cases = ([true, false] as const)
- .map(nonConst => ({
- [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateScalarPairToIntervalCases(
- sparseF16Range(),
- sparseF16Range(),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.remainderInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const vector_scalar_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateVectorScalarToVectorCases(
- sparseVectorF16Range(dim),
- sparseF16Range(),
- nonConst ? 'unfiltered' : 'finite',
- remainderVectorScalarInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const scalar_vector_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateScalarVectorToVectorCases(
- sparseF16Range(),
- sparseVectorF16Range(dim),
- nonConst ? 'unfiltered' : 'finite',
- remainderScalarVectorInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f16_remainder', {
- ...scalar_cases,
- ...vector_scalar_cases,
- ...scalar_vector_cases,
-});
-
g.test('scalar')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -87,7 +28,7 @@ Accuracy: Derived from x - y * trunc(x/y)
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, binary('%'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, binary('%'), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
g.test('vector')
@@ -106,7 +47,7 @@ Accuracy: Derived from x - y * trunc(x/y)
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, binary('%'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, binary('%'), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
g.test('scalar_compound')
@@ -127,7 +68,7 @@ Accuracy: Derived from x - y * trunc(x/y)
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, compoundBinary('%='), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, compoundBinary('%='), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
g.test('vector_scalar')
@@ -150,8 +91,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('%'),
- [TypeVec(dim, TypeF16), TypeF16],
- TypeVec(dim, TypeF16),
+ [Type.vec(dim, Type.f16), Type.f16],
+ Type.vec(dim, Type.f16),
t.params,
cases
);
@@ -177,8 +118,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('%='),
- [TypeVec(dim, TypeF16), TypeF16],
- TypeVec(dim, TypeF16),
+ [Type.vec(dim, Type.f16), Type.f16],
+ Type.vec(dim, Type.f16),
t.params,
cases
);
@@ -204,8 +145,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('%'),
- [TypeF16, TypeVec(dim, TypeF16)],
- TypeVec(dim, TypeF16),
+ [Type.f16, Type.vec(dim, Type.f16)],
+ Type.vec(dim, Type.f16),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_subtraction.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_subtraction.cache.ts
new file mode 100644
index 0000000000..a68b58449b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_subtraction.cache.ts
@@ -0,0 +1,60 @@
+import { FP, FPVector } from '../../../../util/floating_point.js';
+import { sparseScalarF16Range, sparseVectorF16Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+const subtractionVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
+ return FP.f16.toVector(v.map(e => FP.f16.subtractionInterval(e, s)));
+};
+
+const subtractionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
+ return FP.f16.toVector(v.map(e => FP.f16.subtractionInterval(s, e)));
+};
+
+const scalar_cases = ([true, false] as const)
+ .map(nonConst => ({
+ [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateScalarPairToIntervalCases(
+ sparseScalarF16Range(),
+ sparseScalarF16Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.subtractionInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const vector_scalar_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateVectorScalarToVectorCases(
+ sparseVectorF16Range(dim),
+ sparseScalarF16Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ subtractionVectorScalarInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const scalar_vector_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateScalarVectorToVectorCases(
+ sparseScalarF16Range(),
+ sparseVectorF16Range(dim),
+ nonConst ? 'unfiltered' : 'finite',
+ subtractionScalarVectorInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f16_subtraction', {
+ ...scalar_cases,
+ ...vector_scalar_cases,
+ ...scalar_vector_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_subtraction.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_subtraction.spec.ts
index a64d556837..6b29aad6ad 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_subtraction.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_subtraction.spec.ts
@@ -4,73 +4,14 @@ Execution Tests for non-matrix f16 subtraction expression
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF16, TypeVec } from '../../../../util/conversion.js';
-import { FP, FPVector } from '../../../../util/floating_point.js';
-import { sparseF16Range, sparseVectorF16Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
-
-const subtractionVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
- return FP.f16.toVector(v.map(e => FP.f16.subtractionInterval(e, s)));
-};
-
-const subtractionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
- return FP.f16.toVector(v.map(e => FP.f16.subtractionInterval(s, e)));
-};
+import { d } from './f16_subtraction.cache.js';
export const g = makeTestGroup(GPUTest);
-const scalar_cases = ([true, false] as const)
- .map(nonConst => ({
- [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateScalarPairToIntervalCases(
- sparseF16Range(),
- sparseF16Range(),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.subtractionInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const vector_scalar_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateVectorScalarToVectorCases(
- sparseVectorF16Range(dim),
- sparseF16Range(),
- nonConst ? 'unfiltered' : 'finite',
- subtractionVectorScalarInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const scalar_vector_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateScalarVectorToVectorCases(
- sparseF16Range(),
- sparseVectorF16Range(dim),
- nonConst ? 'unfiltered' : 'finite',
- subtractionScalarVectorInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f16_subtraction', {
- ...scalar_cases,
- ...vector_scalar_cases,
- ...scalar_vector_cases,
-});
-
g.test('scalar')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -87,7 +28,7 @@ Accuracy: Correctly rounded
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, binary('-'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, binary('-'), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
g.test('vector')
@@ -106,7 +47,7 @@ Accuracy: Correctly rounded
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const' // Using vectorize to generate vector cases based on scalar cases
);
- await run(t, binary('-'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, binary('-'), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
g.test('scalar_compound')
@@ -127,7 +68,7 @@ Accuracy: Correctly rounded
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, compoundBinary('-='), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, compoundBinary('-='), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
g.test('vector_scalar')
@@ -150,8 +91,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('-'),
- [TypeVec(dim, TypeF16), TypeF16],
- TypeVec(dim, TypeF16),
+ [Type.vec(dim, Type.f16), Type.f16],
+ Type.vec(dim, Type.f16),
t.params,
cases
);
@@ -177,8 +118,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('-='),
- [TypeVec(dim, TypeF16), TypeF16],
- TypeVec(dim, TypeF16),
+ [Type.vec(dim, Type.f16), Type.f16],
+ Type.vec(dim, Type.f16),
t.params,
cases
);
@@ -204,8 +145,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('-'),
- [TypeF16, TypeVec(dim, TypeF16)],
- TypeVec(dim, TypeF16),
+ [Type.f16, Type.vec(dim, Type.f16)],
+ Type.vec(dim, Type.f16),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_addition.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_addition.cache.ts
new file mode 100644
index 0000000000..9353671fb0
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_addition.cache.ts
@@ -0,0 +1,60 @@
+import { FP, FPVector } from '../../../../util/floating_point.js';
+import { sparseScalarF32Range, sparseVectorF32Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+const additionVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
+ return FP.f32.toVector(v.map(e => FP.f32.additionInterval(e, s)));
+};
+
+const additionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
+ return FP.f32.toVector(v.map(e => FP.f32.additionInterval(s, e)));
+};
+
+const scalar_cases = ([true, false] as const)
+ .map(nonConst => ({
+ [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateScalarPairToIntervalCases(
+ sparseScalarF32Range(),
+ sparseScalarF32Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f32.additionInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const vector_scalar_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateVectorScalarToVectorCases(
+ sparseVectorF32Range(dim),
+ sparseScalarF32Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ additionVectorScalarInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const scalar_vector_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateScalarVectorToVectorCases(
+ sparseScalarF32Range(),
+ sparseVectorF32Range(dim),
+ nonConst ? 'unfiltered' : 'finite',
+ additionScalarVectorInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f32_addition', {
+ ...scalar_cases,
+ ...vector_scalar_cases,
+ ...scalar_vector_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_addition.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_addition.spec.ts
index 65739f67ca..9a502ae677 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_addition.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_addition.spec.ts
@@ -4,73 +4,14 @@ Execution Tests for non-matrix f32 addition expression
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF32, TypeVec } from '../../../../util/conversion.js';
-import { FP, FPVector } from '../../../../util/floating_point.js';
-import { sparseF32Range, sparseVectorF32Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
-
-const additionVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
- return FP.f32.toVector(v.map(e => FP.f32.additionInterval(e, s)));
-};
-
-const additionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
- return FP.f32.toVector(v.map(e => FP.f32.additionInterval(s, e)));
-};
+import { d } from './f32_addition.cache.js';
export const g = makeTestGroup(GPUTest);
-const scalar_cases = ([true, false] as const)
- .map(nonConst => ({
- [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateScalarPairToIntervalCases(
- sparseF32Range(),
- sparseF32Range(),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.additionInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const vector_scalar_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateVectorScalarToVectorCases(
- sparseVectorF32Range(dim),
- sparseF32Range(),
- nonConst ? 'unfiltered' : 'finite',
- additionVectorScalarInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const scalar_vector_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateScalarVectorToVectorCases(
- sparseF32Range(),
- sparseVectorF32Range(dim),
- nonConst ? 'unfiltered' : 'finite',
- additionScalarVectorInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f32_addition', {
- ...scalar_cases,
- ...vector_scalar_cases,
- ...scalar_vector_cases,
-});
-
g.test('scalar')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -84,7 +25,7 @@ Accuracy: Correctly rounded
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, binary('+'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, binary('+'), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('vector')
@@ -100,7 +41,7 @@ Accuracy: Correctly rounded
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const' // Using vectorize to generate vector cases based on scalar cases
);
- await run(t, binary('+'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, binary('+'), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('scalar_compound')
@@ -118,7 +59,7 @@ Accuracy: Correctly rounded
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, compoundBinary('+='), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, compoundBinary('+='), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('vector_scalar')
@@ -138,8 +79,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('+'),
- [TypeVec(dim, TypeF32), TypeF32],
- TypeVec(dim, TypeF32),
+ [Type.vec(dim, Type.f32), Type.f32],
+ Type.vec(dim, Type.f32),
t.params,
cases
);
@@ -162,8 +103,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('+='),
- [TypeVec(dim, TypeF32), TypeF32],
- TypeVec(dim, TypeF32),
+ [Type.vec(dim, Type.f32), Type.f32],
+ Type.vec(dim, Type.f32),
t.params,
cases
);
@@ -186,8 +127,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('+'),
- [TypeF32, TypeVec(dim, TypeF32)],
- TypeVec(dim, TypeF32),
+ [Type.f32, Type.vec(dim, Type.f32)],
+ Type.vec(dim, Type.f32),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_comparison.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_comparison.cache.ts
new file mode 100644
index 0000000000..28fb22e820
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_comparison.cache.ts
@@ -0,0 +1,144 @@
+import { anyOf } from '../../../../util/compare.js';
+import { bool, f32, ScalarValue } from '../../../../util/conversion.js';
+import { flushSubnormalNumberF32, vectorF32Range } from '../../../../util/math.js';
+import { Case } from '../case.js';
+import { makeCaseCache } from '../case_cache.js';
+
+/**
+ * @returns a test case for the provided left hand & right hand values and truth function.
+ * Handles quantization and subnormals.
+ */
+function makeCase(
+ lhs: number,
+ rhs: number,
+ truthFunc: (lhs: ScalarValue, rhs: ScalarValue) => boolean
+): Case {
+ // Subnormal float values may be flushed at any time.
+ // https://www.w3.org/TR/WGSL/#floating-point-evaluation
+ const f32_lhs = f32(lhs);
+ const f32_rhs = f32(rhs);
+ const lhs_options = new Set([f32_lhs, f32(flushSubnormalNumberF32(lhs))]);
+ const rhs_options = new Set([f32_rhs, f32(flushSubnormalNumberF32(rhs))]);
+ const expected: Array<ScalarValue> = [];
+ lhs_options.forEach(l => {
+ rhs_options.forEach(r => {
+ const result = bool(truthFunc(l, r));
+ if (!expected.includes(result)) {
+ expected.push(result);
+ }
+ });
+ });
+
+ return { input: [f32_lhs, f32_rhs], expected: anyOf(...expected) };
+}
+
+export const d = makeCaseCache('binary/f32_logical', {
+ equals_non_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) === (rhs.value as number);
+ };
+
+ return vectorF32Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ equals_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) === (rhs.value as number);
+ };
+
+ return vectorF32Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ not_equals_non_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) !== (rhs.value as number);
+ };
+
+ return vectorF32Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ not_equals_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) !== (rhs.value as number);
+ };
+
+ return vectorF32Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ less_than_non_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) < (rhs.value as number);
+ };
+
+ return vectorF32Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ less_than_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) < (rhs.value as number);
+ };
+
+ return vectorF32Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ less_equals_non_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) <= (rhs.value as number);
+ };
+
+ return vectorF32Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ less_equals_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) <= (rhs.value as number);
+ };
+
+ return vectorF32Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ greater_than_non_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) > (rhs.value as number);
+ };
+
+ return vectorF32Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ greater_than_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) > (rhs.value as number);
+ };
+
+ return vectorF32Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ greater_equals_non_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) >= (rhs.value as number);
+ };
+
+ return vectorF32Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ greater_equals_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) >= (rhs.value as number);
+ };
+
+ return vectorF32Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_comparison.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_comparison.spec.ts
index ef862e7757..42eb8934a4 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_comparison.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_comparison.spec.ts
@@ -4,155 +4,14 @@ Execution Tests for the f32 comparison operations
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { anyOf } from '../../../../util/compare.js';
-import { bool, f32, Scalar, TypeBool, TypeF32 } from '../../../../util/conversion.js';
-import { flushSubnormalNumberF32, vectorF32Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
-import { allInputSources, Case, run } from '../expression.js';
+import { Type } from '../../../../util/conversion.js';
+import { allInputSources, run } from '../expression.js';
import { binary } from './binary.js';
+import { d } from './f32_comparison.cache.js';
export const g = makeTestGroup(GPUTest);
-/**
- * @returns a test case for the provided left hand & right hand values and truth function.
- * Handles quantization and subnormals.
- */
-function makeCase(
- lhs: number,
- rhs: number,
- truthFunc: (lhs: Scalar, rhs: Scalar) => boolean
-): Case {
- // Subnormal float values may be flushed at any time.
- // https://www.w3.org/TR/WGSL/#floating-point-evaluation
- const f32_lhs = f32(lhs);
- const f32_rhs = f32(rhs);
- const lhs_options = new Set([f32_lhs, f32(flushSubnormalNumberF32(lhs))]);
- const rhs_options = new Set([f32_rhs, f32(flushSubnormalNumberF32(rhs))]);
- const expected: Array<Scalar> = [];
- lhs_options.forEach(l => {
- rhs_options.forEach(r => {
- const result = bool(truthFunc(l, r));
- if (!expected.includes(result)) {
- expected.push(result);
- }
- });
- });
-
- return { input: [f32_lhs, f32_rhs], expected: anyOf(...expected) };
-}
-
-export const d = makeCaseCache('binary/f32_logical', {
- equals_non_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) === (rhs.value as number);
- };
-
- return vectorF32Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- equals_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) === (rhs.value as number);
- };
-
- return vectorF32Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- not_equals_non_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) !== (rhs.value as number);
- };
-
- return vectorF32Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- not_equals_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) !== (rhs.value as number);
- };
-
- return vectorF32Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- less_than_non_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) < (rhs.value as number);
- };
-
- return vectorF32Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- less_than_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) < (rhs.value as number);
- };
-
- return vectorF32Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- less_equals_non_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) <= (rhs.value as number);
- };
-
- return vectorF32Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- less_equals_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) <= (rhs.value as number);
- };
-
- return vectorF32Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- greater_than_non_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) > (rhs.value as number);
- };
-
- return vectorF32Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- greater_than_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) > (rhs.value as number);
- };
-
- return vectorF32Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- greater_equals_non_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) >= (rhs.value as number);
- };
-
- return vectorF32Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- greater_equals_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) >= (rhs.value as number);
- };
-
- return vectorF32Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
-});
-
g.test('equals')
.specURL('https://www.w3.org/TR/WGSL/#comparison-expr')
.desc(
@@ -168,7 +27,7 @@ Accuracy: Correct result
const cases = await d.get(
t.params.inputSource === 'const' ? 'equals_const' : 'equals_non_const'
);
- await run(t, binary('=='), [TypeF32, TypeF32], TypeBool, t.params, cases);
+ await run(t, binary('=='), [Type.f32, Type.f32], Type.bool, t.params, cases);
});
g.test('not_equals')
@@ -186,7 +45,7 @@ Accuracy: Correct result
const cases = await d.get(
t.params.inputSource === 'const' ? 'not_equals_const' : 'not_equals_non_const'
);
- await run(t, binary('!='), [TypeF32, TypeF32], TypeBool, t.params, cases);
+ await run(t, binary('!='), [Type.f32, Type.f32], Type.bool, t.params, cases);
});
g.test('less_than')
@@ -204,7 +63,7 @@ Accuracy: Correct result
const cases = await d.get(
t.params.inputSource === 'const' ? 'less_than_const' : 'less_than_non_const'
);
- await run(t, binary('<'), [TypeF32, TypeF32], TypeBool, t.params, cases);
+ await run(t, binary('<'), [Type.f32, Type.f32], Type.bool, t.params, cases);
});
g.test('less_equals')
@@ -222,7 +81,7 @@ Accuracy: Correct result
const cases = await d.get(
t.params.inputSource === 'const' ? 'less_equals_const' : 'less_equals_non_const'
);
- await run(t, binary('<='), [TypeF32, TypeF32], TypeBool, t.params, cases);
+ await run(t, binary('<='), [Type.f32, Type.f32], Type.bool, t.params, cases);
});
g.test('greater_than')
@@ -240,7 +99,7 @@ Accuracy: Correct result
const cases = await d.get(
t.params.inputSource === 'const' ? 'greater_than_const' : 'greater_than_non_const'
);
- await run(t, binary('>'), [TypeF32, TypeF32], TypeBool, t.params, cases);
+ await run(t, binary('>'), [Type.f32, Type.f32], Type.bool, t.params, cases);
});
g.test('greater_equals')
@@ -258,5 +117,5 @@ Accuracy: Correct result
const cases = await d.get(
t.params.inputSource === 'const' ? 'greater_equals_const' : 'greater_equals_non_const'
);
- await run(t, binary('>='), [TypeF32, TypeF32], TypeBool, t.params, cases);
+ await run(t, binary('>='), [Type.f32, Type.f32], Type.bool, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_division.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_division.cache.ts
new file mode 100644
index 0000000000..017f7a451a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_division.cache.ts
@@ -0,0 +1,60 @@
+import { FP, FPVector } from '../../../../util/floating_point.js';
+import { sparseScalarF32Range, sparseVectorF32Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+const divisionVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
+ return FP.f32.toVector(v.map(e => FP.f32.divisionInterval(e, s)));
+};
+
+const divisionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
+ return FP.f32.toVector(v.map(e => FP.f32.divisionInterval(s, e)));
+};
+
+const scalar_cases = ([true, false] as const)
+ .map(nonConst => ({
+ [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateScalarPairToIntervalCases(
+ sparseScalarF32Range(),
+ sparseScalarF32Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f32.divisionInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const vector_scalar_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateVectorScalarToVectorCases(
+ sparseVectorF32Range(dim),
+ sparseScalarF32Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ divisionVectorScalarInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const scalar_vector_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateScalarVectorToVectorCases(
+ sparseScalarF32Range(),
+ sparseVectorF32Range(dim),
+ nonConst ? 'unfiltered' : 'finite',
+ divisionScalarVectorInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f32_division', {
+ ...scalar_cases,
+ ...vector_scalar_cases,
+ ...scalar_vector_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_division.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_division.spec.ts
index bd3793bf8a..bdd71e69eb 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_division.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_division.spec.ts
@@ -4,73 +4,14 @@ Execution Tests for non-matrix f32 division expression
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF32, TypeVec } from '../../../../util/conversion.js';
-import { FP, FPVector } from '../../../../util/floating_point.js';
-import { sparseF32Range, sparseVectorF32Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
-
-const divisionVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
- return FP.f32.toVector(v.map(e => FP.f32.divisionInterval(e, s)));
-};
-
-const divisionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
- return FP.f32.toVector(v.map(e => FP.f32.divisionInterval(s, e)));
-};
+import { d } from './f32_division.cache.js';
export const g = makeTestGroup(GPUTest);
-const scalar_cases = ([true, false] as const)
- .map(nonConst => ({
- [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateScalarPairToIntervalCases(
- sparseF32Range(),
- sparseF32Range(),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.divisionInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const vector_scalar_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateVectorScalarToVectorCases(
- sparseVectorF32Range(dim),
- sparseF32Range(),
- nonConst ? 'unfiltered' : 'finite',
- divisionVectorScalarInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const scalar_vector_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateScalarVectorToVectorCases(
- sparseF32Range(),
- sparseVectorF32Range(dim),
- nonConst ? 'unfiltered' : 'finite',
- divisionScalarVectorInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f32_division', {
- ...scalar_cases,
- ...vector_scalar_cases,
- ...scalar_vector_cases,
-});
-
g.test('scalar')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -84,7 +25,7 @@ Accuracy: 2.5 ULP for |y| in the range [2^-126, 2^126]
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, binary('/'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, binary('/'), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('vector')
@@ -100,7 +41,7 @@ Accuracy: 2.5 ULP for |y| in the range [2^-126, 2^126]
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const' // Using vectorize to generate vector cases based on scalar cases
);
- await run(t, binary('/'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, binary('/'), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('scalar_compound')
@@ -118,7 +59,7 @@ Accuracy: 2.5 ULP for |y| in the range [2^-126, 2^126]
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, compoundBinary('/='), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, compoundBinary('/='), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('vector_scalar')
@@ -138,8 +79,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('/'),
- [TypeVec(dim, TypeF32), TypeF32],
- TypeVec(dim, TypeF32),
+ [Type.vec(dim, Type.f32), Type.f32],
+ Type.vec(dim, Type.f32),
t.params,
cases
);
@@ -162,8 +103,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('/='),
- [TypeVec(dim, TypeF32), TypeF32],
- TypeVec(dim, TypeF32),
+ [Type.vec(dim, Type.f32), Type.f32],
+ Type.vec(dim, Type.f32),
t.params,
cases
);
@@ -186,8 +127,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('/'),
- [TypeF32, TypeVec(dim, TypeF32)],
- TypeVec(dim, TypeF32),
+ [Type.f32, Type.vec(dim, Type.f32)],
+ Type.vec(dim, Type.f32),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_addition.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_addition.cache.ts
new file mode 100644
index 0000000000..0f3ced975d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_addition.cache.ts
@@ -0,0 +1,23 @@
+import { FP } from '../../../../util/floating_point.js';
+import { sparseMatrixF32Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+// Cases: matCxR_[non_]const
+const mat_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ ([true, false] as const).map(nonConst => ({
+ [`mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateMatrixPairToMatrixCases(
+ sparseMatrixF32Range(cols, rows),
+ sparseMatrixF32Range(cols, rows),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f32.additionMatrixMatrixInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f32_matrix_addition', mat_cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_addition.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_addition.spec.ts
index 9f11c3cac1..06a4ac47cc 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_addition.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_addition.spec.ts
@@ -4,36 +4,14 @@ Execution Tests for matrix f32 addition expression
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF32, TypeMat } from '../../../../util/conversion.js';
-import { FP } from '../../../../util/floating_point.js';
-import { sparseMatrixF32Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
+import { d } from './f32_matrix_addition.cache.js';
export const g = makeTestGroup(GPUTest);
-// Cases: matCxR_[non_]const
-const mat_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).flatMap(rows =>
- ([true, false] as const).map(nonConst => ({
- [`mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateMatrixPairToMatrixCases(
- sparseMatrixF32Range(cols, rows),
- sparseMatrixF32Range(cols, rows),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.additionMatrixMatrixInterval
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f32_matrix_addition', mat_cases);
-
g.test('matrix')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -57,8 +35,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('+'),
- [TypeMat(cols, rows, TypeF32), TypeMat(cols, rows, TypeF32)],
- TypeMat(cols, rows, TypeF32),
+ [Type.mat(cols, rows, Type.f32), Type.mat(cols, rows, Type.f32)],
+ Type.mat(cols, rows, Type.f32),
t.params,
cases
);
@@ -87,8 +65,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('+='),
- [TypeMat(cols, rows, TypeF32), TypeMat(cols, rows, TypeF32)],
- TypeMat(cols, rows, TypeF32),
+ [Type.mat(cols, rows, Type.f32), Type.mat(cols, rows, Type.f32)],
+ Type.mat(cols, rows, Type.f32),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_matrix_multiplication.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_matrix_multiplication.cache.ts
new file mode 100644
index 0000000000..cde2d74fdf
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_matrix_multiplication.cache.ts
@@ -0,0 +1,25 @@
+import { FP } from '../../../../util/floating_point.js';
+import { sparseMatrixF32Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+// Cases: matKxR_matCxK_[non_]const
+const mat_mat_cases = ([2, 3, 4] as const)
+ .flatMap(k =>
+ ([2, 3, 4] as const).flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ ([true, false] as const).map(nonConst => ({
+ [`mat${k}x${rows}_mat${cols}x${k}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateMatrixPairToMatrixCases(
+ sparseMatrixF32Range(k, rows),
+ sparseMatrixF32Range(cols, k),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f32.multiplicationMatrixMatrixInterval
+ );
+ },
+ }))
+ )
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f32_matrix_matrix_multiplication', mat_mat_cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_matrix_multiplication.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_matrix_multiplication.spec.ts
index 2c48eab187..1ff7799f46 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_matrix_multiplication.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_matrix_multiplication.spec.ts
@@ -4,38 +4,14 @@ Execution Tests for matrix-matrix f32 multiplication expression
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF32, TypeMat } from '../../../../util/conversion.js';
-import { FP } from '../../../../util/floating_point.js';
-import { sparseMatrixF32Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
+import { d } from './f32_matrix_matrix_multiplication.cache.js';
export const g = makeTestGroup(GPUTest);
-// Cases: matKxR_matCxK_[non_]const
-const mat_mat_cases = ([2, 3, 4] as const)
- .flatMap(k =>
- ([2, 3, 4] as const).flatMap(cols =>
- ([2, 3, 4] as const).flatMap(rows =>
- ([true, false] as const).map(nonConst => ({
- [`mat${k}x${rows}_mat${cols}x${k}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateMatrixPairToMatrixCases(
- sparseMatrixF32Range(k, rows),
- sparseMatrixF32Range(cols, k),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.multiplicationMatrixMatrixInterval
- );
- },
- }))
- )
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f32_matrix_matrix_multiplication', mat_mat_cases);
-
g.test('matrix_matrix')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -65,8 +41,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('*'),
- [TypeMat(x_cols, x_rows, TypeF32), TypeMat(y_cols, y_rows, TypeF32)],
- TypeMat(y_cols, x_rows, TypeF32),
+ [Type.mat(x_cols, x_rows, Type.f32), Type.mat(y_cols, y_rows, Type.f32)],
+ Type.mat(y_cols, x_rows, Type.f32),
t.params,
cases
);
@@ -100,8 +76,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('*='),
- [TypeMat(x_cols, x_rows, TypeF32), TypeMat(y_cols, y_rows, TypeF32)],
- TypeMat(y_cols, x_rows, TypeF32),
+ [Type.mat(x_cols, x_rows, Type.f32), Type.mat(y_cols, y_rows, Type.f32)],
+ Type.mat(y_cols, x_rows, Type.f32),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_scalar_multiplication.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_scalar_multiplication.cache.ts
new file mode 100644
index 0000000000..f17dac31e6
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_scalar_multiplication.cache.ts
@@ -0,0 +1,44 @@
+import { FP } from '../../../../util/floating_point.js';
+import { sparseMatrixF32Range, sparseScalarF32Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+// Cases: matCxR_scalar_[non_]const
+const mat_scalar_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ ([true, false] as const).map(nonConst => ({
+ [`mat${cols}x${rows}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateMatrixScalarToMatrixCases(
+ sparseMatrixF32Range(cols, rows),
+ sparseScalarF32Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f32.multiplicationMatrixScalarInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: scalar_matCxR_[non_]const
+const scalar_mat_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ ([true, false] as const).map(nonConst => ({
+ [`scalar_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateScalarMatrixToMatrixCases(
+ sparseScalarF32Range(),
+ sparseMatrixF32Range(cols, rows),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f32.multiplicationScalarMatrixInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f32_matrix_scalar_multiplication', {
+ ...mat_scalar_cases,
+ ...scalar_mat_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_scalar_multiplication.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_scalar_multiplication.spec.ts
index f3d36b8382..e8771d19eb 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_scalar_multiplication.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_scalar_multiplication.spec.ts
@@ -4,57 +4,14 @@ Execution Tests for matrix-scalar and scalar-matrix f32 multiplication expressio
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF32, TypeMat } from '../../../../util/conversion.js';
-import { FP } from '../../../../util/floating_point.js';
-import { sparseF32Range, sparseMatrixF32Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
+import { d } from './f32_matrix_scalar_multiplication.cache.js';
export const g = makeTestGroup(GPUTest);
-// Cases: matCxR_scalar_[non_]const
-const mat_scalar_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).flatMap(rows =>
- ([true, false] as const).map(nonConst => ({
- [`mat${cols}x${rows}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateMatrixScalarToMatrixCases(
- sparseMatrixF32Range(cols, rows),
- sparseF32Range(),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.multiplicationMatrixScalarInterval
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-// Cases: scalar_matCxR_[non_]const
-const scalar_mat_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).flatMap(rows =>
- ([true, false] as const).map(nonConst => ({
- [`scalar_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateScalarMatrixToMatrixCases(
- sparseF32Range(),
- sparseMatrixF32Range(cols, rows),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.multiplicationScalarMatrixInterval
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f32_matrix_scalar_multiplication', {
- ...mat_scalar_cases,
- ...scalar_mat_cases,
-});
-
g.test('matrix_scalar')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -80,8 +37,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('*'),
- [TypeMat(cols, rows, TypeF32), TypeF32],
- TypeMat(cols, rows, TypeF32),
+ [Type.mat(cols, rows, Type.f32), Type.f32],
+ Type.mat(cols, rows, Type.f32),
t.params,
cases
);
@@ -112,8 +69,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('*='),
- [TypeMat(cols, rows, TypeF32), TypeF32],
- TypeMat(cols, rows, TypeF32),
+ [Type.mat(cols, rows, Type.f32), Type.f32],
+ Type.mat(cols, rows, Type.f32),
t.params,
cases
);
@@ -144,8 +101,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('*'),
- [TypeF32, TypeMat(cols, rows, TypeF32)],
- TypeMat(cols, rows, TypeF32),
+ [Type.f32, Type.mat(cols, rows, Type.f32)],
+ Type.mat(cols, rows, Type.f32),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_subtraction.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_subtraction.cache.ts
new file mode 100644
index 0000000000..2cd2bc1c6d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_subtraction.cache.ts
@@ -0,0 +1,23 @@
+import { FP } from '../../../../util/floating_point.js';
+import { sparseMatrixF32Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+// Cases: matCxR_[non_]const
+const mat_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ ([true, false] as const).map(nonConst => ({
+ [`mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateMatrixPairToMatrixCases(
+ sparseMatrixF32Range(cols, rows),
+ sparseMatrixF32Range(cols, rows),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f32.subtractionMatrixMatrixInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f32_matrix_subtraction', mat_cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_subtraction.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_subtraction.spec.ts
index 5f101d9b27..31565ba598 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_subtraction.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_subtraction.spec.ts
@@ -4,36 +4,14 @@ Execution Tests for matrix f32 subtraction expression
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF32, TypeMat } from '../../../../util/conversion.js';
-import { FP } from '../../../../util/floating_point.js';
-import { sparseMatrixF32Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
+import { d } from './f32_matrix_subtraction.cache.js';
export const g = makeTestGroup(GPUTest);
-// Cases: matCxR_[non_]const
-const mat_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).flatMap(rows =>
- ([true, false] as const).map(nonConst => ({
- [`mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateMatrixPairToMatrixCases(
- sparseMatrixF32Range(cols, rows),
- sparseMatrixF32Range(cols, rows),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.subtractionMatrixMatrixInterval
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f32_matrix_subtraction', mat_cases);
-
g.test('matrix')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -57,8 +35,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('-'),
- [TypeMat(cols, rows, TypeF32), TypeMat(cols, rows, TypeF32)],
- TypeMat(cols, rows, TypeF32),
+ [Type.mat(cols, rows, Type.f32), Type.mat(cols, rows, Type.f32)],
+ Type.mat(cols, rows, Type.f32),
t.params,
cases
);
@@ -87,8 +65,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('-='),
- [TypeMat(cols, rows, TypeF32), TypeMat(cols, rows, TypeF32)],
- TypeMat(cols, rows, TypeF32),
+ [Type.mat(cols, rows, Type.f32), Type.mat(cols, rows, Type.f32)],
+ Type.mat(cols, rows, Type.f32),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_vector_multiplication.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_vector_multiplication.cache.ts
new file mode 100644
index 0000000000..f20b95029e
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_vector_multiplication.cache.ts
@@ -0,0 +1,44 @@
+import { FP } from '../../../../util/floating_point.js';
+import { sparseMatrixF32Range, sparseVectorF32Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+// Cases: matCxR_vecC_[non_]const
+const mat_vec_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ ([true, false] as const).map(nonConst => ({
+ [`mat${cols}x${rows}_vec${cols}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateMatrixVectorToVectorCases(
+ sparseMatrixF32Range(cols, rows),
+ sparseVectorF32Range(cols),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f32.multiplicationMatrixVectorInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: vecR_matCxR_[non_]const
+const vec_mat_cases = ([2, 3, 4] as const)
+ .flatMap(rows =>
+ ([2, 3, 4] as const).flatMap(cols =>
+ ([true, false] as const).map(nonConst => ({
+ [`vec${rows}_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateVectorMatrixToVectorCases(
+ sparseVectorF32Range(rows),
+ sparseMatrixF32Range(cols, rows),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f32.multiplicationVectorMatrixInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f32_matrix_vector_multiplication', {
+ ...mat_vec_cases,
+ ...vec_mat_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_vector_multiplication.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_vector_multiplication.spec.ts
index e6cdf16d92..8cd7ed49fe 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_vector_multiplication.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_vector_multiplication.spec.ts
@@ -4,57 +4,14 @@ Execution Tests for matrix-vector and vector-matrix f32 multiplication expressio
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF32, TypeMat, TypeVec } from '../../../../util/conversion.js';
-import { FP } from '../../../../util/floating_point.js';
-import { sparseMatrixF32Range, sparseVectorF32Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
+import { d } from './f32_matrix_vector_multiplication.cache.js';
export const g = makeTestGroup(GPUTest);
-// Cases: matCxR_vecC_[non_]const
-const mat_vec_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).flatMap(rows =>
- ([true, false] as const).map(nonConst => ({
- [`mat${cols}x${rows}_vec${cols}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateMatrixVectorToVectorCases(
- sparseMatrixF32Range(cols, rows),
- sparseVectorF32Range(cols),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.multiplicationMatrixVectorInterval
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-// Cases: vecR_matCxR_[non_]const
-const vec_mat_cases = ([2, 3, 4] as const)
- .flatMap(rows =>
- ([2, 3, 4] as const).flatMap(cols =>
- ([true, false] as const).map(nonConst => ({
- [`vec${rows}_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateVectorMatrixToVectorCases(
- sparseVectorF32Range(rows),
- sparseMatrixF32Range(cols, rows),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.multiplicationVectorMatrixInterval
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f32_matrix_vector_multiplication', {
- ...mat_vec_cases,
- ...vec_mat_cases,
-});
-
g.test('matrix_vector')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -80,8 +37,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('*'),
- [TypeMat(cols, rows, TypeF32), TypeVec(cols, TypeF32)],
- TypeVec(rows, TypeF32),
+ [Type.mat(cols, rows, Type.f32), Type.vec(cols, Type.f32)],
+ Type.vec(rows, Type.f32),
t.params,
cases
);
@@ -112,8 +69,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('*'),
- [TypeVec(rows, TypeF32), TypeMat(cols, rows, TypeF32)],
- TypeVec(cols, TypeF32),
+ [Type.vec(rows, Type.f32), Type.mat(cols, rows, Type.f32)],
+ Type.vec(cols, Type.f32),
t.params,
cases
);
@@ -139,8 +96,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('*='),
- [TypeVec(rows, TypeF32), TypeMat(cols, rows, TypeF32)],
- TypeVec(cols, TypeF32),
+ [Type.vec(rows, Type.f32), Type.mat(cols, rows, Type.f32)],
+ Type.vec(cols, Type.f32),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_multiplication.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_multiplication.cache.ts
new file mode 100644
index 0000000000..6a8c0bd81e
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_multiplication.cache.ts
@@ -0,0 +1,60 @@
+import { FP, FPVector } from '../../../../util/floating_point.js';
+import { sparseScalarF32Range, sparseVectorF32Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+const multiplicationVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
+ return FP.f32.toVector(v.map(e => FP.f32.multiplicationInterval(e, s)));
+};
+
+const multiplicationScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
+ return FP.f32.toVector(v.map(e => FP.f32.multiplicationInterval(s, e)));
+};
+
+const scalar_cases = ([true, false] as const)
+ .map(nonConst => ({
+ [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateScalarPairToIntervalCases(
+ sparseScalarF32Range(),
+ sparseScalarF32Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f32.multiplicationInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const vector_scalar_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateVectorScalarToVectorCases(
+ sparseVectorF32Range(dim),
+ sparseScalarF32Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ multiplicationVectorScalarInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const scalar_vector_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateScalarVectorToVectorCases(
+ sparseScalarF32Range(),
+ sparseVectorF32Range(dim),
+ nonConst ? 'unfiltered' : 'finite',
+ multiplicationScalarVectorInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f32_multiplication', {
+ ...scalar_cases,
+ ...vector_scalar_cases,
+ ...scalar_vector_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_multiplication.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_multiplication.spec.ts
index 38da08fd3e..478ca71ef0 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_multiplication.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_multiplication.spec.ts
@@ -4,73 +4,14 @@ Execution Tests for non-matrix f32 multiplication expression
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF32, TypeVec } from '../../../../util/conversion.js';
-import { FP, FPVector } from '../../../../util/floating_point.js';
-import { sparseF32Range, sparseVectorF32Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
-
-const multiplicationVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
- return FP.f32.toVector(v.map(e => FP.f32.multiplicationInterval(e, s)));
-};
-
-const multiplicationScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
- return FP.f32.toVector(v.map(e => FP.f32.multiplicationInterval(s, e)));
-};
+import { d } from './f32_multiplication.cache.js';
export const g = makeTestGroup(GPUTest);
-const scalar_cases = ([true, false] as const)
- .map(nonConst => ({
- [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateScalarPairToIntervalCases(
- sparseF32Range(),
- sparseF32Range(),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.multiplicationInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const vector_scalar_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateVectorScalarToVectorCases(
- sparseVectorF32Range(dim),
- sparseF32Range(),
- nonConst ? 'unfiltered' : 'finite',
- multiplicationVectorScalarInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const scalar_vector_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateScalarVectorToVectorCases(
- sparseF32Range(),
- sparseVectorF32Range(dim),
- nonConst ? 'unfiltered' : 'finite',
- multiplicationScalarVectorInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f32_multiplication', {
- ...scalar_cases,
- ...vector_scalar_cases,
- ...scalar_vector_cases,
-});
-
g.test('scalar')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -84,7 +25,7 @@ Accuracy: Correctly rounded
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, binary('*'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, binary('*'), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('vector')
@@ -100,7 +41,7 @@ Accuracy: Correctly rounded
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const' // Using vectorize to generate vector cases based on scalar cases
);
- await run(t, binary('*'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, binary('*'), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('scalar_compound')
@@ -118,7 +59,7 @@ Accuracy: Correctly rounded
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, compoundBinary('*='), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, compoundBinary('*='), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('vector_scalar')
@@ -138,8 +79,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('*'),
- [TypeVec(dim, TypeF32), TypeF32],
- TypeVec(dim, TypeF32),
+ [Type.vec(dim, Type.f32), Type.f32],
+ Type.vec(dim, Type.f32),
t.params,
cases
);
@@ -162,8 +103,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('*='),
- [TypeVec(dim, TypeF32), TypeF32],
- TypeVec(dim, TypeF32),
+ [Type.vec(dim, Type.f32), Type.f32],
+ Type.vec(dim, Type.f32),
t.params,
cases
);
@@ -186,8 +127,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('*'),
- [TypeF32, TypeVec(dim, TypeF32)],
- TypeVec(dim, TypeF32),
+ [Type.f32, Type.vec(dim, Type.f32)],
+ Type.vec(dim, Type.f32),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_remainder.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_remainder.cache.ts
new file mode 100644
index 0000000000..2cb1a16f9d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_remainder.cache.ts
@@ -0,0 +1,64 @@
+export const description = `
+Execution Tests for non-matrix f32 remainder expression
+`;
+
+import { FP, FPVector } from '../../../../util/floating_point.js';
+import { sparseScalarF32Range, sparseVectorF32Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+const remainderVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
+ return FP.f32.toVector(v.map(e => FP.f32.remainderInterval(e, s)));
+};
+
+const remainderScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
+ return FP.f32.toVector(v.map(e => FP.f32.remainderInterval(s, e)));
+};
+
+const scalar_cases = ([true, false] as const)
+ .map(nonConst => ({
+ [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateScalarPairToIntervalCases(
+ sparseScalarF32Range(),
+ sparseScalarF32Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f32.remainderInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const vector_scalar_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateVectorScalarToVectorCases(
+ sparseVectorF32Range(dim),
+ sparseScalarF32Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ remainderVectorScalarInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const scalar_vector_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateScalarVectorToVectorCases(
+ sparseScalarF32Range(),
+ sparseVectorF32Range(dim),
+ nonConst ? 'unfiltered' : 'finite',
+ remainderScalarVectorInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f32_remainder', {
+ ...scalar_cases,
+ ...vector_scalar_cases,
+ ...scalar_vector_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_remainder.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_remainder.spec.ts
index 390a7f3426..3a9acb02e0 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_remainder.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_remainder.spec.ts
@@ -4,73 +4,14 @@ Execution Tests for non-matrix f32 remainder expression
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF32, TypeVec } from '../../../../util/conversion.js';
-import { FP, FPVector } from '../../../../util/floating_point.js';
-import { sparseF32Range, sparseVectorF32Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
-
-const remainderVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
- return FP.f32.toVector(v.map(e => FP.f32.remainderInterval(e, s)));
-};
-
-const remainderScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
- return FP.f32.toVector(v.map(e => FP.f32.remainderInterval(s, e)));
-};
+import { d } from './f32_remainder.cache.js';
export const g = makeTestGroup(GPUTest);
-const scalar_cases = ([true, false] as const)
- .map(nonConst => ({
- [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateScalarPairToIntervalCases(
- sparseF32Range(),
- sparseF32Range(),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.remainderInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const vector_scalar_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateVectorScalarToVectorCases(
- sparseVectorF32Range(dim),
- sparseF32Range(),
- nonConst ? 'unfiltered' : 'finite',
- remainderVectorScalarInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const scalar_vector_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateScalarVectorToVectorCases(
- sparseF32Range(),
- sparseVectorF32Range(dim),
- nonConst ? 'unfiltered' : 'finite',
- remainderScalarVectorInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f32_remainder', {
- ...scalar_cases,
- ...vector_scalar_cases,
- ...scalar_vector_cases,
-});
-
g.test('scalar')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -84,7 +25,7 @@ Accuracy: Derived from x - y * trunc(x/y)
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, binary('%'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, binary('%'), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('vector')
@@ -100,7 +41,7 @@ Accuracy: Derived from x - y * trunc(x/y)
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const' // Using vectorize to generate vector cases based on scalar cases
);
- await run(t, binary('%'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, binary('%'), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('scalar_compound')
@@ -118,7 +59,7 @@ Accuracy: Derived from x - y * trunc(x/y)
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, compoundBinary('%='), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, compoundBinary('%='), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('vector_scalar')
@@ -138,8 +79,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('%'),
- [TypeVec(dim, TypeF32), TypeF32],
- TypeVec(dim, TypeF32),
+ [Type.vec(dim, Type.f32), Type.f32],
+ Type.vec(dim, Type.f32),
t.params,
cases
);
@@ -162,8 +103,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('%='),
- [TypeVec(dim, TypeF32), TypeF32],
- TypeVec(dim, TypeF32),
+ [Type.vec(dim, Type.f32), Type.f32],
+ Type.vec(dim, Type.f32),
t.params,
cases
);
@@ -186,8 +127,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('%'),
- [TypeF32, TypeVec(dim, TypeF32)],
- TypeVec(dim, TypeF32),
+ [Type.f32, Type.vec(dim, Type.f32)],
+ Type.vec(dim, Type.f32),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_subtraction.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_subtraction.cache.ts
new file mode 100644
index 0000000000..519c0b3783
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_subtraction.cache.ts
@@ -0,0 +1,60 @@
+import { FP, FPVector } from '../../../../util/floating_point.js';
+import { sparseScalarF32Range, sparseVectorF32Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+const subtractionVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
+ return FP.f32.toVector(v.map(e => FP.f32.subtractionInterval(e, s)));
+};
+
+const subtractionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
+ return FP.f32.toVector(v.map(e => FP.f32.subtractionInterval(s, e)));
+};
+
+const scalar_cases = ([true, false] as const)
+ .map(nonConst => ({
+ [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateScalarPairToIntervalCases(
+ sparseScalarF32Range(),
+ sparseScalarF32Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f32.subtractionInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const vector_scalar_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateVectorScalarToVectorCases(
+ sparseVectorF32Range(dim),
+ sparseScalarF32Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ subtractionVectorScalarInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const scalar_vector_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateScalarVectorToVectorCases(
+ sparseScalarF32Range(),
+ sparseVectorF32Range(dim),
+ nonConst ? 'unfiltered' : 'finite',
+ subtractionScalarVectorInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f32_subtraction', {
+ ...scalar_cases,
+ ...vector_scalar_cases,
+ ...scalar_vector_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_subtraction.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_subtraction.spec.ts
index 91e06b7de8..55097390e9 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_subtraction.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_subtraction.spec.ts
@@ -4,73 +4,14 @@ Execution Tests for non-matrix f32 subtraction expression
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF32, TypeVec } from '../../../../util/conversion.js';
-import { FP, FPVector } from '../../../../util/floating_point.js';
-import { sparseF32Range, sparseVectorF32Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
-
-const subtractionVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
- return FP.f32.toVector(v.map(e => FP.f32.subtractionInterval(e, s)));
-};
-
-const subtractionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
- return FP.f32.toVector(v.map(e => FP.f32.subtractionInterval(s, e)));
-};
+import { d } from './f32_subtraction.cache.js';
export const g = makeTestGroup(GPUTest);
-const scalar_cases = ([true, false] as const)
- .map(nonConst => ({
- [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateScalarPairToIntervalCases(
- sparseF32Range(),
- sparseF32Range(),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.subtractionInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const vector_scalar_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateVectorScalarToVectorCases(
- sparseVectorF32Range(dim),
- sparseF32Range(),
- nonConst ? 'unfiltered' : 'finite',
- subtractionVectorScalarInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const scalar_vector_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateScalarVectorToVectorCases(
- sparseF32Range(),
- sparseVectorF32Range(dim),
- nonConst ? 'unfiltered' : 'finite',
- subtractionScalarVectorInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f32_subtraction', {
- ...scalar_cases,
- ...vector_scalar_cases,
- ...scalar_vector_cases,
-});
-
g.test('scalar')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -84,7 +25,7 @@ Accuracy: Correctly rounded
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, binary('-'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, binary('-'), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('vector')
@@ -100,7 +41,7 @@ Accuracy: Correctly rounded
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const' // Using vectorize to generate vector cases based on scalar cases
);
- await run(t, binary('-'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, binary('-'), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('scalar_compound')
@@ -118,7 +59,7 @@ Accuracy: Correctly rounded
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, compoundBinary('-='), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, compoundBinary('-='), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('vector_scalar')
@@ -138,8 +79,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('-'),
- [TypeVec(dim, TypeF32), TypeF32],
- TypeVec(dim, TypeF32),
+ [Type.vec(dim, Type.f32), Type.f32],
+ Type.vec(dim, Type.f32),
t.params,
cases
);
@@ -162,8 +103,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('-='),
- [TypeVec(dim, TypeF32), TypeF32],
- TypeVec(dim, TypeF32),
+ [Type.vec(dim, Type.f32), Type.f32],
+ Type.vec(dim, Type.f32),
t.params,
cases
);
@@ -186,8 +127,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('-'),
- [TypeF32, TypeVec(dim, TypeF32)],
- TypeVec(dim, TypeF32),
+ [Type.f32, Type.vec(dim, Type.f32)],
+ Type.vec(dim, Type.f32),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/i32_arithmetic.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/i32_arithmetic.cache.ts
new file mode 100644
index 0000000000..ca7ca879f5
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/i32_arithmetic.cache.ts
@@ -0,0 +1,306 @@
+import { kValue } from '../../../../util/constants.js';
+import { sparseI32Range, vectorI32Range } from '../../../../util/math.js';
+import {
+ generateBinaryToI32Cases,
+ generateI32VectorBinaryToVectorCases,
+ generateVectorI32BinaryToVectorCases,
+} from '../case.js';
+import { makeCaseCache } from '../case_cache.js';
+
+function i32_add(x: number, y: number): number | undefined {
+ return x + y;
+}
+
+function i32_subtract(x: number, y: number): number | undefined {
+ return x - y;
+}
+
+function i32_multiply(x: number, y: number): number | undefined {
+ return Math.imul(x, y);
+}
+
+function i32_divide_non_const(x: number, y: number): number | undefined {
+ if (y === 0) {
+ return x;
+ }
+ if (x === kValue.i32.negative.min && y === -1) {
+ return x;
+ }
+ return x / y;
+}
+
+function i32_divide_const(x: number, y: number): number | undefined {
+ if (y === 0) {
+ return undefined;
+ }
+ if (x === kValue.i32.negative.min && y === -1) {
+ return undefined;
+ }
+ return x / y;
+}
+
+function i32_remainder_non_const(x: number, y: number): number | undefined {
+ if (y === 0) {
+ return 0;
+ }
+ if (x === kValue.i32.negative.min && y === -1) {
+ return 0;
+ }
+ return x % y;
+}
+
+function i32_remainder_const(x: number, y: number): number | undefined {
+ if (y === 0) {
+ return undefined;
+ }
+ if (x === kValue.i32.negative.min && y === -1) {
+ return undefined;
+ }
+ return x % y;
+}
+
+export const d = makeCaseCache('binary/i32_arithmetic', {
+ addition: () => {
+ return generateBinaryToI32Cases(sparseI32Range(), sparseI32Range(), i32_add);
+ },
+ subtraction: () => {
+ return generateBinaryToI32Cases(sparseI32Range(), sparseI32Range(), i32_subtract);
+ },
+ multiplication: () => {
+ return generateBinaryToI32Cases(sparseI32Range(), sparseI32Range(), i32_multiply);
+ },
+ division_non_const: () => {
+ return generateBinaryToI32Cases(sparseI32Range(), sparseI32Range(), i32_divide_non_const);
+ },
+ division_const: () => {
+ return generateBinaryToI32Cases(sparseI32Range(), sparseI32Range(), i32_divide_const);
+ },
+ remainder_non_const: () => {
+ return generateBinaryToI32Cases(sparseI32Range(), sparseI32Range(), i32_remainder_non_const);
+ },
+ remainder_const: () => {
+ return generateBinaryToI32Cases(sparseI32Range(), sparseI32Range(), i32_remainder_const);
+ },
+ addition_scalar_vector2: () => {
+ return generateI32VectorBinaryToVectorCases(sparseI32Range(), vectorI32Range(2), i32_add);
+ },
+ addition_scalar_vector3: () => {
+ return generateI32VectorBinaryToVectorCases(sparseI32Range(), vectorI32Range(3), i32_add);
+ },
+ addition_scalar_vector4: () => {
+ return generateI32VectorBinaryToVectorCases(sparseI32Range(), vectorI32Range(4), i32_add);
+ },
+ addition_vector2_scalar: () => {
+ return generateVectorI32BinaryToVectorCases(vectorI32Range(2), sparseI32Range(), i32_add);
+ },
+ addition_vector3_scalar: () => {
+ return generateVectorI32BinaryToVectorCases(vectorI32Range(3), sparseI32Range(), i32_add);
+ },
+ addition_vector4_scalar: () => {
+ return generateVectorI32BinaryToVectorCases(vectorI32Range(4), sparseI32Range(), i32_add);
+ },
+ subtraction_scalar_vector2: () => {
+ return generateI32VectorBinaryToVectorCases(sparseI32Range(), vectorI32Range(2), i32_subtract);
+ },
+ subtraction_scalar_vector3: () => {
+ return generateI32VectorBinaryToVectorCases(sparseI32Range(), vectorI32Range(3), i32_subtract);
+ },
+ subtraction_scalar_vector4: () => {
+ return generateI32VectorBinaryToVectorCases(sparseI32Range(), vectorI32Range(4), i32_subtract);
+ },
+ subtraction_vector2_scalar: () => {
+ return generateVectorI32BinaryToVectorCases(vectorI32Range(2), sparseI32Range(), i32_subtract);
+ },
+ subtraction_vector3_scalar: () => {
+ return generateVectorI32BinaryToVectorCases(vectorI32Range(3), sparseI32Range(), i32_subtract);
+ },
+ subtraction_vector4_scalar: () => {
+ return generateVectorI32BinaryToVectorCases(vectorI32Range(4), sparseI32Range(), i32_subtract);
+ },
+ multiplication_scalar_vector2: () => {
+ return generateI32VectorBinaryToVectorCases(sparseI32Range(), vectorI32Range(2), i32_multiply);
+ },
+ multiplication_scalar_vector3: () => {
+ return generateI32VectorBinaryToVectorCases(sparseI32Range(), vectorI32Range(3), i32_multiply);
+ },
+ multiplication_scalar_vector4: () => {
+ return generateI32VectorBinaryToVectorCases(sparseI32Range(), vectorI32Range(4), i32_multiply);
+ },
+ multiplication_vector2_scalar: () => {
+ return generateVectorI32BinaryToVectorCases(vectorI32Range(2), sparseI32Range(), i32_multiply);
+ },
+ multiplication_vector3_scalar: () => {
+ return generateVectorI32BinaryToVectorCases(vectorI32Range(3), sparseI32Range(), i32_multiply);
+ },
+ multiplication_vector4_scalar: () => {
+ return generateVectorI32BinaryToVectorCases(vectorI32Range(4), sparseI32Range(), i32_multiply);
+ },
+ division_scalar_vector2_non_const: () => {
+ return generateI32VectorBinaryToVectorCases(
+ sparseI32Range(),
+ vectorI32Range(2),
+ i32_divide_non_const
+ );
+ },
+ division_scalar_vector3_non_const: () => {
+ return generateI32VectorBinaryToVectorCases(
+ sparseI32Range(),
+ vectorI32Range(3),
+ i32_divide_non_const
+ );
+ },
+ division_scalar_vector4_non_const: () => {
+ return generateI32VectorBinaryToVectorCases(
+ sparseI32Range(),
+ vectorI32Range(4),
+ i32_divide_non_const
+ );
+ },
+ division_vector2_scalar_non_const: () => {
+ return generateVectorI32BinaryToVectorCases(
+ vectorI32Range(2),
+ sparseI32Range(),
+ i32_divide_non_const
+ );
+ },
+ division_vector3_scalar_non_const: () => {
+ return generateVectorI32BinaryToVectorCases(
+ vectorI32Range(3),
+ sparseI32Range(),
+ i32_divide_non_const
+ );
+ },
+ division_vector4_scalar_non_const: () => {
+ return generateVectorI32BinaryToVectorCases(
+ vectorI32Range(4),
+ sparseI32Range(),
+ i32_divide_non_const
+ );
+ },
+ division_scalar_vector2_const: () => {
+ return generateI32VectorBinaryToVectorCases(
+ sparseI32Range(),
+ vectorI32Range(2),
+ i32_divide_const
+ );
+ },
+ division_scalar_vector3_const: () => {
+ return generateI32VectorBinaryToVectorCases(
+ sparseI32Range(),
+ vectorI32Range(3),
+ i32_divide_const
+ );
+ },
+ division_scalar_vector4_const: () => {
+ return generateI32VectorBinaryToVectorCases(
+ sparseI32Range(),
+ vectorI32Range(4),
+ i32_divide_const
+ );
+ },
+ division_vector2_scalar_const: () => {
+ return generateVectorI32BinaryToVectorCases(
+ vectorI32Range(2),
+ sparseI32Range(),
+ i32_divide_const
+ );
+ },
+ division_vector3_scalar_const: () => {
+ return generateVectorI32BinaryToVectorCases(
+ vectorI32Range(3),
+ sparseI32Range(),
+ i32_divide_const
+ );
+ },
+ division_vector4_scalar_const: () => {
+ return generateVectorI32BinaryToVectorCases(
+ vectorI32Range(4),
+ sparseI32Range(),
+ i32_divide_const
+ );
+ },
+ remainder_scalar_vector2_non_const: () => {
+ return generateI32VectorBinaryToVectorCases(
+ sparseI32Range(),
+ vectorI32Range(2),
+ i32_remainder_non_const
+ );
+ },
+ remainder_scalar_vector3_non_const: () => {
+ return generateI32VectorBinaryToVectorCases(
+ sparseI32Range(),
+ vectorI32Range(3),
+ i32_remainder_non_const
+ );
+ },
+ remainder_scalar_vector4_non_const: () => {
+ return generateI32VectorBinaryToVectorCases(
+ sparseI32Range(),
+ vectorI32Range(4),
+ i32_remainder_non_const
+ );
+ },
+ remainder_vector2_scalar_non_const: () => {
+ return generateVectorI32BinaryToVectorCases(
+ vectorI32Range(2),
+ sparseI32Range(),
+ i32_remainder_non_const
+ );
+ },
+ remainder_vector3_scalar_non_const: () => {
+ return generateVectorI32BinaryToVectorCases(
+ vectorI32Range(3),
+ sparseI32Range(),
+ i32_remainder_non_const
+ );
+ },
+ remainder_vector4_scalar_non_const: () => {
+ return generateVectorI32BinaryToVectorCases(
+ vectorI32Range(4),
+ sparseI32Range(),
+ i32_remainder_non_const
+ );
+ },
+ remainder_scalar_vector2_const: () => {
+ return generateI32VectorBinaryToVectorCases(
+ sparseI32Range(),
+ vectorI32Range(2),
+ i32_remainder_const
+ );
+ },
+ remainder_scalar_vector3_const: () => {
+ return generateI32VectorBinaryToVectorCases(
+ sparseI32Range(),
+ vectorI32Range(3),
+ i32_remainder_const
+ );
+ },
+ remainder_scalar_vector4_const: () => {
+ return generateI32VectorBinaryToVectorCases(
+ sparseI32Range(),
+ vectorI32Range(4),
+ i32_remainder_const
+ );
+ },
+ remainder_vector2_scalar_const: () => {
+ return generateVectorI32BinaryToVectorCases(
+ vectorI32Range(2),
+ sparseI32Range(),
+ i32_remainder_const
+ );
+ },
+ remainder_vector3_scalar_const: () => {
+ return generateVectorI32BinaryToVectorCases(
+ vectorI32Range(3),
+ sparseI32Range(),
+ i32_remainder_const
+ );
+ },
+ remainder_vector4_scalar_const: () => {
+ return generateVectorI32BinaryToVectorCases(
+ vectorI32Range(4),
+ sparseI32Range(),
+ i32_remainder_const
+ );
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/i32_arithmetic.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/i32_arithmetic.spec.ts
index e9b7a2407f..ef8ea00447 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/i32_arithmetic.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/i32_arithmetic.spec.ts
@@ -4,320 +4,14 @@ Execution Tests for the i32 arithmetic binary expression operations
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { kValue } from '../../../../util/constants.js';
-import { TypeI32, TypeVec } from '../../../../util/conversion.js';
-import { sparseI32Range, vectorI32Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
-import {
- allInputSources,
- generateBinaryToI32Cases,
- generateI32VectorBinaryToVectorCases,
- generateVectorI32BinaryToVectorCases,
- run,
-} from '../expression.js';
+import { Type } from '../../../../util/conversion.js';
+import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
-
-function i32_add(x: number, y: number): number | undefined {
- return x + y;
-}
-
-function i32_subtract(x: number, y: number): number | undefined {
- return x - y;
-}
-
-function i32_multiply(x: number, y: number): number | undefined {
- return Math.imul(x, y);
-}
-
-function i32_divide_non_const(x: number, y: number): number | undefined {
- if (y === 0) {
- return x;
- }
- if (x === kValue.i32.negative.min && y === -1) {
- return x;
- }
- return x / y;
-}
-
-function i32_divide_const(x: number, y: number): number | undefined {
- if (y === 0) {
- return undefined;
- }
- if (x === kValue.i32.negative.min && y === -1) {
- return undefined;
- }
- return x / y;
-}
-
-function i32_remainder_non_const(x: number, y: number): number | undefined {
- if (y === 0) {
- return 0;
- }
- if (x === kValue.i32.negative.min && y === -1) {
- return 0;
- }
- return x % y;
-}
-
-function i32_remainder_const(x: number, y: number): number | undefined {
- if (y === 0) {
- return undefined;
- }
- if (x === kValue.i32.negative.min && y === -1) {
- return undefined;
- }
- return x % y;
-}
+import { d } from './i32_arithmetic.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('binary/i32_arithmetic', {
- addition: () => {
- return generateBinaryToI32Cases(sparseI32Range(), sparseI32Range(), i32_add);
- },
- subtraction: () => {
- return generateBinaryToI32Cases(sparseI32Range(), sparseI32Range(), i32_subtract);
- },
- multiplication: () => {
- return generateBinaryToI32Cases(sparseI32Range(), sparseI32Range(), i32_multiply);
- },
- division_non_const: () => {
- return generateBinaryToI32Cases(sparseI32Range(), sparseI32Range(), i32_divide_non_const);
- },
- division_const: () => {
- return generateBinaryToI32Cases(sparseI32Range(), sparseI32Range(), i32_divide_const);
- },
- remainder_non_const: () => {
- return generateBinaryToI32Cases(sparseI32Range(), sparseI32Range(), i32_remainder_non_const);
- },
- remainder_const: () => {
- return generateBinaryToI32Cases(sparseI32Range(), sparseI32Range(), i32_remainder_const);
- },
- addition_scalar_vector2: () => {
- return generateI32VectorBinaryToVectorCases(sparseI32Range(), vectorI32Range(2), i32_add);
- },
- addition_scalar_vector3: () => {
- return generateI32VectorBinaryToVectorCases(sparseI32Range(), vectorI32Range(3), i32_add);
- },
- addition_scalar_vector4: () => {
- return generateI32VectorBinaryToVectorCases(sparseI32Range(), vectorI32Range(4), i32_add);
- },
- addition_vector2_scalar: () => {
- return generateVectorI32BinaryToVectorCases(vectorI32Range(2), sparseI32Range(), i32_add);
- },
- addition_vector3_scalar: () => {
- return generateVectorI32BinaryToVectorCases(vectorI32Range(3), sparseI32Range(), i32_add);
- },
- addition_vector4_scalar: () => {
- return generateVectorI32BinaryToVectorCases(vectorI32Range(4), sparseI32Range(), i32_add);
- },
- subtraction_scalar_vector2: () => {
- return generateI32VectorBinaryToVectorCases(sparseI32Range(), vectorI32Range(2), i32_subtract);
- },
- subtraction_scalar_vector3: () => {
- return generateI32VectorBinaryToVectorCases(sparseI32Range(), vectorI32Range(3), i32_subtract);
- },
- subtraction_scalar_vector4: () => {
- return generateI32VectorBinaryToVectorCases(sparseI32Range(), vectorI32Range(4), i32_subtract);
- },
- subtraction_vector2_scalar: () => {
- return generateVectorI32BinaryToVectorCases(vectorI32Range(2), sparseI32Range(), i32_subtract);
- },
- subtraction_vector3_scalar: () => {
- return generateVectorI32BinaryToVectorCases(vectorI32Range(3), sparseI32Range(), i32_subtract);
- },
- subtraction_vector4_scalar: () => {
- return generateVectorI32BinaryToVectorCases(vectorI32Range(4), sparseI32Range(), i32_subtract);
- },
- multiplication_scalar_vector2: () => {
- return generateI32VectorBinaryToVectorCases(sparseI32Range(), vectorI32Range(2), i32_multiply);
- },
- multiplication_scalar_vector3: () => {
- return generateI32VectorBinaryToVectorCases(sparseI32Range(), vectorI32Range(3), i32_multiply);
- },
- multiplication_scalar_vector4: () => {
- return generateI32VectorBinaryToVectorCases(sparseI32Range(), vectorI32Range(4), i32_multiply);
- },
- multiplication_vector2_scalar: () => {
- return generateVectorI32BinaryToVectorCases(vectorI32Range(2), sparseI32Range(), i32_multiply);
- },
- multiplication_vector3_scalar: () => {
- return generateVectorI32BinaryToVectorCases(vectorI32Range(3), sparseI32Range(), i32_multiply);
- },
- multiplication_vector4_scalar: () => {
- return generateVectorI32BinaryToVectorCases(vectorI32Range(4), sparseI32Range(), i32_multiply);
- },
- division_scalar_vector2_non_const: () => {
- return generateI32VectorBinaryToVectorCases(
- sparseI32Range(),
- vectorI32Range(2),
- i32_divide_non_const
- );
- },
- division_scalar_vector3_non_const: () => {
- return generateI32VectorBinaryToVectorCases(
- sparseI32Range(),
- vectorI32Range(3),
- i32_divide_non_const
- );
- },
- division_scalar_vector4_non_const: () => {
- return generateI32VectorBinaryToVectorCases(
- sparseI32Range(),
- vectorI32Range(4),
- i32_divide_non_const
- );
- },
- division_vector2_scalar_non_const: () => {
- return generateVectorI32BinaryToVectorCases(
- vectorI32Range(2),
- sparseI32Range(),
- i32_divide_non_const
- );
- },
- division_vector3_scalar_non_const: () => {
- return generateVectorI32BinaryToVectorCases(
- vectorI32Range(3),
- sparseI32Range(),
- i32_divide_non_const
- );
- },
- division_vector4_scalar_non_const: () => {
- return generateVectorI32BinaryToVectorCases(
- vectorI32Range(4),
- sparseI32Range(),
- i32_divide_non_const
- );
- },
- division_scalar_vector2_const: () => {
- return generateI32VectorBinaryToVectorCases(
- sparseI32Range(),
- vectorI32Range(2),
- i32_divide_const
- );
- },
- division_scalar_vector3_const: () => {
- return generateI32VectorBinaryToVectorCases(
- sparseI32Range(),
- vectorI32Range(3),
- i32_divide_const
- );
- },
- division_scalar_vector4_const: () => {
- return generateI32VectorBinaryToVectorCases(
- sparseI32Range(),
- vectorI32Range(4),
- i32_divide_const
- );
- },
- division_vector2_scalar_const: () => {
- return generateVectorI32BinaryToVectorCases(
- vectorI32Range(2),
- sparseI32Range(),
- i32_divide_const
- );
- },
- division_vector3_scalar_const: () => {
- return generateVectorI32BinaryToVectorCases(
- vectorI32Range(3),
- sparseI32Range(),
- i32_divide_const
- );
- },
- division_vector4_scalar_const: () => {
- return generateVectorI32BinaryToVectorCases(
- vectorI32Range(4),
- sparseI32Range(),
- i32_divide_const
- );
- },
- remainder_scalar_vector2_non_const: () => {
- return generateI32VectorBinaryToVectorCases(
- sparseI32Range(),
- vectorI32Range(2),
- i32_remainder_non_const
- );
- },
- remainder_scalar_vector3_non_const: () => {
- return generateI32VectorBinaryToVectorCases(
- sparseI32Range(),
- vectorI32Range(3),
- i32_remainder_non_const
- );
- },
- remainder_scalar_vector4_non_const: () => {
- return generateI32VectorBinaryToVectorCases(
- sparseI32Range(),
- vectorI32Range(4),
- i32_remainder_non_const
- );
- },
- remainder_vector2_scalar_non_const: () => {
- return generateVectorI32BinaryToVectorCases(
- vectorI32Range(2),
- sparseI32Range(),
- i32_remainder_non_const
- );
- },
- remainder_vector3_scalar_non_const: () => {
- return generateVectorI32BinaryToVectorCases(
- vectorI32Range(3),
- sparseI32Range(),
- i32_remainder_non_const
- );
- },
- remainder_vector4_scalar_non_const: () => {
- return generateVectorI32BinaryToVectorCases(
- vectorI32Range(4),
- sparseI32Range(),
- i32_remainder_non_const
- );
- },
- remainder_scalar_vector2_const: () => {
- return generateI32VectorBinaryToVectorCases(
- sparseI32Range(),
- vectorI32Range(2),
- i32_remainder_const
- );
- },
- remainder_scalar_vector3_const: () => {
- return generateI32VectorBinaryToVectorCases(
- sparseI32Range(),
- vectorI32Range(3),
- i32_remainder_const
- );
- },
- remainder_scalar_vector4_const: () => {
- return generateI32VectorBinaryToVectorCases(
- sparseI32Range(),
- vectorI32Range(4),
- i32_remainder_const
- );
- },
- remainder_vector2_scalar_const: () => {
- return generateVectorI32BinaryToVectorCases(
- vectorI32Range(2),
- sparseI32Range(),
- i32_remainder_const
- );
- },
- remainder_vector3_scalar_const: () => {
- return generateVectorI32BinaryToVectorCases(
- vectorI32Range(3),
- sparseI32Range(),
- i32_remainder_const
- );
- },
- remainder_vector4_scalar_const: () => {
- return generateVectorI32BinaryToVectorCases(
- vectorI32Range(4),
- sparseI32Range(),
- i32_remainder_const
- );
- },
-});
-
g.test('addition')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -330,7 +24,7 @@ Expression: x + y
)
.fn(async t => {
const cases = await d.get('addition');
- await run(t, binary('+'), [TypeI32, TypeI32], TypeI32, t.params, cases);
+ await run(t, binary('+'), [Type.i32, Type.i32], Type.i32, t.params, cases);
});
g.test('addition_compound')
@@ -345,7 +39,7 @@ Expression: x += y
)
.fn(async t => {
const cases = await d.get('addition');
- await run(t, compoundBinary('+='), [TypeI32, TypeI32], TypeI32, t.params, cases);
+ await run(t, compoundBinary('+='), [Type.i32, Type.i32], Type.i32, t.params, cases);
});
g.test('subtraction')
@@ -360,7 +54,7 @@ Expression: x - y
)
.fn(async t => {
const cases = await d.get('subtraction');
- await run(t, binary('-'), [TypeI32, TypeI32], TypeI32, t.params, cases);
+ await run(t, binary('-'), [Type.i32, Type.i32], Type.i32, t.params, cases);
});
g.test('subtraction_compound')
@@ -375,7 +69,7 @@ Expression: x -= y
)
.fn(async t => {
const cases = await d.get('subtraction');
- await run(t, compoundBinary('-='), [TypeI32, TypeI32], TypeI32, t.params, cases);
+ await run(t, compoundBinary('-='), [Type.i32, Type.i32], Type.i32, t.params, cases);
});
g.test('multiplication')
@@ -390,7 +84,7 @@ Expression: x * y
)
.fn(async t => {
const cases = await d.get('multiplication');
- await run(t, binary('*'), [TypeI32, TypeI32], TypeI32, t.params, cases);
+ await run(t, binary('*'), [Type.i32, Type.i32], Type.i32, t.params, cases);
});
g.test('multiplication_compound')
@@ -405,7 +99,7 @@ Expression: x *= y
)
.fn(async t => {
const cases = await d.get('multiplication');
- await run(t, compoundBinary('*='), [TypeI32, TypeI32], TypeI32, t.params, cases);
+ await run(t, compoundBinary('*='), [Type.i32, Type.i32], Type.i32, t.params, cases);
});
g.test('division')
@@ -422,7 +116,7 @@ Expression: x / y
const cases = await d.get(
t.params.inputSource === 'const' ? 'division_const' : 'division_non_const'
);
- await run(t, binary('/'), [TypeI32, TypeI32], TypeI32, t.params, cases);
+ await run(t, binary('/'), [Type.i32, Type.i32], Type.i32, t.params, cases);
});
g.test('division_compound')
@@ -439,7 +133,7 @@ Expression: x /= y
const cases = await d.get(
t.params.inputSource === 'const' ? 'division_const' : 'division_non_const'
);
- await run(t, compoundBinary('/='), [TypeI32, TypeI32], TypeI32, t.params, cases);
+ await run(t, compoundBinary('/='), [Type.i32, Type.i32], Type.i32, t.params, cases);
});
g.test('remainder')
@@ -456,7 +150,7 @@ Expression: x % y
const cases = await d.get(
t.params.inputSource === 'const' ? 'remainder_const' : 'remainder_non_const'
);
- await run(t, binary('%'), [TypeI32, TypeI32], TypeI32, t.params, cases);
+ await run(t, binary('%'), [Type.i32, Type.i32], Type.i32, t.params, cases);
});
g.test('remainder_compound')
@@ -473,7 +167,7 @@ Expression: x %= y
const cases = await d.get(
t.params.inputSource === 'const' ? 'remainder_const' : 'remainder_non_const'
);
- await run(t, compoundBinary('%='), [TypeI32, TypeI32], TypeI32, t.params, cases);
+ await run(t, compoundBinary('%='), [Type.i32, Type.i32], Type.i32, t.params, cases);
});
g.test('addition_scalar_vector')
@@ -488,9 +182,9 @@ Expression: x + y
)
.fn(async t => {
const vec_size = t.params.vectorize_rhs;
- const vec_type = TypeVec(vec_size, TypeI32);
+ const vec_type = Type.vec(vec_size, Type.i32);
const cases = await d.get(`addition_scalar_vector${vec_size}`);
- await run(t, binary('+'), [TypeI32, vec_type], vec_type, t.params, cases);
+ await run(t, binary('+'), [Type.i32, vec_type], vec_type, t.params, cases);
});
g.test('addition_vector_scalar')
@@ -505,9 +199,9 @@ Expression: x + y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeI32);
+ const vec_type = Type.vec(vec_size, Type.i32);
const cases = await d.get(`addition_vector${vec_size}_scalar`);
- await run(t, binary('+'), [vec_type, TypeI32], vec_type, t.params, cases);
+ await run(t, binary('+'), [vec_type, Type.i32], vec_type, t.params, cases);
});
g.test('addition_vector_scalar_compound')
@@ -522,9 +216,9 @@ Expression: x += y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeI32);
+ const vec_type = Type.vec(vec_size, Type.i32);
const cases = await d.get(`addition_vector${vec_size}_scalar`);
- await run(t, compoundBinary('+='), [vec_type, TypeI32], vec_type, t.params, cases);
+ await run(t, compoundBinary('+='), [vec_type, Type.i32], vec_type, t.params, cases);
});
g.test('subtraction_scalar_vector')
@@ -539,9 +233,9 @@ Expression: x - y
)
.fn(async t => {
const vec_size = t.params.vectorize_rhs;
- const vec_type = TypeVec(vec_size, TypeI32);
+ const vec_type = Type.vec(vec_size, Type.i32);
const cases = await d.get(`subtraction_scalar_vector${vec_size}`);
- await run(t, binary('-'), [TypeI32, vec_type], vec_type, t.params, cases);
+ await run(t, binary('-'), [Type.i32, vec_type], vec_type, t.params, cases);
});
g.test('subtraction_vector_scalar')
@@ -556,9 +250,9 @@ Expression: x - y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeI32);
+ const vec_type = Type.vec(vec_size, Type.i32);
const cases = await d.get(`subtraction_vector${vec_size}_scalar`);
- await run(t, binary('-'), [vec_type, TypeI32], vec_type, t.params, cases);
+ await run(t, binary('-'), [vec_type, Type.i32], vec_type, t.params, cases);
});
g.test('subtraction_vector_scalar_compound')
@@ -573,9 +267,9 @@ Expression: x -= y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeI32);
+ const vec_type = Type.vec(vec_size, Type.i32);
const cases = await d.get(`subtraction_vector${vec_size}_scalar`);
- await run(t, compoundBinary('-='), [vec_type, TypeI32], vec_type, t.params, cases);
+ await run(t, compoundBinary('-='), [vec_type, Type.i32], vec_type, t.params, cases);
});
g.test('multiplication_scalar_vector')
@@ -590,9 +284,9 @@ Expression: x * y
)
.fn(async t => {
const vec_size = t.params.vectorize_rhs;
- const vec_type = TypeVec(vec_size, TypeI32);
+ const vec_type = Type.vec(vec_size, Type.i32);
const cases = await d.get(`multiplication_scalar_vector${vec_size}`);
- await run(t, binary('*'), [TypeI32, vec_type], vec_type, t.params, cases);
+ await run(t, binary('*'), [Type.i32, vec_type], vec_type, t.params, cases);
});
g.test('multiplication_vector_scalar')
@@ -607,9 +301,9 @@ Expression: x * y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeI32);
+ const vec_type = Type.vec(vec_size, Type.i32);
const cases = await d.get(`multiplication_vector${vec_size}_scalar`);
- await run(t, binary('*'), [vec_type, TypeI32], vec_type, t.params, cases);
+ await run(t, binary('*'), [vec_type, Type.i32], vec_type, t.params, cases);
});
g.test('multiplication_vector_scalar_compound')
@@ -624,9 +318,9 @@ Expression: x *= y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeI32);
+ const vec_type = Type.vec(vec_size, Type.i32);
const cases = await d.get(`multiplication_vector${vec_size}_scalar`);
- await run(t, compoundBinary('*='), [vec_type, TypeI32], vec_type, t.params, cases);
+ await run(t, compoundBinary('*='), [vec_type, Type.i32], vec_type, t.params, cases);
});
g.test('division_scalar_vector')
@@ -641,10 +335,10 @@ Expression: x / y
)
.fn(async t => {
const vec_size = t.params.vectorize_rhs;
- const vec_type = TypeVec(vec_size, TypeI32);
+ const vec_type = Type.vec(vec_size, Type.i32);
const source = t.params.inputSource === 'const' ? 'const' : 'non_const';
const cases = await d.get(`division_scalar_vector${vec_size}_${source}`);
- await run(t, binary('/'), [TypeI32, vec_type], vec_type, t.params, cases);
+ await run(t, binary('/'), [Type.i32, vec_type], vec_type, t.params, cases);
});
g.test('division_vector_scalar')
@@ -659,10 +353,10 @@ Expression: x / y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeI32);
+ const vec_type = Type.vec(vec_size, Type.i32);
const source = t.params.inputSource === 'const' ? 'const' : 'non_const';
const cases = await d.get(`division_vector${vec_size}_scalar_${source}`);
- await run(t, binary('/'), [vec_type, TypeI32], vec_type, t.params, cases);
+ await run(t, binary('/'), [vec_type, Type.i32], vec_type, t.params, cases);
});
g.test('division_vector_scalar_compound')
@@ -677,10 +371,10 @@ Expression: x /= y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeI32);
+ const vec_type = Type.vec(vec_size, Type.i32);
const source = t.params.inputSource === 'const' ? 'const' : 'non_const';
const cases = await d.get(`division_vector${vec_size}_scalar_${source}`);
- await run(t, compoundBinary('/='), [vec_type, TypeI32], vec_type, t.params, cases);
+ await run(t, compoundBinary('/='), [vec_type, Type.i32], vec_type, t.params, cases);
});
g.test('remainder_scalar_vector')
@@ -695,10 +389,10 @@ Expression: x % y
)
.fn(async t => {
const vec_size = t.params.vectorize_rhs;
- const vec_type = TypeVec(vec_size, TypeI32);
+ const vec_type = Type.vec(vec_size, Type.i32);
const source = t.params.inputSource === 'const' ? 'const' : 'non_const';
const cases = await d.get(`remainder_scalar_vector${vec_size}_${source}`);
- await run(t, binary('%'), [TypeI32, vec_type], vec_type, t.params, cases);
+ await run(t, binary('%'), [Type.i32, vec_type], vec_type, t.params, cases);
});
g.test('remainder_vector_scalar')
@@ -713,10 +407,10 @@ Expression: x % y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeI32);
+ const vec_type = Type.vec(vec_size, Type.i32);
const source = t.params.inputSource === 'const' ? 'const' : 'non_const';
const cases = await d.get(`remainder_vector${vec_size}_scalar_${source}`);
- await run(t, binary('%'), [vec_type, TypeI32], vec_type, t.params, cases);
+ await run(t, binary('%'), [vec_type, Type.i32], vec_type, t.params, cases);
});
g.test('remainder_vector_scalar_compound')
@@ -731,8 +425,8 @@ Expression: x %= y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeI32);
+ const vec_type = Type.vec(vec_size, Type.i32);
const source = t.params.inputSource === 'const' ? 'const' : 'non_const';
const cases = await d.get(`remainder_vector${vec_size}_scalar_${source}`);
- await run(t, compoundBinary('%='), [vec_type, TypeI32], vec_type, t.params, cases);
+ await run(t, compoundBinary('%='), [vec_type, Type.i32], vec_type, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/i32_comparison.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/i32_comparison.cache.ts
new file mode 100644
index 0000000000..de0a6de477
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/i32_comparison.cache.ts
@@ -0,0 +1,21 @@
+import { bool, i32 } from '../../../../util/conversion.js';
+import { vectorI32Range } from '../../../../util/math.js';
+import { Case } from '../case.js';
+import { makeCaseCache } from '../case_cache.js';
+
+/**
+ * @returns a test case for the provided left hand & right hand values and
+ * expected boolean result.
+ */
+function makeCase(lhs: number, rhs: number, expected_answer: boolean): Case {
+ return { input: [i32(lhs), i32(rhs)], expected: bool(expected_answer) };
+}
+
+export const d = makeCaseCache('binary/i32_comparison', {
+ equals: () => vectorI32Range(2).map(v => makeCase(v[0], v[1], v[0] === v[1])),
+ not_equals: () => vectorI32Range(2).map(v => makeCase(v[0], v[1], v[0] !== v[1])),
+ less_than: () => vectorI32Range(2).map(v => makeCase(v[0], v[1], v[0] < v[1])),
+ less_equal: () => vectorI32Range(2).map(v => makeCase(v[0], v[1], v[0] <= v[1])),
+ greater_than: () => vectorI32Range(2).map(v => makeCase(v[0], v[1], v[0] > v[1])),
+ greater_equal: () => vectorI32Range(2).map(v => makeCase(v[0], v[1], v[0] >= v[1])),
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/i32_comparison.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/i32_comparison.spec.ts
index dce1a2519e..9b6566c9a4 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/i32_comparison.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/i32_comparison.spec.ts
@@ -4,32 +4,14 @@ Execution Tests for the i32 comparison expressions
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { i32, bool, TypeBool, TypeI32 } from '../../../../util/conversion.js';
-import { vectorI32Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
-import { allInputSources, Case, run } from '../expression.js';
+import { Type } from '../../../../util/conversion.js';
+import { allInputSources, run } from '../expression.js';
import { binary } from './binary.js';
+import { d } from './i32_comparison.cache.js';
export const g = makeTestGroup(GPUTest);
-/**
- * @returns a test case for the provided left hand & right hand values and
- * expected boolean result.
- */
-function makeCase(lhs: number, rhs: number, expected_answer: boolean): Case {
- return { input: [i32(lhs), i32(rhs)], expected: bool(expected_answer) };
-}
-
-export const d = makeCaseCache('binary/i32_comparison', {
- equals: () => vectorI32Range(2).map(v => makeCase(v[0], v[1], v[0] === v[1])),
- not_equals: () => vectorI32Range(2).map(v => makeCase(v[0], v[1], v[0] !== v[1])),
- less_than: () => vectorI32Range(2).map(v => makeCase(v[0], v[1], v[0] < v[1])),
- less_equal: () => vectorI32Range(2).map(v => makeCase(v[0], v[1], v[0] <= v[1])),
- greater_than: () => vectorI32Range(2).map(v => makeCase(v[0], v[1], v[0] > v[1])),
- greater_equal: () => vectorI32Range(2).map(v => makeCase(v[0], v[1], v[0] >= v[1])),
-});
-
g.test('equals')
.specURL('https://www.w3.org/TR/WGSL/#comparison-expr')
.desc(
@@ -42,7 +24,7 @@ Expression: x == y
)
.fn(async t => {
const cases = await d.get('equals');
- await run(t, binary('=='), [TypeI32, TypeI32], TypeBool, t.params, cases);
+ await run(t, binary('=='), [Type.i32, Type.i32], Type.bool, t.params, cases);
});
g.test('not_equals')
@@ -57,7 +39,7 @@ Expression: x != y
)
.fn(async t => {
const cases = await d.get('not_equals');
- await run(t, binary('!='), [TypeI32, TypeI32], TypeBool, t.params, cases);
+ await run(t, binary('!='), [Type.i32, Type.i32], Type.bool, t.params, cases);
});
g.test('less_than')
@@ -72,7 +54,7 @@ Expression: x < y
)
.fn(async t => {
const cases = await d.get('less_than');
- await run(t, binary('<'), [TypeI32, TypeI32], TypeBool, t.params, cases);
+ await run(t, binary('<'), [Type.i32, Type.i32], Type.bool, t.params, cases);
});
g.test('less_equals')
@@ -87,7 +69,7 @@ Expression: x <= y
)
.fn(async t => {
const cases = await d.get('less_equal');
- await run(t, binary('<='), [TypeI32, TypeI32], TypeBool, t.params, cases);
+ await run(t, binary('<='), [Type.i32, Type.i32], Type.bool, t.params, cases);
});
g.test('greater_than')
@@ -102,7 +84,7 @@ Expression: x > y
)
.fn(async t => {
const cases = await d.get('greater_than');
- await run(t, binary('>'), [TypeI32, TypeI32], TypeBool, t.params, cases);
+ await run(t, binary('>'), [Type.i32, Type.i32], Type.bool, t.params, cases);
});
g.test('greater_equals')
@@ -117,5 +99,5 @@ Expression: x >= y
)
.fn(async t => {
const cases = await d.get('greater_equal');
- await run(t, binary('>='), [TypeI32, TypeI32], TypeBool, t.params, cases);
+ await run(t, binary('>='), [Type.i32, Type.i32], Type.bool, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/u32_arithmetic.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/u32_arithmetic.cache.ts
new file mode 100644
index 0000000000..91be35a1ee
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/u32_arithmetic.cache.ts
@@ -0,0 +1,293 @@
+import { sparseU32Range, vectorU32Range } from '../../../../util/math.js';
+import {
+ generateBinaryToU32Cases,
+ generateU32VectorBinaryToVectorCases,
+ generateVectorU32BinaryToVectorCases,
+} from '../case.js';
+import { makeCaseCache } from '../case_cache.js';
+
+function u32_add(x: number, y: number): number | undefined {
+ return x + y;
+}
+
+function u32_subtract(x: number, y: number): number | undefined {
+ return x - y;
+}
+
+function u32_multiply(x: number, y: number): number | undefined {
+ return Math.imul(x, y);
+}
+
+function u32_divide_non_const(x: number, y: number): number | undefined {
+ if (y === 0) {
+ return x;
+ }
+ return x / y;
+}
+
+function u32_divide_const(x: number, y: number): number | undefined {
+ if (y === 0) {
+ return undefined;
+ }
+ return x / y;
+}
+
+function u32_remainder_non_const(x: number, y: number): number | undefined {
+ if (y === 0) {
+ return 0;
+ }
+ return x % y;
+}
+
+function u32_remainder_const(x: number, y: number): number | undefined {
+ if (y === 0) {
+ return undefined;
+ }
+ return x % y;
+}
+
+export const d = makeCaseCache('binary/u32_arithmetic', {
+ addition: () => {
+ return generateBinaryToU32Cases(sparseU32Range(), sparseU32Range(), u32_add);
+ },
+ subtraction: () => {
+ return generateBinaryToU32Cases(sparseU32Range(), sparseU32Range(), u32_subtract);
+ },
+ multiplication: () => {
+ return generateBinaryToU32Cases(sparseU32Range(), sparseU32Range(), u32_multiply);
+ },
+ division_non_const: () => {
+ return generateBinaryToU32Cases(sparseU32Range(), sparseU32Range(), u32_divide_non_const);
+ },
+ division_const: () => {
+ return generateBinaryToU32Cases(sparseU32Range(), sparseU32Range(), u32_divide_const);
+ },
+ remainder_non_const: () => {
+ return generateBinaryToU32Cases(sparseU32Range(), sparseU32Range(), u32_remainder_non_const);
+ },
+ remainder_const: () => {
+ return generateBinaryToU32Cases(sparseU32Range(), sparseU32Range(), u32_remainder_const);
+ },
+ addition_scalar_vector2: () => {
+ return generateU32VectorBinaryToVectorCases(sparseU32Range(), vectorU32Range(2), u32_add);
+ },
+ addition_scalar_vector3: () => {
+ return generateU32VectorBinaryToVectorCases(sparseU32Range(), vectorU32Range(3), u32_add);
+ },
+ addition_scalar_vector4: () => {
+ return generateU32VectorBinaryToVectorCases(sparseU32Range(), vectorU32Range(4), u32_add);
+ },
+ addition_vector2_scalar: () => {
+ return generateVectorU32BinaryToVectorCases(vectorU32Range(2), sparseU32Range(), u32_add);
+ },
+ addition_vector3_scalar: () => {
+ return generateVectorU32BinaryToVectorCases(vectorU32Range(3), sparseU32Range(), u32_add);
+ },
+ addition_vector4_scalar: () => {
+ return generateVectorU32BinaryToVectorCases(vectorU32Range(4), sparseU32Range(), u32_add);
+ },
+ subtraction_scalar_vector2: () => {
+ return generateU32VectorBinaryToVectorCases(sparseU32Range(), vectorU32Range(2), u32_subtract);
+ },
+ subtraction_scalar_vector3: () => {
+ return generateU32VectorBinaryToVectorCases(sparseU32Range(), vectorU32Range(3), u32_subtract);
+ },
+ subtraction_scalar_vector4: () => {
+ return generateU32VectorBinaryToVectorCases(sparseU32Range(), vectorU32Range(4), u32_subtract);
+ },
+ subtraction_vector2_scalar: () => {
+ return generateVectorU32BinaryToVectorCases(vectorU32Range(2), sparseU32Range(), u32_subtract);
+ },
+ subtraction_vector3_scalar: () => {
+ return generateVectorU32BinaryToVectorCases(vectorU32Range(3), sparseU32Range(), u32_subtract);
+ },
+ subtraction_vector4_scalar: () => {
+ return generateVectorU32BinaryToVectorCases(vectorU32Range(4), sparseU32Range(), u32_subtract);
+ },
+ multiplication_scalar_vector2: () => {
+ return generateU32VectorBinaryToVectorCases(sparseU32Range(), vectorU32Range(2), u32_multiply);
+ },
+ multiplication_scalar_vector3: () => {
+ return generateU32VectorBinaryToVectorCases(sparseU32Range(), vectorU32Range(3), u32_multiply);
+ },
+ multiplication_scalar_vector4: () => {
+ return generateU32VectorBinaryToVectorCases(sparseU32Range(), vectorU32Range(4), u32_multiply);
+ },
+ multiplication_vector2_scalar: () => {
+ return generateVectorU32BinaryToVectorCases(vectorU32Range(2), sparseU32Range(), u32_multiply);
+ },
+ multiplication_vector3_scalar: () => {
+ return generateVectorU32BinaryToVectorCases(vectorU32Range(3), sparseU32Range(), u32_multiply);
+ },
+ multiplication_vector4_scalar: () => {
+ return generateVectorU32BinaryToVectorCases(vectorU32Range(4), sparseU32Range(), u32_multiply);
+ },
+ division_scalar_vector2_non_const: () => {
+ return generateU32VectorBinaryToVectorCases(
+ sparseU32Range(),
+ vectorU32Range(2),
+ u32_divide_non_const
+ );
+ },
+ division_scalar_vector3_non_const: () => {
+ return generateU32VectorBinaryToVectorCases(
+ sparseU32Range(),
+ vectorU32Range(3),
+ u32_divide_non_const
+ );
+ },
+ division_scalar_vector4_non_const: () => {
+ return generateU32VectorBinaryToVectorCases(
+ sparseU32Range(),
+ vectorU32Range(4),
+ u32_divide_non_const
+ );
+ },
+ division_vector2_scalar_non_const: () => {
+ return generateVectorU32BinaryToVectorCases(
+ vectorU32Range(2),
+ sparseU32Range(),
+ u32_divide_non_const
+ );
+ },
+ division_vector3_scalar_non_const: () => {
+ return generateVectorU32BinaryToVectorCases(
+ vectorU32Range(3),
+ sparseU32Range(),
+ u32_divide_non_const
+ );
+ },
+ division_vector4_scalar_non_const: () => {
+ return generateVectorU32BinaryToVectorCases(
+ vectorU32Range(4),
+ sparseU32Range(),
+ u32_divide_non_const
+ );
+ },
+ division_scalar_vector2_const: () => {
+ return generateU32VectorBinaryToVectorCases(
+ sparseU32Range(),
+ vectorU32Range(2),
+ u32_divide_const
+ );
+ },
+ division_scalar_vector3_const: () => {
+ return generateU32VectorBinaryToVectorCases(
+ sparseU32Range(),
+ vectorU32Range(3),
+ u32_divide_const
+ );
+ },
+ division_scalar_vector4_const: () => {
+ return generateU32VectorBinaryToVectorCases(
+ sparseU32Range(),
+ vectorU32Range(4),
+ u32_divide_const
+ );
+ },
+ division_vector2_scalar_const: () => {
+ return generateVectorU32BinaryToVectorCases(
+ vectorU32Range(2),
+ sparseU32Range(),
+ u32_divide_const
+ );
+ },
+ division_vector3_scalar_const: () => {
+ return generateVectorU32BinaryToVectorCases(
+ vectorU32Range(3),
+ sparseU32Range(),
+ u32_divide_const
+ );
+ },
+ division_vector4_scalar_const: () => {
+ return generateVectorU32BinaryToVectorCases(
+ vectorU32Range(4),
+ sparseU32Range(),
+ u32_divide_const
+ );
+ },
+ remainder_scalar_vector2_non_const: () => {
+ return generateU32VectorBinaryToVectorCases(
+ sparseU32Range(),
+ vectorU32Range(2),
+ u32_remainder_non_const
+ );
+ },
+ remainder_scalar_vector3_non_const: () => {
+ return generateU32VectorBinaryToVectorCases(
+ sparseU32Range(),
+ vectorU32Range(3),
+ u32_remainder_non_const
+ );
+ },
+ remainder_scalar_vector4_non_const: () => {
+ return generateU32VectorBinaryToVectorCases(
+ sparseU32Range(),
+ vectorU32Range(4),
+ u32_remainder_non_const
+ );
+ },
+ remainder_vector2_scalar_non_const: () => {
+ return generateVectorU32BinaryToVectorCases(
+ vectorU32Range(2),
+ sparseU32Range(),
+ u32_remainder_non_const
+ );
+ },
+ remainder_vector3_scalar_non_const: () => {
+ return generateVectorU32BinaryToVectorCases(
+ vectorU32Range(3),
+ sparseU32Range(),
+ u32_remainder_non_const
+ );
+ },
+ remainder_vector4_scalar_non_const: () => {
+ return generateVectorU32BinaryToVectorCases(
+ vectorU32Range(4),
+ sparseU32Range(),
+ u32_remainder_non_const
+ );
+ },
+ remainder_scalar_vector2_const: () => {
+ return generateU32VectorBinaryToVectorCases(
+ sparseU32Range(),
+ vectorU32Range(2),
+ u32_remainder_const
+ );
+ },
+ remainder_scalar_vector3_const: () => {
+ return generateU32VectorBinaryToVectorCases(
+ sparseU32Range(),
+ vectorU32Range(3),
+ u32_remainder_const
+ );
+ },
+ remainder_scalar_vector4_const: () => {
+ return generateU32VectorBinaryToVectorCases(
+ sparseU32Range(),
+ vectorU32Range(4),
+ u32_remainder_const
+ );
+ },
+ remainder_vector2_scalar_const: () => {
+ return generateVectorU32BinaryToVectorCases(
+ vectorU32Range(2),
+ sparseU32Range(),
+ u32_remainder_const
+ );
+ },
+ remainder_vector3_scalar_const: () => {
+ return generateVectorU32BinaryToVectorCases(
+ vectorU32Range(3),
+ sparseU32Range(),
+ u32_remainder_const
+ );
+ },
+ remainder_vector4_scalar_const: () => {
+ return generateVectorU32BinaryToVectorCases(
+ vectorU32Range(4),
+ sparseU32Range(),
+ u32_remainder_const
+ );
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/u32_arithmetic.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/u32_arithmetic.spec.ts
index 88667e8233..6df16f303c 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/u32_arithmetic.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/u32_arithmetic.spec.ts
@@ -4,307 +4,14 @@ Execution Tests for the u32 arithmetic binary expression operations
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeU32, TypeVec } from '../../../../util/conversion.js';
-import { sparseU32Range, vectorU32Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
-import {
- allInputSources,
- generateBinaryToU32Cases,
- generateU32VectorBinaryToVectorCases,
- generateVectorU32BinaryToVectorCases,
- run,
-} from '../expression.js';
+import { Type } from '../../../../util/conversion.js';
+import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
-
-function u32_add(x: number, y: number): number | undefined {
- return x + y;
-}
-
-function u32_subtract(x: number, y: number): number | undefined {
- return x - y;
-}
-
-function u32_multiply(x: number, y: number): number | undefined {
- return Math.imul(x, y);
-}
-
-function u32_divide_non_const(x: number, y: number): number | undefined {
- if (y === 0) {
- return x;
- }
- return x / y;
-}
-
-function u32_divide_const(x: number, y: number): number | undefined {
- if (y === 0) {
- return undefined;
- }
- return x / y;
-}
-
-function u32_remainder_non_const(x: number, y: number): number | undefined {
- if (y === 0) {
- return 0;
- }
- return x % y;
-}
-
-function u32_remainder_const(x: number, y: number): number | undefined {
- if (y === 0) {
- return undefined;
- }
- return x % y;
-}
+import { d } from './u32_arithmetic.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('binary/u32_arithmetic', {
- addition: () => {
- return generateBinaryToU32Cases(sparseU32Range(), sparseU32Range(), u32_add);
- },
- subtraction: () => {
- return generateBinaryToU32Cases(sparseU32Range(), sparseU32Range(), u32_subtract);
- },
- multiplication: () => {
- return generateBinaryToU32Cases(sparseU32Range(), sparseU32Range(), u32_multiply);
- },
- division_non_const: () => {
- return generateBinaryToU32Cases(sparseU32Range(), sparseU32Range(), u32_divide_non_const);
- },
- division_const: () => {
- return generateBinaryToU32Cases(sparseU32Range(), sparseU32Range(), u32_divide_const);
- },
- remainder_non_const: () => {
- return generateBinaryToU32Cases(sparseU32Range(), sparseU32Range(), u32_remainder_non_const);
- },
- remainder_const: () => {
- return generateBinaryToU32Cases(sparseU32Range(), sparseU32Range(), u32_remainder_const);
- },
- addition_scalar_vector2: () => {
- return generateU32VectorBinaryToVectorCases(sparseU32Range(), vectorU32Range(2), u32_add);
- },
- addition_scalar_vector3: () => {
- return generateU32VectorBinaryToVectorCases(sparseU32Range(), vectorU32Range(3), u32_add);
- },
- addition_scalar_vector4: () => {
- return generateU32VectorBinaryToVectorCases(sparseU32Range(), vectorU32Range(4), u32_add);
- },
- addition_vector2_scalar: () => {
- return generateVectorU32BinaryToVectorCases(vectorU32Range(2), sparseU32Range(), u32_add);
- },
- addition_vector3_scalar: () => {
- return generateVectorU32BinaryToVectorCases(vectorU32Range(3), sparseU32Range(), u32_add);
- },
- addition_vector4_scalar: () => {
- return generateVectorU32BinaryToVectorCases(vectorU32Range(4), sparseU32Range(), u32_add);
- },
- subtraction_scalar_vector2: () => {
- return generateU32VectorBinaryToVectorCases(sparseU32Range(), vectorU32Range(2), u32_subtract);
- },
- subtraction_scalar_vector3: () => {
- return generateU32VectorBinaryToVectorCases(sparseU32Range(), vectorU32Range(3), u32_subtract);
- },
- subtraction_scalar_vector4: () => {
- return generateU32VectorBinaryToVectorCases(sparseU32Range(), vectorU32Range(4), u32_subtract);
- },
- subtraction_vector2_scalar: () => {
- return generateVectorU32BinaryToVectorCases(vectorU32Range(2), sparseU32Range(), u32_subtract);
- },
- subtraction_vector3_scalar: () => {
- return generateVectorU32BinaryToVectorCases(vectorU32Range(3), sparseU32Range(), u32_subtract);
- },
- subtraction_vector4_scalar: () => {
- return generateVectorU32BinaryToVectorCases(vectorU32Range(4), sparseU32Range(), u32_subtract);
- },
- multiplication_scalar_vector2: () => {
- return generateU32VectorBinaryToVectorCases(sparseU32Range(), vectorU32Range(2), u32_multiply);
- },
- multiplication_scalar_vector3: () => {
- return generateU32VectorBinaryToVectorCases(sparseU32Range(), vectorU32Range(3), u32_multiply);
- },
- multiplication_scalar_vector4: () => {
- return generateU32VectorBinaryToVectorCases(sparseU32Range(), vectorU32Range(4), u32_multiply);
- },
- multiplication_vector2_scalar: () => {
- return generateVectorU32BinaryToVectorCases(vectorU32Range(2), sparseU32Range(), u32_multiply);
- },
- multiplication_vector3_scalar: () => {
- return generateVectorU32BinaryToVectorCases(vectorU32Range(3), sparseU32Range(), u32_multiply);
- },
- multiplication_vector4_scalar: () => {
- return generateVectorU32BinaryToVectorCases(vectorU32Range(4), sparseU32Range(), u32_multiply);
- },
- division_scalar_vector2_non_const: () => {
- return generateU32VectorBinaryToVectorCases(
- sparseU32Range(),
- vectorU32Range(2),
- u32_divide_non_const
- );
- },
- division_scalar_vector3_non_const: () => {
- return generateU32VectorBinaryToVectorCases(
- sparseU32Range(),
- vectorU32Range(3),
- u32_divide_non_const
- );
- },
- division_scalar_vector4_non_const: () => {
- return generateU32VectorBinaryToVectorCases(
- sparseU32Range(),
- vectorU32Range(4),
- u32_divide_non_const
- );
- },
- division_vector2_scalar_non_const: () => {
- return generateVectorU32BinaryToVectorCases(
- vectorU32Range(2),
- sparseU32Range(),
- u32_divide_non_const
- );
- },
- division_vector3_scalar_non_const: () => {
- return generateVectorU32BinaryToVectorCases(
- vectorU32Range(3),
- sparseU32Range(),
- u32_divide_non_const
- );
- },
- division_vector4_scalar_non_const: () => {
- return generateVectorU32BinaryToVectorCases(
- vectorU32Range(4),
- sparseU32Range(),
- u32_divide_non_const
- );
- },
- division_scalar_vector2_const: () => {
- return generateU32VectorBinaryToVectorCases(
- sparseU32Range(),
- vectorU32Range(2),
- u32_divide_const
- );
- },
- division_scalar_vector3_const: () => {
- return generateU32VectorBinaryToVectorCases(
- sparseU32Range(),
- vectorU32Range(3),
- u32_divide_const
- );
- },
- division_scalar_vector4_const: () => {
- return generateU32VectorBinaryToVectorCases(
- sparseU32Range(),
- vectorU32Range(4),
- u32_divide_const
- );
- },
- division_vector2_scalar_const: () => {
- return generateVectorU32BinaryToVectorCases(
- vectorU32Range(2),
- sparseU32Range(),
- u32_divide_const
- );
- },
- division_vector3_scalar_const: () => {
- return generateVectorU32BinaryToVectorCases(
- vectorU32Range(3),
- sparseU32Range(),
- u32_divide_const
- );
- },
- division_vector4_scalar_const: () => {
- return generateVectorU32BinaryToVectorCases(
- vectorU32Range(4),
- sparseU32Range(),
- u32_divide_const
- );
- },
- remainder_scalar_vector2_non_const: () => {
- return generateU32VectorBinaryToVectorCases(
- sparseU32Range(),
- vectorU32Range(2),
- u32_remainder_non_const
- );
- },
- remainder_scalar_vector3_non_const: () => {
- return generateU32VectorBinaryToVectorCases(
- sparseU32Range(),
- vectorU32Range(3),
- u32_remainder_non_const
- );
- },
- remainder_scalar_vector4_non_const: () => {
- return generateU32VectorBinaryToVectorCases(
- sparseU32Range(),
- vectorU32Range(4),
- u32_remainder_non_const
- );
- },
- remainder_vector2_scalar_non_const: () => {
- return generateVectorU32BinaryToVectorCases(
- vectorU32Range(2),
- sparseU32Range(),
- u32_remainder_non_const
- );
- },
- remainder_vector3_scalar_non_const: () => {
- return generateVectorU32BinaryToVectorCases(
- vectorU32Range(3),
- sparseU32Range(),
- u32_remainder_non_const
- );
- },
- remainder_vector4_scalar_non_const: () => {
- return generateVectorU32BinaryToVectorCases(
- vectorU32Range(4),
- sparseU32Range(),
- u32_remainder_non_const
- );
- },
- remainder_scalar_vector2_const: () => {
- return generateU32VectorBinaryToVectorCases(
- sparseU32Range(),
- vectorU32Range(2),
- u32_remainder_const
- );
- },
- remainder_scalar_vector3_const: () => {
- return generateU32VectorBinaryToVectorCases(
- sparseU32Range(),
- vectorU32Range(3),
- u32_remainder_const
- );
- },
- remainder_scalar_vector4_const: () => {
- return generateU32VectorBinaryToVectorCases(
- sparseU32Range(),
- vectorU32Range(4),
- u32_remainder_const
- );
- },
- remainder_vector2_scalar_const: () => {
- return generateVectorU32BinaryToVectorCases(
- vectorU32Range(2),
- sparseU32Range(),
- u32_remainder_const
- );
- },
- remainder_vector3_scalar_const: () => {
- return generateVectorU32BinaryToVectorCases(
- vectorU32Range(3),
- sparseU32Range(),
- u32_remainder_const
- );
- },
- remainder_vector4_scalar_const: () => {
- return generateVectorU32BinaryToVectorCases(
- vectorU32Range(4),
- sparseU32Range(),
- u32_remainder_const
- );
- },
-});
-
g.test('addition')
.specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr')
.desc(
@@ -317,7 +24,7 @@ Expression: x + y
)
.fn(async t => {
const cases = await d.get('addition');
- await run(t, binary('+'), [TypeU32, TypeU32], TypeU32, t.params, cases);
+ await run(t, binary('+'), [Type.u32, Type.u32], Type.u32, t.params, cases);
});
g.test('addition_compound')
@@ -332,7 +39,7 @@ Expression: x += y
)
.fn(async t => {
const cases = await d.get('addition');
- await run(t, compoundBinary('+='), [TypeU32, TypeU32], TypeU32, t.params, cases);
+ await run(t, compoundBinary('+='), [Type.u32, Type.u32], Type.u32, t.params, cases);
});
g.test('subtraction')
@@ -347,7 +54,7 @@ Expression: x - y
)
.fn(async t => {
const cases = await d.get('subtraction');
- await run(t, binary('-'), [TypeU32, TypeU32], TypeU32, t.params, cases);
+ await run(t, binary('-'), [Type.u32, Type.u32], Type.u32, t.params, cases);
});
g.test('subtraction_compound')
@@ -362,7 +69,7 @@ Expression: x -= y
)
.fn(async t => {
const cases = await d.get('subtraction');
- await run(t, compoundBinary('-='), [TypeU32, TypeU32], TypeU32, t.params, cases);
+ await run(t, compoundBinary('-='), [Type.u32, Type.u32], Type.u32, t.params, cases);
});
g.test('multiplication')
@@ -377,7 +84,7 @@ Expression: x * y
)
.fn(async t => {
const cases = await d.get('multiplication');
- await run(t, binary('*'), [TypeU32, TypeU32], TypeU32, t.params, cases);
+ await run(t, binary('*'), [Type.u32, Type.u32], Type.u32, t.params, cases);
});
g.test('multiplication_compound')
@@ -392,7 +99,7 @@ Expression: x *= y
)
.fn(async t => {
const cases = await d.get('multiplication');
- await run(t, compoundBinary('*='), [TypeU32, TypeU32], TypeU32, t.params, cases);
+ await run(t, compoundBinary('*='), [Type.u32, Type.u32], Type.u32, t.params, cases);
});
g.test('division')
@@ -409,7 +116,7 @@ Expression: x / y
const cases = await d.get(
t.params.inputSource === 'const' ? 'division_const' : 'division_non_const'
);
- await run(t, binary('/'), [TypeU32, TypeU32], TypeU32, t.params, cases);
+ await run(t, binary('/'), [Type.u32, Type.u32], Type.u32, t.params, cases);
});
g.test('division_compound')
@@ -426,7 +133,7 @@ Expression: x /= y
const cases = await d.get(
t.params.inputSource === 'const' ? 'division_const' : 'division_non_const'
);
- await run(t, compoundBinary('/='), [TypeU32, TypeU32], TypeU32, t.params, cases);
+ await run(t, compoundBinary('/='), [Type.u32, Type.u32], Type.u32, t.params, cases);
});
g.test('remainder')
@@ -443,7 +150,7 @@ Expression: x % y
const cases = await d.get(
t.params.inputSource === 'const' ? 'remainder_const' : 'remainder_non_const'
);
- await run(t, binary('%'), [TypeU32, TypeU32], TypeU32, t.params, cases);
+ await run(t, binary('%'), [Type.u32, Type.u32], Type.u32, t.params, cases);
});
g.test('remainder_compound')
@@ -460,7 +167,7 @@ Expression: x %= y
const cases = await d.get(
t.params.inputSource === 'const' ? 'remainder_const' : 'remainder_non_const'
);
- await run(t, compoundBinary('%='), [TypeU32, TypeU32], TypeU32, t.params, cases);
+ await run(t, compoundBinary('%='), [Type.u32, Type.u32], Type.u32, t.params, cases);
});
g.test('addition_scalar_vector')
@@ -475,9 +182,9 @@ Expression: x + y
)
.fn(async t => {
const vec_size = t.params.vectorize_rhs;
- const vec_type = TypeVec(vec_size, TypeU32);
+ const vec_type = Type.vec(vec_size, Type.u32);
const cases = await d.get(`addition_scalar_vector${vec_size}`);
- await run(t, binary('+'), [TypeU32, vec_type], vec_type, t.params, cases);
+ await run(t, binary('+'), [Type.u32, vec_type], vec_type, t.params, cases);
});
g.test('addition_vector_scalar')
@@ -492,9 +199,9 @@ Expression: x + y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeU32);
+ const vec_type = Type.vec(vec_size, Type.u32);
const cases = await d.get(`addition_vector${vec_size}_scalar`);
- await run(t, binary('+'), [vec_type, TypeU32], vec_type, t.params, cases);
+ await run(t, binary('+'), [vec_type, Type.u32], vec_type, t.params, cases);
});
g.test('addition_vector_scalar_compound')
@@ -509,9 +216,9 @@ Expression: x += y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeU32);
+ const vec_type = Type.vec(vec_size, Type.u32);
const cases = await d.get(`addition_vector${vec_size}_scalar`);
- await run(t, compoundBinary('+='), [vec_type, TypeU32], vec_type, t.params, cases);
+ await run(t, compoundBinary('+='), [vec_type, Type.u32], vec_type, t.params, cases);
});
g.test('subtraction_scalar_vector')
@@ -526,9 +233,9 @@ Expression: x - y
)
.fn(async t => {
const vec_size = t.params.vectorize_rhs;
- const vec_type = TypeVec(vec_size, TypeU32);
+ const vec_type = Type.vec(vec_size, Type.u32);
const cases = await d.get(`subtraction_scalar_vector${vec_size}`);
- await run(t, binary('-'), [TypeU32, vec_type], vec_type, t.params, cases);
+ await run(t, binary('-'), [Type.u32, vec_type], vec_type, t.params, cases);
});
g.test('subtraction_vector_scalar')
@@ -543,9 +250,9 @@ Expression: x - y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeU32);
+ const vec_type = Type.vec(vec_size, Type.u32);
const cases = await d.get(`subtraction_vector${vec_size}_scalar`);
- await run(t, binary('-'), [vec_type, TypeU32], vec_type, t.params, cases);
+ await run(t, binary('-'), [vec_type, Type.u32], vec_type, t.params, cases);
});
g.test('subtraction_vector_scalar_compound')
@@ -560,9 +267,9 @@ Expression: x -= y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeU32);
+ const vec_type = Type.vec(vec_size, Type.u32);
const cases = await d.get(`subtraction_vector${vec_size}_scalar`);
- await run(t, compoundBinary('-='), [vec_type, TypeU32], vec_type, t.params, cases);
+ await run(t, compoundBinary('-='), [vec_type, Type.u32], vec_type, t.params, cases);
});
g.test('multiplication_scalar_vector')
@@ -577,9 +284,9 @@ Expression: x * y
)
.fn(async t => {
const vec_size = t.params.vectorize_rhs;
- const vec_type = TypeVec(vec_size, TypeU32);
+ const vec_type = Type.vec(vec_size, Type.u32);
const cases = await d.get(`multiplication_scalar_vector${vec_size}`);
- await run(t, binary('*'), [TypeU32, vec_type], vec_type, t.params, cases);
+ await run(t, binary('*'), [Type.u32, vec_type], vec_type, t.params, cases);
});
g.test('multiplication_vector_scalar')
@@ -594,9 +301,9 @@ Expression: x * y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeU32);
+ const vec_type = Type.vec(vec_size, Type.u32);
const cases = await d.get(`multiplication_vector${vec_size}_scalar`);
- await run(t, binary('*'), [vec_type, TypeU32], vec_type, t.params, cases);
+ await run(t, binary('*'), [vec_type, Type.u32], vec_type, t.params, cases);
});
g.test('multiplication_vector_scalar_compound')
@@ -611,9 +318,9 @@ Expression: x *= y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeU32);
+ const vec_type = Type.vec(vec_size, Type.u32);
const cases = await d.get(`multiplication_vector${vec_size}_scalar`);
- await run(t, compoundBinary('*='), [vec_type, TypeU32], vec_type, t.params, cases);
+ await run(t, compoundBinary('*='), [vec_type, Type.u32], vec_type, t.params, cases);
});
g.test('division_scalar_vector')
@@ -628,10 +335,10 @@ Expression: x / y
)
.fn(async t => {
const vec_size = t.params.vectorize_rhs;
- const vec_type = TypeVec(vec_size, TypeU32);
+ const vec_type = Type.vec(vec_size, Type.u32);
const source = t.params.inputSource === 'const' ? 'const' : 'non_const';
const cases = await d.get(`division_scalar_vector${vec_size}_${source}`);
- await run(t, binary('/'), [TypeU32, vec_type], vec_type, t.params, cases);
+ await run(t, binary('/'), [Type.u32, vec_type], vec_type, t.params, cases);
});
g.test('division_vector_scalar')
@@ -646,10 +353,10 @@ Expression: x / y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeU32);
+ const vec_type = Type.vec(vec_size, Type.u32);
const source = t.params.inputSource === 'const' ? 'const' : 'non_const';
const cases = await d.get(`division_vector${vec_size}_scalar_${source}`);
- await run(t, binary('/'), [vec_type, TypeU32], vec_type, t.params, cases);
+ await run(t, binary('/'), [vec_type, Type.u32], vec_type, t.params, cases);
});
g.test('division_vector_scalar_compound')
@@ -664,10 +371,10 @@ Expression: x /= y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeU32);
+ const vec_type = Type.vec(vec_size, Type.u32);
const source = t.params.inputSource === 'const' ? 'const' : 'non_const';
const cases = await d.get(`division_vector${vec_size}_scalar_${source}`);
- await run(t, compoundBinary('/='), [vec_type, TypeU32], vec_type, t.params, cases);
+ await run(t, compoundBinary('/='), [vec_type, Type.u32], vec_type, t.params, cases);
});
g.test('remainder_scalar_vector')
@@ -682,10 +389,10 @@ Expression: x % y
)
.fn(async t => {
const vec_size = t.params.vectorize_rhs;
- const vec_type = TypeVec(vec_size, TypeU32);
+ const vec_type = Type.vec(vec_size, Type.u32);
const source = t.params.inputSource === 'const' ? 'const' : 'non_const';
const cases = await d.get(`remainder_scalar_vector${vec_size}_${source}`);
- await run(t, binary('%'), [TypeU32, vec_type], vec_type, t.params, cases);
+ await run(t, binary('%'), [Type.u32, vec_type], vec_type, t.params, cases);
});
g.test('remainder_vector_scalar')
@@ -700,10 +407,10 @@ Expression: x % y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeU32);
+ const vec_type = Type.vec(vec_size, Type.u32);
const source = t.params.inputSource === 'const' ? 'const' : 'non_const';
const cases = await d.get(`remainder_vector${vec_size}_scalar_${source}`);
- await run(t, binary('%'), [vec_type, TypeU32], vec_type, t.params, cases);
+ await run(t, binary('%'), [vec_type, Type.u32], vec_type, t.params, cases);
});
g.test('remainder_vector_scalar_compound')
@@ -718,8 +425,8 @@ Expression: x %= y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeU32);
+ const vec_type = Type.vec(vec_size, Type.u32);
const source = t.params.inputSource === 'const' ? 'const' : 'non_const';
const cases = await d.get(`remainder_vector${vec_size}_scalar_${source}`);
- await run(t, compoundBinary('%='), [vec_type, TypeU32], vec_type, t.params, cases);
+ await run(t, compoundBinary('%='), [vec_type, Type.u32], vec_type, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/u32_comparison.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/u32_comparison.cache.ts
new file mode 100644
index 0000000000..93b0500496
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/u32_comparison.cache.ts
@@ -0,0 +1,21 @@
+import { bool, u32 } from '../../../../util/conversion.js';
+import { vectorU32Range } from '../../../../util/math.js';
+import { Case } from '../case.js';
+import { makeCaseCache } from '../case_cache.js';
+
+/**
+ * @returns a test case for the provided left hand & right hand values and
+ * expected boolean result.
+ */
+function makeCase(lhs: number, rhs: number, expected_answer: boolean): Case {
+ return { input: [u32(lhs), u32(rhs)], expected: bool(expected_answer) };
+}
+
+export const d = makeCaseCache('binary/u32_comparison', {
+ equals: () => vectorU32Range(2).map(v => makeCase(v[0], v[1], v[0] === v[1])),
+ not_equals: () => vectorU32Range(2).map(v => makeCase(v[0], v[1], v[0] !== v[1])),
+ less_than: () => vectorU32Range(2).map(v => makeCase(v[0], v[1], v[0] < v[1])),
+ less_equal: () => vectorU32Range(2).map(v => makeCase(v[0], v[1], v[0] <= v[1])),
+ greater_than: () => vectorU32Range(2).map(v => makeCase(v[0], v[1], v[0] > v[1])),
+ greater_equal: () => vectorU32Range(2).map(v => makeCase(v[0], v[1], v[0] >= v[1])),
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/u32_comparison.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/u32_comparison.spec.ts
index 1f693da5fd..5bb6767b01 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/u32_comparison.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/u32_comparison.spec.ts
@@ -4,32 +4,14 @@ Execution Tests for the u32 comparison expressions
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { u32, bool, TypeBool, TypeU32 } from '../../../../util/conversion.js';
-import { vectorU32Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
-import { allInputSources, Case, run } from '../expression.js';
+import { Type } from '../../../../util/conversion.js';
+import { allInputSources, run } from '../expression.js';
import { binary } from './binary.js';
+import { d } from './u32_comparison.cache.js';
export const g = makeTestGroup(GPUTest);
-/**
- * @returns a test case for the provided left hand & right hand values and
- * expected boolean result.
- */
-function makeCase(lhs: number, rhs: number, expected_answer: boolean): Case {
- return { input: [u32(lhs), u32(rhs)], expected: bool(expected_answer) };
-}
-
-export const d = makeCaseCache('binary/u32_comparison', {
- equals: () => vectorU32Range(2).map(v => makeCase(v[0], v[1], v[0] === v[1])),
- not_equals: () => vectorU32Range(2).map(v => makeCase(v[0], v[1], v[0] !== v[1])),
- less_than: () => vectorU32Range(2).map(v => makeCase(v[0], v[1], v[0] < v[1])),
- less_equal: () => vectorU32Range(2).map(v => makeCase(v[0], v[1], v[0] <= v[1])),
- greater_than: () => vectorU32Range(2).map(v => makeCase(v[0], v[1], v[0] > v[1])),
- greater_equal: () => vectorU32Range(2).map(v => makeCase(v[0], v[1], v[0] >= v[1])),
-});
-
g.test('equals')
.specURL('https://www.w3.org/TR/WGSL/#comparison-expr')
.desc(
@@ -42,7 +24,7 @@ Expression: x == y
)
.fn(async t => {
const cases = await d.get('equals');
- await run(t, binary('=='), [TypeU32, TypeU32], TypeBool, t.params, cases);
+ await run(t, binary('=='), [Type.u32, Type.u32], Type.bool, t.params, cases);
});
g.test('not_equals')
@@ -57,7 +39,7 @@ Expression: x != y
)
.fn(async t => {
const cases = await d.get('not_equals');
- await run(t, binary('!='), [TypeU32, TypeU32], TypeBool, t.params, cases);
+ await run(t, binary('!='), [Type.u32, Type.u32], Type.bool, t.params, cases);
});
g.test('less_than')
@@ -72,7 +54,7 @@ Expression: x < y
)
.fn(async t => {
const cases = await d.get('less_than');
- await run(t, binary('<'), [TypeU32, TypeU32], TypeBool, t.params, cases);
+ await run(t, binary('<'), [Type.u32, Type.u32], Type.bool, t.params, cases);
});
g.test('less_equals')
@@ -87,7 +69,7 @@ Expression: x <= y
)
.fn(async t => {
const cases = await d.get('less_equal');
- await run(t, binary('<='), [TypeU32, TypeU32], TypeBool, t.params, cases);
+ await run(t, binary('<='), [Type.u32, Type.u32], Type.bool, t.params, cases);
});
g.test('greater_than')
@@ -102,7 +84,7 @@ Expression: x > y
)
.fn(async t => {
const cases = await d.get('greater_than');
- await run(t, binary('>'), [TypeU32, TypeU32], TypeBool, t.params, cases);
+ await run(t, binary('>'), [Type.u32, Type.u32], Type.bool, t.params, cases);
});
g.test('greater_equals')
@@ -117,5 +99,5 @@ Expression: x >= y
)
.fn(async t => {
const cases = await d.get('greater_equal');
- await run(t, binary('>='), [TypeU32, TypeU32], TypeBool, t.params, cases);
+ await run(t, binary('>='), [Type.u32, Type.u32], Type.bool, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/abs.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/abs.cache.ts
new file mode 100644
index 0000000000..9634ae8f34
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/abs.cache.ts
@@ -0,0 +1,26 @@
+import { abstractInt } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { absBigInt, fullI64Range } from '../../../../../util/math.js';
+import { CaseListBuilder, makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract_float|abstract_int]
+const cases: Record<string, CaseListBuilder> = {
+ ...(['f32', 'f16', 'abstract'] as const)
+ .map(trait => ({
+ [`${trait === 'abstract' ? 'abstract_float' : trait}`]: () => {
+ return FP[trait].generateScalarToIntervalCases(
+ FP[trait].scalarRange(),
+ 'unfiltered',
+ FP[trait].absInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {}),
+ abstract_int: () => {
+ return fullI64Range().map(e => {
+ return { input: abstractInt(e), expected: abstractInt(absBigInt(e)) };
+ });
+ },
+};
+
+export const d = makeCaseCache('abs', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/abs.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/abs.spec.ts
index 05d5242f73..75d41ab163 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/abs.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/abs.spec.ts
@@ -1,14 +1,14 @@
export const description = `
Execution tests for the 'abs' builtin function
-S is AbstractInt, i32, or u32
+S is abstract-int, i32, or u32
T is S or vecN<S>
@const fn abs(e: T ) -> T
The absolute value of e. Component-wise when T is a vector. If e is a signed
integral scalar type and evaluates to the largest negative value, then the
result is e. If e is an unsigned integral type, then the result is e.
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn abs(e: T ) -> T
Returns the absolute value of e (e.g. e with a positive sign bit).
@@ -18,47 +18,26 @@ Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
import { kBit } from '../../../../../util/constants.js';
-import {
- i32Bits,
- TypeF32,
- TypeF16,
- TypeI32,
- TypeU32,
- u32Bits,
- TypeAbstractFloat,
-} from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF16Range, fullF64Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
+import { Type, i32Bits, u32Bits } from '../../../../../util/conversion.js';
import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { abstractBuiltin, builtin } from './builtin.js';
+import { d } from './abs.cache.js';
+import { abstractFloatBuiltin, abstractIntBuiltin, builtin } from './builtin.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('abs', {
- f32: () => {
- return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.absInterval);
- },
- f16: () => {
- return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'unfiltered', FP.f16.absInterval);
- },
- abstract: () => {
- return FP.abstract.generateScalarToIntervalCases(
- fullF64Range(),
- 'unfiltered',
- FP.abstract.absInterval
- );
- },
-});
-
g.test('abstract_int')
.specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
.desc(`abstract int tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract_int');
+ await run(t, abstractIntBuiltin('abs'), [Type.abstractInt], Type.abstractInt, t.params, cases);
+ });
g.test('u32')
.specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
@@ -67,7 +46,7 @@ g.test('u32')
u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
)
.fn(async t => {
- await run(t, builtin('abs'), [TypeU32], TypeU32, t.params, [
+ await run(t, builtin('abs'), [Type.u32], Type.u32, t.params, [
// Min and Max u32
{ input: u32Bits(kBit.u32.min), expected: u32Bits(kBit.u32.min) },
{ input: u32Bits(kBit.u32.max), expected: u32Bits(kBit.u32.max) },
@@ -114,7 +93,7 @@ g.test('i32')
u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
)
.fn(async t => {
- await run(t, builtin('abs'), [TypeI32], TypeI32, t.params, [
+ await run(t, builtin('abs'), [Type.i32], Type.i32, t.params, [
// Min and max i32
// If e evaluates to the largest negative value, then the result is e.
{ input: i32Bits(kBit.i32.negative.min), expected: i32Bits(kBit.i32.negative.min) },
@@ -166,8 +145,15 @@ g.test('abstract_float')
.combine('vectorize', [undefined, 2, 3, 4] as const)
)
.fn(async t => {
- const cases = await d.get('abstract');
- await run(t, abstractBuiltin('abs'), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases);
+ const cases = await d.get('abstract_float');
+ await run(
+ t,
+ abstractFloatBuiltin('abs'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
});
g.test('f32')
@@ -178,7 +164,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('abs'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('abs'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -192,5 +178,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('abs'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('abs'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acos.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acos.cache.ts
new file mode 100644
index 0000000000..466618b6db
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acos.cache.ts
@@ -0,0 +1,24 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]_[non_]const
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateScalarToIntervalCases(
+ [...linearRange(-1, 1, 100), ...FP[trait].scalarRange()], // acos is defined on [-1, 1]
+ nonConst ? 'unfiltered' : 'finite',
+ // acos has an ulp or inherited accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].acosInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('acos', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acos.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acos.spec.ts
index 5755c07905..46089d8576 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acos.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acos.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'acos' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn acos(e: T ) -> T
Returns the arc cosine of e. Component-wise when T is a vector.
@@ -9,48 +9,33 @@ Returns the arc cosine of e. Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { linearRange, fullF32Range, fullF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { d } from './acos.cache.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
export const g = makeTestGroup(GPUTest);
-const f32_inputs = [
- ...linearRange(-1, 1, 100), // acos is defined on [-1, 1]
- ...fullF32Range(),
-];
-
-const f16_inputs = [
- ...linearRange(-1, 1, 100), // acos is defined on [-1, 1]
- ...fullF16Range(),
-];
-
-export const d = makeCaseCache('acos', {
- f32_const: () => {
- return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.acosInterval);
- },
- f32_non_const: () => {
- return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.acosInterval);
- },
- f16_const: () => {
- return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.acosInterval);
- },
- f16_non_const: () => {
- return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.acosInterval);
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract_const');
+ await run(
+ t,
+ abstractFloatBuiltin('acos'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -60,7 +45,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('acos'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('acos'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -74,5 +59,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('acos'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('acos'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acosh.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acosh.cache.ts
new file mode 100644
index 0000000000..587131140d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acosh.cache.ts
@@ -0,0 +1,24 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { biasedRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]_[non_]const
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateScalarToIntervalCases(
+ [...biasedRange(1, 2, 100), ...FP[trait].scalarRange()], // x near 1 can be problematic to implement
+ nonConst ? 'unfiltered' : 'finite',
+ // acosh has an inherited accuracy, so is only expected to be as accurate as f32
+ ...FP[trait !== 'abstract' ? trait : 'f32'].acoshIntervals
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('acosh', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acosh.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acosh.spec.ts
index cc78ce3eee..531a2479c6 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acosh.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acosh.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'acosh' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn acosh(e: T ) -> T
Returns the hyperbolic arc cosine of e. The result is 0 when e < 1.
@@ -13,47 +13,33 @@ Note: The result is not mathematically meaningful when e < 1.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { biasedRange, fullF32Range, fullF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { d } from './acosh.cache.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
export const g = makeTestGroup(GPUTest);
-const f32_inputs = [
- ...biasedRange(1, 2, 100), // x near 1 can be problematic to implement
- ...fullF32Range(),
-];
-const f16_inputs = [
- ...biasedRange(1, 2, 100), // x near 1 can be problematic to implement
- ...fullF16Range(),
-];
-
-export const d = makeCaseCache('acosh', {
- f32_const: () => {
- return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', ...FP.f32.acoshIntervals);
- },
- f32_non_const: () => {
- return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', ...FP.f32.acoshIntervals);
- },
- f16_const: () => {
- return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', ...FP.f16.acoshIntervals);
- },
- f16_non_const: () => {
- return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', ...FP.f16.acoshIntervals);
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract_const');
+ await run(
+ t,
+ abstractFloatBuiltin('acosh'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -63,7 +49,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('acosh'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('acosh'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -77,5 +63,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('acosh'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('acosh'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/all.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/all.spec.ts
index 9a2938c1d5..74e072703d 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/all.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/all.spec.ts
@@ -10,15 +10,7 @@ Returns true if each component of e is true if e is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import {
- False,
- True,
- TypeBool,
- TypeVec,
- vec2,
- vec3,
- vec4,
-} from '../../../../../util/conversion.js';
+import { False, True, Type, vec2, vec3, vec4 } from '../../../../../util/conversion.js';
import { allInputSources, run } from '../../expression.js';
import { builtin } from './builtin.js';
@@ -36,14 +28,14 @@ g.test('bool')
.fn(async t => {
const overloads = {
scalar: {
- type: TypeBool,
+ type: Type.bool,
cases: [
{ input: False, expected: False },
{ input: True, expected: True },
],
},
vec2: {
- type: TypeVec(2, TypeBool),
+ type: Type.vec(2, Type.bool),
cases: [
{ input: vec2(False, False), expected: False },
{ input: vec2(True, False), expected: False },
@@ -52,7 +44,7 @@ g.test('bool')
],
},
vec3: {
- type: TypeVec(3, TypeBool),
+ type: Type.vec(3, Type.bool),
cases: [
{ input: vec3(False, False, False), expected: False },
{ input: vec3(True, False, False), expected: False },
@@ -65,7 +57,7 @@ g.test('bool')
],
},
vec4: {
- type: TypeVec(4, TypeBool),
+ type: Type.vec(4, Type.bool),
cases: [
{ input: vec4(False, False, False, False), expected: False },
{ input: vec4(False, True, False, False), expected: False },
@@ -88,5 +80,5 @@ g.test('bool')
};
const overload = overloads[t.params.overload];
- await run(t, builtin('all'), [overload.type], TypeBool, t.params, overload.cases);
+ await run(t, builtin('all'), [overload.type], Type.bool, t.params, overload.cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/any.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/any.spec.ts
index 19ed7d186f..43c599e2aa 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/any.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/any.spec.ts
@@ -10,15 +10,7 @@ Returns true if any component of e is true if e is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import {
- False,
- True,
- TypeBool,
- TypeVec,
- vec2,
- vec3,
- vec4,
-} from '../../../../../util/conversion.js';
+import { False, True, Type, vec2, vec3, vec4 } from '../../../../../util/conversion.js';
import { allInputSources, run } from '../../expression.js';
import { builtin } from './builtin.js';
@@ -36,14 +28,14 @@ g.test('bool')
.fn(async t => {
const overloads = {
scalar: {
- type: TypeBool,
+ type: Type.bool,
cases: [
{ input: False, expected: False },
{ input: True, expected: True },
],
},
vec2: {
- type: TypeVec(2, TypeBool),
+ type: Type.vec(2, Type.bool),
cases: [
{ input: vec2(False, False), expected: False },
{ input: vec2(True, False), expected: True },
@@ -52,7 +44,7 @@ g.test('bool')
],
},
vec3: {
- type: TypeVec(3, TypeBool),
+ type: Type.vec(3, Type.bool),
cases: [
{ input: vec3(False, False, False), expected: False },
{ input: vec3(True, False, False), expected: True },
@@ -65,7 +57,7 @@ g.test('bool')
],
},
vec4: {
- type: TypeVec(4, TypeBool),
+ type: Type.vec(4, Type.bool),
cases: [
{ input: vec4(False, False, False, False), expected: False },
{ input: vec4(False, True, False, False), expected: True },
@@ -88,5 +80,5 @@ g.test('bool')
};
const overload = overloads[t.params.overload];
- await run(t, builtin('any'), [overload.type], TypeBool, t.params, overload.cases);
+ await run(t, builtin('any'), [overload.type], Type.bool, t.params, overload.cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asin.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asin.cache.ts
new file mode 100644
index 0000000000..d9e6280e0d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asin.cache.ts
@@ -0,0 +1,24 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]_[non_]const
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateScalarToIntervalCases(
+ [...linearRange(-1, 1, 100), ...FP[trait].scalarRange()], // asin is defined on [-1, 1]
+ nonConst ? 'unfiltered' : 'finite',
+ // asin has an ulp or inherited accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].asinInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('asin', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asin.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asin.spec.ts
index 8d18ebb303..f368c3838c 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asin.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asin.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'asin' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn asin(e: T ) -> T
Returns the arc sine of e. Component-wise when T is a vector.
@@ -9,48 +9,33 @@ Returns the arc sine of e. Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { linearRange, fullF32Range, fullF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { d } from './asin.cache.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
export const g = makeTestGroup(GPUTest);
-const f32_inputs = [
- ...linearRange(-1, 1, 100), // asin is defined on [-1, 1]
- ...fullF32Range(),
-];
-
-const f16_inputs = [
- ...linearRange(-1, 1, 100), // asin is defined on [-1, 1]
- ...fullF16Range(),
-];
-
-export const d = makeCaseCache('asin', {
- f32_const: () => {
- return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.asinInterval);
- },
- f32_non_const: () => {
- return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.asinInterval);
- },
- f16_const: () => {
- return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.asinInterval);
- },
- f16_non_const: () => {
- return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.asinInterval);
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract_const');
+ await run(
+ t,
+ abstractFloatBuiltin('asin'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -60,7 +45,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('asin'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('asin'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -74,5 +59,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('asin'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('asin'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asinh.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asinh.cache.ts
new file mode 100644
index 0000000000..4ee66f1ea6
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asinh.cache.ts
@@ -0,0 +1,18 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .map(trait => ({
+ [`${trait}`]: () => {
+ return FP[trait].generateScalarToIntervalCases(
+ FP[trait].scalarRange(),
+ 'unfiltered',
+ // asinh has an inherited accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].asinhInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('asinh', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asinh.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asinh.spec.ts
index 9a8384e090..60867a9bce 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asinh.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asinh.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'sinh' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn asinh(e: T ) -> T
Returns the hyperbolic arc sine of e.
@@ -12,32 +12,33 @@ Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { d } from './asinh.cache.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('asinh', {
- f32: () => {
- return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.asinhInterval);
- },
- f16: () => {
- return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'unfiltered', FP.f16.asinhInterval);
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float test`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractFloatBuiltin('asinh'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -47,7 +48,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('asinh'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('asinh'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -61,5 +62,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('asinh'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('asinh'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan.cache.ts
new file mode 100644
index 0000000000..e39e40448e
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan.cache.ts
@@ -0,0 +1,25 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+const known_values = [-Math.sqrt(3), -1, -1 / Math.sqrt(3), 0, 1, 1 / Math.sqrt(3), Math.sqrt(3)];
+
+// Cases: [f32|f16|abstract]_[non_]const
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateScalarToIntervalCases(
+ [...known_values, ...FP[trait].scalarRange()],
+ nonConst ? 'unfiltered' : 'finite',
+ // atan has an ulp accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].atanInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('atan', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan.spec.ts
index 3d0d3e6725..824a7cd0b5 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'atan' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn atan(e: T ) -> T
Returns the arc tangent of e. Component-wise when T is a vector.
@@ -10,43 +10,33 @@ Returns the arc tangent of e. Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { d } from './atan.cache.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
export const g = makeTestGroup(GPUTest);
-const known_values = [-Math.sqrt(3), -1, -1 / Math.sqrt(3), 0, 1, 1 / Math.sqrt(3), Math.sqrt(3)];
-
-const f32_inputs = [...known_values, ...fullF32Range()];
-const f16_inputs = [...known_values, ...fullF16Range()];
-
-export const d = makeCaseCache('atan', {
- f32_const: () => {
- return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.atanInterval);
- },
- f32_non_const: () => {
- return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.atanInterval);
- },
- f16_const: () => {
- return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.atanInterval);
- },
- f16_non_const: () => {
- return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.atanInterval);
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract_const');
+ await run(
+ t,
+ abstractFloatBuiltin('atan'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -62,7 +52,7 @@ TODO(#792): Decide what the ground-truth is for these tests. [1]
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('atan'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('atan'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -76,5 +66,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('atan'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('atan'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan2.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan2.cache.ts
new file mode 100644
index 0000000000..be25c2dffb
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan2.cache.ts
@@ -0,0 +1,35 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]_[non_]const
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ // Using sparse range since there are N^2 cases being generated, and also including extra values
+ // around 0, where there is a discontinuity that implementations may behave badly at.
+ const numeric_range = [
+ ...FP[trait].sparseScalarRange(),
+ ...linearRange(
+ FP[trait].constants().negative.max,
+ FP[trait].constants().positive.min,
+ 10
+ ),
+ ];
+ return FP[trait].generateScalarPairToIntervalCases(
+ numeric_range,
+ numeric_range,
+ nonConst ? 'unfiltered' : 'finite',
+ // atan2 has an ulp accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].atan2Interval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('atan2', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan2.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan2.spec.ts
index fbace73dd2..067d1fdc66 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan2.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan2.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'atan2' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn atan2(e1: T ,e2: T ) -> T
Returns the arc tangent of e1 over e2. Component-wise when T is a vector.
@@ -9,47 +9,33 @@ Returns the arc tangent of e1 over e2. Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { linearRange, sparseF32Range, sparseF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { d } from './atan2.cache.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
export const g = makeTestGroup(GPUTest);
-const cases = (['f32', 'f16'] as const)
- .flatMap(kind =>
- ([true, false] as const).map(nonConst => ({
- [`${kind}_${nonConst ? 'non_const' : 'const'}`]: () => {
- const fp = FP[kind];
- // Using sparse range since there are N^2 cases being generated, and also including extra values
- // around 0, where there is a discontinuity that implementations may behave badly at.
- const numeric_range = [
- ...(kind === 'f32' ? sparseF32Range() : sparseF16Range()),
- ...linearRange(fp.constants().negative.max, fp.constants().positive.min, 10),
- ];
- return fp.generateScalarPairToIntervalCases(
- numeric_range,
- numeric_range,
- nonConst ? 'unfiltered' : 'finite',
- fp.atan2Interval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('atan2', cases);
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get(`abstract_const`);
+ await run(
+ t,
+ abstractFloatBuiltin('atan2'),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -65,7 +51,7 @@ TODO(#792): Decide what the ground-truth is for these tests. [1]
)
.fn(async t => {
const cases = await d.get(`f32_${t.params.inputSource === 'const' ? 'const' : 'non_const'}`);
- await run(t, builtin('atan2'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('atan2'), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -79,5 +65,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(`f16_${t.params.inputSource === 'const' ? 'const' : 'non_const'}`);
- await run(t, builtin('atan2'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('atan2'), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atanh.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atanh.cache.ts
new file mode 100644
index 0000000000..bcd000bf61
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atanh.cache.ts
@@ -0,0 +1,32 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { biasedRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]_[non_]const
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateScalarToIntervalCases(
+ [
+ // discontinuity at x = -1
+ ...biasedRange(FP[trait].constants().negative.less_than_one, -0.9, 20),
+ -1,
+ // discontinuity at x = 1
+ ...biasedRange(FP[trait].constants().positive.less_than_one, 0.9, 20),
+ 1,
+ ...FP[trait].scalarRange(),
+ ],
+ nonConst ? 'unfiltered' : 'finite',
+ // atanh has an inherited accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].atanhInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('atanh', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atanh.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atanh.spec.ts
index 90f322a7ea..644efafd2f 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atanh.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atanh.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'atanh' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn atanh(e: T ) -> T
Returns the hyperbolic arc tangent of e. The result is 0 when abs(e) ≥ 1.
@@ -12,54 +12,33 @@ Note: The result is not mathematically meaningful when abs(e) >= 1.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { kValue } from '../../../../../util/constants.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { biasedRange, fullF32Range, fullF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { d } from './atanh.cache.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
export const g = makeTestGroup(GPUTest);
-const f32_inputs = [
- ...biasedRange(kValue.f32.negative.less_than_one, -0.9, 20), // discontinuity at x = -1
- -1,
- ...biasedRange(kValue.f32.positive.less_than_one, 0.9, 20), // discontinuity at x = 1
- 1,
- ...fullF32Range(),
-];
-const f16_inputs = [
- ...biasedRange(kValue.f16.negative.less_than_one, -0.9, 20), // discontinuity at x = -1
- -1,
- ...biasedRange(kValue.f16.positive.less_than_one, 0.9, 20), // discontinuity at x = 1
- 1,
- ...fullF16Range(),
-];
-
-export const d = makeCaseCache('atanh', {
- f32_const: () => {
- return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.atanhInterval);
- },
- f32_non_const: () => {
- return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.atanhInterval);
- },
- f16_const: () => {
- return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.atanhInterval);
- },
- f16_non_const: () => {
- return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.atanhInterval);
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract_const');
+ await run(
+ t,
+ abstractFloatBuiltin('atanh'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -69,7 +48,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('atanh'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('atanh'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -83,5 +62,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('atanh'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('atanh'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAdd.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAdd.spec.ts
index 37d3ce5292..187a555449 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAdd.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAdd.spec.ts
@@ -35,7 +35,7 @@ fn atomicAdd(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
u
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
@@ -72,7 +72,7 @@ fn atomicAdd(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
u
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
// Allocate one extra element to ensure it doesn't get modified
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAnd.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAnd.spec.ts
index ed5cfa84a3..ad05bd851d 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAnd.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAnd.spec.ts
@@ -38,7 +38,7 @@ fn atomicAnd(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
.combine('mapId', keysOf(kMapId))
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
@@ -91,7 +91,7 @@ fn atomicAnd(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
.combine('mapId', keysOf(kMapId))
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
const numInvocations = t.params.workgroupSize;
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicCompareExchangeWeak.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicCompareExchangeWeak.spec.ts
index 2556bb744b..79e0597af6 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicCompareExchangeWeak.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicCompareExchangeWeak.spec.ts
@@ -48,7 +48,7 @@ struct __atomic_compare_exchange_result<T> {
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
.combine('mapId', keysOf(kMapId))
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(async t => {
const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
@@ -187,7 +187,7 @@ struct __atomic_compare_exchange_result<T> {
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
.combine('mapId', keysOf(kMapId))
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(async t => {
const numInvocations = t.params.workgroupSize;
@@ -333,12 +333,17 @@ struct __atomic_compare_exchange_result<T> {
.params(u =>
u
.combine('workgroupSize', onlyWorkgroupSizes) //
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(async t => {
const numInvocations = t.params.workgroupSize;
const scalarType = t.params.scalarType;
+ t.skipIf(
+ numInvocations > t.device.limits.maxComputeWorkgroupSizeX,
+ `${numInvocations} > maxComputeWorkgroupSizeX(${t.device.limits.maxComputeWorkgroupSizeX})`
+ );
+
// Number of times each workgroup attempts to exchange the same value to the same memory address
const numWrites = 4;
@@ -550,12 +555,17 @@ struct __atomic_compare_exchange_result<T> {
.params(u =>
u
.combine('workgroupSize', onlyWorkgroupSizes) //
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(async t => {
const numInvocations = t.params.workgroupSize;
const scalarType = t.params.scalarType;
+ t.skipIf(
+ numInvocations > t.device.limits.maxComputeWorkgroupSizeX,
+ `${numInvocations} > maxComputeWorkgroupSizeX(${t.device.limits.maxComputeWorkgroupSizeX})`
+ );
+
// Number of times each workgroup attempts to exchange the same value to the same memory address
const numWrites = 4;
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicExchange.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicExchange.spec.ts
index 540ac16b07..00b6ddb7e3 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicExchange.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicExchange.spec.ts
@@ -26,7 +26,7 @@ fn atomicExchange(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
.combine('mapId', keysOf(kMapId))
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
@@ -125,7 +125,7 @@ fn atomicLoad(atomic_ptr: ptr<AS, atomic<T>, read_write>) -> T
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
.combine('mapId', keysOf(kMapId))
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
const numInvocations = t.params.workgroupSize;
@@ -236,7 +236,7 @@ fn atomicExchange(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
.combine('mapId', keysOf(kMapId))
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(async t => {
const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
@@ -350,7 +350,7 @@ fn atomicLoad(atomic_ptr: ptr<AS, atomic<T>, read_write>) -> T
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
.combine('mapId', keysOf(kMapId))
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(async t => {
const numInvocations = t.params.workgroupSize;
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicLoad.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicLoad.spec.ts
index 2aac7bb9b9..23ca127cae 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicLoad.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicLoad.spec.ts
@@ -26,7 +26,7 @@ fn atomicLoad(atomic_ptr: ptr<AS, atomic<T>, read_write>) -> T
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
.combine('mapId', keysOf(kMapId))
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
@@ -117,7 +117,7 @@ fn atomicLoad(atomic_ptr: ptr<AS, atomic<T>, read_write>) -> T
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
.combine('mapId', keysOf(kMapId))
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
const numInvocations = t.params.workgroupSize;
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMax.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMax.spec.ts
index 066d673018..86bb8b460d 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMax.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMax.spec.ts
@@ -35,7 +35,7 @@ fn atomicMax(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
u
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
@@ -72,7 +72,7 @@ fn atomicMax(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
u
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
// Allocate one extra element to ensure it doesn't get modified
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMin.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMin.spec.ts
index ad880c4182..d9a42d15ee 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMin.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMin.spec.ts
@@ -35,7 +35,7 @@ fn atomicMin(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
u
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
// Allocate one extra element to ensure it doesn't get modified
@@ -71,7 +71,7 @@ fn atomicMin(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
u
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
// Allocate one extra element to ensure it doesn't get modified
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicOr.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicOr.spec.ts
index 3892d41b38..8ddba24385 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicOr.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicOr.spec.ts
@@ -38,7 +38,7 @@ fn atomicOr(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
.combine('mapId', keysOf(kMapId))
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
@@ -90,7 +90,7 @@ fn atomicOr(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
.combine('mapId', keysOf(kMapId))
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
const numInvocations = t.params.workgroupSize;
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicStore.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicStore.spec.ts
index 18ff72975d..4d226e312b 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicStore.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicStore.spec.ts
@@ -32,7 +32,7 @@ fn atomicStore(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T)
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
.combine('mapId', keysOf(kMapId))
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
@@ -72,7 +72,7 @@ fn atomicStore(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T)
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
.combine('mapId', keysOf(kMapId))
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
const numInvocations = t.params.workgroupSize;
@@ -117,7 +117,7 @@ one of the values written.
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
.combine('mapId', keysOf(kMapId))
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(async t => {
const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
@@ -210,7 +210,7 @@ one of the values written.
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
.combine('mapId', keysOf(kMapId))
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(async t => {
const numInvocations = t.params.workgroupSize;
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicSub.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicSub.spec.ts
index 6cea190299..8fdced1df2 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicSub.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicSub.spec.ts
@@ -35,7 +35,7 @@ fn atomicSub(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
u
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
@@ -72,7 +72,7 @@ fn atomicSub(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
u
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
// Allocate one extra element to ensure it doesn't get modified
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicXor.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicXor.spec.ts
index 99192fd9fe..2240043590 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicXor.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicXor.spec.ts
@@ -38,7 +38,7 @@ fn atomicXor(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
.combine('mapId', keysOf(kMapId))
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
@@ -91,7 +91,7 @@ fn atomicXor(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
.combine('mapId', keysOf(kMapId))
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
const numInvocations = t.params.workgroupSize;
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/harness.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/harness.ts
index ed02467f80..e12cf537cd 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/harness.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/harness.ts
@@ -25,15 +25,14 @@ export const kMapId = {
},
};
-export function typedArrayCtor(scalarType: string): TypedArrayBufferViewConstructor {
+export function typedArrayCtor(
+ scalarType: 'u32' | 'i32'
+): TypedArrayBufferViewConstructor<Uint32Array | Int32Array> {
switch (scalarType) {
case 'u32':
return Uint32Array;
case 'i32':
return Int32Array;
- default:
- assert(false, 'Atomic variables can only by u32 or i32');
- return Uint8Array;
}
}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/bitcast.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/bitcast.cache.ts
new file mode 100644
index 0000000000..75dfea0086
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/bitcast.cache.ts
@@ -0,0 +1,837 @@
+import { assert } from '../../../../../../common/util/util.js';
+import { Comparator, alwaysPass, anyOf } from '../../../../../util/compare.js';
+import { kBit, kValue } from '../../../../../util/constants.js';
+import {
+ ScalarValue,
+ VectorValue,
+ f16,
+ f32,
+ i32,
+ toVector,
+ u32,
+ abstractFloat,
+ abstractInt,
+} from '../../../../../util/conversion.js';
+import { FP, FPInterval } from '../../../../../util/floating_point.js';
+import {
+ cartesianProduct,
+ fullI32Range,
+ fullU32Range,
+ isFiniteF16,
+ isFiniteF32,
+ isSubnormalNumberF16,
+ isSubnormalNumberF32,
+ linearRange,
+ scalarF16Range,
+ scalarF32Range,
+} from '../../../../../util/math.js';
+import {
+ reinterpretF16AsU16,
+ reinterpretF32AsI32,
+ reinterpretF32AsU32,
+ reinterpretI32AsF32,
+ reinterpretI32AsU32,
+ reinterpretU16AsF16,
+ reinterpretU32AsF32,
+ reinterpretU32AsI32,
+} from '../../../../../util/reinterpret.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+const numNaNs = 11;
+const f32InfAndNaNInU32: number[] = [
+ // Cover NaNs evenly in integer space.
+ // The positive NaN with the lowest integer representation is the integer
+ // for infinity, plus one.
+ // The positive NaN with the highest integer representation is i32.max (!)
+ ...linearRange(kBit.f32.positive.infinity + 1, kBit.i32.positive.max, numNaNs),
+ // The negative NaN with the lowest integer representation is the integer
+ // for negative infinity, plus one.
+ // The negative NaN with the highest integer representation is u32.max (!)
+ ...linearRange(kBit.f32.negative.infinity + 1, kBit.u32.max, numNaNs),
+ kBit.f32.positive.infinity,
+ kBit.f32.negative.infinity,
+];
+const f32InfAndNaNInF32 = f32InfAndNaNInU32.map(u => reinterpretU32AsF32(u));
+const f32InfAndNaNInI32 = f32InfAndNaNInU32.map(u => reinterpretU32AsI32(u));
+
+const f32ZerosInU32 = [0, kBit.f32.negative.zero];
+const f32ZerosInF32 = f32ZerosInU32.map(u => reinterpretU32AsF32(u));
+const f32ZerosInI32 = f32ZerosInU32.map(u => reinterpretU32AsI32(u));
+const f32ZerosInterval: FPInterval = new FPInterval('f32', -0.0, 0.0);
+
+// f32FiniteRange is a list of finite f32s. fullF32Range() already
+// has +0, we only need to add -0.
+const f32FiniteRange: number[] = [...scalarF32Range(), kValue.f32.negative.zero];
+const f32RangeWithInfAndNaN: number[] = [...f32FiniteRange, ...f32InfAndNaNInF32];
+
+// Type.f16 values, finite, Inf/NaN, and zeros. Represented in float and u16.
+const f16FiniteInF16: number[] = [...scalarF16Range(), kValue.f16.negative.zero];
+const f16FiniteInU16: number[] = f16FiniteInF16.map(u => reinterpretF16AsU16(u));
+
+const f16InfAndNaNInU16: number[] = [
+ // Cover NaNs evenly in integer space.
+ // The positive NaN with the lowest integer representation is the integer
+ // for infinity, plus one.
+ // The positive NaN with the highest integer representation is u16 0x7fff i.e. 32767.
+ ...linearRange(kBit.f16.positive.infinity + 1, 32767, numNaNs).map(v => Math.ceil(v)),
+ // The negative NaN with the lowest integer representation is the integer
+ // for negative infinity, plus one.
+ // The negative NaN with the highest integer representation is u16 0xffff i.e. 65535
+ ...linearRange(kBit.f16.negative.infinity + 1, 65535, numNaNs).map(v => Math.floor(v)),
+ kBit.f16.positive.infinity,
+ kBit.f16.negative.infinity,
+];
+const f16InfAndNaNInF16 = f16InfAndNaNInU16.map(u => reinterpretU16AsF16(u));
+
+const f16ZerosInU16 = [kBit.f16.negative.zero, 0];
+
+// f16 interval that match +/-0.0.
+const f16ZerosInterval: FPInterval = new FPInterval('f16', -0.0, 0.0);
+
+/**
+ * @returns an u32 whose lower and higher 16bits are the two elements of the
+ * given array of two u16 respectively, in little-endian.
+ */
+function u16x2ToU32(u16x2: readonly number[]): number {
+ assert(u16x2.length === 2);
+ // Create a DataView with 4 bytes buffer.
+ const buffer = new ArrayBuffer(4);
+ const view = new DataView(buffer);
+ // Enforce little-endian.
+ view.setUint16(0, u16x2[0], true);
+ view.setUint16(2, u16x2[1], true);
+ return view.getUint32(0, true);
+}
+
+/**
+ * @returns an array of two u16, respectively the lower and higher 16bits of
+ * given u32 in little-endian.
+ */
+function u32ToU16x2(u32: number): number[] {
+ // Create a DataView with 4 bytes buffer.
+ const buffer = new ArrayBuffer(4);
+ const view = new DataView(buffer);
+ // Enforce little-endian.
+ view.setUint32(0, u32, true);
+ return [view.getUint16(0, true), view.getUint16(2, true)];
+}
+
+/**
+ * @returns a vec2<f16> from an array of two u16, each reinterpreted as f16.
+ */
+function u16x2ToVec2F16(u16x2: number[]): VectorValue {
+ assert(u16x2.length === 2);
+ return toVector(u16x2.map(reinterpretU16AsF16), f16);
+}
+
+/**
+ * @returns a vec4<f16> from an array of four u16, each reinterpreted as f16.
+ */
+function u16x4ToVec4F16(u16x4: number[]): VectorValue {
+ assert(u16x4.length === 4);
+ return toVector(u16x4.map(reinterpretU16AsF16), f16);
+}
+
+/**
+ * @returns true if and only if a given u32 can bitcast to a vec2<f16> with all elements
+ * being finite f16 values.
+ */
+function canU32BitcastToFiniteVec2F16(u32: number): boolean {
+ return u32ToU16x2(u32)
+ .map(u16 => isFiniteF16(reinterpretU16AsF16(u16)))
+ .reduce((a, b) => a && b, true);
+}
+
+/**
+ * @returns an array of N elements with the i-th element being an array of len elements
+ * [a_i, a_((i+1)%N), ..., a_((i+len-1)%N)], for the input array of N element [a_1, ... a_N]
+ * and the given len. For example, slidingSlice([1, 2, 3], 2) result in
+ * [[1, 2], [2, 3], [3, 1]].
+ * This helper function is used for generating vector cases from scalar values array.
+ */
+function slidingSlice(input: number[], len: number) {
+ const result: number[][] = [];
+ for (let i = 0; i < input.length; i++) {
+ const sub: number[] = [];
+ for (let j = 0; j < len; j++) {
+ sub.push(input[(i + j) % input.length]);
+ }
+ result.push(sub);
+ }
+ return result;
+}
+
+// vec2<f16> interesting (zeros, Inf, and NaN) values for testing cases.
+// vec2<f16> values that has at least one Inf/NaN f16 element, reinterpreted as u32/i32.
+const f16Vec2InfAndNaNInU32 = [
+ ...cartesianProduct(f16InfAndNaNInU16, [...f16InfAndNaNInU16, ...f16FiniteInU16]),
+ ...cartesianProduct(f16FiniteInU16, f16InfAndNaNInU16),
+].map(u16x2ToU32);
+const f16Vec2InfAndNaNInI32 = f16Vec2InfAndNaNInU32.map(u => reinterpretU32AsI32(u));
+// vec2<f16> values with two f16 0.0 element, reinterpreted as u32/i32.
+const f16Vec2ZerosInU32 = cartesianProduct(f16ZerosInU16, f16ZerosInU16).map(u16x2ToU32);
+const f16Vec2ZerosInI32 = f16Vec2ZerosInU32.map(u => reinterpretU32AsI32(u));
+
+// i32/u32/f32 range for bitcasting to vec2<f16>
+// u32 values for bitcasting to vec2<f16> finite, Inf, and NaN.
+const u32RangeForF16Vec2FiniteInfNaN: number[] = [
+ ...fullU32Range(),
+ ...f16Vec2ZerosInU32,
+ ...f16Vec2InfAndNaNInU32,
+];
+// u32 values for bitcasting to finite only vec2<f16>, used for constant evaluation.
+const u32RangeForF16Vec2Finite: number[] = u32RangeForF16Vec2FiniteInfNaN.filter(
+ canU32BitcastToFiniteVec2F16
+);
+// i32 values for bitcasting to vec2<f16> finite, zeros, Inf, and NaN.
+const i32RangeForF16Vec2FiniteInfNaN: number[] = [
+ ...fullI32Range(),
+ ...f16Vec2ZerosInI32,
+ ...f16Vec2InfAndNaNInI32,
+];
+// i32 values for bitcasting to finite only vec2<f16>, used for constant evaluation.
+const i32RangeForF16Vec2Finite: number[] = i32RangeForF16Vec2FiniteInfNaN.filter(u =>
+ canU32BitcastToFiniteVec2F16(reinterpretI32AsU32(u))
+);
+// f32 values with finite/Inf/NaN f32, for bitcasting to vec2<f16> finite, zeros, Inf, and NaN.
+const f32RangeWithInfAndNaNForF16Vec2FiniteInfNaN: number[] = [
+ ...f32RangeWithInfAndNaN,
+ ...u32RangeForF16Vec2FiniteInfNaN.map(reinterpretU32AsF32),
+];
+// Finite f32 values for bitcasting to finite only vec2<f16>, used for constant evaluation.
+const f32FiniteRangeForF16Vec2Finite: number[] = f32RangeWithInfAndNaNForF16Vec2FiniteInfNaN
+ .filter(isFiniteF32)
+ .filter(u => canU32BitcastToFiniteVec2F16(reinterpretF32AsU32(u)));
+
+// vec2<f16> cases for bitcasting to i32/u32/f32, by combining f16 values into pairs
+const f16Vec2FiniteInU16x2 = slidingSlice(f16FiniteInU16, 2);
+const f16Vec2FiniteInfNanInU16x2 = slidingSlice([...f16FiniteInU16, ...f16InfAndNaNInU16], 2);
+// vec4<f16> cases for bitcasting to vec2<i32/u32/f32>, by combining f16 values 4-by-4
+const f16Vec2FiniteInU16x4 = slidingSlice(f16FiniteInU16, 4);
+const f16Vec2FiniteInfNanInU16x4 = slidingSlice([...f16FiniteInU16, ...f16InfAndNaNInU16], 4);
+
+// alwaysPass comparator for i32/u32/f32 cases. For f32/f16 we also use unbound interval, which
+// allow per-element unbounded expectation for vector.
+const anyF32 = alwaysPass('any f32');
+const anyI32 = alwaysPass('any i32');
+const anyU32 = alwaysPass('any u32');
+
+// Unbounded FPInterval
+const f32UnboundedInterval = FP.f32.constants().unboundedInterval;
+const f16UnboundedInterval = FP.f16.constants().unboundedInterval;
+
+// i32 and u32 cases for bitcasting to f32.
+// i32 cases for bitcasting to f32 finite, zeros, Inf, and NaN.
+const i32RangeForF32FiniteInfNaN: number[] = [
+ ...fullI32Range(),
+ ...f32ZerosInI32,
+ ...f32InfAndNaNInI32,
+];
+// i32 cases for bitcasting to f32 finite only.
+const i32RangeForF32Finite: number[] = i32RangeForF32FiniteInfNaN.filter(i =>
+ isFiniteF32(reinterpretI32AsF32(i))
+);
+// u32 cases for bitcasting to f32 finite, zeros, Inf, and NaN.
+const u32RangeForF32FiniteInfNaN: number[] = [
+ ...fullU32Range(),
+ ...f32ZerosInU32,
+ ...f32InfAndNaNInU32,
+];
+// u32 cases for bitcasting to f32 finite only.
+const u32RangeForF32Finite: number[] = u32RangeForF32FiniteInfNaN.filter(u =>
+ isFiniteF32(reinterpretU32AsF32(u))
+);
+
+/**
+ * @returns a Comparator for checking if a f32 value is a valid
+ * bitcast conversion from f32.
+ */
+function bitcastF32ToF32Comparator(f: number): Comparator {
+ if (!isFiniteF32(f)) return anyF32;
+ const acceptable: number[] = [f, ...(isSubnormalNumberF32(f) ? f32ZerosInF32 : [])];
+ return anyOf(...acceptable.map(f32));
+}
+
+/**
+ * @returns a Comparator for checking if a u32 value is a valid
+ * bitcast conversion from f32.
+ */
+function bitcastF32ToU32Comparator(f: number): Comparator {
+ if (!isFiniteF32(f)) return anyU32;
+ const acceptable: number[] = [
+ reinterpretF32AsU32(f),
+ ...(isSubnormalNumberF32(f) ? f32ZerosInU32 : []),
+ ];
+ return anyOf(...acceptable.map(u32));
+}
+
+/**
+ * @returns a Comparator for checking if a i32 value is a valid
+ * bitcast conversion from f32.
+ */
+function bitcastF32ToI32Comparator(f: number): Comparator {
+ if (!isFiniteF32(f)) return anyI32;
+ const acceptable: number[] = [
+ reinterpretF32AsI32(f),
+ ...(isSubnormalNumberF32(f) ? f32ZerosInI32 : []),
+ ];
+ return anyOf(...acceptable.map(i32));
+}
+
+/**
+ * @returns a Comparator for checking if a f32 value is a valid
+ * bitcast conversion from i32.
+ */
+function bitcastI32ToF32Comparator(i: number): Comparator {
+ const f: number = reinterpretI32AsF32(i);
+ if (!isFiniteF32(f)) return anyI32;
+ // Positive or negative zero bit pattern map to any zero.
+ if (f32ZerosInI32.includes(i)) return anyOf(...f32ZerosInF32.map(f32));
+ const acceptable: number[] = [f, ...(isSubnormalNumberF32(f) ? f32ZerosInF32 : [])];
+ return anyOf(...acceptable.map(f32));
+}
+
+/**
+ * @returns a Comparator for checking if a f32 value is a valid
+ * bitcast conversion from u32.
+ */
+function bitcastU32ToF32Comparator(u: number): Comparator {
+ const f: number = reinterpretU32AsF32(u);
+ if (!isFiniteF32(f)) return anyU32;
+ // Positive or negative zero bit pattern map to any zero.
+ if (f32ZerosInU32.includes(u)) return anyOf(...f32ZerosInF32.map(f32));
+ const acceptable: number[] = [f, ...(isSubnormalNumberF32(f) ? f32ZerosInF32 : [])];
+ return anyOf(...acceptable.map(f32));
+}
+
+/**
+ * @returns an array of expected f16 FPInterval for the given bitcasted f16 value, which may be
+ * subnormal, Inf, or NaN. Test cases that bitcasted to vector of f16 use this function to get
+ * per-element expectation and build vector expectation using cartesianProduct.
+ */
+function generateF16ExpectationIntervals(bitcastedF16Value: number): FPInterval[] {
+ // If the bitcasted f16 value is inf or nan, the result is unbounded
+ if (!isFiniteF16(bitcastedF16Value)) {
+ return [f16UnboundedInterval];
+ }
+ // If the casted f16 value is +/-0.0, the result can be one of both. Note that in JS -0.0 === 0.0.
+ if (bitcastedF16Value === 0.0) {
+ return [f16ZerosInterval];
+ }
+ const exactInterval = FP.f16.toInterval(bitcastedF16Value);
+ // If the casted f16 value is subnormal, it also may be flushed to +/-0.0.
+ return [exactInterval, ...(isSubnormalNumberF16(bitcastedF16Value) ? [f16ZerosInterval] : [])];
+}
+
+/**
+ * @returns a Comparator for checking if a f16 value is a valid
+ * bitcast conversion from f16.
+ */
+function bitcastF16ToF16Comparator(f: number): Comparator {
+ if (!isFiniteF16(f)) return anyOf(f16UnboundedInterval);
+ return anyOf(...generateF16ExpectationIntervals(f));
+}
+
+/**
+ * @returns a Comparator for checking if a vec2<f16> is a valid bitcast
+ * conversion from u32.
+ */
+function bitcastU32ToVec2F16Comparator(u: number): Comparator {
+ const bitcastedVec2F16InU16x2 = u32ToU16x2(u).map(reinterpretU16AsF16);
+ // Generate expection for vec2 f16 result, by generating expected intervals for each elements and
+ // then do cartesian product.
+ const expectedIntervalsCombination = cartesianProduct(
+ ...bitcastedVec2F16InU16x2.map(generateF16ExpectationIntervals)
+ );
+ return anyOf(...expectedIntervalsCombination);
+}
+
+/**
+ * @returns a Comparator for checking if a vec2<f16> value is a valid
+ * bitcast conversion from i32.
+ */
+function bitcastI32ToVec2F16Comparator(i: number): Comparator {
+ const bitcastedVec2F16InU16x2 = u32ToU16x2(reinterpretI32AsU32(i)).map(reinterpretU16AsF16);
+ // Generate expection for vec2 f16 result, by generating expected intervals for each elements and
+ // then do cartesian product.
+ const expectedIntervalsCombination = cartesianProduct(
+ ...bitcastedVec2F16InU16x2.map(generateF16ExpectationIntervals)
+ );
+ return anyOf(...expectedIntervalsCombination);
+}
+
+/**
+ * @returns a Comparator for checking if a vec2<f16> value is a valid
+ * bitcast conversion from f32.
+ */
+function bitcastF32ToVec2F16Comparator(f: number): Comparator {
+ // If input f32 is not finite, it can be evaluated to any value and thus any result f16 vec2 is
+ // possible.
+ if (!isFiniteF32(f)) {
+ return anyOf([f16UnboundedInterval, f16UnboundedInterval]);
+ }
+ const bitcastedVec2F16InU16x2 = u32ToU16x2(reinterpretF32AsU32(f)).map(reinterpretU16AsF16);
+ // Generate expection for vec2 f16 result, by generating expected intervals for each elements and
+ // then do cartesian product.
+ const expectedIntervalsCombination = cartesianProduct(
+ ...bitcastedVec2F16InU16x2.map(generateF16ExpectationIntervals)
+ );
+ return anyOf(...expectedIntervalsCombination);
+}
+
+/**
+ * @returns a Comparator for checking if a vec4<f16> is a valid
+ * bitcast conversion from vec2<u32>.
+ */
+function bitcastVec2U32ToVec4F16Comparator(u32x2: number[]): Comparator {
+ assert(u32x2.length === 2);
+ const bitcastedVec4F16InU16x4 = u32x2.flatMap(u32ToU16x2).map(reinterpretU16AsF16);
+ // Generate expection for vec4 f16 result, by generating expected intervals for each elements and
+ // then do cartesian product.
+ const expectedIntervalsCombination = cartesianProduct(
+ ...bitcastedVec4F16InU16x4.map(generateF16ExpectationIntervals)
+ );
+ return anyOf(...expectedIntervalsCombination);
+}
+
+/**
+ * @returns a Comparator for checking if a vec4<f16> is a valid
+ * bitcast conversion from vec2<i32>.
+ */
+function bitcastVec2I32ToVec4F16Comparator(i32x2: number[]): Comparator {
+ assert(i32x2.length === 2);
+ const bitcastedVec4F16InU16x4 = i32x2
+ .map(reinterpretI32AsU32)
+ .flatMap(u32ToU16x2)
+ .map(reinterpretU16AsF16);
+ // Generate expection for vec4 f16 result, by generating expected intervals for each elements and
+ // then do cartesian product.
+ const expectedIntervalsCombination = cartesianProduct(
+ ...bitcastedVec4F16InU16x4.map(generateF16ExpectationIntervals)
+ );
+ return anyOf(...expectedIntervalsCombination);
+}
+
+/**
+ * @returns a Comparator for checking if a vec4<f16> is a valid
+ * bitcast conversion from vec2<f32>.
+ */
+function bitcastVec2F32ToVec4F16Comparator(f32x2: number[]): Comparator {
+ assert(f32x2.length === 2);
+ const bitcastedVec4F16InU16x4 = f32x2
+ .map(reinterpretF32AsU32)
+ .flatMap(u32ToU16x2)
+ .map(reinterpretU16AsF16);
+ // Generate expection for vec4 f16 result, by generating expected intervals for each elements and
+ // then do cartesian product.
+ const expectedIntervalsCombination = cartesianProduct(
+ ...bitcastedVec4F16InU16x4.map(generateF16ExpectationIntervals)
+ );
+ return anyOf(...expectedIntervalsCombination);
+}
+
+// Structure that store the expectations of a single 32bit scalar/element bitcasted from two f16.
+interface ExpectionFor32BitsScalarFromF16x2 {
+ // possibleExpectations is Scalar array if the expectation is for i32/u32 and FPInterval array for
+ // f32. Note that if the expectation for i32/u32 is unbound, possibleExpectations is meaningless.
+ possibleExpectations: (ScalarValue | FPInterval)[];
+ isUnbounded: boolean;
+}
+
+/**
+ * @returns the array of possible 16bits, represented in u16, that bitcasted
+ * from a given finite f16 represented in u16, handling the possible subnormal
+ * flushing. Used to build up 32bits or larger results.
+ */
+function possibleBitsInU16FromFiniteF16InU16(f16InU16: number): number[] {
+ const h = reinterpretU16AsF16(f16InU16);
+ assert(isFiniteF16(h));
+ return [f16InU16, ...(isSubnormalNumberF16(h) ? f16ZerosInU16 : [])];
+}
+
+/**
+ * @returns the expectation for a single 32bit scalar bitcasted from given pair of
+ * f16, result in ExpectionFor32BitsScalarFromF16x2.
+ */
+function possible32BitScalarIntervalsFromF16x2(
+ f16x2InU16x2: number[],
+ type: 'i32' | 'u32' | 'f32'
+): ExpectionFor32BitsScalarFromF16x2 {
+ assert(f16x2InU16x2.length === 2);
+ let reinterpretFromU32: (x: number) => number;
+ let expectationsForValue: (x: number) => ScalarValue[] | FPInterval[];
+ let unboundedExpectations: FPInterval[] | ScalarValue[];
+ if (type === 'u32') {
+ reinterpretFromU32 = (x: number) => x;
+ expectationsForValue = x => [u32(x)];
+ // Scalar expectation can not express "unbounded" for i32 and u32, so use 0 here as a
+ // placeholder, and the possibleExpectations should be ignored if the result is unbounded.
+ unboundedExpectations = [u32(0)];
+ } else if (type === 'i32') {
+ reinterpretFromU32 = (x: number) => reinterpretU32AsI32(x);
+ expectationsForValue = x => [i32(x)];
+ // Scalar expectation can not express "unbounded" for i32 and u32, so use 0 here as a
+ // placeholder, and the possibleExpectations should be ignored if the result is unbounded.
+ unboundedExpectations = [i32(0)];
+ } else {
+ assert(type === 'f32');
+ reinterpretFromU32 = (x: number) => reinterpretU32AsF32(x);
+ expectationsForValue = x => {
+ // Handle the possible Inf/NaN/zeros and subnormal cases for f32 result.
+ if (!isFiniteF32(x)) {
+ return [f32UnboundedInterval];
+ }
+ // If the casted f16 value is +/-0.0, the result can be one of both. Note that in JS -0.0 === 0.0.
+ if (x === 0.0) {
+ return [f32ZerosInterval];
+ }
+ const exactInterval = FP.f32.toInterval(x);
+ // If the casted f16 value is subnormal, it also may be flushed to +/-0.0.
+ return [exactInterval, ...(isSubnormalNumberF32(x) ? [f32ZerosInterval] : [])];
+ };
+ unboundedExpectations = [f32UnboundedInterval];
+ }
+ // Return unbounded expection if f16 Inf/NaN occurs
+ if (
+ !isFiniteF16(reinterpretU16AsF16(f16x2InU16x2[0])) ||
+ !isFiniteF16(reinterpretU16AsF16(f16x2InU16x2[1]))
+ ) {
+ return { possibleExpectations: unboundedExpectations, isUnbounded: true };
+ }
+ const possibleU16Bits = f16x2InU16x2.map(possibleBitsInU16FromFiniteF16InU16);
+ const possibleExpectations = cartesianProduct(...possibleU16Bits).flatMap<
+ ScalarValue | FPInterval
+ >((possibleBitsU16x2: readonly number[]) => {
+ assert(possibleBitsU16x2.length === 2);
+ return expectationsForValue(reinterpretFromU32(u16x2ToU32(possibleBitsU16x2)));
+ });
+ return { possibleExpectations, isUnbounded: false };
+}
+
+/**
+ * @returns a Comparator for checking if a u32 value is a valid
+ * bitcast conversion from vec2 f16.
+ */
+function bitcastVec2F16ToU32Comparator(vec2F16InU16x2: number[]): Comparator {
+ assert(vec2F16InU16x2.length === 2);
+ const expectations = possible32BitScalarIntervalsFromF16x2(vec2F16InU16x2, 'u32');
+ // Return alwaysPass if result is expected unbounded.
+ if (expectations.isUnbounded) {
+ return anyU32;
+ }
+ return anyOf(...expectations.possibleExpectations);
+}
+
+/**
+ * @returns a Comparator for checking if a i32 value is a valid
+ * bitcast conversion from vec2 f16.
+ */
+function bitcastVec2F16ToI32Comparator(vec2F16InU16x2: number[]): Comparator {
+ assert(vec2F16InU16x2.length === 2);
+ const expectations = possible32BitScalarIntervalsFromF16x2(vec2F16InU16x2, 'i32');
+ // Return alwaysPass if result is expected unbounded.
+ if (expectations.isUnbounded) {
+ return anyI32;
+ }
+ return anyOf(...expectations.possibleExpectations);
+}
+
+/**
+ * @returns a Comparator for checking if a i32 value is a valid
+ * bitcast conversion from vec2 f16.
+ */
+function bitcastVec2F16ToF32Comparator(vec2F16InU16x2: number[]): Comparator {
+ assert(vec2F16InU16x2.length === 2);
+ const expectations = possible32BitScalarIntervalsFromF16x2(vec2F16InU16x2, 'f32');
+ // Return alwaysPass if result is expected unbounded.
+ if (expectations.isUnbounded) {
+ return anyF32;
+ }
+ return anyOf(...expectations.possibleExpectations);
+}
+
+/**
+ * @returns a Comparator for checking if a vec2 u32 value is a valid
+ * bitcast conversion from vec4 f16.
+ */
+function bitcastVec4F16ToVec2U32Comparator(vec4F16InU16x4: number[]): Comparator {
+ assert(vec4F16InU16x4.length === 4);
+ const expectationsPerElement = [vec4F16InU16x4.slice(0, 2), vec4F16InU16x4.slice(2, 4)].map(e =>
+ possible32BitScalarIntervalsFromF16x2(e, 'u32')
+ );
+ // Return alwaysPass if any element is expected unbounded. Although it may be only one unbounded
+ // element in the result vector, currently we don't have a way to build a comparator that expect
+ // only one element of i32/u32 vector unbounded.
+ if (expectationsPerElement.map(e => e.isUnbounded).reduce((a, b) => a || b, false)) {
+ return alwaysPass('any vec2<u32>');
+ }
+ return anyOf(
+ ...cartesianProduct(...expectationsPerElement.map(e => e.possibleExpectations)).map(
+ e => new VectorValue(e as ScalarValue[])
+ )
+ );
+}
+
+/**
+ * @returns a Comparator for checking if a vec2 i32 value is a valid
+ * bitcast conversion from vec4 f16.
+ */
+function bitcastVec4F16ToVec2I32Comparator(vec4F16InU16x4: number[]): Comparator {
+ assert(vec4F16InU16x4.length === 4);
+ const expectationsPerElement = [vec4F16InU16x4.slice(0, 2), vec4F16InU16x4.slice(2, 4)].map(e =>
+ possible32BitScalarIntervalsFromF16x2(e, 'i32')
+ );
+ // Return alwaysPass if any element is expected unbounded. Although it may be only one unbounded
+ // element in the result vector, currently we don't have a way to build a comparator that expect
+ // only one element of i32/u32 vector unbounded.
+ if (expectationsPerElement.map(e => e.isUnbounded).reduce((a, b) => a || b, false)) {
+ return alwaysPass('any vec2<i32>');
+ }
+ return anyOf(
+ ...cartesianProduct(...expectationsPerElement.map(e => e.possibleExpectations)).map(
+ e => new VectorValue(e as ScalarValue[])
+ )
+ );
+}
+
+/**
+ * @returns a Comparator for checking if a vec2 f32 value is a valid
+ * bitcast conversion from vec4 f16.
+ */
+function bitcastVec4F16ToVec2F32Comparator(vec4F16InU16x4: number[]): Comparator {
+ assert(vec4F16InU16x4.length === 4);
+ const expectationsPerElement = [vec4F16InU16x4.slice(0, 2), vec4F16InU16x4.slice(2, 4)].map(e =>
+ possible32BitScalarIntervalsFromF16x2(e, 'f32')
+ );
+ return anyOf(
+ ...cartesianProduct(...expectationsPerElement.map(e => e.possibleExpectations)).map(e => [
+ e[0] as FPInterval,
+ e[1] as FPInterval,
+ ])
+ );
+}
+
+export const d = makeCaseCache('bitcast', {
+ // Identity Cases
+ i32_to_i32: () => fullI32Range().map(e => ({ input: i32(e), expected: i32(e) })),
+ u32_to_u32: () => fullU32Range().map(e => ({ input: u32(e), expected: u32(e) })),
+ f32_inf_nan_to_f32: () =>
+ f32RangeWithInfAndNaN.map(e => ({
+ input: f32(e),
+ expected: bitcastF32ToF32Comparator(e),
+ })),
+ f32_to_f32: () =>
+ f32FiniteRange.map(e => ({ input: f32(e), expected: bitcastF32ToF32Comparator(e) })),
+ f16_inf_nan_to_f16: () =>
+ [...f16FiniteInF16, ...f16InfAndNaNInF16].map(e => ({
+ input: f16(e),
+ expected: bitcastF16ToF16Comparator(e),
+ })),
+ f16_to_f16: () =>
+ f16FiniteInF16.map(e => ({ input: f16(e), expected: bitcastF16ToF16Comparator(e) })),
+
+ // i32,u32,f32,Abstract to different i32,u32,f32
+ i32_to_u32: () => fullI32Range().map(e => ({ input: i32(e), expected: u32(e) })),
+ i32_to_f32: () =>
+ i32RangeForF32Finite.map(e => ({
+ input: i32(e),
+ expected: bitcastI32ToF32Comparator(e),
+ })),
+ ai_to_i32: () => fullI32Range().map(e => ({ input: abstractInt(BigInt(e)), expected: i32(e) })),
+ ai_to_u32: () => fullU32Range().map(e => ({ input: abstractInt(BigInt(e)), expected: u32(e) })),
+ ai_to_f32: () =>
+ // AbstractInt is converted to i32, because there is no explicit overload
+ i32RangeForF32Finite.map(e => ({
+ input: abstractInt(BigInt(e)),
+ expected: bitcastI32ToF32Comparator(e),
+ })),
+ i32_to_f32_inf_nan: () =>
+ i32RangeForF32FiniteInfNaN.map(e => ({
+ input: i32(e),
+ expected: bitcastI32ToF32Comparator(e),
+ })),
+ u32_to_i32: () => fullU32Range().map(e => ({ input: u32(e), expected: i32(e) })),
+ u32_to_f32: () =>
+ u32RangeForF32Finite.map(e => ({
+ input: u32(e),
+ expected: bitcastU32ToF32Comparator(e),
+ })),
+ u32_to_f32_inf_nan: () =>
+ u32RangeForF32FiniteInfNaN.map(e => ({
+ input: u32(e),
+ expected: bitcastU32ToF32Comparator(e),
+ })),
+ f32_inf_nan_to_i32: () =>
+ f32RangeWithInfAndNaN.map(e => ({
+ input: f32(e),
+ expected: bitcastF32ToI32Comparator(e),
+ })),
+ f32_to_i32: () =>
+ f32FiniteRange.map(e => ({ input: f32(e), expected: bitcastF32ToI32Comparator(e) })),
+
+ f32_inf_nan_to_u32: () =>
+ f32RangeWithInfAndNaN.map(e => ({
+ input: f32(e),
+ expected: bitcastF32ToU32Comparator(e),
+ })),
+ f32_to_u32: () =>
+ f32FiniteRange.map(e => ({ input: f32(e), expected: bitcastF32ToU32Comparator(e) })),
+
+ // i32,u32,f32,AbstractInt to vec2<f16>
+ u32_to_vec2_f16_inf_nan: () =>
+ u32RangeForF16Vec2FiniteInfNaN.map(e => ({
+ input: u32(e),
+ expected: bitcastU32ToVec2F16Comparator(e),
+ })),
+ u32_to_vec2_f16: () =>
+ u32RangeForF16Vec2Finite.map(e => ({
+ input: u32(e),
+ expected: bitcastU32ToVec2F16Comparator(e),
+ })),
+ i32_to_vec2_f16_inf_nan: () =>
+ i32RangeForF16Vec2FiniteInfNaN.map(e => ({
+ input: i32(e),
+ expected: bitcastI32ToVec2F16Comparator(e),
+ })),
+ i32_to_vec2_f16: () =>
+ i32RangeForF16Vec2Finite.map(e => ({
+ input: i32(e),
+ expected: bitcastI32ToVec2F16Comparator(e),
+ })),
+ ai_to_vec2_f16: () =>
+ // AbstractInt is converted to i32, because there is no explicit overload
+ i32RangeForF16Vec2Finite.map(e => ({
+ input: abstractInt(BigInt(e)),
+ expected: bitcastI32ToVec2F16Comparator(e),
+ })),
+ f32_inf_nan_to_vec2_f16_inf_nan: () =>
+ f32RangeWithInfAndNaNForF16Vec2FiniteInfNaN.map(e => ({
+ input: f32(e),
+ expected: bitcastF32ToVec2F16Comparator(e),
+ })),
+ f32_to_vec2_f16: () =>
+ f32FiniteRangeForF16Vec2Finite.map(e => ({
+ input: f32(e),
+ expected: bitcastF32ToVec2F16Comparator(e),
+ })),
+ af_to_vec2_f16: () =>
+ f32FiniteRangeForF16Vec2Finite.map(e => ({
+ input: abstractFloat(e),
+ expected: bitcastF32ToVec2F16Comparator(e),
+ })),
+
+ // vec2<i32>, vec2<u32>, vec2<f32>, vec2<AbstractInt> to vec4<f16>
+ vec2_i32_to_vec4_f16_inf_nan: () =>
+ slidingSlice(i32RangeForF16Vec2FiniteInfNaN, 2).map(e => ({
+ input: toVector(e, i32),
+ expected: bitcastVec2I32ToVec4F16Comparator(e),
+ })),
+ vec2_i32_to_vec4_f16: () =>
+ slidingSlice(i32RangeForF16Vec2Finite, 2).map(e => ({
+ input: toVector(e, i32),
+ expected: bitcastVec2I32ToVec4F16Comparator(e),
+ })),
+ vec2_ai_to_vec4_f16: () =>
+ // AbstractInt is converted to i32, because there is no explicit overload
+ slidingSlice(i32RangeForF16Vec2Finite, 2).map(e => ({
+ input: toVector(e, (n: number) => abstractInt(BigInt(n))),
+ expected: bitcastVec2I32ToVec4F16Comparator(e),
+ })),
+ vec2_u32_to_vec4_f16_inf_nan: () =>
+ slidingSlice(u32RangeForF16Vec2FiniteInfNaN, 2).map(e => ({
+ input: toVector(e, u32),
+ expected: bitcastVec2U32ToVec4F16Comparator(e),
+ })),
+ vec2_u32_to_vec4_f16: () =>
+ slidingSlice(u32RangeForF16Vec2Finite, 2).map(e => ({
+ input: toVector(e, u32),
+ expected: bitcastVec2U32ToVec4F16Comparator(e),
+ })),
+ vec2_f32_inf_nan_to_vec4_f16_inf_nan: () =>
+ slidingSlice(f32RangeWithInfAndNaNForF16Vec2FiniteInfNaN, 2).map(e => ({
+ input: toVector(e, f32),
+ expected: bitcastVec2F32ToVec4F16Comparator(e),
+ })),
+ vec2_f32_to_vec4_f16: () =>
+ slidingSlice(f32FiniteRangeForF16Vec2Finite, 2).map(e => ({
+ input: toVector(e, f32),
+ expected: bitcastVec2F32ToVec4F16Comparator(e),
+ })),
+ vec2_af_to_vec4_f16: () =>
+ slidingSlice(f32FiniteRangeForF16Vec2Finite, 2).map(e => ({
+ input: toVector(e, abstractFloat),
+ expected: bitcastVec2F32ToVec4F16Comparator(e),
+ })),
+
+ // vec2<f16> to i32, u32, f32
+ vec2_f16_to_u32: () =>
+ f16Vec2FiniteInU16x2.map(e => ({
+ input: u16x2ToVec2F16(e),
+ expected: bitcastVec2F16ToU32Comparator(e),
+ })),
+ vec2_f16_inf_nan_to_u32: () =>
+ f16Vec2FiniteInfNanInU16x2.map(e => ({
+ input: u16x2ToVec2F16(e),
+ expected: bitcastVec2F16ToU32Comparator(e),
+ })),
+ vec2_f16_to_i32: () =>
+ f16Vec2FiniteInU16x2.map(e => ({
+ input: u16x2ToVec2F16(e),
+ expected: bitcastVec2F16ToI32Comparator(e),
+ })),
+ vec2_f16_inf_nan_to_i32: () =>
+ f16Vec2FiniteInfNanInU16x2.map(e => ({
+ input: u16x2ToVec2F16(e),
+ expected: bitcastVec2F16ToI32Comparator(e),
+ })),
+ vec2_f16_to_f32_finite: () =>
+ f16Vec2FiniteInU16x2
+ .filter(u16x2 => isFiniteF32(reinterpretU32AsF32(u16x2ToU32(u16x2))))
+ .map(e => ({
+ input: u16x2ToVec2F16(e),
+ expected: bitcastVec2F16ToF32Comparator(e),
+ })),
+ vec2_f16_inf_nan_to_f32: () =>
+ f16Vec2FiniteInfNanInU16x2.map(e => ({
+ input: u16x2ToVec2F16(e),
+ expected: bitcastVec2F16ToF32Comparator(e),
+ })),
+
+ // vec4<f16> to vec2 of i32, u32, f32
+ vec4_f16_to_vec2_u32: () =>
+ f16Vec2FiniteInU16x4.map(e => ({
+ input: u16x4ToVec4F16(e),
+ expected: bitcastVec4F16ToVec2U32Comparator(e),
+ })),
+ vec4_f16_inf_nan_to_vec2_u32: () =>
+ f16Vec2FiniteInfNanInU16x4.map(e => ({
+ input: u16x4ToVec4F16(e),
+ expected: bitcastVec4F16ToVec2U32Comparator(e),
+ })),
+ vec4_f16_to_vec2_i32: () =>
+ f16Vec2FiniteInU16x4.map(e => ({
+ input: u16x4ToVec4F16(e),
+ expected: bitcastVec4F16ToVec2I32Comparator(e),
+ })),
+ vec4_f16_inf_nan_to_vec2_i32: () =>
+ f16Vec2FiniteInfNanInU16x4.map(e => ({
+ input: u16x4ToVec4F16(e),
+ expected: bitcastVec4F16ToVec2I32Comparator(e),
+ })),
+ vec4_f16_to_vec2_f32_finite: () =>
+ f16Vec2FiniteInU16x4
+ .filter(
+ u16x4 =>
+ isFiniteF32(reinterpretU32AsF32(u16x2ToU32(u16x4.slice(0, 2)))) &&
+ isFiniteF32(reinterpretU32AsF32(u16x2ToU32(u16x4.slice(2, 4))))
+ )
+ .map(e => ({
+ input: u16x4ToVec4F16(e),
+ expected: bitcastVec4F16ToVec2F32Comparator(e),
+ })),
+ vec4_f16_inf_nan_to_vec2_f32: () =>
+ f16Vec2FiniteInfNanInU16x4.map(e => ({
+ input: u16x4ToVec4F16(e),
+ expected: bitcastVec4F16ToVec2F32Comparator(e),
+ })),
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/bitcast.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/bitcast.spec.ts
index 390129f2c7..02acf98ef4 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/bitcast.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/bitcast.spec.ts
@@ -11,6 +11,9 @@ S is i32, u32, f32
T is i32, u32, f32, and T is not S
Reinterpretation of bits. Beware non-normal f32 values.
+@const @must_use fn bitcast<u32>(e : Type.abstractInt) -> T
+@const @must_use fn bitcast<vecN<u32>>(e : vecN<Type.abstractInt>) -> T
+
@const @must_use fn bitcast<T>(e: vec2<f16> ) -> T
@const @must_use fn bitcast<vec2<T>>(e: vec4<f16> ) -> vec2<T>
@const @must_use fn bitcast<vec2<f16>>(e: T ) -> vec2<f16>
@@ -20,823 +23,26 @@ T is i32, u32, f32
import { TestParams } from '../../../../../../common/framework/fixture.js';
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
-import { assert } from '../../../../../../common/util/util.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { Comparator, alwaysPass, anyOf } from '../../../../../util/compare.js';
-import { kBit, kValue } from '../../../../../util/constants.js';
+import { anyOf } from '../../../../../util/compare.js';
import {
f32,
- i32,
u32,
- f16,
- TypeF32,
- TypeI32,
- TypeU32,
- TypeF16,
- TypeVec,
- Vector,
- Scalar,
- toVector,
+ i32,
+ abstractFloat,
+ uint32ToFloat32,
+ u32Bits,
+ Type,
} from '../../../../../util/conversion.js';
-import { FPInterval, FP } from '../../../../../util/floating_point.js';
-import {
- fullF32Range,
- fullI32Range,
- fullU32Range,
- fullF16Range,
- linearRange,
- isSubnormalNumberF32,
- isSubnormalNumberF16,
- cartesianProduct,
- isFiniteF32,
- isFiniteF16,
-} from '../../../../../util/math.js';
-import {
- reinterpretI32AsF32,
- reinterpretI32AsU32,
- reinterpretF32AsI32,
- reinterpretF32AsU32,
- reinterpretU32AsF32,
- reinterpretU32AsI32,
- reinterpretU16AsF16,
- reinterpretF16AsU16,
-} from '../../../../../util/reinterpret.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run, ShaderBuilder } from '../../expression.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { scalarF32Range } from '../../../../../util/math.js';
+import { ShaderBuilder, allInputSources, onlyConstInputSource, run } from '../../expression.js';
+import { d } from './bitcast.cache.js';
import { builtinWithPredeclaration } from './builtin.js';
export const g = makeTestGroup(GPUTest);
-const numNaNs = 11;
-const f32InfAndNaNInU32: number[] = [
- // Cover NaNs evenly in integer space.
- // The positive NaN with the lowest integer representation is the integer
- // for infinity, plus one.
- // The positive NaN with the highest integer representation is i32.max (!)
- ...linearRange(kBit.f32.positive.infinity + 1, kBit.i32.positive.max, numNaNs),
- // The negative NaN with the lowest integer representation is the integer
- // for negative infinity, plus one.
- // The negative NaN with the highest integer representation is u32.max (!)
- ...linearRange(kBit.f32.negative.infinity + 1, kBit.u32.max, numNaNs),
- kBit.f32.positive.infinity,
- kBit.f32.negative.infinity,
-];
-const f32InfAndNaNInF32 = f32InfAndNaNInU32.map(u => reinterpretU32AsF32(u));
-const f32InfAndNaNInI32 = f32InfAndNaNInU32.map(u => reinterpretU32AsI32(u));
-
-const f32ZerosInU32 = [0, kBit.f32.negative.zero];
-const f32ZerosInF32 = f32ZerosInU32.map(u => reinterpretU32AsF32(u));
-const f32ZerosInI32 = f32ZerosInU32.map(u => reinterpretU32AsI32(u));
-const f32ZerosInterval: FPInterval = new FPInterval('f32', -0.0, 0.0);
-
-// f32FiniteRange is a list of finite f32s. fullF32Range() already
-// has +0, we only need to add -0.
-const f32FiniteRange: number[] = [...fullF32Range(), kValue.f32.negative.zero];
-const f32RangeWithInfAndNaN: number[] = [...f32FiniteRange, ...f32InfAndNaNInF32];
-
-// F16 values, finite, Inf/NaN, and zeros. Represented in float and u16.
-const f16FiniteInF16: number[] = [...fullF16Range(), kValue.f16.negative.zero];
-const f16FiniteInU16: number[] = f16FiniteInF16.map(u => reinterpretF16AsU16(u));
-
-const f16InfAndNaNInU16: number[] = [
- // Cover NaNs evenly in integer space.
- // The positive NaN with the lowest integer representation is the integer
- // for infinity, plus one.
- // The positive NaN with the highest integer representation is u16 0x7fff i.e. 32767.
- ...linearRange(kBit.f16.positive.infinity + 1, 32767, numNaNs).map(v => Math.ceil(v)),
- // The negative NaN with the lowest integer representation is the integer
- // for negative infinity, plus one.
- // The negative NaN with the highest integer representation is u16 0xffff i.e. 65535
- ...linearRange(kBit.f16.negative.infinity + 1, 65535, numNaNs).map(v => Math.floor(v)),
- kBit.f16.positive.infinity,
- kBit.f16.negative.infinity,
-];
-const f16InfAndNaNInF16 = f16InfAndNaNInU16.map(u => reinterpretU16AsF16(u));
-
-const f16ZerosInU16 = [kBit.f16.negative.zero, 0];
-
-// f16 interval that match +/-0.0.
-const f16ZerosInterval: FPInterval = new FPInterval('f16', -0.0, 0.0);
-
-/**
- * @returns an u32 whose lower and higher 16bits are the two elements of the
- * given array of two u16 respectively, in little-endian.
- */
-function u16x2ToU32(u16x2: readonly number[]): number {
- assert(u16x2.length === 2);
- // Create a DataView with 4 bytes buffer.
- const buffer = new ArrayBuffer(4);
- const view = new DataView(buffer);
- // Enforce little-endian.
- view.setUint16(0, u16x2[0], true);
- view.setUint16(2, u16x2[1], true);
- return view.getUint32(0, true);
-}
-
-/**
- * @returns an array of two u16, respectively the lower and higher 16bits of
- * given u32 in little-endian.
- */
-function u32ToU16x2(u32: number): number[] {
- // Create a DataView with 4 bytes buffer.
- const buffer = new ArrayBuffer(4);
- const view = new DataView(buffer);
- // Enforce little-endian.
- view.setUint32(0, u32, true);
- return [view.getUint16(0, true), view.getUint16(2, true)];
-}
-
-/**
- * @returns a vec2<f16> from an array of two u16, each reinterpreted as f16.
- */
-function u16x2ToVec2F16(u16x2: number[]): Vector {
- assert(u16x2.length === 2);
- return toVector(u16x2.map(reinterpretU16AsF16), f16);
-}
-
-/**
- * @returns a vec4<f16> from an array of four u16, each reinterpreted as f16.
- */
-function u16x4ToVec4F16(u16x4: number[]): Vector {
- assert(u16x4.length === 4);
- return toVector(u16x4.map(reinterpretU16AsF16), f16);
-}
-
-/**
- * @returns true if and only if a given u32 can bitcast to a vec2<f16> with all elements
- * being finite f16 values.
- */
-function canU32BitcastToFiniteVec2F16(u32: number): boolean {
- return u32ToU16x2(u32)
- .map(u16 => isFiniteF16(reinterpretU16AsF16(u16)))
- .reduce((a, b) => a && b, true);
-}
-
-/**
- * @returns an array of N elements with the i-th element being an array of len elements
- * [a_i, a_((i+1)%N), ..., a_((i+len-1)%N)], for the input array of N element [a_1, ... a_N]
- * and the given len. For example, slidingSlice([1, 2, 3], 2) result in
- * [[1, 2], [2, 3], [3, 1]].
- * This helper function is used for generating vector cases from scalar values array.
- */
-function slidingSlice(input: number[], len: number) {
- const result: number[][] = [];
- for (let i = 0; i < input.length; i++) {
- const sub: number[] = [];
- for (let j = 0; j < len; j++) {
- sub.push(input[(i + j) % input.length]);
- }
- result.push(sub);
- }
- return result;
-}
-
-// vec2<f16> interesting (zeros, Inf, and NaN) values for testing cases.
-// vec2<f16> values that has at least one Inf/NaN f16 element, reinterpreted as u32/i32.
-const f16Vec2InfAndNaNInU32 = [
- ...cartesianProduct(f16InfAndNaNInU16, [...f16InfAndNaNInU16, ...f16FiniteInU16]),
- ...cartesianProduct(f16FiniteInU16, f16InfAndNaNInU16),
-].map(u16x2ToU32);
-const f16Vec2InfAndNaNInI32 = f16Vec2InfAndNaNInU32.map(u => reinterpretU32AsI32(u));
-// vec2<f16> values with two f16 0.0 element, reinterpreted as u32/i32.
-const f16Vec2ZerosInU32 = cartesianProduct(f16ZerosInU16, f16ZerosInU16).map(u16x2ToU32);
-const f16Vec2ZerosInI32 = f16Vec2ZerosInU32.map(u => reinterpretU32AsI32(u));
-
-// i32/u32/f32 range for bitcasting to vec2<f16>
-// u32 values for bitcasting to vec2<f16> finite, Inf, and NaN.
-const u32RangeForF16Vec2FiniteInfNaN: number[] = [
- ...fullU32Range(),
- ...f16Vec2ZerosInU32,
- ...f16Vec2InfAndNaNInU32,
-];
-// u32 values for bitcasting to finite only vec2<f16>, used for constant evaluation.
-const u32RangeForF16Vec2Finite: number[] = u32RangeForF16Vec2FiniteInfNaN.filter(
- canU32BitcastToFiniteVec2F16
-);
-// i32 values for bitcasting to vec2<f16> finite, zeros, Inf, and NaN.
-const i32RangeForF16Vec2FiniteInfNaN: number[] = [
- ...fullI32Range(),
- ...f16Vec2ZerosInI32,
- ...f16Vec2InfAndNaNInI32,
-];
-// i32 values for bitcasting to finite only vec2<f16>, used for constant evaluation.
-const i32RangeForF16Vec2Finite: number[] = i32RangeForF16Vec2FiniteInfNaN.filter(u =>
- canU32BitcastToFiniteVec2F16(reinterpretI32AsU32(u))
-);
-// f32 values with finite/Inf/NaN f32, for bitcasting to vec2<f16> finite, zeros, Inf, and NaN.
-const f32RangeWithInfAndNaNForF16Vec2FiniteInfNaN: number[] = [
- ...f32RangeWithInfAndNaN,
- ...u32RangeForF16Vec2FiniteInfNaN.map(reinterpretU32AsF32),
-];
-// Finite f32 values for bitcasting to finite only vec2<f16>, used for constant evaluation.
-const f32FiniteRangeForF16Vec2Finite: number[] = f32RangeWithInfAndNaNForF16Vec2FiniteInfNaN
- .filter(isFiniteF32)
- .filter(u => canU32BitcastToFiniteVec2F16(reinterpretF32AsU32(u)));
-
-// vec2<f16> cases for bitcasting to i32/u32/f32, by combining f16 values into pairs
-const f16Vec2FiniteInU16x2 = slidingSlice(f16FiniteInU16, 2);
-const f16Vec2FiniteInfNanInU16x2 = slidingSlice([...f16FiniteInU16, ...f16InfAndNaNInU16], 2);
-// vec4<f16> cases for bitcasting to vec2<i32/u32/f32>, by combining f16 values 4-by-4
-const f16Vec2FiniteInU16x4 = slidingSlice(f16FiniteInU16, 4);
-const f16Vec2FiniteInfNanInU16x4 = slidingSlice([...f16FiniteInU16, ...f16InfAndNaNInU16], 4);
-
-// alwaysPass comparator for i32/u32/f32 cases. For f32/f16 we also use unbound interval, which
-// allow per-element unbounded expectation for vector.
-const anyF32 = alwaysPass('any f32');
-const anyI32 = alwaysPass('any i32');
-const anyU32 = alwaysPass('any u32');
-
-// Unbounded FPInterval
-const f32UnboundedInterval = FP.f32.constants().unboundedInterval;
-const f16UnboundedInterval = FP.f16.constants().unboundedInterval;
-
-// i32 and u32 cases for bitcasting to f32.
-// i32 cases for bitcasting to f32 finite, zeros, Inf, and NaN.
-const i32RangeForF32FiniteInfNaN: number[] = [
- ...fullI32Range(),
- ...f32ZerosInI32,
- ...f32InfAndNaNInI32,
-];
-// i32 cases for bitcasting to f32 finite only.
-const i32RangeForF32Finite: number[] = i32RangeForF32FiniteInfNaN.filter(i =>
- isFiniteF32(reinterpretI32AsF32(i))
-);
-// u32 cases for bitcasting to f32 finite, zeros, Inf, and NaN.
-const u32RangeForF32FiniteInfNaN: number[] = [
- ...fullU32Range(),
- ...f32ZerosInU32,
- ...f32InfAndNaNInU32,
-];
-// u32 cases for bitcasting to f32 finite only.
-const u32RangeForF32Finite: number[] = u32RangeForF32FiniteInfNaN.filter(u =>
- isFiniteF32(reinterpretU32AsF32(u))
-);
-
-/**
- * @returns a Comparator for checking if a f32 value is a valid
- * bitcast conversion from f32.
- */
-function bitcastF32ToF32Comparator(f: number): Comparator {
- if (!isFiniteF32(f)) return anyF32;
- const acceptable: number[] = [f, ...(isSubnormalNumberF32(f) ? f32ZerosInF32 : [])];
- return anyOf(...acceptable.map(f32));
-}
-
-/**
- * @returns a Comparator for checking if a u32 value is a valid
- * bitcast conversion from f32.
- */
-function bitcastF32ToU32Comparator(f: number): Comparator {
- if (!isFiniteF32(f)) return anyU32;
- const acceptable: number[] = [
- reinterpretF32AsU32(f),
- ...(isSubnormalNumberF32(f) ? f32ZerosInU32 : []),
- ];
- return anyOf(...acceptable.map(u32));
-}
-
-/**
- * @returns a Comparator for checking if a i32 value is a valid
- * bitcast conversion from f32.
- */
-function bitcastF32ToI32Comparator(f: number): Comparator {
- if (!isFiniteF32(f)) return anyI32;
- const acceptable: number[] = [
- reinterpretF32AsI32(f),
- ...(isSubnormalNumberF32(f) ? f32ZerosInI32 : []),
- ];
- return anyOf(...acceptable.map(i32));
-}
-
-/**
- * @returns a Comparator for checking if a f32 value is a valid
- * bitcast conversion from i32.
- */
-function bitcastI32ToF32Comparator(i: number): Comparator {
- const f: number = reinterpretI32AsF32(i);
- if (!isFiniteF32(f)) return anyI32;
- // Positive or negative zero bit pattern map to any zero.
- if (f32ZerosInI32.includes(i)) return anyOf(...f32ZerosInF32.map(f32));
- const acceptable: number[] = [f, ...(isSubnormalNumberF32(f) ? f32ZerosInF32 : [])];
- return anyOf(...acceptable.map(f32));
-}
-
-/**
- * @returns a Comparator for checking if a f32 value is a valid
- * bitcast conversion from u32.
- */
-function bitcastU32ToF32Comparator(u: number): Comparator {
- const f: number = reinterpretU32AsF32(u);
- if (!isFiniteF32(f)) return anyU32;
- // Positive or negative zero bit pattern map to any zero.
- if (f32ZerosInU32.includes(u)) return anyOf(...f32ZerosInF32.map(f32));
- const acceptable: number[] = [f, ...(isSubnormalNumberF32(f) ? f32ZerosInF32 : [])];
- return anyOf(...acceptable.map(f32));
-}
-
-/**
- * @returns an array of expected f16 FPInterval for the given bitcasted f16 value, which may be
- * subnormal, Inf, or NaN. Test cases that bitcasted to vector of f16 use this function to get
- * per-element expectation and build vector expectation using cartesianProduct.
- */
-function generateF16ExpectationIntervals(bitcastedF16Value: number): FPInterval[] {
- // If the bitcasted f16 value is inf or nan, the result is unbounded
- if (!isFiniteF16(bitcastedF16Value)) {
- return [f16UnboundedInterval];
- }
- // If the casted f16 value is +/-0.0, the result can be one of both. Note that in JS -0.0 === 0.0.
- if (bitcastedF16Value === 0.0) {
- return [f16ZerosInterval];
- }
- const exactInterval = FP.f16.toInterval(bitcastedF16Value);
- // If the casted f16 value is subnormal, it also may be flushed to +/-0.0.
- return [exactInterval, ...(isSubnormalNumberF16(bitcastedF16Value) ? [f16ZerosInterval] : [])];
-}
-
-/**
- * @returns a Comparator for checking if a f16 value is a valid
- * bitcast conversion from f16.
- */
-function bitcastF16ToF16Comparator(f: number): Comparator {
- if (!isFiniteF16(f)) return anyOf(f16UnboundedInterval);
- return anyOf(...generateF16ExpectationIntervals(f));
-}
-
-/**
- * @returns a Comparator for checking if a vec2<f16> is a valid bitcast
- * conversion from u32.
- */
-function bitcastU32ToVec2F16Comparator(u: number): Comparator {
- const bitcastedVec2F16InU16x2 = u32ToU16x2(u).map(reinterpretU16AsF16);
- // Generate expection for vec2 f16 result, by generating expected intervals for each elements and
- // then do cartesian product.
- const expectedIntervalsCombination = cartesianProduct(
- ...bitcastedVec2F16InU16x2.map(generateF16ExpectationIntervals)
- );
- return anyOf(...expectedIntervalsCombination);
-}
-
-/**
- * @returns a Comparator for checking if a vec2<f16> value is a valid
- * bitcast conversion from i32.
- */
-function bitcastI32ToVec2F16Comparator(i: number): Comparator {
- const bitcastedVec2F16InU16x2 = u32ToU16x2(reinterpretI32AsU32(i)).map(reinterpretU16AsF16);
- // Generate expection for vec2 f16 result, by generating expected intervals for each elements and
- // then do cartesian product.
- const expectedIntervalsCombination = cartesianProduct(
- ...bitcastedVec2F16InU16x2.map(generateF16ExpectationIntervals)
- );
- return anyOf(...expectedIntervalsCombination);
-}
-
-/**
- * @returns a Comparator for checking if a vec2<f16> value is a valid
- * bitcast conversion from f32.
- */
-function bitcastF32ToVec2F16Comparator(f: number): Comparator {
- // If input f32 is not finite, it can be evaluated to any value and thus any result f16 vec2 is
- // possible.
- if (!isFiniteF32(f)) {
- return anyOf([f16UnboundedInterval, f16UnboundedInterval]);
- }
- const bitcastedVec2F16InU16x2 = u32ToU16x2(reinterpretF32AsU32(f)).map(reinterpretU16AsF16);
- // Generate expection for vec2 f16 result, by generating expected intervals for each elements and
- // then do cartesian product.
- const expectedIntervalsCombination = cartesianProduct(
- ...bitcastedVec2F16InU16x2.map(generateF16ExpectationIntervals)
- );
- return anyOf(...expectedIntervalsCombination);
-}
-
-/**
- * @returns a Comparator for checking if a vec4<f16> is a valid
- * bitcast conversion from vec2<u32>.
- */
-function bitcastVec2U32ToVec4F16Comparator(u32x2: number[]): Comparator {
- assert(u32x2.length === 2);
- const bitcastedVec4F16InU16x4 = u32x2.flatMap(u32ToU16x2).map(reinterpretU16AsF16);
- // Generate expection for vec4 f16 result, by generating expected intervals for each elements and
- // then do cartesian product.
- const expectedIntervalsCombination = cartesianProduct(
- ...bitcastedVec4F16InU16x4.map(generateF16ExpectationIntervals)
- );
- return anyOf(...expectedIntervalsCombination);
-}
-
-/**
- * @returns a Comparator for checking if a vec4<f16> is a valid
- * bitcast conversion from vec2<i32>.
- */
-function bitcastVec2I32ToVec4F16Comparator(i32x2: number[]): Comparator {
- assert(i32x2.length === 2);
- const bitcastedVec4F16InU16x4 = i32x2
- .map(reinterpretI32AsU32)
- .flatMap(u32ToU16x2)
- .map(reinterpretU16AsF16);
- // Generate expection for vec4 f16 result, by generating expected intervals for each elements and
- // then do cartesian product.
- const expectedIntervalsCombination = cartesianProduct(
- ...bitcastedVec4F16InU16x4.map(generateF16ExpectationIntervals)
- );
- return anyOf(...expectedIntervalsCombination);
-}
-
-/**
- * @returns a Comparator for checking if a vec4<f16> is a valid
- * bitcast conversion from vec2<f32>.
- */
-function bitcastVec2F32ToVec4F16Comparator(f32x2: number[]): Comparator {
- assert(f32x2.length === 2);
- const bitcastedVec4F16InU16x4 = f32x2
- .map(reinterpretF32AsU32)
- .flatMap(u32ToU16x2)
- .map(reinterpretU16AsF16);
- // Generate expection for vec4 f16 result, by generating expected intervals for each elements and
- // then do cartesian product.
- const expectedIntervalsCombination = cartesianProduct(
- ...bitcastedVec4F16InU16x4.map(generateF16ExpectationIntervals)
- );
- return anyOf(...expectedIntervalsCombination);
-}
-
-// Structure that store the expectations of a single 32bit scalar/element bitcasted from two f16.
-interface ExpectionFor32BitsScalarFromF16x2 {
- // possibleExpectations is Scalar array if the expectation is for i32/u32 and FPInterval array for
- // f32. Note that if the expectation for i32/u32 is unbound, possibleExpectations is meaningless.
- possibleExpectations: (Scalar | FPInterval)[];
- isUnbounded: boolean;
-}
-
-/**
- * @returns the array of possible 16bits, represented in u16, that bitcasted
- * from a given finite f16 represented in u16, handling the possible subnormal
- * flushing. Used to build up 32bits or larger results.
- */
-function possibleBitsInU16FromFiniteF16InU16(f16InU16: number): number[] {
- const h = reinterpretU16AsF16(f16InU16);
- assert(isFiniteF16(h));
- return [f16InU16, ...(isSubnormalNumberF16(h) ? f16ZerosInU16 : [])];
-}
-
-/**
- * @returns the expectation for a single 32bit scalar bitcasted from given pair of
- * f16, result in ExpectionFor32BitsScalarFromF16x2.
- */
-function possible32BitScalarIntervalsFromF16x2(
- f16x2InU16x2: number[],
- type: 'i32' | 'u32' | 'f32'
-): ExpectionFor32BitsScalarFromF16x2 {
- assert(f16x2InU16x2.length === 2);
- let reinterpretFromU32: (x: number) => number;
- let expectationsForValue: (x: number) => Scalar[] | FPInterval[];
- let unboundedExpectations: FPInterval[] | Scalar[];
- if (type === 'u32') {
- reinterpretFromU32 = (x: number) => x;
- expectationsForValue = x => [u32(x)];
- // Scalar expectation can not express "unbounded" for i32 and u32, so use 0 here as a
- // placeholder, and the possibleExpectations should be ignored if the result is unbounded.
- unboundedExpectations = [u32(0)];
- } else if (type === 'i32') {
- reinterpretFromU32 = (x: number) => reinterpretU32AsI32(x);
- expectationsForValue = x => [i32(x)];
- // Scalar expectation can not express "unbounded" for i32 and u32, so use 0 here as a
- // placeholder, and the possibleExpectations should be ignored if the result is unbounded.
- unboundedExpectations = [i32(0)];
- } else {
- assert(type === 'f32');
- reinterpretFromU32 = (x: number) => reinterpretU32AsF32(x);
- expectationsForValue = x => {
- // Handle the possible Inf/NaN/zeros and subnormal cases for f32 result.
- if (!isFiniteF32(x)) {
- return [f32UnboundedInterval];
- }
- // If the casted f16 value is +/-0.0, the result can be one of both. Note that in JS -0.0 === 0.0.
- if (x === 0.0) {
- return [f32ZerosInterval];
- }
- const exactInterval = FP.f32.toInterval(x);
- // If the casted f16 value is subnormal, it also may be flushed to +/-0.0.
- return [exactInterval, ...(isSubnormalNumberF32(x) ? [f32ZerosInterval] : [])];
- };
- unboundedExpectations = [f32UnboundedInterval];
- }
- // Return unbounded expection if f16 Inf/NaN occurs
- if (
- !isFiniteF16(reinterpretU16AsF16(f16x2InU16x2[0])) ||
- !isFiniteF16(reinterpretU16AsF16(f16x2InU16x2[1]))
- ) {
- return { possibleExpectations: unboundedExpectations, isUnbounded: true };
- }
- const possibleU16Bits = f16x2InU16x2.map(possibleBitsInU16FromFiniteF16InU16);
- const possibleExpectations = cartesianProduct(...possibleU16Bits).flatMap<Scalar | FPInterval>(
- (possibleBitsU16x2: readonly number[]) => {
- assert(possibleBitsU16x2.length === 2);
- return expectationsForValue(reinterpretFromU32(u16x2ToU32(possibleBitsU16x2)));
- }
- );
- return { possibleExpectations, isUnbounded: false };
-}
-
-/**
- * @returns a Comparator for checking if a u32 value is a valid
- * bitcast conversion from vec2 f16.
- */
-function bitcastVec2F16ToU32Comparator(vec2F16InU16x2: number[]): Comparator {
- assert(vec2F16InU16x2.length === 2);
- const expectations = possible32BitScalarIntervalsFromF16x2(vec2F16InU16x2, 'u32');
- // Return alwaysPass if result is expected unbounded.
- if (expectations.isUnbounded) {
- return anyU32;
- }
- return anyOf(...expectations.possibleExpectations);
-}
-
-/**
- * @returns a Comparator for checking if a i32 value is a valid
- * bitcast conversion from vec2 f16.
- */
-function bitcastVec2F16ToI32Comparator(vec2F16InU16x2: number[]): Comparator {
- assert(vec2F16InU16x2.length === 2);
- const expectations = possible32BitScalarIntervalsFromF16x2(vec2F16InU16x2, 'i32');
- // Return alwaysPass if result is expected unbounded.
- if (expectations.isUnbounded) {
- return anyI32;
- }
- return anyOf(...expectations.possibleExpectations);
-}
-
-/**
- * @returns a Comparator for checking if a i32 value is a valid
- * bitcast conversion from vec2 f16.
- */
-function bitcastVec2F16ToF32Comparator(vec2F16InU16x2: number[]): Comparator {
- assert(vec2F16InU16x2.length === 2);
- const expectations = possible32BitScalarIntervalsFromF16x2(vec2F16InU16x2, 'f32');
- // Return alwaysPass if result is expected unbounded.
- if (expectations.isUnbounded) {
- return anyF32;
- }
- return anyOf(...expectations.possibleExpectations);
-}
-
-/**
- * @returns a Comparator for checking if a vec2 u32 value is a valid
- * bitcast conversion from vec4 f16.
- */
-function bitcastVec4F16ToVec2U32Comparator(vec4F16InU16x4: number[]): Comparator {
- assert(vec4F16InU16x4.length === 4);
- const expectationsPerElement = [vec4F16InU16x4.slice(0, 2), vec4F16InU16x4.slice(2, 4)].map(e =>
- possible32BitScalarIntervalsFromF16x2(e, 'u32')
- );
- // Return alwaysPass if any element is expected unbounded. Although it may be only one unbounded
- // element in the result vector, currently we don't have a way to build a comparator that expect
- // only one element of i32/u32 vector unbounded.
- if (expectationsPerElement.map(e => e.isUnbounded).reduce((a, b) => a || b, false)) {
- return alwaysPass('any vec2<u32>');
- }
- return anyOf(
- ...cartesianProduct(...expectationsPerElement.map(e => e.possibleExpectations)).map(
- e => new Vector(e as Scalar[])
- )
- );
-}
-
-/**
- * @returns a Comparator for checking if a vec2 i32 value is a valid
- * bitcast conversion from vec4 f16.
- */
-function bitcastVec4F16ToVec2I32Comparator(vec4F16InU16x4: number[]): Comparator {
- assert(vec4F16InU16x4.length === 4);
- const expectationsPerElement = [vec4F16InU16x4.slice(0, 2), vec4F16InU16x4.slice(2, 4)].map(e =>
- possible32BitScalarIntervalsFromF16x2(e, 'i32')
- );
- // Return alwaysPass if any element is expected unbounded. Although it may be only one unbounded
- // element in the result vector, currently we don't have a way to build a comparator that expect
- // only one element of i32/u32 vector unbounded.
- if (expectationsPerElement.map(e => e.isUnbounded).reduce((a, b) => a || b, false)) {
- return alwaysPass('any vec2<i32>');
- }
- return anyOf(
- ...cartesianProduct(...expectationsPerElement.map(e => e.possibleExpectations)).map(
- e => new Vector(e as Scalar[])
- )
- );
-}
-
-/**
- * @returns a Comparator for checking if a vec2 f32 value is a valid
- * bitcast conversion from vec4 f16.
- */
-function bitcastVec4F16ToVec2F32Comparator(vec4F16InU16x4: number[]): Comparator {
- assert(vec4F16InU16x4.length === 4);
- const expectationsPerElement = [vec4F16InU16x4.slice(0, 2), vec4F16InU16x4.slice(2, 4)].map(e =>
- possible32BitScalarIntervalsFromF16x2(e, 'f32')
- );
- return anyOf(
- ...cartesianProduct(...expectationsPerElement.map(e => e.possibleExpectations)).map(e => [
- e[0] as FPInterval,
- e[1] as FPInterval,
- ])
- );
-}
-
-export const d = makeCaseCache('bitcast', {
- // Identity Cases
- i32_to_i32: () => fullI32Range().map(e => ({ input: i32(e), expected: i32(e) })),
- u32_to_u32: () => fullU32Range().map(e => ({ input: u32(e), expected: u32(e) })),
- f32_inf_nan_to_f32: () =>
- f32RangeWithInfAndNaN.map(e => ({
- input: f32(e),
- expected: bitcastF32ToF32Comparator(e),
- })),
- f32_to_f32: () =>
- f32FiniteRange.map(e => ({ input: f32(e), expected: bitcastF32ToF32Comparator(e) })),
- f16_inf_nan_to_f16: () =>
- [...f16FiniteInF16, ...f16InfAndNaNInF16].map(e => ({
- input: f16(e),
- expected: bitcastF16ToF16Comparator(e),
- })),
- f16_to_f16: () =>
- f16FiniteInF16.map(e => ({ input: f16(e), expected: bitcastF16ToF16Comparator(e) })),
-
- // i32,u32,f32 to different i32,u32,f32
- i32_to_u32: () => fullI32Range().map(e => ({ input: i32(e), expected: u32(e) })),
- i32_to_f32: () =>
- i32RangeForF32Finite.map(e => ({
- input: i32(e),
- expected: bitcastI32ToF32Comparator(e),
- })),
- i32_to_f32_inf_nan: () =>
- i32RangeForF32FiniteInfNaN.map(e => ({
- input: i32(e),
- expected: bitcastI32ToF32Comparator(e),
- })),
- u32_to_i32: () => fullU32Range().map(e => ({ input: u32(e), expected: i32(e) })),
- u32_to_f32: () =>
- u32RangeForF32Finite.map(e => ({
- input: u32(e),
- expected: bitcastU32ToF32Comparator(e),
- })),
- u32_to_f32_inf_nan: () =>
- u32RangeForF32FiniteInfNaN.map(e => ({
- input: u32(e),
- expected: bitcastU32ToF32Comparator(e),
- })),
- f32_inf_nan_to_i32: () =>
- f32RangeWithInfAndNaN.map(e => ({
- input: f32(e),
- expected: bitcastF32ToI32Comparator(e),
- })),
- f32_to_i32: () =>
- f32FiniteRange.map(e => ({ input: f32(e), expected: bitcastF32ToI32Comparator(e) })),
-
- f32_inf_nan_to_u32: () =>
- f32RangeWithInfAndNaN.map(e => ({
- input: f32(e),
- expected: bitcastF32ToU32Comparator(e),
- })),
- f32_to_u32: () =>
- f32FiniteRange.map(e => ({ input: f32(e), expected: bitcastF32ToU32Comparator(e) })),
-
- // i32,u32,f32 to vec2<f16>
- u32_to_vec2_f16_inf_nan: () =>
- u32RangeForF16Vec2FiniteInfNaN.map(e => ({
- input: u32(e),
- expected: bitcastU32ToVec2F16Comparator(e),
- })),
- u32_to_vec2_f16: () =>
- u32RangeForF16Vec2Finite.map(e => ({
- input: u32(e),
- expected: bitcastU32ToVec2F16Comparator(e),
- })),
- i32_to_vec2_f16_inf_nan: () =>
- i32RangeForF16Vec2FiniteInfNaN.map(e => ({
- input: i32(e),
- expected: bitcastI32ToVec2F16Comparator(e),
- })),
- i32_to_vec2_f16: () =>
- i32RangeForF16Vec2Finite.map(e => ({
- input: i32(e),
- expected: bitcastI32ToVec2F16Comparator(e),
- })),
- f32_inf_nan_to_vec2_f16_inf_nan: () =>
- f32RangeWithInfAndNaNForF16Vec2FiniteInfNaN.map(e => ({
- input: f32(e),
- expected: bitcastF32ToVec2F16Comparator(e),
- })),
- f32_to_vec2_f16: () =>
- f32FiniteRangeForF16Vec2Finite.map(e => ({
- input: f32(e),
- expected: bitcastF32ToVec2F16Comparator(e),
- })),
-
- // vec2<i32>, vec2<u32>, vec2<f32> to vec4<f16>
- vec2_i32_to_vec4_f16_inf_nan: () =>
- slidingSlice(i32RangeForF16Vec2FiniteInfNaN, 2).map(e => ({
- input: toVector(e, i32),
- expected: bitcastVec2I32ToVec4F16Comparator(e),
- })),
- vec2_i32_to_vec4_f16: () =>
- slidingSlice(i32RangeForF16Vec2Finite, 2).map(e => ({
- input: toVector(e, i32),
- expected: bitcastVec2I32ToVec4F16Comparator(e),
- })),
- vec2_u32_to_vec4_f16_inf_nan: () =>
- slidingSlice(u32RangeForF16Vec2FiniteInfNaN, 2).map(e => ({
- input: toVector(e, u32),
- expected: bitcastVec2U32ToVec4F16Comparator(e),
- })),
- vec2_u32_to_vec4_f16: () =>
- slidingSlice(u32RangeForF16Vec2Finite, 2).map(e => ({
- input: toVector(e, u32),
- expected: bitcastVec2U32ToVec4F16Comparator(e),
- })),
- vec2_f32_inf_nan_to_vec4_f16_inf_nan: () =>
- slidingSlice(f32RangeWithInfAndNaNForF16Vec2FiniteInfNaN, 2).map(e => ({
- input: toVector(e, f32),
- expected: bitcastVec2F32ToVec4F16Comparator(e),
- })),
- vec2_f32_to_vec4_f16: () =>
- slidingSlice(f32FiniteRangeForF16Vec2Finite, 2).map(e => ({
- input: toVector(e, f32),
- expected: bitcastVec2F32ToVec4F16Comparator(e),
- })),
-
- // vec2<f16> to i32, u32, f32
- vec2_f16_to_u32: () =>
- f16Vec2FiniteInU16x2.map(e => ({
- input: u16x2ToVec2F16(e),
- expected: bitcastVec2F16ToU32Comparator(e),
- })),
- vec2_f16_inf_nan_to_u32: () =>
- f16Vec2FiniteInfNanInU16x2.map(e => ({
- input: u16x2ToVec2F16(e),
- expected: bitcastVec2F16ToU32Comparator(e),
- })),
- vec2_f16_to_i32: () =>
- f16Vec2FiniteInU16x2.map(e => ({
- input: u16x2ToVec2F16(e),
- expected: bitcastVec2F16ToI32Comparator(e),
- })),
- vec2_f16_inf_nan_to_i32: () =>
- f16Vec2FiniteInfNanInU16x2.map(e => ({
- input: u16x2ToVec2F16(e),
- expected: bitcastVec2F16ToI32Comparator(e),
- })),
- vec2_f16_to_f32_finite: () =>
- f16Vec2FiniteInU16x2
- .filter(u16x2 => isFiniteF32(reinterpretU32AsF32(u16x2ToU32(u16x2))))
- .map(e => ({
- input: u16x2ToVec2F16(e),
- expected: bitcastVec2F16ToF32Comparator(e),
- })),
- vec2_f16_inf_nan_to_f32: () =>
- f16Vec2FiniteInfNanInU16x2.map(e => ({
- input: u16x2ToVec2F16(e),
- expected: bitcastVec2F16ToF32Comparator(e),
- })),
-
- // vec4<f16> to vec2 of i32, u32, f32
- vec4_f16_to_vec2_u32: () =>
- f16Vec2FiniteInU16x4.map(e => ({
- input: u16x4ToVec4F16(e),
- expected: bitcastVec4F16ToVec2U32Comparator(e),
- })),
- vec4_f16_inf_nan_to_vec2_u32: () =>
- f16Vec2FiniteInfNanInU16x4.map(e => ({
- input: u16x4ToVec4F16(e),
- expected: bitcastVec4F16ToVec2U32Comparator(e),
- })),
- vec4_f16_to_vec2_i32: () =>
- f16Vec2FiniteInU16x4.map(e => ({
- input: u16x4ToVec4F16(e),
- expected: bitcastVec4F16ToVec2I32Comparator(e),
- })),
- vec4_f16_inf_nan_to_vec2_i32: () =>
- f16Vec2FiniteInfNanInU16x4.map(e => ({
- input: u16x4ToVec4F16(e),
- expected: bitcastVec4F16ToVec2I32Comparator(e),
- })),
- vec4_f16_to_vec2_f32_finite: () =>
- f16Vec2FiniteInU16x4
- .filter(
- u16x4 =>
- isFiniteF32(reinterpretU32AsF32(u16x2ToU32(u16x4.slice(0, 2)))) &&
- isFiniteF32(reinterpretU32AsF32(u16x2ToU32(u16x4.slice(2, 4))))
- )
- .map(e => ({
- input: u16x4ToVec4F16(e),
- expected: bitcastVec4F16ToVec2F32Comparator(e),
- })),
- vec4_f16_inf_nan_to_vec2_f32: () =>
- f16Vec2FiniteInfNanInU16x4.map(e => ({
- input: u16x4ToVec4F16(e),
- expected: bitcastVec4F16ToVec2F32Comparator(e),
- })),
-});
-
/**
* @returns a ShaderBuilder that generates a call to bitcast,
* using appropriate destination type, which optionally can be
@@ -865,7 +71,7 @@ g.test('i32_to_i32')
)
.fn(async t => {
const cases = await d.get('i32_to_i32');
- await run(t, bitcastBuilder('i32', t.params), [TypeI32], TypeI32, t.params, cases);
+ await run(t, bitcastBuilder('i32', t.params), [Type.i32], Type.i32, t.params, cases);
});
g.test('u32_to_u32')
@@ -879,7 +85,7 @@ g.test('u32_to_u32')
)
.fn(async t => {
const cases = await d.get('u32_to_u32');
- await run(t, bitcastBuilder('u32', t.params), [TypeU32], TypeU32, t.params, cases);
+ await run(t, bitcastBuilder('u32', t.params), [Type.u32], Type.u32, t.params, cases);
});
g.test('f32_to_f32')
@@ -896,7 +102,7 @@ g.test('f32_to_f32')
// Infinities and NaNs are errors in const-eval.
t.params.inputSource === 'const' ? 'f32_to_f32' : 'f32_inf_nan_to_f32'
);
- await run(t, bitcastBuilder('f32', t.params), [TypeF32], TypeF32, t.params, cases);
+ await run(t, bitcastBuilder('f32', t.params), [Type.f32], Type.f32, t.params, cases);
});
// To i32 from u32, f32
@@ -911,7 +117,7 @@ g.test('u32_to_i32')
)
.fn(async t => {
const cases = await d.get('u32_to_i32');
- await run(t, bitcastBuilder('i32', t.params), [TypeU32], TypeI32, t.params, cases);
+ await run(t, bitcastBuilder('i32', t.params), [Type.u32], Type.i32, t.params, cases);
});
g.test('f32_to_i32')
@@ -928,7 +134,7 @@ g.test('f32_to_i32')
// Infinities and NaNs are errors in const-eval.
t.params.inputSource === 'const' ? 'f32_to_i32' : 'f32_inf_nan_to_i32'
);
- await run(t, bitcastBuilder('i32', t.params), [TypeF32], TypeI32, t.params, cases);
+ await run(t, bitcastBuilder('i32', t.params), [Type.f32], Type.i32, t.params, cases);
});
// To u32 from i32, f32
@@ -943,7 +149,7 @@ g.test('i32_to_u32')
)
.fn(async t => {
const cases = await d.get('i32_to_u32');
- await run(t, bitcastBuilder('u32', t.params), [TypeI32], TypeU32, t.params, cases);
+ await run(t, bitcastBuilder('u32', t.params), [Type.i32], Type.u32, t.params, cases);
});
g.test('f32_to_u32')
@@ -960,7 +166,7 @@ g.test('f32_to_u32')
// Infinities and NaNs are errors in const-eval.
t.params.inputSource === 'const' ? 'f32_to_u32' : 'f32_inf_nan_to_u32'
);
- await run(t, bitcastBuilder('u32', t.params), [TypeF32], TypeU32, t.params, cases);
+ await run(t, bitcastBuilder('u32', t.params), [Type.f32], Type.u32, t.params, cases);
});
// To f32 from i32, u32
@@ -978,7 +184,7 @@ g.test('i32_to_f32')
// Infinities and NaNs are errors in const-eval.
t.params.inputSource === 'const' ? 'i32_to_f32' : 'i32_to_f32_inf_nan'
);
- await run(t, bitcastBuilder('f32', t.params), [TypeI32], TypeF32, t.params, cases);
+ await run(t, bitcastBuilder('f32', t.params), [Type.i32], Type.f32, t.params, cases);
});
g.test('u32_to_f32')
@@ -995,7 +201,7 @@ g.test('u32_to_f32')
// Infinities and NaNs are errors in const-eval.
t.params.inputSource === 'const' ? 'u32_to_f32' : 'u32_to_f32_inf_nan'
);
- await run(t, bitcastBuilder('f32', t.params), [TypeU32], TypeF32, t.params, cases);
+ await run(t, bitcastBuilder('f32', t.params), [Type.u32], Type.f32, t.params, cases);
});
// 16 bit types
@@ -1020,7 +226,7 @@ g.test('f16_to_f16')
// Infinities and NaNs are errors in const-eval.
t.params.inputSource === 'const' ? 'f16_to_f16' : 'f16_inf_nan_to_f16'
);
- await run(t, bitcastBuilder('f16', t.params), [TypeF16], TypeF16, t.params, cases);
+ await run(t, bitcastBuilder('f16', t.params), [Type.f16], Type.f16, t.params, cases);
});
// f16: 32-bit scalar numeric to vec2<f16>
@@ -1036,14 +242,7 @@ g.test('i32_to_vec2h')
// Infinities and NaNs are errors in const-eval.
t.params.inputSource === 'const' ? 'i32_to_vec2_f16' : 'i32_to_vec2_f16_inf_nan'
);
- await run(
- t,
- bitcastBuilder('vec2<f16>', t.params),
- [TypeI32],
- TypeVec(2, TypeF16),
- t.params,
- cases
- );
+ await run(t, bitcastBuilder('vec2<f16>', t.params), [Type.i32], Type.vec2h, t.params, cases);
});
g.test('u32_to_vec2h')
@@ -1058,14 +257,7 @@ g.test('u32_to_vec2h')
// Infinities and NaNs are errors in const-eval.
t.params.inputSource === 'const' ? 'u32_to_vec2_f16' : 'u32_to_vec2_f16_inf_nan'
);
- await run(
- t,
- bitcastBuilder('vec2<f16>', t.params),
- [TypeU32],
- TypeVec(2, TypeF16),
- t.params,
- cases
- );
+ await run(t, bitcastBuilder('vec2<f16>', t.params), [Type.u32], Type.vec2h, t.params, cases);
});
g.test('f32_to_vec2h')
@@ -1080,14 +272,7 @@ g.test('f32_to_vec2h')
// Infinities and NaNs are errors in const-eval.
t.params.inputSource === 'const' ? 'f32_to_vec2_f16' : 'f32_inf_nan_to_vec2_f16_inf_nan'
);
- await run(
- t,
- bitcastBuilder('vec2<f16>', t.params),
- [TypeF32],
- TypeVec(2, TypeF16),
- t.params,
- cases
- );
+ await run(t, bitcastBuilder('vec2<f16>', t.params), [Type.f32], Type.vec2h, t.params, cases);
});
// f16: vec2<32-bit scalar numeric> to vec4<f16>
@@ -1103,14 +288,7 @@ g.test('vec2i_to_vec4h')
// Infinities and NaNs are errors in const-eval.
t.params.inputSource === 'const' ? 'vec2_i32_to_vec4_f16' : 'vec2_i32_to_vec4_f16_inf_nan'
);
- await run(
- t,
- bitcastBuilder('vec4<f16>', t.params),
- [TypeVec(2, TypeI32)],
- TypeVec(4, TypeF16),
- t.params,
- cases
- );
+ await run(t, bitcastBuilder('vec4<f16>', t.params), [Type.vec2i], Type.vec4h, t.params, cases);
});
g.test('vec2u_to_vec4h')
@@ -1125,14 +303,7 @@ g.test('vec2u_to_vec4h')
// Infinities and NaNs are errors in const-eval.
t.params.inputSource === 'const' ? 'vec2_u32_to_vec4_f16' : 'vec2_u32_to_vec4_f16_inf_nan'
);
- await run(
- t,
- bitcastBuilder('vec4<f16>', t.params),
- [TypeVec(2, TypeU32)],
- TypeVec(4, TypeF16),
- t.params,
- cases
- );
+ await run(t, bitcastBuilder('vec4<f16>', t.params), [Type.vec2u], Type.vec4h, t.params, cases);
});
g.test('vec2f_to_vec4h')
@@ -1149,14 +320,7 @@ g.test('vec2f_to_vec4h')
? 'vec2_f32_to_vec4_f16'
: 'vec2_f32_inf_nan_to_vec4_f16_inf_nan'
);
- await run(
- t,
- bitcastBuilder('vec4<f16>', t.params),
- [TypeVec(2, TypeF32)],
- TypeVec(4, TypeF16),
- t.params,
- cases
- );
+ await run(t, bitcastBuilder('vec4<f16>', t.params), [Type.vec2f], Type.vec4h, t.params, cases);
});
// f16: vec2<f16> to 32-bit scalar numeric
@@ -1172,7 +336,7 @@ g.test('vec2h_to_i32')
// Infinities and NaNs are errors in const-eval.
t.params.inputSource === 'const' ? 'vec2_f16_to_i32' : 'vec2_f16_inf_nan_to_i32'
);
- await run(t, bitcastBuilder('i32', t.params), [TypeVec(2, TypeF16)], TypeI32, t.params, cases);
+ await run(t, bitcastBuilder('i32', t.params), [Type.vec2h], Type.i32, t.params, cases);
});
g.test('vec2h_to_u32')
@@ -1187,7 +351,7 @@ g.test('vec2h_to_u32')
// Infinities and NaNs are errors in const-eval.
t.params.inputSource === 'const' ? 'vec2_f16_to_u32' : 'vec2_f16_inf_nan_to_u32'
);
- await run(t, bitcastBuilder('u32', t.params), [TypeVec(2, TypeF16)], TypeU32, t.params, cases);
+ await run(t, bitcastBuilder('u32', t.params), [Type.vec2h], Type.u32, t.params, cases);
});
g.test('vec2h_to_f32')
@@ -1202,7 +366,7 @@ g.test('vec2h_to_f32')
// Infinities and NaNs are errors in const-eval.
t.params.inputSource === 'const' ? 'vec2_f16_to_f32_finite' : 'vec2_f16_inf_nan_to_f32'
);
- await run(t, bitcastBuilder('f32', t.params), [TypeVec(2, TypeF16)], TypeF32, t.params, cases);
+ await run(t, bitcastBuilder('f32', t.params), [Type.vec2h], Type.f32, t.params, cases);
});
// f16: vec4<f16> to vec2<32-bit scalar numeric>
@@ -1218,14 +382,7 @@ g.test('vec4h_to_vec2i')
// Infinities and NaNs are errors in const-eval.
t.params.inputSource === 'const' ? 'vec4_f16_to_vec2_i32' : 'vec4_f16_inf_nan_to_vec2_i32'
);
- await run(
- t,
- bitcastBuilder('vec2<i32>', t.params),
- [TypeVec(4, TypeF16)],
- TypeVec(2, TypeI32),
- t.params,
- cases
- );
+ await run(t, bitcastBuilder('vec2<i32>', t.params), [Type.vec4h], Type.vec2i, t.params, cases);
});
g.test('vec4h_to_vec2u')
@@ -1240,14 +397,7 @@ g.test('vec4h_to_vec2u')
// Infinities and NaNs are errors in const-eval.
t.params.inputSource === 'const' ? 'vec4_f16_to_vec2_u32' : 'vec4_f16_inf_nan_to_vec2_u32'
);
- await run(
- t,
- bitcastBuilder('vec2<u32>', t.params),
- [TypeVec(4, TypeF16)],
- TypeVec(2, TypeU32),
- t.params,
- cases
- );
+ await run(t, bitcastBuilder('vec2<u32>', t.params), [Type.vec4h], Type.vec2u, t.params, cases);
});
g.test('vec4h_to_vec2f')
@@ -1264,12 +414,230 @@ g.test('vec4h_to_vec2f')
? 'vec4_f16_to_vec2_f32_finite'
: 'vec4_f16_inf_nan_to_vec2_f32'
);
+ await run(t, bitcastBuilder('vec2<f32>', t.params), [Type.vec4h], Type.vec2f, t.params, cases);
+ });
+
+// Abstract Float
+g.test('af_to_f32')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast abstract float to f32 tests`)
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = scalarF32Range().map(u => {
+ const res = FP['f32'].addFlushedIfNeeded([u]).map(f => {
+ return f32(f);
+ });
+ return {
+ input: abstractFloat(u),
+ expected: anyOf(...res),
+ };
+ });
+
+ await run(t, bitcastBuilder('f32', t.params), [Type.abstractFloat], Type.f32, t.params, cases);
+ });
+
+g.test('af_to_i32')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast abstract float to i32 tests`)
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const values = [
+ 0,
+ 1,
+ 10,
+ 256,
+ u32Bits(0b11111111011111111111111111111111).value,
+ u32Bits(0b11111111010000000000000000000000).value,
+ u32Bits(0b11111110110000000000000000000000).value,
+ u32Bits(0b11111101110000000000000000000000).value,
+ u32Bits(0b11111011110000000000000000000000).value,
+ u32Bits(0b11110111110000000000000000000000).value,
+ u32Bits(0b11101111110000000000000000000000).value,
+ u32Bits(0b11011111110000000000000000000000).value,
+ u32Bits(0b10111111110000000000000000000000).value,
+ u32Bits(0b01111111011111111111111111111111).value,
+ u32Bits(0b01111111010000000000000000000000).value,
+ u32Bits(0b01111110110000000000000000000000).value,
+ u32Bits(0b01111101110000000000000000000000).value,
+ u32Bits(0b01111011110000000000000000000000).value,
+ u32Bits(0b01110111110000000000000000000000).value,
+ u32Bits(0b01101111110000000000000000000000).value,
+ u32Bits(0b01011111110000000000000000000000).value,
+ u32Bits(0b00111111110000000000000000000000).value,
+ ];
+
+ const cases = values.map(u => {
+ return {
+ input: abstractFloat(uint32ToFloat32(u)),
+ expected: i32(u),
+ };
+ });
+
+ await run(t, bitcastBuilder('i32', t.params), [Type.abstractFloat], Type.i32, t.params, cases);
+ });
+
+g.test('af_to_u32')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast abstract float to u32 tests`)
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const values = [
+ 0,
+ 1,
+ 10,
+ 256,
+ u32Bits(0b11111111011111111111111111111111).value,
+ u32Bits(0b11111111010000000000000000000000).value,
+ u32Bits(0b11111110110000000000000000000000).value,
+ u32Bits(0b11111101110000000000000000000000).value,
+ u32Bits(0b11111011110000000000000000000000).value,
+ u32Bits(0b11110111110000000000000000000000).value,
+ u32Bits(0b11101111110000000000000000000000).value,
+ u32Bits(0b11011111110000000000000000000000).value,
+ u32Bits(0b10111111110000000000000000000000).value,
+ u32Bits(0b01111111011111111111111111111111).value,
+ u32Bits(0b01111111010000000000000000000000).value,
+ u32Bits(0b01111110110000000000000000000000).value,
+ u32Bits(0b01111101110000000000000000000000).value,
+ u32Bits(0b01111011110000000000000000000000).value,
+ u32Bits(0b01110111110000000000000000000000).value,
+ u32Bits(0b01101111110000000000000000000000).value,
+ u32Bits(0b01011111110000000000000000000000).value,
+ u32Bits(0b00111111110000000000000000000000).value,
+ ];
+
+ const cases = values.map(u => {
+ return {
+ input: abstractFloat(uint32ToFloat32(u)),
+ expected: u32(u),
+ };
+ });
+
+ await run(t, bitcastBuilder('u32', t.params), [Type.abstractFloat], Type.u32, t.params, cases);
+ });
+
+g.test('af_to_vec2f16')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast abstract float to f16 tests`)
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('af_to_vec2_f16');
+
+ await run(
+ t,
+ bitcastBuilder('vec2<f16>', t.params),
+ [Type.abstractFloat],
+ Type.vec2h,
+ t.params,
+ cases
+ );
+ });
+
+g.test('vec2af_to_vec4f16')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast abstract float to f16 tests`)
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('vec2_af_to_vec4_f16');
+
+ await run(
+ t,
+ bitcastBuilder('vec4<f16>', t.params),
+ [Type.vec(2, Type.abstractFloat)],
+ Type.vec4h,
+ t.params,
+ cases
+ );
+ });
+
+// Abstract Int
+g.test('ai_to_i32')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast abstract int to i32 tests`)
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ .combine('alias', [false, true])
+ )
+ .fn(async t => {
+ const cases = await d.get('ai_to_i32');
+ await run(t, bitcastBuilder('i32', t.params), [Type.abstractInt], Type.i32, t.params, cases);
+ });
+
+g.test('ai_to_u32')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast abstract int to u32 tests`)
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ .combine('alias', [false, true])
+ )
+ .fn(async t => {
+ const cases = await d.get('ai_to_u32');
+ await run(t, bitcastBuilder('u32', t.params), [Type.abstractInt], Type.u32, t.params, cases);
+ });
+
+g.test('ai_to_f32')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast abstract int to f32 tests`)
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ .combine('alias', [false, true])
+ )
+ .fn(async t => {
+ const cases = await d.get('ai_to_f32');
+ await run(t, bitcastBuilder('f32', t.params), [Type.abstractInt], Type.f32, t.params, cases);
+ });
+
+g.test('ai_to_vec2h')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast ai to vec2h tests`)
+ .params(u => u.combine('inputSource', onlyConstInputSource).combine('alias', [false, true]))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get('ai_to_vec2_f16');
await run(
t,
- bitcastBuilder('vec2<f32>', t.params),
- [TypeVec(4, TypeF16)],
- TypeVec(2, TypeF32),
+ bitcastBuilder('vec2<f16>', t.params),
+ [Type.abstractInt],
+ Type.vec2h,
t.params,
cases
);
});
+
+g.test('vec2ai_to_vec4h')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast vec2ai to vec4h tests`)
+ .params(u => u.combine('inputSource', onlyConstInputSource).combine('alias', [false, true]))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get('vec2_ai_to_vec4_f16');
+ await run(t, bitcastBuilder('vec4<f16>', t.params), [Type.vec2ai], Type.vec4h, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/builtin.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/builtin.ts
index 282feea703..0afd8c3980 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/builtin.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/builtin.ts
@@ -1,5 +1,6 @@
import {
abstractFloatShaderBuilder,
+ abstractIntShaderBuilder,
basicExpressionBuilder,
basicExpressionWithPredeclarationBuilder,
ShaderBuilder,
@@ -11,10 +12,15 @@ export function builtin(name: string): ShaderBuilder {
}
/* @returns a ShaderBuilder that calls the builtin with the given name that returns AbstractFloats */
-export function abstractBuiltin(name: string): ShaderBuilder {
+export function abstractFloatBuiltin(name: string): ShaderBuilder {
return abstractFloatShaderBuilder(values => `${name}(${values.join(', ')})`);
}
+/* @returns a ShaderBuilder that calls the builtin with the given name that returns AbstractInts */
+export function abstractIntBuiltin(name: string): ShaderBuilder {
+ return abstractIntShaderBuilder(values => `${name}(${values.join(', ')})`);
+}
+
/* @returns a ShaderBuilder that calls the builtin with the given name and has given predeclaration */
export function builtinWithPredeclaration(name: string, predeclaration: string): ShaderBuilder {
return basicExpressionWithPredeclarationBuilder(
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ceil.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ceil.cache.ts
new file mode 100644
index 0000000000..c0178d9b83
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ceil.cache.ts
@@ -0,0 +1,26 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+const kSmallMagnitudeTestValues = [0.1, 0.9, 1.0, 1.1, 1.9, -0.1, -0.9, -1.0, -1.1, -1.9];
+
+// See https://github.com/gpuweb/cts/issues/2766 for details
+const kIssue2766Value = {
+ f32: 0x8000_0000,
+ f16: 0x8000,
+ abstract: 0x8000_0000_0000_0000,
+};
+
+// Cases: [f32|f16]
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .map(trait => ({
+ [`${trait}`]: () => {
+ return FP[trait].generateScalarToIntervalCases(
+ [...kSmallMagnitudeTestValues, kIssue2766Value[trait], ...FP[trait].scalarRange()],
+ 'unfiltered',
+ FP[trait].ceilInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('ceil', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ceil.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ceil.spec.ts
index 6cdf90986b..842875f094 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ceil.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ceil.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'ceil' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn ceil(e: T ) -> T
Returns the ceiling of e. Component-wise when T is a vector.
@@ -10,70 +10,33 @@ Returns the ceiling of e. Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './ceil.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('ceil', {
- f32: () => {
- return FP.f32.generateScalarToIntervalCases(
- [
- // Small positive numbers
- 0.1,
- 0.9,
- 1.0,
- 1.1,
- 1.9,
- // Small negative numbers
- -0.1,
- -0.9,
- -1.0,
- -1.1,
- -1.9,
- 0x80000000, // https://github.com/gpuweb/cts/issues/2766
- ...fullF32Range(),
- ],
- 'unfiltered',
- FP.f32.ceilInterval
- );
- },
- f16: () => {
- return FP.f16.generateScalarToIntervalCases(
- [
- // Small positive numbers
- 0.1,
- 0.9,
- 1.0,
- 1.1,
- 1.9,
- // Small negative numbers
- -0.1,
- -0.9,
- -1.0,
- -1.1,
- -1.9,
- 0x8000, // https://github.com/gpuweb/cts/issues/2766
- ...fullF16Range(),
- ],
- 'unfiltered',
- FP.f16.ceilInterval
- );
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractFloatBuiltin('ceil'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -83,7 +46,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('ceil'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('ceil'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -97,5 +60,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('ceil'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('ceil'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/clamp.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/clamp.cache.ts
new file mode 100644
index 0000000000..909d15e7e7
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/clamp.cache.ts
@@ -0,0 +1,131 @@
+import { kValue } from '../../../../../util/constants.js';
+import { ScalarType, Type } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { maxBigInt, minBigInt } from '../../../../../util/math.js';
+import { Case } from '../../case.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+const u32Values = [0, 1, 2, 3, 0x70000000, 0x80000000, kValue.u32.max];
+
+const i32Values = [
+ kValue.i32.negative.min,
+ -3,
+ -2,
+ -1,
+ 0,
+ 1,
+ 2,
+ 3,
+ 0x70000000,
+ kValue.i32.positive.max,
+];
+
+const abstractFloatValues = [
+ kValue.i64.negative.min,
+ -3n,
+ -2n,
+ -1n,
+ 0n,
+ 1n,
+ 2n,
+ 3n,
+ 0x70000000n,
+ kValue.i64.positive.max,
+];
+
+/** @returns a set of clamp test cases from an ascending list of concrete integer values */
+function generateConcreteIntegerTestCases(
+ test_values: Array<number>,
+ type: ScalarType,
+ stage: 'const' | 'non_const'
+): Array<Case> {
+ return test_values.flatMap(low =>
+ test_values.flatMap(high =>
+ stage === 'const' && low > high
+ ? []
+ : test_values.map(e => ({
+ input: [type.create(e), type.create(low), type.create(high)],
+ expected: type.create(Math.min(Math.max(e, low), high)),
+ }))
+ )
+ );
+}
+
+/** @returns a set of clamp test cases from an ascending list of abstract integer values */
+function generateAbstractIntegerTestCases(test_values: Array<bigint>): Array<Case> {
+ return test_values.flatMap(low =>
+ test_values.flatMap(high =>
+ low > high
+ ? []
+ : test_values.map(e => ({
+ input: [
+ Type.abstractInt.create(e),
+ Type.abstractInt.create(low),
+ Type.abstractInt.create(high),
+ ],
+ expected: Type.abstractInt.create(minBigInt(maxBigInt(e, low), high)),
+ }))
+ )
+ );
+}
+
+function generateFloatTestCases(
+ test_values: readonly number[],
+ trait: 'f32' | 'f16' | 'abstract',
+ stage: 'const' | 'non_const'
+): Array<Case> {
+ return test_values.flatMap(low =>
+ test_values.flatMap(high =>
+ stage === 'const' && low > high
+ ? []
+ : test_values.flatMap(e => {
+ const c = FP[trait].makeScalarTripleToIntervalCase(
+ e,
+ low,
+ high,
+ stage === 'const' ? 'finite' : 'unfiltered',
+ ...FP[trait].clampIntervals
+ );
+ return c === undefined ? [] : [c];
+ })
+ )
+ );
+}
+
+// Cases: [f32|f16|abstract]_[non_]const
+// abstract_non_const is empty and unused
+const fp_cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return generateFloatTestCases(
+ FP[trait].sparseScalarRange(),
+ trait,
+ nonConst ? 'non_const' : 'const'
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('clamp', {
+ u32_non_const: () => {
+ return generateConcreteIntegerTestCases(u32Values, Type.u32, 'non_const');
+ },
+ u32_const: () => {
+ return generateConcreteIntegerTestCases(u32Values, Type.u32, 'const');
+ },
+ i32_non_const: () => {
+ return generateConcreteIntegerTestCases(i32Values, Type.i32, 'non_const');
+ },
+ i32_const: () => {
+ return generateConcreteIntegerTestCases(i32Values, Type.i32, 'const');
+ },
+ abstract_int: () => {
+ return generateAbstractIntegerTestCases(abstractFloatValues);
+ },
+ ...fp_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/clamp.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/clamp.spec.ts
index 0113fd656f..0b524bccf0 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/clamp.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/clamp.spec.ts
@@ -1,12 +1,12 @@
export const description = `
Execution tests for the 'clamp' builtin function
-S is AbstractInt, i32, or u32
+S is abstract-int, i32, or u32
T is S or vecN<S>
@const fn clamp(e: T , low: T, high: T) -> T
Returns min(max(e,low),high). Component-wise when T is a vector.
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const clamp(e: T , low: T , high: T) -> T
Returns either min(max(e,low),high), or the median of the three values e, low, high.
@@ -15,117 +15,33 @@ Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { kValue } from '../../../../../util/constants.js';
-import {
- ScalarType,
- TypeF32,
- TypeF16,
- TypeI32,
- TypeU32,
- TypeAbstractFloat,
-} from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { sparseF32Range, sparseF16Range, sparseF64Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, Case, onlyConstInputSource, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { abstractBuiltin, builtin } from './builtin.js';
+import { abstractFloatBuiltin, abstractIntBuiltin, builtin } from './builtin.js';
+import { d } from './clamp.cache.js';
export const g = makeTestGroup(GPUTest);
-const u32Values = [0, 1, 2, 3, 0x70000000, 0x80000000, kValue.u32.max];
-
-const i32Values = [
- kValue.i32.negative.min,
- -3,
- -2,
- -1,
- 0,
- 1,
- 2,
- 3,
- 0x70000000,
- kValue.i32.positive.max,
-];
-
-export const d = makeCaseCache('clamp', {
- u32_non_const: () => {
- return generateIntegerTestCases(u32Values, TypeU32, 'non-const');
- },
- u32_const: () => {
- return generateIntegerTestCases(u32Values, TypeU32, 'const');
- },
- i32_non_const: () => {
- return generateIntegerTestCases(i32Values, TypeI32, 'non-const');
- },
- i32_const: () => {
- return generateIntegerTestCases(i32Values, TypeI32, 'const');
- },
- f32_const: () => {
- return generateFloatTestCases(sparseF32Range(), 'f32', 'const');
- },
- f32_non_const: () => {
- return generateFloatTestCases(sparseF32Range(), 'f32', 'non-const');
- },
- f16_const: () => {
- return generateFloatTestCases(sparseF16Range(), 'f16', 'const');
- },
- f16_non_const: () => {
- return generateFloatTestCases(sparseF16Range(), 'f16', 'non-const');
- },
- abstract: () => {
- return generateFloatTestCases(sparseF64Range(), 'abstract', 'const');
- },
-});
-
-/** @returns a set of clamp test cases from an ascending list of integer values */
-function generateIntegerTestCases(
- test_values: Array<number>,
- type: ScalarType,
- stage: 'const' | 'non-const'
-): Array<Case> {
- return test_values.flatMap(low =>
- test_values.flatMap(high =>
- stage === 'const' && low > high
- ? []
- : test_values.map(e => ({
- input: [type.create(e), type.create(low), type.create(high)],
- expected: type.create(Math.min(Math.max(e, low), high)),
- }))
- )
- );
-}
-
-function generateFloatTestCases(
- test_values: readonly number[],
- trait: 'f32' | 'f16' | 'abstract',
- stage: 'const' | 'non-const'
-): Array<Case> {
- return test_values.flatMap(low =>
- test_values.flatMap(high =>
- stage === 'const' && low > high
- ? []
- : test_values.flatMap(e => {
- const c = FP[trait].makeScalarTripleToIntervalCase(
- e,
- low,
- high,
- stage === 'const' ? 'finite' : 'unfiltered',
- ...FP[trait].clampIntervals
- );
- return c === undefined ? [] : [c];
- })
- )
- );
-}
-
g.test('abstract_int')
.specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
.desc(`abstract int tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract_int');
+ await run(
+ t,
+ abstractIntBuiltin('clamp'),
+ [Type.abstractInt, Type.abstractInt, Type.abstractInt],
+ Type.abstractInt,
+ t.params,
+ cases
+ );
+ });
g.test('u32')
.specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
@@ -135,7 +51,7 @@ g.test('u32')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'u32_const' : 'u32_non_const');
- await run(t, builtin('clamp'), [TypeU32, TypeU32, TypeU32], TypeU32, t.params, cases);
+ await run(t, builtin('clamp'), [Type.u32, Type.u32, Type.u32], Type.u32, t.params, cases);
});
g.test('i32')
@@ -146,7 +62,7 @@ g.test('i32')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'i32_const' : 'i32_non_const');
- await run(t, builtin('clamp'), [TypeI32, TypeI32, TypeI32], TypeI32, t.params, cases);
+ await run(t, builtin('clamp'), [Type.i32, Type.i32, Type.i32], Type.i32, t.params, cases);
});
g.test('abstract_float')
@@ -158,12 +74,12 @@ g.test('abstract_float')
.combine('vectorize', [undefined, 2, 3, 4] as const)
)
.fn(async t => {
- const cases = await d.get('abstract');
+ const cases = await d.get('abstract_const');
await run(
t,
- abstractBuiltin('clamp'),
- [TypeAbstractFloat, TypeAbstractFloat, TypeAbstractFloat],
- TypeAbstractFloat,
+ abstractFloatBuiltin('clamp'),
+ [Type.abstractFloat, Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
t.params,
cases
);
@@ -177,7 +93,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('clamp'), [TypeF32, TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('clamp'), [Type.f32, Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -191,5 +107,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('clamp'), [TypeF16, TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('clamp'), [Type.f16, Type.f16, Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cos.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cos.cache.ts
new file mode 100644
index 0000000000..11c9f50a1f
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cos.cache.ts
@@ -0,0 +1,23 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .map(trait => ({
+ [`${trait}`]: () => {
+ return FP[trait].generateScalarToIntervalCases(
+ [
+ // Well-defined accuracy range
+ ...linearRange(-Math.PI, Math.PI, 100),
+ ...FP[trait].scalarRange(),
+ ],
+ trait === 'abstract' ? 'finite' : 'unfiltered',
+ // cos has an absolute accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].cosInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('cos', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cos.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cos.spec.ts
index 723bca2efd..5675f220bf 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cos.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cos.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'cos' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn cos(e: T ) -> T
Returns the cosine of e. Component-wise when T is a vector.
@@ -9,48 +9,33 @@ Returns the cosine of e. Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF16Range, linearRange } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './cos.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('cos', {
- f32: () => {
- return FP.f32.generateScalarToIntervalCases(
- [
- // Well-defined accuracy range
- ...linearRange(-Math.PI, Math.PI, 1000),
- ...fullF32Range(),
- ],
- 'unfiltered',
- FP.f32.cosInterval
- );
- },
- f16: () => {
- return FP.f16.generateScalarToIntervalCases(
- [
- // Well-defined accuracy range
- ...linearRange(-Math.PI, Math.PI, 1000),
- ...fullF16Range(),
- ],
- 'unfiltered',
- FP.f16.cosInterval
- );
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractFloatBuiltin('cos'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -66,7 +51,7 @@ TODO(#792): Decide what the ground-truth is for these tests. [1]
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('cos'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('cos'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -80,5 +65,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('cos'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('cos'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cosh.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cosh.cache.ts
new file mode 100644
index 0000000000..0c90f178df
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cosh.cache.ts
@@ -0,0 +1,23 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]_[non_]const
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateScalarToIntervalCases(
+ FP[trait].scalarRange(),
+ nonConst ? 'unfiltered' : 'finite',
+ // cosh has an inherited accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].coshInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('cosh', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cosh.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cosh.spec.ts
index 37fb961c98..d043df9527 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cosh.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cosh.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'cosh' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn cosh(e: T ) -> T
Returns the hyperbolic cosine of e. Component-wise when T is a vector
@@ -9,38 +9,33 @@ Returns the hyperbolic cosine of e. Component-wise when T is a vector
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './cosh.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('cosh', {
- f32_const: () => {
- return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'finite', FP.f32.coshInterval);
- },
- f32_non_const: () => {
- return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.coshInterval);
- },
- f16_const: () => {
- return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'finite', FP.f16.coshInterval);
- },
- f16_non_const: () => {
- return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'unfiltered', FP.f16.coshInterval);
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract_const');
+ await run(
+ t,
+ abstractFloatBuiltin('cosh'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -50,7 +45,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('cosh'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('cosh'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -64,5 +59,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('cosh'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('cosh'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countLeadingZeros.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countLeadingZeros.spec.ts
index cfae4bb6e0..ea0c38ae58 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countLeadingZeros.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countLeadingZeros.spec.ts
@@ -12,7 +12,7 @@ Also known as "clz" in some languages.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeU32, u32Bits, u32, TypeI32, i32Bits, i32 } from '../../../../../util/conversion.js';
+import { Type, u32Bits, u32, i32Bits, i32 } from '../../../../../util/conversion.js';
import { allInputSources, Config, run } from '../../expression.js';
import { builtin } from './builtin.js';
@@ -27,7 +27,7 @@ g.test('u32')
)
.fn(async t => {
const cfg: Config = t.params;
- await run(t, builtin('countLeadingZeros'), [TypeU32], TypeU32, cfg, [
+ await run(t, builtin('countLeadingZeros'), [Type.u32], Type.u32, cfg, [
// Zero
{ input: u32Bits(0b00000000000000000000000000000000), expected: u32(32) },
@@ -142,7 +142,7 @@ g.test('i32')
)
.fn(async t => {
const cfg: Config = t.params;
- await run(t, builtin('countLeadingZeros'), [TypeI32], TypeI32, cfg, [
+ await run(t, builtin('countLeadingZeros'), [Type.i32], Type.i32, cfg, [
// Zero
{ input: i32Bits(0b00000000000000000000000000000000), expected: i32(32) },
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countOneBits.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countOneBits.spec.ts
index f0be916285..1937e04283 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countOneBits.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countOneBits.spec.ts
@@ -11,7 +11,7 @@ Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeU32, u32Bits, u32, TypeI32, i32Bits, i32 } from '../../../../../util/conversion.js';
+import { Type, u32Bits, u32, i32Bits, i32 } from '../../../../../util/conversion.js';
import { allInputSources, Config, run } from '../../expression.js';
import { builtin } from './builtin.js';
@@ -26,7 +26,7 @@ g.test('u32')
)
.fn(async t => {
const cfg: Config = t.params;
- await run(t, builtin('countOneBits'), [TypeU32], TypeU32, cfg, [
+ await run(t, builtin('countOneBits'), [Type.u32], Type.u32, cfg, [
// Zero
{ input: u32Bits(0b00000000000000000000000000000000), expected: u32(0) },
@@ -141,7 +141,7 @@ g.test('i32')
)
.fn(async t => {
const cfg: Config = t.params;
- await run(t, builtin('countOneBits'), [TypeI32], TypeI32, cfg, [
+ await run(t, builtin('countOneBits'), [Type.i32], Type.i32, cfg, [
// Zero
{ input: i32Bits(0b00000000000000000000000000000000), expected: i32(0) },
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countTrailingZeros.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countTrailingZeros.spec.ts
index d0b3198f49..3392a47810 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countTrailingZeros.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countTrailingZeros.spec.ts
@@ -12,7 +12,7 @@ Also known as "ctz" in some languages.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { i32, i32Bits, TypeI32, u32, TypeU32, u32Bits } from '../../../../../util/conversion.js';
+import { i32, i32Bits, Type, u32, u32Bits } from '../../../../../util/conversion.js';
import { allInputSources, Config, run } from '../../expression.js';
import { builtin } from './builtin.js';
@@ -27,7 +27,7 @@ g.test('u32')
)
.fn(async t => {
const cfg: Config = t.params;
- await run(t, builtin('countTrailingZeros'), [TypeU32], TypeU32, cfg, [
+ await run(t, builtin('countTrailingZeros'), [Type.u32], Type.u32, cfg, [
// Zero
{ input: u32Bits(0b00000000000000000000000000000000), expected: u32(32) },
@@ -142,7 +142,7 @@ g.test('i32')
)
.fn(async t => {
const cfg: Config = t.params;
- await run(t, builtin('countTrailingZeros'), [TypeI32], TypeI32, cfg, [
+ await run(t, builtin('countTrailingZeros'), [Type.i32], Type.i32, cfg, [
// Zero
{ input: i32Bits(0b00000000000000000000000000000000), expected: i32(32) },
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cross.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cross.cache.ts
new file mode 100644
index 0000000000..396789b4ed
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cross.cache.ts
@@ -0,0 +1,25 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]_[non_]const
+// abstract_non_const is empty and not used
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateVectorPairToVectorCases(
+ FP[trait].vectorRange(3),
+ FP[trait].vectorRange(3),
+ nonConst ? 'unfiltered' : 'finite',
+ // cross has an inherited accuracy, so abstract is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].crossInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('cross', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cross.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cross.spec.ts
index 2b0b3e58ce..cc32537076 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cross.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cross.spec.ts
@@ -1,77 +1,32 @@
export const description = `
Execution tests for the 'cross' builtin function
-T is AbstractFloat, f32, or f16
+T is abstract-float, f32, or f16
@const fn cross(e1: vec3<T> ,e2: vec3<T>) -> vec3<T>
Returns the cross product of e1 and e2.
`;
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeAbstractFloat, TypeF16, TypeF32, TypeVec } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { sparseVectorF64Range, vectorF16Range, vectorF32Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
+import { Type } from '../../../../../util/conversion.js';
import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { abstractBuiltin, builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './cross.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('cross', {
- f32_const: () => {
- return FP.f32.generateVectorPairToVectorCases(
- vectorF32Range(3),
- vectorF32Range(3),
- 'finite',
- FP.f32.crossInterval
- );
- },
- f32_non_const: () => {
- return FP.f32.generateVectorPairToVectorCases(
- vectorF32Range(3),
- vectorF32Range(3),
- 'unfiltered',
- FP.f32.crossInterval
- );
- },
- f16_const: () => {
- return FP.f16.generateVectorPairToVectorCases(
- vectorF16Range(3),
- vectorF16Range(3),
- 'finite',
- FP.f16.crossInterval
- );
- },
- f16_non_const: () => {
- return FP.f16.generateVectorPairToVectorCases(
- vectorF16Range(3),
- vectorF16Range(3),
- 'unfiltered',
- FP.f16.crossInterval
- );
- },
- abstract: () => {
- return FP.abstract.generateVectorPairToVectorCases(
- sparseVectorF64Range(3),
- sparseVectorF64Range(3),
- 'finite',
- FP.abstract.crossInterval
- );
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u => u.combine('inputSource', onlyConstInputSource))
.fn(async t => {
- const cases = await d.get('abstract');
+ const cases = await d.get('abstract_const');
await run(
t,
- abstractBuiltin('cross'),
- [TypeVec(3, TypeAbstractFloat), TypeVec(3, TypeAbstractFloat)],
- TypeVec(3, TypeAbstractFloat),
+ abstractFloatBuiltin('cross'),
+ [Type.vec(3, Type.abstractFloat), Type.vec(3, Type.abstractFloat)],
+ Type.vec(3, Type.abstractFloat),
t.params,
cases
);
@@ -83,14 +38,7 @@ g.test('f32')
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(
- t,
- builtin('cross'),
- [TypeVec(3, TypeF32), TypeVec(3, TypeF32)],
- TypeVec(3, TypeF32),
- t.params,
- cases
- );
+ await run(t, builtin('cross'), [Type.vec3f, Type.vec3f], Type.vec3f, t.params, cases);
});
g.test('f16')
@@ -102,12 +50,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(
- t,
- builtin('cross'),
- [TypeVec(3, TypeF16), TypeVec(3, TypeF16)],
- TypeVec(3, TypeF16),
- t.params,
- cases
- );
+ await run(t, builtin('cross'), [Type.vec3h, Type.vec3h], Type.vec3h, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/degrees.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/degrees.cache.ts
new file mode 100644
index 0000000000..16abe41f34
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/degrees.cache.ts
@@ -0,0 +1,24 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]_[non_]const
+// abstract_non_const is empty and not used
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateScalarToIntervalCases(
+ FP[trait].scalarRange(),
+ nonConst ? 'unfiltered' : 'finite',
+ // degrees has an inherited accuracy, so abstract is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].degreesInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('degrees', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/degrees.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/degrees.spec.ts
index f82153ffca..e8589c9933 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/degrees.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/degrees.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'degrees' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<T>
@const fn degrees(e1: T ) -> T
Converts radians to degrees, approximating e1 × 180 ÷ π. Component-wise when T is a vector
@@ -9,46 +9,14 @@ Converts radians to degrees, approximating e1 × 180 ÷ π. Component-wise when
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeAbstractFloat, TypeF16, TypeF32 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF16Range, fullF32Range, fullF64Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
+import { Type } from '../../../../../util/conversion.js';
import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { abstractBuiltin, builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './degrees.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('degrees', {
- f32_const: () => {
- return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'finite', FP.f32.degreesInterval);
- },
- f32_non_const: () => {
- return FP.f32.generateScalarToIntervalCases(
- fullF32Range(),
- 'unfiltered',
- FP.f32.degreesInterval
- );
- },
- f16_const: () => {
- return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'finite', FP.f16.degreesInterval);
- },
- f16_non_const: () => {
- return FP.f16.generateScalarToIntervalCases(
- fullF16Range(),
- 'unfiltered',
- FP.f16.degreesInterval
- );
- },
- abstract: () => {
- return FP.abstract.generateScalarToIntervalCases(
- fullF64Range(),
- 'finite',
- FP.abstract.degreesInterval
- );
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
@@ -58,12 +26,12 @@ g.test('abstract_float')
.combine('vectorize', [undefined, 2, 3, 4] as const)
)
.fn(async t => {
- const cases = await d.get('abstract');
+ const cases = await d.get('abstract_const');
await run(
t,
- abstractBuiltin('degrees'),
- [TypeAbstractFloat],
- TypeAbstractFloat,
+ abstractFloatBuiltin('degrees'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
t.params,
cases
);
@@ -77,7 +45,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('degrees'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('degrees'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -91,5 +59,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('degrees'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('degrees'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/derivatives.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/derivatives.cache.ts
new file mode 100644
index 0000000000..ebd414f395
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/derivatives.cache.ts
@@ -0,0 +1,14 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { sparseScalarF32Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+export const d = makeCaseCache('derivatives', {
+ scalar: () => {
+ return FP.f32.generateScalarPairToIntervalCases(
+ sparseScalarF32Range(),
+ sparseScalarF32Range(),
+ 'unfiltered',
+ FP.f32.subtractionInterval
+ );
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/derivatives.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/derivatives.ts
new file mode 100644
index 0000000000..2af4a48096
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/derivatives.ts
@@ -0,0 +1,215 @@
+import { GPUTest } from '../../../../../gpu_test.js';
+import { Type, Value } from '../../../../../util/conversion.js';
+import { Case } from '../../case.js';
+import { toComparator } from '../../expectation.js';
+import { packScalarsToVector } from '../../expression.js';
+
+/**
+ * Run a test for a derivative builtin function.
+ * @param t the GPUTest
+ * @param cases list of test cases to run
+ * @param builtin the builtin function to test
+ * @param non_uniform_discard if true, one of each pair of invocations will discard
+ * @param vectorize if defined, the vector width to use (2, 3, or 4)
+ */
+export function runDerivativeTest(
+ t: GPUTest,
+ cases: Case[],
+ builtin: string,
+ non_uniform_discard: boolean,
+ vectorize?: number
+) {
+ // If the 'vectorize' config option was provided, pack the cases into vectors.
+ let type: Type = Type.f32;
+ if (vectorize !== undefined) {
+ const packed = packScalarsToVector([type, type], type, cases, vectorize);
+ cases = packed.cases;
+ type = packed.resultType;
+ }
+
+ ////////////////////////////////////////////////////////////////
+ // The two input values for a given case are distributed to two different invocations in a quad.
+ // We will populate a storage buffer with these input values laid out sequentially:
+ // [ case_0_input_1, case_0_input_0, case_1_input_1, case_1_input_0, ...]
+ //
+ // The render pipeline will be launched several times over a viewport size of (2, 2). Each draw
+ // call will execute a single quad (four fragment invocation), which will exercise two test cases.
+ // Each of these draw calls will use a different instance index, which is forwarded to the
+ // fragment shader. Each invocation will determine its index into the storage buffer using its
+ // fragment position and the instance index for that draw call.
+ //
+ // Consider two draw calls that test 4 cases (c_0, c_1, c_2, c_3).
+ //
+ // For derivatives along the 'x' direction, the mapping from fragment position to case input is:
+ // Quad 0: | c_0_i_1 | c_0_i_0 | Quad 1: | c_2_i_1 | c_2_i_0 |
+ // | c_1_i_1 | c_1_i_0 | | c_3_i_1 | c_3_i_0 |
+ //
+ // For derivatives along the 'y' direction, the mapping from fragment position to case input is:
+ // Quad 0: | c_0_i_1 | c_1_i_1 | Quad 1: | c_2_i_1 | c_3_i_1 |
+ // | c_0_i_0 | c_1_i_0 | | c_2_i_0 | c_3_i_0 |
+ //
+ ////////////////////////////////////////////////////////////////
+
+ // Determine the direction of the derivative ('x' or 'y') from the builtin name.
+ const dir = builtin[3];
+
+ // Determine the WGSL type to use in the shader, and the stride in bytes between values.
+ let valueStride = 4;
+ let wgslType = 'f32';
+ if (vectorize) {
+ wgslType = `vec${vectorize}f`;
+ valueStride = vectorize * 4;
+ if (vectorize === 3) {
+ valueStride = 16;
+ }
+ }
+
+ // Define a vertex shader that draws a triangle over the full viewport, and a fragment shader that
+ // calls the derivative builtin with a value loaded from that fragment's index into the storage
+ // buffer (determined using the quad index and fragment position, as described above).
+ const code = `
+struct CaseInfo {
+ @builtin(position) position: vec4f,
+ @location(0) @interpolate(flat) quad_idx: u32,
+}
+
+@vertex
+fn vert(@builtin(vertex_index) vertex_idx: u32,
+ @builtin(instance_index) instance_idx: u32) -> CaseInfo {
+ const kVertices = array(
+ vec2f(-2, -2),
+ vec2f( 2, -2),
+ vec2f( 0, 2),
+ );
+ return CaseInfo(vec4(kVertices[vertex_idx], 0, 1), instance_idx);
+}
+
+@group(0) @binding(0) var<storage, read> inputs : array<${wgslType}>;
+@group(0) @binding(1) var<storage, read_write> outputs : array<${wgslType}>;
+
+@fragment
+fn frag(info : CaseInfo) {
+ let case_idx = u32(info.position.${dir === 'x' ? 'y' : 'x'});
+ let inv_idx = u32(info.position.${dir});
+ let index = info.quad_idx*4 + case_idx*2 + inv_idx;
+ let input = inputs[index];
+ ${non_uniform_discard ? 'if inv_idx == 0 { discard; }' : ''}
+ outputs[index] = ${builtin}(input);
+}
+`;
+
+ // Create the render pipeline.
+ const module = t.device.createShaderModule({ code });
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: { module },
+ fragment: { module, targets: [{ format: 'rgba8unorm', writeMask: 0 }] },
+ });
+
+ // Create storage buffers to hold the inputs and outputs.
+ const bufferSize = cases.length * 2 * valueStride;
+ const inputBuffer = t.device.createBuffer({
+ size: bufferSize,
+ usage: GPUBufferUsage.STORAGE,
+ mappedAtCreation: true,
+ });
+ const outputBuffer = t.device.createBuffer({
+ size: bufferSize,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ });
+
+ // Populate the input storage buffer with case input values.
+ const valuesData = new Uint8Array(inputBuffer.getMappedRange());
+ for (let i = 0; i < cases.length; i++) {
+ const inputs = cases[i].input as ReadonlyArray<Value>;
+ inputs[0].copyTo(valuesData, (i * 2 + 1) * valueStride);
+ inputs[1].copyTo(valuesData, i * 2 * valueStride);
+ }
+ inputBuffer.unmap();
+
+ // Create a bind group for the storage buffers.
+ const group = t.device.createBindGroup({
+ entries: [
+ { binding: 0, resource: { buffer: inputBuffer } },
+ { binding: 1, resource: { buffer: outputBuffer } },
+ ],
+ layout: pipeline.getBindGroupLayout(0),
+ });
+
+ // Create a texture to use as a color attachment.
+ // We only need this for launching the desired number of fragment invocations.
+ const colorAttachment = t.device.createTexture({
+ size: { width: 2, height: 2 },
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ });
+
+ // Submit the render pass to the device.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: colorAttachment.createView(),
+ loadOp: 'clear',
+ storeOp: 'discard',
+ },
+ ],
+ });
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, group);
+ for (let quad = 0; quad < cases.length / 2; quad++) {
+ pass.draw(3, 1, undefined, quad);
+ }
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Check the outputs match the expected results.
+ t.expectGPUBufferValuesPassCheck(
+ outputBuffer,
+ (outputData: Uint8Array) => {
+ for (let i = 0; i < cases.length; i++) {
+ const c = cases[i];
+
+ // Both invocations involved in the derivative should get the same result.
+ for (let d = 0; d < 2; d++) {
+ if (non_uniform_discard && d === 0) {
+ continue;
+ }
+
+ const index = (i * 2 + d) * valueStride;
+ const result = type.read(outputData, index);
+ const cmp = toComparator(c.expected).compare(result);
+ if (!cmp.matched) {
+ // If this is a coarse derivative, the implementation is also allowed to calculate only
+ // one of the two derivatives and return that result to all of the invocations.
+ if (!builtin.endsWith('Fine')) {
+ const c0 = cases[i % 2 === 0 ? i + 1 : i - 1];
+ const cmp0 = toComparator(c0.expected).compare(result);
+ if (!cmp0.matched) {
+ return new Error(`
+ 1st pair: (${(c.input as Value[]).join(', ')})
+ expected: ${cmp.expected}
+
+ 2nd pair: (${(c0.input as Value[]).join(', ')})
+ expected: ${cmp0.expected}
+
+ returned: ${result}`);
+ }
+ } else {
+ return new Error(`
+ inputs: (${(c.input as Value[]).join(', ')})
+ expected: ${cmp.expected}
+
+ returned: ${result}`);
+ }
+ }
+ }
+ }
+ return undefined;
+ },
+ {
+ type: Uint8Array,
+ typedLength: bufferSize,
+ }
+ );
+}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/determinant.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/determinant.cache.ts
new file mode 100644
index 0000000000..ccd073bce5
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/determinant.cache.ts
@@ -0,0 +1,99 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Accuracy for determinant is only defined for e, where e is an integer and
+// |e| < quadroot(2**21) [~38],
+// due to computational complexity of calculating the general solution for 4x4,
+// so custom matrices are used.
+//
+// Note: For 2x2 and 3x3 the limits are squareroot and cuberoot instead of
+// quadroot, but using the tighter 4x4 limits for all cases for simplicity.
+const kDeterminantValues = [-38, -10, -5, -1, 0, 1, 5, 10, 38];
+
+const kDeterminantMatrixValues = {
+ 2: kDeterminantValues.map((f, idx) => [
+ [idx % 4 === 0 ? f : idx, idx % 4 === 1 ? f : -idx],
+ [idx % 4 === 2 ? f : -idx, idx % 4 === 3 ? f : idx],
+ ]),
+ 3: kDeterminantValues.map((f, idx) => [
+ [idx % 9 === 0 ? f : idx, idx % 9 === 1 ? f : -idx, idx % 9 === 2 ? f : idx],
+ [idx % 9 === 3 ? f : -idx, idx % 9 === 4 ? f : idx, idx % 9 === 5 ? f : -idx],
+ [idx % 9 === 6 ? f : idx, idx % 9 === 7 ? f : -idx, idx % 9 === 8 ? f : idx],
+ ]),
+ 4: kDeterminantValues.map((f, idx) => [
+ [
+ idx % 16 === 0 ? f : idx,
+ idx % 16 === 1 ? f : -idx,
+ idx % 16 === 2 ? f : idx,
+ idx % 16 === 3 ? f : -idx,
+ ],
+ [
+ idx % 16 === 4 ? f : -idx,
+ idx % 16 === 5 ? f : idx,
+ idx % 16 === 6 ? f : -idx,
+ idx % 16 === 7 ? f : idx,
+ ],
+ [
+ idx % 16 === 8 ? f : idx,
+ idx % 16 === 9 ? f : -idx,
+ idx % 16 === 10 ? f : idx,
+ idx % 16 === 11 ? f : -idx,
+ ],
+ [
+ idx % 16 === 12 ? f : -idx,
+ idx % 16 === 13 ? f : idx,
+ idx % 16 === 14 ? f : -idx,
+ idx % 16 === 15 ? f : idx,
+ ],
+ ]),
+};
+
+// Cases: f32_matDxD_[non_]const
+const f32_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`f32_mat${dim}x${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateMatrixToScalarCases(
+ kDeterminantMatrixValues[dim],
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f32.determinantInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: f16_matDxD_[non_]const
+const f16_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`f16_mat${dim}x${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateMatrixToScalarCases(
+ kDeterminantMatrixValues[dim],
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.determinantInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: abstract_matDxD
+const abstract_cases = ([2, 3, 4] as const)
+ .map(dim => ({
+ [`abstract_mat${dim}x${dim}`]: () => {
+ return FP.abstract.generateMatrixToScalarCases(
+ kDeterminantMatrixValues[dim],
+ 'finite',
+ // determinant has an inherited accuracy, so abstract is only expected to be as accurate as f32
+ FP.f32.determinantInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('determinant', {
+ ...f32_cases,
+ ...f16_cases,
+ ...abstract_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/determinant.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/determinant.spec.ts
index f08f4f0b6b..638af80aca 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/determinant.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/determinant.spec.ts
@@ -1,109 +1,37 @@
export const description = `
Execution tests for the 'determinant' builtin function
-T is AbstractFloat, f32, or f16
+T is abstract-float, f32, or f16
@const determinant(e: matCxC<T> ) -> T
Returns the determinant of e.
`;
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16, TypeMat } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './determinant.cache.js';
export const g = makeTestGroup(GPUTest);
-// Accuracy for determinant is only defined for e, where e is an integer and
-// |e| < quadroot(2**21) [~38],
-// due to computational complexity of calculating the general solution for 4x4,
-// so custom matrices are used.
-//
-// Note: For 2x2 and 3x3 the limits are squareroot and cuberoot instead of
-// quadroot, but using the tighter 4x4 limits for all cases for simplicity.
-const kDeterminantValues = [-38, -10, -5, -1, 0, 1, 5, 10, 38];
-
-const kDeterminantMatrixValues = {
- 2: kDeterminantValues.map((f, idx) => [
- [idx % 4 === 0 ? f : idx, idx % 4 === 1 ? f : -idx],
- [idx % 4 === 2 ? f : -idx, idx % 4 === 3 ? f : idx],
- ]),
- 3: kDeterminantValues.map((f, idx) => [
- [idx % 9 === 0 ? f : idx, idx % 9 === 1 ? f : -idx, idx % 9 === 2 ? f : idx],
- [idx % 9 === 3 ? f : -idx, idx % 9 === 4 ? f : idx, idx % 9 === 5 ? f : -idx],
- [idx % 9 === 6 ? f : idx, idx % 9 === 7 ? f : -idx, idx % 9 === 8 ? f : idx],
- ]),
- 4: kDeterminantValues.map((f, idx) => [
- [
- idx % 16 === 0 ? f : idx,
- idx % 16 === 1 ? f : -idx,
- idx % 16 === 2 ? f : idx,
- idx % 16 === 3 ? f : -idx,
- ],
- [
- idx % 16 === 4 ? f : -idx,
- idx % 16 === 5 ? f : idx,
- idx % 16 === 6 ? f : -idx,
- idx % 16 === 7 ? f : idx,
- ],
- [
- idx % 16 === 8 ? f : idx,
- idx % 16 === 9 ? f : -idx,
- idx % 16 === 10 ? f : idx,
- idx % 16 === 11 ? f : -idx,
- ],
- [
- idx % 16 === 12 ? f : -idx,
- idx % 16 === 13 ? f : idx,
- idx % 16 === 14 ? f : -idx,
- idx % 16 === 15 ? f : idx,
- ],
- ]),
-};
-
-// Cases: f32_matDxD_[non_]const
-const f32_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`f32_mat${dim}x${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateMatrixToScalarCases(
- kDeterminantMatrixValues[dim],
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.determinantInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-// Cases: f16_matDxD_[non_]const
-const f16_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`f16_mat${dim}x${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateMatrixToScalarCases(
- kDeterminantMatrixValues[dim],
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.determinantInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('determinant', {
- ...f32_cases,
- ...f16_cases,
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#matrix-builtin-functions')
.desc(`abstract float tests`)
- .params(u => u.combine('inputSource', allInputSources).combine('dimension', [2, 3, 4] as const))
- .unimplemented();
+ .params(u => u.combine('inputSource', onlyConstInputSource).combine('dim', [2, 3, 4] as const))
+ .fn(async t => {
+ const dim = t.params.dim;
+ const cases = await d.get(`abstract_mat${dim}x${dim}`);
+ await run(
+ t,
+ abstractFloatBuiltin('determinant'),
+ [Type.mat(dim, dim, Type.abstractFloat)],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#matrix-builtin-functions')
@@ -116,7 +44,7 @@ g.test('f32')
? `f32_mat${dim}x${dim}_const`
: `f32_mat${dim}x${dim}_non_const`
);
- await run(t, builtin('determinant'), [TypeMat(dim, dim, TypeF32)], TypeF32, t.params, cases);
+ await run(t, builtin('determinant'), [Type.mat(dim, dim, Type.f32)], Type.f32, t.params, cases);
});
g.test('f16')
@@ -133,5 +61,5 @@ g.test('f16')
? `f16_mat${dim}x${dim}_const`
: `f16_mat${dim}x${dim}_non_const`
);
- await run(t, builtin('determinant'), [TypeMat(dim, dim, TypeF16)], TypeF16, t.params, cases);
+ await run(t, builtin('determinant'), [Type.mat(dim, dim, Type.f16)], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/distance.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/distance.cache.ts
new file mode 100644
index 0000000000..0b37190cf7
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/distance.cache.ts
@@ -0,0 +1,49 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]_[non_]const
+const scalar_cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateScalarPairToIntervalCases(
+ FP[trait].scalarRange(),
+ FP[trait].scalarRange(),
+ nonConst ? 'unfiltered' : 'finite',
+ // distance has an inherited accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].distanceInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: [f32|f16|abstract]_vecN_[non_]const
+const vec_cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([2, 3, 4] as const).flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateVectorPairToIntervalCases(
+ FP[trait].sparseVectorRange(dim),
+ FP[trait].sparseVectorRange(dim),
+ nonConst ? 'unfiltered' : 'finite',
+ // distance has an inherited accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].distanceInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('distance', {
+ ...scalar_cases,
+ ...vec_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/distance.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/distance.spec.ts
index 13cddf6403..5f03c49319 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/distance.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/distance.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'distance' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn distance(e1: T ,e2: T ) -> f32
Returns the distance between e1 and e2 (e.g. length(e1-e2)).
@@ -10,97 +10,77 @@ Returns the distance between e1 and e2 (e.g. length(e1-e2)).
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16, TypeVec } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import {
- fullF32Range,
- fullF16Range,
- sparseVectorF32Range,
- sparseVectorF16Range,
-} from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
-
-import { builtin } from './builtin.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
+
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './distance.cache.js';
export const g = makeTestGroup(GPUTest);
-// Cases: f32_vecN_[non_]const
-const f32_vec_cases = ([2, 3, 4] as const)
- .flatMap(n =>
- ([true, false] as const).map(nonConst => ({
- [`f32_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateVectorPairToIntervalCases(
- sparseVectorF32Range(n),
- sparseVectorF32Range(n),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.distanceInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-// Cases: f16_vecN_[non_]const
-const f16_vec_cases = ([2, 3, 4] as const)
- .flatMap(n =>
- ([true, false] as const).map(nonConst => ({
- [`f16_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateVectorPairToIntervalCases(
- sparseVectorF16Range(n),
- sparseVectorF16Range(n),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.distanceInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('distance', {
- f32_const: () => {
- return FP.f32.generateScalarPairToIntervalCases(
- fullF32Range(),
- fullF32Range(),
- 'finite',
- FP.f32.distanceInterval
- );
- },
- f32_non_const: () => {
- return FP.f32.generateScalarPairToIntervalCases(
- fullF32Range(),
- fullF32Range(),
- 'unfiltered',
- FP.f32.distanceInterval
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_const');
+ await run(
+ t,
+ abstractFloatBuiltin('distance'),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
);
- },
- ...f32_vec_cases,
- f16_const: () => {
- return FP.f16.generateScalarPairToIntervalCases(
- fullF16Range(),
- fullF16Range(),
- 'finite',
- FP.f16.distanceInterval
+ });
+
+g.test('abstract_float_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract float tests using vec2s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec2_const');
+ await run(
+ t,
+ abstractFloatBuiltin('distance'),
+ [Type.vec2af, Type.vec2af],
+ Type.abstractFloat,
+ t.params,
+ cases
);
- },
- f16_non_const: () => {
- return FP.f16.generateScalarPairToIntervalCases(
- fullF16Range(),
- fullF16Range(),
- 'unfiltered',
- FP.f16.distanceInterval
+ });
+
+g.test('abstract_float_vec3')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract float tests using vec3s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec3_const');
+ await run(
+ t,
+ abstractFloatBuiltin('distance'),
+ [Type.vec3af, Type.vec3af],
+ Type.abstractFloat,
+ t.params,
+ cases
);
- },
- ...f16_vec_cases,
-});
+ });
-g.test('abstract_float')
- .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
- .desc(`abstract float tests`)
- .params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
- )
- .unimplemented();
+g.test('abstract_float_vec4')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract float tests using vec4s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec4_const');
+ await run(
+ t,
+ abstractFloatBuiltin('distance'),
+ [Type.vec4af, Type.vec4af],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
@@ -108,7 +88,7 @@ g.test('f32')
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('distance'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('distance'), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('f32_vec2')
@@ -119,14 +99,7 @@ g.test('f32_vec2')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f32_vec2_const' : 'f32_vec2_non_const'
);
- await run(
- t,
- builtin('distance'),
- [TypeVec(2, TypeF32), TypeVec(2, TypeF32)],
- TypeF32,
- t.params,
- cases
- );
+ await run(t, builtin('distance'), [Type.vec2f, Type.vec2f], Type.f32, t.params, cases);
});
g.test('f32_vec3')
@@ -137,14 +110,7 @@ g.test('f32_vec3')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f32_vec3_const' : 'f32_vec3_non_const'
);
- await run(
- t,
- builtin('distance'),
- [TypeVec(3, TypeF32), TypeVec(3, TypeF32)],
- TypeF32,
- t.params,
- cases
- );
+ await run(t, builtin('distance'), [Type.vec3f, Type.vec3f], Type.f32, t.params, cases);
});
g.test('f32_vec4')
@@ -155,14 +121,7 @@ g.test('f32_vec4')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f32_vec4_const' : 'f32_vec4_non_const'
);
- await run(
- t,
- builtin('distance'),
- [TypeVec(4, TypeF32), TypeVec(4, TypeF32)],
- TypeF32,
- t.params,
- cases
- );
+ await run(t, builtin('distance'), [Type.vec4f, Type.vec4f], Type.f32, t.params, cases);
});
g.test('f16')
@@ -174,7 +133,7 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('distance'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('distance'), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
g.test('f16_vec2')
@@ -188,14 +147,7 @@ g.test('f16_vec2')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f16_vec2_const' : 'f16_vec2_non_const'
);
- await run(
- t,
- builtin('distance'),
- [TypeVec(2, TypeF16), TypeVec(2, TypeF16)],
- TypeF16,
- t.params,
- cases
- );
+ await run(t, builtin('distance'), [Type.vec2h, Type.vec2h], Type.f16, t.params, cases);
});
g.test('f16_vec3')
@@ -209,14 +161,7 @@ g.test('f16_vec3')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f16_vec3_const' : 'f16_vec3_non_const'
);
- await run(
- t,
- builtin('distance'),
- [TypeVec(3, TypeF16), TypeVec(3, TypeF16)],
- TypeF16,
- t.params,
- cases
- );
+ await run(t, builtin('distance'), [Type.vec3h, Type.vec3h], Type.f16, t.params, cases);
});
g.test('f16_vec4')
@@ -230,12 +175,5 @@ g.test('f16_vec4')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f16_vec4_const' : 'f16_vec4_non_const'
);
- await run(
- t,
- builtin('distance'),
- [TypeVec(4, TypeF16), TypeVec(4, TypeF16)],
- TypeF16,
- t.params,
- cases
- );
+ await run(t, builtin('distance'), [Type.vec4h, Type.vec4h], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot.cache.ts
new file mode 100644
index 0000000000..6d1f22def1
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot.cache.ts
@@ -0,0 +1,118 @@
+import { ROArrayArray } from '../../../../../../common/util/types.js';
+import { assert } from '../../../../../../common/util/util.js';
+import { kValue } from '../../../../../util/constants.js';
+import { FP } from '../../../../../util/floating_point.js';
+import {
+ calculatePermutations,
+ sparseVectorI32Range,
+ sparseVectorI64Range,
+ sparseVectorU32Range,
+ vectorI32Range,
+ vectorI64Range,
+ vectorU32Range,
+} from '../../../../../util/math.js';
+import {
+ generateVectorVectorToI32Cases,
+ generateVectorVectorToI64Cases,
+ generateVectorVectorToU32Cases,
+} from '../../case.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+function ai_dot(x: bigint[], y: bigint[]): bigint | undefined {
+ assert(x.length === y.length, 'Cannot calculate dot for vectors of different lengths');
+ const multiplications = x.map((_, idx) => x[idx] * y[idx]);
+ if (multiplications.some(kValue.i64.isOOB)) return undefined;
+
+ const result = multiplications.reduce((prev, curr) => prev + curr);
+ if (kValue.i64.isOOB(result)) return undefined;
+
+ // The spec does not state the ordering of summation, so all the
+ // permutations are calculated and the intermediate results checked for
+ // going OOB. vec2 does not need permutations, since a + b === b + a.
+ // All the end results should be the same regardless of the order if the
+ // intermediate additions stay inbounds.
+ if (x.length !== 2) {
+ let wentOOB: boolean = false;
+ const permutations: ROArrayArray<bigint> = calculatePermutations(multiplications);
+ permutations.forEach(p => {
+ if (!wentOOB) {
+ p.reduce((prev, curr) => {
+ const next = prev + curr;
+ if (kValue.i64.isOOB(next)) {
+ wentOOB = true;
+ }
+ return next;
+ });
+ }
+ });
+
+ if (wentOOB) return undefined;
+ }
+
+ return !kValue.i64.isOOB(result) ? result : undefined;
+}
+
+function ci_dot(x: number[], y: number[]): number | undefined {
+ assert(x.length === y.length, 'Cannot calculate dot for vectors of different lengths');
+ return x.reduce((prev, _, idx) => prev + Math.imul(x[idx], y[idx]), 0);
+}
+
+// Cases: [f32|f16|abstract]_vecN_[non_]const
+const float_cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([2, 3, 4] as const).flatMap(N =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait === 'abstract' ? 'abstract_float' : trait}_vec${N}_${
+ nonConst ? 'non_const' : 'const'
+ }`]: () => {
+ // Emit an empty array for not const abstract float, since they will never be run
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ // vec3 and vec4 require calculating all possible permutations, so their runtime is much
+ // longer per test, so only using sparse vectors for them.
+ return FP[trait].generateVectorPairToIntervalCases(
+ N === 2 ? FP[trait].vectorRange(2) : FP[trait].sparseVectorRange(N),
+ N === 2 ? FP[trait].vectorRange(2) : FP[trait].sparseVectorRange(N),
+ nonConst ? 'unfiltered' : 'finite',
+ // dot has an inherited accuracy, so abstract is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].dotInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const cases = {
+ ...float_cases,
+ abstract_int_vec2: () => {
+ return generateVectorVectorToI64Cases(vectorI64Range(2), vectorI64Range(2), ai_dot);
+ },
+ abstract_int_vec3: () => {
+ return generateVectorVectorToI64Cases(sparseVectorI64Range(3), sparseVectorI64Range(3), ai_dot);
+ },
+ abstract_int_vec4: () => {
+ return generateVectorVectorToI64Cases(sparseVectorI64Range(4), sparseVectorI64Range(4), ai_dot);
+ },
+ i32_vec2: () => {
+ return generateVectorVectorToI32Cases(vectorI32Range(2), vectorI32Range(2), ci_dot);
+ },
+ i32_vec3: () => {
+ return generateVectorVectorToI32Cases(sparseVectorI32Range(3), sparseVectorI32Range(3), ci_dot);
+ },
+ i32_vec4: () => {
+ return generateVectorVectorToI32Cases(sparseVectorI32Range(4), sparseVectorI32Range(4), ci_dot);
+ },
+ u32_vec2: () => {
+ return generateVectorVectorToU32Cases(vectorU32Range(2), vectorU32Range(2), ci_dot);
+ },
+ u32_vec3: () => {
+ return generateVectorVectorToU32Cases(sparseVectorU32Range(3), sparseVectorU32Range(3), ci_dot);
+ },
+ u32_vec4: () => {
+ return generateVectorVectorToU32Cases(sparseVectorU32Range(4), sparseVectorU32Range(4), ci_dot);
+ },
+};
+
+export const d = makeCaseCache('dot', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot.spec.ts
index 2726546183..188409bf21 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot.spec.ts
@@ -1,87 +1,182 @@
export const description = `
Execution tests for the 'dot' builtin function
-T is AbstractInt, AbstractFloat, i32, u32, f32, or f16
+T is Type.abstractInt, Type.abstractFloat, i32, u32, f32, or f16
@const fn dot(e1: vecN<T>,e2: vecN<T>) -> T
Returns the dot product of e1 and e2.
`;
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16, TypeVec } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { sparseVectorF32Range, vectorF32Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, abstractIntBuiltin, builtin } from './builtin.js';
+import { d } from './dot.cache.js';
export const g = makeTestGroup(GPUTest);
-// Cases: [f32|f16]_vecN_[non_]const
-const cases = (['f32', 'f16'] as const)
- .flatMap(trait =>
- ([2, 3, 4] as const).flatMap(N =>
- ([true, false] as const).map(nonConst => ({
- [`${trait}_vec${N}_${nonConst ? 'non_const' : 'const'}`]: () => {
- // vec3 and vec4 require calculating all possible permutations, so their runtime is much
- // longer per test, so only using sparse vectors for them.
- return FP[trait].generateVectorPairToIntervalCases(
- N === 2 ? vectorF32Range(2) : sparseVectorF32Range(N),
- N === 2 ? vectorF32Range(2) : sparseVectorF32Range(N),
- nonConst ? 'unfiltered' : 'finite',
- FP[trait].dotInterval
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('dot', cases);
-
-g.test('abstract_int')
- .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
- .desc(`abstract int tests`)
+g.test('abstract_int_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
+ .desc(`abstract integer tests using vec2s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_int_vec2');
+ await run(
+ t,
+ abstractIntBuiltin('dot'),
+ [Type.vec(2, Type.abstractInt), Type.vec(2, Type.abstractInt)],
+ Type.abstractInt,
+ t.params,
+ cases
+ );
+ });
+
+g.test('abstract_int_vec3')
+ .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
+ .desc(`abstract integer tests using vec3s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_int_vec3');
+ await run(
+ t,
+ abstractIntBuiltin('dot'),
+ [Type.vec(3, Type.abstractInt), Type.vec(3, Type.abstractInt)],
+ Type.abstractInt,
+ t.params,
+ cases
+ );
+ });
+
+g.test('abstract_int_vec4')
+ .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
+ .desc(`abstract integer tests using vec4s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_int_vec4');
+ await run(
+ t,
+ abstractIntBuiltin('dot'),
+ [Type.vec(4, Type.abstractInt), Type.vec(4, Type.abstractInt)],
+ Type.abstractInt,
+ t.params,
+ cases
+ );
+ });
+
+g.test('i32_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
+ .desc(`i32 tests using vec2s`)
.params(u => u.combine('inputSource', allInputSources))
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('i32_vec2');
+ await run(t, builtin('dot'), [Type.vec2i, Type.vec2i], Type.i32, t.params, cases);
+ });
-g.test('i32')
+g.test('i32_vec3')
.specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
- .desc(`i32 tests`)
+ .desc(`i32 tests using vec3s`)
.params(u => u.combine('inputSource', allInputSources))
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('i32_vec3');
+ await run(t, builtin('dot'), [Type.vec3i, Type.vec3i], Type.i32, t.params, cases);
+ });
-g.test('u32')
+g.test('i32_vec4')
.specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
- .desc(`u32 tests`)
+ .desc(`i32 tests using vec4s`)
.params(u => u.combine('inputSource', allInputSources))
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('i32_vec4');
+ await run(t, builtin('dot'), [Type.vec4i, Type.vec4i], Type.i32, t.params, cases);
+ });
-g.test('abstract_float')
+g.test('u32_vec2')
.specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
- .desc(`abstract float test`)
+ .desc(`u32 tests using vec2s`)
.params(u => u.combine('inputSource', allInputSources))
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('u32_vec2');
+ await run(t, builtin('dot'), [Type.vec2u, Type.vec2u], Type.u32, t.params, cases);
+ });
-g.test('f32_vec2')
+g.test('u32_vec3')
.specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
- .desc(`f32 tests using vec2s`)
+ .desc(`u32 tests using vec3s`)
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
- const cases = await d.get(
- t.params.inputSource === 'const' ? 'f32_vec2_const' : 'f32_vec2_non_const'
+ const cases = await d.get('u32_vec3');
+ await run(t, builtin('dot'), [Type.vec3u, Type.vec3u], Type.u32, t.params, cases);
+ });
+
+g.test('u32_vec4')
+ .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
+ .desc(`u32 tests using vec4s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get('u32_vec4');
+ await run(t, builtin('dot'), [Type.vec4u, Type.vec4u], Type.u32, t.params, cases);
+ });
+
+g.test('abstract_float_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
+ .desc(`abstract float tests using vec2s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_float_vec2_const');
+ await run(
+ t,
+ abstractFloatBuiltin('dot'),
+ [Type.vec(2, Type.abstractFloat), Type.vec(2, Type.abstractFloat)],
+ Type.abstractFloat,
+ t.params,
+ cases
);
+ });
+
+g.test('abstract_float_vec3')
+ .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
+ .desc(`abstract float tests using vec3s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_float_vec3_const');
await run(
t,
- builtin('dot'),
- [TypeVec(2, TypeF32), TypeVec(2, TypeF32)],
- TypeF32,
+ abstractFloatBuiltin('dot'),
+ [Type.vec(3, Type.abstractFloat), Type.vec(3, Type.abstractFloat)],
+ Type.abstractFloat,
t.params,
cases
);
});
+g.test('abstract_float_vec4')
+ .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
+ .desc(`abstract float tests using vec4s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_float_vec4_const');
+ await run(
+ t,
+ abstractFloatBuiltin('dot'),
+ [Type.vec(4, Type.abstractFloat), Type.vec(4, Type.abstractFloat)],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
+
+g.test('f32_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
+ .desc(`f32 tests using vec2s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec2_const' : 'f32_vec2_non_const'
+ );
+ await run(t, builtin('dot'), [Type.vec2f, Type.vec2f], Type.f32, t.params, cases);
+ });
+
g.test('f32_vec3')
.specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
.desc(`f32 tests using vec3s`)
@@ -90,14 +185,7 @@ g.test('f32_vec3')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f32_vec3_const' : 'f32_vec3_non_const'
);
- await run(
- t,
- builtin('dot'),
- [TypeVec(3, TypeF32), TypeVec(3, TypeF32)],
- TypeF32,
- t.params,
- cases
- );
+ await run(t, builtin('dot'), [Type.vec3f, Type.vec3f], Type.f32, t.params, cases);
});
g.test('f32_vec4')
@@ -108,14 +196,7 @@ g.test('f32_vec4')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f32_vec4_const' : 'f32_vec4_non_const'
);
- await run(
- t,
- builtin('dot'),
- [TypeVec(4, TypeF32), TypeVec(4, TypeF32)],
- TypeF32,
- t.params,
- cases
- );
+ await run(t, builtin('dot'), [Type.vec4f, Type.vec4f], Type.f32, t.params, cases);
});
g.test('f16_vec2')
@@ -129,14 +210,7 @@ g.test('f16_vec2')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f16_vec2_const' : 'f16_vec2_non_const'
);
- await run(
- t,
- builtin('dot'),
- [TypeVec(2, TypeF16), TypeVec(2, TypeF16)],
- TypeF16,
- t.params,
- cases
- );
+ await run(t, builtin('dot'), [Type.vec2h, Type.vec2h], Type.f16, t.params, cases);
});
g.test('f16_vec3')
@@ -150,14 +224,7 @@ g.test('f16_vec3')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f16_vec3_const' : 'f16_vec3_non_const'
);
- await run(
- t,
- builtin('dot'),
- [TypeVec(3, TypeF16), TypeVec(3, TypeF16)],
- TypeF16,
- t.params,
- cases
- );
+ await run(t, builtin('dot'), [Type.vec3h, Type.vec3h], Type.f16, t.params, cases);
});
g.test('f16_vec4')
@@ -171,12 +238,5 @@ g.test('f16_vec4')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f16_vec4_const' : 'f16_vec4_non_const'
);
- await run(
- t,
- builtin('dot'),
- [TypeVec(4, TypeF16), TypeVec(4, TypeF16)],
- TypeF16,
- t.params,
- cases
- );
+ await run(t, builtin('dot'), [Type.vec4h, Type.vec4h], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot4I8Packed.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot4I8Packed.spec.ts
new file mode 100644
index 0000000000..de537c473e
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot4I8Packed.spec.ts
@@ -0,0 +1,74 @@
+export const description = `
+Execution tests for the 'dot4I8Packed' builtin function
+
+@const fn dot4I8Packed(e1: u32 ,e2: u32) -> i32
+e1 and e2 are interpreted as vectors with four 8-bit signed integer components. Return the signed
+integer dot product of these two vectors. Each component is sign-extended to i32 before performing
+the multiply, and then the add operations are done in WGSL i32 with wrapping behaviour.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { Type, i32, u32 } from '../../../../../util/conversion.js';
+import { Case } from '../../case.js';
+import { allInputSources, Config, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('basic')
+ .specURL('https://www.w3.org/TR/WGSL/#dot4I8Packed-builtin')
+ .desc(
+ `
+@const fn dot4I8Packed(e1: u32, e2: u32) -> i32
+ `
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cfg: Config = t.params;
+
+ const dot4I8Packed = (e1: number, e2: number) => {
+ let result = 0;
+ for (let i = 0; i < 4; ++i) {
+ let e1_i = (e1 >> (i * 8)) & 0xff;
+ if (e1_i >= 128) {
+ e1_i -= 256;
+ }
+ let e2_i = (e2 >> (i * 8)) & 0xff;
+ if (e2_i >= 128) {
+ e2_i -= 256;
+ }
+ result += e1_i * e2_i;
+ }
+ return result;
+ };
+
+ const testInputs = [
+ // dot({0, 0, 0, 0}, {0, 0, 0, 0})
+ [0, 0],
+ // dot({127, 127, 127, 127}, {127, 127, 127, 127})
+ [0x7f7f7f7f, 0x7f7f7f7f],
+ // dot({-128, -128, -128, -128}, {-128, -128, -128, -128})
+ [0x80808080, 0x80808080],
+ // dot({127, 127, 127, 127}, {-128, -128, -128, -128})
+ [0x7f7f7f7f, 0x80808080],
+ // dot({1, 2, 3, 4}, {5, 6, 7, 8})
+ [0x01020304, 0x05060708],
+ // dot({1, 2, 3, 4}, {-1, -2, -3, -4})
+ [0x01020304, 0xfffefdfc],
+ // dot({-5, -6, -7, -8}, {5, 6, 7, 8})
+ [0xfbfaf9f8, 0x05060708],
+ // dot({-9, -10, -11, -12}, {-13, -14, -15, -16})
+ [0xf7f6f5f4, 0xf3f2f1f0],
+ ] as const;
+
+ const makeCase = (x: number, y: number): Case => {
+ return { input: [u32(x), u32(y)], expected: i32(dot4I8Packed(x, y)) };
+ };
+ const cases: Array<Case> = testInputs.flatMap(v => {
+ return [makeCase(...(v as [number, number]))];
+ });
+
+ await run(t, builtin('dot4I8Packed'), [Type.u32, Type.u32], Type.i32, cfg, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot4U8Packed.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot4U8Packed.spec.ts
new file mode 100644
index 0000000000..a12a3d0123
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot4U8Packed.spec.ts
@@ -0,0 +1,59 @@
+export const description = `
+Execution tests for the 'dot4U8Packed' builtin function
+
+@const fn dot4U8Packed(e1: u32 ,e2: u32) -> u32
+e1 and e2 are interpreted as vectors with four 8-bit unsigned integer components. Return the
+unsigned integer dot product of these two vectors.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { Type, u32 } from '../../../../../util/conversion.js';
+import { Case } from '../../case.js';
+import { allInputSources, Config, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('basic')
+ .specURL('https://www.w3.org/TR/WGSL/#dot4U8Packed-builtin')
+ .desc(
+ `
+@const fn dot4U8Packed(e1: u32, e2: u32) -> u32
+ `
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cfg: Config = t.params;
+
+ const dot4U8Packed = (e1: number, e2: number) => {
+ let result = 0;
+ for (let i = 0; i < 4; ++i) {
+ const e1_i = (e1 >> (i * 8)) & 0xff;
+ const e2_i = (e2 >> (i * 8)) & 0xff;
+ result += e1_i * e2_i;
+ }
+ return result;
+ };
+
+ const testInputs = [
+ // dot({0, 0, 0, 0}, {0, 0, 0, 0})
+ [0, 0],
+ // dot({255u, 255u, 255u, 255u}, {255u, 255u, 255u, 255u})
+ [0xffffffff, 0xffffffff],
+ // dot({1u, 2u, 3u, 4u}, {5u, 6u, 7u, 8u})
+ [0x01020304, 0x05060708],
+ // dot({120u, 90u, 60u, 30u}, {50u, 100u, 150u, 200u})
+ [0x785a3c1e, 0x326496c8],
+ ] as const;
+
+ const makeCase = (x: number, y: number): Case => {
+ return { input: [u32(x), u32(y)], expected: u32(dot4U8Packed(x, y)) };
+ };
+ const cases: Array<Case> = testInputs.flatMap(v => {
+ return [makeCase(...(v as [number, number]))];
+ });
+
+ await run(t, builtin('dot4U8Packed'), [Type.u32, Type.u32], Type.u32, cfg, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdx.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdx.spec.ts
index 287a51c699..d922603a9f 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdx.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdx.spec.ts
@@ -10,14 +10,22 @@ The result is the same as either dpdxFine(e) or dpdxCoarse(e).
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { allInputSources } from '../../expression.js';
+
+import { d } from './derivatives.cache.js';
+import { runDerivativeTest } from './derivatives.js';
export const g = makeTestGroup(GPUTest);
+const builtin = 'dpdx';
+
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions')
- .desc(`f32 tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ .combine('non_uniform_discard', [false, true])
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('scalar');
+ runDerivativeTest(t, cases, builtin, t.params.non_uniform_discard, t.params.vectorize);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxCoarse.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxCoarse.spec.ts
index 67a75bb010..488f7e5440 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxCoarse.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxCoarse.spec.ts
@@ -9,14 +9,22 @@ This may result in fewer unique positions that dpdxFine(e).
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { allInputSources } from '../../expression.js';
+
+import { d } from './derivatives.cache.js';
+import { runDerivativeTest } from './derivatives.js';
export const g = makeTestGroup(GPUTest);
+const builtin = 'dpdxCoarse';
+
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions')
- .desc(`f32 tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ .combine('non_uniform_discard', [false, true])
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('scalar');
+ runDerivativeTest(t, cases, builtin, t.params.non_uniform_discard, t.params.vectorize);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxFine.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxFine.spec.ts
index 91d65b990b..180aec2ec4 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxFine.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxFine.spec.ts
@@ -8,14 +8,22 @@ Returns the partial derivative of e with respect to window x coordinates.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { allInputSources } from '../../expression.js';
+
+import { d } from './derivatives.cache.js';
+import { runDerivativeTest } from './derivatives.js';
export const g = makeTestGroup(GPUTest);
+const builtin = 'dpdxFine';
+
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions')
- .desc(`f32 tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ .combine('non_uniform_discard', [false, true])
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('scalar');
+ runDerivativeTest(t, cases, builtin, t.params.non_uniform_discard, t.params.vectorize);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdy.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdy.spec.ts
index 0cd9cafdb9..94df913d5c 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdy.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdy.spec.ts
@@ -9,14 +9,22 @@ The result is the same as either dpdyFine(e) or dpdyCoarse(e).
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { allInputSources } from '../../expression.js';
+
+import { d } from './derivatives.cache.js';
+import { runDerivativeTest } from './derivatives.js';
export const g = makeTestGroup(GPUTest);
+const builtin = 'dpdy';
+
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions')
- .desc(`f32 tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ .combine('non_uniform_discard', [false, true])
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('scalar');
+ runDerivativeTest(t, cases, builtin, t.params.non_uniform_discard, t.params.vectorize);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyCoarse.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyCoarse.spec.ts
index f06869fdc2..2974475a6c 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyCoarse.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyCoarse.spec.ts
@@ -9,14 +9,22 @@ This may result in fewer unique positions that dpdyFine(e).
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { allInputSources } from '../../expression.js';
+
+import { d } from './derivatives.cache.js';
+import { runDerivativeTest } from './derivatives.js';
export const g = makeTestGroup(GPUTest);
+const builtin = 'dpdyCoarse';
+
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions')
- .desc(`f32 test`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ .combine('non_uniform_discard', [false, true])
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('scalar');
+ runDerivativeTest(t, cases, builtin, t.params.non_uniform_discard, t.params.vectorize);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyFine.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyFine.spec.ts
index e09761de95..5772024cc6 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyFine.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyFine.spec.ts
@@ -8,14 +8,22 @@ Returns the partial derivative of e with respect to window y coordinates.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { allInputSources } from '../../expression.js';
+
+import { d } from './derivatives.cache.js';
+import { runDerivativeTest } from './derivatives.js';
export const g = makeTestGroup(GPUTest);
+const builtin = 'dpdyFine';
+
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions')
- .desc(`f32 tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ .combine('non_uniform_discard', [false, true])
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('scalar');
+ runDerivativeTest(t, cases, builtin, t.params.non_uniform_discard, t.params.vectorize);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp.cache.ts
new file mode 100644
index 0000000000..5ecc8b2d97
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp.cache.ts
@@ -0,0 +1,44 @@
+import { kValue } from '../../../../../util/constants.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { biasedRange, linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// floor(ln(max f32 value)) = 88, so exp(88) will be within range of a f32, but exp(89) will not
+// floor(ln(max f64 value)) = 709, so exp(709) can be handled by the testing framework, but exp(710) will misbehave
+const f32_inputs = [
+ 0, // Returns 1 by definition
+ -89, // Returns subnormal value
+ kValue.f32.negative.min, // Closest to returning 0 as possible
+ ...biasedRange(kValue.f32.negative.max, -88, 100),
+ ...biasedRange(kValue.f32.positive.min, 88, 100),
+ ...linearRange(89, 709, 10), // Overflows f32, but not f64
+];
+
+// floor(ln(max f16 value)) = 11, so exp(11) will be within range of a f16, but exp(12) will not
+const f16_inputs = [
+ 0, // Returns 1 by definition
+ -12, // Returns subnormal value
+ kValue.f16.negative.min, // Closest to returning 0 as possible
+ ...biasedRange(kValue.f16.negative.max, -11, 100),
+ ...biasedRange(kValue.f16.positive.min, 11, 100),
+ ...linearRange(12, 709, 10), // Overflows f16, but not f64
+];
+
+export const d = makeCaseCache('exp', {
+ f32_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.expInterval);
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.expInterval);
+ },
+ f16_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.expInterval);
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.expInterval);
+ },
+ abstract: () => {
+ // exp has an ulp accuracy, so is only expected to be as accurate as f32
+ return FP.abstract.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.expInterval);
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp.spec.ts
index 8b1ced3cab..e6bf65fe4f 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'exp' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn exp(e1: T ) -> T
Returns the natural exponentiation of e1 (e.g. e^e1). Component-wise when T is a vector.
@@ -9,60 +9,33 @@ Returns the natural exponentiation of e1 (e.g. e^e1). Component-wise when T is a
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { kValue } from '../../../../../util/constants.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { biasedRange, linearRange } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './exp.cache.js';
export const g = makeTestGroup(GPUTest);
-// floor(ln(max f32 value)) = 88, so exp(88) will be within range of a f32, but exp(89) will not
-// floor(ln(max f64 value)) = 709, so exp(709) can be handled by the testing framework, but exp(710) will misbehave
-const f32_inputs = [
- 0, // Returns 1 by definition
- -89, // Returns subnormal value
- kValue.f32.negative.min, // Closest to returning 0 as possible
- ...biasedRange(kValue.f32.negative.max, -88, 100),
- ...biasedRange(kValue.f32.positive.min, 88, 100),
- ...linearRange(89, 709, 10), // Overflows f32, but not f64
-];
-
-// floor(ln(max f16 value)) = 11, so exp(11) will be within range of a f16, but exp(12) will not
-const f16_inputs = [
- 0, // Returns 1 by definition
- -12, // Returns subnormal value
- kValue.f16.negative.min, // Closest to returning 0 as possible
- ...biasedRange(kValue.f16.negative.max, -11, 100),
- ...biasedRange(kValue.f16.positive.min, 11, 100),
- ...linearRange(12, 709, 10), // Overflows f16, but not f64
-];
-
-export const d = makeCaseCache('exp', {
- f32_const: () => {
- return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.expInterval);
- },
- f32_non_const: () => {
- return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.expInterval);
- },
- f16_const: () => {
- return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.expInterval);
- },
- f16_non_const: () => {
- return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.expInterval);
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractFloatBuiltin('exp'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -72,7 +45,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('exp'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('exp'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -86,5 +59,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('exp'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('exp'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp2.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp2.cache.ts
new file mode 100644
index 0000000000..e2d0f1c16c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp2.cache.ts
@@ -0,0 +1,44 @@
+import { kValue } from '../../../../../util/constants.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { biasedRange, linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// floor(log2(max f32 value)) = 127, so exp2(127) will be within range of a f32, but exp2(128) will not
+// floor(ln(max f64 value)) = 1023, so exp2(1023) can be handled by the testing framework, but exp2(1024) will misbehave
+const f32_inputs = [
+ 0, // Returns 1 by definition
+ -128, // Returns subnormal value
+ kValue.f32.negative.min, // Closest to returning 0 as possible
+ ...biasedRange(kValue.f32.negative.max, -127, 100),
+ ...biasedRange(kValue.f32.positive.min, 127, 100),
+ ...linearRange(128, 1023, 10), // Overflows f32, but not f64
+];
+
+// floor(log2(max f16 value)) = 15, so exp2(15) will be within range of a f16, but exp2(15) will not
+const f16_inputs = [
+ 0, // Returns 1 by definition
+ -16, // Returns subnormal value
+ kValue.f16.negative.min, // Closest to returning 0 as possible
+ ...biasedRange(kValue.f16.negative.max, -15, 100),
+ ...biasedRange(kValue.f16.positive.min, 15, 100),
+ ...linearRange(16, 1023, 10), // Overflows f16, but not f64
+];
+
+export const d = makeCaseCache('exp2', {
+ f32_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.exp2Interval);
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.exp2Interval);
+ },
+ f16_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.exp2Interval);
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.exp2Interval);
+ },
+ abstract: () => {
+ // exp2 has an ulp accuracy, so is only expected to be as accurate as f32
+ return FP.abstract.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.exp2Interval);
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp2.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp2.spec.ts
index 67e123cb30..f47d2e5066 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp2.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp2.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'exp2' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn exp2(e: T ) -> T
Returns 2 raised to the power e (e.g. 2^e). Component-wise when T is a vector.
@@ -9,60 +9,33 @@ Returns 2 raised to the power e (e.g. 2^e). Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { kValue } from '../../../../../util/constants.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { biasedRange, linearRange } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './exp2.cache.js';
export const g = makeTestGroup(GPUTest);
-// floor(log2(max f32 value)) = 127, so exp2(127) will be within range of a f32, but exp2(128) will not
-// floor(ln(max f64 value)) = 1023, so exp2(1023) can be handled by the testing framework, but exp2(1024) will misbehave
-const f32_inputs = [
- 0, // Returns 1 by definition
- -128, // Returns subnormal value
- kValue.f32.negative.min, // Closest to returning 0 as possible
- ...biasedRange(kValue.f32.negative.max, -127, 100),
- ...biasedRange(kValue.f32.positive.min, 127, 100),
- ...linearRange(128, 1023, 10), // Overflows f32, but not f64
-];
-
-// floor(log2(max f16 value)) = 15, so exp2(15) will be within range of a f16, but exp2(15) will not
-const f16_inputs = [
- 0, // Returns 1 by definition
- -16, // Returns subnormal value
- kValue.f16.negative.min, // Closest to returning 0 as possible
- ...biasedRange(kValue.f16.negative.max, -15, 100),
- ...biasedRange(kValue.f16.positive.min, 15, 100),
- ...linearRange(16, 1023, 10), // Overflows f16, but not f64
-];
-
-export const d = makeCaseCache('exp2', {
- f32_const: () => {
- return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.exp2Interval);
- },
- f32_non_const: () => {
- return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.exp2Interval);
- },
- f16_const: () => {
- return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.exp2Interval);
- },
- f16_non_const: () => {
- return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.exp2Interval);
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractFloatBuiltin('exp2'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -72,7 +45,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('exp2'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('exp2'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -86,5 +59,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('exp2'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('exp2'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/extractBits.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/extractBits.spec.ts
index d535bf5d74..ef04b661bd 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/extractBits.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/extractBits.spec.ts
@@ -33,17 +33,7 @@ Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import {
- i32Bits,
- TypeI32,
- u32,
- TypeU32,
- u32Bits,
- vec2,
- vec3,
- vec4,
- TypeVec,
-} from '../../../../../util/conversion.js';
+import { i32Bits, Type, u32, u32Bits, vec2, vec3, vec4 } from '../../../../../util/conversion.js';
import { allInputSources, Config, run } from '../../expression.js';
import { builtin } from './builtin.js';
@@ -57,7 +47,7 @@ g.test('u32')
.fn(async t => {
const cfg: Config = t.params;
- const T = t.params.width === 1 ? TypeU32 : TypeVec(t.params.width, TypeU32);
+ const T = t.params.width === 1 ? Type.u32 : Type.vec(t.params.width, Type.u32);
const V = (x: number, y?: number, z?: number, w?: number) => {
y = y === undefined ? x : y;
@@ -193,7 +183,7 @@ g.test('u32')
);
}
- await run(t, builtin('extractBits'), [T, TypeU32, TypeU32], T, cfg, cases);
+ await run(t, builtin('extractBits'), [T, Type.u32, Type.u32], T, cfg, cases);
});
g.test('i32')
@@ -203,7 +193,7 @@ g.test('i32')
.fn(async t => {
const cfg: Config = t.params;
- const T = t.params.width === 1 ? TypeI32 : TypeVec(t.params.width, TypeI32);
+ const T = t.params.width === 1 ? Type.i32 : Type.vec(t.params.width, Type.i32);
const V = (x: number, y?: number, z?: number, w?: number) => {
y = y === undefined ? x : y;
@@ -333,5 +323,5 @@ g.test('i32')
);
}
- await run(t, builtin('extractBits'), [T, TypeU32, TypeU32], T, cfg, cases);
+ await run(t, builtin('extractBits'), [T, Type.u32, Type.u32], T, cfg, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/faceForward.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/faceForward.cache.ts
new file mode 100644
index 0000000000..17a5d0fd8f
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/faceForward.cache.ts
@@ -0,0 +1,125 @@
+import { ROArrayArray } from '../../../../../../common/util/types.js';
+import { anyOf } from '../../../../../util/compare.js';
+import { toVector } from '../../../../../util/conversion.js';
+import { FP, FPKind, FPVector } from '../../../../../util/floating_point.js';
+import { cartesianProduct } from '../../../../../util/math.js';
+import { Case, selectNCases } from '../../case.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { IntervalFilter } from '../../interval_filter.js';
+
+// Using a bespoke implementation of make*Case and generate*Cases here
+// since faceForwardIntervals is the only builtin with the API signature
+// (vec, vec, vec) -> vec
+//
+// Additionally faceForward has significant complexities around it due to the
+// fact that `dot` is calculated in its operation, but the result of dot isn't
+// used to calculate the builtin's result.
+
+/**
+ * @returns a Case for `faceForward`
+ * @param argumentKind what kind of floating point numbers being operated on
+ * @param parameterKind what kind of floating point operation should be performed,
+ * should be the same as argumentKind, except for abstract
+ * @param x the `x` param for the case
+ * @param y the `y` param for the case
+ * @param z the `z` param for the case
+ * @param check what interval checking to apply
+ * */
+function makeCase(
+ argumentKind: FPKind,
+ parameterKind: FPKind,
+ x: readonly number[],
+ y: readonly number[],
+ z: readonly number[],
+ check: IntervalFilter
+): Case | undefined {
+ const fp = FP[argumentKind];
+ x = x.map(fp.quantize);
+ y = y.map(fp.quantize);
+ z = z.map(fp.quantize);
+
+ const results = FP[parameterKind].faceForwardIntervals(x, y, z);
+ if (check === 'finite' && results.some(r => r === undefined)) {
+ return undefined;
+ }
+
+ // Stripping the undefined results, since undefined is used to signal that an OOB
+ // could occur within the calculation that isn't reflected in the result
+ // intervals.
+ const define_results = results.filter((r): r is FPVector => r !== undefined);
+
+ return {
+ input: [
+ toVector(x, fp.scalarBuilder),
+ toVector(y, fp.scalarBuilder),
+ toVector(z, fp.scalarBuilder),
+ ],
+ expected: anyOf(...define_results),
+ };
+}
+
+/**
+ * @returns an array of Cases for `faceForward`
+ * @param argumentKind what kind of floating point numbers being operated on
+ * @param parameterKind what kind of floating point operation should be performed,
+ * should be the same as argumentKind, except for abstract
+ * @param xs array of inputs to try for the `x` param
+ * @param ys array of inputs to try for the `y` param
+ * @param zs array of inputs to try for the `z` param
+ * @param check what interval checking to apply
+ */
+function generateCases(
+ argumentKind: FPKind,
+ parameterKind: FPKind,
+ xs: ROArrayArray<number>,
+ ys: ROArrayArray<number>,
+ zs: ROArrayArray<number>,
+ check: IntervalFilter
+): Case[] {
+ // Cannot use `cartesianProduct` here due to heterogeneous param types
+ return cartesianProduct(xs, ys, zs)
+ .map(e => makeCase(argumentKind, parameterKind, e[0], e[1], e[2], check))
+ .filter((c): c is Case => c !== undefined);
+}
+
+// Cases: [f32|f16|abstract]_vecN_[non_]const
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([2, 3, 4] as const).flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ if (trait !== 'abstract') {
+ return generateCases(
+ trait,
+ trait,
+ FP[trait].sparseVectorRange(dim),
+ FP[trait].sparseVectorRange(dim),
+ FP[trait].sparseVectorRange(dim),
+ nonConst ? 'unfiltered' : 'finite'
+ );
+ } else {
+ // Restricting the number of cases, because a vector of abstract floats needs to be returned, which is costly.
+ return selectNCases(
+ 'faceForward',
+ 20,
+ generateCases(
+ trait,
+ // faceForward has an inherited accuracy, so is only expected to be as accurate as f32
+ 'f32',
+ FP[trait].sparseVectorRange(dim),
+ FP[trait].sparseVectorRange(dim),
+ FP[trait].sparseVectorRange(dim),
+ nonConst ? 'unfiltered' : 'finite'
+ )
+ );
+ }
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('faceForward', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/faceForward.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/faceForward.spec.ts
index 6b6794fb9f..096abe034f 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/faceForward.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/faceForward.spec.ts
@@ -1,142 +1,68 @@
export const description = `
Execution tests for the 'faceForward' builtin function
-T is vecN<AbstractFloat>, vecN<f32>, or vecN<f16>
+T is vecN<Type.abstractFloat>, vecN<f32>, or vecN<f16>
@const fn faceForward(e1: T ,e2: T ,e3: T ) -> T
Returns e1 if dot(e2,e3) is negative, and -e1 otherwise.
`;
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
-import { ROArrayArray } from '../../../../../../common/util/types.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { anyOf } from '../../../../../util/compare.js';
-import { toVector, TypeF32, TypeF16, TypeVec } from '../../../../../util/conversion.js';
-import { FP, FPKind, FPVector } from '../../../../../util/floating_point.js';
-import {
- cartesianProduct,
- sparseVectorF32Range,
- sparseVectorF16Range,
-} from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, Case, IntervalFilter, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './faceForward.cache.js';
export const g = makeTestGroup(GPUTest);
-// Using a bespoke implementation of make*Case and generate*Cases here
-// since faceForwardIntervals is the only builtin with the API signature
-// (vec, vec, vec) -> vec
-//
-// Additionally faceForward has significant complexities around it due to the
-// fact that `dot` is calculated in it s operation, but the result of dot isn't
-// used to calculate the builtin's result.
-
-/**
- * @returns a Case for `faceForward`
- * @param kind what kind of floating point numbers being operated on
- * @param x the `x` param for the case
- * @param y the `y` param for the case
- * @param z the `z` param for the case
- * @param check what interval checking to apply
- * */
-function makeCase(
- kind: FPKind,
- x: readonly number[],
- y: readonly number[],
- z: readonly number[],
- check: IntervalFilter
-): Case | undefined {
- const fp = FP[kind];
- x = x.map(fp.quantize);
- y = y.map(fp.quantize);
- z = z.map(fp.quantize);
-
- const results = FP[kind].faceForwardIntervals(x, y, z);
- if (check === 'finite' && results.some(r => r === undefined)) {
- return undefined;
- }
-
- // Stripping the undefined results, since undefined is used to signal that an OOB
- // could occur within the calculation that isn't reflected in the result
- // intervals.
- const define_results = results.filter((r): r is FPVector => r !== undefined);
-
- return {
- input: [
- toVector(x, fp.scalarBuilder),
- toVector(y, fp.scalarBuilder),
- toVector(z, fp.scalarBuilder),
- ],
- expected: anyOf(...define_results),
- };
-}
-
-/**
- * @returns an array of Cases for `faceForward`
- * @param kind what kind of floating point numbers being operated on
- * @param xs array of inputs to try for the `x` param
- * @param ys array of inputs to try for the `y` param
- * @param zs array of inputs to try for the `z` param
- * @param check what interval checking to apply
- */
-function generateCases(
- kind: FPKind,
- xs: ROArrayArray<number>,
- ys: ROArrayArray<number>,
- zs: ROArrayArray<number>,
- check: IntervalFilter
-): Case[] {
- // Cannot use `cartesianProduct` here due to heterogeneous param types
- return cartesianProduct(xs, ys, zs)
- .map(e => makeCase(kind, e[0], e[1], e[2], check))
- .filter((c): c is Case => c !== undefined);
-}
-
-// Cases: f32_vecN_[non_]const
-const f32_vec_cases = ([2, 3, 4] as const)
- .flatMap(n =>
- ([true, false] as const).map(nonConst => ({
- [`f32_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return generateCases(
- 'f32',
- sparseVectorF32Range(n),
- sparseVectorF32Range(n),
- sparseVectorF32Range(n),
- nonConst ? 'unfiltered' : 'finite'
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-// Cases: f16_vecN_[non_]const
-const f16_vec_cases = ([2, 3, 4] as const)
- .flatMap(n =>
- ([true, false] as const).map(nonConst => ({
- [`f16_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return generateCases(
- 'f16',
- sparseVectorF16Range(n),
- sparseVectorF16Range(n),
- sparseVectorF16Range(n),
- nonConst ? 'unfiltered' : 'finite'
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
+g.test('abstract_float_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract float tests using vec2s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec2_const');
+ await run(
+ t,
+ abstractFloatBuiltin('faceForward'),
+ [Type.vec2af, Type.vec2af, Type.vec2af],
+ Type.vec2af,
+ t.params,
+ cases
+ );
+ });
-export const d = makeCaseCache('faceForward', {
- ...f32_vec_cases,
- ...f16_vec_cases,
-});
+g.test('abstract_float_vec3')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract float tests using vec3s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec3_const');
+ await run(
+ t,
+ abstractFloatBuiltin('faceForward'),
+ [Type.vec3af, Type.vec3af, Type.vec3af],
+ Type.vec3af,
+ t.params,
+ cases
+ );
+ });
-g.test('abstract_float')
- .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
- .desc(`abstract float tests`)
- .params(u => u.combine('inputSource', allInputSources).combine('vectorize', [2, 3, 4] as const))
- .unimplemented();
+g.test('abstract_float_vec4')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract float tests using vec4s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec4_const');
+ await run(
+ t,
+ abstractFloatBuiltin('faceForward'),
+ [Type.vec4af, Type.vec4af, Type.vec4af],
+ Type.vec4af,
+ t.params,
+ cases
+ );
+ });
g.test('f32_vec2')
.specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
@@ -149,8 +75,8 @@ g.test('f32_vec2')
await run(
t,
builtin('faceForward'),
- [TypeVec(2, TypeF32), TypeVec(2, TypeF32), TypeVec(2, TypeF32)],
- TypeVec(2, TypeF32),
+ [Type.vec2f, Type.vec2f, Type.vec2f],
+ Type.vec2f,
t.params,
cases
);
@@ -167,8 +93,8 @@ g.test('f32_vec3')
await run(
t,
builtin('faceForward'),
- [TypeVec(3, TypeF32), TypeVec(3, TypeF32), TypeVec(3, TypeF32)],
- TypeVec(3, TypeF32),
+ [Type.vec3f, Type.vec3f, Type.vec3f],
+ Type.vec3f,
t.params,
cases
);
@@ -185,8 +111,8 @@ g.test('f32_vec4')
await run(
t,
builtin('faceForward'),
- [TypeVec(4, TypeF32), TypeVec(4, TypeF32), TypeVec(4, TypeF32)],
- TypeVec(4, TypeF32),
+ [Type.vec4f, Type.vec4f, Type.vec4f],
+ Type.vec4f,
t.params,
cases
);
@@ -206,8 +132,8 @@ g.test('f16_vec2')
await run(
t,
builtin('faceForward'),
- [TypeVec(2, TypeF16), TypeVec(2, TypeF16), TypeVec(2, TypeF16)],
- TypeVec(2, TypeF16),
+ [Type.vec2h, Type.vec2h, Type.vec2h],
+ Type.vec2h,
t.params,
cases
);
@@ -227,8 +153,8 @@ g.test('f16_vec3')
await run(
t,
builtin('faceForward'),
- [TypeVec(3, TypeF16), TypeVec(3, TypeF16), TypeVec(3, TypeF16)],
- TypeVec(3, TypeF16),
+ [Type.vec3h, Type.vec3h, Type.vec3h],
+ Type.vec3h,
t.params,
cases
);
@@ -248,8 +174,8 @@ g.test('f16_vec4')
await run(
t,
builtin('faceForward'),
- [TypeVec(4, TypeF16), TypeVec(4, TypeF16), TypeVec(4, TypeF16)],
- TypeVec(4, TypeF16),
+ [Type.vec4h, Type.vec4h, Type.vec4h],
+ Type.vec4h,
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstLeadingBit.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstLeadingBit.spec.ts
index 26216563cd..9248b1e2bf 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstLeadingBit.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstLeadingBit.spec.ts
@@ -16,7 +16,7 @@ Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { i32, i32Bits, TypeI32, u32, TypeU32, u32Bits } from '../../../../../util/conversion.js';
+import { i32, i32Bits, Type, u32, u32Bits } from '../../../../../util/conversion.js';
import { allInputSources, Config, run } from '../../expression.js';
import { builtin } from './builtin.js';
@@ -31,7 +31,7 @@ g.test('u32')
)
.fn(async t => {
const cfg: Config = t.params;
- await run(t, builtin('firstLeadingBit'), [TypeU32], TypeU32, cfg, [
+ await run(t, builtin('firstLeadingBit'), [Type.u32], Type.u32, cfg, [
// Zero
{ input: u32Bits(0b00000000000000000000000000000000), expected: u32(-1) },
@@ -146,7 +146,7 @@ g.test('i32')
)
.fn(async t => {
const cfg: Config = t.params;
- await run(t, builtin('firstLeadingBit'), [TypeI32], TypeI32, cfg, [
+ await run(t, builtin('firstLeadingBit'), [Type.i32], Type.i32, cfg, [
// Zero
{ input: i32Bits(0b00000000000000000000000000000000), expected: i32(-1) },
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstTrailingBit.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstTrailingBit.spec.ts
index 5c65f59d28..a8dd27ee87 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstTrailingBit.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstTrailingBit.spec.ts
@@ -12,7 +12,7 @@ Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { i32, i32Bits, TypeI32, u32, TypeU32, u32Bits } from '../../../../../util/conversion.js';
+import { i32, i32Bits, Type, u32, u32Bits } from '../../../../../util/conversion.js';
import { allInputSources, Config, run } from '../../expression.js';
import { builtin } from './builtin.js';
@@ -27,7 +27,7 @@ g.test('u32')
)
.fn(async t => {
const cfg: Config = t.params;
- await run(t, builtin('firstTrailingBit'), [TypeU32], TypeU32, cfg, [
+ await run(t, builtin('firstTrailingBit'), [Type.u32], Type.u32, cfg, [
// Zero
{ input: u32Bits(0b00000000000000000000000000000000), expected: u32(-1) },
@@ -142,7 +142,7 @@ g.test('i32')
)
.fn(async t => {
const cfg: Config = t.params;
- await run(t, builtin('firstTrailingBit'), [TypeI32], TypeI32, cfg, [
+ await run(t, builtin('firstTrailingBit'), [Type.i32], Type.i32, cfg, [
// Zero
{ input: i32Bits(0b00000000000000000000000000000000), expected: i32(-1) },
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/floor.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/floor.cache.ts
new file mode 100644
index 0000000000..0af966cfd2
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/floor.cache.ts
@@ -0,0 +1,26 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+const kSmallMagnitudeTestValues = [0.1, 0.9, 1.0, 1.1, 1.9, -0.1, -0.9, -1.0, -1.1, -1.9];
+
+// See https://github.com/gpuweb/cts/issues/2766 for details
+const kIssue2766Value = {
+ abstract: 0x8000_0000_0000_0000,
+ f32: 0x8000_0000,
+ f16: 0x8000,
+};
+
+// Cases: [f32|f16|abstract]
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .map(trait => ({
+ [`${trait}`]: () => {
+ return FP[trait].generateScalarToIntervalCases(
+ [...kSmallMagnitudeTestValues, kIssue2766Value[trait], ...FP[trait].scalarRange()],
+ 'unfiltered',
+ FP[trait].floorInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('floor', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/floor.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/floor.spec.ts
index 873a6772c3..26cffe5d10 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/floor.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/floor.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'floor' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn floor(e: T ) -> T
Returns the floor of e. Component-wise when T is a vector.
@@ -9,54 +9,14 @@ Returns the floor of e. Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16, TypeAbstractFloat } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF16Range, fullF64Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
+import { Type } from '../../../../../util/conversion.js';
import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { abstractBuiltin, builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './floor.cache.js';
export const g = makeTestGroup(GPUTest);
-const kSmallMagnitudeTestValues = [0.1, 0.9, 1.0, 1.1, 1.9, -0.1, -0.9, -1.0, -1.1, -1.9];
-
-export const d = makeCaseCache('floor', {
- f32: () => {
- return FP.f32.generateScalarToIntervalCases(
- [
- ...kSmallMagnitudeTestValues,
- ...fullF32Range(),
- 0x8000_0000, // https://github.com/gpuweb/cts/issues/2766
- ],
- 'unfiltered',
- FP.f32.floorInterval
- );
- },
- f16: () => {
- return FP.f16.generateScalarToIntervalCases(
- [
- ...kSmallMagnitudeTestValues,
- ...fullF16Range(),
- 0x8000, // https://github.com/gpuweb/cts/issues/2766
- ],
- 'unfiltered',
- FP.f16.floorInterval
- );
- },
- abstract: () => {
- return FP.abstract.generateScalarToIntervalCases(
- [
- ...kSmallMagnitudeTestValues,
- ...fullF64Range(),
- 0x8000_0000_0000_0000, // https://github.com/gpuweb/cts/issues/2766
- ],
- 'unfiltered',
- FP.abstract.floorInterval
- );
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
@@ -67,7 +27,14 @@ g.test('abstract_float')
)
.fn(async t => {
const cases = await d.get('abstract');
- await run(t, abstractBuiltin('floor'), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases);
+ await run(
+ t,
+ abstractFloatBuiltin('floor'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
});
g.test('f32')
@@ -78,7 +45,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('floor'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('floor'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -92,5 +59,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('floor'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('floor'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fma.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fma.cache.ts
new file mode 100644
index 0000000000..20a61ce837
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fma.cache.ts
@@ -0,0 +1,26 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]_[non_]const
+// abstract_non_const is empty and not used
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateScalarTripleToIntervalCases(
+ FP[trait].sparseScalarRange(),
+ FP[trait].sparseScalarRange(),
+ FP[trait].sparseScalarRange(),
+ nonConst ? 'unfiltered' : 'finite',
+ // fma has an inherited accuracy, so abstract is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].fmaInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('fma', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fma.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fma.spec.ts
index 701f9d7ca9..620792d420 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fma.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fma.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'fma' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn fma(e1: T ,e2: T ,e3: T ) -> T
Returns e1 * e2 + e3. Component-wise when T is a vector.
@@ -9,64 +9,14 @@ Returns e1 * e2 + e3. Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16, TypeAbstractFloat } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { sparseF32Range, sparseF16Range, sparseF64Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
+import { Type } from '../../../../../util/conversion.js';
import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { abstractBuiltin, builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './fma.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('fma', {
- f32_const: () => {
- return FP.f32.generateScalarTripleToIntervalCases(
- sparseF32Range(),
- sparseF32Range(),
- sparseF32Range(),
- 'finite',
- FP.f32.fmaInterval
- );
- },
- f32_non_const: () => {
- return FP.f32.generateScalarTripleToIntervalCases(
- sparseF32Range(),
- sparseF32Range(),
- sparseF32Range(),
- 'unfiltered',
- FP.f32.fmaInterval
- );
- },
- f16_const: () => {
- return FP.f16.generateScalarTripleToIntervalCases(
- sparseF16Range(),
- sparseF16Range(),
- sparseF16Range(),
- 'finite',
- FP.f16.fmaInterval
- );
- },
- f16_non_const: () => {
- return FP.f16.generateScalarTripleToIntervalCases(
- sparseF16Range(),
- sparseF16Range(),
- sparseF16Range(),
- 'unfiltered',
- FP.f16.fmaInterval
- );
- },
- abstract: () => {
- return FP.abstract.generateScalarTripleToIntervalCases(
- sparseF64Range(),
- sparseF64Range(),
- sparseF64Range(),
- 'finite',
- FP.abstract.fmaInterval
- );
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
@@ -76,12 +26,12 @@ g.test('abstract_float')
.combine('vectorize', [undefined, 2, 3, 4] as const)
)
.fn(async t => {
- const cases = await d.get('abstract');
+ const cases = await d.get('abstract_const');
await run(
t,
- abstractBuiltin('fma'),
- [TypeAbstractFloat, TypeAbstractFloat, TypeAbstractFloat],
- TypeAbstractFloat,
+ abstractFloatBuiltin('fma'),
+ [Type.abstractFloat, Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
t.params,
cases
);
@@ -95,7 +45,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('fma'), [TypeF32, TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('fma'), [Type.f32, Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -109,5 +59,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('fma'), [TypeF16, TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('fma'), [Type.f16, Type.f16, Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fract.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fract.cache.ts
new file mode 100644
index 0000000000..5d0933554b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fract.cache.ts
@@ -0,0 +1,50 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+const kCommonValues = [
+ 0.5, // 0.5 -> 0.5
+ 0.9, // ~0.9 -> ~0.9
+ 1, // 1 -> 0
+ 2, // 2 -> 0
+ 1.11, // ~1.11 -> ~0.11
+ -0.1, // ~-0.1 -> ~0.9
+ -0.5, // -0.5 -> 0.5
+ -0.9, // ~-0.9 -> ~0.1
+ -1, // -1 -> 0
+ -2, // -2 -> 0
+ -1.11, // ~-1.11 -> ~0.89
+];
+
+const kTraitSpecificValues = {
+ f32: [
+ 10.0001, // ~10.0001 -> ~0.0001
+ -10.0001, // -10.0001 -> ~0.9999
+ 0x8000_0000, // https://github.com/gpuweb/cts/issues/2766
+ ],
+ f16: [
+ 10.0078125, // 10.0078125 -> 0.0078125
+ -10.0078125, // -10.0078125 -> 0.9921875
+ 658.5, // 658.5 -> 0.5
+ 0x8000, // https://github.com/gpuweb/cts/issues/2766
+ ],
+ abstract: [
+ 10.0001, // ~10.0001 -> ~0.0001
+ -10.0001, // -10.0001 -> ~0.9999
+ 0x8000_0000, // https://github.com/gpuweb/cts/issues/2766
+ ],
+};
+
+// Cases: [f32|f16|abstract]
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .map(trait => ({
+ [`${trait}`]: () => {
+ return FP[trait].generateScalarToIntervalCases(
+ [...kCommonValues, ...kTraitSpecificValues[trait], ...FP[trait].scalarRange()],
+ trait === 'abstract' ? 'finite' : 'unfiltered',
+ FP[trait].fractInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('fract', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fract.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fract.spec.ts
index 44ea31fde2..f1f7279c0b 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fract.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fract.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'fract' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn fract(e: T ) -> T
Returns the fractional part of e, computed as e - floor(e).
@@ -10,72 +10,33 @@ Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './fract.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('fract', {
- f32: () => {
- return FP.f32.generateScalarToIntervalCases(
- [
- 0.5, // 0.5 -> 0.5
- 0.9, // ~0.9 -> ~0.9
- 1, // 1 -> 0
- 2, // 2 -> 0
- 1.11, // ~1.11 -> ~0.11
- 10.0001, // ~10.0001 -> ~0.0001
- -0.1, // ~-0.1 -> ~0.9
- -0.5, // -0.5 -> 0.5
- -0.9, // ~-0.9 -> ~0.1
- -1, // -1 -> 0
- -2, // -2 -> 0
- -1.11, // ~-1.11 -> ~0.89
- -10.0001, // -10.0001 -> ~0.9999
- 0x80000000, // https://github.com/gpuweb/cts/issues/2766
- ...fullF32Range(),
- ],
- 'unfiltered',
- FP.f32.fractInterval
- );
- },
- f16: () => {
- return FP.f16.generateScalarToIntervalCases(
- [
- 0.5, // 0.5 -> 0.5
- 0.9, // ~0.9 -> ~0.9
- 1, // 1 -> 0
- 2, // 2 -> 0
- 1.11, // ~1.11 -> ~0.11
- 10.0078125, // 10.0078125 -> 0.0078125
- -0.1, // ~-0.1 -> ~0.9
- -0.5, // -0.5 -> 0.5
- -0.9, // ~-0.9 -> ~0.1
- -1, // -1 -> 0
- -2, // -2 -> 0
- -1.11, // ~-1.11 -> ~0.89
- -10.0078125, // -10.0078125 -> 0.9921875
- 658.5, // 658.5 -> 0.5
- ...fullF16Range(),
- ],
- 'unfiltered',
- FP.f16.fractInterval
- );
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractFloatBuiltin('fract'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -85,7 +46,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('fract'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('fract'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -99,5 +60,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('fract'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('fract'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/frexp.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/frexp.cache.ts
new file mode 100644
index 0000000000..2211e2adc9
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/frexp.cache.ts
@@ -0,0 +1,103 @@
+import { skipUndefined } from '../../../../../util/compare.js';
+import {
+ ScalarValue,
+ VectorValue,
+ i32,
+ toVector,
+ abstractInt,
+} from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { frexp } from '../../../../../util/math.js';
+import { Case } from '../../case.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+/* @returns a fract Case for a given scalar or vector input */
+function makeCaseFract(v: number | readonly number[], trait: 'f32' | 'f16' | 'abstract'): Case {
+ const fp = FP[trait];
+ let toInput: (n: readonly number[]) => ScalarValue | VectorValue;
+ let toOutput: (n: readonly number[]) => ScalarValue | VectorValue;
+ if (v instanceof Array) {
+ // Input is vector
+ toInput = (n: readonly number[]) => toVector(n, fp.scalarBuilder);
+ toOutput = (n: readonly number[]) => toVector(n, fp.scalarBuilder);
+ } else {
+ // Input is scalar, also wrap it in an array.
+ v = [v];
+ toInput = (n: readonly number[]) => fp.scalarBuilder(n[0]);
+ toOutput = (n: readonly number[]) => fp.scalarBuilder(n[0]);
+ }
+
+ v = v.map(fp.quantize);
+ if (v.some(e => e !== 0 && fp.isSubnormal(e))) {
+ return { input: toInput(v), expected: skipUndefined(undefined) };
+ }
+
+ const fs = v.map(e => {
+ return frexp(e, trait !== 'abstract' ? trait : 'f64').fract;
+ });
+
+ return { input: toInput(v), expected: toOutput(fs) };
+}
+
+/* @returns an exp Case for a given scalar or vector input */
+function makeCaseExp(v: number | readonly number[], trait: 'f32' | 'f16' | 'abstract'): Case {
+ const fp = FP[trait];
+ let toInput: (n: readonly number[]) => ScalarValue | VectorValue;
+ let toOutput: (n: readonly number[]) => ScalarValue | VectorValue;
+ if (v instanceof Array) {
+ // Input is vector
+ toInput = (n: readonly number[]) => toVector(n, fp.scalarBuilder);
+ toOutput = (n: readonly number[]) =>
+ toVector(n, trait !== 'abstract' ? i32 : (n: number) => abstractInt(BigInt(n)));
+ } else {
+ // Input is scalar, also wrap it in an array.
+ v = [v];
+ toInput = (n: readonly number[]) => fp.scalarBuilder(n[0]);
+ toOutput = (n: readonly number[]) =>
+ trait !== 'abstract' ? i32(n[0]) : abstractInt(BigInt(n[0]));
+ }
+
+ v = v.map(fp.quantize);
+ if (v.some(e => e !== 0 && fp.isSubnormal(e))) {
+ return { input: toInput(v), expected: skipUndefined(undefined) };
+ }
+
+ const fs = v.map(e => {
+ return frexp(e, trait !== 'abstract' ? trait : 'f64').exp;
+ });
+
+ return { input: toInput(v), expected: toOutput(fs) };
+}
+
+// Cases: [f32|f16]_vecN_[exp|whole]
+const vec_cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([2, 3, 4] as const).flatMap(dim =>
+ (['exp', 'fract'] as const).map(portion => ({
+ [`${trait}_vec${dim}_${portion}`]: () => {
+ return FP[trait]
+ .vectorRange(dim)
+ .map(v => (portion === 'exp' ? makeCaseExp(v, trait) : makeCaseFract(v, trait)));
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: [f32|f16]_[exp|whole]
+const scalar_cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ (['exp', 'fract'] as const).map(portion => ({
+ [`${trait}_${portion}`]: () => {
+ return FP[trait]
+ .scalarRange()
+ .map(v => (portion === 'exp' ? makeCaseExp(v, trait) : makeCaseFract(v, trait)));
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('frexp', {
+ ...scalar_cases,
+ ...vec_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/frexp.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/frexp.spec.ts
index ffe672b08c..8f07f3990d 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/frexp.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/frexp.spec.ts
@@ -15,34 +15,19 @@ The magnitude of the significand is in the range of [0.5, 1.0) or 0.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { skipUndefined } from '../../../../../util/compare.js';
-import {
- i32,
- Scalar,
- toVector,
- TypeF32,
- TypeF16,
- TypeI32,
- TypeVec,
- Vector,
-} from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import {
- frexp,
- fullF16Range,
- fullF32Range,
- vectorF16Range,
- vectorF32Range,
-} from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
+import { Type } from '../../../../../util/conversion.js';
import {
+ ShaderBuilder,
allInputSources,
basicExpressionBuilder,
- Case,
run,
- ShaderBuilder,
+ abstractFloatShaderBuilder,
+ abstractIntShaderBuilder,
+ onlyConstInputSource,
} from '../../expression.js';
+import { d } from './frexp.cache.js';
+
export const g = makeTestGroup(GPUTest);
/* @returns an ShaderBuilder that evaluates frexp and returns .fract from the result structure */
@@ -55,112 +40,159 @@ function expBuilder(): ShaderBuilder {
return basicExpressionBuilder(value => `frexp(${value}).exp`);
}
-/* @returns a fract Case for a given scalar or vector input */
-function makeVectorCaseFract(v: number | readonly number[], trait: 'f32' | 'f16'): Case {
- const fp = FP[trait];
- let toInput: (n: readonly number[]) => Scalar | Vector;
- let toOutput: (n: readonly number[]) => Scalar | Vector;
- if (v instanceof Array) {
- // Input is vector
- toInput = (n: readonly number[]) => toVector(n, fp.scalarBuilder);
- toOutput = (n: readonly number[]) => toVector(n, fp.scalarBuilder);
- } else {
- // Input is scalar, also wrap it in an array.
- v = [v];
- toInput = (n: readonly number[]) => fp.scalarBuilder(n[0]);
- toOutput = (n: readonly number[]) => fp.scalarBuilder(n[0]);
- }
-
- v = v.map(fp.quantize);
- if (v.some(e => e !== 0 && fp.isSubnormal(e))) {
- return { input: toInput(v), expected: skipUndefined(undefined) };
- }
-
- const fs = v.map(e => {
- return frexp(e, trait).fract;
+/* @returns an ShaderBuilder that evaluates frexp and returns .fract from the result structure, for abstract inputs */
+function abstractFractBuilder(): ShaderBuilder {
+ return abstractFloatShaderBuilder(value => `frexp(${value}).fract`);
+}
+
+/* @returns an ShaderBuilder that evaluates frexp and returns .exp from the result structure, for abstract inputs */
+function abstractExpBuilder(): ShaderBuilder {
+ return abstractIntShaderBuilder(value => `frexp(${value}).exp`);
+}
+
+g.test('abstract_float_fract')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is AbstractFloat
+
+struct __frexp_result_abstract {
+ fract : AbstractFloat, // fract part
+ exp : AbstractInt // exponent part
+}
+`
+ )
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_fract');
+ await run(t, abstractFractBuilder(), [Type.abstractFloat], Type.abstractFloat, t.params, cases);
+ });
+
+g.test('abstract_float_exp')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is AbstractFloat
+
+struct __frexp_result_abstract {
+ fract : AbstractFloat, // fract part
+ exp : AbstractInt // exponent part
+}
+`
+ )
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_exp');
+ await run(t, abstractExpBuilder(), [Type.abstractFloat], Type.abstractInt, t.params, cases);
+ });
+
+g.test('abstract_float_vec2_fract')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec2<AbstractFloat>
+
+struct __frexp_result_vec2_abstract {
+ fract : vec2<AbstractFloat>, // fract part
+ exp : vec2<AbstractInt> // exponent part
+}
+`
+ )
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec2_fract');
+ await run(t, abstractFractBuilder(), [Type.vec2af], Type.vec2af, t.params, cases);
+ });
+
+g.test('abstract_float_vec2_exp')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec2<AbstractFloat>
+
+struct __frexp_result_vec2_abstract {
+ fract : vec2<AbstractFloat>, // fractional part
+ exp : vec2<AbstractInt> // exponent part
+}
+`
+ )
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec2_exp');
+ await run(t, abstractExpBuilder(), [Type.vec2af], Type.vec2ai, t.params, cases);
});
- return { input: toInput(v), expected: toOutput(fs) };
+g.test('abstract_float_vec3_fract')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec3<AbstractFloat>
+
+struct __frexp_result_vec3_abstract {
+ fract : vec3<AbstractFloat>, // fractional part
+ exp : vec3<AbstractInt> // exponent part
}
+`
+ )
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec3_fract');
+ await run(t, abstractFractBuilder(), [Type.vec3af], Type.vec3af, t.params, cases);
+ });
-/* @returns an exp Case for a given scalar or vector input */
-function makeVectorCaseExp(v: number | readonly number[], trait: 'f32' | 'f16'): Case {
- const fp = FP[trait];
- let toInput: (n: readonly number[]) => Scalar | Vector;
- let toOutput: (n: readonly number[]) => Scalar | Vector;
- if (v instanceof Array) {
- // Input is vector
- toInput = (n: readonly number[]) => toVector(n, fp.scalarBuilder);
- toOutput = (n: readonly number[]) => toVector(n, i32);
- } else {
- // Input is scalar, also wrap it in an array.
- v = [v];
- toInput = (n: readonly number[]) => fp.scalarBuilder(n[0]);
- toOutput = (n: readonly number[]) => i32(n[0]);
- }
-
- v = v.map(fp.quantize);
- if (v.some(e => e !== 0 && fp.isSubnormal(e))) {
- return { input: toInput(v), expected: skipUndefined(undefined) };
- }
-
- const fs = v.map(e => {
- return frexp(e, trait).exp;
+g.test('abstract_float_vec3_exp')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec3<AbstractFloat>
+
+struct __frexp_result_vec3_abstract {
+ fract : vec3<AbstractFloat>, // fractional part
+ exp : vec3<AbstractInt> // exponent part
+}
+`
+ )
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec3_exp');
+ await run(t, abstractExpBuilder(), [Type.vec3af], Type.vec3ai, t.params, cases);
});
- return { input: toInput(v), expected: toOutput(fs) };
+g.test('abstract_float_vec4_fract')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec4<AbstractFloat>
+
+struct __frexp_result_vec4_abstract {
+ fract : vec4<AbstractFloat>, // fractional part
+ exp : vec4<AbstractInt> // exponent part
}
+`
+ )
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec4_fract');
+ await run(t, abstractFractBuilder(), [Type.vec4af], Type.vec4af, t.params, cases);
+ });
+
+g.test('abstract_float_vec4_exp')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec4<AbstractFloat>
-export const d = makeCaseCache('frexp', {
- f32_fract: () => {
- return fullF32Range().map(v => makeVectorCaseFract(v, 'f32'));
- },
- f32_exp: () => {
- return fullF32Range().map(v => makeVectorCaseExp(v, 'f32'));
- },
- f32_vec2_fract: () => {
- return vectorF32Range(2).map(v => makeVectorCaseFract(v, 'f32'));
- },
- f32_vec2_exp: () => {
- return vectorF32Range(2).map(v => makeVectorCaseExp(v, 'f32'));
- },
- f32_vec3_fract: () => {
- return vectorF32Range(3).map(v => makeVectorCaseFract(v, 'f32'));
- },
- f32_vec3_exp: () => {
- return vectorF32Range(3).map(v => makeVectorCaseExp(v, 'f32'));
- },
- f32_vec4_fract: () => {
- return vectorF32Range(4).map(v => makeVectorCaseFract(v, 'f32'));
- },
- f32_vec4_exp: () => {
- return vectorF32Range(4).map(v => makeVectorCaseExp(v, 'f32'));
- },
- f16_fract: () => {
- return fullF16Range().map(v => makeVectorCaseFract(v, 'f16'));
- },
- f16_exp: () => {
- return fullF16Range().map(v => makeVectorCaseExp(v, 'f16'));
- },
- f16_vec2_fract: () => {
- return vectorF16Range(2).map(v => makeVectorCaseFract(v, 'f16'));
- },
- f16_vec2_exp: () => {
- return vectorF16Range(2).map(v => makeVectorCaseExp(v, 'f16'));
- },
- f16_vec3_fract: () => {
- return vectorF16Range(3).map(v => makeVectorCaseFract(v, 'f16'));
- },
- f16_vec3_exp: () => {
- return vectorF16Range(3).map(v => makeVectorCaseExp(v, 'f16'));
- },
- f16_vec4_fract: () => {
- return vectorF16Range(4).map(v => makeVectorCaseFract(v, 'f16'));
- },
- f16_vec4_exp: () => {
- return vectorF16Range(4).map(v => makeVectorCaseExp(v, 'f16'));
- },
-});
+struct __frexp_result_vec4_abstract {
+ fract : vec4<AbstractFloat>, // fractional part
+ exp : vec4<AbstractInt> // exponent part
+}
+`
+ )
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec4_exp');
+ await run(t, abstractExpBuilder(), [Type.vec4af], Type.vec4ai, t.params, cases);
+ });
g.test('f32_fract')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -177,7 +209,7 @@ struct __frexp_result_f32 {
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get('f32_fract');
- await run(t, fractBuilder(), [TypeF32], TypeF32, t.params, cases);
+ await run(t, fractBuilder(), [Type.f32], Type.f32, t.params, cases);
});
g.test('f32_exp')
@@ -195,7 +227,7 @@ struct __frexp_result_f32 {
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get('f32_exp');
- await run(t, expBuilder(), [TypeF32], TypeI32, t.params, cases);
+ await run(t, expBuilder(), [Type.f32], Type.i32, t.params, cases);
});
g.test('f32_vec2_fract')
@@ -213,7 +245,7 @@ struct __frexp_result_vec2_f32 {
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get('f32_vec2_fract');
- await run(t, fractBuilder(), [TypeVec(2, TypeF32)], TypeVec(2, TypeF32), t.params, cases);
+ await run(t, fractBuilder(), [Type.vec2f], Type.vec2f, t.params, cases);
});
g.test('f32_vec2_exp')
@@ -231,7 +263,7 @@ struct __frexp_result_vec2_f32 {
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get('f32_vec2_exp');
- await run(t, expBuilder(), [TypeVec(2, TypeF32)], TypeVec(2, TypeI32), t.params, cases);
+ await run(t, expBuilder(), [Type.vec2f], Type.vec2i, t.params, cases);
});
g.test('f32_vec3_fract')
@@ -249,7 +281,7 @@ struct __frexp_result_vec3_f32 {
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get('f32_vec3_fract');
- await run(t, fractBuilder(), [TypeVec(3, TypeF32)], TypeVec(3, TypeF32), t.params, cases);
+ await run(t, fractBuilder(), [Type.vec3f], Type.vec3f, t.params, cases);
});
g.test('f32_vec3_exp')
@@ -267,7 +299,7 @@ struct __frexp_result_vec3_f32 {
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get('f32_vec3_exp');
- await run(t, expBuilder(), [TypeVec(3, TypeF32)], TypeVec(3, TypeI32), t.params, cases);
+ await run(t, expBuilder(), [Type.vec3f], Type.vec3i, t.params, cases);
});
g.test('f32_vec4_fract')
@@ -285,7 +317,7 @@ struct __frexp_result_vec4_f32 {
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get('f32_vec4_fract');
- await run(t, fractBuilder(), [TypeVec(4, TypeF32)], TypeVec(4, TypeF32), t.params, cases);
+ await run(t, fractBuilder(), [Type.vec4f], Type.vec4f, t.params, cases);
});
g.test('f32_vec4_exp')
@@ -303,7 +335,7 @@ struct __frexp_result_vec4_f32 {
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get('f32_vec4_exp');
- await run(t, expBuilder(), [TypeVec(4, TypeF32)], TypeVec(4, TypeI32), t.params, cases);
+ await run(t, expBuilder(), [Type.vec4f], Type.vec4i, t.params, cases);
});
g.test('f16_fract')
@@ -324,7 +356,7 @@ struct __frexp_result_f16 {
})
.fn(async t => {
const cases = await d.get('f16_fract');
- await run(t, fractBuilder(), [TypeF16], TypeF16, t.params, cases);
+ await run(t, fractBuilder(), [Type.f16], Type.f16, t.params, cases);
});
g.test('f16_exp')
@@ -345,7 +377,7 @@ struct __frexp_result_f16 {
})
.fn(async t => {
const cases = await d.get('f16_exp');
- await run(t, expBuilder(), [TypeF16], TypeI32, t.params, cases);
+ await run(t, expBuilder(), [Type.f16], Type.i32, t.params, cases);
});
g.test('f16_vec2_fract')
@@ -366,7 +398,7 @@ struct __frexp_result_vec2_f16 {
})
.fn(async t => {
const cases = await d.get('f16_vec2_fract');
- await run(t, fractBuilder(), [TypeVec(2, TypeF16)], TypeVec(2, TypeF16), t.params, cases);
+ await run(t, fractBuilder(), [Type.vec2h], Type.vec2h, t.params, cases);
});
g.test('f16_vec2_exp')
@@ -387,7 +419,7 @@ struct __frexp_result_vec2_f16 {
})
.fn(async t => {
const cases = await d.get('f16_vec2_exp');
- await run(t, expBuilder(), [TypeVec(2, TypeF16)], TypeVec(2, TypeI32), t.params, cases);
+ await run(t, expBuilder(), [Type.vec2h], Type.vec2i, t.params, cases);
});
g.test('f16_vec3_fract')
@@ -408,7 +440,7 @@ struct __frexp_result_vec3_f16 {
})
.fn(async t => {
const cases = await d.get('f16_vec3_fract');
- await run(t, fractBuilder(), [TypeVec(3, TypeF16)], TypeVec(3, TypeF16), t.params, cases);
+ await run(t, fractBuilder(), [Type.vec3h], Type.vec3h, t.params, cases);
});
g.test('f16_vec3_exp')
@@ -429,7 +461,7 @@ struct __frexp_result_vec3_f16 {
})
.fn(async t => {
const cases = await d.get('f16_vec3_exp');
- await run(t, expBuilder(), [TypeVec(3, TypeF16)], TypeVec(3, TypeI32), t.params, cases);
+ await run(t, expBuilder(), [Type.vec3h], Type.vec3i, t.params, cases);
});
g.test('f16_vec4_fract')
@@ -450,7 +482,7 @@ struct __frexp_result_vec4_f16 {
})
.fn(async t => {
const cases = await d.get('f16_vec4_fract');
- await run(t, fractBuilder(), [TypeVec(4, TypeF16)], TypeVec(4, TypeF16), t.params, cases);
+ await run(t, fractBuilder(), [Type.vec4h], Type.vec4h, t.params, cases);
});
g.test('f16_vec4_exp')
@@ -471,5 +503,5 @@ struct __frexp_result_vec4_f16 {
})
.fn(async t => {
const cases = await d.get('f16_vec4_exp');
- await run(t, expBuilder(), [TypeVec(4, TypeF16)], TypeVec(4, TypeI32), t.params, cases);
+ await run(t, expBuilder(), [Type.vec4h], Type.vec4i, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/insertBits.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/insertBits.spec.ts
index 1068e76252..b3eb65781d 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/insertBits.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/insertBits.spec.ts
@@ -18,17 +18,7 @@ Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import {
- i32Bits,
- TypeI32,
- u32,
- TypeU32,
- u32Bits,
- vec2,
- vec3,
- vec4,
- TypeVec,
-} from '../../../../../util/conversion.js';
+import { i32Bits, Type, u32, u32Bits, vec2, vec3, vec4 } from '../../../../../util/conversion.js';
import { allInputSources, Config, run } from '../../expression.js';
import { builtin } from './builtin.js';
@@ -46,8 +36,8 @@ g.test('integer')
)
.fn(async t => {
const cfg: Config = t.params;
- const scalarType = t.params.signed ? TypeI32 : TypeU32;
- const T = t.params.width === 1 ? scalarType : TypeVec(t.params.width, scalarType);
+ const scalarType = t.params.signed ? Type.i32 : Type.u32;
+ const T = t.params.width === 1 ? scalarType : Type.vec(t.params.width, scalarType);
const V = (x: number, y?: number, z?: number, w?: number) => {
y = y === undefined ? x : y;
@@ -382,5 +372,5 @@ g.test('integer')
);
}
- await run(t, builtin('insertBits'), [T, T, TypeU32, TypeU32], T, cfg, cases);
+ await run(t, builtin('insertBits'), [T, T, Type.u32, Type.u32], T, cfg, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/inversesqrt.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/inversesqrt.cache.ts
new file mode 100644
index 0000000000..e50d6d4866
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/inversesqrt.cache.ts
@@ -0,0 +1,44 @@
+import { kValue } from '../../../../../util/constants.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { biasedRange, linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+export const d = makeCaseCache('inverseSqrt', {
+ f32: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ [
+ // 0 < x <= 1 linearly spread
+ ...linearRange(kValue.f32.positive.min, 1, 100),
+ // 1 <= x < 2^32, biased towards 1
+ ...biasedRange(1, 2 ** 32, 1000),
+ ],
+ 'unfiltered',
+ FP.f32.inverseSqrtInterval
+ );
+ },
+ f16: () => {
+ return FP.f16.generateScalarToIntervalCases(
+ [
+ // 0 < x <= 1 linearly spread
+ ...linearRange(kValue.f16.positive.min, 1, 100),
+ // 1 <= x < 2^15, biased towards 1
+ ...biasedRange(1, 2 ** 15, 1000),
+ ],
+ 'unfiltered',
+ FP.f16.inverseSqrtInterval
+ );
+ },
+ abstract: () => {
+ return FP.abstract.generateScalarToIntervalCases(
+ [
+ // 0 < x <= 1 linearly spread
+ ...linearRange(kValue.f64.positive.min, 1, 100),
+ // 1 <= x < 2^64, biased towards 1, only using 100 steps, because af tests are more expensive per case
+ ...biasedRange(1, 2 ** 64, 100),
+ ],
+ 'finite',
+ // inverseSqrt has an ulp accuracy, so is only expected to be as accurate as f32
+ FP.f32.inverseSqrtInterval
+ );
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/inversesqrt.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/inversesqrt.spec.ts
index 3e83816387..954718de6c 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/inversesqrt.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/inversesqrt.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'inverseSqrt' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn inverseSqrt(e: T ) -> T
Returns the reciprocal of sqrt(e). Component-wise when T is a vector.
@@ -9,51 +9,33 @@ Returns the reciprocal of sqrt(e). Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { kValue } from '../../../../../util/constants.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { biasedRange, linearRange } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './inversesqrt.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('inverseSqrt', {
- f32: () => {
- return FP.f32.generateScalarToIntervalCases(
- [
- // 0 < x <= 1 linearly spread
- ...linearRange(kValue.f32.positive.min, 1, 100),
- // 1 <= x < 2^32, biased towards 1
- ...biasedRange(1, 2 ** 32, 1000),
- ],
- 'unfiltered',
- FP.f32.inverseSqrtInterval
- );
- },
- f16: () => {
- return FP.f16.generateScalarToIntervalCases(
- [
- // 0 < x <= 1 linearly spread
- ...linearRange(kValue.f16.positive.min, 1, 100),
- // 1 <= x < 2^15, biased towards 1
- ...biasedRange(1, 2 ** 15, 1000),
- ],
- 'unfiltered',
- FP.f16.inverseSqrtInterval
- );
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractFloatBuiltin('inverseSqrt'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -63,7 +45,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('inverseSqrt'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('inverseSqrt'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -77,5 +59,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('inverseSqrt'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('inverseSqrt'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ldexp.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ldexp.cache.ts
new file mode 100644
index 0000000000..cf9194319b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ldexp.cache.ts
@@ -0,0 +1,61 @@
+import { assert } from '../../../../../../common/util/util.js';
+import { anyOf } from '../../../../../util/compare.js';
+import { abstractInt, i32 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { biasedRange, quantizeToI32, sparseI32Range } from '../../../../../util/math.js';
+import { Case } from '../../case.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// ldexpInterval's return interval doesn't cover the flush-to-zero cases when e2 + bias <= 0, thus
+// special examination is required.
+// See the comment block on ldexpInterval for more details
+// e2 is an integer (i32) while e1 is float.
+const makeCase = (trait: 'f32' | 'f16' | 'abstract', e1: number, e2: number): Case => {
+ const FPTrait = FP[trait];
+ e1 = FPTrait.quantize(e1);
+ // e2 should be in i32 range for the convenience.
+ assert(-2147483648 <= e2 && e2 <= 2147483647, 'e2 should be in i32 range');
+ e2 = quantizeToI32(e2);
+
+ const expected = FPTrait.ldexpInterval(e1, e2);
+
+ const e2_scalar = trait === 'abstract' ? abstractInt(BigInt(e2)) : i32(e2);
+ // Result may be zero if e2 + bias <= 0
+ if (e2 + FPTrait.constants().bias <= 0) {
+ return {
+ input: [FPTrait.scalarBuilder(e1), e2_scalar],
+ expected: anyOf(expected, FPTrait.constants().zeroInterval),
+ };
+ }
+
+ return { input: [FPTrait.scalarBuilder(e1), e2_scalar], expected };
+};
+
+// Cases: [f32|f16|abstract]_[non_]const
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (nonConst) {
+ if (trait === 'abstract') {
+ return [];
+ }
+ return FP[trait]
+ .sparseScalarRange()
+ .flatMap(e1 => sparseI32Range().map(e2 => makeCase(trait, e1, e2)));
+ }
+ const bias = FP[trait].constants().bias;
+ // const
+ return FP[trait]
+ .sparseScalarRange()
+ .flatMap(e1 =>
+ biasedRange(-bias - 10, bias + 1, 10).flatMap(e2 =>
+ FP[trait].isFinite(e1 * 2 ** quantizeToI32(e2)) ? makeCase(trait, e1, e2) : []
+ )
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('ldexp', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ldexp.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ldexp.spec.ts
index 3829867752..d6cebfef6d 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ldexp.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ldexp.spec.ts
@@ -1,10 +1,10 @@
export const description = `
Execution tests for the 'ldexp' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
-K is AbstractInt, i32
+K is Type.abstractInt, i32
I is K or vecN<K>, where
I is a scalar if T is a scalar, or a vector when T is a vector
@@ -13,77 +13,15 @@ Returns e1 * 2^e2. Component-wise when T is a vector.
`;
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
-import { assert } from '../../../../../../common/util/util.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { anyOf } from '../../../../../util/compare.js';
-import { i32, TypeF32, TypeF16, TypeI32 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import {
- biasedRange,
- quantizeToI32,
- sparseF32Range,
- sparseI32Range,
- sparseF16Range,
-} from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, Case, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './ldexp.cache.js';
export const g = makeTestGroup(GPUTest);
-const bias = {
- f32: 127,
- f16: 15,
-} as const;
-
-// ldexpInterval's return interval doesn't cover the flush-to-zero cases when e2 + bias <= 0, thus
-// special examination is required.
-// See the comment block on ldexpInterval for more details
-// e2 is an integer (i32) while e1 is float.
-const makeCase = (trait: 'f32' | 'f16', e1: number, e2: number): Case => {
- const FPTrait = FP[trait];
- e1 = FPTrait.quantize(e1);
- // e2 should be in i32 range for the convinience.
- assert(-2147483648 <= e2 && e2 <= 2147483647, 'e2 should be in i32 range');
- e2 = quantizeToI32(e2);
-
- const expected = FPTrait.ldexpInterval(e1, e2);
-
- // Result may be zero if e2 + bias <= 0
- if (e2 + bias[trait] <= 0) {
- return {
- input: [FPTrait.scalarBuilder(e1), i32(e2)],
- expected: anyOf(expected, FPTrait.constants().zeroInterval),
- };
- }
-
- return { input: [FPTrait.scalarBuilder(e1), i32(e2)], expected };
-};
-
-export const d = makeCaseCache('ldexp', {
- f32_non_const: () => {
- return sparseF32Range().flatMap(e1 => sparseI32Range().map(e2 => makeCase('f32', e1, e2)));
- },
- f32_const: () => {
- return sparseF32Range().flatMap(e1 =>
- biasedRange(-bias.f32 - 10, bias.f32 + 1, 10).flatMap(e2 =>
- FP.f32.isFinite(e1 * 2 ** quantizeToI32(e2)) ? makeCase('f32', e1, e2) : []
- )
- );
- },
- f16_non_const: () => {
- return sparseF16Range().flatMap(e1 => sparseI32Range().map(e2 => makeCase('f16', e1, e2)));
- },
- f16_const: () => {
- return sparseF16Range().flatMap(e1 =>
- biasedRange(-bias.f16 - 10, bias.f16 + 1, 10).flatMap(e2 =>
- FP.f16.isFinite(e1 * 2 ** quantizeToI32(e2)) ? makeCase('f16', e1, e2) : []
- )
- );
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(
@@ -91,9 +29,21 @@ g.test('abstract_float')
`
)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract_const');
+ await run(
+ t,
+ abstractFloatBuiltin('ldexp'),
+ [Type.abstractFloat, Type.abstractInt],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -103,7 +53,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('ldexp'), [TypeF32, TypeI32], TypeF32, t.params, cases);
+ await run(t, builtin('ldexp'), [Type.f32, Type.i32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -117,5 +67,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('ldexp'), [TypeF16, TypeI32], TypeF16, t.params, cases);
+ await run(t, builtin('ldexp'), [Type.f16, Type.i32], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/length.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/length.cache.ts
new file mode 100644
index 0000000000..e7a2ee22ba
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/length.cache.ts
@@ -0,0 +1,42 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]_[non_]const
+const scalar_cases = (['f32', 'f16', 'abstract'] as const)
+ .map(trait => ({
+ [`${trait}`]: () => {
+ return FP[trait].generateScalarToIntervalCases(
+ FP[trait].scalarRange(),
+ trait !== 'abstract' ? 'unfiltered' : 'finite',
+ // length has an inherited accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].lengthInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: [f32|f16|abstract]_vecN_[non_]const
+const vec_cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([2, 3, 4] as const).flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateVectorToIntervalCases(
+ FP[trait].vectorRange(dim),
+ nonConst ? 'unfiltered' : 'finite',
+ // length has an inherited accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].lengthInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('length', {
+ ...scalar_cases,
+ ...vec_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/length.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/length.spec.ts
index 85c1f85169..735c8468b7 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/length.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/length.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'length' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn length(e: T ) -> f32
Returns the length of e (e.g. abs(e) if T is a scalar, or sqrt(e[0]^2 + e[1]^2 + ...) if T is a vector).
@@ -9,77 +9,77 @@ Returns the length of e (e.g. abs(e) if T is a scalar, or sqrt(e[0]^2 + e[1]^2 +
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16, TypeVec } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import {
- fullF32Range,
- fullF16Range,
- vectorF32Range,
- vectorF16Range,
-} from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
-
-import { builtin } from './builtin.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
+
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './length.cache.js';
export const g = makeTestGroup(GPUTest);
-// Cases: f32_vecN_[non_]const
-const f32_vec_cases = ([2, 3, 4] as const)
- .flatMap(n =>
- ([true, false] as const).map(nonConst => ({
- [`f32_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateVectorToIntervalCases(
- vectorF32Range(n),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.lengthInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-// Cases: f16_vecN_[non_]const
-const f16_vec_cases = ([2, 3, 4] as const)
- .flatMap(n =>
- ([true, false] as const).map(nonConst => ({
- [`f16_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateVectorToIntervalCases(
- vectorF16Range(n),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.lengthInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('length', {
- f32: () => {
- return FP.f32.generateScalarToIntervalCases(
- fullF32Range(),
- 'unfiltered',
- FP.f32.lengthInterval
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract_float tests`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractFloatBuiltin('length'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
+
+g.test('abstract_float_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract float tests using vec2s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec2_const');
+ await run(
+ t,
+ abstractFloatBuiltin('length'),
+ [Type.vec2af],
+ Type.abstractFloat,
+ t.params,
+ cases
);
- },
- ...f32_vec_cases,
- f16: () => {
- return FP.f16.generateScalarToIntervalCases(
- fullF16Range(),
- 'unfiltered',
- FP.f16.lengthInterval
+ });
+
+g.test('abstract_float_vec3')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract_float tests using vec3s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec3_const');
+ await run(
+ t,
+ abstractFloatBuiltin('length'),
+ [Type.vec3af],
+ Type.abstractFloat,
+ t.params,
+ cases
);
- },
- ...f16_vec_cases,
-});
+ });
-g.test('abstract_float')
+g.test('abstract_float_vec4')
.specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
- .desc(`abstract float tests`)
- .params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
- )
- .unimplemented();
+ .desc(`abstract_float tests using vec4s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec4_const');
+ await run(
+ t,
+ abstractFloatBuiltin('length'),
+ [Type.vec4af],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
@@ -87,7 +87,7 @@ g.test('f32')
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('length'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('length'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f32_vec2')
@@ -98,7 +98,7 @@ g.test('f32_vec2')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f32_vec2_const' : 'f32_vec2_non_const'
);
- await run(t, builtin('length'), [TypeVec(2, TypeF32)], TypeF32, t.params, cases);
+ await run(t, builtin('length'), [Type.vec2f], Type.f32, t.params, cases);
});
g.test('f32_vec3')
@@ -109,7 +109,7 @@ g.test('f32_vec3')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f32_vec3_const' : 'f32_vec3_non_const'
);
- await run(t, builtin('length'), [TypeVec(3, TypeF32)], TypeF32, t.params, cases);
+ await run(t, builtin('length'), [Type.vec3f], Type.f32, t.params, cases);
});
g.test('f32_vec4')
@@ -120,7 +120,7 @@ g.test('f32_vec4')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f32_vec4_const' : 'f32_vec4_non_const'
);
- await run(t, builtin('length'), [TypeVec(4, TypeF32)], TypeF32, t.params, cases);
+ await run(t, builtin('length'), [Type.vec4f], Type.f32, t.params, cases);
});
g.test('f16')
@@ -132,7 +132,7 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('length'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('length'), [Type.f16], Type.f16, t.params, cases);
});
g.test('f16_vec2')
@@ -146,7 +146,7 @@ g.test('f16_vec2')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f16_vec2_const' : 'f16_vec2_non_const'
);
- await run(t, builtin('length'), [TypeVec(2, TypeF16)], TypeF16, t.params, cases);
+ await run(t, builtin('length'), [Type.vec2h], Type.f16, t.params, cases);
});
g.test('f16_vec3')
@@ -160,7 +160,7 @@ g.test('f16_vec3')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f16_vec3_const' : 'f16_vec3_non_const'
);
- await run(t, builtin('length'), [TypeVec(3, TypeF16)], TypeF16, t.params, cases);
+ await run(t, builtin('length'), [Type.vec3h], Type.f16, t.params, cases);
});
g.test('f16_vec4')
@@ -174,5 +174,5 @@ g.test('f16_vec4')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f16_vec4_const' : 'f16_vec4_non_const'
);
- await run(t, builtin('length'), [TypeVec(4, TypeF16)], TypeF16, t.params, cases);
+ await run(t, builtin('length'), [Type.vec4h], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log.cache.ts
new file mode 100644
index 0000000000..76b8bfa1af
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log.cache.ts
@@ -0,0 +1,30 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { biasedRange, linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// log's accuracy is defined in three regions { [0, 0.5), [0.5, 2.0], (2.0, +∞] }
+// Cases: [f32|f16|abstract]_[non_]const
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateScalarToIntervalCases(
+ [
+ ...linearRange(FP[trait].constants().positive.min, 0.5, 20),
+ ...linearRange(0.5, 2.0, 20),
+ ...biasedRange(2.0, 2 ** 32, 1000),
+ ...FP[trait].scalarRange(),
+ ],
+ nonConst ? 'unfiltered' : 'finite',
+ // log has an absolute or ulp accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].logInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('log', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log.spec.ts
index ac60e2b1bc..99b4a6983e 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'log' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn log(e: T ) -> T
Returns the natural logarithm of e. Component-wise when T is a vector.
@@ -9,53 +9,33 @@ Returns the natural logarithm of e. Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { kValue } from '../../../../../util/constants.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { biasedRange, fullF32Range, fullF16Range, linearRange } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './log.cache.js';
export const g = makeTestGroup(GPUTest);
-// log's accuracy is defined in three regions { [0, 0.5), [0.5, 2.0], (2.0, +∞] }
-const f32_inputs = [
- ...linearRange(kValue.f32.positive.min, 0.5, 20),
- ...linearRange(0.5, 2.0, 20),
- ...biasedRange(2.0, 2 ** 32, 1000),
- ...fullF32Range(),
-];
-const f16_inputs = [
- ...linearRange(kValue.f16.positive.min, 0.5, 20),
- ...linearRange(0.5, 2.0, 20),
- ...biasedRange(2.0, 2 ** 32, 1000),
- ...fullF16Range(),
-];
-
-export const d = makeCaseCache('log', {
- f32_const: () => {
- return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.logInterval);
- },
- f32_non_const: () => {
- return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.logInterval);
- },
- f16_const: () => {
- return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.logInterval);
- },
- f16_non_const: () => {
- return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.logInterval);
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract_const');
+ await run(
+ t,
+ abstractFloatBuiltin('log'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -71,7 +51,7 @@ TODO(#792): Decide what the ground-truth is for these tests. [1]
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('log'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('log'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -85,5 +65,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('log'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('log'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log2.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log2.cache.ts
new file mode 100644
index 0000000000..d7781f328d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log2.cache.ts
@@ -0,0 +1,30 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { biasedRange, linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// log2's accuracy is defined in three regions { [0, 0.5), [0.5, 2.0], (2.0, +∞] }
+// Cases: [f32|f16|abstract]_[non_]const
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateScalarToIntervalCases(
+ [
+ ...linearRange(FP[trait].constants().positive.min, 0.5, 20),
+ ...linearRange(0.5, 2.0, 20),
+ ...biasedRange(2.0, 2 ** 32, 1000),
+ ...FP[trait].scalarRange(),
+ ],
+ nonConst ? 'unfiltered' : 'finite',
+ // log2 has an absolute or ulp accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].log2Interval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('log2', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log2.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log2.spec.ts
index 37931579b9..dc623a6784 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log2.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log2.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'log2' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn log2(e: T ) -> T
Returns the base-2 logarithm of e. Component-wise when T is a vector.
@@ -9,53 +9,33 @@ Returns the base-2 logarithm of e. Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { kValue } from '../../../../../util/constants.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { biasedRange, fullF32Range, fullF16Range, linearRange } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './log2.cache.js';
export const g = makeTestGroup(GPUTest);
-// log2's accuracy is defined in three regions { [0, 0.5), [0.5, 2.0], (2.0, +∞] }
-const f32_inputs = [
- ...linearRange(kValue.f32.positive.min, 0.5, 20),
- ...linearRange(0.5, 2.0, 20),
- ...biasedRange(2.0, 2 ** 32, 1000),
- ...fullF32Range(),
-];
-const f16_inputs = [
- ...linearRange(kValue.f16.positive.min, 0.5, 20),
- ...linearRange(0.5, 2.0, 20),
- ...biasedRange(2.0, 2 ** 32, 1000),
- ...fullF16Range(),
-];
-
-export const d = makeCaseCache('log2', {
- f32_const: () => {
- return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.log2Interval);
- },
- f32_non_const: () => {
- return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.log2Interval);
- },
- f16_const: () => {
- return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.log2Interval);
- },
- f16_non_const: () => {
- return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.log2Interval);
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract_const');
+ await run(
+ t,
+ abstractFloatBuiltin('log2'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -71,7 +51,7 @@ TODO(#792): Decide what the ground-truth is for these tests. [1]
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('log2'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('log2'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -85,5 +65,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('log2'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('log2'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/max.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/max.cache.ts
new file mode 100644
index 0000000000..72def03dab
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/max.cache.ts
@@ -0,0 +1,18 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .map(trait => ({
+ [`${trait}`]: () => {
+ return FP[trait].generateScalarPairToIntervalCases(
+ FP[trait].sparseScalarRange(),
+ FP[trait].sparseScalarRange(),
+ 'unfiltered',
+ FP[trait].maxInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('max', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/max.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/max.spec.ts
index 6654b4951c..ee7cb0d674 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/max.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/max.spec.ts
@@ -1,12 +1,12 @@
export const description = `
Execution tests for the 'max' builtin function
-S is AbstractInt, i32, or u32
+S is abstract-int, i32, or u32
T is S or vecN<S>
@const fn max(e1: T ,e2: T) -> T
Returns e2 if e1 is less than e2, and e1 otherwise. Component-wise when T is a vector.
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is vecN<S>
@const fn max(e1: T ,e2: T) -> T
Returns e2 if e1 is less than e2, and e1 otherwise.
@@ -18,72 +18,50 @@ Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import {
- i32,
- TypeF32,
- TypeF16,
- TypeI32,
- TypeU32,
- u32,
- TypeAbstractFloat,
-} from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF16Range, sparseF64Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, Case, onlyConstInputSource, run } from '../../expression.js';
-
-import { abstractBuiltin, builtin } from './builtin.js';
+import { Type, i32, u32, abstractInt } from '../../../../../util/conversion.js';
+import { maxBigInt } from '../../../../../util/math.js';
+import { Case } from '../../case.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
+
+import { abstractFloatBuiltin, abstractIntBuiltin, builtin } from './builtin.js';
+import { d } from './max.cache.js';
/** Generate set of max test cases from list of interesting values */
-function generateTestCases(
- values: Array<number>,
- makeCase: (x: number, y: number) => Case
-): Array<Case> {
- const cases = new Array<Case>();
- values.forEach(e => {
- values.forEach(f => {
- cases.push(makeCase(e, f));
+function generateTestCases<Type>(values: Type[], makeCase: (x: Type, y: Type) => Case): Case[] {
+ return values.flatMap(e => {
+ return values.map(f => {
+ return makeCase(e, f);
});
});
- return cases;
}
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('max', {
- f32: () => {
- return FP.f32.generateScalarPairToIntervalCases(
- fullF32Range(),
- fullF32Range(),
- 'unfiltered',
- FP.f32.maxInterval
- );
- },
- f16: () => {
- return FP.f16.generateScalarPairToIntervalCases(
- fullF16Range(),
- fullF16Range(),
- 'unfiltered',
- FP.f16.maxInterval
- );
- },
- abstract: () => {
- return FP.abstract.generateScalarPairToIntervalCases(
- sparseF64Range(),
- sparseF64Range(),
- 'unfiltered',
- FP.abstract.maxInterval
- );
- },
-});
-
g.test('abstract_int')
.specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
.desc(`abstract int tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const makeCase = (x: bigint, y: bigint): Case => {
+ return { input: [abstractInt(x), abstractInt(y)], expected: abstractInt(maxBigInt(x, y)) };
+ };
+
+ const test_values = [-0x70000000n, -2n, -1n, 0n, 1n, 2n, 0x70000000n];
+ const cases = generateTestCases(test_values, makeCase);
+
+ await run(
+ t,
+ abstractIntBuiltin('max'),
+ [Type.abstractInt, Type.abstractInt],
+ Type.abstractInt,
+ t.params,
+ cases
+ );
+ });
g.test('u32')
.specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
@@ -96,10 +74,10 @@ g.test('u32')
return { input: [u32(x), u32(y)], expected: u32(Math.max(x, y)) };
};
- const test_values: Array<number> = [0, 1, 2, 0x70000000, 0x80000000, 0xffffffff];
+ const test_values: number[] = [0, 1, 2, 0x70000000, 0x80000000, 0xffffffff];
const cases = generateTestCases(test_values, makeCase);
- await run(t, builtin('max'), [TypeU32, TypeU32], TypeU32, t.params, cases);
+ await run(t, builtin('max'), [Type.u32, Type.u32], Type.u32, t.params, cases);
});
g.test('i32')
@@ -113,10 +91,10 @@ g.test('i32')
return { input: [i32(x), i32(y)], expected: i32(Math.max(x, y)) };
};
- const test_values: Array<number> = [-0x70000000, -2, -1, 0, 1, 2, 0x70000000];
+ const test_values: number[] = [-0x70000000, -2, -1, 0, 1, 2, 0x70000000];
const cases = generateTestCases(test_values, makeCase);
- await run(t, builtin('max'), [TypeI32, TypeI32], TypeI32, t.params, cases);
+ await run(t, builtin('max'), [Type.i32, Type.i32], Type.i32, t.params, cases);
});
g.test('abstract_float')
@@ -131,9 +109,9 @@ g.test('abstract_float')
const cases = await d.get('abstract');
await run(
t,
- abstractBuiltin('max'),
- [TypeAbstractFloat, TypeAbstractFloat],
- TypeAbstractFloat,
+ abstractFloatBuiltin('max'),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
t.params,
cases
);
@@ -147,7 +125,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('max'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('max'), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -161,5 +139,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('max'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('max'), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/min.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/min.cache.ts
new file mode 100644
index 0000000000..cddca325f0
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/min.cache.ts
@@ -0,0 +1,18 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .map(trait => ({
+ [`${trait}`]: () => {
+ return FP[trait].generateScalarPairToIntervalCases(
+ FP[trait].sparseScalarRange(),
+ FP[trait].sparseScalarRange(),
+ 'unfiltered',
+ FP[trait].minInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('min', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/min.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/min.spec.ts
index 6c05319546..ac63641399 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/min.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/min.spec.ts
@@ -1,12 +1,12 @@
export const description = `
Execution tests for the 'min' builtin function
-S is AbstractInt, i32, or u32
+S is abstract-int, i32, or u32
T is S or vecN<S>
@const fn min(e1: T ,e2: T) -> T
Returns e1 if e1 is less than e2, and e2 otherwise. Component-wise when T is a vector.
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn min(e1: T ,e2: T) -> T
Returns e2 if e2 is less than e1, and e1 otherwise.
@@ -17,72 +17,50 @@ Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import {
- i32,
- TypeF32,
- TypeF16,
- TypeI32,
- TypeU32,
- u32,
- TypeAbstractFloat,
-} from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF16Range, sparseF64Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, Case, onlyConstInputSource, run } from '../../expression.js';
-
-import { abstractBuiltin, builtin } from './builtin.js';
+import { Type, i32, u32, abstractInt } from '../../../../../util/conversion.js';
+import { minBigInt } from '../../../../../util/math.js';
+import { Case } from '../../case.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-export const g = makeTestGroup(GPUTest);
+import { abstractFloatBuiltin, abstractIntBuiltin, builtin } from './builtin.js';
+import { d } from './min.cache.js';
-export const d = makeCaseCache('min', {
- f32: () => {
- return FP.f32.generateScalarPairToIntervalCases(
- fullF32Range(),
- fullF32Range(),
- 'unfiltered',
- FP.f32.minInterval
- );
- },
- f16: () => {
- return FP.f16.generateScalarPairToIntervalCases(
- fullF16Range(),
- fullF16Range(),
- 'unfiltered',
- FP.f16.minInterval
- );
- },
- abstract: () => {
- return FP.abstract.generateScalarPairToIntervalCases(
- sparseF64Range(),
- sparseF64Range(),
- 'unfiltered',
- FP.abstract.minInterval
- );
- },
-});
+export const g = makeTestGroup(GPUTest);
/** Generate set of min test cases from list of interesting values */
-function generateTestCases(
- values: Array<number>,
- makeCase: (x: number, y: number) => Case
-): Array<Case> {
- const cases = new Array<Case>();
- values.forEach(e => {
- values.forEach(f => {
- cases.push(makeCase(e, f));
+function generateTestCases<Type>(values: Type[], makeCase: (x: Type, y: Type) => Case): Case[] {
+ return values.flatMap(e => {
+ return values.map(f => {
+ return makeCase(e, f);
});
});
- return cases;
}
g.test('abstract_int')
.specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
.desc(`abstract int tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const makeCase = (x: bigint, y: bigint): Case => {
+ return { input: [abstractInt(x), abstractInt(y)], expected: abstractInt(minBigInt(x, y)) };
+ };
+
+ const test_values = [-0x70000000n, -2n, -1n, 0n, 1n, 2n, 0x70000000n];
+ const cases = generateTestCases(test_values, makeCase);
+
+ await run(
+ t,
+ abstractIntBuiltin('min'),
+ [Type.abstractInt, Type.abstractInt],
+ Type.abstractInt,
+ t.params,
+ cases
+ );
+ });
g.test('u32')
.specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
@@ -95,10 +73,10 @@ g.test('u32')
return { input: [u32(x), u32(y)], expected: u32(Math.min(x, y)) };
};
- const test_values: Array<number> = [0, 1, 2, 0x70000000, 0x80000000, 0xffffffff];
+ const test_values: number[] = [0, 1, 2, 0x70000000, 0x80000000, 0xffffffff];
const cases = generateTestCases(test_values, makeCase);
- await run(t, builtin('min'), [TypeU32, TypeU32], TypeU32, t.params, cases);
+ await run(t, builtin('min'), [Type.u32, Type.u32], Type.u32, t.params, cases);
});
g.test('i32')
@@ -112,10 +90,10 @@ g.test('i32')
return { input: [i32(x), i32(y)], expected: i32(Math.min(x, y)) };
};
- const test_values: Array<number> = [-0x70000000, -2, -1, 0, 1, 2, 0x70000000];
+ const test_values: number[] = [-0x70000000, -2, -1, 0, 1, 2, 0x70000000];
const cases = generateTestCases(test_values, makeCase);
- await run(t, builtin('min'), [TypeI32, TypeI32], TypeI32, t.params, cases);
+ await run(t, builtin('min'), [Type.i32, Type.i32], Type.i32, t.params, cases);
});
g.test('abstract_float')
@@ -130,9 +108,9 @@ g.test('abstract_float')
const cases = await d.get('abstract');
await run(
t,
- abstractBuiltin('min'),
- [TypeAbstractFloat, TypeAbstractFloat],
- TypeAbstractFloat,
+ abstractFloatBuiltin('min'),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
t.params,
cases
);
@@ -146,7 +124,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('min'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('min'), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -160,5 +138,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('min'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('min'), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/mix.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/mix.cache.ts
new file mode 100644
index 0000000000..be221297b0
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/mix.cache.ts
@@ -0,0 +1,56 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { selectNCases } from '../../case.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]_[non_]const
+// abstract_non_const is empty and unused
+const scalar_cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ const cases = FP[trait].generateScalarTripleToIntervalCases(
+ FP[trait].sparseScalarRange(),
+ FP[trait].sparseScalarRange(),
+ FP[trait].sparseScalarRange(),
+ nonConst ? 'unfiltered' : 'finite',
+ // mix has an inherited accuracy, so abstract is only expected to be as accurate as f32
+ ...FP[trait !== 'abstract' ? trait : 'f32'].mixIntervals
+ );
+ return selectNCases('mix_scalar', trait === 'abstract' ? 50 : cases.length, cases);
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: [f32|f16]_vecN_scalar_[non_]const
+// abstract_vecN_non_const is empty and unused
+const vec_scalar_cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([2, 3, 4] as const).flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ const cases = FP[trait].generateVectorPairScalarToVectorComponentWiseCase(
+ FP[trait].sparseVectorRange(dim),
+ FP[trait].sparseVectorRange(dim),
+ FP[trait].sparseScalarRange(),
+ nonConst ? 'unfiltered' : 'finite',
+ // mix has an inherited accuracy, so abstract is only expected to be as accurate as f32
+ ...FP[trait !== 'abstract' ? trait : 'f32'].mixIntervals
+ );
+ return selectNCases('mix_vector', trait === 'abstract' ? 50 : cases.length, cases);
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('mix', {
+ ...scalar_cases,
+ ...vec_scalar_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/mix.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/mix.spec.ts
index 95e9f6b310..0005ab5c0e 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/mix.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/mix.spec.ts
@@ -1,12 +1,12 @@
export const description = `
Execution tests for the 'mix' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn mix(e1: T, e2: T, e3: T) -> T
Returns the linear blend of e1 and e2 (e.g. e1*(1-e3)+e2*e3). Component-wise when T is a vector.
-T is AbstractFloat, f32, or f16
+T is abstract-float, f32, or f16
T2 is vecN<T>
@const fn mix(e1: T2, e2: T2, e3: T) -> T2
Returns the component-wise linear blend of e1 and e2, using scalar blending factor e3 for each component.
@@ -16,121 +16,81 @@ Same as mix(e1,e2,T2(e3)).
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeVec, TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import {
- sparseF32Range,
- sparseF16Range,
- sparseVectorF32Range,
- sparseVectorF16Range,
-} from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './mix.cache.js';
export const g = makeTestGroup(GPUTest);
-// Cases: f32_vecN_scalar_[non_]const
-const f32_vec_scalar_cases = ([2, 3, 4] as const)
- .flatMap(n =>
- ([true, false] as const).map(nonConst => ({
- [`f32_vec${n}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateVectorPairScalarToVectorComponentWiseCase(
- sparseVectorF32Range(n),
- sparseVectorF32Range(n),
- sparseF32Range(),
- nonConst ? 'unfiltered' : 'finite',
- ...FP.f32.mixIntervals
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-// Cases: f16_vecN_scalar_[non_]const
-const f16_vec_scalar_cases = ([2, 3, 4] as const)
- .flatMap(n =>
- ([true, false] as const).map(nonConst => ({
- [`f16_vec${n}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateVectorPairScalarToVectorComponentWiseCase(
- sparseVectorF16Range(n),
- sparseVectorF16Range(n),
- sparseF16Range(),
- nonConst ? 'unfiltered' : 'finite',
- ...FP.f16.mixIntervals
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('mix', {
- f32_const: () => {
- return FP.f32.generateScalarTripleToIntervalCases(
- sparseF32Range(),
- sparseF32Range(),
- sparseF32Range(),
- 'finite',
- ...FP.f32.mixIntervals
- );
- },
- f32_non_const: () => {
- return FP.f32.generateScalarTripleToIntervalCases(
- sparseF32Range(),
- sparseF32Range(),
- sparseF32Range(),
- 'unfiltered',
- ...FP.f32.mixIntervals
- );
- },
- ...f32_vec_scalar_cases,
- f16_const: () => {
- return FP.f16.generateScalarTripleToIntervalCases(
- sparseF16Range(),
- sparseF16Range(),
- sparseF16Range(),
- 'finite',
- ...FP.f16.mixIntervals
- );
- },
- f16_non_const: () => {
- return FP.f16.generateScalarTripleToIntervalCases(
- sparseF16Range(),
- sparseF16Range(),
- sparseF16Range(),
- 'unfiltered',
- ...FP.f16.mixIntervals
- );
- },
- ...f16_vec_scalar_cases,
-});
-
g.test('abstract_float_matching')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract_float test with matching third param`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract_const');
+ await run(
+ t,
+ abstractFloatBuiltin('mix'),
+ [Type.abstractFloat, Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('abstract_float_nonmatching_vec2')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract_float tests with two vec2<abstract_float> params and scalar third param`)
- .params(u => u.combine('inputSource', allInputSources))
- .unimplemented();
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec2_scalar_const');
+ await run(
+ t,
+ abstractFloatBuiltin('mix'),
+ [Type.vec(2, Type.abstractFloat), Type.vec(2, Type.abstractFloat), Type.abstractFloat],
+ Type.vec(2, Type.abstractFloat),
+ t.params,
+ cases
+ );
+ });
g.test('abstract_float_nonmatching_vec3')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract_float tests with two vec3<abstract_float> params and scalar third param`)
- .params(u => u.combine('inputSource', allInputSources))
- .unimplemented();
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec3_scalar_const');
+ await run(
+ t,
+ abstractFloatBuiltin('mix'),
+ [Type.vec(3, Type.abstractFloat), Type.vec(3, Type.abstractFloat), Type.abstractFloat],
+ Type.vec(3, Type.abstractFloat),
+ t.params,
+ cases
+ );
+ });
g.test('abstract_float_nonmatching_vec4')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract_float tests with two vec4<abstract_float> params and scalar third param`)
- .params(u => u.combine('inputSource', allInputSources))
- .unimplemented();
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec4_scalar_const');
+ await run(
+ t,
+ abstractFloatBuiltin('mix'),
+ [Type.vec(4, Type.abstractFloat), Type.vec(4, Type.abstractFloat), Type.abstractFloat],
+ Type.vec(4, Type.abstractFloat),
+ t.params,
+ cases
+ );
+ });
g.test('f32_matching')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -140,7 +100,7 @@ g.test('f32_matching')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('mix'), [TypeF32, TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('mix'), [Type.f32, Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('f32_nonmatching_vec2')
@@ -151,14 +111,7 @@ g.test('f32_nonmatching_vec2')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f32_vec2_scalar_const' : 'f32_vec2_scalar_non_const'
);
- await run(
- t,
- builtin('mix'),
- [TypeVec(2, TypeF32), TypeVec(2, TypeF32), TypeF32],
- TypeVec(2, TypeF32),
- t.params,
- cases
- );
+ await run(t, builtin('mix'), [Type.vec2f, Type.vec2f, Type.f32], Type.vec2f, t.params, cases);
});
g.test('f32_nonmatching_vec3')
@@ -169,14 +122,7 @@ g.test('f32_nonmatching_vec3')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f32_vec3_scalar_const' : 'f32_vec3_scalar_non_const'
);
- await run(
- t,
- builtin('mix'),
- [TypeVec(3, TypeF32), TypeVec(3, TypeF32), TypeF32],
- TypeVec(3, TypeF32),
- t.params,
- cases
- );
+ await run(t, builtin('mix'), [Type.vec3f, Type.vec3f, Type.f32], Type.vec3f, t.params, cases);
});
g.test('f32_nonmatching_vec4')
@@ -187,14 +133,7 @@ g.test('f32_nonmatching_vec4')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f32_vec4_scalar_const' : 'f32_vec4_scalar_non_const'
);
- await run(
- t,
- builtin('mix'),
- [TypeVec(4, TypeF32), TypeVec(4, TypeF32), TypeF32],
- TypeVec(4, TypeF32),
- t.params,
- cases
- );
+ await run(t, builtin('mix'), [Type.vec4f, Type.vec4f, Type.f32], Type.vec4f, t.params, cases);
});
g.test('f16_matching')
@@ -208,7 +147,7 @@ g.test('f16_matching')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('mix'), [TypeF16, TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('mix'), [Type.f16, Type.f16, Type.f16], Type.f16, t.params, cases);
});
g.test('f16_nonmatching_vec2')
@@ -222,14 +161,7 @@ g.test('f16_nonmatching_vec2')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f16_vec2_scalar_const' : 'f16_vec2_scalar_non_const'
);
- await run(
- t,
- builtin('mix'),
- [TypeVec(2, TypeF16), TypeVec(2, TypeF16), TypeF16],
- TypeVec(2, TypeF16),
- t.params,
- cases
- );
+ await run(t, builtin('mix'), [Type.vec2h, Type.vec2h, Type.f16], Type.vec2h, t.params, cases);
});
g.test('f16_nonmatching_vec3')
@@ -243,14 +175,7 @@ g.test('f16_nonmatching_vec3')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f16_vec3_scalar_const' : 'f16_vec3_scalar_non_const'
);
- await run(
- t,
- builtin('mix'),
- [TypeVec(3, TypeF16), TypeVec(3, TypeF16), TypeF16],
- TypeVec(3, TypeF16),
- t.params,
- cases
- );
+ await run(t, builtin('mix'), [Type.vec3h, Type.vec3h, Type.f16], Type.vec3h, t.params, cases);
});
g.test('f16_nonmatching_vec4')
@@ -264,12 +189,5 @@ g.test('f16_nonmatching_vec4')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f16_vec4_scalar_const' : 'f16_vec4_scalar_non_const'
);
- await run(
- t,
- builtin('mix'),
- [TypeVec(4, TypeF16), TypeVec(4, TypeF16), TypeF16],
- TypeVec(4, TypeF16),
- t.params,
- cases
- );
+ await run(t, builtin('mix'), [Type.vec4h, Type.vec4h, Type.f16], Type.vec4h, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/modf.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/modf.cache.ts
new file mode 100644
index 0000000000..1a76de56bb
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/modf.cache.ts
@@ -0,0 +1,75 @@
+import { toVector } from '../../../../../util/conversion.js';
+import { FP, FPKind } from '../../../../../util/floating_point.js';
+import { Case } from '../../case.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+/** @returns a fract Case for a scalar vector input */
+function makeScalarCaseFract(kind: FPKind, n: number): Case {
+ const fp = FP[kind];
+ n = fp.quantize(n);
+ const result = fp.modfInterval(n).fract;
+
+ return { input: fp.scalarBuilder(n), expected: result };
+}
+
+/** @returns a whole Case for a scalar vector input */
+function makeScalarCaseWhole(kind: FPKind, n: number): Case {
+ const fp = FP[kind];
+ n = fp.quantize(n);
+ const result = fp.modfInterval(n).whole;
+
+ return { input: fp.scalarBuilder(n), expected: result };
+}
+
+/** @returns a fract Case for a given vector input */
+function makeVectorCaseFract(kind: FPKind, v: readonly number[]): Case {
+ const fp = FP[kind];
+ v = v.map(fp.quantize);
+ const fs = v.map(e => {
+ return fp.modfInterval(e).fract;
+ });
+
+ return { input: toVector(v, fp.scalarBuilder), expected: fs };
+}
+
+/** @returns a whole Case for a given vector input */
+function makeVectorCaseWhole(kind: FPKind, v: readonly number[]): Case {
+ const fp = FP[kind];
+ v = v.map(fp.quantize);
+ const ws = v.map(e => {
+ return fp.modfInterval(e).whole;
+ });
+
+ return { input: toVector(v, fp.scalarBuilder), expected: ws };
+}
+
+// Cases: [f32|f16|abstract]_[fract|whole]
+const scalar_cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(kind =>
+ (['whole', 'fract'] as const).map(portion => ({
+ [`${kind}_${portion}`]: () => {
+ const makeCase = portion === 'whole' ? makeScalarCaseWhole : makeScalarCaseFract;
+ return FP[kind].scalarRange().map(makeCase.bind(null, kind));
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: [f32|f16|abstract]_vecN_[fract|whole]
+const vec_cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(kind =>
+ ([2, 3, 4] as const).flatMap(n =>
+ (['whole', 'fract'] as const).map(portion => ({
+ [`${kind}_vec${n}_${portion}`]: () => {
+ const makeCase = portion === 'whole' ? makeVectorCaseWhole : makeVectorCaseFract;
+ return FP[kind].vectorRange(n).map(makeCase.bind(null, kind));
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('modf', {
+ ...scalar_cases,
+ ...vec_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/modf.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/modf.spec.ts
index 1a3d8a2850..6c988008f8 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/modf.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/modf.spec.ts
@@ -1,13 +1,13 @@
export const description = `
Execution tests for the 'modf' builtin function
-T is f32 or f16 or AbstractFloat
+T is f32 or f16 or Type.abstractFloat
@const fn modf(e:T) -> result_struct
Splits |e| into fractional and whole number parts.
The whole part is (|e| % 1.0), and the fractional part is |e| minus the whole part.
Returns the result_struct for the given type.
-S is f32 or f16 or AbstractFloat
+S is f32 or f16 or Type.abstractFloat
T is vecN<S>
@const fn modf(e:T) -> result_struct
Splits the components of |e| into fractional and whole number parts.
@@ -18,33 +18,18 @@ Returns the result_struct for the given type.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import {
- toVector,
- TypeAbstractFloat,
- TypeF16,
- TypeF32,
- TypeVec,
-} from '../../../../../util/conversion.js';
-import { FP, FPKind } from '../../../../../util/floating_point.js';
-import {
- fullF16Range,
- fullF32Range,
- fullF64Range,
- vectorF16Range,
- vectorF32Range,
- vectorF64Range,
-} from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
+import { Type } from '../../../../../util/conversion.js';
import {
abstractFloatShaderBuilder,
allInputSources,
basicExpressionBuilder,
- Case,
onlyConstInputSource,
run,
ShaderBuilder,
} from '../../expression.js';
+import { d } from './modf.cache.js';
+
export const g = makeTestGroup(GPUTest);
/** @returns an ShaderBuilder that evaluates modf and returns .whole from the result structure */
@@ -67,101 +52,6 @@ function abstractFractBuilder(): ShaderBuilder {
return abstractFloatShaderBuilder(value => `modf(${value}).fract`);
}
-/** @returns a fract Case for a scalar vector input */
-function makeScalarCaseFract(kind: FPKind, n: number): Case {
- const fp = FP[kind];
- n = fp.quantize(n);
- const result = fp.modfInterval(n).fract;
-
- return { input: fp.scalarBuilder(n), expected: result };
-}
-
-/** @returns a whole Case for a scalar vector input */
-function makeScalarCaseWhole(kind: FPKind, n: number): Case {
- const fp = FP[kind];
- n = fp.quantize(n);
- const result = fp.modfInterval(n).whole;
-
- return { input: fp.scalarBuilder(n), expected: result };
-}
-
-/** @returns a fract Case for a given vector input */
-function makeVectorCaseFract(kind: FPKind, v: readonly number[]): Case {
- const fp = FP[kind];
- v = v.map(fp.quantize);
- const fs = v.map(e => {
- return fp.modfInterval(e).fract;
- });
-
- return { input: toVector(v, fp.scalarBuilder), expected: fs };
-}
-
-/** @returns a whole Case for a given vector input */
-function makeVectorCaseWhole(kind: FPKind, v: readonly number[]): Case {
- const fp = FP[kind];
- v = v.map(fp.quantize);
- const ws = v.map(e => {
- return fp.modfInterval(e).whole;
- });
-
- return { input: toVector(v, fp.scalarBuilder), expected: ws };
-}
-
-const scalar_range = {
- f32: fullF32Range(),
- f16: fullF16Range(),
- abstract: fullF64Range(),
-};
-
-const vector_range = {
- f32: {
- 2: vectorF32Range(2),
- 3: vectorF32Range(3),
- 4: vectorF32Range(4),
- },
- f16: {
- 2: vectorF16Range(2),
- 3: vectorF16Range(3),
- 4: vectorF16Range(4),
- },
- abstract: {
- 2: vectorF64Range(2),
- 3: vectorF64Range(3),
- 4: vectorF64Range(4),
- },
-};
-
-// Cases: [f32|f16|abstract]_[fract|whole]
-const scalar_cases = (['f32', 'f16', 'abstract'] as const)
- .flatMap(kind =>
- (['whole', 'fract'] as const).map(portion => ({
- [`${kind}_${portion}`]: () => {
- const makeCase = portion === 'whole' ? makeScalarCaseWhole : makeScalarCaseFract;
- return scalar_range[kind].map(makeCase.bind(null, kind));
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-// Cases: [f32|f16|abstract]_vecN_[fract|whole]
-const vec_cases = (['f32', 'f16', 'abstract'] as const)
- .flatMap(kind =>
- ([2, 3, 4] as const).flatMap(n =>
- (['whole', 'fract'] as const).map(portion => ({
- [`${kind}_vec${n}_${portion}`]: () => {
- const makeCase = portion === 'whole' ? makeVectorCaseWhole : makeVectorCaseFract;
- return vector_range[kind][n].map(makeCase.bind(null, kind));
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('modf', {
- ...scalar_cases,
- ...vec_cases,
-});
-
g.test('f32_fract')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(
@@ -177,7 +67,7 @@ struct __modf_result_f32 {
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get('f32_fract');
- await run(t, fractBuilder(), [TypeF32], TypeF32, t.params, cases);
+ await run(t, fractBuilder(), [Type.f32], Type.f32, t.params, cases);
});
g.test('f32_whole')
@@ -195,7 +85,7 @@ struct __modf_result_f32 {
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get('f32_whole');
- await run(t, wholeBuilder(), [TypeF32], TypeF32, t.params, cases);
+ await run(t, wholeBuilder(), [Type.f32], Type.f32, t.params, cases);
});
g.test('f32_vec2_fract')
@@ -213,7 +103,7 @@ struct __modf_result_vec2_f32 {
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get('f32_vec2_fract');
- await run(t, fractBuilder(), [TypeVec(2, TypeF32)], TypeVec(2, TypeF32), t.params, cases);
+ await run(t, fractBuilder(), [Type.vec2f], Type.vec2f, t.params, cases);
});
g.test('f32_vec2_whole')
@@ -231,7 +121,7 @@ struct __modf_result_vec2_f32 {
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get('f32_vec2_whole');
- await run(t, wholeBuilder(), [TypeVec(2, TypeF32)], TypeVec(2, TypeF32), t.params, cases);
+ await run(t, wholeBuilder(), [Type.vec2f], Type.vec2f, t.params, cases);
});
g.test('f32_vec3_fract')
@@ -249,7 +139,7 @@ struct __modf_result_vec3_f32 {
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get('f32_vec3_fract');
- await run(t, fractBuilder(), [TypeVec(3, TypeF32)], TypeVec(3, TypeF32), t.params, cases);
+ await run(t, fractBuilder(), [Type.vec3f], Type.vec3f, t.params, cases);
});
g.test('f32_vec3_whole')
@@ -267,7 +157,7 @@ struct __modf_result_vec3_f32 {
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get('f32_vec3_whole');
- await run(t, wholeBuilder(), [TypeVec(3, TypeF32)], TypeVec(3, TypeF32), t.params, cases);
+ await run(t, wholeBuilder(), [Type.vec3f], Type.vec3f, t.params, cases);
});
g.test('f32_vec4_fract')
@@ -285,7 +175,7 @@ struct __modf_result_vec4_f32 {
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get('f32_vec4_fract');
- await run(t, fractBuilder(), [TypeVec(4, TypeF32)], TypeVec(4, TypeF32), t.params, cases);
+ await run(t, fractBuilder(), [Type.vec4f], Type.vec4f, t.params, cases);
});
g.test('f32_vec4_whole')
@@ -303,7 +193,7 @@ struct __modf_result_vec4_f32 {
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get('f32_vec4_whole');
- await run(t, wholeBuilder(), [TypeVec(4, TypeF32)], TypeVec(4, TypeF32), t.params, cases);
+ await run(t, wholeBuilder(), [Type.vec4f], Type.vec4f, t.params, cases);
});
g.test('f16_fract')
@@ -324,7 +214,7 @@ struct __modf_result_f16 {
})
.fn(async t => {
const cases = await d.get('f16_fract');
- await run(t, fractBuilder(), [TypeF16], TypeF16, t.params, cases);
+ await run(t, fractBuilder(), [Type.f16], Type.f16, t.params, cases);
});
g.test('f16_whole')
@@ -345,7 +235,7 @@ struct __modf_result_f16 {
})
.fn(async t => {
const cases = await d.get('f16_whole');
- await run(t, wholeBuilder(), [TypeF16], TypeF16, t.params, cases);
+ await run(t, wholeBuilder(), [Type.f16], Type.f16, t.params, cases);
});
g.test('f16_vec2_fract')
@@ -366,7 +256,7 @@ struct __modf_result_vec2_f16 {
})
.fn(async t => {
const cases = await d.get('f16_vec2_fract');
- await run(t, fractBuilder(), [TypeVec(2, TypeF16)], TypeVec(2, TypeF16), t.params, cases);
+ await run(t, fractBuilder(), [Type.vec2h], Type.vec2h, t.params, cases);
});
g.test('f16_vec2_whole')
@@ -387,7 +277,7 @@ struct __modf_result_vec2_f16 {
})
.fn(async t => {
const cases = await d.get('f16_vec2_whole');
- await run(t, wholeBuilder(), [TypeVec(2, TypeF16)], TypeVec(2, TypeF16), t.params, cases);
+ await run(t, wholeBuilder(), [Type.vec2h], Type.vec2h, t.params, cases);
});
g.test('f16_vec3_fract')
@@ -408,7 +298,7 @@ struct __modf_result_vec3_f16 {
})
.fn(async t => {
const cases = await d.get('f16_vec3_fract');
- await run(t, fractBuilder(), [TypeVec(3, TypeF16)], TypeVec(3, TypeF16), t.params, cases);
+ await run(t, fractBuilder(), [Type.vec3h], Type.vec3h, t.params, cases);
});
g.test('f16_vec3_whole')
@@ -429,7 +319,7 @@ struct __modf_result_vec3_f16 {
})
.fn(async t => {
const cases = await d.get('f16_vec3_whole');
- await run(t, wholeBuilder(), [TypeVec(3, TypeF16)], TypeVec(3, TypeF16), t.params, cases);
+ await run(t, wholeBuilder(), [Type.vec3h], Type.vec3h, t.params, cases);
});
g.test('f16_vec4_fract')
@@ -450,7 +340,7 @@ struct __modf_result_vec4_f16 {
})
.fn(async t => {
const cases = await d.get('f16_vec4_fract');
- await run(t, fractBuilder(), [TypeVec(4, TypeF16)], TypeVec(4, TypeF16), t.params, cases);
+ await run(t, fractBuilder(), [Type.vec4h], Type.vec4h, t.params, cases);
});
g.test('f16_vec4_whole')
@@ -471,43 +361,43 @@ struct __modf_result_vec4_f16 {
})
.fn(async t => {
const cases = await d.get('f16_vec4_whole');
- await run(t, wholeBuilder(), [TypeVec(4, TypeF16)], TypeVec(4, TypeF16), t.params, cases);
+ await run(t, wholeBuilder(), [Type.vec4h], Type.vec4h, t.params, cases);
});
g.test('abstract_fract')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(
`
-T is AbstractFloat
+T is abstract-float
struct __modf_result_abstract {
- fract : AbstractFloat, // fractional part
- whole : AbstractFloat // whole part
+ fract : Type.abstractFloat, // fractional part
+ whole : Type.abstractFloat // whole part
}
`
)
.params(u => u.combine('inputSource', onlyConstInputSource))
.fn(async t => {
const cases = await d.get('abstract_fract');
- await run(t, abstractFractBuilder(), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases);
+ await run(t, abstractFractBuilder(), [Type.abstractFloat], Type.abstractFloat, t.params, cases);
});
g.test('abstract_whole')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(
`
-T is AbstractFloat
+T is abstract-float
struct __modf_result_abstract {
- fract : AbstractFloat, // fractional part
- whole : AbstractFloat // whole part
+ fract : Type.abstractFloat, // fractional part
+ whole : Type.abstractFloat // whole part
}
`
)
.params(u => u.combine('inputSource', onlyConstInputSource))
.fn(async t => {
const cases = await d.get('abstract_whole');
- await run(t, abstractWholeBuilder(), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases);
+ await run(t, abstractWholeBuilder(), [Type.abstractFloat], Type.abstractFloat, t.params, cases);
});
g.test('abstract_vec2_fract')
@@ -528,8 +418,8 @@ struct __modf_result_vec2_abstract {
await run(
t,
abstractFractBuilder(),
- [TypeVec(2, TypeAbstractFloat)],
- TypeVec(2, TypeAbstractFloat),
+ [Type.vec(2, Type.abstractFloat)],
+ Type.vec(2, Type.abstractFloat),
t.params,
cases
);
@@ -553,8 +443,8 @@ struct __modf_result_vec2_abstract {
await run(
t,
abstractWholeBuilder(),
- [TypeVec(2, TypeAbstractFloat)],
- TypeVec(2, TypeAbstractFloat),
+ [Type.vec(2, Type.abstractFloat)],
+ Type.vec(2, Type.abstractFloat),
t.params,
cases
);
@@ -578,8 +468,8 @@ struct __modf_result_vec3_abstract {
await run(
t,
abstractFractBuilder(),
- [TypeVec(3, TypeAbstractFloat)],
- TypeVec(3, TypeAbstractFloat),
+ [Type.vec(3, Type.abstractFloat)],
+ Type.vec(3, Type.abstractFloat),
t.params,
cases
);
@@ -603,8 +493,8 @@ struct __modf_result_vec3_abstract {
await run(
t,
abstractWholeBuilder(),
- [TypeVec(3, TypeAbstractFloat)],
- TypeVec(3, TypeAbstractFloat),
+ [Type.vec(3, Type.abstractFloat)],
+ Type.vec(3, Type.abstractFloat),
t.params,
cases
);
@@ -628,8 +518,8 @@ struct __modf_result_vec4_abstract {
await run(
t,
abstractFractBuilder(),
- [TypeVec(4, TypeAbstractFloat)],
- TypeVec(4, TypeAbstractFloat),
+ [Type.vec(4, Type.abstractFloat)],
+ Type.vec(4, Type.abstractFloat),
t.params,
cases
);
@@ -653,8 +543,8 @@ struct __modf_result_vec4_abstract {
await run(
t,
abstractWholeBuilder(),
- [TypeVec(4, TypeAbstractFloat)],
- TypeVec(4, TypeAbstractFloat),
+ [Type.vec(4, Type.abstractFloat)],
+ Type.vec(4, Type.abstractFloat),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/normalize.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/normalize.cache.ts
new file mode 100644
index 0000000000..7da5a43c22
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/normalize.cache.ts
@@ -0,0 +1,25 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]_vecN_[non_]const
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([2, 3, 4] as const).flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateVectorToVectorCases(
+ FP[trait].vectorRange(dim),
+ nonConst ? 'unfiltered' : 'finite',
+ // normalize has an inherited accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].normalizeInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('normalize', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/normalize.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/normalize.spec.ts
index 615617b448..06be8a125e 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/normalize.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/normalize.spec.ts
@@ -1,65 +1,47 @@
export const description = `
Execution tests for the 'normalize' builtin function
-T is AbstractFloat, f32, or f16
+T is abstract-float, f32, or f16
@const fn normalize(e: vecN<T> ) -> vecN<T>
Returns a unit vector in the same direction as e.
`;
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16, TypeVec } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { vectorF32Range, vectorF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './normalize.cache.js';
export const g = makeTestGroup(GPUTest);
-// Cases: f32_vecN_[non_]const
-const f32_vec_cases = ([2, 3, 4] as const)
- .flatMap(n =>
- ([true, false] as const).map(nonConst => ({
- [`f32_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateVectorToVectorCases(
- vectorF32Range(n),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.normalizeInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-// Cases: f16_vecN_[non_]const
-const f16_vec_cases = ([2, 3, 4] as const)
- .flatMap(n =>
- ([true, false] as const).map(nonConst => ({
- [`f16_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateVectorToVectorCases(
- vectorF16Range(n),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.normalizeInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
+g.test('abstract_float_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract float tests using vec2s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec2_const');
+ await run(t, abstractFloatBuiltin('normalize'), [Type.vec2af], Type.vec2af, t.params, cases);
+ });
-export const d = makeCaseCache('normalize', {
- ...f32_vec_cases,
- ...f16_vec_cases,
-});
+g.test('abstract_float_vec3')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract float tests using vec3s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec3_const');
+ await run(t, abstractFloatBuiltin('normalize'), [Type.vec3af], Type.vec3af, t.params, cases);
+ });
-g.test('abstract_float')
- .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
- .desc(`abstract float tests`)
- .params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
- )
- .unimplemented();
+g.test('abstract_float_vec4')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract float tests using vec4s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec4_const');
+ await run(t, abstractFloatBuiltin('normalize'), [Type.vec4af], Type.vec4af, t.params, cases);
+ });
g.test('f32_vec2')
.specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
@@ -69,7 +51,7 @@ g.test('f32_vec2')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f32_vec2_const' : 'f32_vec2_non_const'
);
- await run(t, builtin('normalize'), [TypeVec(2, TypeF32)], TypeVec(2, TypeF32), t.params, cases);
+ await run(t, builtin('normalize'), [Type.vec2f], Type.vec2f, t.params, cases);
});
g.test('f32_vec3')
@@ -80,7 +62,7 @@ g.test('f32_vec3')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f32_vec3_const' : 'f32_vec3_non_const'
);
- await run(t, builtin('normalize'), [TypeVec(3, TypeF32)], TypeVec(3, TypeF32), t.params, cases);
+ await run(t, builtin('normalize'), [Type.vec3f], Type.vec3f, t.params, cases);
});
g.test('f32_vec4')
@@ -91,7 +73,7 @@ g.test('f32_vec4')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f32_vec4_const' : 'f32_vec4_non_const'
);
- await run(t, builtin('normalize'), [TypeVec(4, TypeF32)], TypeVec(4, TypeF32), t.params, cases);
+ await run(t, builtin('normalize'), [Type.vec4f], Type.vec4f, t.params, cases);
});
g.test('f16_vec2')
@@ -105,7 +87,7 @@ g.test('f16_vec2')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f16_vec2_const' : 'f16_vec2_non_const'
);
- await run(t, builtin('normalize'), [TypeVec(2, TypeF16)], TypeVec(2, TypeF16), t.params, cases);
+ await run(t, builtin('normalize'), [Type.vec2h], Type.vec2h, t.params, cases);
});
g.test('f16_vec3')
@@ -119,7 +101,7 @@ g.test('f16_vec3')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f16_vec3_const' : 'f16_vec3_non_const'
);
- await run(t, builtin('normalize'), [TypeVec(3, TypeF16)], TypeVec(3, TypeF16), t.params, cases);
+ await run(t, builtin('normalize'), [Type.vec3h], Type.vec3h, t.params, cases);
});
g.test('f16_vec4')
@@ -133,5 +115,5 @@ g.test('f16_vec4')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f16_vec4_const' : 'f16_vec4_non_const'
);
- await run(t, builtin('normalize'), [TypeVec(4, TypeF16)], TypeVec(4, TypeF16), t.params, cases);
+ await run(t, builtin('normalize'), [Type.vec4h], Type.vec4h, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16float.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16float.cache.ts
new file mode 100644
index 0000000000..9cd7824cd9
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16float.cache.ts
@@ -0,0 +1,55 @@
+import { anyOf, skipUndefined } from '../../../../../util/compare.js';
+import { f32, pack2x16float, u32, vec2 } from '../../../../../util/conversion.js';
+import { cartesianProduct, quantizeToF32, scalarF32Range } from '../../../../../util/math.js';
+import { Case } from '../../case.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// pack2x16float has somewhat unusual behaviour, specifically around how it is
+// supposed to behave when values go OOB and when they are considered to have
+// gone OOB, so has its own bespoke implementation.
+
+/**
+ * @returns a Case for `pack2x16float`
+ * @param param0 first param for the case
+ * @param param1 second param for the case
+ * @param filter_undefined should inputs that cause an undefined expectation be
+ * filtered out, needed for const-eval
+ */
+function makeCase(param0: number, param1: number, filter_undefined: boolean): Case | undefined {
+ param0 = quantizeToF32(param0);
+ param1 = quantizeToF32(param1);
+
+ const results = pack2x16float(param0, param1);
+ if (filter_undefined && results.some(r => r === undefined)) {
+ return undefined;
+ }
+
+ return {
+ input: [vec2(f32(param0), f32(param1))],
+ expected: anyOf(
+ ...results.map(r => (r === undefined ? skipUndefined(undefined) : skipUndefined(u32(r))))
+ ),
+ };
+}
+
+/**
+ * @returns an array of Cases for `pack2x16float`
+ * @param param0s array of inputs to try for the first param
+ * @param param1s array of inputs to try for the second param
+ * @param filter_undefined should inputs that cause an undefined expectation be
+ * filtered out, needed for const-eval
+ */
+function generateCases(param0s: number[], param1s: number[], filter_undefined: boolean): Case[] {
+ return cartesianProduct(param0s, param1s)
+ .map(e => makeCase(e[0], e[1], filter_undefined))
+ .filter((c): c is Case => c !== undefined);
+}
+
+export const d = makeCaseCache('pack2x16float', {
+ f32_const: () => {
+ return generateCases(scalarF32Range(), scalarF32Range(), true);
+ },
+ f32_non_const: () => {
+ return generateCases(scalarF32Range(), scalarF32Range(), false);
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16float.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16float.spec.ts
index 790e54720c..5ba6993427 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16float.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16float.spec.ts
@@ -6,74 +6,14 @@ which is then placed in bits 16 × i through 16 × i + 15 of the result.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { anyOf, skipUndefined } from '../../../../../util/compare.js';
-import {
- f32,
- pack2x16float,
- TypeF32,
- TypeU32,
- TypeVec,
- u32,
- vec2,
-} from '../../../../../util/conversion.js';
-import { cartesianProduct, fullF32Range, quantizeToF32 } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, Case, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, run } from '../../expression.js';
import { builtin } from './builtin.js';
+import { d } from './pack2x16float.cache.js';
export const g = makeTestGroup(GPUTest);
-// pack2x16float has somewhat unusual behaviour, specifically around how it is
-// supposed to behave when values go OOB and when they are considered to have
-// gone OOB, so has its own bespoke implementation.
-
-/**
- * @returns a Case for `pack2x16float`
- * @param param0 first param for the case
- * @param param1 second param for the case
- * @param filter_undefined should inputs that cause an undefined expectation be
- * filtered out, needed for const-eval
- */
-function makeCase(param0: number, param1: number, filter_undefined: boolean): Case | undefined {
- param0 = quantizeToF32(param0);
- param1 = quantizeToF32(param1);
-
- const results = pack2x16float(param0, param1);
- if (filter_undefined && results.some(r => r === undefined)) {
- return undefined;
- }
-
- return {
- input: [vec2(f32(param0), f32(param1))],
- expected: anyOf(
- ...results.map(r => (r === undefined ? skipUndefined(undefined) : skipUndefined(u32(r))))
- ),
- };
-}
-
-/**
- * @returns an array of Cases for `pack2x16float`
- * @param param0s array of inputs to try for the first param
- * @param param1s array of inputs to try for the second param
- * @param filter_undefined should inputs that cause an undefined expectation be
- * filtered out, needed for const-eval
- */
-function generateCases(param0s: number[], param1s: number[], filter_undefined: boolean): Case[] {
- return cartesianProduct(param0s, param1s)
- .map(e => makeCase(e[0], e[1], filter_undefined))
- .filter((c): c is Case => c !== undefined);
-}
-
-export const d = makeCaseCache('pack2x16float', {
- f32_const: () => {
- return generateCases(fullF32Range(), fullF32Range(), true);
- },
- f32_non_const: () => {
- return generateCases(fullF32Range(), fullF32Range(), false);
- },
-});
-
g.test('pack')
.specURL('https://www.w3.org/TR/WGSL/#pack-builtin-functions')
.desc(
@@ -84,5 +24,5 @@ g.test('pack')
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('pack2x16float'), [TypeVec(2, TypeF32)], TypeU32, t.params, cases);
+ await run(t, builtin('pack2x16float'), [Type.vec2f], Type.u32, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16snorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16snorm.spec.ts
index 54bb21f6c6..1bcca2f73f 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16snorm.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16snorm.spec.ts
@@ -8,17 +8,10 @@ bits 16 × i through 16 × i + 15 of the result.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
import { kValue } from '../../../../../util/constants.js';
-import {
- f32,
- pack2x16snorm,
- TypeF32,
- TypeU32,
- TypeVec,
- u32,
- vec2,
-} from '../../../../../util/conversion.js';
+import { f32, pack2x16snorm, u32, vec2, Type } from '../../../../../util/conversion.js';
import { quantizeToF32, vectorF32Range } from '../../../../../util/math.js';
-import { allInputSources, Case, run } from '../../expression.js';
+import { Case } from '../../case.js';
+import { allInputSources, run } from '../../expression.js';
import { builtin } from './builtin.js';
@@ -51,5 +44,5 @@ g.test('pack')
];
});
- await run(t, builtin('pack2x16snorm'), [TypeVec(2, TypeF32)], TypeU32, t.params, cases);
+ await run(t, builtin('pack2x16snorm'), [Type.vec2f], Type.u32, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16unorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16unorm.spec.ts
index a875a9c7e1..334d106482 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16unorm.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16unorm.spec.ts
@@ -8,17 +8,10 @@ bits 16 × i through 16 × i + 15 of the result.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
import { kValue } from '../../../../../util/constants.js';
-import {
- f32,
- pack2x16unorm,
- TypeF32,
- TypeU32,
- TypeVec,
- u32,
- vec2,
-} from '../../../../../util/conversion.js';
+import { f32, pack2x16unorm, u32, vec2, Type } from '../../../../../util/conversion.js';
import { quantizeToF32, vectorF32Range } from '../../../../../util/math.js';
-import { allInputSources, Case, run } from '../../expression.js';
+import { Case } from '../../case.js';
+import { allInputSources, run } from '../../expression.js';
import { builtin } from './builtin.js';
@@ -51,5 +44,5 @@ g.test('pack')
];
});
- await run(t, builtin('pack2x16unorm'), [TypeVec(2, TypeF32)], TypeU32, t.params, cases);
+ await run(t, builtin('pack2x16unorm'), [Type.vec2f], Type.u32, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8snorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8snorm.spec.ts
index de0463e9fc..fbe362ea45 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8snorm.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8snorm.spec.ts
@@ -8,18 +8,10 @@ bits 8 × i through 8 × i + 7 of the result.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
import { kValue } from '../../../../../util/constants.js';
-import {
- f32,
- pack4x8snorm,
- Scalar,
- TypeF32,
- TypeU32,
- TypeVec,
- u32,
- vec4,
-} from '../../../../../util/conversion.js';
+import { f32, pack4x8snorm, ScalarValue, u32, vec4, Type } from '../../../../../util/conversion.js';
import { quantizeToF32, vectorF32Range } from '../../../../../util/math.js';
-import { allInputSources, Case, run } from '../../expression.js';
+import { Case } from '../../case.js';
+import { allInputSources, run } from '../../expression.js';
import { builtin } from './builtin.js';
@@ -35,7 +27,12 @@ g.test('pack')
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const makeCase = (vals: [number, number, number, number]): Case => {
- const vals_f32 = new Array<Scalar>(4) as [Scalar, Scalar, Scalar, Scalar];
+ const vals_f32 = new Array<ScalarValue>(4) as [
+ ScalarValue,
+ ScalarValue,
+ ScalarValue,
+ ScalarValue,
+ ];
for (const idx in vals) {
vals[idx] = quantizeToF32(vals[idx]);
vals_f32[idx] = f32(vals[idx]);
@@ -56,5 +53,5 @@ g.test('pack')
];
});
- await run(t, builtin('pack4x8snorm'), [TypeVec(4, TypeF32)], TypeU32, t.params, cases);
+ await run(t, builtin('pack4x8snorm'), [Type.vec4f], Type.u32, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8unorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8unorm.spec.ts
index b670e92fbb..c7d62e722b 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8unorm.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8unorm.spec.ts
@@ -8,18 +8,10 @@ bits 8 × i through 8 × i + 7 of the result.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
import { kValue } from '../../../../../util/constants.js';
-import {
- f32,
- pack4x8unorm,
- Scalar,
- TypeF32,
- TypeU32,
- TypeVec,
- u32,
- vec4,
-} from '../../../../../util/conversion.js';
+import { f32, pack4x8unorm, ScalarValue, u32, vec4, Type } from '../../../../../util/conversion.js';
import { quantizeToF32, vectorF32Range } from '../../../../../util/math.js';
-import { allInputSources, Case, run } from '../../expression.js';
+import { Case } from '../../case.js';
+import { allInputSources, run } from '../../expression.js';
import { builtin } from './builtin.js';
@@ -35,7 +27,12 @@ g.test('pack')
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const makeCase = (vals: [number, number, number, number]): Case => {
- const vals_f32 = new Array<Scalar>(4) as [Scalar, Scalar, Scalar, Scalar];
+ const vals_f32 = new Array<ScalarValue>(4) as [
+ ScalarValue,
+ ScalarValue,
+ ScalarValue,
+ ScalarValue,
+ ];
for (const idx in vals) {
vals[idx] = quantizeToF32(vals[idx]);
vals_f32[idx] = f32(vals[idx]);
@@ -56,5 +53,5 @@ g.test('pack')
];
});
- await run(t, builtin('pack4x8unorm'), [TypeVec(4, TypeF32)], TypeU32, t.params, cases);
+ await run(t, builtin('pack4x8unorm'), [Type.vec4f], Type.u32, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xI8.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xI8.spec.ts
new file mode 100644
index 0000000000..94aa67f0ae
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xI8.spec.ts
@@ -0,0 +1,69 @@
+export const description = `
+Execution tests for the 'pack4xI8' builtin function
+
+@const fn pack4xI8(e: vec4<i32>) -> u32
+Pack the lower 8 bits of each component of e into a u32 value and drop all the unused bits.
+Component e[i] of the input is mapped to bits (8 * i) through (8 * (i + 7)) of the result.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { u32, toVector, i32, Type } from '../../../../../util/conversion.js';
+import { Case } from '../../case.js';
+import { allInputSources, Config, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('basic')
+ .specURL('https://www.w3.org/TR/WGSL/#pack4xI8-builtin')
+ .desc(
+ `
+@const fn pack4xI8(e: vec4<i32>) -> u32
+ `
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cfg: Config = t.params;
+
+ const pack4xI8 = (vals: readonly [number, number, number, number]) => {
+ const result = new Uint32Array(1);
+ for (let i = 0; i < 4; ++i) {
+ result[0] |= (vals[i] & 0xff) << (i * 8);
+ }
+ return result[0];
+ };
+
+ const testInputs = [
+ [0, 0, 0, 0],
+ [1, 2, 3, 4],
+ [-1, 2, 3, 4],
+ [1, -2, 3, 4],
+ [1, 2, -3, 4],
+ [1, 2, 3, -4],
+ [-1, -2, 3, 4],
+ [-1, 2, -3, 4],
+ [-1, 2, 3, -4],
+ [1, -2, -3, 4],
+ [1, -2, 3, -4],
+ [1, 2, -3, -4],
+ [-1, -2, -3, 4],
+ [-1, -2, 3, -4],
+ [-1, 2, -3, -4],
+ [1, -2, -3, -4],
+ [-1, -2, -3, -4],
+ [127, 128, -128, -129],
+ [128, 128, -128, -128],
+ [32767, 32768, -32768, -32769],
+ ] as const;
+
+ const makeCase = (vals: readonly [number, number, number, number]): Case => {
+ return { input: [toVector(vals, i32)], expected: u32(pack4xI8(vals)) };
+ };
+ const cases: Array<Case> = testInputs.flatMap(v => {
+ return [makeCase(v)];
+ });
+
+ await run(t, builtin('pack4xI8'), [Type.vec4i], Type.u32, cfg, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xI8Clamp.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xI8Clamp.spec.ts
new file mode 100644
index 0000000000..4968ed1e04
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xI8Clamp.spec.ts
@@ -0,0 +1,73 @@
+export const description = `
+Execution tests for the 'pack4xI8Clamp' builtin function
+
+@const fn pack4xI8Clamp(e: vec4<i32>) -> u32
+Clamp each component of e in the range [-128, 127] and then pack the lower 8 bits of each component
+into a u32 value. Component e[i] of the input is mapped to bits (8 * i) through (8 * (i + 7)) of the
+result.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { u32, toVector, i32, Type } from '../../../../../util/conversion.js';
+import { clamp } from '../../../../../util/math.js';
+import { Case } from '../../case.js';
+import { allInputSources, Config, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('basic')
+ .specURL('https://www.w3.org/TR/WGSL/#pack4xI8Clamp-builtin')
+ .desc(
+ `
+@const fn pack4xI8Clamp(e: vec4<i32>) -> u32
+ `
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cfg: Config = t.params;
+
+ const pack4xI8Clamp = (vals: readonly [number, number, number, number]) => {
+ const result = new Uint32Array(1);
+ for (let i = 0; i < 4; ++i) {
+ const clampedValue = clamp(vals[i], { min: -128, max: 127 });
+ result[0] |= (clampedValue & 0xff) << (i * 8);
+ }
+ return result[0];
+ };
+
+ const testInputs = [
+ [0, 0, 0, 0],
+ [1, 2, 3, 4],
+ [-1, 2, 3, 4],
+ [1, -2, 3, 4],
+ [1, 2, -3, 4],
+ [1, 2, 3, -4],
+ [-1, -2, 3, 4],
+ [-1, 2, -3, 4],
+ [-1, 2, 3, -4],
+ [1, -2, -3, 4],
+ [1, -2, 3, -4],
+ [1, 2, -3, -4],
+ [-1, -2, -3, 4],
+ [-1, -2, 3, -4],
+ [-1, 2, -3, -4],
+ [1, -2, -3, -4],
+ [-1, -2, -3, -4],
+ [126, 127, 128, 129],
+ [-130, -129, -128, -127],
+ [127, 128, -128, -129],
+ [32767, 32768, -32768, -32769],
+ ] as const;
+
+ const makeCase = (vals: readonly [number, number, number, number]): Case => {
+ return { input: [toVector(vals, i32)], expected: u32(pack4xI8Clamp(vals)) };
+ };
+ const cases: Array<Case> = testInputs.flatMap(v => {
+ return [makeCase(v)];
+ });
+
+ await run(t, builtin('pack4xI8Clamp'), [Type.vec4i], Type.u32, cfg, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xU8.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xU8.spec.ts
new file mode 100644
index 0000000000..9d08e88d44
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xU8.spec.ts
@@ -0,0 +1,54 @@
+export const description = `
+Execution tests for the 'pack4xU8' builtin function
+
+@const fn pack4xU8(e: vec4<u32>) -> u32
+Pack the lower 8 bits of each component of e into a u32 value and drop all the unused bits.
+Component e[i] of the input is mapped to bits (8 * i) through (8 * (i + 7)) of the result.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { u32, toVector, Type } from '../../../../../util/conversion.js';
+import { Case } from '../../case.js';
+import { allInputSources, Config, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('basic')
+ .specURL('https://www.w3.org/TR/WGSL/#pack4xU8-builtin')
+ .desc(
+ `
+@const fn pack4xU8(e: vec4<u32>) -> u32
+ `
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cfg: Config = t.params;
+
+ const pack4xU8 = (vals: readonly [number, number, number, number]) => {
+ const result = new Uint32Array(1);
+ for (let i = 0; i < 4; ++i) {
+ result[0] |= (vals[i] & 0xff) << (i * 8);
+ }
+ return result[0];
+ };
+
+ const testInputs = [
+ [0, 0, 0, 0],
+ [1, 2, 3, 4],
+ [255, 255, 255, 255],
+ [254, 255, 256, 257],
+ [65535, 65536, 255, 254],
+ ] as const;
+
+ const makeCase = (vals: readonly [number, number, number, number]): Case => {
+ return { input: [toVector(vals, u32)], expected: u32(pack4xU8(vals)) };
+ };
+ const cases: Array<Case> = testInputs.flatMap(v => {
+ return [makeCase(v)];
+ });
+
+ await run(t, builtin('pack4xU8'), [Type.vec4u], Type.u32, cfg, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xU8Clamp.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xU8Clamp.spec.ts
new file mode 100644
index 0000000000..ffecf9b877
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xU8Clamp.spec.ts
@@ -0,0 +1,57 @@
+export const description = `
+Execution tests for the 'pack4xU8Clamp' builtin function
+
+@const fn pack4xU8Clamp(e: vec4<u32>) -> u32
+Clamp each component of e in the range of [0, 255] and then pack the lower 8 bits of each component
+into a u32 value. Component e[i] of the input is mapped to bits (8 * i) through (8 * (i + 7)) of the
+result.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { u32, toVector, Type } from '../../../../../util/conversion.js';
+import { clamp } from '../../../../../util/math.js';
+import { Case } from '../../case.js';
+import { allInputSources, Config, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('basic')
+ .specURL('https://www.w3.org/TR/WGSL/#pack4xU8Clamp-builtin')
+ .desc(
+ `
+@const fn pack4xU8Clamp(e: vec4<u32>) -> u32
+ `
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cfg: Config = t.params;
+
+ const pack4xU8Clamp = (vals: readonly [number, number, number, number]) => {
+ const result = new Uint32Array(1);
+ for (let i = 0; i < 4; ++i) {
+ const clampedValue = clamp(vals[i], { min: 0, max: 255 });
+ result[0] |= clampedValue << (i * 8);
+ }
+ return result[0];
+ };
+
+ const testInputs = [
+ [0, 0, 0, 0],
+ [1, 2, 3, 4],
+ [255, 255, 255, 255],
+ [254, 255, 256, 257],
+ [65535, 65536, 255, 254],
+ ] as const;
+
+ const makeCase = (vals: readonly [number, number, number, number]): Case => {
+ return { input: [toVector(vals, u32)], expected: u32(pack4xU8Clamp(vals)) };
+ };
+ const cases: Array<Case> = testInputs.flatMap(v => {
+ return [makeCase(v)];
+ });
+
+ await run(t, builtin('pack4xU8Clamp'), [Type.vec4u], Type.u32, cfg, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pow.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pow.cache.ts
new file mode 100644
index 0000000000..54777e702f
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pow.cache.ts
@@ -0,0 +1,24 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]_[non_]const
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateScalarPairToIntervalCases(
+ FP[trait].scalarRange(),
+ FP[trait].scalarRange(),
+ nonConst ? 'unfiltered' : 'finite',
+ // pow has an inherited accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].powInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('pow', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pow.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pow.spec.ts
index f9b4fe1cfa..84e9649c96 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pow.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pow.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'pow' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn pow(e1: T ,e2: T ) -> T
Returns e1 raised to the power e2. Component-wise when T is a vector.
@@ -9,58 +9,33 @@ Returns e1 raised to the power e2. Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './pow.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('pow', {
- f32_const: () => {
- return FP.f32.generateScalarPairToIntervalCases(
- fullF32Range(),
- fullF32Range(),
- 'finite',
- FP.f32.powInterval
- );
- },
- f32_non_const: () => {
- return FP.f32.generateScalarPairToIntervalCases(
- fullF32Range(),
- fullF32Range(),
- 'unfiltered',
- FP.f32.powInterval
- );
- },
- f16_const: () => {
- return FP.f16.generateScalarPairToIntervalCases(
- fullF16Range(),
- fullF16Range(),
- 'finite',
- FP.f16.powInterval
- );
- },
- f16_non_const: () => {
- return FP.f16.generateScalarPairToIntervalCases(
- fullF16Range(),
- fullF16Range(),
- 'unfiltered',
- FP.f16.powInterval
- );
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract_const');
+ await run(
+ t,
+ abstractFloatBuiltin('pow'),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -70,7 +45,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('pow'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('pow'), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -84,5 +59,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('pow'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('pow'), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/quantizeToF16.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/quantizeToF16.cache.ts
new file mode 100644
index 0000000000..91aa845d29
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/quantizeToF16.cache.ts
@@ -0,0 +1,41 @@
+import { kValue } from '../../../../../util/constants.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { scalarF16Range, scalarF32Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+export const d = makeCaseCache('quantizeToF16', {
+ f32_const: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ [
+ kValue.f16.negative.min,
+ kValue.f16.negative.max,
+ kValue.f16.negative.subnormal.min,
+ kValue.f16.negative.subnormal.max,
+ kValue.f16.positive.subnormal.min,
+ kValue.f16.positive.subnormal.max,
+ kValue.f16.positive.min,
+ kValue.f16.positive.max,
+ ...scalarF16Range(),
+ ],
+ 'finite',
+ FP.f32.quantizeToF16Interval
+ );
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ [
+ kValue.f16.negative.min,
+ kValue.f16.negative.max,
+ kValue.f16.negative.subnormal.min,
+ kValue.f16.negative.subnormal.max,
+ kValue.f16.positive.subnormal.min,
+ kValue.f16.positive.subnormal.max,
+ kValue.f16.positive.min,
+ kValue.f16.positive.max,
+ ...scalarF32Range(),
+ ],
+ 'unfiltered',
+ FP.f32.quantizeToF16Interval
+ );
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/quantizeToF16.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/quantizeToF16.spec.ts
index b37d4c5afb..0aa9669e93 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/quantizeToF16.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/quantizeToF16.spec.ts
@@ -10,54 +10,14 @@ Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { kValue } from '../../../../../util/constants.js';
-import { TypeF32 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF16Range, fullF32Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
+import { Type } from '../../../../../util/conversion.js';
import { allInputSources, run } from '../../expression.js';
import { builtin } from './builtin.js';
+import { d } from './quantizeToF16.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('quantizeToF16', {
- f32_const: () => {
- return FP.f32.generateScalarToIntervalCases(
- [
- kValue.f16.negative.min,
- kValue.f16.negative.max,
- kValue.f16.negative.subnormal.min,
- kValue.f16.negative.subnormal.max,
- kValue.f16.positive.subnormal.min,
- kValue.f16.positive.subnormal.max,
- kValue.f16.positive.min,
- kValue.f16.positive.max,
- ...fullF16Range(),
- ],
- 'finite',
- FP.f32.quantizeToF16Interval
- );
- },
- f32_non_const: () => {
- return FP.f32.generateScalarToIntervalCases(
- [
- kValue.f16.negative.min,
- kValue.f16.negative.max,
- kValue.f16.negative.subnormal.min,
- kValue.f16.negative.subnormal.max,
- kValue.f16.positive.subnormal.min,
- kValue.f16.positive.subnormal.max,
- kValue.f16.positive.min,
- kValue.f16.positive.max,
- ...fullF32Range(),
- ],
- 'unfiltered',
- FP.f32.quantizeToF16Interval
- );
- },
-});
-
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`f32 tests`)
@@ -66,5 +26,5 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('quantizeToF16'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('quantizeToF16'), [Type.f32], Type.f32, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/radians.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/radians.cache.ts
new file mode 100644
index 0000000000..8ed0fbbd2b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/radians.cache.ts
@@ -0,0 +1,18 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .map(trait => ({
+ [`${trait}`]: () => {
+ return FP[trait].generateScalarToIntervalCases(
+ FP[trait].scalarRange(),
+ trait !== 'abstract' ? 'unfiltered' : 'finite',
+ // radians has an inherited accuracy, so abstract is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].radiansInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('radians', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/radians.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/radians.spec.ts
index 63ae45b656..a405807ec0 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/radians.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/radians.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'radians' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn radians(e1: T ) -> T
Converts degrees to radians, approximating e1 * π / 180.
@@ -10,40 +10,14 @@ Component-wise when T is a vector
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeAbstractFloat, TypeF16, TypeF32 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF16Range, fullF32Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
+import { Type } from '../../../../../util/conversion.js';
import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { abstractBuiltin, builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './radians.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('radians', {
- f32: () => {
- return FP.f32.generateScalarToIntervalCases(
- fullF32Range(),
- 'unfiltered',
- FP.f32.radiansInterval
- );
- },
- f16: () => {
- return FP.f16.generateScalarToIntervalCases(
- fullF16Range(),
- 'unfiltered',
- FP.f16.radiansInterval
- );
- },
- abstract: () => {
- return FP.abstract.generateScalarToIntervalCases(
- fullF16Range(),
- 'unfiltered',
- FP.abstract.radiansInterval
- );
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
@@ -56,9 +30,9 @@ g.test('abstract_float')
const cases = await d.get('abstract');
await run(
t,
- abstractBuiltin('radians'),
- [TypeAbstractFloat],
- TypeAbstractFloat,
+ abstractFloatBuiltin('radians'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
t.params,
cases
);
@@ -72,7 +46,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('radians'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('radians'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -86,5 +60,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('radians'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('radians'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reflect.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reflect.cache.ts
new file mode 100644
index 0000000000..ca57226f3a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reflect.cache.ts
@@ -0,0 +1,26 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]_vecN_[non_]const
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([2, 3, 4] as const).flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateVectorPairToVectorCases(
+ FP[trait].sparseVectorRange(dim),
+ FP[trait].sparseVectorRange(dim),
+ nonConst ? 'unfiltered' : 'finite',
+ // reflect has an inherited accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].reflectInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('reflect', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reflect.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reflect.spec.ts
index 2614c4e686..e36558d30c 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reflect.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reflect.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'reflect' builtin function
-T is vecN<AbstractFloat>, vecN<f32>, or vecN<f16>
+T is vecN<Type.abstractFloat>, vecN<f32>, or vecN<f16>
@const fn reflect(e1: T, e2: T ) -> T
For the incident vector e1 and surface orientation e2, returns the reflection
direction e1-2*dot(e2,e1)*e2.
@@ -9,58 +9,61 @@ direction e1-2*dot(e2,e1)*e2.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16, TypeVec } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { sparseVectorF32Range, sparseVectorF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './reflect.cache.js';
export const g = makeTestGroup(GPUTest);
-// Cases: f32_vecN_[non_]const
-const f32_vec_cases = ([2, 3, 4] as const)
- .flatMap(n =>
- ([true, false] as const).map(nonConst => ({
- [`f32_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateVectorPairToVectorCases(
- sparseVectorF32Range(n),
- sparseVectorF32Range(n),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.reflectInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-// Cases: f16_vecN_[non_]const
-const f16_vec_cases = ([2, 3, 4] as const)
- .flatMap(n =>
- ([true, false] as const).map(nonConst => ({
- [`f16_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateVectorPairToVectorCases(
- sparseVectorF16Range(n),
- sparseVectorF16Range(n),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.reflectInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
+g.test('abstract_float_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract float tests using vec2s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec2_const');
+ await run(
+ t,
+ abstractFloatBuiltin('reflect'),
+ [Type.vec2af, Type.vec2af],
+ Type.vec2af,
+ t.params,
+ cases
+ );
+ });
-export const d = makeCaseCache('reflect', {
- ...f32_vec_cases,
- ...f16_vec_cases,
-});
+g.test('abstract_float_vec3')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract float tests using vec3s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec3_const');
+ await run(
+ t,
+ abstractFloatBuiltin('reflect'),
+ [Type.vec3af, Type.vec3af],
+ Type.vec3af,
+ t.params,
+ cases
+ );
+ });
-g.test('abstract_float')
- .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
- .desc(`abstract float tests`)
- .params(u => u.combine('inputSource', allInputSources).combine('vectorize', [2, 3, 4] as const))
- .unimplemented();
+g.test('abstract_float_vec4')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract float tests using vec4s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec4_const');
+ await run(
+ t,
+ abstractFloatBuiltin('reflect'),
+ [Type.vec4af, Type.vec4af],
+ Type.vec4af,
+ t.params,
+ cases
+ );
+ });
g.test('f32_vec2')
.specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
@@ -70,14 +73,7 @@ g.test('f32_vec2')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f32_vec2_const' : 'f32_vec2_non_const'
);
- await run(
- t,
- builtin('reflect'),
- [TypeVec(2, TypeF32), TypeVec(2, TypeF32)],
- TypeVec(2, TypeF32),
- t.params,
- cases
- );
+ await run(t, builtin('reflect'), [Type.vec2f, Type.vec2f], Type.vec2f, t.params, cases);
});
g.test('f32_vec3')
@@ -88,14 +84,7 @@ g.test('f32_vec3')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f32_vec3_const' : 'f32_vec3_non_const'
);
- await run(
- t,
- builtin('reflect'),
- [TypeVec(3, TypeF32), TypeVec(3, TypeF32)],
- TypeVec(3, TypeF32),
- t.params,
- cases
- );
+ await run(t, builtin('reflect'), [Type.vec3f, Type.vec3f], Type.vec3f, t.params, cases);
});
g.test('f32_vec4')
@@ -106,14 +95,7 @@ g.test('f32_vec4')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f32_vec4_const' : 'f32_vec4_non_const'
);
- await run(
- t,
- builtin('reflect'),
- [TypeVec(4, TypeF32), TypeVec(4, TypeF32)],
- TypeVec(4, TypeF32),
- t.params,
- cases
- );
+ await run(t, builtin('reflect'), [Type.vec4f, Type.vec4f], Type.vec4f, t.params, cases);
});
g.test('f16_vec2')
@@ -127,14 +109,7 @@ g.test('f16_vec2')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f16_vec2_const' : 'f16_vec2_non_const'
);
- await run(
- t,
- builtin('reflect'),
- [TypeVec(2, TypeF16), TypeVec(2, TypeF16)],
- TypeVec(2, TypeF16),
- t.params,
- cases
- );
+ await run(t, builtin('reflect'), [Type.vec2h, Type.vec2h], Type.vec2h, t.params, cases);
});
g.test('f16_vec3')
@@ -148,14 +123,7 @@ g.test('f16_vec3')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f16_vec3_const' : 'f16_vec3_non_const'
);
- await run(
- t,
- builtin('reflect'),
- [TypeVec(3, TypeF16), TypeVec(3, TypeF16)],
- TypeVec(3, TypeF16),
- t.params,
- cases
- );
+ await run(t, builtin('reflect'), [Type.vec3h, Type.vec3h], Type.vec3h, t.params, cases);
});
g.test('f16_vec4')
@@ -169,12 +137,5 @@ g.test('f16_vec4')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f16_vec4_const' : 'f16_vec4_non_const'
);
- await run(
- t,
- builtin('reflect'),
- [TypeVec(4, TypeF16), TypeVec(4, TypeF16)],
- TypeVec(4, TypeF16),
- t.params,
- cases
- );
+ await run(t, builtin('reflect'), [Type.vec4h, Type.vec4h], Type.vec4h, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/refract.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/refract.cache.ts
new file mode 100644
index 0000000000..a759f5b669
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/refract.cache.ts
@@ -0,0 +1,116 @@
+import { ROArrayArray } from '../../../../../../common/util/types.js';
+import { toVector } from '../../../../../util/conversion.js';
+import { FP, FPKind } from '../../../../../util/floating_point.js';
+import { Case, selectNCases } from '../../case.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { IntervalFilter } from '../../interval_filter.js';
+
+// Using a bespoke implementation of make*Case and generate*Cases here
+// since refract is the only builtin with the API signature
+// (vec, vec, scalar) -> vec
+
+/**
+ * @returns a Case for `refract`
+ * @param argumentKind what kind of floating point numbers being operated on
+ * @param parameterKind what kind of floating point operation should be performed,
+ * should be the same as argumentKind, except for abstract
+ * @param i the `i` param for the case
+ * @param s the `s` param for the case
+ * @param r the `r` param for the case
+ * @param check what interval checking to apply
+ * */
+function makeCase(
+ argumentKind: FPKind,
+ parameterKind: FPKind,
+ i: readonly number[],
+ s: readonly number[],
+ r: number,
+ check: IntervalFilter
+): Case | undefined {
+ const fp = FP[argumentKind];
+ i = i.map(fp.quantize);
+ s = s.map(fp.quantize);
+ r = fp.quantize(r);
+
+ const vectors = FP[parameterKind].refractInterval(i, s, r);
+ if (check === 'finite' && vectors.some(e => !e.isFinite())) {
+ return undefined;
+ }
+
+ return {
+ input: [toVector(i, fp.scalarBuilder), toVector(s, fp.scalarBuilder), fp.scalarBuilder(r)],
+ expected: vectors,
+ };
+}
+
+/**
+ * @returns an array of Cases for `refract`
+ * @param argumentKind what kind of floating point numbers being operated on
+ * @param parameterKind what kind of floating point operation should be performed,
+ * should be the same as argumentKind, except for abstract
+ * @param param_is array of inputs to try for the `i` param
+ * @param param_ss array of inputs to try for the `s` param
+ * @param param_rs array of inputs to try for the `r` param
+ * @param check what interval checking to apply
+ */
+function generateCases(
+ argumentKind: FPKind,
+ parameterKind: FPKind,
+ param_is: ROArrayArray<number>,
+ param_ss: ROArrayArray<number>,
+ param_rs: readonly number[],
+ check: IntervalFilter
+): Case[] {
+ // Cannot use `cartesianProduct` here due to heterogeneous param types
+ return param_is
+ .flatMap(i => {
+ return param_ss.flatMap(s => {
+ return param_rs.map(r => {
+ return makeCase(argumentKind, parameterKind, i, s, r, check);
+ });
+ });
+ })
+ .filter((c): c is Case => c !== undefined);
+}
+
+// Cases: [f32|f16|abstract]_vecN_[non_]const
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([2, 3, 4] as const).flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ if (trait !== 'abstract') {
+ return generateCases(
+ trait,
+ trait,
+ FP[trait].sparseVectorRange(dim),
+ FP[trait].sparseVectorRange(dim),
+ FP[trait].sparseScalarRange(),
+ nonConst ? 'unfiltered' : 'finite'
+ );
+ } else {
+ // Restricting the number of cases, because a vector of abstract floats needs to be returned, which is costly.
+ return selectNCases(
+ 'faceForward',
+ 20,
+ generateCases(
+ trait,
+ // refract has an inherited accuracy, so is only expected to be as accurate as f32
+ 'f32',
+ FP[trait].sparseVectorRange(dim),
+ FP[trait].sparseVectorRange(dim),
+ FP[trait].sparseScalarRange(),
+ nonConst ? 'unfiltered' : 'finite'
+ )
+ );
+ }
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('refract', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/refract.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/refract.spec.ts
index be1a76b437..5b51f30eee 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/refract.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/refract.spec.ts
@@ -2,7 +2,7 @@ export const description = `
Execution tests for the 'refract' builtin function
T is vecN<I>
-I is AbstractFloat, f32, or f16
+I is abstract-float, f32, or f16
@const fn refract(e1: T ,e2: T ,e3: I ) -> T
For the incident vector e1 and surface normal e2, and the ratio of indices of
refraction e3, let k = 1.0 -e3*e3* (1.0 - dot(e2,e1) * dot(e2,e1)).
@@ -11,129 +11,62 @@ vector e3*e1- (e3* dot(e2,e1) + sqrt(k)) *e2.
`;
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
-import { ROArrayArray } from '../../../../../../common/util/types.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { toVector, TypeF32, TypeF16, TypeVec } from '../../../../../util/conversion.js';
-import { FP, FPKind } from '../../../../../util/floating_point.js';
-import {
- sparseVectorF32Range,
- sparseVectorF16Range,
- sparseF32Range,
- sparseF16Range,
-} from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, Case, IntervalFilter, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './refract.cache.js';
export const g = makeTestGroup(GPUTest);
-// Using a bespoke implementation of make*Case and generate*Cases here
-// since refract is the only builtin with the API signature
-// (vec, vec, scalar) -> vec
-
-/**
- * @returns a Case for `refract`
- * @param kind what type of floating point numbers to operate on
- * @param i the `i` param for the case
- * @param s the `s` param for the case
- * @param r the `r` param for the case
- * @param check what interval checking to apply
- * */
-function makeCase(
- kind: FPKind,
- i: readonly number[],
- s: readonly number[],
- r: number,
- check: IntervalFilter
-): Case | undefined {
- const fp = FP[kind];
- i = i.map(fp.quantize);
- s = s.map(fp.quantize);
- r = fp.quantize(r);
-
- const vectors = fp.refractInterval(i, s, r);
- if (check === 'finite' && vectors.some(e => !e.isFinite())) {
- return undefined;
- }
-
- return {
- input: [toVector(i, fp.scalarBuilder), toVector(s, fp.scalarBuilder), fp.scalarBuilder(r)],
- expected: fp.refractInterval(i, s, r),
- };
-}
-
-/**
- * @returns an array of Cases for `refract`
- * @param kind what type of floating point numbers to operate on
- * @param param_is array of inputs to try for the `i` param
- * @param param_ss array of inputs to try for the `s` param
- * @param param_rs array of inputs to try for the `r` param
- * @param check what interval checking to apply
- */
-function generateCases(
- kind: FPKind,
- param_is: ROArrayArray<number>,
- param_ss: ROArrayArray<number>,
- param_rs: readonly number[],
- check: IntervalFilter
-): Case[] {
- // Cannot use `cartesianProduct` here due to heterogeneous param types
- return param_is
- .flatMap(i => {
- return param_ss.flatMap(s => {
- return param_rs.map(r => {
- return makeCase(kind, i, s, r, check);
- });
- });
- })
- .filter((c): c is Case => c !== undefined);
-}
-
-// Cases: f32_vecN_[non_]const
-const f32_vec_cases = ([2, 3, 4] as const)
- .flatMap(n =>
- ([true, false] as const).map(nonConst => ({
- [`f32_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return generateCases(
- 'f32',
- sparseVectorF32Range(n),
- sparseVectorF32Range(n),
- sparseF32Range(),
- nonConst ? 'unfiltered' : 'finite'
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-// Cases: f16_vecN_[non_]const
-const f16_vec_cases = ([2, 3, 4] as const)
- .flatMap(n =>
- ([true, false] as const).map(nonConst => ({
- [`f16_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return generateCases(
- 'f16',
- sparseVectorF16Range(n),
- sparseVectorF16Range(n),
- sparseF16Range(),
- nonConst ? 'unfiltered' : 'finite'
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
+g.test('abstract_float_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract float tests using vec2s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec2_const');
+ await run(
+ t,
+ abstractFloatBuiltin('refract'),
+ [Type.vec2af, Type.vec2af, Type.abstractFloat],
+ Type.vec2af,
+ t.params,
+ cases
+ );
+ });
-export const d = makeCaseCache('refract', {
- ...f32_vec_cases,
- ...f16_vec_cases,
-});
+g.test('abstract_float_vec3')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract float tests using vec3s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec3_const');
+ await run(
+ t,
+ abstractFloatBuiltin('refract'),
+ [Type.vec3af, Type.vec3af, Type.abstractFloat],
+ Type.vec3af,
+ t.params,
+ cases
+ );
+ });
-g.test('abstract_float')
- .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
- .desc(`abstract float tests`)
- .params(u => u.combine('inputSource', allInputSources).combine('vectorize', [2, 3, 4] as const))
- .unimplemented();
+g.test('abstract_float_vec4')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract float tests using vec4s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec4_const');
+ await run(
+ t,
+ abstractFloatBuiltin('refract'),
+ [Type.vec4af, Type.vec4af, Type.abstractFloat],
+ Type.vec4af,
+ t.params,
+ cases
+ );
+ });
g.test('f32_vec2')
.specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
@@ -146,8 +79,8 @@ g.test('f32_vec2')
await run(
t,
builtin('refract'),
- [TypeVec(2, TypeF32), TypeVec(2, TypeF32), TypeF32],
- TypeVec(2, TypeF32),
+ [Type.vec2f, Type.vec2f, Type.f32],
+ Type.vec2f,
t.params,
cases
);
@@ -164,8 +97,8 @@ g.test('f32_vec3')
await run(
t,
builtin('refract'),
- [TypeVec(3, TypeF32), TypeVec(3, TypeF32), TypeF32],
- TypeVec(3, TypeF32),
+ [Type.vec3f, Type.vec3f, Type.f32],
+ Type.vec3f,
t.params,
cases
);
@@ -182,8 +115,8 @@ g.test('f32_vec4')
await run(
t,
builtin('refract'),
- [TypeVec(4, TypeF32), TypeVec(4, TypeF32), TypeF32],
- TypeVec(4, TypeF32),
+ [Type.vec4f, Type.vec4f, Type.f32],
+ Type.vec4f,
t.params,
cases
);
@@ -203,8 +136,8 @@ g.test('f16_vec2')
await run(
t,
builtin('refract'),
- [TypeVec(2, TypeF16), TypeVec(2, TypeF16), TypeF16],
- TypeVec(2, TypeF16),
+ [Type.vec2h, Type.vec2h, Type.f16],
+ Type.vec2h,
t.params,
cases
);
@@ -224,8 +157,8 @@ g.test('f16_vec3')
await run(
t,
builtin('refract'),
- [TypeVec(3, TypeF16), TypeVec(3, TypeF16), TypeF16],
- TypeVec(3, TypeF16),
+ [Type.vec3h, Type.vec3h, Type.f16],
+ Type.vec3h,
t.params,
cases
);
@@ -245,8 +178,8 @@ g.test('f16_vec4')
await run(
t,
builtin('refract'),
- [TypeVec(4, TypeF16), TypeVec(4, TypeF16), TypeF16],
- TypeVec(4, TypeF16),
+ [Type.vec4h, Type.vec4h, Type.f16],
+ Type.vec4h,
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reverseBits.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reverseBits.spec.ts
index 6acb359822..e235e62a52 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reverseBits.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reverseBits.spec.ts
@@ -10,7 +10,7 @@ Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeU32, u32Bits, TypeI32, i32Bits } from '../../../../../util/conversion.js';
+import { u32Bits, i32Bits, Type } from '../../../../../util/conversion.js';
import { allInputSources, Config, run } from '../../expression.js';
import { builtin } from './builtin.js';
@@ -26,7 +26,7 @@ g.test('u32')
.fn(async t => {
const cfg: Config = t.params;
// prettier-ignore
- await run(t, builtin('reverseBits'), [TypeU32], TypeU32, cfg, [
+ await run(t, builtin('reverseBits'), [Type.u32], Type.u32, cfg, [
// Zero
{ input: u32Bits(0b00000000000000000000000000000000), expected: u32Bits(0b00000000000000000000000000000000) },
@@ -142,7 +142,7 @@ g.test('i32')
.fn(async t => {
const cfg: Config = t.params;
// prettier-ignore
- await run(t, builtin('reverseBits'), [TypeI32], TypeI32, cfg, [
+ await run(t, builtin('reverseBits'), [Type.i32], Type.i32, cfg, [
// Zero
{ input: i32Bits(0b00000000000000000000000000000000), expected: i32Bits(0b00000000000000000000000000000000) },
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/round.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/round.cache.ts
new file mode 100644
index 0000000000..e5383b2075
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/round.cache.ts
@@ -0,0 +1,24 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// See https://github.com/gpuweb/cts/issues/2766 for details
+const kIssue2766Value = {
+ abstract: 0x8000_0000_0000_0000,
+ f32: 0x8000_0000,
+ f16: 0x8000,
+};
+
+// Cases: [f32|f16|abstract]
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .map(trait => ({
+ [`${trait}`]: () => {
+ return FP[trait].generateScalarToIntervalCases(
+ [kIssue2766Value[trait], ...FP[trait].scalarRange()],
+ 'unfiltered',
+ FP[trait].roundInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('round', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/round.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/round.spec.ts
index bd40ed4b2a..eeaf41b381 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/round.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/round.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'round' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn round(e: T) -> T
Result is the integer k nearest to e, as a floating point value.
@@ -12,46 +12,33 @@ Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './round.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('round', {
- f32: () => {
- return FP.f32.generateScalarToIntervalCases(
- [
- 0x80000000, // https://github.com/gpuweb/cts/issues/2766,
- ...fullF32Range(),
- ],
- 'unfiltered',
- FP.f32.roundInterval
- );
- },
- f16: () => {
- return FP.f16.generateScalarToIntervalCases(
- [
- 0x8000, // https://github.com/gpuweb/cts/issues/2766
- ...fullF16Range(),
- ],
- 'unfiltered',
- FP.f16.roundInterval
- );
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractFloatBuiltin('round'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -61,7 +48,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('round'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('round'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -75,5 +62,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('round'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('round'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/saturate.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/saturate.cache.ts
new file mode 100644
index 0000000000..4a4ffeee30
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/saturate.cache.ts
@@ -0,0 +1,18 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .map(trait => ({
+ [`${trait}`]: () => {
+ return FP[trait].generateScalarToIntervalCases(
+ [...linearRange(0.0, 1.0, 20), ...FP[trait].scalarRange()],
+ 'unfiltered',
+ FP[trait].saturateInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('saturate', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/saturate.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/saturate.spec.ts
index 2f16502921..79c61e4eec 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/saturate.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/saturate.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'saturate' builtin function
-S is AbstractFloat, f32, or f16
+S is abstract-float, f32, or f16
T is S or vecN<S>
@const fn saturate(e: T) -> T
Returns clamp(e, 0.0, 1.0). Component-wise when T is a vector.
@@ -9,52 +9,14 @@ Returns clamp(e, 0.0, 1.0). Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeAbstractFloat, TypeF16, TypeF32 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF16Range, fullF32Range, fullF64Range, linearRange } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
+import { Type } from '../../../../../util/conversion.js';
import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { abstractBuiltin, builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './saturate.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('saturate', {
- f32: () => {
- return FP.f32.generateScalarToIntervalCases(
- [
- // Non-clamped values
- ...linearRange(0.0, 1.0, 20),
- ...fullF32Range(),
- ],
- 'unfiltered',
- FP.f32.saturateInterval
- );
- },
- f16: () => {
- return FP.f16.generateScalarToIntervalCases(
- [
- // Non-clamped values
- ...linearRange(0.0, 1.0, 20),
- ...fullF16Range(),
- ],
- 'unfiltered',
- FP.f16.saturateInterval
- );
- },
- abstract: () => {
- return FP.abstract.generateScalarToIntervalCases(
- [
- // Non-clamped values
- ...linearRange(0.0, 1.0, 20),
- ...fullF64Range(),
- ],
- 'unfiltered',
- FP.abstract.saturateInterval
- );
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
@@ -67,9 +29,9 @@ g.test('abstract_float')
const cases = await d.get('abstract');
await run(
t,
- abstractBuiltin('saturate'),
- [TypeAbstractFloat],
- TypeAbstractFloat,
+ abstractFloatBuiltin('saturate'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
t.params,
cases
);
@@ -82,7 +44,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('saturate'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('saturate'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -96,5 +58,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('saturate'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('saturate'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/select.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/select.spec.ts
index c64f989f42..63accbc2d4 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/select.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/select.spec.ts
@@ -14,12 +14,6 @@ import { makeTestGroup } from '../../../../../../common/framework/test_group.js'
import { GPUTest } from '../../../../../gpu_test.js';
import {
VectorType,
- TypeVec,
- TypeBool,
- TypeF32,
- TypeF16,
- TypeI32,
- TypeU32,
f32,
f16,
i32,
@@ -31,11 +25,14 @@ import {
vec3,
vec4,
abstractFloat,
- TypeAbstractFloat,
+ abstractInt,
+ ScalarValue,
+ Type,
} from '../../../../../util/conversion.js';
-import { run, CaseList, allInputSources } from '../../expression.js';
+import { Case } from '../../case.js';
+import { run, allInputSources } from '../../expression.js';
-import { abstractBuiltin, builtin } from './builtin.js';
+import { abstractFloatBuiltin, abstractIntBuiltin, builtin } from './builtin.js';
export const g = makeTestGroup(GPUTest);
@@ -43,32 +40,47 @@ function makeBool(n: number) {
return bool((n & 1) === 1);
}
-type scalarKind = 'b' | 'af' | 'f' | 'h' | 'i' | 'u';
+type scalarKind = 'b' | 'af' | 'f' | 'h' | 'ai' | 'i' | 'u';
const dataType = {
b: {
- type: TypeBool,
- constructor: makeBool,
+ type: Type.bool,
+ scalar_builder: makeBool,
+ shader_builder: builtin('select'),
},
af: {
- type: TypeAbstractFloat,
- constructor: abstractFloat,
+ type: Type.abstractFloat,
+ scalar_builder: abstractFloat,
+ shader_builder: abstractFloatBuiltin('select'),
},
f: {
- type: TypeF32,
- constructor: f32,
+ type: Type.f32,
+ scalar_builder: f32,
+ shader_builder: builtin('select'),
},
h: {
- type: TypeF16,
- constructor: f16,
+ type: Type.f16,
+ scalar_builder: f16,
+ shader_builder: builtin('select'),
+ },
+ ai: {
+ type: Type.abstractInt,
+ // Only ints are used in the tests below, so the conversion to bigint will
+ // be safe. If a non-int is passed in this will Error.
+ scalar_builder: (v: number): ScalarValue => {
+ return abstractInt(BigInt(v));
+ },
+ shader_builder: abstractIntBuiltin('select'),
},
i: {
- type: TypeI32,
- constructor: i32,
+ type: Type.i32,
+ scalar_builder: i32,
+ shader_builder: builtin('select'),
},
u: {
- type: TypeU32,
- constructor: u32,
+ type: Type.u32,
+ scalar_builder: u32,
+ shader_builder: builtin('select'),
},
};
@@ -78,7 +90,7 @@ g.test('scalar')
.params(u =>
u
.combine('inputSource', allInputSources)
- .combine('component', ['b', 'af', 'f', 'h', 'i', 'u'] as const)
+ .combine('component', ['b', 'af', 'f', 'h', 'ai', 'i', 'u'] as const)
.combine('overload', ['scalar', 'vec2', 'vec3', 'vec4'] as const)
)
.beforeAllSubcases(t => {
@@ -86,10 +98,11 @@ g.test('scalar')
t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
}
t.skipIf(t.params.component === 'af' && t.params.inputSource !== 'const');
+ t.skipIf(t.params.component === 'ai' && t.params.inputSource !== 'const');
})
.fn(async t => {
- const componentType = dataType[t.params.component as scalarKind].type;
- const cons = dataType[t.params.component as scalarKind].constructor;
+ const componentType = dataType[t.params.component].type;
+ const scalar_builder = dataType[t.params.component].scalar_builder;
// Create the scalar values that will be selected from, either as scalars
// or vectors.
@@ -97,39 +110,40 @@ g.test('scalar')
// Each boolean will select between c[k] and c[k+4]. Those values must
// always compare as different. The tricky case is boolean, where the parity
// has to be different, i.e. c[k]-c[k+4] must be odd.
- const c = [0, 1, 2, 3, 5, 6, 7, 8].map(i => cons(i));
+ const scalars = [0, 1, 2, 3, 5, 6, 7, 8].map(i => scalar_builder(i));
+
// Now form vectors that will have different components from each other.
- const v2a = vec2(c[0], c[1]);
- const v2b = vec2(c[4], c[5]);
- const v3a = vec3(c[0], c[1], c[2]);
- const v3b = vec3(c[4], c[5], c[6]);
- const v4a = vec4(c[0], c[1], c[2], c[3]);
- const v4b = vec4(c[4], c[5], c[6], c[7]);
+ const v2a = vec2(scalars[0], scalars[1]);
+ const v2b = vec2(scalars[4], scalars[5]);
+ const v3a = vec3(scalars[0], scalars[1], scalars[2]);
+ const v3b = vec3(scalars[4], scalars[5], scalars[6]);
+ const v4a = vec4(scalars[0], scalars[1], scalars[2], scalars[3]);
+ const v4b = vec4(scalars[4], scalars[5], scalars[6], scalars[7]);
const overloads = {
scalar: {
type: componentType,
cases: [
- { input: [c[0], c[1], False], expected: c[0] },
- { input: [c[0], c[1], True], expected: c[1] },
+ { input: [scalars[0], scalars[1], False], expected: scalars[0] },
+ { input: [scalars[0], scalars[1], True], expected: scalars[1] },
],
},
vec2: {
- type: TypeVec(2, componentType),
+ type: Type.vec(2, componentType),
cases: [
{ input: [v2a, v2b, False], expected: v2a },
{ input: [v2a, v2b, True], expected: v2b },
],
},
vec3: {
- type: TypeVec(3, componentType),
+ type: Type.vec(3, componentType),
cases: [
{ input: [v3a, v3b, False], expected: v3a },
{ input: [v3a, v3b, True], expected: v3b },
],
},
vec4: {
- type: TypeVec(4, componentType),
+ type: Type.vec(4, componentType),
cases: [
{ input: [v4a, v4b, False], expected: v4a },
{ input: [v4a, v4b, True], expected: v4b },
@@ -140,8 +154,8 @@ g.test('scalar')
await run(
t,
- t.params.component === 'af' ? abstractBuiltin('select') : builtin('select'),
- [overload.type, overload.type, TypeBool],
+ dataType[t.params.component as scalarKind].shader_builder,
+ [overload.type, overload.type, Type.bool],
overload.type,
t.params,
overload.cases
@@ -154,7 +168,7 @@ g.test('vector')
.params(u =>
u
.combine('inputSource', allInputSources)
- .combine('component', ['b', 'af', 'f', 'h', 'i', 'u'] as const)
+ .combine('component', ['b', 'af', 'f', 'h', 'ai', 'i', 'u'] as const)
.combine('overload', ['vec2', 'vec3', 'vec4'] as const)
)
.beforeAllSubcases(t => {
@@ -162,29 +176,30 @@ g.test('vector')
t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
}
t.skipIf(t.params.component === 'af' && t.params.inputSource !== 'const');
+ t.skipIf(t.params.component === 'ai' && t.params.inputSource !== 'const');
})
.fn(async t => {
- const componentType = dataType[t.params.component as scalarKind].type;
- const cons = dataType[t.params.component as scalarKind].constructor;
+ const componentType = dataType[t.params.component].type;
+ const scalar_builder = dataType[t.params.component].scalar_builder;
// Create the scalar values that will be selected from.
//
// Each boolean will select between c[k] and c[k+4]. Those values must
// always compare as different. The tricky case is boolean, where the parity
// has to be different, i.e. c[k]-c[k+4] must be odd.
- const c = [0, 1, 2, 3, 5, 6, 7, 8].map(i => cons(i));
+ const scalars = [0, 1, 2, 3, 5, 6, 7, 8].map(i => scalar_builder(i));
const T = True;
const F = False;
- let tests: { dataType: VectorType; boolType: VectorType; cases: CaseList };
+ let tests: { dataType: VectorType; boolType: VectorType; cases: Case[] };
switch (t.params.overload) {
case 'vec2': {
- const a = vec2(c[0], c[1]);
- const b = vec2(c[4], c[5]);
+ const a = vec2(scalars[0], scalars[1]);
+ const b = vec2(scalars[4], scalars[5]);
tests = {
- dataType: TypeVec(2, componentType),
- boolType: TypeVec(2, TypeBool),
+ dataType: Type.vec(2, componentType),
+ boolType: Type.vec(2, Type.bool),
cases: [
{ input: [a, b, vec2(F, F)], expected: vec2(a.x, a.y) },
{ input: [a, b, vec2(F, T)], expected: vec2(a.x, b.y) },
@@ -195,11 +210,11 @@ g.test('vector')
break;
}
case 'vec3': {
- const a = vec3(c[0], c[1], c[2]);
- const b = vec3(c[4], c[5], c[6]);
+ const a = vec3(scalars[0], scalars[1], scalars[2]);
+ const b = vec3(scalars[4], scalars[5], scalars[6]);
tests = {
- dataType: TypeVec(3, componentType),
- boolType: TypeVec(3, TypeBool),
+ dataType: Type.vec(3, componentType),
+ boolType: Type.vec(3, Type.bool),
cases: [
{ input: [a, b, vec3(F, F, F)], expected: vec3(a.x, a.y, a.z) },
{ input: [a, b, vec3(F, F, T)], expected: vec3(a.x, a.y, b.z) },
@@ -214,11 +229,11 @@ g.test('vector')
break;
}
case 'vec4': {
- const a = vec4(c[0], c[1], c[2], c[3]);
- const b = vec4(c[4], c[5], c[6], c[7]);
+ const a = vec4(scalars[0], scalars[1], scalars[2], scalars[3]);
+ const b = vec4(scalars[4], scalars[5], scalars[6], scalars[7]);
tests = {
- dataType: TypeVec(4, componentType),
- boolType: TypeVec(4, TypeBool),
+ dataType: Type.vec(4, componentType),
+ boolType: Type.vec(4, Type.bool),
cases: [
{ input: [a, b, vec4(F, F, F, F)], expected: vec4(a.x, a.y, a.z, a.w) },
{ input: [a, b, vec4(F, F, F, T)], expected: vec4(a.x, a.y, a.z, b.w) },
@@ -244,7 +259,7 @@ g.test('vector')
await run(
t,
- t.params.component === 'af' ? abstractBuiltin('select') : builtin('select'),
+ dataType[t.params.component].shader_builder,
[tests.dataType, tests.dataType, tests.boolType],
tests.dataType,
t.params,
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sign.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sign.cache.ts
new file mode 100644
index 0000000000..09f4de19ac
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sign.cache.ts
@@ -0,0 +1,31 @@
+import { abstractInt, i32 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullI32Range, fullI64Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]
+const fp_cases = (['f32', 'f16', 'abstract'] as const)
+ .map(trait => ({
+ [`${trait === 'abstract' ? 'abstract_float' : trait}`]: () => {
+ return FP[trait].generateScalarToIntervalCases(
+ FP[trait].scalarRange(),
+ 'unfiltered',
+ FP[trait].signInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('sign', {
+ ...fp_cases,
+ i32: () =>
+ fullI32Range().map(i => {
+ const signFunc = (i: number): number => (i < 0 ? -1 : i > 0 ? 1 : 0);
+ return { input: [i32(i)], expected: i32(signFunc(i)) };
+ }),
+ abstract_int: () =>
+ fullI64Range().map(i => {
+ const signFunc = (i: bigint): bigint => (i < 0n ? -1n : i > 0n ? 1n : 0n);
+ return { input: [abstractInt(i)], expected: abstractInt(signFunc(i)) };
+ }),
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sign.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sign.spec.ts
index a147acf6fb..f7d2e3ccfd 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sign.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sign.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'sign' builtin function
-S is AbstractFloat, AbstractInt, i32, f32, f16
+S is abstract-float, Type.abstractInt, i32, f32, f16
T is S or vecN<S>
@const fn sign(e: T ) -> T
Returns the sign of e. Component-wise when T is a vector.
@@ -9,48 +9,14 @@ Returns the sign of e. Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import {
- i32,
- TypeF32,
- TypeF16,
- TypeI32,
- TypeAbstractFloat,
-} from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import {
- fullF32Range,
- fullF16Range,
- fullI32Range,
- fullF64Range,
-} from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
+import { Type } from '../../../../../util/conversion.js';
import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { abstractBuiltin, builtin } from './builtin.js';
+import { abstractFloatBuiltin, abstractIntBuiltin, builtin } from './builtin.js';
+import { d } from './sign.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('sign', {
- f32: () => {
- return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.signInterval);
- },
- f16: () => {
- return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'unfiltered', FP.f16.signInterval);
- },
- abstract_float: () => {
- return FP.abstract.generateScalarToIntervalCases(
- fullF64Range(),
- 'unfiltered',
- FP.abstract.signInterval
- );
- },
- i32: () =>
- fullI32Range().map(i => {
- const signFunc = (i: number): number => (i < 0 ? -1 : i > 0 ? 1 : 0);
- return { input: [i32(i)], expected: i32(signFunc(i)) };
- }),
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#sign-builtin')
.desc(`abstract float tests`)
@@ -61,16 +27,28 @@ g.test('abstract_float')
)
.fn(async t => {
const cases = await d.get('abstract_float');
- await run(t, abstractBuiltin('sign'), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases);
+ await run(
+ t,
+ abstractFloatBuiltin('sign'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
});
g.test('abstract_int')
.specURL('https://www.w3.org/TR/WGSL/#sign-builtin')
.desc(`abstract int tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract_int');
+ await run(t, abstractIntBuiltin('sign'), [Type.abstractInt], Type.abstractInt, t.params, cases);
+ });
g.test('i32')
.specURL('https://www.w3.org/TR/WGSL/#sign-builtin')
@@ -80,7 +58,7 @@ g.test('i32')
)
.fn(async t => {
const cases = await d.get('i32');
- await run(t, builtin('sign'), [TypeI32], TypeI32, t.params, cases);
+ await run(t, builtin('sign'), [Type.i32], Type.i32, t.params, cases);
});
g.test('f32')
@@ -91,7 +69,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('sign'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('sign'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -105,5 +83,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('sign'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('sign'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sin.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sin.cache.ts
new file mode 100644
index 0000000000..74acf6a62c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sin.cache.ts
@@ -0,0 +1,23 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .map(trait => ({
+ [`${trait}`]: () => {
+ return FP[trait].generateScalarToIntervalCases(
+ [
+ // Well-defined accuracy range
+ ...linearRange(-Math.PI, Math.PI, 100),
+ ...FP[trait].scalarRange(),
+ ],
+ trait === 'abstract' ? 'finite' : 'unfiltered',
+ // sin has an inherited accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].sinInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('sin', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sin.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sin.spec.ts
index 4ab3ae7a3d..fc706c5d63 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sin.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sin.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'sin' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn sin(e: T ) -> T
Returns the sine of e. Component-wise when T is a vector.
@@ -9,48 +9,33 @@ Returns the sine of e. Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF16Range, linearRange } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './sin.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('sin', {
- f32: () => {
- return FP.f32.generateScalarToIntervalCases(
- [
- // Well-defined accuracy range
- ...linearRange(-Math.PI, Math.PI, 1000),
- ...fullF32Range(),
- ],
- 'unfiltered',
- FP.f32.sinInterval
- );
- },
- f16: () => {
- return FP.f16.generateScalarToIntervalCases(
- [
- // Well-defined accuracy range
- ...linearRange(-Math.PI, Math.PI, 1000),
- ...fullF16Range(),
- ],
- 'unfiltered',
- FP.f16.sinInterval
- );
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractFloatBuiltin('sin'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -66,7 +51,7 @@ TODO(#792): Decide what the ground-truth is for these tests. [1]
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('sin'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('sin'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -80,5 +65,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('sin'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('sin'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sinh.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sinh.cache.ts
new file mode 100644
index 0000000000..aba275581d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sinh.cache.ts
@@ -0,0 +1,23 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]_[non_]const
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateScalarToIntervalCases(
+ FP[trait].scalarRange(),
+ nonConst ? 'unfiltered' : 'finite',
+ // sinh has an inherited accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].sinhInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('sinh', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sinh.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sinh.spec.ts
index d9b93a3dc8..5ffe628de3 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sinh.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sinh.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'sinh' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn sinh(e: T ) -> T
Returns the hyperbolic sine of e. Component-wise when T is a vector.
@@ -9,38 +9,33 @@ Returns the hyperbolic sine of e. Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './sinh.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('sinh', {
- f32_const: () => {
- return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'finite', FP.f32.sinhInterval);
- },
- f32_non_const: () => {
- return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.sinhInterval);
- },
- f16_const: () => {
- return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'finite', FP.f16.sinhInterval);
- },
- f16_non_const: () => {
- return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'unfiltered', FP.f16.sinhInterval);
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract_const');
+ await run(
+ t,
+ abstractFloatBuiltin('sinh'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -50,7 +45,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('sinh'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('sinh'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -64,5 +59,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('sinh'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('sinh'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/smoothstep.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/smoothstep.cache.ts
new file mode 100644
index 0000000000..4ba7615b43
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/smoothstep.cache.ts
@@ -0,0 +1,25 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16]_[non_]const
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateScalarTripleToIntervalCases(
+ FP[trait].sparseScalarRange(),
+ FP[trait].sparseScalarRange(),
+ FP[trait].sparseScalarRange(),
+ nonConst ? 'unfiltered' : 'finite',
+ // smoothstep has an inherited accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].smoothStepInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('smoothstep', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/smoothstep.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/smoothstep.spec.ts
index 20d2a4edbc..42d8d09ff5 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/smoothstep.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/smoothstep.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'smoothstep' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn smoothstep(low: T , high: T , x: T ) -> T
Returns the smooth Hermite interpolation between 0 and 1.
@@ -11,62 +11,33 @@ For scalar T, the result is t * t * (3.0 - 2.0 * t), where t = clamp((x - low) /
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { sparseF32Range, sparseF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './smoothstep.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('smoothstep', {
- f32_const: () => {
- return FP.f32.generateScalarTripleToIntervalCases(
- sparseF32Range(),
- sparseF32Range(),
- sparseF32Range(),
- 'finite',
- FP.f32.smoothStepInterval
- );
- },
- f32_non_const: () => {
- return FP.f32.generateScalarTripleToIntervalCases(
- sparseF32Range(),
- sparseF32Range(),
- sparseF32Range(),
- 'unfiltered',
- FP.f32.smoothStepInterval
- );
- },
- f16_const: () => {
- return FP.f16.generateScalarTripleToIntervalCases(
- sparseF16Range(),
- sparseF16Range(),
- sparseF16Range(),
- 'finite',
- FP.f16.smoothStepInterval
- );
- },
- f16_non_const: () => {
- return FP.f16.generateScalarTripleToIntervalCases(
- sparseF16Range(),
- sparseF16Range(),
- sparseF16Range(),
- 'unfiltered',
- FP.f16.smoothStepInterval
- );
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract_const');
+ await run(
+ t,
+ abstractFloatBuiltin('smoothstep'),
+ [Type.abstractFloat, Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -76,7 +47,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('smoothstep'), [TypeF32, TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('smoothstep'), [Type.f32, Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -90,5 +61,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('smoothstep'), [TypeF16, TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('smoothstep'), [Type.f16, Type.f16, Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sqrt.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sqrt.cache.ts
new file mode 100644
index 0000000000..2151b2730a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sqrt.cache.ts
@@ -0,0 +1,23 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16]_[non_]const
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateScalarToIntervalCases(
+ FP[trait].scalarRange(),
+ nonConst ? 'unfiltered' : 'finite',
+ // sqrt has an inherited accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].sqrtInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('sqrt', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sqrt.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sqrt.spec.ts
index a092438043..3d6c4390e2 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sqrt.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sqrt.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'sqrt' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn sqrt(e: T ) -> T
Returns the square root of e. Component-wise when T is a vector.
@@ -9,38 +9,33 @@ Returns the square root of e. Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './sqrt.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('sqrt', {
- f32_const: () => {
- return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'finite', FP.f32.sqrtInterval);
- },
- f32_non_const: () => {
- return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.sqrtInterval);
- },
- f16_const: () => {
- return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'finite', FP.f16.sqrtInterval);
- },
- f16_non_const: () => {
- return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'unfiltered', FP.f16.sqrtInterval);
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract_const');
+ await run(
+ t,
+ abstractFloatBuiltin('sqrt'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -50,7 +45,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('sqrt'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('sqrt'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -64,5 +59,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('sqrt'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('sqrt'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/step.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/step.cache.ts
new file mode 100644
index 0000000000..28d1e1952b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/step.cache.ts
@@ -0,0 +1,41 @@
+import { anyOf } from '../../../../../util/compare.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { Case } from '../../case.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// stepInterval's return value can't always be interpreted as a single acceptance
+// interval, valid result may be 0.0 or 1.0 or both of them, but will never be a
+// value in interval (0.0, 1.0).
+// See the comment block on stepInterval for more details
+const makeCase = (trait: 'f32' | 'f16' | 'abstract', edge: number, x: number): Case => {
+ const FPTrait = FP[trait];
+ edge = FPTrait.quantize(edge);
+ x = FPTrait.quantize(x);
+ const expected = FPTrait.stepInterval(edge, x);
+
+ // [0, 0], [1, 1], or [-∞, +∞] cases
+ if (expected.isPoint() || !expected.isFinite()) {
+ return { input: [FPTrait.scalarBuilder(edge), FPTrait.scalarBuilder(x)], expected };
+ }
+
+ // [0, 1] case, valid result is either 0.0 or 1.0.
+ const zeroInterval = FPTrait.toInterval(0);
+ const oneInterval = FPTrait.toInterval(1);
+ return {
+ input: [FPTrait.scalarBuilder(edge), FPTrait.scalarBuilder(x)],
+ expected: anyOf(zeroInterval, oneInterval),
+ };
+};
+
+// Cases: [f32|f16|abstract]
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .map(trait => ({
+ [`${trait}`]: () => {
+ return FP[trait]
+ .sparseScalarRange()
+ .flatMap(edge => FP[trait].sparseScalarRange().map(x => makeCase(trait, edge, x)));
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('step', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/step.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/step.spec.ts
index 752e2676e6..fd76ee16c1 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/step.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/step.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'step' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn step(edge: T ,x: T ) -> T
Returns 1.0 if edge ≤ x, and 0.0 otherwise. Component-wise when T is a vector.
@@ -9,57 +9,33 @@ Returns 1.0 if edge ≤ x, and 0.0 otherwise. Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { anyOf } from '../../../../../util/compare.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, Case, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './step.cache.js';
export const g = makeTestGroup(GPUTest);
-// stepInterval's return value can't always be interpreted as a single acceptance
-// interval, valid result may be 0.0 or 1.0 or both of them, but will never be a
-// value in interval (0.0, 1.0).
-// See the comment block on stepInterval for more details
-const makeCase = (trait: 'f32' | 'f16', edge: number, x: number): Case => {
- const FPTrait = FP[trait];
- edge = FPTrait.quantize(edge);
- x = FPTrait.quantize(x);
- const expected = FPTrait.stepInterval(edge, x);
-
- // [0, 0], [1, 1], or [-∞, +∞] cases
- if (expected.isPoint() || !expected.isFinite()) {
- return { input: [FPTrait.scalarBuilder(edge), FPTrait.scalarBuilder(x)], expected };
- }
-
- // [0, 1] case, valid result is either 0.0 or 1.0.
- const zeroInterval = FPTrait.toInterval(0);
- const oneInterval = FPTrait.toInterval(1);
- return {
- input: [FPTrait.scalarBuilder(edge), FPTrait.scalarBuilder(x)],
- expected: anyOf(zeroInterval, oneInterval),
- };
-};
-
-export const d = makeCaseCache('step', {
- f32: () => {
- return fullF32Range().flatMap(edge => fullF32Range().map(x => makeCase('f32', edge, x)));
- },
- f16: () => {
- return fullF16Range().flatMap(edge => fullF16Range().map(x => makeCase('f16', edge, x)));
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractFloatBuiltin('step'),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -69,7 +45,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('step'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('step'), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -83,5 +59,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('step'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('step'), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tan.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tan.cache.ts
new file mode 100644
index 0000000000..8d8e0d980b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tan.cache.ts
@@ -0,0 +1,23 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .map(trait => ({
+ [`${trait}`]: () => {
+ return FP[trait].generateScalarToIntervalCases(
+ [
+ // Well-defined accuracy range
+ ...linearRange(-Math.PI, Math.PI, 100),
+ ...FP[trait].scalarRange(),
+ ],
+ 'unfiltered',
+ // tan has an inherited accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].tanInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('tan', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tan.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tan.spec.ts
index be3bdee046..7b682d0968 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tan.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tan.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'tan' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn tan(e: T ) -> T
Returns the tangent of e. Component-wise when T is a vector.
@@ -9,48 +9,33 @@ Returns the tangent of e. Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF16Range, linearRange } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './tan.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('tan', {
- f32: () => {
- return FP.f32.generateScalarToIntervalCases(
- [
- // Defined accuracy range
- ...linearRange(-Math.PI, Math.PI, 100),
- ...fullF32Range(),
- ],
- 'unfiltered',
- FP.f32.tanInterval
- );
- },
- f16: () => {
- return FP.f16.generateScalarToIntervalCases(
- [
- // Defined accuracy range
- ...linearRange(-Math.PI, Math.PI, 100),
- ...fullF16Range(),
- ],
- 'unfiltered',
- FP.f16.tanInterval
- );
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractFloatBuiltin('tan'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -60,7 +45,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('tan'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('tan'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -74,5 +59,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('tan'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('tan'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tanh.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tanh.cache.ts
new file mode 100644
index 0000000000..5bada7fef5
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tanh.cache.ts
@@ -0,0 +1,18 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .map(trait => ({
+ [`${trait}`]: () => {
+ return FP[trait].generateScalarToIntervalCases(
+ FP[trait].scalarRange(),
+ 'unfiltered',
+ // tanh has an inherited accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].tanhInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('tanh', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tanh.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tanh.spec.ts
index 3aca5b924b..17978926a3 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tanh.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tanh.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'tanh' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn tanh(e: T ) -> T
Returns the hyperbolic tangent of e. Component-wise when T is a vector.
@@ -9,32 +9,33 @@ Returns the hyperbolic tangent of e. Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './tanh.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('tanh', {
- f32: () => {
- return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.tanhInterval);
- },
- f16: () => {
- return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'unfiltered', FP.f16.tanhInterval);
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractFloatBuiltin('tanh'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -44,7 +45,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('tanh'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('tanh'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -58,5 +59,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('tanh'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('tanh'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureDimension.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureDimension.spec.ts
deleted file mode 100644
index 0ecb9964cf..0000000000
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureDimension.spec.ts
+++ /dev/null
@@ -1,160 +0,0 @@
-export const description = `
-Execution tests for the 'textureDimension' builtin function
-
-The dimensions of the texture in texels.
-For textures based on cubes, the results are the dimensions of each face of the cube.
-Cube faces are square, so the x and y components of the result are equal.
-If level is outside the range [0, textureNumLevels(t)) then any valid value for the return type may be returned.
-`;
-
-import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
-import { GPUTest } from '../../../../../gpu_test.js';
-
-export const g = makeTestGroup(GPUTest);
-
-g.test('sampled')
- .specURL('https://www.w3.org/TR/WGSL/#texturedimensions')
- .desc(
- `
-T: f32, i32, u32
-
-fn textureDimensions(t: texture_1d<T>) -> u32
-fn textureDimensions(t: texture_1d<T>, level: u32) -> u32
-fn textureDimensions(t: texture_2d<T>) -> vec2<u32>
-fn textureDimensions(t: texture_2d<T>, level: u32) -> vec2<u32>
-fn textureDimensions(t: texture_2d_array<T>) -> vec2<u32>
-fn textureDimensions(t: texture_2d_array<T>, level: u32) -> vec2<u32>
-fn textureDimensions(t: texture_3d<T>) -> vec3<u32>
-fn textureDimensions(t: texture_3d<T>, level: u32) -> vec3<u32>
-fn textureDimensions(t: texture_cube<T>) -> vec2<u32>
-fn textureDimensions(t: texture_cube<T>, level: u32) -> vec2<u32>
-fn textureDimensions(t: texture_cube_array<T>) -> vec2<u32>
-fn textureDimensions(t: texture_cube_array<T>, level: u32) -> vec2<u32>
-fn textureDimensions(t: texture_multisampled_2d<T>)-> vec2<u32>
-
-Parameters:
- * t: the sampled texture
- * level:
- - The mip level, with level 0 containing a full size version of the texture.
- - If omitted, the dimensions of level 0 are returned.
-`
- )
- .params(u =>
- u
- .combine('texture_type', [
- 'texture_1d',
- 'texture_2d',
- 'texture_2d_array',
- 'texture_3d',
- 'texture_cube',
- 'texture_cube_array',
- 'texture_multisampled_2d',
- ] as const)
- .beginSubcases()
- .combine('sampled_type', ['f32-only', 'i32', 'u32'] as const)
- .combine('level', [undefined, 0, 1, 'textureNumLevels', 'textureNumLevels+1'] as const)
- )
- .unimplemented();
-
-g.test('depth')
- .specURL('https://www.w3.org/TR/WGSL/#texturedimensions')
- .desc(
- `
-fn textureDimensions(t: texture_depth_2d) -> vec2<u32>
-fn textureDimensions(t: texture_depth_2d, level: u32) -> vec2<u32>
-fn textureDimensions(t: texture_depth_2d_array) -> vec2<u32>
-fn textureDimensions(t: texture_depth_2d_array, level: u32) -> vec2<u32>
-fn textureDimensions(t: texture_depth_cube) -> vec2<u32>
-fn textureDimensions(t: texture_depth_cube, level: u32) -> vec2<u32>
-fn textureDimensions(t: texture_depth_cube_array) -> vec2<u32>
-fn textureDimensions(t: texture_depth_cube_array, level: u32) -> vec2<u32>
-fn textureDimensions(t: texture_depth_multisampled_2d)-> vec2<u32>
-
-Parameters:
- * t: the depth or multisampled texture
- * level:
- - The mip level, with level 0 containing a full size version of the texture.
- - If omitted, the dimensions of level 0 are returned.
-`
- )
- .params(u =>
- u
- .combine('texture_type', [
- 'texture_depth_2d',
- 'texture_depth_2d_array',
- 'texture_depth_cube',
- 'texture_depth_cube_array',
- 'texture_depth_multisampled_2d',
- ])
- .beginSubcases()
- .combine('level', [undefined, 0, 1, 'textureNumLevels', 'textureNumLevels+1'] as const)
- )
- .unimplemented();
-
-g.test('storage')
- .specURL('https://www.w3.org/TR/WGSL/#texturedimensions')
- .desc(
- `
-F: rgba8unorm
- rgba8snorm
- rgba8uint
- rgba8sint
- rgba16uint
- rgba16sint
- rgba16float
- r32uint
- r32sint
- r32float
- rg32uint
- rg32sint
- rg32float
- rgba32uint
- rgba32sint
- rgba32float
-A: read, write, read_write
-
-fn textureDimensions(t: texture_storage_1d<F,A>) -> u32
-fn textureDimensions(t: texture_storage_2d<F,A>) -> vec2<u32>
-fn textureDimensions(t: texture_storage_2d_array<F,A>) -> vec2<u32>
-fn textureDimensions(t: texture_storage_3d<F,A>) -> vec3<u32>
-
-Parameters:
- * t: the storage texture
-`
- )
- .params(u =>
- u
- .combine('texel_format', [
- 'rgba8unorm',
- 'rgba8snorm',
- 'rgba8uint',
- 'rgba8sint',
- 'rgba16uint',
- 'rgba16sint',
- 'rgba16float',
- 'r32uint',
- 'r32sint',
- 'r32float',
- 'rg32uint',
- 'rg32sint',
- 'rg32float',
- 'rgba32uint',
- 'rgba32sint',
- 'rgba32float',
- ] as const)
- .beginSubcases()
- .combine('access_mode', ['read', 'write', 'read_write'] as const)
- )
- .unimplemented();
-
-g.test('external')
- .specURL('https://www.w3.org/TR/WGSL/#texturedimensions')
- .desc(
- `
-fn textureDimensions(t: texture_external) -> vec2<u32>
-
-Parameters:
- * t: the external texture
-`
- )
- .unimplemented();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureDimensions.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureDimensions.spec.ts
new file mode 100644
index 0000000000..1e025ca618
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureDimensions.spec.ts
@@ -0,0 +1,518 @@
+export const description = `
+Execution tests for the 'textureDimensions' builtin function
+
+The dimensions of the texture in texels.
+For textures based on cubes, the results are the dimensions of each face of the cube.
+Cube faces are square, so the x and y components of the result are equal.
+If level is outside the range [0, textureNumLevels(t)) then any valid value for the return type may be returned.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import {
+ kAllTextureFormats,
+ kColorTextureFormats,
+ kTextureFormatInfo,
+ sampleTypeForFormatAndAspect,
+ textureDimensionAndFormatCompatible,
+} from '../../../../../format_info.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { align } from '../../../../../util/math.js';
+
+export const g = makeTestGroup(GPUTest);
+
+/// The maximum number of texture mipmap levels to test.
+/// Keep this small to reduce memory and test permutations.
+const kMaxMipsForTest = 3;
+
+/// The maximum number of texture samples to test.
+const kMaxSamplesForTest = 4;
+
+/// All the possible GPUTextureViewDimensions.
+const kAllViewDimensions: readonly GPUTextureViewDimension[] = [
+ '1d',
+ '2d',
+ '2d-array',
+ '3d',
+ 'cube',
+ 'cube-array',
+] as const;
+
+/** @returns the aspects to test for the given format */
+function aspectsForFormat(format: GPUTextureFormat): readonly GPUTextureAspect[] {
+ const formatInfo = kTextureFormatInfo[format];
+ if (formatInfo.depth !== undefined && formatInfo.stencil !== undefined) {
+ return ['depth-only', 'stencil-only'];
+ }
+ return ['all'];
+}
+
+/** @returns the sample counts to test for the given format */
+function samplesForFormat(format: GPUTextureFormat): readonly number[] {
+ const info = kTextureFormatInfo[format];
+ return info.multisample ? [1, kMaxSamplesForTest] : [1];
+}
+
+/**
+ * @returns a list of number of texture mipmap levels to test, given the format, view dimensions and
+ * number of samples.
+ */
+function textureMipCount(params: {
+ format: GPUTextureFormat;
+ dimensions: GPUTextureViewDimension;
+ samples?: number;
+}): readonly number[] {
+ if (params.samples !== undefined && params.samples !== 1) {
+ // https://www.w3.org/TR/webgpu/#texture-creation
+ // If descriptor.sampleCount > 1: descriptor.mipLevelCount must be 1.
+ return [1];
+ }
+ if (textureDimensionsForViewDimensions(params.dimensions) === '1d') {
+ // https://www.w3.org/TR/webgpu/#dom-gputexturedimension-2d
+ // Only "2d" textures may have mipmaps, be multisampled, use a compressed or depth/stencil
+ // format, and be used as a render attachment.
+ return [1];
+ }
+ return [1, kMaxMipsForTest];
+}
+
+/**
+ * @returns a list of GPUTextureViewDescriptor.baseMipLevel to test, give the texture mipmap count.
+ */
+function baseMipLevel(params: { textureMipCount: number }): readonly number[] {
+ const out: number[] = [];
+ for (let i = 0; i < params.textureMipCount; i++) {
+ out.push(i);
+ }
+ return out;
+}
+
+/**
+ * @returns the argument values for the textureDimensions() `level` parameter to test.
+ * An `undefined` represents a call to textureDimensions() without the level argument.
+ */
+function textureDimensionsLevel(params: {
+ samples?: number;
+ textureMipCount: number;
+ baseMipLevel: number;
+}): readonly (number | undefined)[] {
+ if (params.samples !== undefined && params.samples > 1) {
+ return [undefined]; // textureDimensions() overload with `level` not available.
+ }
+ const out: (number | undefined)[] = [undefined];
+ for (let i = 0; i < params.textureMipCount - params.baseMipLevel; i++) {
+ out.push(i);
+ }
+ return out;
+}
+
+/** @returns the GPUTextureViewDimensions to test for the format and number of samples */
+function viewDimensions(params: {
+ format: GPUTextureFormat;
+ samples?: number;
+}): readonly GPUTextureViewDimension[] {
+ if (params.samples !== undefined && params.samples > 1) {
+ // https://www.w3.org/TR/webgpu/#dom-gputexturedimension-2d
+ // Only 2d textures can be multisampled
+ return ['2d'];
+ }
+
+ return kAllViewDimensions.filter(dim =>
+ textureDimensionAndFormatCompatible(textureDimensionsForViewDimensions(dim), params.format)
+ );
+}
+
+/** @returns the GPUTextureDimension for the GPUTextureViewDimension */
+function textureDimensionsForViewDimensions(dim: GPUTextureViewDimension): GPUTextureDimension {
+ switch (dim) {
+ case '1d':
+ return '1d';
+ case '2d':
+ case '2d-array':
+ case 'cube':
+ case 'cube-array':
+ return '2d';
+ case '3d':
+ return '3d';
+ }
+}
+
+/** TestValues holds the texture size and expected return value of textureDimensions() */
+type TestValues = {
+ /** The value to pass to GPUTextureDescriptor.size, when creating the texture */
+ size: number[];
+ /** The expected result of calling textureDimensions() */
+ expected: number[];
+};
+
+/** @returns The TestValues to use for the given texture dimensions and format */
+function testValues(params: {
+ dimensions: GPUTextureViewDimension;
+ format: GPUTextureFormat;
+ baseMipLevel: number;
+ textureDimensionsLevel?: number;
+}): TestValues {
+ // The minimum dimension length, given the number of mipmap levels that are being tested.
+ const kMinLen = 1 << kMaxMipsForTest;
+ const kNumCubeFaces = 6;
+
+ const formatInfo = kTextureFormatInfo[params.format];
+ const bw = formatInfo.blockWidth;
+ const bh = formatInfo.blockHeight;
+ let mip = params.baseMipLevel;
+ if (params.textureDimensionsLevel !== undefined) {
+ mip += params.textureDimensionsLevel;
+ }
+
+ // Magic constants to multiply the minimum texture dimensions with, to provide
+ // different dimension values in the test. These could be parameterized, but
+ // these are currently fixed to reduce the number of test parameterizations.
+ const kMultipleA = 2;
+ const kMultipleB = 3;
+ const kMultipleC = 4;
+
+ switch (params.dimensions) {
+ case '1d': {
+ const w = align(kMinLen, bw) * kMultipleA;
+ return { size: [w], expected: [w >>> mip] };
+ }
+ case '2d': {
+ const w = align(kMinLen, bw) * kMultipleA;
+ const h = align(kMinLen, bh) * kMultipleB;
+ return { size: [w, h], expected: [w >>> mip, h >>> mip] };
+ }
+ case '2d-array': {
+ const w = align(kMinLen, bw) * kMultipleC;
+ const h = align(kMinLen, bh) * kMultipleB;
+ return { size: [w, h, 4], expected: [w >>> mip, h >>> mip] };
+ }
+ case '3d': {
+ const w = align(kMinLen, bw) * kMultipleA;
+ const h = align(kMinLen, bh) * kMultipleB;
+ const d = kMinLen * kMultipleC;
+ return {
+ size: [w, h, d],
+ expected: [w >>> mip, h >>> mip, d >>> mip],
+ };
+ }
+ case 'cube': {
+ const l = align(kMinLen, bw) * align(kMinLen, bh) * kMultipleB;
+ return {
+ size: [l, l, kNumCubeFaces],
+ expected: [l >>> mip, l >>> mip],
+ };
+ }
+ case 'cube-array': {
+ const l = align(kMinLen, bw) * align(kMinLen, bh) * kMultipleC;
+ return {
+ size: [l, l, kNumCubeFaces * 3],
+ expected: [l >>> mip, l >>> mip],
+ };
+ }
+ }
+}
+
+/**
+ * Builds a shader module with the texture view bound to the WGSL texture with the given WGSL type,
+ * which calls textureDimensions(), assigning the result to an output buffer.
+ * This shader is executed with a compute shader, and the output buffer is compared to
+ * `values.expected`.
+ */
+function run(
+ t: GPUTest,
+ view: GPUTextureView,
+ textureType: string,
+ levelArg: number | undefined,
+ values: TestValues
+) {
+ const outputType = values.expected.length > 1 ? `vec${values.expected.length}u` : 'u32';
+ const wgsl = `
+@group(0) @binding(0) var texture : ${textureType};
+@group(0) @binding(1) var<storage, read_write> output : ${outputType};
+
+@compute @workgroup_size(1)
+fn main() {
+output = ${
+ levelArg !== undefined
+ ? `textureDimensions(texture, ${levelArg})`
+ : 'textureDimensions(texture)'
+ };
+}
+`;
+ const module = t.device.createShaderModule({
+ code: wgsl,
+ });
+ const pipeline = t.device.createComputePipeline({
+ compute: { module },
+ layout: 'auto',
+ });
+ const outputBuffer = t.device.createBuffer({
+ size: 32,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE,
+ });
+ const bindgroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: view },
+ { binding: 1, resource: { buffer: outputBuffer } },
+ ],
+ });
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindgroup);
+ pass.dispatchWorkgroups(1);
+ pass.end();
+ t.device.queue.submit([encoder.finish()]);
+
+ t.expectGPUBufferValuesEqual(outputBuffer, new Uint32Array(values.expected));
+}
+
+/** @returns true if the GPUTextureViewDimension is valid for a storage texture */
+function dimensionsValidForStorage(dimensions: GPUTextureViewDimension) {
+ switch (dimensions) {
+ case '1d':
+ case '2d':
+ case '2d-array':
+ case '3d':
+ return true;
+ default:
+ return false;
+ }
+}
+
+g.test('sampled_and_multisampled')
+ .specURL('https://www.w3.org/TR/WGSL/#texturedimensions')
+ .desc(
+ `
+T: f32, i32, u32
+
+fn textureDimensions(t: texture_1d<T>) -> u32
+fn textureDimensions(t: texture_1d<T>, level: u32) -> u32
+fn textureDimensions(t: texture_2d<T>) -> vec2<u32>
+fn textureDimensions(t: texture_2d<T>, level: u32) -> vec2<u32>
+fn textureDimensions(t: texture_2d_array<T>) -> vec2<u32>
+fn textureDimensions(t: texture_2d_array<T>, level: u32) -> vec2<u32>
+fn textureDimensions(t: texture_3d<T>) -> vec3<u32>
+fn textureDimensions(t: texture_3d<T>, level: u32) -> vec3<u32>
+fn textureDimensions(t: texture_cube<T>) -> vec2<u32>
+fn textureDimensions(t: texture_cube<T>, level: u32) -> vec2<u32>
+fn textureDimensions(t: texture_cube_array<T>) -> vec2<u32>
+fn textureDimensions(t: texture_cube_array<T>, level: u32) -> vec2<u32>
+fn textureDimensions(t: texture_multisampled_2d<T>)-> vec2<u32>
+
+Parameters:
+ * t: the sampled texture
+ * level:
+ - The mip level, with level 0 containing a full size version of the texture.
+ - If omitted, the dimensions of level 0 are returned.
+`
+ )
+ .params(u =>
+ u
+ .combine('format', kAllTextureFormats)
+ .unless(p => kTextureFormatInfo[p.format].color?.type === 'unfilterable-float')
+ .expand('aspect', u => aspectsForFormat(u.format))
+ .expand('samples', u => samplesForFormat(u.format))
+ .beginSubcases()
+ .expand('dimensions', viewDimensions)
+ .expand('textureMipCount', textureMipCount)
+ .expand('baseMipLevel', baseMipLevel)
+ .expand('textureDimensionsLevel', textureDimensionsLevel)
+ )
+ .beforeAllSubcases(t => {
+ const info = kTextureFormatInfo[t.params.format];
+ t.skipIfTextureFormatNotSupported(t.params.format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+ })
+ .fn(t => {
+ t.skipIfTextureViewDimensionNotSupported(t.params.dimensions);
+ const values = testValues(t.params);
+ const texture = t.device.createTexture({
+ size: values.size,
+ dimension: textureDimensionsForViewDimensions(t.params.dimensions),
+ ...(t.isCompatibility && { textureBindingViewDimension: t.params.dimensions }),
+ usage:
+ t.params.samples === 1
+ ? GPUTextureUsage.TEXTURE_BINDING
+ : GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT,
+ format: t.params.format,
+ sampleCount: t.params.samples,
+ mipLevelCount: t.params.textureMipCount,
+ });
+ const textureView = texture.createView({
+ dimension: t.params.dimensions,
+ aspect: t.params.aspect,
+ baseMipLevel: t.params.baseMipLevel,
+ });
+
+ function wgslSampledTextureType(): string {
+ const base = t.params.samples !== 1 ? 'texture_multisampled' : 'texture';
+ const dimensions = t.params.dimensions.replace('-', '_');
+ const sampleType = sampleTypeForFormatAndAspect(t.params.format, t.params.aspect);
+ switch (sampleType) {
+ case 'depth':
+ case 'float':
+ return `${base}_${dimensions}<f32>`;
+ case 'uint':
+ return `${base}_${dimensions}<u32>`;
+ case 'sint':
+ return `${base}_${dimensions}<i32>`;
+ case 'unfilterable-float':
+ throw new Error(`'${t.params.format}' does not support sampling`);
+ }
+ }
+
+ run(t, textureView, wgslSampledTextureType(), t.params.textureDimensionsLevel, values);
+ });
+
+g.test('depth')
+ .specURL('https://www.w3.org/TR/WGSL/#texturedimensions')
+ .desc(
+ `
+fn textureDimensions(t: texture_depth_2d) -> vec2<u32>
+fn textureDimensions(t: texture_depth_2d, level: u32) -> vec2<u32>
+fn textureDimensions(t: texture_depth_2d_array) -> vec2<u32>
+fn textureDimensions(t: texture_depth_2d_array, level: u32) -> vec2<u32>
+fn textureDimensions(t: texture_depth_cube) -> vec2<u32>
+fn textureDimensions(t: texture_depth_cube, level: u32) -> vec2<u32>
+fn textureDimensions(t: texture_depth_cube_array) -> vec2<u32>
+fn textureDimensions(t: texture_depth_cube_array, level: u32) -> vec2<u32>
+fn textureDimensions(t: texture_depth_multisampled_2d)-> vec2<u32>
+
+Parameters:
+ * t: the depth or multisampled texture
+ * level:
+ - The mip level, with level 0 containing a full size version of the texture.
+ - If omitted, the dimensions of level 0 are returned.
+`
+ )
+ .params(u =>
+ u
+ .combine('format', kAllTextureFormats)
+ .filter(p => !!kTextureFormatInfo[p.format].depth)
+ .expand('aspect', u => aspectsForFormat(u.format))
+ .unless(u => u.aspect === 'stencil-only')
+ .expand('samples', u => samplesForFormat(u.format))
+ .beginSubcases()
+ .expand('dimensions', viewDimensions)
+ .expand('textureMipCount', textureMipCount)
+ .expand('baseMipLevel', baseMipLevel)
+ .expand('textureDimensionsLevel', textureDimensionsLevel)
+ )
+ .beforeAllSubcases(t => {
+ const info = kTextureFormatInfo[t.params.format];
+ t.skipIfTextureFormatNotSupported(t.params.format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+ })
+ .fn(t => {
+ t.skipIfTextureViewDimensionNotSupported(t.params.dimensions);
+ const values = testValues(t.params);
+ const texture = t.device.createTexture({
+ size: values.size,
+ dimension: textureDimensionsForViewDimensions(t.params.dimensions),
+ ...(t.isCompatibility && { textureBindingViewDimension: t.params.dimensions }),
+ usage:
+ t.params.samples === 1
+ ? GPUTextureUsage.TEXTURE_BINDING
+ : GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT,
+ format: t.params.format,
+ sampleCount: t.params.samples,
+ mipLevelCount: t.params.textureMipCount,
+ });
+ const textureView = texture.createView({
+ dimension: t.params.dimensions,
+ aspect: t.params.aspect,
+ baseMipLevel: t.params.baseMipLevel,
+ });
+
+ function wgslDepthTextureType(): string {
+ const base = t.params.samples !== 1 ? 'texture_depth_multisampled' : 'texture_depth';
+ const dimensions = t.params.dimensions.replace('-', '_');
+ return `${base}_${dimensions}`;
+ }
+
+ run(t, textureView, wgslDepthTextureType(), t.params.textureDimensionsLevel, values);
+ });
+
+g.test('storage')
+ .specURL('https://www.w3.org/TR/WGSL/#texturedimensions')
+ .desc(
+ `
+F: rgba8unorm
+ rgba8snorm
+ rgba8uint
+ rgba8sint
+ rgba16uint
+ rgba16sint
+ rgba16float
+ r32uint
+ r32sint
+ r32float
+ rg32uint
+ rg32sint
+ rg32float
+ rgba32uint
+ rgba32sint
+ rgba32float
+A: read, write, read_write
+
+fn textureDimensions(t: texture_storage_1d<F,A>) -> u32
+fn textureDimensions(t: texture_storage_2d<F,A>) -> vec2<u32>
+fn textureDimensions(t: texture_storage_2d_array<F,A>) -> vec2<u32>
+fn textureDimensions(t: texture_storage_3d<F,A>) -> vec3<u32>
+
+Parameters:
+ * t: the storage texture
+`
+ )
+ .params(u =>
+ u
+ .combine('format', kColorTextureFormats)
+ .filter(p => kTextureFormatInfo[p.format].color?.storage === true)
+ .expand('aspect', u => aspectsForFormat(u.format))
+ .beginSubcases()
+ .expand('dimensions', u => viewDimensions(u).filter(dimensionsValidForStorage))
+ .expand('textureMipCount', textureMipCount)
+ .expand('baseMipLevel', baseMipLevel)
+ )
+ .beforeAllSubcases(t => {
+ const info = kTextureFormatInfo[t.params.format];
+ t.skipIfTextureFormatNotSupported(t.params.format);
+ t.skipIfTextureFormatNotUsableAsStorageTexture(t.params.format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+ })
+ .fn(t => {
+ const values = testValues(t.params);
+ const texture = t.device.createTexture({
+ size: values.size,
+ dimension: textureDimensionsForViewDimensions(t.params.dimensions),
+ usage: GPUTextureUsage.STORAGE_BINDING,
+ format: t.params.format,
+ mipLevelCount: t.params.textureMipCount,
+ });
+ const textureView = texture.createView({
+ dimension: t.params.dimensions,
+ aspect: t.params.aspect,
+ mipLevelCount: 1,
+ baseMipLevel: t.params.baseMipLevel,
+ });
+
+ function wgslStorageTextureType(): string {
+ const dimensions = t.params.dimensions.replace('-', '_');
+ return `texture_storage_${dimensions}<${t.params.format}, write>`;
+ }
+
+ run(t, textureView, wgslStorageTextureType(), undefined, values);
+ });
+
+g.test('external')
+ .specURL('https://www.w3.org/TR/WGSL/#texturedimensions')
+ .desc(
+ `
+fn textureDimensions(t: texture_external) -> vec2<u32>
+
+Parameters:
+ * t: the external texture
+`
+ )
+ .unimplemented();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSample.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSample.spec.ts
index f5b01dfc63..7c743576e9 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSample.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSample.spec.ts
@@ -1,26 +1,24 @@
export const description = `
Samples a texture.
-
-Must only be used in a fragment shader stage.
-Must only be invoked in uniform control flow.
`;
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
-import { GPUTest } from '../../../../../gpu_test.js';
+import { kEncodableTextureFormats, kTextureFormatInfo } from '../../../../../format_info.js';
+import { GPUTest, TextureTestMixin } from '../../../../../gpu_test.js';
+import { hashU32 } from '../../../../../util/math.js';
+import { kTexelRepresentationInfo } from '../../../../../util/texture/texel_data.js';
+import {
+ vec2,
+ createRandomTexelView,
+ TextureCall,
+ putDataInTextureThenDrawAndCheckResults,
+ generateSamplePoints,
+ kSamplePointMethods,
+} from './texture_utils.js';
import { generateCoordBoundaries, generateOffsets } from './utils.js';
-export const g = makeTestGroup(GPUTest);
-
-g.test('stage')
- .specURL('https://www.w3.org/TR/WGSL/#texturesample')
- .desc(
- `
-Tests that 'textureSample' can only be called in 'fragment' shaders.
-`
- )
- .params(u => u.combine('stage', ['fragment', 'vertex', 'compute'] as const))
- .unimplemented();
+export const g = makeTestGroup(TextureTestMixin(GPUTest));
g.test('control_flow')
.specURL('https://www.w3.org/TR/WGSL/#texturesample')
@@ -70,11 +68,74 @@ Parameters:
Values outside of this range will result in a shader-creation error.
`
)
- .paramsSubcasesOnly(u =>
+ .params(u =>
u
- .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'] as const)
- .combine('coords', generateCoordBoundaries(2))
- .combine('offset', generateOffsets(2))
+ .combine('format', kEncodableTextureFormats)
+ .filter(t => {
+ const type = kTextureFormatInfo[t.format].color?.type;
+ return type === 'float' || type === 'unfilterable-float';
+ })
+ .combine('sample_points', kSamplePointMethods)
+ .combine('addressModeU', ['clamp-to-edge', 'repeat', 'mirror-repeat'] as const)
+ .combine('addressModeV', ['clamp-to-edge', 'repeat', 'mirror-repeat'] as const)
+ .combine('minFilter', ['nearest', 'linear'] as const)
+ .combine('offset', [false, true] as const)
+ )
+ .beforeAllSubcases(t => {
+ const format = kTexelRepresentationInfo[t.params.format];
+ t.skipIfTextureFormatNotSupported(t.params.format);
+ const hasFloat32 = format.componentOrder.some(c => {
+ const info = format.componentInfo[c]!;
+ return info.dataType === 'float' && info.bitLength === 32;
+ });
+ if (hasFloat32) {
+ t.selectDeviceOrSkipTestCase('float32-filterable');
+ }
+ })
+ .fn(async t => {
+ const descriptor: GPUTextureDescriptor = {
+ format: t.params.format,
+ size: { width: 8, height: 8 },
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING,
+ };
+ const texelView = createRandomTexelView(descriptor);
+ const calls: TextureCall<vec2>[] = generateSamplePoints(50, t.params.minFilter === 'nearest', {
+ method: t.params.sample_points,
+ textureWidth: 8,
+ textureHeight: 8,
+ }).map((c, i) => {
+ const hash = hashU32(i) & 0xff;
+ return {
+ builtin: 'textureSample',
+ coordType: 'f',
+ coords: c,
+ offset: t.params.offset ? [(hash & 15) - 8, (hash >> 4) - 8] : undefined,
+ };
+ });
+ const sampler: GPUSamplerDescriptor = {
+ addressModeU: t.params.addressModeU,
+ addressModeV: t.params.addressModeV,
+ minFilter: t.params.minFilter,
+ magFilter: t.params.minFilter,
+ };
+ const res = await putDataInTextureThenDrawAndCheckResults(
+ t.device,
+ { texels: texelView, descriptor },
+ sampler,
+ calls
+ );
+ t.expectOK(res);
+ });
+
+g.test('sampled_2d_coords,derivatives')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesample')
+ .desc(
+ `
+fn textureSample(t: texture_2d<f32>, s: sampler, coords: vec2<f32>) -> vec4<f32>
+fn textureSample(t: texture_2d<f32>, s: sampler, coords: vec2<f32>, offset: vec2<i32>) -> vec4<f32>
+
+test mip level selection based on derivatives
+ `
)
.unimplemented();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleBias.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleBias.spec.ts
index 786bce4830..1c61c1a5f2 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleBias.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleBias.spec.ts
@@ -2,8 +2,6 @@ export const description = `
Execution tests for the 'textureSampleBias' builtin function
Samples a texture with a bias to the mip level.
-Must only be used in a fragment shader stage.
-Must only be invoked in uniform control flow.
`;
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
@@ -13,26 +11,6 @@ import { generateCoordBoundaries, generateOffsets } from './utils.js';
export const g = makeTestGroup(GPUTest);
-g.test('stage')
- .specURL('https://www.w3.org/TR/WGSL/#texturesamplebias')
- .desc(
- `
-Tests that 'textureSampleBias' can only be called in 'fragment' shaders.
-`
- )
- .params(u => u.combine('stage', ['fragment', 'vertex', 'compute'] as const))
- .unimplemented();
-
-g.test('control_flow')
- .specURL('https://www.w3.org/TR/WGSL/#texturesamplebias')
- .desc(
- `
-Tests that 'textureSampleBias' can only be called in uniform control flow.
-`
- )
- .params(u => u.combine('stage', ['fragment', 'vertex', 'compute'] as const))
- .unimplemented();
-
g.test('sampled_2d_coords')
.specURL('https://www.w3.org/TR/WGSL/#texturesamplebias')
.desc(
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleCompare.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleCompare.spec.ts
index 9f723fac2e..eae5098257 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleCompare.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleCompare.spec.ts
@@ -1,8 +1,5 @@
export const description = `
Samples a depth texture and compares the sampled depth values against a reference value.
-
-Must only be used in a fragment shader stage.
-Must only be invoked in uniform control flow.
`;
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
@@ -12,26 +9,6 @@ import { generateCoordBoundaries, generateOffsets } from './utils.js';
export const g = makeTestGroup(GPUTest);
-g.test('stage')
- .specURL('https://www.w3.org/TR/WGSL/#texturesamplecompare')
- .desc(
- `
-Tests that 'textureSampleCompare' can only be called in 'fragment' shaders.
-`
- )
- .params(u => u.combine('stage', ['fragment', 'vertex', 'compute'] as const))
- .unimplemented();
-
-g.test('control_flow')
- .specURL('https://www.w3.org/TR/WGSL/#texturesamplecompare')
- .desc(
- `
-Tests that 'textureSampleCompare' can only be called in uniform control flow.
-`
- )
- .params(u => u.combine('stage', ['fragment', 'vertex', 'compute'] as const))
- .unimplemented();
-
g.test('2d_coords')
.specURL('https://www.w3.org/TR/WGSL/#texturesamplecompare')
.desc(
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/texture_utils.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/texture_utils.ts
new file mode 100644
index 0000000000..fe2da53d5a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/texture_utils.ts
@@ -0,0 +1,809 @@
+import { assert, range, unreachable } from '../../../../../../common/util/util.js';
+import { EncodableTextureFormat } from '../../../../../format_info.js';
+import { float32ToUint32 } from '../../../../../util/conversion.js';
+import { align, clamp, hashU32, lerp, quantizeToF32 } from '../../../../../util/math.js';
+import {
+ kTexelRepresentationInfo,
+ PerTexelComponent,
+ TexelRepresentationInfo,
+} from '../../../../../util/texture/texel_data.js';
+import { TexelView } from '../../../../../util/texture/texel_view.js';
+import { createTextureFromTexelView } from '../../../../../util/texture.js';
+import { reifyExtent3D } from '../../../../../util/unions.js';
+
+function getLimitValue(v: number) {
+ switch (v) {
+ case Number.POSITIVE_INFINITY:
+ return 1000;
+ case Number.NEGATIVE_INFINITY:
+ return -1000;
+ default:
+ return v;
+ }
+}
+
+function getValueBetweenMinAndMaxTexelValueInclusive(
+ rep: TexelRepresentationInfo,
+ normalized: number
+) {
+ return lerp(
+ getLimitValue(rep.numericRange!.min),
+ getLimitValue(rep.numericRange!.max),
+ normalized
+ );
+}
+
+/**
+ * Creates a TexelView filled with random values.
+ */
+export function createRandomTexelView(info: {
+ format: GPUTextureFormat;
+ size: GPUExtent3D;
+}): TexelView {
+ const rep = kTexelRepresentationInfo[info.format as EncodableTextureFormat];
+ const generator = (coords: Required<GPUOrigin3DDict>): Readonly<PerTexelComponent<number>> => {
+ const texel: PerTexelComponent<number> = {};
+ for (const component of rep.componentOrder) {
+ const rnd = hashU32(coords.x, coords.y, coords.z, component.charCodeAt(0));
+ const normalized = clamp(rnd / 0xffffffff, { min: 0, max: 1 });
+ texel[component] = getValueBetweenMinAndMaxTexelValueInclusive(rep, normalized);
+ }
+ return quantize(texel, rep);
+ };
+ return TexelView.fromTexelsAsColors(info.format as EncodableTextureFormat, generator);
+}
+
+export type vec2 = [number, number];
+export type vec3 = [number, number, number];
+export type vec4 = [number, number, number, number];
+export type Dimensionality = number | vec2 | vec3;
+
+type TextureCallArgKeys = keyof TextureCallArgs<number>;
+const kTextureCallArgNames: TextureCallArgKeys[] = [
+ 'coords',
+ 'mipLevel',
+ 'arrayIndex',
+ 'ddx',
+ 'ddy',
+ 'offset',
+];
+
+export interface TextureCallArgs<T extends Dimensionality> {
+ coords?: T;
+ mipLevel?: number;
+ arrayIndex?: number;
+ ddx?: T;
+ ddy?: T;
+ offset?: T;
+}
+
+export interface TextureCall<T extends Dimensionality> extends TextureCallArgs<T> {
+ builtin: 'textureSample' | 'textureLoad';
+ coordType: 'f';
+}
+
+function toArray(coords: Dimensionality): number[] {
+ if (coords instanceof Array) {
+ return coords;
+ }
+ return [coords];
+}
+
+function quantize(texel: PerTexelComponent<number>, repl: TexelRepresentationInfo) {
+ return repl.bitsToNumber(repl.unpackBits(new Uint8Array(repl.pack(repl.encode(texel)))));
+}
+
+function apply(a: number[], b: number[], op: (x: number, y: number) => number) {
+ assert(a.length === b.length, `apply(${a}, ${b}): arrays must have same length`);
+ return a.map((v, i) => op(v, b[i]));
+}
+
+const add = (a: number[], b: number[]) => apply(a, b, (x, y) => x + y);
+
+export interface Texture {
+ texels: TexelView;
+ descriptor: GPUTextureDescriptor;
+}
+
+/**
+ * Returns the expect value for a WGSL builtin texture function
+ */
+export function expected<T extends Dimensionality>(
+ call: TextureCall<T>,
+ texture: Texture,
+ sampler: GPUSamplerDescriptor
+): PerTexelComponent<number> {
+ const rep = kTexelRepresentationInfo[texture.texels.format];
+ const textureExtent = reifyExtent3D(texture.descriptor.size);
+ const textureSize = [textureExtent.width, textureExtent.height, textureExtent.depthOrArrayLayers];
+ const addressMode = [
+ sampler.addressModeU ?? 'clamp-to-edge',
+ sampler.addressModeV ?? 'clamp-to-edge',
+ sampler.addressModeW ?? 'clamp-to-edge',
+ ];
+
+ const load = (at: number[]) =>
+ texture.texels.color({
+ x: Math.floor(at[0]),
+ y: Math.floor(at[1] ?? 0),
+ z: Math.floor(at[2] ?? 0),
+ });
+
+ switch (call.builtin) {
+ case 'textureSample': {
+ const coords = toArray(call.coords!);
+
+ // convert normalized to absolute texel coordinate
+ // ┌───┬───┬───┬───┐
+ // │ a │ │ │ │ norm: a = 1/8, b = 7/8
+ // ├───┼───┼───┼───┤ abs: a = 0, b = 3
+ // │ │ │ │ │
+ // ├───┼───┼───┼───┤
+ // │ │ │ │ │
+ // ├───┼───┼───┼───┤
+ // │ │ │ │ b │
+ // └───┴───┴───┴───┘
+ let at = coords.map((v, i) => v * textureSize[i] - 0.5);
+
+ // Apply offset in whole texel units
+ if (call.offset !== undefined) {
+ at = add(at, toArray(call.offset));
+ }
+
+ const samples: { at: number[]; weight: number }[] = [];
+
+ const filter = sampler.minFilter;
+ switch (filter) {
+ case 'linear': {
+ // 'p0' is the lower texel for 'at'
+ const p0 = at.map(v => Math.floor(v));
+ // 'p1' is the higher texel for 'at'
+ const p1 = p0.map(v => v + 1);
+
+ // interpolation weights for p0 and p1
+ const p1W = at.map((v, i) => v - p0[i]);
+ const p0W = p1W.map(v => 1 - v);
+
+ switch (coords.length) {
+ case 1:
+ samples.push({ at: p0, weight: p0W[0] });
+ samples.push({ at: p1, weight: p1W[0] });
+ break;
+ case 2: {
+ samples.push({ at: p0, weight: p0W[0] * p0W[1] });
+ samples.push({ at: [p1[0], p0[1]], weight: p1W[0] * p0W[1] });
+ samples.push({ at: [p0[0], p1[1]], weight: p0W[0] * p1W[1] });
+ samples.push({ at: p1, weight: p1W[0] * p1W[1] });
+ break;
+ }
+ }
+ break;
+ }
+ case 'nearest': {
+ const p = at.map(v => Math.round(quantizeToF32(v)));
+ samples.push({ at: p, weight: 1 });
+ break;
+ }
+ default:
+ unreachable();
+ }
+
+ const out: PerTexelComponent<number> = {};
+ const ss = [];
+ for (const sample of samples) {
+ // Apply sampler address mode
+ const c = sample.at.map((v, i) => {
+ switch (addressMode[i]) {
+ case 'clamp-to-edge':
+ return clamp(v, { min: 0, max: textureSize[i] - 1 });
+ case 'mirror-repeat': {
+ const n = Math.floor(v / textureSize[i]);
+ v = v - n * textureSize[i];
+ return (n & 1) !== 0 ? textureSize[i] - v - 1 : v;
+ }
+ case 'repeat':
+ return v - Math.floor(v / textureSize[i]) * textureSize[i];
+ default:
+ unreachable();
+ }
+ });
+ const v = load(c);
+ ss.push(v);
+ for (const component of rep.componentOrder) {
+ out[component] = (out[component] ?? 0) + v[component]! * sample.weight;
+ }
+ }
+
+ return out;
+ }
+ case 'textureLoad': {
+ return load(toArray(call.coords!));
+ }
+ }
+}
+
+/**
+ * Puts random data in a texture, generates a shader that implements `calls`
+ * such that each call's result is written to the next consecutive texel of
+ * a rgba32float texture. It then checks the result of each call matches
+ * the expected result.
+ */
+export async function putDataInTextureThenDrawAndCheckResults<T extends Dimensionality>(
+ device: GPUDevice,
+ texture: Texture,
+ sampler: GPUSamplerDescriptor,
+ calls: TextureCall<T>[]
+) {
+ const results = await doTextureCalls(device, texture, sampler, calls);
+ const errs: string[] = [];
+ const rep = kTexelRepresentationInfo[texture.texels.format];
+ for (let callIdx = 0; callIdx < calls.length; callIdx++) {
+ const call = calls[callIdx];
+ const got = results[callIdx];
+ const expect = expected(call, texture, sampler);
+
+ const gULP = rep.bitsToULPFromZero(rep.numberToBits(got));
+ const eULP = rep.bitsToULPFromZero(rep.numberToBits(expect));
+ for (const component of rep.componentOrder) {
+ const g = got[component]!;
+ const e = expect[component]!;
+ const absDiff = Math.abs(g - e);
+ const ulpDiff = Math.abs(gULP[component]! - eULP[component]!);
+ const relDiff = absDiff / Math.max(Math.abs(g), Math.abs(e));
+ if (ulpDiff > 3 && relDiff > 0.03) {
+ const desc = describeTextureCall(call);
+ errs.push(`component was not as expected:
+ call: ${desc}
+ component: ${component}
+ got: ${g}
+ expected: ${e}
+ abs diff: ${absDiff.toFixed(4)}
+ rel diff: ${(relDiff * 100).toFixed(2)}%
+ ulp diff: ${ulpDiff}
+ sample points:
+`);
+ const expectedSamplePoints = [
+ 'expected:',
+ ...(await identifySamplePoints(texture.descriptor, (texels: TexelView) => {
+ return Promise.resolve(
+ expected(call, { texels, descriptor: texture.descriptor }, sampler)
+ );
+ })),
+ ];
+ const gotSamplePoints = [
+ 'got:',
+ ...(await identifySamplePoints(
+ texture.descriptor,
+ async (texels: TexelView) =>
+ (
+ await doTextureCalls(device, { texels, descriptor: texture.descriptor }, sampler, [
+ call,
+ ])
+ )[0]
+ )),
+ ];
+ errs.push(layoutTwoColumns(expectedSamplePoints, gotSamplePoints).join('\n'));
+ errs.push('', '');
+ }
+ }
+ }
+
+ return errs.length > 0 ? new Error(errs.join('\n')) : undefined;
+}
+
+/**
+ * Generates a text art grid showing which texels were sampled
+ * followed by a list of the samples and the weights used for each
+ * component.
+ *
+ * Example:
+ *
+ * 0 1 2 3 4 5 6 7
+ * ┌───┬───┬───┬───┬───┬───┬───┬───┐
+ * 0 │ │ │ │ │ │ │ │ │
+ * ├───┼───┼───┼───┼───┼───┼───┼───┤
+ * 1 │ │ │ │ │ │ │ │ a │
+ * ├───┼───┼───┼───┼───┼───┼───┼───┤
+ * 2 │ │ │ │ │ │ │ │ b │
+ * ├───┼───┼───┼───┼───┼───┼───┼───┤
+ * 3 │ │ │ │ │ │ │ │ │
+ * ├───┼───┼───┼───┼───┼───┼───┼───┤
+ * 4 │ │ │ │ │ │ │ │ │
+ * ├───┼───┼───┼───┼───┼───┼───┼───┤
+ * 5 │ │ │ │ │ │ │ │ │
+ * ├───┼───┼───┼───┼───┼───┼───┼───┤
+ * 6 │ │ │ │ │ │ │ │ │
+ * ├───┼───┼───┼───┼───┼───┼───┼───┤
+ * 7 │ │ │ │ │ │ │ │ │
+ * └───┴───┴───┴───┴───┴───┴───┴───┘
+ * a: at: [7, 1], weights: [R: 0.75000]
+ * b: at: [7, 2], weights: [R: 0.25000]
+ */
+async function identifySamplePoints(
+ info: GPUTextureDescriptor,
+ run: (texels: TexelView) => Promise<PerTexelComponent<number>>
+) {
+ const textureSize = reifyExtent3D(info.size);
+ const numTexels = textureSize.width * textureSize.height;
+ const rep = kTexelRepresentationInfo[info.format as EncodableTextureFormat];
+
+ // Identify all the texels that are sampled, and their weights.
+ const sampledTexelWeights = new Map<number, PerTexelComponent<number>>();
+ const unclassifiedStack = [new Set<number>(range(numTexels, v => v))];
+ while (unclassifiedStack.length > 0) {
+ // Pop the an unclassified texels stack
+ const unclassified = unclassifiedStack.pop()!;
+
+ // Split unclassified texels evenly into two new sets
+ const setA = new Set<number>();
+ const setB = new Set<number>();
+ [...unclassified.keys()].forEach((t, i) => ((i & 1) === 0 ? setA : setB).add(t));
+
+ // Push setB to the unclassified texels stack
+ if (setB.size > 0) {
+ unclassifiedStack.push(setB);
+ }
+
+ // See if any of the texels in setA were sampled.
+ const results = await run(
+ TexelView.fromTexelsAsColors(
+ info.format as EncodableTextureFormat,
+ (coords: Required<GPUOrigin3DDict>): Readonly<PerTexelComponent<number>> => {
+ const isCandidate = setA.has(coords.x + coords.y * textureSize.width);
+ const texel: PerTexelComponent<number> = {};
+ for (const component of rep.componentOrder) {
+ texel[component] = isCandidate ? 1 : 0;
+ }
+ return texel;
+ }
+ )
+ );
+ if (rep.componentOrder.some(c => results[c] !== 0)) {
+ // One or more texels of setA were sampled.
+ if (setA.size === 1) {
+ // We identified a specific texel was sampled.
+ // As there was only one texel in the set, results holds the sampling weights.
+ setA.forEach(texel => sampledTexelWeights.set(texel, results));
+ } else {
+ // More than one texel in the set. Needs splitting.
+ unclassifiedStack.push(setA);
+ }
+ }
+ }
+
+ // ┌───┬───┬───┬───┐
+ // │ a │ │ │ │
+ // ├───┼───┼───┼───┤
+ // │ │ │ │ │
+ // ├───┼───┼───┼───┤
+ // │ │ │ │ │
+ // ├───┼───┼───┼───┤
+ // │ │ │ │ b │
+ // └───┴───┴───┴───┘
+ const letter = (idx: number) => String.fromCharCode(97 + idx); // 97: 'a'
+ const orderedTexelIndices: number[] = [];
+ const lines: string[] = [];
+ {
+ let line = ' ';
+ for (let x = 0; x < textureSize.width; x++) {
+ line += ` ${x} `;
+ }
+ lines.push(line);
+ }
+ {
+ let line = ' ┌';
+ for (let x = 0; x < textureSize.width; x++) {
+ line += x === textureSize.width - 1 ? '───┐' : '───┬';
+ }
+ lines.push(line);
+ }
+ for (let y = 0; y < textureSize.height; y++) {
+ {
+ let line = `${y} │`;
+ for (let x = 0; x < textureSize.width; x++) {
+ const texelIdx = x + y * textureSize.height;
+ const weight = sampledTexelWeights.get(texelIdx);
+ if (weight !== undefined) {
+ line += ` ${letter(orderedTexelIndices.length)} │`;
+ orderedTexelIndices.push(texelIdx);
+ } else {
+ line += ' │';
+ }
+ }
+ lines.push(line);
+ }
+ if (y < textureSize.height - 1) {
+ let line = ' ├';
+ for (let x = 0; x < textureSize.width; x++) {
+ line += x === textureSize.width - 1 ? '───┤' : '───┼';
+ }
+ lines.push(line);
+ }
+ }
+ {
+ let line = ' └';
+ for (let x = 0; x < textureSize.width; x++) {
+ line += x === textureSize.width - 1 ? '───┘' : '───┴';
+ }
+ lines.push(line);
+ }
+
+ orderedTexelIndices.forEach((texelIdx, i) => {
+ const weights = sampledTexelWeights.get(texelIdx)!;
+ const y = Math.floor(texelIdx / textureSize.width);
+ const x = texelIdx - y * textureSize.height;
+ const w = rep.componentOrder.map(c => `${c}: ${weights[c]?.toFixed(5)}`).join(', ');
+ lines.push(`${letter(i)}: at: [${x}, ${y}], weights: [${w}]`);
+ });
+ return lines;
+}
+
+function layoutTwoColumns(columnA: string[], columnB: string[]) {
+ const widthA = Math.max(...columnA.map(l => l.length));
+ const lines = Math.max(columnA.length, columnB.length);
+ const out: string[] = new Array<string>(lines);
+ for (let line = 0; line < lines; line++) {
+ const a = columnA[line] ?? '';
+ const b = columnB[line] ?? '';
+ out[line] = `${a}${' '.repeat(widthA - a.length)} | ${b}`;
+ }
+ return out;
+}
+
+export const kSamplePointMethods = ['texel-centre', 'spiral'] as const;
+export type SamplePointMethods = (typeof kSamplePointMethods)[number];
+
+/**
+ * Generates an array of coordinates at which to sample a texture.
+ */
+export function generateSamplePoints(
+ n: number,
+ nearest: boolean,
+ args:
+ | {
+ method: 'texel-centre';
+ textureWidth: number;
+ textureHeight: number;
+ }
+ | {
+ method: 'spiral';
+ radius?: number;
+ loops?: number;
+ textureWidth: number;
+ textureHeight: number;
+ }
+) {
+ const out: vec2[] = [];
+ switch (args.method) {
+ case 'texel-centre': {
+ for (let i = 0; i < n; i++) {
+ const r = hashU32(i);
+ const x = Math.floor(lerp(0, args.textureWidth - 1, (r & 0xffff) / 0xffff)) + 0.5;
+ const y = Math.floor(lerp(0, args.textureHeight - 1, (r >>> 16) / 0xffff)) + 0.5;
+ out.push([x / args.textureWidth, y / args.textureHeight]);
+ }
+ break;
+ }
+ case 'spiral': {
+ for (let i = 0; i < n; i++) {
+ const f = i / (Math.max(n, 2) - 1);
+ const r = (args.radius ?? 1.5) * f;
+ const a = (args.loops ?? 2) * 2 * Math.PI * f;
+ out.push([0.5 + r * Math.cos(a), 0.5 + r * Math.sin(a)]);
+ }
+ break;
+ }
+ }
+ // Samplers across devices use different methods to interpolate.
+ // Quantizing the texture coordinates seems to hit coords that produce
+ // comparable results to our computed results.
+ // Note: This value works with 8x8 textures. Other sizes have not been tested.
+ // Values that worked for reference:
+ // Win 11, NVidia 2070 Super: 16
+ // Linux, AMD Radeon Pro WX 3200: 256
+ // MacOS, M1 Mac: 256
+ const kSubdivisionsPerTexel = 4;
+ const q = [args.textureWidth * kSubdivisionsPerTexel, args.textureHeight * kSubdivisionsPerTexel];
+ return out.map(
+ c =>
+ c.map((v, i) => {
+ // Quantize to kSubdivisionsPerPixel
+ const v1 = Math.floor(v * q[i]);
+ // If it's nearest and we're on the edge of a texel then move us off the edge
+ // since the edge could choose one texel or another in nearest mode
+ const v2 = nearest && v1 % kSubdivisionsPerTexel === 0 ? v1 + 1 : v1;
+ // Convert back to texture coords
+ return v2 / q[i];
+ }) as vec2
+ );
+}
+
+function wgslTypeFor(data: Dimensionality, type: 'f' | 'i' | 'u'): string {
+ if (data instanceof Array) {
+ switch (data.length) {
+ case 2:
+ return `vec2${type}`;
+ case 3:
+ return `vec3${type}`;
+ }
+ }
+ return '${type}32';
+}
+
+function wgslExpr(data: number | vec2 | vec3 | vec4): string {
+ if (data instanceof Array) {
+ switch (data.length) {
+ case 2:
+ return `vec2(${data.map(v => v.toString()).join(', ')})`;
+ case 3:
+ return `vec3(${data.map(v => v.toString()).join(', ')})`;
+ }
+ }
+ return data.toString();
+}
+
+function binKey<T extends Dimensionality>(call: TextureCall<T>): string {
+ const keys: string[] = [];
+ for (const name of kTextureCallArgNames) {
+ const value = call[name];
+ if (value !== undefined) {
+ if (name === 'offset') {
+ // offset must be a constant expression
+ keys.push(`${name}: ${wgslExpr(value)}`);
+ } else {
+ keys.push(`${name}: ${wgslTypeFor(value, call.coordType)}`);
+ }
+ }
+ }
+ return `${call.builtin}(${keys.join(', ')})`;
+}
+
+function buildBinnedCalls<T extends Dimensionality>(calls: TextureCall<T>[]) {
+ const args: string[] = ['T']; // All texture builtins take the texture as the first argument
+ const fields: string[] = [];
+ const data: number[] = [];
+
+ const prototype = calls[0];
+ if (prototype.builtin.startsWith('textureSample')) {
+ // textureSample*() builtins take a sampler as the second argument
+ args.push('S');
+ }
+
+ for (const name of kTextureCallArgNames) {
+ const value = prototype[name];
+ if (value !== undefined) {
+ if (name === 'offset') {
+ args.push(`/* offset */ ${wgslExpr(value)}`);
+ } else {
+ args.push(`args.${name}`);
+ fields.push(`@align(16) ${name} : ${wgslTypeFor(value, prototype.coordType)}`);
+ }
+ }
+ }
+
+ for (const call of calls) {
+ for (const name of kTextureCallArgNames) {
+ const value = call[name];
+ assert(
+ (prototype[name] === undefined) === (value === undefined),
+ 'texture calls are not binned correctly'
+ );
+ if (value !== undefined && name !== 'offset') {
+ const bitcastToU32 = (value: number) => {
+ if (calls[0].coordType === 'f') {
+ return float32ToUint32(value);
+ }
+ return value;
+ };
+ if (value instanceof Array) {
+ for (const c of value) {
+ data.push(bitcastToU32(c));
+ }
+ } else {
+ data.push(bitcastToU32(value));
+ }
+ // All fields are aligned to 16 bytes.
+ while ((data.length & 3) !== 0) {
+ data.push(0);
+ }
+ }
+ }
+ }
+
+ const expr = `${prototype.builtin}(${args.join(', ')})`;
+
+ return { expr, fields, data };
+}
+
+function binCalls<T extends Dimensionality>(calls: TextureCall<T>[]): number[][] {
+ const map = new Map<string, number>(); // key to bin index
+ const bins: number[][] = [];
+ calls.forEach((call, callIdx) => {
+ const key = binKey(call);
+ const binIdx = map.get(key);
+ if (binIdx === undefined) {
+ map.set(key, bins.length);
+ bins.push([callIdx]);
+ } else {
+ bins[binIdx].push(callIdx);
+ }
+ });
+ return bins;
+}
+
+export function describeTextureCall<T extends Dimensionality>(call: TextureCall<T>): string {
+ const args: string[] = ['texture: T'];
+ if (call.builtin.startsWith('textureSample')) {
+ args.push('sampler: S');
+ }
+ for (const name of kTextureCallArgNames) {
+ const value = call[name];
+ if (value !== undefined) {
+ args.push(`${name}: ${wgslExpr(value)}`);
+ }
+ }
+ return `${call.builtin}(${args.join(', ')})`;
+}
+
+/**
+ * Given a list of "calls", each one of which has a texture coordinate,
+ * generates a fragment shader that uses the fragment position as an index
+ * (position.y * 256 + position.x) That index is then used to look up a
+ * coordinate from a storage buffer which is used to call the WGSL texture
+ * function to read/sample the texture, and then write to an rgba32float
+ * texture. We then read the rgba32float texture for the per "call" results.
+ *
+ * Calls are "binned" by call parameters. Each bin has its own structure and
+ * field in the storage buffer. This allows the calls to be non-homogenous and
+ * each have their own data type for coordinates.
+ */
+export async function doTextureCalls<T extends Dimensionality>(
+ device: GPUDevice,
+ texture: Texture,
+ sampler: GPUSamplerDescriptor,
+ calls: TextureCall<T>[]
+) {
+ let structs = '';
+ let body = '';
+ let dataFields = '';
+ const data: number[] = [];
+ let callCount = 0;
+ const binned = binCalls(calls);
+ binned.forEach((binCalls, binIdx) => {
+ const b = buildBinnedCalls(binCalls.map(callIdx => calls[callIdx]));
+ structs += `struct Args${binIdx} {
+ ${b.fields.join(', \n')}
+}
+`;
+ dataFields += ` args${binIdx} : array<Args${binIdx}, ${binCalls.length}>,
+`;
+ body += `
+ {
+ let is_active = (frag_idx >= ${callCount}) & (frag_idx < ${callCount + binCalls.length});
+ let args = data.args${binIdx}[frag_idx - ${callCount}];
+ let call = ${b.expr};
+ result = select(result, call, is_active);
+ }
+`;
+ callCount += binCalls.length;
+ data.push(...b.data);
+ });
+
+ const dataBuffer = device.createBuffer({
+ size: data.length * 4,
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE,
+ });
+ device.queue.writeBuffer(dataBuffer, 0, new Uint32Array(data));
+
+ const rtWidth = 256;
+ const renderTarget = device.createTexture({
+ format: 'rgba32float',
+ size: { width: rtWidth, height: Math.ceil(calls.length / rtWidth) },
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
+ });
+
+ const code = `
+${structs}
+
+struct Data {
+${dataFields}
+}
+
+@vertex
+fn vs_main(@builtin(vertex_index) vertex_index : u32) -> @builtin(position) vec4f {
+ let positions = array(
+ vec4f(-1, 1, 0, 1), vec4f( 1, 1, 0, 1),
+ vec4f(-1, -1, 0, 1), vec4f( 1, -1, 0, 1),
+ );
+ return positions[vertex_index];
+}
+
+@group(0) @binding(0) var T : texture_2d<f32>;
+@group(0) @binding(1) var S : sampler;
+@group(0) @binding(2) var<storage> data : Data;
+
+@fragment
+fn fs_main(@builtin(position) frag_pos : vec4f) -> @location(0) vec4f {
+ let frag_idx = u32(frag_pos.x) + u32(frag_pos.y) * ${renderTarget.width};
+ var result : vec4f;
+${body}
+ return result;
+}
+`;
+ const shaderModule = device.createShaderModule({ code });
+
+ const pipeline = device.createRenderPipeline({
+ layout: 'auto',
+ vertex: { module: shaderModule, entryPoint: 'vs_main' },
+ fragment: {
+ module: shaderModule,
+ entryPoint: 'fs_main',
+ targets: [{ format: renderTarget.format }],
+ },
+ primitive: { topology: 'triangle-strip', cullMode: 'none' },
+ });
+
+ const gpuTexture = createTextureFromTexelView(device, texture.texels, texture.descriptor);
+ const gpuSampler = device.createSampler(sampler);
+
+ const bindGroup = device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: gpuTexture.createView() },
+ { binding: 1, resource: gpuSampler },
+ { binding: 2, resource: { buffer: dataBuffer } },
+ ],
+ });
+
+ const bytesPerRow = align(16 * renderTarget.width, 256);
+ const resultBuffer = device.createBuffer({
+ size: renderTarget.height * bytesPerRow,
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
+ });
+ const encoder = device.createCommandEncoder();
+
+ const renderPass = encoder.beginRenderPass({
+ colorAttachments: [{ view: renderTarget.createView(), loadOp: 'clear', storeOp: 'store' }],
+ });
+
+ renderPass.setPipeline(pipeline);
+ renderPass.setBindGroup(0, bindGroup);
+ renderPass.draw(4);
+ renderPass.end();
+ encoder.copyTextureToBuffer(
+ { texture: renderTarget },
+ { buffer: resultBuffer, bytesPerRow },
+ { width: renderTarget.width, height: renderTarget.height }
+ );
+ device.queue.submit([encoder.finish()]);
+
+ await resultBuffer.mapAsync(GPUMapMode.READ);
+
+ const view = TexelView.fromTextureDataByReference(
+ renderTarget.format as EncodableTextureFormat,
+ new Uint8Array(resultBuffer.getMappedRange()),
+ {
+ bytesPerRow,
+ rowsPerImage: renderTarget.height,
+ subrectOrigin: [0, 0, 0],
+ subrectSize: [renderTarget.width, renderTarget.height],
+ }
+ );
+
+ let outIdx = 0;
+ const out = new Array<PerTexelComponent<number>>(calls.length);
+ for (const bin of binned) {
+ for (const callIdx of bin) {
+ const x = outIdx % rtWidth;
+ const y = Math.floor(outIdx / rtWidth);
+ out[callIdx] = view.color({ x, y, z: 0 });
+ outIdx++;
+ }
+ }
+
+ renderTarget.destroy();
+ gpuTexture.destroy();
+ resultBuffer.destroy();
+
+ return out;
+}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/transpose.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/transpose.cache.ts
new file mode 100644
index 0000000000..cb89e2d194
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/transpose.cache.ts
@@ -0,0 +1,27 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]_matCxR_[non_]const
+// abstract_matCxR_non_const is empty and not used
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([2, 3, 4] as const).flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateMatrixToMatrixCases(
+ FP[trait].sparseMatrixRange(cols, rows),
+ nonConst ? 'unfiltered' : 'finite',
+ FP[trait].transposeInterval
+ );
+ },
+ }))
+ )
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('transpose', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/transpose.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/transpose.spec.ts
index 6fd4887f35..7a96906cfa 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/transpose.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/transpose.spec.ts
@@ -1,82 +1,21 @@
export const description = `
Execution tests for the 'transpose' builtin function
-T is AbstractFloat, f32, or f16
+T is abstract-float, f32, or f16
@const transpose(e: matRxC<T> ) -> matCxR<T>
Returns the transpose of e.
`;
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeAbstractFloat, TypeF16, TypeF32, TypeMat } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import {
- sparseMatrixF16Range,
- sparseMatrixF32Range,
- sparseMatrixF64Range,
-} from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
+import { Type } from '../../../../../util/conversion.js';
import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { abstractBuiltin, builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './transpose.cache.js';
export const g = makeTestGroup(GPUTest);
-// Cases: f32_matCxR_[non_]const
-const f32_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).flatMap(rows =>
- ([true, false] as const).map(nonConst => ({
- [`f32_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateMatrixToMatrixCases(
- sparseMatrixF32Range(cols, rows),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.transposeInterval
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-// Cases: f16_matCxR_[non_]const
-const f16_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).flatMap(rows =>
- ([true, false] as const).map(nonConst => ({
- [`f16_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateMatrixToMatrixCases(
- sparseMatrixF16Range(cols, rows),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.transposeInterval
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-// Cases: abstract_matCxR
-const abstract_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).map(rows => ({
- [`abstract_mat${cols}x${rows}`]: () => {
- return FP.abstract.generateMatrixToMatrixCases(
- sparseMatrixF64Range(cols, rows),
- 'finite',
- FP.abstract.transposeInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('transpose', {
- ...f32_cases,
- ...f16_cases,
- ...abstract_cases,
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#matrix-builtin-functions')
.desc(`abstract float tests`)
@@ -89,12 +28,12 @@ g.test('abstract_float')
.fn(async t => {
const cols = t.params.cols;
const rows = t.params.rows;
- const cases = await d.get(`abstract_mat${cols}x${rows}`);
+ const cases = await d.get(`abstract_mat${cols}x${rows}_const`);
await run(
t,
- abstractBuiltin('transpose'),
- [TypeMat(cols, rows, TypeAbstractFloat)],
- TypeMat(rows, cols, TypeAbstractFloat),
+ abstractFloatBuiltin('transpose'),
+ [Type.mat(cols, rows, Type.abstractFloat)],
+ Type.mat(rows, cols, Type.abstractFloat),
t.params,
cases
);
@@ -120,8 +59,8 @@ g.test('f32')
await run(
t,
builtin('transpose'),
- [TypeMat(cols, rows, TypeF32)],
- TypeMat(rows, cols, TypeF32),
+ [Type.mat(cols, rows, Type.f32)],
+ Type.mat(rows, cols, Type.f32),
t.params,
cases
);
@@ -150,8 +89,8 @@ g.test('f16')
await run(
t,
builtin('transpose'),
- [TypeMat(cols, rows, TypeF16)],
- TypeMat(rows, cols, TypeF16),
+ [Type.mat(cols, rows, Type.f16)],
+ Type.mat(rows, cols, Type.f16),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/trunc.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/trunc.cache.ts
new file mode 100644
index 0000000000..061c95b07f
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/trunc.cache.ts
@@ -0,0 +1,17 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .map(trait => ({
+ [`${trait}`]: () => {
+ return FP[trait].generateScalarToIntervalCases(
+ FP[trait].scalarRange(),
+ 'unfiltered',
+ FP[trait].truncInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('trunc', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/trunc.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/trunc.spec.ts
index 63cd8470f5..9d05709fc6 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/trunc.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/trunc.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'trunc' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn trunc(e: T ) -> T
Returns the nearest whole number whose absolute value is less than or equal to e.
@@ -10,32 +10,14 @@ Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeAbstractFloat, TypeF16, TypeF32 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF64Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
+import { Type } from '../../../../../util/conversion.js';
import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { abstractBuiltin, builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './trunc.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('trunc', {
- f32: () => {
- return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.truncInterval);
- },
- f16: () => {
- return FP.f16.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f16.truncInterval);
- },
- abstract: () => {
- return FP.abstract.generateScalarToIntervalCases(
- fullF64Range(),
- 'unfiltered',
- FP.abstract.truncInterval
- );
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
@@ -46,7 +28,14 @@ g.test('abstract_float')
)
.fn(async t => {
const cases = await d.get('abstract');
- await run(t, abstractBuiltin('trunc'), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases);
+ await run(
+ t,
+ abstractFloatBuiltin('trunc'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
});
g.test('f32')
@@ -57,7 +46,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('trunc'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('trunc'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -71,5 +60,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('trunc'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('trunc'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16float.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16float.cache.ts
new file mode 100644
index 0000000000..79a7a568d2
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16float.cache.ts
@@ -0,0 +1,20 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { fullU32Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+export const d = makeCaseCache('unpack2x16float', {
+ u32_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'finite',
+ FP.f32.unpack2x16floatInterval
+ );
+ },
+ u32_non_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'unfiltered',
+ FP.f32.unpack2x16floatInterval
+ );
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16float.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16float.spec.ts
index 4a0bf075e9..e145afca50 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16float.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16float.spec.ts
@@ -7,33 +7,14 @@ interpretation of bits 16×i through 16×i+15 of e as an IEEE-754 binary16 value
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeU32, TypeVec } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullU32Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
+import { Type } from '../../../../../util/conversion.js';
import { allInputSources, run } from '../../expression.js';
import { builtin } from './builtin.js';
+import { d } from './unpack2x16float.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('unpack2x16float', {
- u32_const: () => {
- return FP.f32.generateU32ToIntervalCases(
- fullU32Range(),
- 'finite',
- FP.f32.unpack2x16floatInterval
- );
- },
- u32_non_const: () => {
- return FP.f32.generateU32ToIntervalCases(
- fullU32Range(),
- 'unfiltered',
- FP.f32.unpack2x16floatInterval
- );
- },
-});
-
g.test('unpack')
.specURL('https://www.w3.org/TR/WGSL/#unpack-builtin-functions')
.desc(
@@ -44,5 +25,5 @@ g.test('unpack')
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'u32_const' : 'u32_non_const');
- await run(t, builtin('unpack2x16float'), [TypeU32], TypeVec(2, TypeF32), t.params, cases);
+ await run(t, builtin('unpack2x16float'), [Type.u32], Type.vec2f, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm.cache.ts
new file mode 100644
index 0000000000..89dfb475d3
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm.cache.ts
@@ -0,0 +1,20 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { fullU32Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+export const d = makeCaseCache('unpack2x16snorm', {
+ u32_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'finite',
+ FP.f32.unpack2x16snormInterval
+ );
+ },
+ u32_non_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'unfiltered',
+ FP.f32.unpack2x16snormInterval
+ );
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm.spec.ts
index 195cfd9a01..059a5664f9 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm.spec.ts
@@ -7,33 +7,14 @@ of bits 16×i through 16×i+15 of e as a twos-complement signed integer.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeU32, TypeVec } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullU32Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
+import { Type } from '../../../../../util/conversion.js';
import { allInputSources, run } from '../../expression.js';
import { builtin } from './builtin.js';
+import { d } from './unpack2x16snorm.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('unpack2x16snorm', {
- u32_const: () => {
- return FP.f32.generateU32ToIntervalCases(
- fullU32Range(),
- 'finite',
- FP.f32.unpack2x16snormInterval
- );
- },
- u32_non_const: () => {
- return FP.f32.generateU32ToIntervalCases(
- fullU32Range(),
- 'unfiltered',
- FP.f32.unpack2x16snormInterval
- );
- },
-});
-
g.test('unpack')
.specURL('https://www.w3.org/TR/WGSL/#unpack-builtin-functions')
.desc(
@@ -44,5 +25,5 @@ g.test('unpack')
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'u32_const' : 'u32_non_const');
- await run(t, builtin('unpack2x16snorm'), [TypeU32], TypeVec(2, TypeF32), t.params, cases);
+ await run(t, builtin('unpack2x16snorm'), [Type.u32], Type.vec2f, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm.cache.ts
new file mode 100644
index 0000000000..7f0fe84af6
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm.cache.ts
@@ -0,0 +1,20 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { fullU32Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+export const d = makeCaseCache('unpack2x16unorm', {
+ u32_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'finite',
+ FP.f32.unpack2x16unormInterval
+ );
+ },
+ u32_non_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'unfiltered',
+ FP.f32.unpack2x16unormInterval
+ );
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm.spec.ts
index 16b4e6397c..971f384023 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm.spec.ts
@@ -7,33 +7,14 @@ Component i of the result is v ÷ 65535, where v is the interpretation of bits
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeU32, TypeVec } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullU32Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
+import { Type } from '../../../../../util/conversion.js';
import { allInputSources, run } from '../../expression.js';
import { builtin } from './builtin.js';
+import { d } from './unpack2x16unorm.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('unpack2x16unorm', {
- u32_const: () => {
- return FP.f32.generateU32ToIntervalCases(
- fullU32Range(),
- 'finite',
- FP.f32.unpack2x16unormInterval
- );
- },
- u32_non_const: () => {
- return FP.f32.generateU32ToIntervalCases(
- fullU32Range(),
- 'unfiltered',
- FP.f32.unpack2x16unormInterval
- );
- },
-});
-
g.test('unpack')
.specURL('https://www.w3.org/TR/WGSL/#unpack-builtin-functions')
.desc(
@@ -44,5 +25,5 @@ g.test('unpack')
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'u32_const' : 'u32_non_const');
- await run(t, builtin('unpack2x16unorm'), [TypeU32], TypeVec(2, TypeF32), t.params, cases);
+ await run(t, builtin('unpack2x16unorm'), [Type.u32], Type.vec2f, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm.cache.ts
new file mode 100644
index 0000000000..3a4790f188
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm.cache.ts
@@ -0,0 +1,20 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { fullU32Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+export const d = makeCaseCache('unpack4x8snorm', {
+ u32_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'finite',
+ FP.f32.unpack4x8snormInterval
+ );
+ },
+ u32_non_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'unfiltered',
+ FP.f32.unpack4x8snormInterval
+ );
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm.spec.ts
index 7ea8d51918..d6e533621b 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm.spec.ts
@@ -7,33 +7,14 @@ bits 8×i through 8×i+7 of e as a twos-complement signed integer.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeU32, TypeVec } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullU32Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
+import { Type } from '../../../../../util/conversion.js';
import { allInputSources, run } from '../../expression.js';
import { builtin } from './builtin.js';
+import { d } from './unpack4x8snorm.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('unpack4x8snorm', {
- u32_const: () => {
- return FP.f32.generateU32ToIntervalCases(
- fullU32Range(),
- 'finite',
- FP.f32.unpack4x8snormInterval
- );
- },
- u32_non_const: () => {
- return FP.f32.generateU32ToIntervalCases(
- fullU32Range(),
- 'unfiltered',
- FP.f32.unpack4x8snormInterval
- );
- },
-});
-
g.test('unpack')
.specURL('https://www.w3.org/TR/WGSL/#unpack-builtin-functions')
.desc(
@@ -44,5 +25,5 @@ g.test('unpack')
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'u32_const' : 'u32_non_const');
- await run(t, builtin('unpack4x8snorm'), [TypeU32], TypeVec(4, TypeF32), t.params, cases);
+ await run(t, builtin('unpack4x8snorm'), [Type.u32], Type.vec4f, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm.cache.ts
new file mode 100644
index 0000000000..21390f74b1
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm.cache.ts
@@ -0,0 +1,20 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { fullU32Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+export const d = makeCaseCache('unpack4x8unorm', {
+ u32_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'finite',
+ FP.f32.unpack4x8unormInterval
+ );
+ },
+ u32_non_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'unfiltered',
+ FP.f32.unpack4x8unormInterval
+ );
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm.spec.ts
index bf54d23c12..026043da1a 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm.spec.ts
@@ -7,33 +7,14 @@ through 8×i+7 of e as an unsigned integer.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeU32, TypeVec } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullU32Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
+import { Type } from '../../../../../util/conversion.js';
import { allInputSources, run } from '../../expression.js';
import { builtin } from './builtin.js';
+import { d } from './unpack4x8unorm.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('unpack4x8unorm', {
- u32_const: () => {
- return FP.f32.generateU32ToIntervalCases(
- fullU32Range(),
- 'finite',
- FP.f32.unpack4x8unormInterval
- );
- },
- u32_non_const: () => {
- return FP.f32.generateU32ToIntervalCases(
- fullU32Range(),
- 'unfiltered',
- FP.f32.unpack4x8unormInterval
- );
- },
-});
-
g.test('unpack')
.specURL('https://www.w3.org/TR/WGSL/#unpack-builtin-functions')
.desc(
@@ -44,5 +25,5 @@ g.test('unpack')
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'u32_const' : 'u32_non_const');
- await run(t, builtin('unpack4x8unorm'), [TypeU32], TypeVec(4, TypeF32), t.params, cases);
+ await run(t, builtin('unpack4x8unorm'), [Type.u32], Type.vec4f, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4xI8.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4xI8.spec.ts
new file mode 100644
index 0000000000..4f7e4ed3a7
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4xI8.spec.ts
@@ -0,0 +1,56 @@
+export const description = `
+Execution tests for the 'unpack4xI8' builtin function
+
+@const fn unpack4xI8(e: u32) -> vec4<i32>
+e is interpreted as a vector with four 8-bit signed integer components. Unpack e into a vec4<i32>
+with sign extension.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { u32, toVector, i32, Type } from '../../../../../util/conversion.js';
+import { Case } from '../../case.js';
+import { allInputSources, Config, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('basic')
+ .specURL('https://www.w3.org/TR/WGSL/#unpack4xI8-builtin')
+ .desc(
+ `
+@const fn unpack4xI8(e: u32) -> vec4<i32>
+ `
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cfg: Config = t.params;
+
+ const unpack4xI8 = (e: number) => {
+ const result: [number, number, number, number] = [0, 0, 0, 0];
+ for (let i = 0; i < 4; ++i) {
+ let intValue = (e >> (8 * i)) & 0xff;
+ if (intValue > 127) {
+ intValue -= 256;
+ }
+ result[i] = intValue;
+ }
+ return result;
+ };
+
+ const testInputs = [
+ 0, 0x01020304, 0xfcfdfeff, 0x040302ff, 0x0403fe01, 0x04fd0201, 0xfc030201, 0xfcfdfe01,
+ 0xfcfd02ff, 0xfc03feff, 0x04fdfeff, 0x0403feff, 0x04fd02ff, 0xfc0302ff, 0x04fdfe01,
+ 0xfc03fe01, 0xfcfd0201, 0x80817f7e,
+ ] as const;
+
+ const makeCase = (e: number): Case => {
+ return { input: [u32(e)], expected: toVector(unpack4xI8(e), i32) };
+ };
+ const cases: Array<Case> = testInputs.flatMap(v => {
+ return [makeCase(v)];
+ });
+
+ await run(t, builtin('unpack4xI8'), [Type.u32], Type.vec4i, cfg, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4xU8.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4xU8.spec.ts
new file mode 100644
index 0000000000..09849030eb
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4xU8.spec.ts
@@ -0,0 +1,48 @@
+export const description = `
+Execution tests for the 'unpack4xU8' builtin function
+
+@const fn unpack4xU8(e: u32) -> vec4<u32>
+e is interpreted as a vector with four 8-bit unsigned integer components. Unpack e into a vec4<u32>
+with zero extension.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { u32, toVector, Type } from '../../../../../util/conversion.js';
+import { Case } from '../../case.js';
+import { allInputSources, Config, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('basic')
+ .specURL('https://www.w3.org/TR/WGSL/#unpack4xU8-builtin')
+ .desc(
+ `
+@const fn unpack4xU8(e: u32) -> vec4<u32>
+ `
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cfg: Config = t.params;
+
+ const unpack4xU8 = (e: number) => {
+ const result: [number, number, number, number] = [0, 0, 0, 0];
+ for (let i = 0; i < 4; ++i) {
+ result[i] = (e >> (8 * i)) & 0xff;
+ }
+ return result;
+ };
+
+ const testInputs = [0, 0x08060402, 0xffffffff, 0xfefdfcfb] as const;
+
+ const makeCase = (e: number): Case => {
+ return { input: [u32(e)], expected: toVector(unpack4xU8(e), u32) };
+ };
+ const cases: Array<Case> = testInputs.flatMap(v => {
+ return [makeCase(v)];
+ });
+
+ await run(t, builtin('unpack4xU8'), [Type.u32], Type.vec4u, cfg, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/workgroupUniformLoad.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/workgroupUniformLoad.spec.ts
new file mode 100644
index 0000000000..099b54146d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/workgroupUniformLoad.spec.ts
@@ -0,0 +1,182 @@
+export const description = `
+Executes a control barrier synchronization function that affects memory and atomic operations in the workgroup address space.
+`;
+
+// NOTE: The control barrier executed by this builtin is tested in the memory_model tests.
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import {
+ TypedArrayBufferView,
+ TypedArrayBufferViewConstructor,
+ iterRange,
+} from '../../../../../../common/util/util.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { checkElementsEqualGenerated } from '../../../../../util/check_contents.js';
+
+export const g = makeTestGroup(GPUTest);
+
+interface TypeConfig {
+ // The value to store the workgroup variable.
+ store_val: string;
+ // The expected values once the variable has been copied back to the host.
+ expected: TypedArrayBufferView;
+ // The type used for the host-visible buffer, if different from the workgroup variable.
+ host_type?: string;
+ // A type conversion function, if the types are different.
+ to_host?: (x: string) => string;
+ // Any additional module-scope declarations needed by the type.
+ decls?: string;
+}
+
+// A list of types configurations used for the workgroup variable.
+const kTypes: Record<string, TypeConfig> = {
+ bool: {
+ store_val: `true`,
+ expected: new Uint32Array([1]),
+ host_type: 'u32',
+ to_host: (x: string) => `u32(${x})`,
+ },
+ u32: {
+ store_val: `42`,
+ expected: new Uint32Array([42]),
+ },
+ vec4u: {
+ store_val: `vec4u(42, 1, 0xffffffff, 777)`,
+ expected: new Uint32Array([42, 1, 0xffffffff, 777]),
+ },
+ mat3x2f: {
+ store_val: `mat3x2(42, 1, 65536, -42, -1, -65536)`,
+ expected: new Float32Array([42, 1, 65536, -42, -1, -65536]),
+ },
+ 'array<u32, 4>': {
+ store_val: `array(42, 1, 0xffffffff, 777)`,
+ expected: new Uint32Array([42, 1, 0xffffffff, 777]),
+ },
+ SimpleStruct: {
+ decls: 'struct SimpleStruct { a: u32, b: u32, c: u32, d: u32, }',
+ store_val: `SimpleStruct(42, 1, 0xffffffff, 777)`,
+ expected: new Uint32Array([42, 1, 0xffffffff, 777]),
+ },
+ ComplexStruct: {
+ decls: `struct Inner { v: vec4u, }
+ struct ComplexStruct {
+ a: array<Inner, 4>,
+ @size(28) b: vec4u,
+ c: u32
+ }
+ const v = vec4(42, 1, 0xffffffff, 777);
+ const rhs = ComplexStruct(
+ array(Inner(v.xyzw), Inner(v.yzwx), Inner(v.zwxy), Inner(v.wxyz)),
+ v.xzxz,
+ 0x12345678,
+ );`,
+ store_val: `rhs`,
+ expected: new Uint32Array([
+ // v.xyzw
+ 42, 1, 0xffffffff, 777,
+ // v.yzwx
+ 1, 0xffffffff, 777, 42,
+ // v.zwxy
+ 0xffffffff, 777, 42, 1,
+ // v.wxyz
+ 777, 42, 1, 0xffffffff,
+ // v.xzxz
+ 42, 0xffffffff, 42, 0xffffffff,
+ // 12 bytes of padding
+ 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, 0x12345678,
+ ]),
+ },
+};
+
+g.test('types')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#workgroupUniformLoad-builtin')
+ .desc(
+ `Test that the result of a workgroupUniformLoad is the value previously stored to the workgroup variable, for a variety of types.
+ `
+ )
+ .params(u =>
+ u.combine('type', keysOf(kTypes)).combine('wgsize', [
+ [1, 1],
+ [3, 7],
+ [1, 128],
+ [16, 16],
+ ])
+ )
+ .fn(t => {
+ const type = kTypes[t.params.type];
+ const wgsize_x = t.params.wgsize[0];
+ const wgsize_y = t.params.wgsize[1];
+ const num_invocations = wgsize_x * wgsize_y;
+ const num_words_per_invocation = type.expected.length;
+ const total_host_words = num_invocations * num_words_per_invocation;
+
+ t.skipIf(
+ num_invocations > t.device.limits.maxComputeInvocationsPerWorkgroup,
+ `num_invocations (${num_invocations}) > maxComputeInvocationsPerWorkgroup (${t.device.limits.maxComputeInvocationsPerWorkgroup})`
+ );
+
+ let load = `workgroupUniformLoad(&wgvar)`;
+ if (type.to_host) {
+ load = type.to_host(load);
+ }
+
+ // Construct a shader that stores a value to workgroup variable and then loads it using
+ // workgroupUniformLoad() in every invocation, copying the results back to a storage buffer.
+ const code = `
+ ${type.decls ? type.decls : ''}
+
+ @group(0) @binding(0) var<storage, read_write> buffer : array<${
+ type.host_type ? type.host_type : t.params.type
+ }, ${num_invocations}>;
+
+ var<workgroup> wgvar : ${t.params.type};
+
+ @compute @workgroup_size(${wgsize_x}, ${wgsize_y})
+ fn main(@builtin(local_invocation_index) lid: u32) {
+ if (lid == ${num_invocations - 1}) {
+ wgvar = ${type.store_val};
+ }
+ buffer[lid] = ${load};
+ }
+ `;
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code }),
+ entryPoint: 'main',
+ },
+ });
+
+ // Allocate a buffer and fill it with 0xdeadbeef values.
+ const outputBuffer = t.makeBufferWithContents(
+ new Uint32Array([...iterRange(total_host_words, _i => 0xdeadbeef)]),
+ GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
+ );
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [{ binding: 0, resource: { buffer: outputBuffer } }],
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(1);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Check that the output matches the expected values for each invocation.
+ t.expectGPUBufferValuesPassCheck(
+ outputBuffer,
+ data =>
+ checkElementsEqualGenerated(data, i => {
+ return Number(type.expected[i % num_words_per_invocation]);
+ }),
+ {
+ type: type.expected.constructor as TypedArrayBufferViewConstructor,
+ typedLength: total_host_words,
+ }
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/user/ptr_params.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/user/ptr_params.spec.ts
new file mode 100644
index 0000000000..87c3b9f2e1
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/user/ptr_params.spec.ts
@@ -0,0 +1,849 @@
+export const description = `
+User function call tests for pointer parameters.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+function wgslTypeDecl(kind: 'vec4i' | 'array' | 'struct') {
+ switch (kind) {
+ case 'vec4i':
+ return `
+alias T = vec4i;
+`;
+ case 'array':
+ return `
+alias T = array<vec4f, 3>;
+`;
+ case 'struct':
+ return `
+struct S {
+a : i32,
+b : u32,
+c : i32,
+d : u32,
+}
+alias T = S;
+`;
+ }
+}
+
+function valuesForType(kind: 'vec4i' | 'array' | 'struct') {
+ switch (kind) {
+ case 'vec4i':
+ return new Uint32Array([1, 2, 3, 4]);
+ case 'array':
+ return new Float32Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
+ case 'struct':
+ return new Uint32Array([1, 2, 3, 4]);
+ }
+}
+
+function run(
+ t: GPUTest,
+ wgsl: string,
+ inputUsage: 'uniform' | 'storage',
+ input: Uint32Array | Float32Array,
+ expected: Uint32Array | Float32Array
+) {
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main',
+ },
+ });
+
+ const inputBuffer = t.makeBufferWithContents(
+ input,
+ inputUsage === 'uniform' ? GPUBufferUsage.UNIFORM : GPUBufferUsage.STORAGE
+ );
+
+ const outputBuffer = t.device.createBuffer({
+ size: expected.buffer.byteLength,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ });
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: { buffer: inputBuffer } },
+ { binding: 1, resource: { buffer: outputBuffer } },
+ ],
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(1);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ t.expectGPUBufferValuesEqual(outputBuffer, expected);
+}
+
+g.test('read_full_object')
+ .desc('Test a pointer parameter can be read by a callee function')
+ .params(u =>
+ u
+ .combine('address_space', ['function', 'private', 'workgroup', 'storage', 'uniform'] as const)
+ .combine('call_indirection', [0, 1, 2] as const)
+ .combine('type', ['vec4i', 'array', 'struct'] as const)
+ )
+ .fn(t => {
+ switch (t.params.address_space) {
+ case 'workgroup':
+ case 'storage':
+ case 'uniform':
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+ }
+
+ const main: string = {
+ function: `
+@compute @workgroup_size(1)
+fn main() {
+ var F : T = input;
+ f0(&F);
+}
+`,
+ private: `
+var<private> P : T;
+@compute @workgroup_size(1)
+fn main() {
+ P = input;
+ f0(&P);
+}
+`,
+ workgroup: `
+var<workgroup> W : T;
+@compute @workgroup_size(1)
+fn main() {
+ W = input;
+ f0(&W);
+}
+`,
+ storage: `
+@compute @workgroup_size(1)
+fn main() {
+ f0(&input);
+}
+`,
+ uniform: `
+@compute @workgroup_size(1)
+fn main() {
+ f0(&input);
+}
+`,
+ }[t.params.address_space];
+
+ let call_chain = '';
+ for (let i = 0; i < t.params.call_indirection; i++) {
+ call_chain += `
+fn f${i}(p : ptr<${t.params.address_space}, T>) {
+ f${i + 1}(p);
+}
+`;
+ }
+
+ const inputVar: string =
+ t.params.address_space === 'uniform'
+ ? `@binding(0) @group(0) var<uniform> input : T;`
+ : `@binding(0) @group(0) var<storage, read> input : T;`;
+
+ const wgsl = `
+${wgslTypeDecl(t.params.type)}
+
+${inputVar}
+
+@binding(1) @group(0) var<storage, read_write> output : T;
+
+fn f${t.params.call_indirection}(p : ptr<${t.params.address_space}, T>) {
+ output = *p;
+}
+
+${call_chain}
+
+${main}
+`;
+
+ const values = valuesForType(t.params.type);
+
+ run(t, wgsl, t.params.address_space === 'uniform' ? 'uniform' : 'storage', values, values);
+ });
+
+g.test('read_ptr_to_member')
+ .desc('Test a pointer parameter to a member of a structure can be read by a callee function')
+ .params(u =>
+ u.combine('address_space', ['function', 'private', 'workgroup', 'storage', 'uniform'] as const)
+ )
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+
+ const main: string = {
+ function: `
+@compute @workgroup_size(1)
+fn main() {
+ var v : S = input;
+ output = f0(&v);
+}
+`,
+ private: `
+var<private> P : S;
+@compute @workgroup_size(1)
+fn main() {
+ P = input;
+ output = f0(&P);
+}
+`,
+ workgroup: `
+var<workgroup> W : S;
+@compute @workgroup_size(1)
+fn main() {
+ W = input;
+ output = f0(&W);
+}
+`,
+ storage: `
+@compute @workgroup_size(1)
+fn main() {
+ output = f0(&input);
+}
+`,
+ uniform: `
+@compute @workgroup_size(1)
+fn main() {
+ output = f0(&input);
+}
+`,
+ }[t.params.address_space];
+
+ const inputVar: string =
+ t.params.address_space === 'uniform'
+ ? `@binding(0) @group(0) var<uniform> input : S;`
+ : `@binding(0) @group(0) var<storage, read> input : S;`;
+
+ const wgsl = `
+struct S {
+ a : vec4i,
+ b : T,
+ c : vec4i,
+}
+
+struct T {
+ a : vec4i,
+ b : vec4i,
+}
+
+
+${inputVar}
+@binding(1) @group(0) var<storage, read_write> output : T;
+
+fn f2(p : ptr<${t.params.address_space}, T>) -> T {
+ return *p;
+}
+
+fn f1(p : ptr<${t.params.address_space}, S>) -> T {
+ return f2(&(*p).b);
+}
+
+fn f0(p : ptr<${t.params.address_space}, S>) -> T {
+ return f1(p);
+}
+
+${main}
+`;
+
+ // prettier-ignore
+ const input = new Uint32Array([
+ /* S.a */ 1, 2, 3, 4,
+ /* S.b.a */ 5, 6, 7, 8,
+ /* S.b.b */ 9, 10, 11, 12,
+ /* S.c */ 13, 14, 15, 16,
+ ]);
+
+ // prettier-ignore
+ const expected = new Uint32Array([
+ /* S.b.a */ 5, 6, 7, 8,
+ /* S.b.b */ 9, 10, 11, 12,
+ ]);
+
+ run(t, wgsl, t.params.address_space === 'uniform' ? 'uniform' : 'storage', input, expected);
+ });
+
+g.test('read_ptr_to_element')
+ .desc('Test a pointer parameter to an element of an array can be read by a callee function')
+ .params(u =>
+ u.combine('address_space', ['function', 'private', 'workgroup', 'storage', 'uniform'] as const)
+ )
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+
+ const main: string = {
+ function: `
+@compute @workgroup_size(1)
+fn main() {
+ var v : T = input;
+ output = f0(&v);
+}
+`,
+ private: `
+var<private> P : T;
+@compute @workgroup_size(1)
+fn main() {
+ P = input;
+ output = f0(&P);
+}
+`,
+ workgroup: `
+var<workgroup> W : T;
+@compute @workgroup_size(1)
+fn main() {
+ W = input;
+ output = f0(&W);
+}
+`,
+ storage: `
+@compute @workgroup_size(1)
+fn main() {
+ output = f0(&input);
+}
+`,
+ uniform: `
+@compute @workgroup_size(1)
+fn main() {
+ output = f0(&input);
+}
+`,
+ }[t.params.address_space];
+
+ const inputVar: string =
+ t.params.address_space === 'uniform'
+ ? `@binding(0) @group(0) var<uniform> input : T;`
+ : `@binding(0) @group(0) var<storage, read> input : T;`;
+
+ const wgsl = `
+alias T3 = vec4i;
+alias T2 = array<T3, 2>;
+alias T1 = array<T2, 3>;
+alias T = array<T1, 2>;
+
+${inputVar}
+@binding(1) @group(0) var<storage, read_write> output : T3;
+
+fn f2(p : ptr<${t.params.address_space}, T2>) -> T3 {
+ return (*p)[1];
+}
+
+fn f1(p : ptr<${t.params.address_space}, T1>) -> T3 {
+ return f2(&(*p)[0]) + f2(&(*p)[2]);
+}
+
+fn f0(p : ptr<${t.params.address_space}, T>) -> T3 {
+ return f1(&(*p)[0]);
+}
+
+${main}
+`;
+
+ // prettier-ignore
+ const input = new Uint32Array([
+ /* [0][0][0] */ 1, 2, 3, 4,
+ /* [0][0][1] */ 5, 6, 7, 8,
+ /* [0][1][0] */ 9, 10, 11, 12,
+ /* [0][1][1] */ 13, 14, 15, 16,
+ /* [0][2][0] */ 17, 18, 19, 20,
+ /* [0][2][1] */ 21, 22, 23, 24,
+ /* [1][0][0] */ 25, 26, 27, 28,
+ /* [1][0][1] */ 29, 30, 31, 32,
+ /* [1][1][0] */ 33, 34, 35, 36,
+ /* [1][1][1] */ 37, 38, 39, 40,
+ /* [1][2][0] */ 41, 42, 43, 44,
+ /* [1][2][1] */ 45, 46, 47, 48,
+ ]);
+ const expected = new Uint32Array([/* [0][0][1] + [0][2][1] */ 5 + 21, 6 + 22, 7 + 23, 8 + 24]);
+
+ run(t, wgsl, t.params.address_space === 'uniform' ? 'uniform' : 'storage', input, expected);
+ });
+
+g.test('write_full_object')
+ .desc('Test a pointer parameter can be written to by a callee function')
+ .params(u =>
+ u
+ .combine('address_space', ['function', 'private', 'workgroup', 'storage'] as const)
+ .combine('call_indirection', [0, 1, 2] as const)
+ .combine('type', ['vec4i', 'array', 'struct'] as const)
+ )
+ .fn(t => {
+ switch (t.params.address_space) {
+ case 'workgroup':
+ case 'storage':
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+ }
+
+ const ptr =
+ t.params.address_space === 'storage'
+ ? `ptr<storage, T, read_write>`
+ : `ptr<${t.params.address_space}, T>`;
+
+ const main: string = {
+ function: `
+@compute @workgroup_size(1)
+fn main() {
+ var F : T;
+ f0(&F);
+ output = F;
+}
+`,
+ private: `
+var<private> P : T;
+@compute @workgroup_size(1)
+fn main() {
+ f0(&P);
+ output = P;
+}
+`,
+ workgroup: `
+var<workgroup> W : T;
+@compute @workgroup_size(1)
+fn main() {
+ f0(&W);
+ output = W;
+}
+`,
+ storage: `
+@compute @workgroup_size(1)
+fn main() {
+ f0(&output);
+}
+`,
+ }[t.params.address_space];
+
+ let call_chain = '';
+ for (let i = 0; i < t.params.call_indirection; i++) {
+ call_chain += `
+fn f${i}(p : ${ptr}) {
+ f${i + 1}(p);
+}
+`;
+ }
+
+ const wgsl = `
+${wgslTypeDecl(t.params.type)}
+
+@binding(0) @group(0) var<uniform> input : T;
+@binding(1) @group(0) var<storage, read_write> output : T;
+
+fn f${t.params.call_indirection}(p : ${ptr}) {
+ *p = input;
+}
+
+${call_chain}
+
+${main}
+`;
+
+ const values = valuesForType(t.params.type);
+
+ run(t, wgsl, 'uniform', values, values);
+ });
+
+g.test('write_ptr_to_member')
+ .desc(
+ 'Test a pointer parameter to a member of a structure can be written to by a callee function'
+ )
+ .params(u => u.combine('address_space', ['function', 'private', 'workgroup', 'storage'] as const))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+
+ const main: string = {
+ function: `
+@compute @workgroup_size(1)
+fn main() {
+ var v : S;
+ f0(&v);
+ output = v;
+}
+`,
+ private: `
+var<private> P : S;
+@compute @workgroup_size(1)
+fn main() {
+ f0(&P);
+ output = P;
+}
+`,
+ workgroup: `
+var<workgroup> W : S;
+@compute @workgroup_size(1)
+fn main() {
+ f0(&W);
+ output = W;
+}
+`,
+ storage: `
+@compute @workgroup_size(1)
+fn main() {
+ f1(&output);
+}
+`,
+ }[t.params.address_space];
+
+ const ptr = (ty: string) =>
+ t.params.address_space === 'storage'
+ ? `ptr<storage, ${ty}, read_write>`
+ : `ptr<${t.params.address_space}, ${ty}>`;
+
+ const wgsl = `
+struct S {
+ a : vec4i,
+ b : T,
+ c : vec4i,
+}
+
+struct T {
+ a : vec4i,
+ b : vec4i,
+}
+
+
+@binding(0) @group(0) var<storage> input : T;
+@binding(1) @group(0) var<storage, read_write> output : S;
+
+fn f2(p : ${ptr('T')}) {
+ *p = input;
+}
+
+fn f1(p : ${ptr('S')}) {
+ f2(&(*p).b);
+}
+
+fn f0(p : ${ptr('S')}) {
+ f1(p);
+}
+
+${main}
+`;
+
+ // prettier-ignore
+ const input = new Uint32Array([
+ /* S.b.a */ 5, 6, 7, 8,
+ /* S.b.b */ 9, 10, 11, 12,
+ ]);
+
+ // prettier-ignore
+ const expected = new Uint32Array([
+ /* S.a */ 0, 0, 0, 0,
+ /* S.b.a */ 5, 6, 7, 8,
+ /* S.b.b */ 9, 10, 11, 12,
+ /* S.c */ 0, 0, 0, 0,
+ ]);
+
+ run(t, wgsl, 'storage', input, expected);
+ });
+
+g.test('write_ptr_to_element')
+ .desc('Test a pointer parameter to an element of an array can be written to by a callee function')
+ .params(u => u.combine('address_space', ['function', 'private', 'workgroup', 'storage'] as const))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+
+ const main: string = {
+ function: `
+@compute @workgroup_size(1)
+fn main() {
+ var v : T;
+ f0(&v);
+ output = v;
+}
+`,
+ private: `
+var<private> P : T;
+@compute @workgroup_size(1)
+fn main() {
+ f0(&P);
+ output = P;
+}
+`,
+ workgroup: `
+var<workgroup> W : T;
+@compute @workgroup_size(1)
+fn main() {
+ f0(&W);
+ output = W;
+}
+`,
+ storage: `
+@compute @workgroup_size(1)
+fn main() {
+ f0(&output);
+}
+`,
+ }[t.params.address_space];
+
+ const ptr = (ty: string) =>
+ t.params.address_space === 'storage'
+ ? `ptr<storage, ${ty}, read_write>`
+ : `ptr<${t.params.address_space}, ${ty}>`;
+
+ const wgsl = `
+alias T3 = vec4i;
+alias T2 = array<T3, 2>;
+alias T1 = array<T2, 3>;
+alias T = array<T1, 2>;
+
+@binding(0) @group(0) var<storage, read> input : T3;
+@binding(1) @group(0) var<storage, read_write> output : T;
+
+fn f2(p : ${ptr('T2')}) {
+ (*p)[1] = input;
+}
+
+fn f1(p : ${ptr('T1')}) {
+ f2(&(*p)[0]);
+ f2(&(*p)[2]);
+}
+
+fn f0(p : ${ptr('T')}) {
+ f1(&(*p)[0]);
+}
+
+${main}
+`;
+
+ const input = new Uint32Array([1, 2, 3, 4]);
+
+ // prettier-ignore
+ const expected = new Uint32Array([
+ /* [0][0][0] */ 0, 0, 0, 0,
+ /* [0][0][1] */ 1, 2, 3, 4,
+ /* [0][1][0] */ 0, 0, 0, 0,
+ /* [0][1][1] */ 0, 0, 0, 0,
+ /* [0][2][0] */ 0, 0, 0, 0,
+ /* [0][2][1] */ 1, 2, 3, 4,
+ /* [1][0][0] */ 0, 0, 0, 0,
+ /* [1][0][1] */ 0, 0, 0, 0,
+ /* [1][1][0] */ 0, 0, 0, 0,
+ /* [1][1][1] */ 0, 0, 0, 0,
+ /* [1][2][0] */ 0, 0, 0, 0,
+ /* [1][2][1] */ 0, 0, 0, 0,
+ ]);
+
+ run(t, wgsl, 'storage', input, expected);
+ });
+
+g.test('atomic_ptr_to_element')
+ .desc(
+ 'Test a pointer parameter to an atomic<i32> of an array can be read from and written to by a callee function'
+ )
+ .params(u => u.combine('address_space', ['workgroup', 'storage'] as const))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+
+ const main: string = {
+ workgroup: `
+var<workgroup> W_input : T;
+var<workgroup> W_output : T;
+@compute @workgroup_size(1)
+fn main() {
+ // Copy input -> W_input
+ for (var i = 0; i < 2; i++) {
+ for (var j = 0; j < 3; j++) {
+ for (var k = 0; k < 2; k++) {
+ atomicStore(&W_input[k][j][i], atomicLoad(&input[k][j][i]));
+ }
+ }
+ }
+
+ f0(&W_input, &W_output);
+
+ // Copy W_output -> output
+ for (var i = 0; i < 2; i++) {
+ for (var j = 0; j < 3; j++) {
+ for (var k = 0; k < 2; k++) {
+ atomicStore(&output[k][j][i], atomicLoad(&W_output[k][j][i]));
+ }
+ }
+ }
+}
+`,
+ storage: `
+@compute @workgroup_size(1)
+fn main() {
+ f0(&input, &output);
+}
+`,
+ }[t.params.address_space];
+
+ const ptr = (ty: string) =>
+ t.params.address_space === 'storage'
+ ? `ptr<storage, ${ty}, read_write>`
+ : `ptr<${t.params.address_space}, ${ty}>`;
+
+ const wgsl = `
+alias T3 = atomic<i32>;
+alias T2 = array<T3, 2>;
+alias T1 = array<T2, 3>;
+alias T = array<T1, 2>;
+
+@binding(0) @group(0) var<storage, read_write> input : T;
+@binding(1) @group(0) var<storage, read_write> output : T;
+
+fn f2(in : ${ptr('T2')}, out : ${ptr('T2')}) {
+ let v = atomicLoad(&(*in)[0]);
+ atomicStore(&(*out)[1], v);
+}
+
+fn f1(in : ${ptr('T1')}, out : ${ptr('T1')}) {
+ f2(&(*in)[0], &(*out)[1]);
+ f2(&(*in)[2], &(*out)[0]);
+}
+
+fn f0(in : ${ptr('T')}, out : ${ptr('T')}) {
+ f1(&(*in)[1], &(*out)[0]);
+}
+
+${main}
+`;
+
+ // prettier-ignore
+ const input = new Uint32Array([
+ /* [0][0][0] */ 1,
+ /* [0][0][1] */ 2,
+ /* [0][1][0] */ 3,
+ /* [0][1][1] */ 4,
+ /* [0][2][0] */ 5,
+ /* [0][2][1] */ 6,
+ /* [1][0][0] */ 7, // -> [0][1][1]
+ /* [1][0][1] */ 8,
+ /* [1][1][0] */ 9,
+ /* [1][1][1] */ 10,
+ /* [1][2][0] */ 11, // -> [0][0][1]
+ /* [1][2][1] */ 12,
+ ]);
+
+ // prettier-ignore
+ const expected = new Uint32Array([
+ /* [0][0][0] */ 0,
+ /* [0][0][1] */ 11,
+ /* [0][1][0] */ 0,
+ /* [0][1][1] */ 7,
+ /* [0][2][0] */ 0,
+ /* [0][2][1] */ 0,
+ /* [1][0][0] */ 0,
+ /* [1][0][1] */ 0,
+ /* [1][1][0] */ 0,
+ /* [1][1][1] */ 0,
+ /* [1][2][0] */ 0,
+ /* [1][2][1] */ 0,
+ ]);
+
+ run(t, wgsl, 'storage', input, expected);
+ });
+
+g.test('array_length')
+ .desc(
+ 'Test a pointer parameter to a runtime sized array can be used by arrayLength() in a callee function'
+ )
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+
+ const wgsl = `
+@binding(0) @group(0) var<storage, read> arr : array<u32>;
+@binding(1) @group(0) var<storage, read_write> output : u32;
+
+fn f2(p : ptr<storage, array<u32>, read>) -> u32 {
+ return arrayLength(p);
+}
+
+fn f1(p : ptr<storage, array<u32>, read>) -> u32 {
+ return f2(p);
+}
+
+fn f0(p : ptr<storage, array<u32>, read>) -> u32 {
+ return f1(p);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ output = f0(&arr);
+}
+`;
+
+ const input = new Uint32Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
+ const expected = new Uint32Array([12]);
+
+ run(t, wgsl, 'storage', input, expected);
+ });
+
+g.test('mixed_ptr_parameters')
+ .desc('Test that functions can accept multiple, mixed pointer parameters')
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+
+ const wgsl = `
+@binding(0) @group(0) var<uniform> input : array<vec4i, 4>;
+@binding(1) @group(0) var<storage, read_write> output : array<vec4i, 4>;
+
+fn sum(f : ptr<function, i32>,
+ w : ptr<workgroup, atomic<i32>>,
+ p : ptr<private, i32>,
+ u : ptr<uniform, vec4i>) -> vec4i {
+
+ return vec4(*f + atomicLoad(w) + *p) + *u;
+}
+
+struct S {
+ i : i32,
+}
+
+var<private> P0 = S(0);
+var<private> P1 = S(10);
+var<private> P2 = 20;
+var<private> P3 = 30;
+
+struct T {
+ i : atomic<i32>,
+}
+
+var<workgroup> W0 : T;
+var<workgroup> W1 : atomic<i32>;
+var<workgroup> W2 : T;
+var<workgroup> W3 : atomic<i32>;
+
+@compute @workgroup_size(1)
+fn main() {
+ atomicStore(&W0.i, 0);
+ atomicStore(&W1, 100);
+ atomicStore(&W2.i, 200);
+ atomicStore(&W3, 300);
+
+ var F = array(0, 1000, 2000, 3000);
+
+ output[0] = sum(&F[2], &W3, &P1.i, &input[0]); // vec4(2310) + vec4(1, 2, 3, 4)
+ output[1] = sum(&F[1], &W2.i, &P0.i, &input[1]); // vec4(1200) + vec4(4, 3, 2, 1)
+ output[2] = sum(&F[3], &W0.i, &P3, &input[2]); // vec4(3030) + vec4(2, 4, 1, 3)
+ output[3] = sum(&F[2], &W1, &P2, &input[3]); // vec4(2120) + vec4(4, 1, 2, 3)
+}
+`;
+
+ // prettier-ignore
+ const input = new Uint32Array([
+ /* [0] */ 1, 2, 3, 4,
+ /* [1] */ 4, 3, 2, 1,
+ /* [2] */ 2, 4, 1, 3,
+ /* [3] */ 4, 1, 2, 3,
+ ]);
+
+ // prettier-ignore
+ const expected = new Uint32Array([
+ /* [0] */ 2311, 2312, 2313, 2314,
+ /* [1] */ 1204, 1203, 1202, 1201,
+ /* [2] */ 3032, 3034, 3031, 3033,
+ /* [3] */ 2124, 2121, 2122, 2123,
+ ]);
+
+ run(t, wgsl, 'uniform', input, expected);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/case.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/case.ts
new file mode 100644
index 0000000000..d837b1c32f
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/case.ts
@@ -0,0 +1,440 @@
+import { crc32 } from '../../../../common/util/crc32.js';
+import { ROArrayArray } from '../../../../common/util/types.js';
+import { assert } from '../../../../common/util/util.js';
+import {
+ abstractInt,
+ i32,
+ ScalarBuilder,
+ u32,
+ Value,
+ VectorValue,
+} from '../../../util/conversion.js';
+import {
+ cartesianProduct,
+ QuantizeFunc,
+ quantizeToI32,
+ quantizeToI64,
+ quantizeToU32,
+} from '../../../util/math.js';
+
+import { Expectation } from './expectation.js';
+
+function notUndefined<T>(value: T | undefined): value is T {
+ return value !== undefined;
+}
+
+/** Case is a single expression test case. */
+export type Case = {
+ // The input value(s)
+ input: Value | ReadonlyArray<Value>;
+ // The expected result, or function to check the result
+ expected: Expectation;
+};
+
+/**
+ * Filters a given set of Cases down to a target number of cases by
+ * randomly selecting which Cases to return.
+ *
+ * The selection algorithm is deterministic and stable for a case's
+ * inputs.
+ *
+ * This means that if a specific case is selected is not affected by the
+ * presence of other cases in the list, so in theory it is possible to create a
+ * pathological set of cases such that all or not of the cases are selected
+ * in spite of the target number.
+ *
+ * This is a trade-off from guaranteeing stability of the selected cases over
+ * small changes, so the target number of cases is more of a suggestion. It is
+ * still guaranteed that if you set n0 < n1, then the invocation with n0 will
+ * return at most the number of cases that n1 does, it just isn't guaranteed to
+ * be less.
+ *
+ * @param dis is a string provided for additional hashing information to avoid
+ * systemic bias in the selection process across different test
+ * suites. Specifically every Case with the same input values being
+ * included or skipped regardless of the operation that they are
+ * testing. This string should be something like the name of the case
+ * cache the values are for or the operation under test.
+ * @param n number of cases targeted be returned. Expected to be a positive
+ * integer. If equal or greater than the number of cases, then all the
+ * cases are returned. 0 is not allowed, since it is likely a
+ * programming error, because if the caller intentionally wants 0
+ * items, they can just use [].
+ * @param cases list of Cases to be selected from.
+ */
+export function selectNCases(dis: string, n: number, cases: Case[]): Case[] {
+ assert(n > 0 && Math.round(n) === n, `n ${n} is expected to be a positive integer`);
+ const count = cases.length;
+ if (n >= count) {
+ return cases;
+ }
+ const dis_crc32 = crc32(dis);
+ return cases.filter(
+ c => Math.trunc((n / count) * 0xffff_ffff) > (crc32(c.input.toString()) ^ dis_crc32) >>> 0
+ );
+}
+
+/**
+ * A function that performs a binary operation on x and y, and returns the
+ * expected result.
+ */
+export interface BinaryOp<T> {
+ (x: T, y: T): T | undefined;
+}
+
+/**
+ * A function that performs a vector-vector operation on x and y, and returns the
+ * expected result.
+ */
+export interface VectorVectorToScalarOp<T> {
+ (x: T[], y: T[]): T | undefined;
+}
+
+/**
+ * @returns a Case for the input params with op applied
+ * @param scalar scalar param
+ * @param vector vector param (2, 3, or 4 elements)
+ * @param op the op to apply to scalar and vector
+ * @param quantize function to quantize all values in vectors and scalars
+ * @param scalarize function to convert numbers to Scalars
+ */
+function makeScalarVectorBinaryToVectorCase<T>(
+ scalar: T,
+ vector: readonly T[],
+ op: BinaryOp<T>,
+ quantize: QuantizeFunc<T>,
+ scalarize: ScalarBuilder<T>
+): Case | undefined {
+ scalar = quantize(scalar);
+ vector = vector.map(quantize);
+ const result = vector.map(v => op(scalar, v));
+ if (result.includes(undefined)) {
+ return undefined;
+ }
+ return {
+ input: [scalarize(scalar), new VectorValue(vector.map(scalarize))],
+ expected: new VectorValue(result.filter(notUndefined).map(scalarize)),
+ };
+}
+
+/**
+ * @returns array of Case for the input params with op applied
+ * @param scalars array of scalar params
+ * @param vectors array of vector params (2, 3, or 4 elements)
+ * @param op the op to apply to each pair of scalar and vector
+ * @param quantize function to quantize all values in vectors and scalars
+ * @param scalarize function to convert numbers to Scalars
+ */
+function generateScalarVectorBinaryToVectorCases<T>(
+ scalars: readonly T[],
+ vectors: ROArrayArray<T>,
+ op: BinaryOp<T>,
+ quantize: QuantizeFunc<T>,
+ scalarize: ScalarBuilder<T>
+): Case[] {
+ return scalars.flatMap(s => {
+ return vectors
+ .map(v => {
+ return makeScalarVectorBinaryToVectorCase(s, v, op, quantize, scalarize);
+ })
+ .filter(notUndefined);
+ });
+}
+
+/**
+ * @returns a Case for the input params with op applied
+ * @param vector vector param (2, 3, or 4 elements)
+ * @param scalar scalar param
+ * @param op the op to apply to vector and scalar
+ * @param quantize function to quantize all values in vectors and scalars
+ * @param scalarize function to convert numbers to Scalars
+ */
+function makeVectorScalarBinaryToVectorCase<T>(
+ vector: readonly T[],
+ scalar: T,
+ op: BinaryOp<T>,
+ quantize: QuantizeFunc<T>,
+ scalarize: ScalarBuilder<T>
+): Case | undefined {
+ vector = vector.map(quantize);
+ scalar = quantize(scalar);
+ const result = vector.map(v => op(v, scalar));
+ if (result.includes(undefined)) {
+ return undefined;
+ }
+ return {
+ input: [new VectorValue(vector.map(scalarize)), scalarize(scalar)],
+ expected: new VectorValue(result.filter(notUndefined).map(scalarize)),
+ };
+}
+
+/**
+ * @returns array of Case for the input params with op applied
+ * @param vectors array of vector params (2, 3, or 4 elements)
+ * @param scalars array of scalar params
+ * @param op the op to apply to each pair of vector and scalar
+ * @param quantize function to quantize all values in vectors and scalars
+ * @param scalarize function to convert numbers to Scalars
+ */
+function generateVectorScalarBinaryToVectorCases<T>(
+ vectors: ROArrayArray<T>,
+ scalars: readonly T[],
+ op: BinaryOp<T>,
+ quantize: QuantizeFunc<T>,
+ scalarize: ScalarBuilder<T>
+): Case[] {
+ return scalars.flatMap(s => {
+ return vectors
+ .map(v => {
+ return makeVectorScalarBinaryToVectorCase(v, s, op, quantize, scalarize);
+ })
+ .filter(notUndefined);
+ });
+}
+
+/**
+ * @returns array of Case for the input params with op applied
+ * @param scalars array of scalar params
+ * @param vectors array of vector params (2, 3, or 4 elements)
+ * @param op he op to apply to each pair of scalar and vector
+ */
+export function generateU32VectorBinaryToVectorCases(
+ scalars: readonly number[],
+ vectors: ROArrayArray<number>,
+ op: BinaryOp<number>
+): Case[] {
+ return generateScalarVectorBinaryToVectorCases(scalars, vectors, op, quantizeToU32, u32);
+}
+
+/**
+ * @returns array of Case for the input params with op applied
+ * @param vectors array of vector params (2, 3, or 4 elements)
+ * @param scalars array of scalar params
+ * @param op he op to apply to each pair of vector and scalar
+ */
+export function generateVectorU32BinaryToVectorCases(
+ vectors: ROArrayArray<number>,
+ scalars: readonly number[],
+ op: BinaryOp<number>
+): Case[] {
+ return generateVectorScalarBinaryToVectorCases(vectors, scalars, op, quantizeToU32, u32);
+}
+
+/**
+ * @returns array of Case for the input params with op applied
+ * @param scalars array of scalar params
+ * @param vectors array of vector params (2, 3, or 4 elements)
+ * @param op he op to apply to each pair of scalar and vector
+ */
+export function generateI32VectorBinaryToVectorCases(
+ scalars: readonly number[],
+ vectors: ROArrayArray<number>,
+ op: BinaryOp<number>
+): Case[] {
+ return generateScalarVectorBinaryToVectorCases(scalars, vectors, op, quantizeToI32, i32);
+}
+
+/**
+ * @returns array of Case for the input params with op applied
+ * @param vectors array of vector params (2, 3, or 4 elements)
+ * @param scalars array of scalar params
+ * @param op he op to apply to each pair of vector and scalar
+ */
+export function generateVectorI32BinaryToVectorCases(
+ vectors: ROArrayArray<number>,
+ scalars: readonly number[],
+ op: BinaryOp<number>
+): Case[] {
+ return generateVectorScalarBinaryToVectorCases(vectors, scalars, op, quantizeToI32, i32);
+}
+
+/**
+ * @returns array of Case for the input params with op applied
+ * @param scalars array of scalar params
+ * @param vectors array of vector params (2, 3, or 4 elements)
+ * @param op he op to apply to each pair of scalar and vector
+ */
+export function generateI64VectorBinaryToVectorCases(
+ scalars: readonly bigint[],
+ vectors: ROArrayArray<bigint>,
+ op: BinaryOp<bigint>
+): Case[] {
+ return generateScalarVectorBinaryToVectorCases(scalars, vectors, op, quantizeToI64, abstractInt);
+}
+
+/**
+ * @returns array of Case for the input params with op applied
+ * @param vectors array of vector params (2, 3, or 4 elements)
+ * @param scalars array of scalar params
+ * @param op he op to apply to each pair of vector and scalar
+ */
+export function generateVectorI64BinaryToVectorCases(
+ vectors: ROArrayArray<bigint>,
+ scalars: readonly bigint[],
+ op: BinaryOp<bigint>
+): Case[] {
+ return generateVectorScalarBinaryToVectorCases(vectors, scalars, op, quantizeToI64, abstractInt);
+}
+
+/**
+ * @returns array of Case for the input params with op applied
+ * @param param0s array of inputs to try for the first param
+ * @param param1s array of inputs to try for the second param
+ * @param op callback called on each pair of inputs to produce each case
+ * @param quantize function to quantize all values
+ * @param scalarize function to convert numbers to Scalars
+ */
+function generateScalarBinaryToScalarCases<T>(
+ param0s: readonly T[],
+ param1s: readonly T[],
+ op: BinaryOp<T>,
+ quantize: QuantizeFunc<T>,
+ scalarize: ScalarBuilder<T>
+): Case[] {
+ param0s = param0s.map(quantize);
+ param1s = param1s.map(quantize);
+ return cartesianProduct(param0s, param1s).reduce((cases, e) => {
+ const expected = op(e[0], e[1]);
+ if (expected !== undefined) {
+ cases.push({ input: [scalarize(e[0]), scalarize(e[1])], expected: scalarize(expected) });
+ }
+ return cases;
+ }, new Array<Case>());
+}
+
+/**
+ * @returns an array of Cases for operations over a range of inputs
+ * @param param0s array of inputs to try for the first param
+ * @param param1s array of inputs to try for the second param
+ * @param op callback called on each pair of inputs to produce each case
+ */
+export function generateBinaryToI32Cases(
+ param0s: readonly number[],
+ param1s: readonly number[],
+ op: BinaryOp<number>
+) {
+ return generateScalarBinaryToScalarCases(param0s, param1s, op, quantizeToI32, i32);
+}
+
+/**
+ * @returns an array of Cases for operations over a range of inputs
+ * @param param0s array of inputs to try for the first param
+ * @param param1s array of inputs to try for the second param
+ * @param op callback called on each pair of inputs to produce each case
+ */
+export function generateBinaryToU32Cases(
+ param0s: readonly number[],
+ param1s: readonly number[],
+ op: BinaryOp<number>
+) {
+ return generateScalarBinaryToScalarCases(param0s, param1s, op, quantizeToU32, u32);
+}
+
+/**
+ * @returns an array of Cases for operations over a range of inputs
+ * @param param0s array of inputs to try for the first param
+ * @param param1s array of inputs to try for the second param
+ * @param op callback called on each pair of inputs to produce each case
+ */
+export function generateBinaryToI64Cases(
+ param0s: readonly bigint[],
+ param1s: readonly bigint[],
+ op: BinaryOp<bigint>
+) {
+ return generateScalarBinaryToScalarCases(param0s, param1s, op, quantizeToI64, abstractInt);
+}
+
+/**
+ * @returns a Case for the input params with op applied
+ * @param param0 vector param (2, 3, or 4 elements) for the first param
+ * @param param1 vector param (2, 3, or 4 elements) for the second param
+ * @param op the op to apply to each pair of vectors
+ * @param quantize function to quantize all values in vectors and scalars
+ * @param scalarize function to convert numbers to Scalars
+ */
+function makeVectorVectorToScalarCase<T>(
+ param0: readonly T[],
+ param1: readonly T[],
+ op: VectorVectorToScalarOp<T>,
+ quantize: QuantizeFunc<T>,
+ scalarize: ScalarBuilder<T>
+): Case | undefined {
+ const param0_quantized = param0.map(quantize);
+ const param1_quantized = param1.map(quantize);
+ const result = op(param0_quantized, param1_quantized);
+ if (result === undefined) return undefined;
+
+ return {
+ input: [
+ new VectorValue(param0_quantized.map(scalarize)),
+ new VectorValue(param1_quantized.map(scalarize)),
+ ],
+ expected: scalarize(result),
+ };
+}
+
+/**
+ * @returns array of Case for the input params with op applied
+ * @param param0s array of vector params (2, 3, or 4 elements) for the first param
+ * @param param1s array of vector params (2, 3, or 4 elements) for the second param
+ * @param op the op to apply to each pair of vectors
+ * @param quantize function to quantize all values in vectors and scalars
+ * @param scalarize function to convert numbers to Scalars
+ */
+function generateVectorVectorToScalarCases<T>(
+ param0s: ROArrayArray<T>,
+ param1s: ROArrayArray<T>,
+ op: VectorVectorToScalarOp<T>,
+ quantize: QuantizeFunc<T>,
+ scalarize: ScalarBuilder<T>
+): Case[] {
+ return param0s.flatMap(param0 => {
+ return param1s
+ .map(param1 => {
+ return makeVectorVectorToScalarCase(param0, param1, op, quantize, scalarize);
+ })
+ .filter(notUndefined);
+ });
+}
+
+/**
+ * @returns array of Case for the input params with op applied
+ * @param param0s array of vector params (2, 3, or 4 elements) for the first param
+ * @param param1s array of vector params (2, 3, or 4 elements) for the second param
+ * @param op the op to apply to each pair of vectors
+ */
+export function generateVectorVectorToI32Cases(
+ param0s: ROArrayArray<number>,
+ param1s: ROArrayArray<number>,
+ op: VectorVectorToScalarOp<number>
+): Case[] {
+ return generateVectorVectorToScalarCases(param0s, param1s, op, quantizeToI32, i32);
+}
+
+/**
+ * @returns array of Case for the input params with op applied
+ * @param param0s array of vector params (2, 3, or 4 elements) for the first param
+ * @param param1s array of vector params (2, 3, or 4 elements) for the second param
+ * @param op the op to apply to each pair of vectors
+ */
+export function generateVectorVectorToU32Cases(
+ param0s: ROArrayArray<number>,
+ param1s: ROArrayArray<number>,
+ op: VectorVectorToScalarOp<number>
+): Case[] {
+ return generateVectorVectorToScalarCases(param0s, param1s, op, quantizeToU32, u32);
+}
+
+/**
+ * @returns array of Case for the input params with op applied
+ * @param param0s array of vector params (2, 3, or 4 elements) for the first param
+ * @param param1s array of vector params (2, 3, or 4 elements) for the second param
+ * @param op the op to apply to each pair of vectors
+ */
+export function generateVectorVectorToI64Cases(
+ param0s: ROArrayArray<bigint>,
+ param1s: ROArrayArray<bigint>,
+ op: VectorVectorToScalarOp<bigint>
+): Case[] {
+ return generateVectorVectorToScalarCases(param0s, param1s, op, quantizeToI64, abstractInt);
+}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/case_cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/case_cache.ts
index ff82792d64..345183c5d5 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/case_cache.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/case_cache.ts
@@ -3,21 +3,22 @@ import { unreachable } from '../../../../common/util/util.js';
import BinaryStream from '../../../util/binary_stream.js';
import { deserializeComparator, serializeComparator } from '../../../util/compare.js';
import {
- Scalar,
- Vector,
- serializeValue,
- deserializeValue,
- Matrix,
+ MatrixValue,
Value,
+ VectorValue,
+ deserializeValue,
+ isScalarValue,
+ serializeValue,
} from '../../../util/conversion.js';
import {
- deserializeFPInterval,
FPInterval,
+ deserializeFPInterval,
serializeFPInterval,
} from '../../../util/floating_point.js';
import { flatten2DArray, unflatten2DArray } from '../../../util/math.js';
-import { Case, CaseList, Expectation, isComparator } from './expression.js';
+import { Case } from './case.js';
+import { Expectation, isComparator } from './expectation.js';
enum SerializedExpectationKind {
Value,
@@ -30,7 +31,7 @@ enum SerializedExpectationKind {
/** serializeExpectation() serializes an Expectation to a BinaryStream */
export function serializeExpectation(s: BinaryStream, e: Expectation) {
- if (e instanceof Scalar || e instanceof Vector || e instanceof Matrix) {
+ if (isScalarValue(e) || e instanceof VectorValue || e instanceof MatrixValue) {
s.writeU8(SerializedExpectationKind.Value);
serializeValue(s, e);
return;
@@ -122,27 +123,27 @@ export function deserializeCase(s: BinaryStream): Case {
return { input, expected };
}
-/** CaseListBuilder is a function that builds a CaseList */
-export type CaseListBuilder = () => CaseList;
+/** CaseListBuilder is a function that builds a list of cases, Case[] */
+export type CaseListBuilder = () => Case[];
/**
- * CaseCache is a cache of CaseList.
+ * CaseCache is a cache of Case[].
* CaseCache implements the Cacheable interface, so the cases can be pre-built
* and stored in the data cache, reducing computation costs at CTS runtime.
*/
-export class CaseCache implements Cacheable<Record<string, CaseList>> {
+export class CaseCache implements Cacheable<Record<string, Case[]>> {
/**
* Constructor
* @param name the name of the cache. This must be globally unique.
* @param builders a Record of case-list name to case-list builder.
*/
constructor(name: string, builders: Record<string, CaseListBuilder>) {
- this.path = `webgpu/shader/execution/case-cache/${name}.bin`;
+ this.path = `webgpu/shader/execution/${name}.bin`;
this.builders = builders;
}
/** get() returns the list of cases with the given name */
- public async get(name: string): Promise<CaseList> {
+ public async get(name: string): Promise<Case[]> {
const data = await dataCache.fetch(this);
return data[name];
}
@@ -151,8 +152,8 @@ export class CaseCache implements Cacheable<Record<string, CaseList>> {
* build() implements the Cacheable.build interface.
* @returns the data.
*/
- build(): Promise<Record<string, CaseList>> {
- const built: Record<string, CaseList> = {};
+ build(): Promise<Record<string, Case[]>> {
+ const built: Record<string, Case[]> = {};
for (const name in this.builders) {
const cases = this.builders[name]();
built[name] = cases;
@@ -164,7 +165,7 @@ export class CaseCache implements Cacheable<Record<string, CaseList>> {
* serialize() implements the Cacheable.serialize interface.
* @returns the serialized data.
*/
- serialize(data: Record<string, CaseList>): Uint8Array {
+ serialize(data: Record<string, Case[]>): Uint8Array {
const maxSize = 32 << 20; // 32MB - max size for a file
const stream = new BinaryStream(new ArrayBuffer(maxSize));
stream.writeU32(Object.keys(data).length);
@@ -179,9 +180,9 @@ export class CaseCache implements Cacheable<Record<string, CaseList>> {
* deserialize() implements the Cacheable.deserialize interface.
* @returns the deserialize data.
*/
- deserialize(array: Uint8Array): Record<string, CaseList> {
+ deserialize(array: Uint8Array): Record<string, Case[]> {
const s = new BinaryStream(array.buffer);
- const casesByName: Record<string, CaseList> = {};
+ const casesByName: Record<string, Case[]> = {};
const numRecords = s.readU32();
for (let i = 0; i < numRecords; i++) {
const name = s.readString();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/constructor/non_zero.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/constructor/non_zero.spec.ts
new file mode 100644
index 0000000000..c28d3a8edb
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/constructor/non_zero.spec.ts
@@ -0,0 +1,797 @@
+export const description = `
+Execution Tests for value constructors from components
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../gpu_test.js';
+import {
+ ArrayValue,
+ MatrixType,
+ ScalarKind,
+ Type,
+ Value,
+ VectorType,
+ VectorValue,
+ scalarTypeOf,
+ vec2,
+ vec3,
+} from '../../../../util/conversion.js';
+import { FP } from '../../../../util/floating_point.js';
+import { allInputSources, basicExpressionBuilder, run } from '../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+/** @returns true if 'v' is 'min' or 'max' */
+function isMinOrMax(v: number | 'min' | 'max') {
+ return v === 'min' || v === 'max';
+}
+
+/** A list of concrete types to test for the given abstract-numeric type */
+const kConcreteTypesForAbstractType = {
+ 'abstract-float': ['f32', 'f16'] as const,
+ 'abstract-int': ['f32', 'f16', 'i32', 'u32'] as const,
+ 'vec3<abstract-int>': ['vec3f', 'vec3h', 'vec3i', 'vec3u'] as const,
+ 'vec4<abstract-float>': ['vec4f', 'vec4h'] as const,
+ 'mat2x3<abstract-float>': ['mat2x3f', 'mat2x3h'] as const,
+};
+
+/**
+ * @returns the lowest finite value for 'kind' if 'v' is 'min',
+ * the highest finite value for 'kind' if 'v' is 'max',
+ * otherwise returns 'v'
+ */
+function valueFor(v: number | 'min' | 'max', kind: 'bool' | 'i32' | 'u32' | 'f32' | 'f16') {
+ if (!isMinOrMax(v)) {
+ return v as number;
+ }
+ switch (kind) {
+ case 'bool':
+ return v === 'min' ? 0 : 1;
+ case 'i32':
+ return v === 'min' ? -0x80000000 : 0x7fffffff;
+ case 'u32':
+ return v === 'min' ? 0 : 0xffffffff;
+ case 'f32':
+ return v === 'min' ? FP['f32'].constants().negative.min : FP['f32'].constants().positive.max;
+ case 'f16':
+ return v === 'min' ? FP['f16'].constants().negative.min : FP['f16'].constants().positive.max;
+ }
+}
+
+g.test('scalar_identity')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(`Test that a scalar constructed from a value of the same type produces the expected value`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('type', ['bool', 'i32', 'u32', 'f32', 'f16'] as const)
+ .combine('value', ['min', 'max', 1, 2, 5, 100] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ t.skipIf(t.params.type === 'bool' && !isMinOrMax(t.params.value));
+ })
+ .fn(async t => {
+ const type = Type[t.params.type];
+ const value = valueFor(t.params.value, t.params.type);
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${type}(${ops[0]})`),
+ [type],
+ type,
+ t.params,
+ [{ input: [type.create(value)], expected: type.create(value) }]
+ );
+ });
+
+g.test('vector_identity')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(`Test that a vector constructed from a value of the same type produces the expected value`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('type', ['bool', 'i32', 'u32', 'f32', 'f16'] as const)
+ .combine('width', [2, 3, 4] as const)
+ .combine('infer_type', [false, true] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const elementType = Type[t.params.type];
+ const vectorType = Type.vec(t.params.width, elementType);
+ const elements: number[] = [];
+ const fn = t.params.infer_type ? `vec${t.params.width}` : `${vectorType}`;
+ for (let i = 0; i < t.params.width; i++) {
+ if (t.params.type === 'bool') {
+ elements.push(i & 1);
+ } else {
+ elements.push((i + 1) * 10);
+ }
+ }
+
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${fn}(${ops[0]})`),
+ [vectorType],
+ vectorType,
+ t.params,
+ [
+ {
+ input: vectorType.create(elements),
+ expected: vectorType.create(elements),
+ },
+ ]
+ );
+ });
+
+g.test('concrete_vector_splat')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(`Test that a vector constructed from a single concrete scalar produces the expected value`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('type', ['bool', 'i32', 'u32', 'f32', 'f16'] as const)
+ .combine('value', ['min', 'max', 1, 2, 5, 100] as const)
+ .combine('width', [2, 3, 4] as const)
+ .combine('infer_type', [false, true] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ t.skipIf(t.params.type === 'bool' && !isMinOrMax(t.params.value));
+ })
+ .fn(async t => {
+ const value = valueFor(t.params.value, t.params.type);
+ const elementType = Type[t.params.type];
+ const vectorType = Type.vec(t.params.width, elementType);
+ const fn = t.params.infer_type ? `vec${t.params.width}` : `${vectorType}`;
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${fn}(${ops[0]})`),
+ [elementType],
+ vectorType,
+ t.params,
+ [{ input: [elementType.create(value)], expected: vectorType.create(value) }]
+ );
+ });
+
+g.test('abstract_vector_splat')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(`Test that a vector constructed from a single abstract scalar produces the expected value`)
+ .params(u =>
+ u
+ .combine('abstract_type', ['abstract-int', 'abstract-float'] as const)
+ .expand('concrete_type', t => kConcreteTypesForAbstractType[t.abstract_type])
+ .combine('value', [1, 2, 5, 100] as const)
+ .combine('width', [2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.concrete_type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const suffix = t.params.abstract_type === 'abstract-float' ? '.0' : '';
+ const concreteElementType = Type[t.params.concrete_type];
+ const concreteVectorType = Type.vec(t.params.width, concreteElementType);
+ const fn = `vec${t.params.width}`;
+ await run(
+ t,
+ basicExpressionBuilder(_ => `${fn}(${t.params.value * 0x100000000}${suffix}) / 0x100000000`),
+ [],
+ concreteVectorType,
+ { inputSource: 'const' },
+ [{ input: [], expected: concreteVectorType.create(t.params.value) }]
+ );
+ });
+
+g.test('concrete_vector_elements')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(`Test that a vector constructed from concrete element values produces the expected value`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('type', ['bool', 'i32', 'u32', 'f32', 'f16'] as const)
+ .combine('width', [2, 3, 4] as const)
+ .combine('infer_type', [false, true] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const elementType = Type[t.params.type];
+ const vectorType = Type.vec(t.params.width, elementType);
+ const elements: number[] = [];
+ const fn = t.params.infer_type ? `vec${t.params.width}` : `${vectorType}`;
+ for (let i = 0; i < t.params.width; i++) {
+ if (t.params.type === 'bool') {
+ elements.push(i & 1);
+ } else {
+ elements.push((i + 1) * 10);
+ }
+ }
+
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${fn}(${ops.join(', ')})`),
+ elements.map(e => elementType),
+ vectorType,
+ t.params,
+ [
+ {
+ input: elements.map(v => elementType.create(v)),
+ expected: vectorType.create(elements),
+ },
+ ]
+ );
+ });
+
+g.test('abstract_vector_elements')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(`Test that a vector constructed from abstract element values produces the expected value`)
+ .params(u =>
+ u
+ .combine('abstract_type', ['abstract-int', 'abstract-float'] as const)
+ .expand('concrete_type', t => kConcreteTypesForAbstractType[t.abstract_type])
+ .combine('width', [2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.concrete_type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const suffix = t.params.abstract_type === 'abstract-float' ? '.0' : '';
+ const concreteElementType = Type[t.params.concrete_type];
+ const concreteVectorType = Type.vec(t.params.width, concreteElementType);
+ const fn = `vec${t.params.width}`;
+ const elements: number[] = [];
+ for (let i = 0; i < t.params.width; i++) {
+ elements.push((i + 1) * 10);
+ }
+ await run(
+ t,
+ basicExpressionBuilder(
+ _ => `${fn}(${elements.map(v => `${v * 0x100000000}${suffix}`).join(', ')}) / 0x100000000`
+ ),
+ [],
+ concreteVectorType,
+ { inputSource: 'const' },
+ [{ input: [], expected: concreteVectorType.create(elements) }]
+ );
+ });
+
+const kMixSignatures = [
+ '2s', // [vec2, scalar]
+ 's2', // [scalar, vec2]
+ '2ss', // [vec2, scalar, scalar]
+ 's2s', // [scalar, vec2, scalar]
+ 'ss2', // [scalar, scalar, vec2 ]
+ '22', // [vec2, vec2]
+ '3s', // [vec3, scalar]
+ 's3', // [scalar, vec3]
+] as const;
+
+g.test('concrete_vector_mix')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(
+ `Test that a vector constructed from a mix of concrete element values and sub-vectors produces the expected value`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('type', ['bool', 'i32', 'u32', 'f32', 'f16'] as const)
+ .combine('signature', kMixSignatures)
+ .combine('infer_type', [false, true] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const elementType = Type[t.params.type];
+ let width = 0;
+ const elementValue = (i: number) => (t.params.type === 'bool' ? i & 1 : (i + 1) * 10);
+ const elements: number[] = [];
+ const nextValue = () => {
+ const value = elementValue(width++);
+ elements.push(value);
+ return elementType.create(value);
+ };
+ const args: Value[] = [];
+ for (const c of t.params.signature) {
+ switch (c) {
+ case '2':
+ args.push(vec2(nextValue(), nextValue()));
+ break;
+ case '3':
+ args.push(vec3(nextValue(), nextValue(), nextValue()));
+ break;
+ case 's':
+ args.push(nextValue());
+ break;
+ }
+ }
+ const vectorType = Type.vec(width, elementType);
+ const fn = t.params.infer_type ? `vec${width}` : `${vectorType}`;
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${fn}(${ops.join(', ')})`),
+ args.map(e => e.type),
+ vectorType,
+ t.params,
+ [
+ {
+ input: args,
+ expected: vectorType.create(elements),
+ },
+ ]
+ );
+ });
+
+g.test('abstract_vector_mix')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(
+ `Test that a vector constructed from a mix of abstract element values and sub-vectors produces the expected value`
+ )
+ .params(u =>
+ u
+ .combine('abstract_type', ['abstract-int', 'abstract-float'] as const)
+ .expand('concrete_type', t => kConcreteTypesForAbstractType[t.abstract_type])
+ .combine('signature', kMixSignatures)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.concrete_type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ let width = 0;
+ const suffix = t.params.abstract_type === 'abstract-float' ? '.0' : '';
+ const concreteElementType = Type[t.params.concrete_type];
+ const elementValue = (i: number) => (i + 1) * 10;
+ const elements: number[] = [];
+ const nextValue = () => {
+ const value = elementValue(width++);
+ elements.push(value);
+ return `${value * 0x100000000}${suffix}`;
+ };
+ const args: string[] = [];
+ for (const c of t.params.signature) {
+ switch (c) {
+ case '2':
+ args.push(`vec2(${nextValue()}, ${nextValue()})`);
+ break;
+ case '3':
+ args.push(`vec3(${nextValue()}, ${nextValue()}, ${nextValue()})`);
+ break;
+ case 's':
+ args.push(`${nextValue()}`);
+ break;
+ }
+ }
+ const concreteVectorType = Type.vec(width, concreteElementType);
+ const fn = `vec${width}`;
+ await run(
+ t,
+ basicExpressionBuilder(_ => `${fn}(${args.join(', ')}) / 0x100000000`),
+ [],
+ concreteVectorType,
+ { inputSource: 'const' },
+ [
+ {
+ input: [],
+ expected: concreteVectorType.create(elements),
+ },
+ ]
+ );
+ });
+
+g.test('matrix_identity')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(`Test that a matrix constructed from a value of the same type produces the expected value`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('type', ['f32', 'f16'] as const)
+ .combine('columns', [2, 3, 4] as const)
+ .combine('rows', [2, 3, 4] as const)
+ .combine('infer_type', [false, true] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const elementType = Type[t.params.type];
+ const matrixType = Type.mat(t.params.columns, t.params.rows, elementType);
+ const elements: number[] = [];
+ for (let column = 0; column < t.params.columns; column++) {
+ for (let row = 0; row < t.params.rows; row++) {
+ elements.push((column + 1) * 10 + (row + 1));
+ }
+ }
+ const fn = t.params.infer_type ? `mat${t.params.columns}x${t.params.rows}` : `${matrixType}`;
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${fn}(${ops[0]})`),
+ [matrixType],
+ matrixType,
+ t.params,
+ [
+ {
+ input: matrixType.create(elements),
+ expected: matrixType.create(elements),
+ },
+ ]
+ );
+ });
+
+g.test('concrete_matrix_elements')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(`Test that a matrix constructed from concrete element values produces the expected value`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('type', ['f32', 'f16'] as const)
+ .combine('columns', [2, 3, 4] as const)
+ .combine('rows', [2, 3, 4] as const)
+ .combine('infer_type', [false, true] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const elementType = Type[t.params.type];
+ const matrixType = Type.mat(t.params.columns, t.params.rows, elementType);
+ const elements: number[] = [];
+ for (let column = 0; column < t.params.columns; column++) {
+ for (let row = 0; row < t.params.rows; row++) {
+ elements.push((column + 1) * 10 + (row + 1));
+ }
+ }
+ const fn = t.params.infer_type ? `mat${t.params.columns}x${t.params.rows}` : `${matrixType}`;
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${fn}(${ops.join(', ')})`),
+ elements.map(e => elementType),
+ matrixType,
+ t.params,
+ [
+ {
+ input: elements.map(e => elementType.create(e)),
+ expected: matrixType.create(elements),
+ },
+ ]
+ );
+ });
+
+g.test('abstract_matrix_elements')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(`Test that a matrix constructed from concrete element values produces the expected value`)
+ .params(u =>
+ u
+ .combine('concrete_type', ['f32', 'f16'] as const)
+ .combine('columns', [2, 3, 4] as const)
+ .combine('rows', [2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.concrete_type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const concreteElementType = Type[t.params.concrete_type];
+ const concreteMatrixType = Type.mat(t.params.columns, t.params.rows, concreteElementType);
+ const elements: number[] = [];
+ for (let column = 0; column < t.params.columns; column++) {
+ for (let row = 0; row < t.params.rows; row++) {
+ elements.push((column + 1) * 10 + (row + 1));
+ }
+ }
+ const fn = `mat${t.params.columns}x${t.params.rows}`;
+ await run(
+ t,
+ basicExpressionBuilder(
+ _ => `${fn}(${elements.map(v => `${v * 0x100000000}.0`).join(', ')}) * (1.0 / 0x100000000)`
+ ),
+ [],
+ concreteMatrixType,
+ { inputSource: 'const' },
+ [
+ {
+ input: [],
+ expected: concreteMatrixType.create(elements),
+ },
+ ]
+ );
+ });
+
+g.test('concrete_matrix_column_vectors')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(`Test that a matrix constructed from concrete column vectors produces the expected value`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('type', ['f32', 'f16'] as const)
+ .combine('columns', [2, 3, 4] as const)
+ .combine('rows', [2, 3, 4] as const)
+ .combine('infer_type', [false, true] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const elementType = Type[t.params.type];
+ const columnType = Type.vec(t.params.rows, elementType);
+ const matrixType = Type.mat(t.params.columns, t.params.rows, elementType);
+ const elements: number[] = [];
+ const columnVectors: VectorValue[] = [];
+ for (let column = 0; column < t.params.columns; column++) {
+ const columnElements: number[] = [];
+ for (let row = 0; row < t.params.rows; row++) {
+ const v = (column + 1) * 10 + (row + 1);
+ elements.push(v);
+ columnElements.push(v);
+ }
+ columnVectors.push(columnType.create(columnElements));
+ }
+ const fn = t.params.infer_type ? `mat${t.params.columns}x${t.params.rows}` : `${matrixType}`;
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${fn}(${ops.join(', ')})`),
+ columnVectors.map(v => v.type),
+ matrixType,
+ t.params,
+ [
+ {
+ input: columnVectors,
+ expected: matrixType.create(elements),
+ },
+ ]
+ );
+ });
+
+g.test('abstract_matrix_column_vectors')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(`Test that a matrix constructed from abstract column vectors produces the expected value`)
+ .params(u =>
+ u
+ .combine('concrete_type', ['f32', 'f16'] as const)
+ .combine('columns', [2, 3, 4] as const)
+ .combine('rows', [2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.concrete_type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const concreteElementType = Type[t.params.concrete_type];
+ const concreteMatrixType = Type.mat(t.params.columns, t.params.rows, concreteElementType);
+ const elements: number[] = [];
+ const columnVectors: string[] = [];
+ for (let column = 0; column < t.params.columns; column++) {
+ const columnElements: string[] = [];
+ for (let row = 0; row < t.params.rows; row++) {
+ const v = (column + 1) * 10 + (row + 1);
+ elements.push(v);
+ columnElements.push(`${v * 0x100000000}`);
+ }
+ columnVectors.push(`vec${t.params.rows}(${columnElements.join(', ')})`);
+ }
+ const fn = `mat${t.params.columns}x${t.params.rows}`;
+ await run(
+ t,
+ basicExpressionBuilder(_ => `${fn}(${columnVectors.join(', ')}) * (1.0 / 0x100000000)`),
+ [],
+ concreteMatrixType,
+ { inputSource: 'const' },
+ [
+ {
+ input: [],
+ expected: concreteMatrixType.create(elements),
+ },
+ ]
+ );
+ });
+
+g.test('concrete_array_elements')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(`Test that an array constructed from concrete element values produces the expected value`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('type', ['bool', 'i32', 'u32', 'f32', 'f16', 'vec3f', 'vec4i'] as const)
+ .combine('length', [1, 5, 10] as const)
+ .combine('infer_type', [false, true] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const elementType = Type[t.params.type];
+ const arrayType = Type.array(t.params.length, elementType);
+ const elements: number[] = [];
+ for (let i = 0; i < t.params.length; i++) {
+ elements.push((i + 1) * 10);
+ }
+ const fn = t.params.infer_type ? `array` : `${arrayType}`;
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${fn}(${ops.join(', ')})`),
+ elements.map(e => elementType),
+ arrayType,
+ t.params,
+ [
+ {
+ input: elements.map(e => elementType.create(e)),
+ expected: arrayType.create(elements),
+ },
+ ]
+ );
+ });
+
+g.test('abstract_array_elements')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(`Test that an array constructed from element values produces the expected value`)
+ .params(u =>
+ u
+ .combine('abstract_type', [
+ 'abstract-int',
+ 'abstract-float',
+ 'vec3<abstract-int>',
+ 'vec4<abstract-float>',
+ 'mat2x3<abstract-float>',
+ ] as const)
+ .expand('concrete_type', t => kConcreteTypesForAbstractType[t.abstract_type])
+ .combine('length', [1, 5, 10] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(Type[t.params.concrete_type]).kind === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const count = t.params.length;
+ const concreteElementType = Type[t.params.concrete_type];
+ const concreteArrayType = Type.array(count, concreteElementType);
+ const elements: { args: string; value: Value }[] = [];
+ let i = 0;
+ const nextValue = () => ++i * 10;
+ for (let i = 0; i < count; i++) {
+ switch (t.params.abstract_type) {
+ case 'abstract-int': {
+ const value = nextValue();
+ elements.push({ args: `${value}`, value: concreteElementType.create(value) });
+ break;
+ }
+ case 'abstract-float': {
+ const value = nextValue();
+ elements.push({ args: `${value}.0`, value: concreteElementType.create(value) });
+ break;
+ }
+ case 'vec3<abstract-int>': {
+ const x = nextValue();
+ const y = nextValue();
+ const z = nextValue();
+ elements.push({
+ args: `vec3(${x}, ${y}, ${z})`,
+ value: (concreteElementType as VectorType).create([x, y, z]),
+ });
+ break;
+ }
+ case 'vec4<abstract-float>': {
+ const x = nextValue();
+ const y = nextValue();
+ const z = nextValue();
+ const w = nextValue();
+ elements.push({
+ args: `vec4(${x}.0, ${y}.0, ${z}.0, ${w}.0)`,
+ value: (concreteElementType as VectorType).create([x, y, z, w]),
+ });
+ break;
+ }
+ case 'mat2x3<abstract-float>': {
+ const e00 = nextValue();
+ const e01 = nextValue();
+ const e02 = nextValue();
+ const e10 = nextValue();
+ const e11 = nextValue();
+ const e12 = nextValue();
+ elements.push({
+ args: `mat2x3(vec3(${e00}.0, ${e01}.0, ${e02}.0), vec3(${e10}.0, ${e11}.0, ${e12}.0))`,
+ value: (concreteElementType as MatrixType).create([e00, e01, e02, e10, e11, e12]),
+ });
+ break;
+ }
+ }
+ }
+ const fn = `array`;
+ await run(
+ t,
+ basicExpressionBuilder(_ => `${fn}(${elements.map(e => e.args).join(', ')})`),
+ [],
+ concreteArrayType,
+ { inputSource: 'const' },
+ [
+ {
+ input: [],
+ expected: new ArrayValue(elements.map(e => e.value)),
+ },
+ ]
+ );
+ });
+
+g.test('structure')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(`Test that an structure constructed from element values produces the expected value`)
+ .params(u =>
+ u
+ .combine('member_types', [
+ ['bool'],
+ ['u32'],
+ ['vec3f'],
+ ['i32', 'u32'],
+ ['i32', 'f16', 'vec4i', 'mat3x2f'],
+ ['bool', 'u32', 'f16', 'vec3f', 'vec2i'],
+ ['i32', 'u32', 'f32', 'f16', 'vec3f', 'vec4i'],
+ ] as readonly ScalarKind[][])
+ .combine('nested', [false, true])
+ .beginSubcases()
+ .expand('member_index', t => t.member_types.map((_, i) => i))
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.member_types.includes('f16')) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const memberType = Type[t.params.member_types[t.params.member_index]];
+ const values = t.params.member_types.map((ty, i) => Type[ty].create(i));
+
+ const builder = basicExpressionBuilder(ops =>
+ t.params.nested
+ ? `OuterStruct(10, MyStruct(${ops.join(', ')}), 20).inner.member_${t.params.member_index}`
+ : `MyStruct(${ops.join(', ')}).member_${t.params.member_index}`
+ );
+ await run(
+ t,
+ (parameterTypes, resultType, cases, inputSource) => {
+ return `
+${t.params.member_types.includes('f16') ? 'enable f16;' : ''}
+
+${builder(parameterTypes, resultType, cases, inputSource)}
+
+struct MyStruct {
+${t.params.member_types.map((ty, i) => ` member_${i} : ${ty},`).join('\n')}
+};
+struct OuterStruct {
+ pre : i32,
+ inner : MyStruct,
+ post : i32,
+};
+`;
+ },
+ t.params.member_types.map(ty => Type[ty]),
+ memberType,
+ { inputSource: 'const' },
+ [{ input: values, expected: values[t.params.member_index] }]
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/constructor/zero_value.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/constructor/zero_value.spec.ts
new file mode 100644
index 0000000000..5899021550
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/constructor/zero_value.spec.ts
@@ -0,0 +1,162 @@
+export const description = `
+Execution Tests for zero value constructors
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../gpu_test.js';
+import { ScalarKind, Type } from '../../../../util/conversion.js';
+import { basicExpressionBuilder, run } from '../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('scalar')
+ .specURL('https://www.w3.org/TR/WGSL/#zero-value-builtin-function')
+ .desc(`Test that a zero value scalar constructor produces the expected zero value`)
+ .params(u => u.combine('type', ['bool', 'i32', 'u32', 'f32', 'f16'] as const))
+ .beforeAllSubcases(t => {
+ if (t.params.type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const type = Type[t.params.type];
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${type}()`),
+ [],
+ type,
+ { inputSource: 'const' },
+ [{ input: [], expected: type.create(0) }]
+ );
+ });
+
+g.test('vector')
+ .specURL('https://www.w3.org/TR/WGSL/#zero-value-builtin-function')
+ .desc(`Test that a zero value vector constructor produces the expected zero value`)
+ .params(u =>
+ u
+ .combine('type', ['bool', 'i32', 'u32', 'f32', 'f16'] as const)
+ .combine('width', [2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const type = Type.vec(t.params.width, Type[t.params.type]);
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${type}()`),
+ [],
+ type,
+ { inputSource: 'const' },
+ [{ input: [], expected: type.create(0) }]
+ );
+ });
+
+g.test('matrix')
+ .specURL('https://www.w3.org/TR/WGSL/#zero-value-builtin-function')
+ .desc(`Test that a zero value matrix constructor produces the expected zero value`)
+ .params(u =>
+ u
+ .combine('type', ['f32', 'f16'] as const)
+ .combine('columns', [2, 3, 4] as const)
+ .combine('rows', [2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const type = Type.mat(t.params.columns, t.params.rows, Type[t.params.type]);
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${type}()`),
+ [],
+ type,
+ { inputSource: 'const' },
+ [{ input: [], expected: type.create(0) }]
+ );
+ });
+
+g.test('array')
+ .specURL('https://www.w3.org/TR/WGSL/#zero-value-builtin-function')
+ .desc(`Test that a zero value matrix constructor produces the expected zero value`)
+ .params(u =>
+ u
+ .combine('type', ['bool', 'i32', 'u32', 'f32', 'f16', 'vec3f', 'vec4i'] as const)
+ .combine('length', [1, 5, 10] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const type = Type.array(t.params.length, Type[t.params.type]);
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${type}()`),
+ [],
+ type,
+ { inputSource: 'const' },
+ [{ input: [], expected: type.create(0) }]
+ );
+ });
+
+g.test('structure')
+ .specURL('https://www.w3.org/TR/WGSL/#zero-value-builtin-function')
+ .desc(`Test that an structure constructed from element values produces the expected value`)
+ .params(u =>
+ u
+ .combine('member_types', [
+ ['bool'],
+ ['u32'],
+ ['vec3f'],
+ ['i32', 'u32'],
+ ['i32', 'f16', 'vec4i', 'mat3x2f'],
+ ['bool', 'u32', 'f16', 'vec3f', 'vec2i'],
+ ['i32', 'u32', 'f32', 'f16', 'vec3f', 'vec4i'],
+ ] as readonly ScalarKind[][])
+ .combine('nested', [false, true])
+ .beginSubcases()
+ .expand('member_index', t => t.member_types.map((_, i) => i))
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.member_types.includes('f16')) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const memberType = Type[t.params.member_types[t.params.member_index]];
+ const builder = basicExpressionBuilder(_ =>
+ t.params.nested
+ ? `OuterStruct().inner.member_${t.params.member_index}`
+ : `MyStruct().member_${t.params.member_index}`
+ );
+ await run(
+ t,
+ (parameterTypes, resultType, cases, inputSource) => {
+ return `
+${t.params.member_types.includes('f16') ? 'enable f16;' : ''}
+
+${builder(parameterTypes, resultType, cases, inputSource)}
+
+struct MyStruct {
+${t.params.member_types.map((ty, i) => ` member_${i} : ${ty},`).join('\n')}
+};
+struct OuterStruct {
+ pre : i32,
+ inner : MyStruct,
+ post : i32,
+};
+`;
+ },
+ [],
+ memberType,
+ { inputSource: 'const' },
+ [{ input: [], expected: memberType.create(0) }]
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/expectation.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/expectation.ts
new file mode 100644
index 0000000000..955fa68138
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/expectation.ts
@@ -0,0 +1,38 @@
+import { ROArrayArray } from '../../../../common/util/types.js';
+import { Comparator, compare } from '../../../util/compare.js';
+import {
+ ArrayValue,
+ MatrixValue,
+ Value,
+ VectorValue,
+ isScalarValue,
+} from '../../../util/conversion.js';
+import { FPInterval } from '../../../util/floating_point.js';
+
+export type Expectation =
+ | Value
+ | FPInterval
+ | readonly FPInterval[]
+ | ROArrayArray<FPInterval>
+ | Comparator;
+
+/** @returns if this Expectation actually a Comparator */
+export function isComparator(e: Expectation): e is Comparator {
+ return !(
+ e instanceof FPInterval ||
+ isScalarValue(e) ||
+ e instanceof VectorValue ||
+ e instanceof MatrixValue ||
+ e instanceof ArrayValue ||
+ e instanceof Array
+ );
+}
+
+/** @returns the input if it is already a Comparator, otherwise wraps it in a 'value' comparator */
+export function toComparator(input: Expectation): Comparator {
+ if (isComparator(input)) {
+ return input;
+ }
+
+ return { compare: got => compare(got, input as Value), kind: 'value' };
+}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/expression.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/expression.ts
index f85516f29b..be8f1fd7fd 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/expression.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/expression.ts
@@ -1,70 +1,25 @@
import { globalTestConfig } from '../../../../common/framework/test_config.js';
-import { ROArrayArray } from '../../../../common/util/types.js';
import { assert, objectEquals, unreachable } from '../../../../common/util/util.js';
import { GPUTest } from '../../../gpu_test.js';
-import { compare, Comparator, ComparatorImpl } from '../../../util/compare.js';
+import { Comparator, ComparatorImpl } from '../../../util/compare.js';
import { kValue } from '../../../util/constants.js';
import {
+ MatrixType,
+ ScalarValue,
ScalarType,
- Scalar,
Type,
- TypeVec,
- TypeU32,
- Value,
- Vector,
VectorType,
- u32,
- i32,
- Matrix,
- MatrixType,
- ScalarBuilder,
+ Value,
+ VectorValue,
+ isAbstractType,
scalarTypeOf,
+ ArrayType,
+ elementTypeOf,
} from '../../../util/conversion.js';
-import { FPInterval } from '../../../util/floating_point.js';
-import {
- cartesianProduct,
- QuantizeFunc,
- quantizeToI32,
- quantizeToU32,
-} from '../../../util/math.js';
-
-export type Expectation =
- | Value
- | FPInterval
- | readonly FPInterval[]
- | ROArrayArray<FPInterval>
- | Comparator;
-
-/** @returns if this Expectation actually a Comparator */
-export function isComparator(e: Expectation): e is Comparator {
- return !(
- e instanceof FPInterval ||
- e instanceof Scalar ||
- e instanceof Vector ||
- e instanceof Matrix ||
- e instanceof Array
- );
-}
-
-/** @returns the input if it is already a Comparator, otherwise wraps it in a 'value' comparator */
-export function toComparator(input: Expectation): Comparator {
- if (isComparator(input)) {
- return input;
- }
-
- return { compare: got => compare(got, input as Value), kind: 'value' };
-}
-
-/** Case is a single expression test case. */
-export type Case = {
- // The input value(s)
- input: Value | ReadonlyArray<Value>;
- // The expected result, or function to check the result
- expected: Expectation;
-};
+import { align } from '../../../util/math.js';
-/** CaseList is a list of Cases */
-export type CaseList = Array<Case>;
+import { Case } from './case.js';
+import { toComparator } from './expectation.js';
/** The input value source */
export type InputSource =
@@ -79,6 +34,9 @@ export const allInputSources: InputSource[] = ['const', 'uniform', 'storage_r',
/** Just constant input source */
export const onlyConstInputSource: InputSource[] = ['const'];
+/** All input sources except const */
+export const allButConstInputSource: InputSource[] = ['uniform', 'storage_r', 'storage_rw'];
+
/** Configuration for running a expression test */
export type Config = {
// Where the input values are read from
@@ -92,127 +50,157 @@ export type Config = {
vectorize?: number;
};
-// Helper for returning the stride for a given Type
-function valueStride(ty: Type): number {
- // AbstractFloats are passed out of the shader via a struct of 2x u32s and
- // unpacking containers as arrays
- if (scalarTypeOf(ty).kind === 'abstract-float') {
- if (ty instanceof ScalarType) {
- return 16;
- }
- if (ty instanceof VectorType) {
- if (ty.width === 2) {
- return 16;
- }
- // vec3s have padding to make them the same size as vec4s
- return 32;
- }
- if (ty instanceof MatrixType) {
- switch (ty.cols) {
- case 2:
- switch (ty.rows) {
- case 2:
- return 32;
- case 3:
- return 64;
- case 4:
- return 64;
- }
- break;
- case 3:
- switch (ty.rows) {
- case 2:
- return 48;
- case 3:
- return 96;
- case 4:
- return 96;
- }
- break;
- case 4:
- switch (ty.rows) {
- case 2:
- return 64;
- case 3:
- return 128;
- case 4:
- return 128;
- }
- break;
- }
+/**
+ * @returns the size and alignment in bytes of the type 'ty', taking into
+ * consideration storage alignment constraints and abstract numerics, which are
+ * encoded as a struct of holding two u32s.
+ */
+function sizeAndAlignmentOf(ty: Type, source: InputSource): { size: number; alignment: number } {
+ if (ty instanceof ScalarType) {
+ if (ty.kind === 'abstract-float' || ty.kind === 'abstract-int') {
+ // AbstractFloats and AbstractInts are passed out of the shader via structs of
+ // 2x u32s and unpacking containers as arrays
+ return { size: 8, alignment: 8 };
}
- unreachable(`AbstractFloats have not yet been implemented for ${ty.toString()}`);
+ return { size: ty.size, alignment: ty.alignment };
+ }
+
+ if (ty instanceof VectorType) {
+ const out = sizeAndAlignmentOf(ty.elementType, source);
+ const n = ty.width === 3 ? 4 : ty.width;
+ out.size *= n;
+ out.alignment *= n;
+ return out;
}
if (ty instanceof MatrixType) {
- switch (ty.cols) {
- case 2:
- switch (ty.rows) {
- case 2:
- return 16;
- case 3:
- return 32;
- case 4:
- return 32;
- }
- break;
- case 3:
- switch (ty.rows) {
- case 2:
- return 32;
- case 3:
- return 64;
- case 4:
- return 64;
- }
- break;
- case 4:
- switch (ty.rows) {
- case 2:
- return 32;
- case 3:
- return 64;
- case 4:
- return 64;
- }
- break;
+ const out = sizeAndAlignmentOf(ty.elementType, source);
+ const n = ty.rows === 3 ? 4 : ty.rows;
+ out.size *= n * ty.cols;
+ out.alignment *= n;
+ return out;
+ }
+
+ if (ty instanceof ArrayType) {
+ const out = sizeAndAlignmentOf(ty.elementType, source);
+ if (source === 'uniform') {
+ out.alignment = align(out.alignment, 16);
}
- unreachable(
- `Attempted to get stride length for a matrix with dimensions (${ty.cols}x${ty.rows}), which isn't currently handled`
- );
+ out.size *= ty.count;
+ return out;
+ }
+
+ unreachable(`unhandled type: ${ty}`);
+}
+
+/**
+ * @returns the stride in bytes of the type 'ty', taking into consideration abstract numerics,
+ * which are encoded as a struct of 2 x u32.
+ */
+function strideOf(ty: Type, source: InputSource): number {
+ const sizeAndAlign = sizeAndAlignmentOf(ty, source);
+ return align(sizeAndAlign.size, sizeAndAlign.alignment);
+}
+
+/**
+ * Calls 'callback' with the layout information of each structure member with the types 'members'.
+ * @returns the byte size, stride and alignment of the structure.
+ */
+export function structLayout(
+ members: Type[],
+ source: InputSource,
+ callback?: (m: {
+ index: number;
+ type: Type;
+ size: number;
+ alignment: number;
+ offset: number;
+ }) => void
+): { size: number; stride: number; alignment: number } {
+ let offset = 0;
+ let alignment = 1;
+ for (let i = 0; i < members.length; i++) {
+ const member = members[i];
+ const sizeAndAlign = sizeAndAlignmentOf(member, source);
+ offset = align(offset, sizeAndAlign.alignment);
+ if (callback) {
+ callback({
+ index: i,
+ type: member,
+ size: sizeAndAlign.size,
+ alignment: sizeAndAlign.alignment,
+ offset,
+ });
+ }
+ offset += sizeAndAlign.size;
+ alignment = Math.max(alignment, sizeAndAlign.alignment);
}
- // Handles scalars and vectors
- return 16;
+ if (source === 'uniform') {
+ alignment = align(alignment, 16);
+ }
+
+ const size = offset;
+ const stride = align(size, alignment);
+ return { size, stride, alignment };
+}
+
+/** @returns the stride in bytes between two consecutive structures with the given members */
+export function structStride(members: Type[], source: InputSource): number {
+ return structLayout(members, source).stride;
}
-// Helper for summing up all of the stride values for an array of Types
-function valueStrides(tys: Type[]): number {
- return tys.map(valueStride).reduce((sum, c) => sum + c);
+/** @returns the WGSL to describe the structure members in 'members' */
+function wgslMembers(members: Type[], source: InputSource, memberName: (i: number) => string) {
+ const lines: string[] = [];
+ const layout = structLayout(members, source, m => {
+ lines.push(` @size(${m.size}) ${memberName(lines.length)} : ${m.type},`);
+ });
+ const padding = layout.stride - layout.size;
+ if (padding > 0) {
+ // Pad with a 'f16' if the padding requires an odd multiple of 2 bytes.
+ // This is required as 'i32' has an alignment and size of 4 bytes.
+ const ty = (padding & 2) !== 0 ? 'f16' : 'i32';
+ lines.push(` @size(${padding}) padding : ${ty},`);
+ }
+ return lines.join('\n');
}
// Helper for returning the WGSL storage type for the given Type.
function storageType(ty: Type): Type {
if (ty instanceof ScalarType) {
assert(ty.kind !== 'f64', `No storage type defined for 'f64' values`);
+ assert(ty.kind !== 'abstract-int', `Custom handling is implemented for 'abstract-int' values`);
assert(
ty.kind !== 'abstract-float',
`Custom handling is implemented for 'abstract-float' values`
);
if (ty.kind === 'bool') {
- return TypeU32;
+ return Type.u32;
}
}
if (ty instanceof VectorType) {
- return TypeVec(ty.width, storageType(ty.elementType) as ScalarType);
+ return Type.vec(ty.width, storageType(ty.elementType) as ScalarType);
+ }
+ if (ty instanceof ArrayType) {
+ return Type.array(ty.count, storageType(ty.elementType));
}
return ty;
}
+/** Structure used to hold [from|to]Storage conversion helpers */
+type TypeConversionHelpers = {
+ // The module-scope WGSL to emit with the shader.
+ wgsl: string;
+ // A function that generates a unique WGSL identifier.
+ uniqueID: () => string;
+};
+
// Helper for converting a value of the type 'ty' from the storage type.
-function fromStorage(ty: Type, expr: string): string {
+function fromStorage(ty: Type, expr: string, helpers: TypeConversionHelpers): string {
if (ty instanceof ScalarType) {
- assert(ty.kind !== 'abstract-float', `AbstractFloat values should not be in input storage`);
+ assert(ty.kind !== 'abstract-int', `'abstract-int' values should not be in input storage`);
+ assert(ty.kind !== 'abstract-float', `'abstract-float' values should not be in input storage`);
assert(ty.kind !== 'f64', `'No storage type defined for 'f64' values`);
if (ty.kind === 'bool') {
return `${expr} != 0u`;
@@ -220,23 +208,46 @@ function fromStorage(ty: Type, expr: string): string {
}
if (ty instanceof VectorType) {
assert(
+ ty.elementType.kind !== 'abstract-int',
+ `'abstract-int' values cannot appear in input storage`
+ );
+ assert(
ty.elementType.kind !== 'abstract-float',
- `AbstractFloat values cannot appear in input storage`
+ `'abstract-float' values cannot appear in input storage`
);
assert(ty.elementType.kind !== 'f64', `'No storage type defined for 'f64' values`);
if (ty.elementType.kind === 'bool') {
- return `${expr} != vec${ty.width}<u32>(0u)`;
+ return `(${expr} != vec${ty.width}<u32>(0u))`;
}
}
+ if (ty instanceof ArrayType && elementTypeOf(ty) === Type.bool) {
+ // array<u32, N> -> array<bool, N>
+ const conv = helpers.uniqueID();
+ const inTy = Type.array(ty.count, Type.u32);
+ helpers.wgsl += `
+fn ${conv}(in : ${inTy}) -> ${ty} {
+ var out : ${ty};
+ for (var i = 0; i < ${ty.count}; i++) {
+ out[i] = in[i] != 0;
+ }
+ return out;
+}
+`;
+ return `${conv}(${expr})`;
+ }
return expr;
}
// Helper for converting a value of the type 'ty' to the storage type.
-function toStorage(ty: Type, expr: string): string {
+function toStorage(ty: Type, expr: string, helpers: TypeConversionHelpers): string {
if (ty instanceof ScalarType) {
assert(
+ ty.kind !== 'abstract-int',
+ `'abstract-int' values have custom code for writing to storage`
+ );
+ assert(
ty.kind !== 'abstract-float',
- `AbstractFloat values have custom code for writing to storage`
+ `'abstract-float' values have custom code for writing to storage`
);
assert(ty.kind !== 'f64', `No storage type defined for 'f64' values`);
if (ty.kind === 'bool') {
@@ -245,14 +256,33 @@ function toStorage(ty: Type, expr: string): string {
}
if (ty instanceof VectorType) {
assert(
+ ty.elementType.kind !== 'abstract-int',
+ `'abstract-int' values have custom code for writing to storage`
+ );
+ assert(
ty.elementType.kind !== 'abstract-float',
- `AbstractFloat values have custom code for writing to storage`
+ `'abstract-float' values have custom code for writing to storage`
);
assert(ty.elementType.kind !== 'f64', `'No storage type defined for 'f64' values`);
if (ty.elementType.kind === 'bool') {
return `select(vec${ty.width}<u32>(0u), vec${ty.width}<u32>(1u), ${expr})`;
}
}
+ if (ty instanceof ArrayType && elementTypeOf(ty) === Type.bool) {
+ // array<bool, N> -> array<u32, N>
+ const conv = helpers.uniqueID();
+ const outTy = Type.array(ty.count, Type.u32);
+ helpers.wgsl += `
+fn ${conv}(in : ${ty}) -> ${outTy} {
+ var out : ${outTy};
+ for (var i = 0; i < ${ty.count}; i++) {
+ out[i] = select(0u, 1u, in[i]);
+ }
+ return out;
+}
+`;
+ return `${conv}(${expr})`;
+ }
return expr;
}
@@ -296,7 +326,7 @@ export async function run(
parameterTypes: Array<Type>,
resultType: Type,
cfg: Config = { inputSource: 'storage_r' },
- cases: CaseList,
+ cases: Case[],
batch_size?: number
) {
// If the 'vectorize' config option was provided, pack the cases into vectors.
@@ -325,12 +355,13 @@ export async function run(
// 2k appears to be a sweet-spot when benchmarking.
return Math.floor(
Math.min(1024 * 2, t.device.limits.maxUniformBufferBindingSize) /
- valueStrides(parameterTypes)
+ structStride(parameterTypes, cfg.inputSource)
);
case 'storage_r':
case 'storage_rw':
return Math.floor(
- t.device.limits.maxStorageBufferBindingSize / valueStrides(parameterTypes)
+ t.device.limits.maxStorageBufferBindingSize /
+ structStride(parameterTypes, cfg.inputSource)
);
}
})();
@@ -353,7 +384,7 @@ export async function run(
}
};
- const processBatch = async (batchCases: CaseList) => {
+ const processBatch = async (batchCases: Case[]) => {
const checkBatch = await submitBatch(
t,
shaderBuilder,
@@ -404,12 +435,13 @@ async function submitBatch(
shaderBuilder: ShaderBuilder,
parameterTypes: Array<Type>,
resultType: Type,
- cases: CaseList,
+ cases: Case[],
inputSource: InputSource,
pipelineCache: PipelineCache
): Promise<() => void> {
// Construct a buffer to hold the results of the expression tests
- const outputBufferSize = cases.length * valueStride(resultType);
+ const outputStride = structStride([resultType], 'storage_rw');
+ const outputBufferSize = align(cases.length * outputStride, 4);
const outputBuffer = t.device.createBuffer({
size: outputBufferSize,
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE,
@@ -444,7 +476,7 @@ async function submitBatch(
// Read the outputs from the output buffer
const outputs = new Array<Value>(cases.length);
for (let i = 0; i < cases.length; i++) {
- outputs[i] = resultType.read(outputData, i * valueStride(resultType));
+ outputs[i] = resultType.read(outputData, i * outputStride);
}
// The list of expectation failures
@@ -498,7 +530,7 @@ function map<T, U>(v: T | readonly T[], fn: (value: T, index?: number) => U): U[
export type ShaderBuilder = (
parameterTypes: Array<Type>,
resultType: Type,
- cases: CaseList,
+ cases: Case[],
inputSource: InputSource
) => string;
@@ -507,10 +539,13 @@ export type ShaderBuilder = (
*/
function wgslOutputs(resultType: Type, count: number): string {
let output_struct = undefined;
- if (scalarTypeOf(resultType).kind !== 'abstract-float') {
+ if (
+ scalarTypeOf(resultType).kind !== 'abstract-float' &&
+ scalarTypeOf(resultType).kind !== 'abstract-int'
+ ) {
output_struct = `
struct Output {
- @size(${valueStride(resultType)}) value : ${storageType(resultType)}
+ @size(${strideOf(resultType, 'storage_rw')}) value : ${storageType(resultType)}
};`;
} else {
if (resultType instanceof ScalarType) {
@@ -520,7 +555,7 @@ struct Output {
};
struct Output {
- @size(${valueStride(resultType)}) value: AF,
+ @size(${strideOf(resultType, 'storage_rw')}) value: AF,
};`;
}
if (resultType instanceof VectorType) {
@@ -531,7 +566,7 @@ struct Output {
};
struct Output {
- @size(${valueStride(resultType)}) value: array<AF, ${dim}>,
+ @size(${strideOf(resultType, 'storage_rw')}) value: array<AF, ${dim}>,
};`;
}
@@ -544,7 +579,7 @@ struct Output {
};
struct Output {
- @size(${valueStride(resultType)}) value: array<array<AF, ${rows}>, ${cols}>,
+ @size(${strideOf(resultType, 'storage_rw')}) value: array<array<AF, ${rows}>, ${cols}>,
};`;
}
@@ -562,7 +597,7 @@ struct Output {
function wgslValuesArray(
parameterTypes: Array<Type>,
resultType: Type,
- cases: CaseList,
+ cases: Case[],
expressionBuilder: ExpressionBuilder
): string {
return `
@@ -612,19 +647,28 @@ function basicExpressionShaderBody(
expressionBuilder: ExpressionBuilder,
parameterTypes: Array<Type>,
resultType: Type,
- cases: CaseList,
+ cases: Case[],
inputSource: InputSource
): string {
assert(
+ scalarTypeOf(resultType).kind !== 'abstract-int',
+ `abstractIntShaderBuilder should be used when result type is 'abstract-int'`
+ );
+ assert(
scalarTypeOf(resultType).kind !== 'abstract-float',
- `abstractFloatShaderBuilder should be used when result type is 'abstract-float`
+ `abstractFloatShaderBuilder should be used when result type is 'abstract-float'`
);
+ let nextUniqueIDSuffix = 0;
+ const convHelpers: TypeConversionHelpers = {
+ wgsl: '',
+ uniqueID: () => `cts_symbol_${nextUniqueIDSuffix++}`,
+ };
if (inputSource === 'const') {
//////////////////////////////////////////////////////////////////////////
// Constant eval
//////////////////////////////////////////////////////////////////////////
let body = '';
- if (parameterTypes.some(ty => scalarTypeOf(ty).kind === 'abstract-float')) {
+ if (parameterTypes.some(ty => isAbstractType(elementTypeOf(ty)))) {
// Directly assign the expression to the output, to avoid an
// intermediate store, which will concretize the value early
body = cases
@@ -632,7 +676,8 @@ function basicExpressionShaderBody(
(c, i) =>
` outputs[${i}].value = ${toStorage(
resultType,
- expressionBuilder(map(c.input, v => v.wgsl()))
+ expressionBuilder(map(c.input, v => v.wgsl())),
+ convHelpers
)};`
)
.join('\n ');
@@ -640,47 +685,60 @@ function basicExpressionShaderBody(
body = cases
.map((_, i) => {
const value = `values[${i}]`;
- return ` outputs[${i}].value = ${toStorage(resultType, value)};`;
+ return ` outputs[${i}].value = ${toStorage(resultType, value, convHelpers)};`;
})
.join('\n ');
} else {
body = `
for (var i = 0u; i < ${cases.length}; i++) {
- outputs[i].value = ${toStorage(resultType, `values[i]`)};
+ outputs[i].value = ${toStorage(resultType, `values[i]`, convHelpers)};
}`;
}
+ // If params are abstract, we will assign them directly to the storage array, so skip the values array.
+ let valuesArray = '';
+ if (!parameterTypes.some(isAbstractType)) {
+ valuesArray = wgslValuesArray(parameterTypes, resultType, cases, expressionBuilder);
+ }
+
return `
${wgslOutputs(resultType, cases.length)}
-${wgslValuesArray(parameterTypes, resultType, cases, expressionBuilder)}
+${valuesArray}
+
+${convHelpers.wgsl}
@compute @workgroup_size(1)
fn main() {
${body}
-}`;
+}
+`;
} else {
//////////////////////////////////////////////////////////////////////////
// Runtime eval
//////////////////////////////////////////////////////////////////////////
// returns the WGSL expression to load the ith parameter of the given type from the input buffer
- const paramExpr = (ty: Type, i: number) => fromStorage(ty, `inputs[i].param${i}`);
+ const paramExpr = (ty: Type, i: number) => fromStorage(ty, `inputs[i].param${i}`, convHelpers);
// resolves to the expression that calls the builtin
- const expr = toStorage(resultType, expressionBuilder(parameterTypes.map(paramExpr)));
+ const expr = toStorage(
+ resultType,
+ expressionBuilder(parameterTypes.map(paramExpr)),
+ convHelpers
+ );
return `
struct Input {
-${parameterTypes
- .map((ty, i) => ` @size(${valueStride(ty)}) param${i} : ${storageType(ty)},`)
- .join('\n')}
-};
+${wgslMembers(parameterTypes.map(storageType), inputSource, i => `param${i}`)}
+}
${wgslOutputs(resultType, cases.length)}
${wgslInputVar(inputSource, cases.length)}
+${convHelpers.wgsl}
+
@compute @workgroup_size(1)
fn main() {
for (var i = 0; i < ${cases.length}; i++) {
@@ -699,7 +757,7 @@ export function basicExpressionBuilder(expressionBuilder: ExpressionBuilder): Sh
return (
parameterTypes: Array<Type>,
resultType: Type,
- cases: CaseList,
+ cases: Case[],
inputSource: InputSource
) => {
return `\
@@ -722,7 +780,7 @@ export function basicExpressionWithPredeclarationBuilder(
return (
parameterTypes: Array<Type>,
resultType: Type,
- cases: CaseList,
+ cases: Case[],
inputSource: InputSource
) => {
return `\
@@ -742,7 +800,7 @@ export function compoundAssignmentBuilder(op: string): ShaderBuilder {
return (
parameterTypes: Array<Type>,
resultType: Type,
- cases: CaseList,
+ cases: Case[],
inputSource: InputSource
) => {
//////////////////////////////////////////////////////////////////////////
@@ -807,8 +865,7 @@ ${wgslHeader(parameterTypes, resultType)}
${wgslOutputs(resultType, cases.length)}
struct Input {
- @size(${valueStride(lhsType)}) lhs : ${storageType(lhsType)},
- @size(${valueStride(rhsType)}) rhs : ${storageType(rhsType)},
+${wgslMembers([lhsType, rhsType].map(storageType), inputSource, i => ['lhs', 'rhs'][i])}
}
${wgslInputVar(inputSource, cases.length)}
@@ -969,10 +1026,10 @@ export function abstractFloatShaderBuilder(expressionBuilder: ExpressionBuilder)
return (
parameterTypes: Array<Type>,
resultType: Type,
- cases: CaseList,
+ cases: Case[],
inputSource: InputSource
) => {
- assert(inputSource === 'const', 'AbstractFloat results are only defined for const-eval');
+ assert(inputSource === 'const', `'abstract-float' results are only defined for const-eval`);
assert(
scalarTypeOf(resultType).kind === 'abstract-float',
`Expected resultType of 'abstract-float', received '${scalarTypeOf(resultType).kind}' instead`
@@ -998,6 +1055,90 @@ ${body}
}
/**
+ * @returns a string that extracts the value of an AbstractInt into an output
+ * destination
+ * @param expr expression for an AbstractInt value, if working with vectors,
+ * this string needs to include indexing into the container.
+ * @param case_idx index in the case output array to assign the result
+ * @param accessor string representing how access to the AbstractInt that needs
+ * to be operated on.
+ * For scalars this should be left as ''.
+ * For vectors this will be an indexing operation,
+ * i.e. '[i]'
+ */
+function abstractIntSnippet(expr: string, case_idx: number, accessor: string = ''): string {
+ // AbstractInts are i64s under the hood. WebGPU does not support
+ // putting i64s in buffers, or any 64-bit simple types, so the result needs to
+ // be split up into u32 bitfields
+ //
+ // Since there is no 64-bit data type that can be used as an element for a
+ // vector or a matrix in WGSL, the testing framework needs to pass the u32s
+ // via a struct with two u32s, and deconstruct vectors into arrays.
+ //
+ // This is complicated by the fact that user defined functions cannot
+ // take/return AbstractInts, and AbstractInts cannot be stored in
+ // variables, so the code cannot just inject a simple utility function
+ // at the top of the shader, instead this snippet needs to be inlined
+ // everywhere the test needs to return an AbstractInt.
+ return ` {
+ outputs[${case_idx}].value${accessor}.high = bitcast<u32>(i32(${expr}${accessor} >> 32)) & 0xFFFFFFFF;
+ const low_sign = (${expr}${accessor} & (1 << 31));
+ outputs[${case_idx}].value${accessor}.low = bitcast<u32>((${expr}${accessor} & 0x7FFFFFFF)) | low_sign;
+ }`;
+}
+
+/** @returns a string for a specific case that has a AbstractInt result */
+function abstractIntCaseBody(expr: string, resultType: Type, i: number): string {
+ if (resultType instanceof ScalarType) {
+ return abstractIntSnippet(expr, i);
+ }
+
+ if (resultType instanceof VectorType) {
+ return [...Array(resultType.width).keys()]
+ .map(idx => abstractIntSnippet(expr, i, `[${idx}]`))
+ .join(' \n');
+ }
+
+ unreachable(`Results of type '${resultType}' not yet implemented`);
+}
+
+/**
+ * @returns a ShaderBuilder that builds a test shader hands AbstractInt results.
+ * @param expressionBuilder an expression builder that will return AbstractInts
+ */
+export function abstractIntShaderBuilder(expressionBuilder: ExpressionBuilder): ShaderBuilder {
+ return (
+ parameterTypes: Array<Type>,
+ resultType: Type,
+ cases: Case[],
+ inputSource: InputSource
+ ) => {
+ assert(inputSource === 'const', `'abstract-int' results are only defined for const-eval`);
+ assert(
+ scalarTypeOf(resultType).kind === 'abstract-int',
+ `Expected resultType of 'abstract-int', received '${scalarTypeOf(resultType).kind}' instead`
+ );
+
+ const body = cases
+ .map((c, i) => {
+ const expr = `${expressionBuilder(map(c.input, v => v.wgsl()))}`;
+ return abstractIntCaseBody(expr, resultType, i);
+ })
+ .join('\n ');
+
+ return `
+${wgslHeader(parameterTypes, resultType)}
+
+${wgslOutputs(resultType, cases.length)}
+
+@compute @workgroup_size(1)
+fn main() {
+${body}
+}`;
+ };
+}
+
+/**
* Constructs and returns a GPUComputePipeline and GPUBindGroup for running a
* batch of test cases. If a pre-created pipeline can be found in
* `pipelineCache`, then this may be returned instead of creating a new
@@ -1016,7 +1157,7 @@ async function buildPipeline(
shaderBuilder: ShaderBuilder,
parameterTypes: Array<Type>,
resultType: Type,
- cases: CaseList,
+ cases: Case[],
inputSource: InputSource,
outputBuffer: GPUBuffer,
pipelineCache: PipelineCache
@@ -1060,27 +1201,23 @@ async function buildPipeline(
// Input values come from a uniform or storage buffer
// size in bytes of the input buffer
- const inputSize = cases.length * valueStrides(parameterTypes);
+ const caseStride = structStride(parameterTypes, inputSource);
+ const inputSize = align(cases.length * caseStride, 4);
// Holds all the parameter values for all cases
const inputData = new Uint8Array(inputSize);
// Pack all the input parameter values into the inputData buffer
- {
- const caseStride = valueStrides(parameterTypes);
- for (let caseIdx = 0; caseIdx < cases.length; caseIdx++) {
- const caseBase = caseIdx * caseStride;
- let offset = caseBase;
- for (let paramIdx = 0; paramIdx < parameterTypes.length; paramIdx++) {
- const params = cases[caseIdx].input;
- if (params instanceof Array) {
- params[paramIdx].copyTo(inputData, offset);
- } else {
- params.copyTo(inputData, offset);
- }
- offset += valueStride(parameterTypes[paramIdx]);
+ for (let caseIdx = 0; caseIdx < cases.length; caseIdx++) {
+ const offset = caseIdx * caseStride;
+ structLayout(parameterTypes, inputSource, m => {
+ const arg = cases[caseIdx].input;
+ if (arg instanceof Array) {
+ arg[m.index].copyTo(inputData, offset + m.offset);
+ } else {
+ arg.copyTo(inputData, offset + m.offset);
}
- }
+ });
}
// build the compute pipeline, if the shader hasn't been compiled already.
@@ -1123,12 +1260,12 @@ async function buildPipeline(
* If `cases.length` is not a multiple of `vectorWidth`, then the last scalar
* test case value is repeated to fill the vector value.
*/
-function packScalarsToVector(
+export function packScalarsToVector(
parameterTypes: Array<Type>,
resultType: Type,
- cases: CaseList,
+ cases: Case[],
vectorWidth: number
-): { cases: CaseList; parameterTypes: Array<Type>; resultType: Type } {
+): { cases: Case[]; parameterTypes: Array<Type>; resultType: Type } {
// Validate that the parameters and return type are all vectorizable
for (let i = 0; i < parameterTypes.length; i++) {
const ty = parameterTypes[i];
@@ -1145,22 +1282,22 @@ function packScalarsToVector(
}
const packedCases: Array<Case> = [];
- const packedParameterTypes = parameterTypes.map(p => TypeVec(vectorWidth, p as ScalarType));
- const packedResultType = new VectorType(vectorWidth, resultType);
+ const packedParameterTypes = parameterTypes.map(p => Type.vec(vectorWidth, p as ScalarType));
+ const packedResultType = Type.vec(vectorWidth, resultType);
const clampCaseIdx = (idx: number) => Math.min(idx, cases.length - 1);
let caseIdx = 0;
while (caseIdx < cases.length) {
// Construct the vectorized inputs from the scalar cases
- const packedInputs = new Array<Vector>(parameterTypes.length);
+ const packedInputs = new Array<VectorValue>(parameterTypes.length);
for (let paramIdx = 0; paramIdx < parameterTypes.length; paramIdx++) {
- const inputElements = new Array<Scalar>(vectorWidth);
+ const inputElements = new Array<ScalarValue>(vectorWidth);
for (let i = 0; i < vectorWidth; i++) {
const input = cases[clampCaseIdx(caseIdx + i)].input;
- inputElements[i] = (input instanceof Array ? input[paramIdx] : input) as Scalar;
+ inputElements[i] = (input instanceof Array ? input[paramIdx] : input) as ScalarValue;
}
- packedInputs[paramIdx] = new Vector(inputElements);
+ packedInputs[paramIdx] = new VectorValue(inputElements);
}
// Gather the comparators for the packed cases
@@ -1174,7 +1311,7 @@ function packScalarsToVector(
const gElements = new Array<string>(vectorWidth);
const eElements = new Array<string>(vectorWidth);
for (let i = 0; i < vectorWidth; i++) {
- const d = cmp_impls[i]((got as Vector).elements[i]);
+ const d = cmp_impls[i]((got as VectorValue).elements[i]);
matched = matched && d.matched;
gElements[i] = d.got;
eElements[i] = d.expected;
@@ -1199,238 +1336,3 @@ function packScalarsToVector(
resultType: packedResultType,
};
}
-
-/**
- * Indicates bounds that acceptance intervals need to be within to avoid inputs
- * being filtered out. This is used for const-eval tests, since going OOB will
- * cause a validation error not an execution error.
- */
-export type IntervalFilter =
- | 'finite' // Expected to be finite in the interval numeric space
- | 'unfiltered'; // No expectations
-
-/**
- * A function that performs a binary operation on x and y, and returns the expected
- * result.
- */
-export interface BinaryOp {
- (x: number, y: number): number | undefined;
-}
-
-/**
- * @returns array of Case for the input params with op applied
- * @param param0s array of inputs to try for the first param
- * @param param1s array of inputs to try for the second param
- * @param op callback called on each pair of inputs to produce each case
- * @param quantize function to quantize all values
- * @param scalarize function to convert numbers to Scalars
- */
-function generateScalarBinaryToScalarCases(
- param0s: readonly number[],
- param1s: readonly number[],
- op: BinaryOp,
- quantize: QuantizeFunc,
- scalarize: ScalarBuilder
-): Case[] {
- param0s = param0s.map(quantize);
- param1s = param1s.map(quantize);
- return cartesianProduct(param0s, param1s).reduce((cases, e) => {
- const expected = op(e[0], e[1]);
- if (expected !== undefined) {
- cases.push({ input: [scalarize(e[0]), scalarize(e[1])], expected: scalarize(expected) });
- }
- return cases;
- }, new Array<Case>());
-}
-
-/**
- * @returns an array of Cases for operations over a range of inputs
- * @param param0s array of inputs to try for the first param
- * @param param1s array of inputs to try for the second param
- * @param op callback called on each pair of inputs to produce each case
- */
-export function generateBinaryToI32Cases(
- param0s: readonly number[],
- param1s: readonly number[],
- op: BinaryOp
-) {
- return generateScalarBinaryToScalarCases(param0s, param1s, op, quantizeToI32, i32);
-}
-
-/**
- * @returns an array of Cases for operations over a range of inputs
- * @param param0s array of inputs to try for the first param
- * @param param1s array of inputs to try for the second param
- * @param op callback called on each pair of inputs to produce each case
- */
-export function generateBinaryToU32Cases(
- param0s: readonly number[],
- param1s: readonly number[],
- op: BinaryOp
-) {
- return generateScalarBinaryToScalarCases(param0s, param1s, op, quantizeToU32, u32);
-}
-
-/**
- * @returns a Case for the input params with op applied
- * @param scalar scalar param
- * @param vector vector param (2, 3, or 4 elements)
- * @param op the op to apply to scalar and vector
- * @param quantize function to quantize all values in vectors and scalars
- * @param scalarize function to convert numbers to Scalars
- */
-function makeScalarVectorBinaryToVectorCase(
- scalar: number,
- vector: readonly number[],
- op: BinaryOp,
- quantize: QuantizeFunc,
- scalarize: ScalarBuilder
-): Case | undefined {
- scalar = quantize(scalar);
- vector = vector.map(quantize);
- const result = vector.map(v => op(scalar, v));
- if (result.includes(undefined)) {
- return undefined;
- }
- return {
- input: [scalarize(scalar), new Vector(vector.map(scalarize))],
- expected: new Vector((result as readonly number[]).map(scalarize)),
- };
-}
-
-/**
- * @returns array of Case for the input params with op applied
- * @param scalars array of scalar params
- * @param vectors array of vector params (2, 3, or 4 elements)
- * @param op the op to apply to each pair of scalar and vector
- * @param quantize function to quantize all values in vectors and scalars
- * @param scalarize function to convert numbers to Scalars
- */
-function generateScalarVectorBinaryToVectorCases(
- scalars: readonly number[],
- vectors: ROArrayArray<number>,
- op: BinaryOp,
- quantize: QuantizeFunc,
- scalarize: ScalarBuilder
-): Case[] {
- const cases = new Array<Case>();
- scalars.forEach(s => {
- vectors.forEach(v => {
- const c = makeScalarVectorBinaryToVectorCase(s, v, op, quantize, scalarize);
- if (c !== undefined) {
- cases.push(c);
- }
- });
- });
- return cases;
-}
-
-/**
- * @returns a Case for the input params with op applied
- * @param vector vector param (2, 3, or 4 elements)
- * @param scalar scalar param
- * @param op the op to apply to vector and scalar
- * @param quantize function to quantize all values in vectors and scalars
- * @param scalarize function to convert numbers to Scalars
- */
-function makeVectorScalarBinaryToVectorCase(
- vector: readonly number[],
- scalar: number,
- op: BinaryOp,
- quantize: QuantizeFunc,
- scalarize: ScalarBuilder
-): Case | undefined {
- vector = vector.map(quantize);
- scalar = quantize(scalar);
- const result = vector.map(v => op(v, scalar));
- if (result.includes(undefined)) {
- return undefined;
- }
- return {
- input: [new Vector(vector.map(scalarize)), scalarize(scalar)],
- expected: new Vector((result as readonly number[]).map(scalarize)),
- };
-}
-
-/**
- * @returns array of Case for the input params with op applied
- * @param vectors array of vector params (2, 3, or 4 elements)
- * @param scalars array of scalar params
- * @param op the op to apply to each pair of vector and scalar
- * @param quantize function to quantize all values in vectors and scalars
- * @param scalarize function to convert numbers to Scalars
- */
-function generateVectorScalarBinaryToVectorCases(
- vectors: ROArrayArray<number>,
- scalars: readonly number[],
- op: BinaryOp,
- quantize: QuantizeFunc,
- scalarize: ScalarBuilder
-): Case[] {
- const cases = new Array<Case>();
- scalars.forEach(s => {
- vectors.forEach(v => {
- const c = makeVectorScalarBinaryToVectorCase(v, s, op, quantize, scalarize);
- if (c !== undefined) {
- cases.push(c);
- }
- });
- });
- return cases;
-}
-
-/**
- * @returns array of Case for the input params with op applied
- * @param scalars array of scalar params
- * @param vectors array of vector params (2, 3, or 4 elements)
- * @param op he op to apply to each pair of scalar and vector
- */
-export function generateU32VectorBinaryToVectorCases(
- scalars: readonly number[],
- vectors: ROArrayArray<number>,
- op: BinaryOp
-): Case[] {
- return generateScalarVectorBinaryToVectorCases(scalars, vectors, op, quantizeToU32, u32);
-}
-
-/**
- * @returns array of Case for the input params with op applied
- * @param vectors array of vector params (2, 3, or 4 elements)
- * @param scalars array of scalar params
- * @param op he op to apply to each pair of vector and scalar
- */
-export function generateVectorU32BinaryToVectorCases(
- vectors: ROArrayArray<number>,
- scalars: readonly number[],
- op: BinaryOp
-): Case[] {
- return generateVectorScalarBinaryToVectorCases(vectors, scalars, op, quantizeToU32, u32);
-}
-
-/**
- * @returns array of Case for the input params with op applied
- * @param scalars array of scalar params
- * @param vectors array of vector params (2, 3, or 4 elements)
- * @param op he op to apply to each pair of scalar and vector
- */
-export function generateI32VectorBinaryToVectorCases(
- scalars: readonly number[],
- vectors: ROArrayArray<number>,
- op: BinaryOp
-): Case[] {
- return generateScalarVectorBinaryToVectorCases(scalars, vectors, op, quantizeToI32, i32);
-}
-
-/**
- * @returns array of Case for the input params with op applied
- * @param vectors array of vector params (2, 3, or 4 elements)
- * @param scalars array of scalar params
- * @param op he op to apply to each pair of vector and scalar
- */
-export function generateVectorI32BinaryToVectorCases(
- vectors: ROArrayArray<number>,
- scalars: readonly number[],
- op: BinaryOp
-): Case[] {
- return generateVectorScalarBinaryToVectorCases(vectors, scalars, op, quantizeToI32, i32);
-}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/interval_filter.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/interval_filter.ts
new file mode 100644
index 0000000000..7471247e54
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/interval_filter.ts
@@ -0,0 +1,8 @@
+/**
+ * Indicates bounds that acceptance intervals need to be within to avoid inputs
+ * being filtered out. This is used for const-eval tests, since going OOB will
+ * cause a validation error not an execution error.
+ */
+export type IntervalFilter =
+ | 'finite' // Expected to be finite in the interval numeric space
+ | 'unfiltered'; // No expectations
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/precedence.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/precedence.spec.ts
new file mode 100644
index 0000000000..643bda657e
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/precedence.spec.ts
@@ -0,0 +1,113 @@
+export const description = `
+Execution tests for operator precedence.
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// The list of test cases and their expected results.
+interface Expression {
+ expr: string;
+ result: number;
+}
+const kExpressions: Record<string, Expression> = {
+ add_mul: { expr: 'kThree + kSeven * kEleven', result: 80 },
+ mul_add: { expr: 'kThree * kSeven + kEleven', result: 32 },
+ sub_neg: { expr: 'kThree - - kSeven', result: 10 },
+ neg_shl: { expr: '- kThree << u32(kSeven)', result: -384 },
+ neg_shr: { expr: '- kThree >> u32(kSeven)', result: -1 },
+ neg_add: { expr: '- kThree + kSeven', result: 4 },
+ neg_mul: { expr: '- kThree * kSeven', result: -21 },
+ neg_and: { expr: '- kThree & kSeven', result: 5 },
+ neg_or: { expr: '- kThree | kSeven', result: -1 },
+ neg_xor: { expr: '- kThree ^ kSeven', result: -6 },
+ comp_add: { expr: '~ kThree + kSeven', result: 3 },
+ mul_deref: { expr: 'kThree * * ptr_five', result: 15 },
+ not_and: { expr: 'i32(! kFalse && kFalse)', result: 0 },
+ not_or: { expr: 'i32(! kTrue || kTrue)', result: 1 },
+ eq_and: { expr: 'i32(kFalse == kTrue && kFalse)', result: 0 },
+ and_eq: { expr: 'i32(kFalse && kTrue == kFalse)', result: 0 },
+ eq_or: { expr: 'i32(kFalse == kFalse || kTrue)', result: 1 },
+ or_eq: { expr: 'i32(kTrue || kFalse == kFalse)', result: 1 },
+ add_swizzle: { expr: '(vec + vec . y) . z', result: 8 },
+};
+
+g.test('precedence')
+ .desc(
+ `
+ Test that operator precedence rules are correctly implemented.
+ `
+ )
+ .params(u =>
+ u
+ .combine('expr', keysOf(kExpressions))
+ .combine('decl', ['literal', 'const', 'override', 'var<private>'])
+ .combine('strip_spaces', [false, true])
+ )
+ .fn(t => {
+ const expr = kExpressions[t.params.expr];
+
+ let decl = t.params.decl;
+ let expr_wgsl = expr.expr;
+ if (t.params.decl === 'literal') {
+ decl = 'const';
+ expr_wgsl = expr_wgsl.replace(/kThree/g, '3');
+ expr_wgsl = expr_wgsl.replace(/kSeven/g, '7');
+ expr_wgsl = expr_wgsl.replace(/kEleven/g, '11');
+ expr_wgsl = expr_wgsl.replace(/kFalse/g, 'false');
+ expr_wgsl = expr_wgsl.replace(/kTrue/g, 'true');
+ }
+ if (t.params.strip_spaces) {
+ expr_wgsl = expr_wgsl.replace(/ /g, '');
+ }
+ const wgsl = `
+ @group(0) @binding(0) var<storage, read_write> buffer : i32;
+
+ ${decl} kFalse = false;
+ ${decl} kTrue = true;
+
+ ${decl} kThree = 3;
+ ${decl} kSeven = 7;
+ ${decl} kEleven = 11;
+
+ @compute @workgroup_size(1)
+ fn main() {
+ var five = 5;
+ var vec = vec4(1, kThree, 5, kSeven);
+ let ptr_five = &five;
+
+ buffer = ${expr_wgsl};
+ }
+ `;
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ },
+ });
+
+ // Allocate a buffer and fill it with 0xdeadbeef.
+ const outputBuffer = t.makeBufferWithContents(
+ new Uint32Array([0xdeadbeef]),
+ GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
+ );
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [{ binding: 0, resource: { buffer: outputBuffer } }],
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(1);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Check that the result is as expected.
+ t.expectGPUBufferValuesEqual(outputBuffer, new Int32Array([expr.result]));
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/address_of_and_indirection.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/address_of_and_indirection.spec.ts
new file mode 100644
index 0000000000..c4961558e0
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/address_of_and_indirection.spec.ts
@@ -0,0 +1,171 @@
+export const description = `
+Execution Tests for unary address-of and indirection (dereference)
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../common/util/data_tables.js';
+import { GPUTest } from '../../../../gpu_test.js';
+import { ScalarKind, scalarType } from '../../../../util/conversion.js';
+import { sparseScalarF32Range } from '../../../../util/math.js';
+import {
+ allButConstInputSource,
+ basicExpressionWithPredeclarationBuilder,
+ run,
+} from '../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// All the ways to deref an expression
+const kDerefCases = {
+ deref_address_of_identifier: {
+ wgsl: '(*(&a))',
+ requires_pointer_composite_access: false,
+ },
+ deref_pointer: {
+ wgsl: '(*p)',
+ requires_pointer_composite_access: false,
+ },
+ address_of_identifier: {
+ wgsl: '(&a)',
+ requires_pointer_composite_access: true,
+ },
+ pointer: {
+ wgsl: 'p',
+ requires_pointer_composite_access: true,
+ },
+};
+
+g.test('deref')
+ .specURL('https://www.w3.org/TR/WGSL/#indirection')
+ .desc(
+ `
+Expression: *e
+
+Pointer expression dereference.
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', allButConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ .combine('scalarType', ['bool', 'u32', 'i32', 'f32', 'f16'] as ScalarKind[])
+ .combine('derefType', keysOf(kDerefCases))
+ .filter(p => !kDerefCases[p.derefType].requires_pointer_composite_access)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.scalarType === 'f16') {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+ }
+ })
+ .fn(async t => {
+ const ty = scalarType(t.params.scalarType);
+ const cases = sparseScalarF32Range().map(e => {
+ return { input: ty.create(e), expected: ty.create(e) };
+ });
+ const elemType = ty.kind;
+ const type = t.params.vectorize ? `vec${t.params.vectorize}<${elemType}>` : elemType;
+ const shaderBuilder = basicExpressionWithPredeclarationBuilder(
+ value => `get_dereferenced_value(${value})`,
+ `fn get_dereferenced_value(value: ${type}) -> ${type} {
+ var a = value;
+ let p = &a;
+ return ${kDerefCases[t.params.derefType].wgsl};
+ }`
+ );
+ await run(t, shaderBuilder, [ty], ty, t.params, cases);
+ });
+
+g.test('deref_index')
+ .specURL('https://www.w3.org/TR/WGSL/#logical-expr')
+ .desc(
+ `
+Expression: (*e)[index]
+
+Pointer expression dereference as lhs of index accessor expression
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', allButConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ .combine('scalarType', ['bool', 'u32', 'i32', 'f32', 'f16'] as ScalarKind[])
+ .combine('derefType', keysOf(kDerefCases))
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.scalarType === 'f16') {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+ }
+ })
+ .fn(async t => {
+ if (
+ kDerefCases[t.params.derefType].requires_pointer_composite_access &&
+ !t.hasLanguageFeature('pointer_composite_access')
+ ) {
+ return;
+ }
+
+ const ty = scalarType(t.params.scalarType);
+ const cases = sparseScalarF32Range().map(e => {
+ return { input: ty.create(e), expected: ty.create(e) };
+ });
+ const elemType = ty.kind;
+ const type = t.params.vectorize ? `vec${t.params.vectorize}<${elemType}>` : elemType;
+ const shaderBuilder = basicExpressionWithPredeclarationBuilder(
+ value => `get_dereferenced_value(${value})`,
+ `fn get_dereferenced_value(value: ${type}) -> ${type} {
+ var a = array<${type}, 1>(value);
+ let p = &a;
+ return ${kDerefCases[t.params.derefType].wgsl}[0];
+ }`
+ );
+ await run(t, shaderBuilder, [ty], ty, t.params, cases);
+ });
+
+g.test('deref_member')
+ .specURL('https://www.w3.org/TR/WGSL/#logical-expr')
+ .desc(
+ `
+Expression: (*e).member
+
+Pointer expression dereference as lhs of member accessor expression
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', allButConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ .combine('scalarType', ['bool', 'u32', 'i32', 'f32', 'f16'] as ScalarKind[])
+ .combine('derefType', keysOf(kDerefCases))
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.scalarType === 'f16') {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+ }
+ })
+ .fn(async t => {
+ if (
+ kDerefCases[t.params.derefType].requires_pointer_composite_access &&
+ !t.hasLanguageFeature('pointer_composite_access')
+ ) {
+ return;
+ }
+
+ const ty = scalarType(t.params.scalarType);
+ const cases = sparseScalarF32Range().map(e => {
+ return { input: ty.create(e), expected: ty.create(e) };
+ });
+ const elemType = ty.kind;
+ const type = t.params.vectorize ? `vec${t.params.vectorize}<${elemType}>` : elemType;
+ const shaderBuilder = basicExpressionWithPredeclarationBuilder(
+ value => `get_dereferenced_value(${value})`,
+ `struct S {
+ m : ${type}
+ }
+ fn get_dereferenced_value(value: ${type}) -> ${type} {
+ var a = S(value);
+ let p = &a;
+ return ${kDerefCases[t.params.derefType].wgsl}.m;
+ }`
+ );
+ await run(t, shaderBuilder, [ty], ty, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_arithmetic.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_arithmetic.cache.ts
new file mode 100644
index 0000000000..4f274e8922
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_arithmetic.cache.ts
@@ -0,0 +1,13 @@
+import { FP } from '../../../../util/floating_point.js';
+import { scalarF64Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+export const d = makeCaseCache('unary/af_arithmetic', {
+ negation: () => {
+ return FP.abstract.generateScalarToIntervalCases(
+ scalarF64Range({ neg_norm: 250, neg_sub: 20, pos_sub: 20, pos_norm: 250 }),
+ 'unfiltered',
+ FP.abstract.negationInterval
+ );
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_arithmetic.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_arithmetic.spec.ts
index 182c0d76a9..686d4b7c4a 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_arithmetic.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_arithmetic.spec.ts
@@ -1,29 +1,17 @@
export const description = `
-Execution Tests for AbstractFloat arithmetic unary expression operations
+Execution Tests for Type.abstractFloat arithmetic unary expression operations
`;
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeAbstractFloat } from '../../../../util/conversion.js';
-import { FP } from '../../../../util/floating_point.js';
-import { fullF64Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { onlyConstInputSource, run } from '../expression.js';
-import { abstractUnary } from './unary.js';
+import { d } from './af_arithmetic.cache.js';
+import { abstractFloatUnary } from './unary.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('unary/af_arithmetic', {
- negation: () => {
- return FP.abstract.generateScalarToIntervalCases(
- fullF64Range({ neg_norm: 250, neg_sub: 20, pos_sub: 20, pos_norm: 250 }),
- 'unfiltered',
- FP.abstract.negationInterval
- );
- },
-});
-
g.test('negation')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -39,5 +27,13 @@ Accuracy: Correctly rounded
)
.fn(async t => {
const cases = await d.get('negation');
- await run(t, abstractUnary('-'), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases, 1);
+ await run(
+ t,
+ abstractFloatUnary('-'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases,
+ 1
+ );
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_assignment.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_assignment.cache.ts
new file mode 100644
index 0000000000..7c607927fe
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_assignment.cache.ts
@@ -0,0 +1,51 @@
+import { kValue } from '../../../../util/constants.js';
+import { abstractFloat } from '../../../../util/conversion.js';
+import { FP } from '../../../../util/floating_point.js';
+import {
+ isSubnormalNumberF64,
+ limitedScalarF64Range,
+ scalarF64Range,
+} from '../../../../util/math.js';
+import { reinterpretU64AsF64 } from '../../../../util/reinterpret.js';
+import { makeCaseCache } from '../case_cache.js';
+
+export const d = makeCaseCache('unary/af_assignment', {
+ abstract: () => {
+ const inputs = [
+ // Values that are useful for debugging the underlying framework/shader code, since it cannot be directly unit tested.
+ 0,
+ 0.5,
+ 0.5,
+ 1,
+ -1,
+ reinterpretU64AsF64(0x7000_0000_0000_0001n), // smallest magnitude negative subnormal with non-zero mantissa
+ reinterpretU64AsF64(0x0000_0000_0000_0001n), // smallest magnitude positive subnormal with non-zero mantissa
+ reinterpretU64AsF64(0x600a_aaaa_5555_5555n), // negative subnormal with obvious pattern
+ reinterpretU64AsF64(0x000a_aaaa_5555_5555n), // positive subnormal with obvious pattern
+ reinterpretU64AsF64(0x0010_0000_0000_0001n), // smallest magnitude negative normal with non-zero mantissa
+ reinterpretU64AsF64(0x0010_0000_0000_0001n), // smallest magnitude positive normal with non-zero mantissa
+ reinterpretU64AsF64(0xf555_5555_aaaa_aaaan), // negative normal with obvious pattern
+ reinterpretU64AsF64(0x5555_5555_aaaa_aaaan), // positive normal with obvious pattern
+ reinterpretU64AsF64(0xffef_ffff_ffff_ffffn), // largest magnitude negative normal
+ reinterpretU64AsF64(0x7fef_ffff_ffff_ffffn), // largest magnitude positive normal
+ // WebGPU implementation stressing values
+ ...scalarF64Range(),
+ ];
+ return inputs.map(f => {
+ return {
+ input: abstractFloat(f),
+ expected: isSubnormalNumberF64(f) ? abstractFloat(0) : abstractFloat(f),
+ };
+ });
+ },
+ f32: () => {
+ return limitedScalarF64Range(kValue.f32.negative.min, kValue.f32.positive.max).map(f => {
+ return { input: abstractFloat(f), expected: FP.f32.correctlyRoundedInterval(f) };
+ });
+ },
+ f16: () => {
+ return limitedScalarF64Range(kValue.f16.negative.min, kValue.f16.positive.max).map(f => {
+ return { input: abstractFloat(f), expected: FP.f16.correctlyRoundedInterval(f) };
+ });
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_assignment.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_assignment.spec.ts
index 141d87d0f2..001c47e117 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_assignment.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_assignment.spec.ts
@@ -4,20 +4,17 @@ Execution Tests for assignment of AbstractFloats
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { kValue } from '../../../../util/constants.js';
-import { abstractFloat, TypeAbstractFloat, TypeF16, TypeF32 } from '../../../../util/conversion.js';
-import { FP } from '../../../../util/floating_point.js';
-import { filteredF64Range, fullF64Range, isSubnormalNumberF64 } from '../../../../util/math.js';
-import { reinterpretU64AsF64 } from '../../../../util/reinterpret.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import {
+ ShaderBuilder,
abstractFloatShaderBuilder,
basicExpressionBuilder,
onlyConstInputSource,
run,
- ShaderBuilder,
} from '../expression.js';
+import { d } from './af_assignment.cache.js';
+
function concrete_assignment(): ShaderBuilder {
return basicExpressionBuilder(value => `${value}`);
}
@@ -28,47 +25,6 @@ function abstract_assignment(): ShaderBuilder {
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('unary/af_assignment', {
- abstract: () => {
- const inputs = [
- // Values that are useful for debugging the underlying framework/shader code, since it cannot be directly unit tested.
- 0,
- 0.5,
- 0.5,
- 1,
- -1,
- reinterpretU64AsF64(0x7000_0000_0000_0001n), // smallest magnitude negative subnormal with non-zero mantissa
- reinterpretU64AsF64(0x0000_0000_0000_0001n), // smallest magnitude positive subnormal with non-zero mantissa
- reinterpretU64AsF64(0x600a_aaaa_5555_5555n), // negative subnormal with obvious pattern
- reinterpretU64AsF64(0x000a_aaaa_5555_5555n), // positive subnormal with obvious pattern
- reinterpretU64AsF64(0x0010_0000_0000_0001n), // smallest magnitude negative normal with non-zero mantissa
- reinterpretU64AsF64(0x0010_0000_0000_0001n), // smallest magnitude positive normal with non-zero mantissa
- reinterpretU64AsF64(0xf555_5555_aaaa_aaaan), // negative normal with obvious pattern
- reinterpretU64AsF64(0x5555_5555_aaaa_aaaan), // positive normal with obvious pattern
- reinterpretU64AsF64(0xffef_ffff_ffff_ffffn), // largest magnitude negative normal
- reinterpretU64AsF64(0x7fef_ffff_ffff_ffffn), // largest magnitude positive normal
- // WebGPU implementation stressing values
- ...fullF64Range(),
- ];
- return inputs.map(f => {
- return {
- input: abstractFloat(f),
- expected: isSubnormalNumberF64(f) ? abstractFloat(0) : abstractFloat(f),
- };
- });
- },
- f32: () => {
- return filteredF64Range(kValue.f32.negative.min, kValue.f32.positive.max).map(f => {
- return { input: abstractFloat(f), expected: FP.f32.correctlyRoundedInterval(f) };
- });
- },
- f16: () => {
- return filteredF64Range(kValue.f16.negative.min, kValue.f16.positive.max).map(f => {
- return { input: abstractFloat(f), expected: FP.f16.correctlyRoundedInterval(f) };
- });
- },
-});
-
g.test('abstract')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-conversion')
.desc(
@@ -79,7 +35,15 @@ testing that extracting abstract floats works
.params(u => u.combine('inputSource', onlyConstInputSource))
.fn(async t => {
const cases = await d.get('abstract');
- await run(t, abstract_assignment(), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases, 1);
+ await run(
+ t,
+ abstract_assignment(),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases,
+ 1
+ );
});
g.test('f32')
@@ -92,7 +56,7 @@ concretizing to f32
.params(u => u.combine('inputSource', onlyConstInputSource))
.fn(async t => {
const cases = await d.get('f32');
- await run(t, concrete_assignment(), [TypeAbstractFloat], TypeF32, t.params, cases);
+ await run(t, concrete_assignment(), [Type.abstractFloat], Type.f32, t.params, cases);
});
g.test('f16')
@@ -108,5 +72,5 @@ concretizing to f16
.params(u => u.combine('inputSource', onlyConstInputSource))
.fn(async t => {
const cases = await d.get('f16');
- await run(t, concrete_assignment(), [TypeAbstractFloat], TypeF16, t.params, cases);
+ await run(t, concrete_assignment(), [Type.abstractFloat], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_arithmetic.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_arithmetic.cache.ts
new file mode 100644
index 0000000000..904c0bc700
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_arithmetic.cache.ts
@@ -0,0 +1,11 @@
+import { abstractInt } from '../../../../util/conversion.js';
+import { fullI64Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+export const d = makeCaseCache('unary/ai_arithmetic', {
+ negation: () => {
+ return fullI64Range().map(e => {
+ return { input: abstractInt(e), expected: abstractInt(-e) };
+ });
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_arithmetic.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_arithmetic.spec.ts
new file mode 100644
index 0000000000..625a38b2d5
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_arithmetic.spec.ts
@@ -0,0 +1,30 @@
+export const description = `
+Execution Tests for the abstract integer arithmetic unary expression operations
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../gpu_test.js';
+import { Type } from '../../../../util/conversion.js';
+import { onlyConstInputSource, run } from '../expression.js';
+
+import { d } from './ai_arithmetic.cache.js';
+import { abstractIntUnary } from './unary.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('negation')
+ .specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr')
+ .desc(
+ `
+Expression: -x
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('negation');
+ await run(t, abstractIntUnary('-'), [Type.abstractInt], Type.abstractInt, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_assignment.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_assignment.cache.ts
new file mode 100644
index 0000000000..0fa4f2efc2
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_assignment.cache.ts
@@ -0,0 +1,21 @@
+import { abstractInt, i32, u32 } from '../../../../util/conversion.js';
+import { fullI32Range, fullI64Range, fullU32Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+export const d = makeCaseCache('unary/ai_assignment', {
+ abstract: () => {
+ return fullI64Range().map(n => {
+ return { input: abstractInt(n), expected: abstractInt(n) };
+ });
+ },
+ i32: () => {
+ return fullI32Range().map(n => {
+ return { input: abstractInt(BigInt(n)), expected: i32(n) };
+ });
+ },
+ u32: () => {
+ return fullU32Range().map(n => {
+ return { input: abstractInt(BigInt(n)), expected: u32(n) };
+ });
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_assignment.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_assignment.spec.ts
new file mode 100644
index 0000000000..fe1ba2d9fc
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_assignment.spec.ts
@@ -0,0 +1,65 @@
+export const description = `
+Execution Tests for assignment of AbstractInts
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../gpu_test.js';
+import { Type } from '../../../../util/conversion.js';
+import {
+ ShaderBuilder,
+ abstractIntShaderBuilder,
+ basicExpressionBuilder,
+ onlyConstInputSource,
+ run,
+} from '../expression.js';
+
+import { d } from './ai_assignment.cache.js';
+
+function concrete_assignment(): ShaderBuilder {
+ return basicExpressionBuilder(value => `${value}`);
+}
+
+function abstract_assignment(): ShaderBuilder {
+ return abstractIntShaderBuilder(value => `${value}`);
+}
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('abstract')
+ .specURL('https://www.w3.org/TR/WGSL/#abstract-types')
+ .desc(
+ `
+testing that extracting abstract ints works
+`
+ )
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(t, abstract_assignment(), [Type.abstractInt], Type.abstractInt, t.params, cases, 1);
+ });
+
+g.test('i32')
+ .specURL('https://www.w3.org/TR/WGSL/#i32-builtin')
+ .desc(
+ `
+concretizing to i32
+`
+ )
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('i32');
+ await run(t, concrete_assignment(), [Type.abstractInt], Type.i32, t.params, cases);
+ });
+
+g.test('u32')
+ .specURL('https://www.w3.org/TR/WGSL/#u32-builtin')
+ .desc(
+ `
+concretizing to u32
+`
+ )
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('u32');
+ await run(t, concrete_assignment(), [Type.abstractInt], Type.u32, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_complement.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_complement.spec.ts
new file mode 100644
index 0000000000..507ae219cb
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_complement.spec.ts
@@ -0,0 +1,32 @@
+export const description = `
+Execution Tests for the Type.abstractInt bitwise complement operation
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../gpu_test.js';
+import { abstractInt, Type } from '../../../../util/conversion.js';
+import { fullI64Range } from '../../../../util/math.js';
+import { onlyConstInputSource, run } from '../expression.js';
+
+import { abstractIntUnary } from './unary.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('complement')
+ .specURL('https://www.w3.org/TR/WGSL/#bit-expr')
+ .desc(
+ `
+Expression: ~x
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = fullI64Range().map(e => {
+ return { input: abstractInt(e), expected: abstractInt(~e) };
+ });
+ await run(t, abstractIntUnary('~'), [Type.abstractInt], Type.abstractInt, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_conversion.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_conversion.cache.ts
new file mode 100644
index 0000000000..5b16c49c42
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_conversion.cache.ts
@@ -0,0 +1,54 @@
+import { anyOf } from '../../../../util/compare.js';
+import { ScalarValue, bool, f16, f32, i32, u32 } from '../../../../util/conversion.js';
+import {
+ fullI32Range,
+ fullU32Range,
+ isSubnormalNumberF16,
+ isSubnormalNumberF32,
+ scalarF16Range,
+ scalarF32Range,
+} from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+export const d = makeCaseCache('unary/bool_conversion', {
+ bool: () => {
+ return [
+ { input: bool(true), expected: bool(true) },
+ { input: bool(false), expected: bool(false) },
+ ];
+ },
+ u32: () => {
+ return fullU32Range().map(u => {
+ return { input: u32(u), expected: u === 0 ? bool(false) : bool(true) };
+ });
+ },
+ i32: () => {
+ return fullI32Range().map(i => {
+ return { input: i32(i), expected: i === 0 ? bool(false) : bool(true) };
+ });
+ },
+ f32: () => {
+ return scalarF32Range().map(f => {
+ const expected: ScalarValue[] = [];
+ if (f !== 0) {
+ expected.push(bool(true));
+ }
+ if (isSubnormalNumberF32(f)) {
+ expected.push(bool(false));
+ }
+ return { input: f32(f), expected: anyOf(...expected) };
+ });
+ },
+ f16: () => {
+ return scalarF16Range().map(f => {
+ const expected: ScalarValue[] = [];
+ if (f !== 0) {
+ expected.push(bool(true));
+ }
+ if (isSubnormalNumberF16(f)) {
+ expected.push(bool(false));
+ }
+ return { input: f16(f), expected: anyOf(...expected) };
+ });
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_conversion.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_conversion.spec.ts
index 8fcfed339f..55d01d3eda 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_conversion.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_conversion.spec.ts
@@ -4,78 +4,14 @@ Execution Tests for the boolean conversion operations
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { anyOf } from '../../../../util/compare.js';
-import {
- bool,
- f32,
- f16,
- i32,
- Scalar,
- TypeBool,
- TypeF32,
- TypeF16,
- TypeI32,
- TypeU32,
- u32,
-} from '../../../../util/conversion.js';
-import {
- fullF32Range,
- fullF16Range,
- fullI32Range,
- fullU32Range,
- isSubnormalNumberF32,
- isSubnormalNumberF16,
-} from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
-import { allInputSources, run, ShaderBuilder } from '../expression.js';
+import { Type } from '../../../../util/conversion.js';
+import { ShaderBuilder, allInputSources, run } from '../expression.js';
+import { d } from './bool_conversion.cache.js';
import { unary } from './unary.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('unary/bool_conversion', {
- bool: () => {
- return [
- { input: bool(true), expected: bool(true) },
- { input: bool(false), expected: bool(false) },
- ];
- },
- u32: () => {
- return fullU32Range().map(u => {
- return { input: u32(u), expected: u === 0 ? bool(false) : bool(true) };
- });
- },
- i32: () => {
- return fullI32Range().map(i => {
- return { input: i32(i), expected: i === 0 ? bool(false) : bool(true) };
- });
- },
- f32: () => {
- return fullF32Range().map(f => {
- const expected: Scalar[] = [];
- if (f !== 0) {
- expected.push(bool(true));
- }
- if (isSubnormalNumberF32(f)) {
- expected.push(bool(false));
- }
- return { input: f32(f), expected: anyOf(...expected) };
- });
- },
- f16: () => {
- return fullF16Range().map(f => {
- const expected: Scalar[] = [];
- if (f !== 0) {
- expected.push(bool(true));
- }
- if (isSubnormalNumberF16(f)) {
- expected.push(bool(false));
- }
- return { input: f16(f), expected: anyOf(...expected) };
- });
- },
-});
-
/** Generate expression builder based on how the test case is to be vectorized */
function vectorizeToExpression(vectorize: undefined | 2 | 3 | 4): ShaderBuilder {
return vectorize === undefined ? unary('bool') : unary(`vec${vectorize}<bool>`);
@@ -95,7 +31,14 @@ Identity operation
)
.fn(async t => {
const cases = await d.get('bool');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeBool], TypeBool, t.params, cases);
+ await run(
+ t,
+ vectorizeToExpression(t.params.vectorize),
+ [Type.bool],
+ Type.bool,
+ t.params,
+ cases
+ );
});
g.test('u32')
@@ -113,7 +56,7 @@ The result is false if e is 0, and true otherwise.
)
.fn(async t => {
const cases = await d.get('u32');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeU32], TypeBool, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.u32], Type.bool, t.params, cases);
});
g.test('i32')
@@ -131,7 +74,7 @@ The result is false if e is 0, and true otherwise.
)
.fn(async t => {
const cases = await d.get('i32');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeI32], TypeBool, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.i32], Type.bool, t.params, cases);
});
g.test('f32')
@@ -149,7 +92,7 @@ The result is false if e is 0.0 or -0.0, and true otherwise.
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeF32], TypeBool, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.f32], Type.bool, t.params, cases);
});
g.test('f16')
@@ -170,5 +113,5 @@ The result is false if e is 0.0 or -0.0, and true otherwise.
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeF16], TypeBool, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.f16], Type.bool, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_logical.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_logical.spec.ts
index 01eaaab43a..58d4b9fc0f 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_logical.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_logical.spec.ts
@@ -4,7 +4,7 @@ Execution Tests for the boolean unary logical expression operations
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { bool, TypeBool } from '../../../../util/conversion.js';
+import { bool, Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { unary } from './unary.js';
@@ -29,5 +29,5 @@ Logical negation. The result is true when e is false and false when e is true. C
{ input: bool(false), expected: bool(true) },
];
- await run(t, unary('!'), [TypeBool], TypeBool, t.params, cases);
+ await run(t, unary('!'), [Type.bool], Type.bool, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_arithmetic.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_arithmetic.cache.ts
new file mode 100644
index 0000000000..7d9ee35eec
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_arithmetic.cache.ts
@@ -0,0 +1,13 @@
+import { FP } from '../../../../util/floating_point.js';
+import { scalarF16Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+export const d = makeCaseCache('unary/f16_arithmetic', {
+ negation: () => {
+ return FP.f16.generateScalarToIntervalCases(
+ scalarF16Range({ neg_norm: 250, neg_sub: 20, pos_sub: 20, pos_norm: 250 }),
+ 'unfiltered',
+ FP.f16.negationInterval
+ );
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_arithmetic.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_arithmetic.spec.ts
index 83d7579c07..813bbf7a64 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_arithmetic.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_arithmetic.spec.ts
@@ -4,26 +4,14 @@ Execution Tests for the f16 arithmetic unary expression operations
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF16 } from '../../../../util/conversion.js';
-import { FP } from '../../../../util/floating_point.js';
-import { fullF16Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
+import { d } from './f16_arithmetic.cache.js';
import { unary } from './unary.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('unary/f16_arithmetic', {
- negation: () => {
- return FP.f16.generateScalarToIntervalCases(
- fullF16Range({ neg_norm: 250, neg_sub: 20, pos_sub: 20, pos_norm: 250 }),
- 'unfiltered',
- FP.f16.negationInterval
- );
- },
-});
-
g.test('negation')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -40,5 +28,5 @@ Accuracy: Correctly rounded
})
.fn(async t => {
const cases = await d.get('negation');
- await run(t, unary('-'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, unary('-'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_conversion.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_conversion.cache.ts
new file mode 100644
index 0000000000..bb0eb091dd
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_conversion.cache.ts
@@ -0,0 +1,135 @@
+import { abstractInt, bool, f16, i32, u32 } from '../../../../util/conversion.js';
+import { FP, FPInterval } from '../../../../util/floating_point.js';
+import { fullI32Range, fullI64Range, fullU32Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+const f16FiniteRangeInterval = new FPInterval(
+ 'f16',
+ FP.f16.constants().negative.min,
+ FP.f16.constants().positive.max
+);
+
+// Cases: f32_matCxR_[non_]const
+// Note that f32 values may be not exactly representable in f16 and/or out of range.
+const f32_mat_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ ([true, false] as const).map(nonConst => ({
+ [`f32_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateMatrixToMatrixCases(
+ FP.f32.sparseMatrixRange(cols, rows),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.correctlyRoundedMatrix
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: f16_matCxR_[non_]const
+const f16_mat_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ ([true, false] as const).map(nonConst => ({
+ [`f16_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ // Input matrix is of f16 types, use f16.generateMatrixToMatrixCases.
+ return FP.f16.generateMatrixToMatrixCases(
+ FP.f16.sparseMatrixRange(cols, rows),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.correctlyRoundedMatrix
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: abstract_float_matCxR
+// Note that abstract float values may be not exactly representable in f16
+// and/or out of range.
+const abstract_float_mat_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).map(rows => ({
+ [`abstract_float_mat${cols}x${rows}`]: () => {
+ return FP.abstract.generateMatrixToMatrixCases(
+ FP.abstract.sparseMatrixRange(cols, rows),
+ 'finite',
+ FP.f16.correctlyRoundedMatrix
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('unary/f16_conversion', {
+ bool: () => {
+ return [
+ { input: bool(true), expected: f16(1.0) },
+ { input: bool(false), expected: f16(0.0) },
+ ];
+ },
+ u32_non_const: () => {
+ return [...fullU32Range(), 65504].map(u => {
+ return { input: u32(u), expected: FP.f16.correctlyRoundedInterval(u) };
+ });
+ },
+ u32_const: () => {
+ return [...fullU32Range(), 65504]
+ .filter(v => f16FiniteRangeInterval.contains(v))
+ .map(u => {
+ return { input: u32(u), expected: FP.f16.correctlyRoundedInterval(u) };
+ });
+ },
+ i32_non_const: () => {
+ return [...fullI32Range(), 65504, -65504].map(i => {
+ return { input: i32(i), expected: FP.f16.correctlyRoundedInterval(i) };
+ });
+ },
+ i32_const: () => {
+ return [...fullI32Range(), 65504, -65504]
+ .filter(v => f16FiniteRangeInterval.contains(v))
+ .map(i => {
+ return { input: i32(i), expected: FP.f16.correctlyRoundedInterval(i) };
+ });
+ },
+ abstract_int: () => {
+ return [...fullI64Range(), 65504n, -65504n]
+ .filter(v => f16FiniteRangeInterval.contains(Number(v)))
+ .map(i => {
+ return { input: abstractInt(i), expected: FP.f16.correctlyRoundedInterval(Number(i)) };
+ });
+ },
+ // Note that f32 values may be not exactly representable in f16 and/or out of range.
+ f32_non_const: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ [...FP.f32.scalarRange(), 65535.996, -65535.996],
+ 'unfiltered',
+ FP.f16.correctlyRoundedInterval
+ );
+ },
+ f32_const: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ [...FP.f32.scalarRange(), 65535.996, -65535.996],
+ 'finite',
+ FP.f16.correctlyRoundedInterval
+ );
+ },
+ // Note that abstract float values may be not exactly representable in f16.
+ abstract_float: () => {
+ return FP.abstract.generateScalarToIntervalCases(
+ [...FP.abstract.scalarRange(), 65535.996, -65535.996],
+ 'finite',
+ FP.f16.correctlyRoundedInterval
+ );
+ },
+ // All f16 values are exactly representable in f16.
+ f16: () => {
+ return FP.f16.scalarRange().map(f => {
+ return { input: f16(f), expected: FP.f16.correctlyRoundedInterval(f) };
+ });
+ },
+ ...f32_mat_cases,
+ ...f16_mat_cases,
+ ...abstract_float_mat_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_conversion.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_conversion.spec.ts
index 9eb84f0270..92bd9c6a07 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_conversion.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_conversion.spec.ts
@@ -4,132 +4,14 @@ Execution Tests for the f32 conversion operations
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import {
- bool,
- f16,
- i32,
- TypeBool,
- TypeF32,
- TypeF16,
- TypeI32,
- TypeMat,
- TypeU32,
- u32,
-} from '../../../../util/conversion.js';
-import { FP, FPInterval } from '../../../../util/floating_point.js';
-import {
- fullF32Range,
- fullF16Range,
- fullI32Range,
- fullU32Range,
- sparseMatrixF32Range,
- sparseMatrixF16Range,
-} from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
-import { allInputSources, run, ShaderBuilder } from '../expression.js';
+import { Type } from '../../../../util/conversion.js';
+import { ShaderBuilder, allInputSources, run, onlyConstInputSource } from '../expression.js';
+import { d } from './f16_conversion.cache.js';
import { unary } from './unary.js';
export const g = makeTestGroup(GPUTest);
-const f16FiniteRangeInterval = new FPInterval(
- 'f32',
- FP.f16.constants().negative.min,
- FP.f16.constants().positive.max
-);
-
-// Cases: f32_matCxR_[non_]const
-// Note that f32 values may be not exactly representable in f16 and/or out of range.
-const f32_mat_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).flatMap(rows =>
- ([true, false] as const).map(nonConst => ({
- [`f32_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateMatrixToMatrixCases(
- sparseMatrixF32Range(cols, rows),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.correctlyRoundedMatrix
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-// Cases: f16_matCxR_[non_]const
-const f16_mat_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).flatMap(rows =>
- ([true, false] as const).map(nonConst => ({
- [`f16_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
- // Input matrix is of f16 types, use f16.generateMatrixToMatrixCases.
- return FP.f16.generateMatrixToMatrixCases(
- sparseMatrixF16Range(cols, rows),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.correctlyRoundedMatrix
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('unary/f16_conversion', {
- bool: () => {
- return [
- { input: bool(true), expected: f16(1.0) },
- { input: bool(false), expected: f16(0.0) },
- ];
- },
- u32_non_const: () => {
- return [...fullU32Range(), 65504].map(u => {
- return { input: u32(u), expected: FP.f16.correctlyRoundedInterval(u) };
- });
- },
- u32_const: () => {
- return [...fullU32Range(), 65504]
- .filter(v => f16FiniteRangeInterval.contains(v))
- .map(u => {
- return { input: u32(u), expected: FP.f16.correctlyRoundedInterval(u) };
- });
- },
- i32_non_const: () => {
- return [...fullI32Range(), 65504, -65504].map(i => {
- return { input: i32(i), expected: FP.f16.correctlyRoundedInterval(i) };
- });
- },
- i32_const: () => {
- return [...fullI32Range(), 65504, -65504]
- .filter(v => f16FiniteRangeInterval.contains(v))
- .map(i => {
- return { input: i32(i), expected: FP.f16.correctlyRoundedInterval(i) };
- });
- },
- // Note that f32 values may be not exactly representable in f16 and/or out of range.
- f32_non_const: () => {
- return FP.f32.generateScalarToIntervalCases(
- [...fullF32Range(), 65535.996, -65535.996],
- 'unfiltered',
- FP.f16.correctlyRoundedInterval
- );
- },
- f32_const: () => {
- return FP.f32.generateScalarToIntervalCases(
- [...fullF32Range(), 65535.996, -65535.996],
- 'finite',
- FP.f16.correctlyRoundedInterval
- );
- },
- // All f16 values are exactly representable in f16.
- f16: () => {
- return fullF16Range().map(f => {
- return { input: f16(f), expected: FP.f16.correctlyRoundedInterval(f) };
- });
- },
- ...f32_mat_cases,
- ...f16_mat_cases,
-});
-
/** Generate a ShaderBuilder based on how the test case is to be vectorized */
function vectorizeToExpression(vectorize: undefined | 2 | 3 | 4): ShaderBuilder {
return vectorize === undefined ? unary('f16') : unary(`vec${vectorize}<f16>`);
@@ -157,7 +39,7 @@ The result is 1.0 if e is true and 0.0 otherwise
})
.fn(async t => {
const cases = await d.get('bool');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeBool], TypeF16, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.bool], Type.f16, t.params, cases);
});
g.test('u32')
@@ -177,7 +59,7 @@ Converted to f16, +/-Inf if out of range
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'u32_const' : 'u32_non_const');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeU32], TypeF16, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.u32], Type.f16, t.params, cases);
});
g.test('i32')
@@ -197,7 +79,36 @@ Converted to f16, +/-Inf if out of range
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'i32_const' : 'i32_non_const');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeI32], TypeF16, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.i32], Type.f16, t.params, cases);
+ });
+
+g.test('abstract_int')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(
+ `
+f16(e), where e is an AbstractInt
+
+Converted to f16, +/-Inf if out of range
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+ })
+ .fn(async t => {
+ const cases = await d.get('abstract_int');
+ await run(
+ t,
+ vectorizeToExpression(t.params.vectorize),
+ [Type.abstractInt],
+ Type.f16,
+ t.params,
+ cases
+ );
});
g.test('f32')
@@ -217,7 +128,7 @@ Correctly rounded to f16
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeF32], TypeF16, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.f32], Type.f16, t.params, cases);
});
g.test('f32_mat')
@@ -243,8 +154,8 @@ g.test('f32_mat')
await run(
t,
matrixExperession(cols, rows),
- [TypeMat(cols, rows, TypeF32)],
- TypeMat(cols, rows, TypeF16),
+ [Type.mat(cols, rows, Type.f32)],
+ Type.mat(cols, rows, Type.f16),
t.params,
cases
);
@@ -267,7 +178,7 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeF16], TypeF16, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.f16], Type.f16, t.params, cases);
});
g.test('f16_mat')
@@ -293,8 +204,63 @@ g.test('f16_mat')
await run(
t,
matrixExperession(cols, rows),
- [TypeMat(cols, rows, TypeF16)],
- TypeMat(cols, rows, TypeF16),
+ [Type.mat(cols, rows, Type.f16)],
+ Type.mat(cols, rows, Type.f16),
+ t.params,
+ cases
+ );
+ });
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(
+ `
+f16(e), where e is an AbstractFloat
+
+Correctly rounded to f16
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+ })
+ .fn(async t => {
+ const cases = await d.get('abstract_float');
+ await run(
+ t,
+ vectorizeToExpression(t.params.vectorize),
+ [Type.abstractFloat],
+ Type.f16,
+ t.params,
+ cases
+ );
+ });
+
+g.test('abstract_float_mat')
+ .specURL('https://www.w3.org/TR/WGSL/#matrix-builtin-functions')
+ .desc(`AbstractFloat matrix to f16 matrix tests`)
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('cols', [2, 3, 4] as const)
+ .combine('rows', [2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+ })
+ .fn(async t => {
+ const cols = t.params.cols;
+ const rows = t.params.rows;
+ const cases = await d.get(`abstract_float_mat${cols}x${rows}`);
+ await run(
+ t,
+ matrixExperession(cols, rows),
+ [Type.mat(cols, rows, Type.abstractFloat)],
+ Type.mat(cols, rows, Type.f16),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_arithmetic.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_arithmetic.cache.ts
new file mode 100644
index 0000000000..b23ed3216b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_arithmetic.cache.ts
@@ -0,0 +1,13 @@
+import { FP } from '../../../../util/floating_point.js';
+import { scalarF32Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+export const d = makeCaseCache('unary/f32_arithmetic', {
+ negation: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ scalarF32Range({ neg_norm: 250, neg_sub: 20, pos_sub: 20, pos_norm: 250 }),
+ 'unfiltered',
+ FP.f32.negationInterval
+ );
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_arithmetic.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_arithmetic.spec.ts
index f53cff46d8..3bb48705e8 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_arithmetic.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_arithmetic.spec.ts
@@ -4,26 +4,14 @@ Execution Tests for the f32 arithmetic unary expression operations
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF32 } from '../../../../util/conversion.js';
-import { FP } from '../../../../util/floating_point.js';
-import { fullF32Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
+import { d } from './f32_arithmetic.cache.js';
import { unary } from './unary.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('unary/f32_arithmetic', {
- negation: () => {
- return FP.f32.generateScalarToIntervalCases(
- fullF32Range({ neg_norm: 250, neg_sub: 20, pos_sub: 20, pos_norm: 250 }),
- 'unfiltered',
- FP.f32.negationInterval
- );
- },
-});
-
g.test('negation')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -37,5 +25,5 @@ Accuracy: Correctly rounded
)
.fn(async t => {
const cases = await d.get('negation');
- await run(t, unary('-'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, unary('-'), [Type.f32], Type.f32, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_conversion.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_conversion.cache.ts
new file mode 100644
index 0000000000..f61435f07c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_conversion.cache.ts
@@ -0,0 +1,79 @@
+import { bool, f16, f32, i32, u32 } from '../../../../util/conversion.js';
+import { FP } from '../../../../util/floating_point.js';
+import {
+ fullI32Range,
+ fullU32Range,
+ scalarF16Range,
+ scalarF32Range,
+ sparseMatrixF16Range,
+ sparseMatrixF32Range,
+} from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+// Cases: f32_matCxR_[non_]const
+const f32_mat_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ ([true, false] as const).map(nonConst => ({
+ [`f32_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateMatrixToMatrixCases(
+ sparseMatrixF32Range(cols, rows),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f32.correctlyRoundedMatrix
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: f16_matCxR_[non_]const
+// Note that all f16 values are exactly representable in f32.
+const f16_mat_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ ([true, false] as const).map(nonConst => ({
+ [`f16_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ // Input matrix is of f16 types, use f16.generateMatrixToMatrixCases.
+ return FP.f16.generateMatrixToMatrixCases(
+ sparseMatrixF16Range(cols, rows),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f32.correctlyRoundedMatrix
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('unary/f32_conversion', {
+ bool: () => {
+ return [
+ { input: bool(true), expected: f32(1.0) },
+ { input: bool(false), expected: f32(0.0) },
+ ];
+ },
+ u32: () => {
+ return fullU32Range().map(u => {
+ return { input: u32(u), expected: FP.f32.correctlyRoundedInterval(u) };
+ });
+ },
+ i32: () => {
+ return fullI32Range().map(i => {
+ return { input: i32(i), expected: FP.f32.correctlyRoundedInterval(i) };
+ });
+ },
+ f32: () => {
+ return scalarF32Range().map(f => {
+ return { input: f32(f), expected: FP.f32.correctlyRoundedInterval(f) };
+ });
+ },
+ // All f16 values are exactly representable in f32.
+ f16: () => {
+ return scalarF16Range().map(f => {
+ return { input: f16(f), expected: FP.f32.correctlyRoundedInterval(f) };
+ });
+ },
+ ...f32_mat_cases,
+ ...f16_mat_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_conversion.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_conversion.spec.ts
index 223b13c2d5..464fdee44e 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_conversion.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_conversion.spec.ts
@@ -4,103 +4,14 @@ Execution Tests for the f32 conversion operations
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import {
- bool,
- f32,
- f16,
- i32,
- TypeBool,
- TypeF32,
- TypeF16,
- TypeI32,
- TypeMat,
- TypeU32,
- u32,
-} from '../../../../util/conversion.js';
-import { FP } from '../../../../util/floating_point.js';
-import {
- fullF32Range,
- fullF16Range,
- fullI32Range,
- fullU32Range,
- sparseMatrixF32Range,
- sparseMatrixF16Range,
-} from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
-import { allInputSources, run, ShaderBuilder } from '../expression.js';
+import { Type } from '../../../../util/conversion.js';
+import { ShaderBuilder, allInputSources, run } from '../expression.js';
+import { d } from './f32_conversion.cache.js';
import { unary } from './unary.js';
export const g = makeTestGroup(GPUTest);
-// Cases: f32_matCxR_[non_]const
-const f32_mat_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).flatMap(rows =>
- ([true, false] as const).map(nonConst => ({
- [`f32_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateMatrixToMatrixCases(
- sparseMatrixF32Range(cols, rows),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.correctlyRoundedMatrix
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-// Cases: f16_matCxR_[non_]const
-// Note that all f16 values are exactly representable in f32.
-const f16_mat_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).flatMap(rows =>
- ([true, false] as const).map(nonConst => ({
- [`f16_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
- // Input matrix is of f16 types, use f16.generateMatrixToMatrixCases.
- return FP.f16.generateMatrixToMatrixCases(
- sparseMatrixF16Range(cols, rows),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.correctlyRoundedMatrix
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('unary/f32_conversion', {
- bool: () => {
- return [
- { input: bool(true), expected: f32(1.0) },
- { input: bool(false), expected: f32(0.0) },
- ];
- },
- u32: () => {
- return fullU32Range().map(u => {
- return { input: u32(u), expected: FP.f32.correctlyRoundedInterval(u) };
- });
- },
- i32: () => {
- return fullI32Range().map(i => {
- return { input: i32(i), expected: FP.f32.correctlyRoundedInterval(i) };
- });
- },
- f32: () => {
- return fullF32Range().map(f => {
- return { input: f32(f), expected: FP.f32.correctlyRoundedInterval(f) };
- });
- },
- // All f16 values are exactly representable in f32.
- f16: () => {
- return fullF16Range().map(f => {
- return { input: f16(f), expected: FP.f32.correctlyRoundedInterval(f) };
- });
- },
- ...f32_mat_cases,
- ...f16_mat_cases,
-});
-
/** Generate a ShaderBuilder based on how the test case is to be vectorized */
function vectorizeToExpression(vectorize: undefined | 2 | 3 | 4): ShaderBuilder {
return vectorize === undefined ? unary('f32') : unary(`vec${vectorize}<f32>`);
@@ -125,7 +36,7 @@ The result is 1.0 if e is true and 0.0 otherwise
)
.fn(async t => {
const cases = await d.get('bool');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeBool], TypeF32, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.bool], Type.f32, t.params, cases);
});
g.test('u32')
@@ -142,7 +53,7 @@ Converted to f32
)
.fn(async t => {
const cases = await d.get('u32');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeU32], TypeF32, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.u32], Type.f32, t.params, cases);
});
g.test('i32')
@@ -159,7 +70,7 @@ Converted to f32
)
.fn(async t => {
const cases = await d.get('i32');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeI32], TypeF32, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.i32], Type.f32, t.params, cases);
});
g.test('f32')
@@ -176,7 +87,7 @@ Identity operation
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeF32], TypeF32, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.f32], Type.f32, t.params, cases);
});
g.test('f32_mat')
@@ -199,8 +110,8 @@ g.test('f32_mat')
await run(
t,
matrixExperession(cols, rows),
- [TypeMat(cols, rows, TypeF32)],
- TypeMat(cols, rows, TypeF32),
+ [Type.mat(cols, rows, Type.f32)],
+ Type.mat(cols, rows, Type.f32),
t.params,
cases
);
@@ -223,7 +134,7 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeF16], TypeF32, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.f16], Type.f32, t.params, cases);
});
g.test('f16_mat')
@@ -249,8 +160,8 @@ g.test('f16_mat')
await run(
t,
matrixExperession(cols, rows),
- [TypeMat(cols, rows, TypeF16)],
- TypeMat(cols, rows, TypeF32),
+ [Type.mat(cols, rows, Type.f16)],
+ Type.mat(cols, rows, Type.f32),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_arithmetic.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_arithmetic.cache.ts
new file mode 100644
index 0000000000..b7206bcf45
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_arithmetic.cache.ts
@@ -0,0 +1,11 @@
+import { i32 } from '../../../../util/conversion.js';
+import { fullI32Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+export const d = makeCaseCache('unary/i32_arithmetic', {
+ negation: () => {
+ return fullI32Range().map(e => {
+ return { input: i32(e), expected: i32(-e) };
+ });
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_arithmetic.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_arithmetic.spec.ts
index 14519b8967..a7d16a96cd 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_arithmetic.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_arithmetic.spec.ts
@@ -4,23 +4,14 @@ Execution Tests for the i32 arithmetic unary expression operations
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { i32, TypeI32 } from '../../../../util/conversion.js';
-import { fullI32Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
+import { d } from './i32_arithmetic.cache.js';
import { unary } from './unary.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('unary/i32_arithmetic', {
- negation: () => {
- return fullI32Range().map(e => {
- return { input: i32(e), expected: i32(-e) };
- });
- },
-});
-
g.test('negation')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -33,5 +24,5 @@ Expression: -x
)
.fn(async t => {
const cases = await d.get('negation');
- await run(t, unary('-'), [TypeI32], TypeI32, t.params, cases);
+ await run(t, unary('-'), [Type.i32], Type.i32, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_complement.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_complement.spec.ts
index e8bda51b51..01e5728d70 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_complement.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_complement.spec.ts
@@ -4,23 +4,14 @@ Execution Tests for the i32 bitwise complement operation
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { i32, TypeI32 } from '../../../../util/conversion.js';
+import { i32, Type } from '../../../../util/conversion.js';
import { fullI32Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
import { allInputSources, run } from '../expression.js';
import { unary } from './unary.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('unary/i32_complement', {
- complement: () => {
- return fullI32Range().map(e => {
- return { input: i32(e), expected: i32(~e) };
- });
- },
-});
-
g.test('i32_complement')
.specURL('https://www.w3.org/TR/WGSL/#bit-expr')
.desc(
@@ -32,6 +23,8 @@ Expression: ~x
u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
)
.fn(async t => {
- const cases = await d.get('complement');
- await run(t, unary('~'), [TypeI32], TypeI32, t.params, cases);
+ const cases = fullI32Range().map(e => {
+ return { input: i32(e), expected: i32(~e) };
+ });
+ await run(t, unary('~'), [Type.i32], Type.i32, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_conversion.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_conversion.cache.ts
new file mode 100644
index 0000000000..1c2d01548d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_conversion.cache.ts
@@ -0,0 +1,116 @@
+import { kValue } from '../../../../util/constants.js';
+import {
+ abstractFloat,
+ abstractInt,
+ bool,
+ f16,
+ f32,
+ i32,
+ u32,
+} from '../../../../util/conversion.js';
+import {
+ fullI32Range,
+ fullU32Range,
+ quantizeToF16,
+ quantizeToF32,
+ scalarF16Range,
+ scalarF32Range,
+ scalarF64Range,
+} from '../../../../util/math.js';
+import { reinterpretU32AsI32 } from '../../../../util/reinterpret.js';
+import { makeCaseCache } from '../case_cache.js';
+
+export const d = makeCaseCache('unary/i32_conversion', {
+ bool: () => {
+ return [
+ { input: bool(true), expected: i32(1) },
+ { input: bool(false), expected: i32(0) },
+ ];
+ },
+ abstractInt: () => {
+ return fullI32Range().map(i => {
+ return { input: abstractInt(BigInt(i)), expected: i32(i) };
+ });
+ },
+ u32: () => {
+ return fullU32Range().map(u => {
+ return { input: u32(u), expected: i32(reinterpretU32AsI32(u)) };
+ });
+ },
+ i32: () => {
+ return fullI32Range().map(i => {
+ return { input: i32(i), expected: i32(i) };
+ });
+ },
+ abstractFloat: () => {
+ return scalarF64Range().map(f => {
+ // Handles zeros and subnormals
+ if (Math.abs(f) < 1.0) {
+ return { input: abstractFloat(f), expected: i32(0) };
+ }
+
+ if (f <= kValue.i32.negative.min) {
+ return { input: abstractFloat(f), expected: i32(kValue.i32.negative.min) };
+ }
+
+ if (f >= kValue.i32.positive.max) {
+ return { input: abstractFloat(f), expected: i32(kValue.i32.positive.max) };
+ }
+
+ // All i32s are representable as f64, and both AbstractFloat and number
+ // are f64 internally, so there is no need for special casing like f32 and
+ // f16 below.
+ return { input: abstractFloat(f), expected: i32(Math.trunc(f)) };
+ });
+ },
+ f32: () => {
+ return scalarF32Range().map(f => {
+ // Handles zeros and subnormals
+ if (Math.abs(f) < 1.0) {
+ return { input: f32(f), expected: i32(0) };
+ }
+
+ if (f <= kValue.i32.negative.min) {
+ return { input: f32(f), expected: i32(kValue.i32.negative.min) };
+ }
+
+ if (f >= kValue.i32.positive.max) {
+ return { input: f32(f), expected: i32(kValue.i32.positive.max) };
+ }
+
+ // All f32 no larger than 2^24 has a precise interger part and a fractional part, just need
+ // to trunc towards 0 for the result integer.
+ if (Math.abs(f) <= 2 ** 24) {
+ return { input: f32(f), expected: i32(Math.trunc(f)) };
+ }
+
+ // All f32s between 2 ** 24 and kValue.i32.negative.min/.positive.max are
+ // integers, so in theory one could use them directly, expect that number
+ // is actually f64 internally, so they need to be quantized to f32 first.
+ // Cannot just use trunc here, since that might produce a i32 value that
+ // is precise in f64, but not in f32.
+ return { input: f32(f), expected: i32(quantizeToF32(f)) };
+ });
+ },
+ f16: () => {
+ // Note that finite f16 values are always in range of i32.
+ return scalarF16Range().map(f => {
+ // Handles zeros and subnormals
+ if (Math.abs(f) < 1.0) {
+ return { input: f16(f), expected: i32(0) };
+ }
+
+ // All f16 no larger than <= 2^12 has a precise interger part and a fractional part, just need
+ // to trunc towards 0 for the result integer.
+ if (Math.abs(f) <= 2 ** 12) {
+ return { input: f16(f), expected: i32(Math.trunc(f)) };
+ }
+
+ // All f16s larger than 2 ** 12 are integers, so in theory one could use them directly, expect
+ // that number is actually f64 internally, so they need to be quantized to f16 first.
+ // Cannot just use trunc here, since that might produce a i32 value that is precise in f64,
+ // but not in f16.
+ return { input: f16(f), expected: i32(quantizeToF16(f)) };
+ });
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_conversion.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_conversion.spec.ts
index a77aa0e4d3..b47ffe0d07 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_conversion.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_conversion.spec.ts
@@ -4,104 +4,14 @@ Execution Tests for the i32 conversion operations
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { kValue } from '../../../../util/constants.js';
-import {
- bool,
- f32,
- f16,
- i32,
- TypeBool,
- TypeF32,
- TypeF16,
- TypeI32,
- TypeU32,
- u32,
-} from '../../../../util/conversion.js';
-import {
- fullF32Range,
- fullF16Range,
- fullI32Range,
- fullU32Range,
- quantizeToF32,
- quantizeToF16,
-} from '../../../../util/math.js';
-import { reinterpretU32AsI32 } from '../../../../util/reinterpret.js';
-import { makeCaseCache } from '../case_cache.js';
-import { allInputSources, run, ShaderBuilder } from '../expression.js';
+import { Type } from '../../../../util/conversion.js';
+import { ShaderBuilder, allInputSources, run, onlyConstInputSource } from '../expression.js';
+import { d } from './i32_conversion.cache.js';
import { unary } from './unary.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('unary/i32_conversion', {
- bool: () => {
- return [
- { input: bool(true), expected: i32(1) },
- { input: bool(false), expected: i32(0) },
- ];
- },
- u32: () => {
- return fullU32Range().map(u => {
- return { input: u32(u), expected: i32(reinterpretU32AsI32(u)) };
- });
- },
- i32: () => {
- return fullI32Range().map(i => {
- return { input: i32(i), expected: i32(i) };
- });
- },
- f32: () => {
- return fullF32Range().map(f => {
- // Handles zeros and subnormals
- if (Math.abs(f) < 1.0) {
- return { input: f32(f), expected: i32(0) };
- }
-
- if (f <= kValue.i32.negative.min) {
- return { input: f32(f), expected: i32(kValue.i32.negative.min) };
- }
-
- if (f >= kValue.i32.positive.max) {
- return { input: f32(f), expected: i32(kValue.i32.positive.max) };
- }
-
- // All f32 no larger than 2^24 has a precise interger part and a fractional part, just need
- // to trunc towards 0 for the result integer.
- if (Math.abs(f) <= 2 ** 24) {
- return { input: f32(f), expected: i32(Math.trunc(f)) };
- }
-
- // All f32s between 2 ** 24 and kValue.i32.negative.min/.positive.max are
- // integers, so in theory one could use them directly, expect that number
- // is actually f64 internally, so they need to be quantized to f32 first.
- // Cannot just use trunc here, since that might produce a i32 value that
- // is precise in f64, but not in f32.
- return { input: f32(f), expected: i32(quantizeToF32(f)) };
- });
- },
- f16: () => {
- // Note that finite f16 values are always in range of i32.
- return fullF16Range().map(f => {
- // Handles zeros and subnormals
- if (Math.abs(f) < 1.0) {
- return { input: f16(f), expected: i32(0) };
- }
-
- // All f16 no larger than <= 2^12 has a precise interger part and a fractional part, just need
- // to trunc towards 0 for the result integer.
- if (Math.abs(f) <= 2 ** 12) {
- return { input: f16(f), expected: i32(Math.trunc(f)) };
- }
-
- // All f16s larger than 2 ** 12 are integers, so in theory one could use them directly, expect
- // that number is actually f64 internally, so they need to be quantized to f16 first.
- // Cannot just use trunc here, since that might produce a i32 value that is precise in f64,
- // but not in f16.
- return { input: f16(f), expected: i32(quantizeToF16(f)) };
- });
- },
-});
-
/** Generate a ShaderBuilder based on how the test case is to be vectorized */
function vectorizeToExpression(vectorize: undefined | 2 | 3 | 4): ShaderBuilder {
return vectorize === undefined ? unary('i32') : unary(`vec${vectorize}<i32>`);
@@ -121,7 +31,7 @@ The result is 1u if e is true and 0u otherwise
)
.fn(async t => {
const cases = await d.get('bool');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeBool], TypeI32, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.bool], Type.i32, t.params, cases);
});
g.test('u32')
@@ -138,7 +48,7 @@ Reinterpretation of bits
)
.fn(async t => {
const cases = await d.get('u32');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeU32], TypeI32, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.u32], Type.i32, t.params, cases);
});
g.test('i32')
@@ -155,7 +65,7 @@ Identity operation
)
.fn(async t => {
const cases = await d.get('i32');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeI32], TypeI32, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.i32], Type.i32, t.params, cases);
});
g.test('f32')
@@ -172,7 +82,7 @@ e is converted to i32, rounding towards zero
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeF32], TypeI32, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.f32], Type.i32, t.params, cases);
});
g.test('f16')
@@ -192,5 +102,57 @@ e is converted to u32, rounding towards zero
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeF16], TypeI32, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.f16], Type.i32, t.params, cases);
+ });
+
+g.test('abstract_int')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(
+ `
+i32(e), where e is an AbstractInt
+
+Identity operation if e is in bounds for i32, otherwise shader creation error
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('abstractInt');
+ await run(
+ t,
+ vectorizeToExpression(t.params.vectorize),
+ [Type.abstractInt],
+ Type.i32,
+ t.params,
+ cases
+ );
+ });
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(
+ `
+i32(e), where e is an AbstractFloat
+
+e is converted to i32, rounding towards zero
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('abstractFloat');
+ await run(
+ t,
+ vectorizeToExpression(t.params.vectorize),
+ [Type.abstractFloat],
+ Type.i32,
+ t.params,
+ cases
+ );
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_complement.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_complement.spec.ts
index 446e0918bd..74251a32c6 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_complement.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_complement.spec.ts
@@ -4,23 +4,14 @@ Execution Tests for the u32 bitwise complement operation
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { u32, TypeU32 } from '../../../../util/conversion.js';
+import { Type, u32 } from '../../../../util/conversion.js';
import { fullU32Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
import { allInputSources, run } from '../expression.js';
import { unary } from './unary.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('unary/u32_complement', {
- complement: () => {
- return fullU32Range().map(e => {
- return { input: u32(e), expected: u32(~e) };
- });
- },
-});
-
g.test('u32_complement')
.specURL('https://www.w3.org/TR/WGSL/#bit-expr')
.desc(
@@ -32,6 +23,8 @@ Expression: ~x
u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
)
.fn(async t => {
- const cases = await d.get('complement');
- await run(t, unary('~'), [TypeU32], TypeU32, t.params, cases);
+ const cases = fullU32Range().map(e => {
+ return { input: u32(e), expected: u32(~e) };
+ });
+ await run(t, unary('~'), [Type.u32], Type.u32, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_conversion.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_conversion.cache.ts
new file mode 100644
index 0000000000..1ef307810f
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_conversion.cache.ts
@@ -0,0 +1,107 @@
+import { kValue } from '../../../../util/constants.js';
+import {
+ abstractFloat,
+ abstractInt,
+ bool,
+ f16,
+ f32,
+ i32,
+ u32,
+} from '../../../../util/conversion.js';
+import {
+ fullI32Range,
+ fullU32Range,
+ quantizeToF16,
+ quantizeToF32,
+ scalarF16Range,
+ scalarF32Range,
+ scalarF64Range,
+} from '../../../../util/math.js';
+import { reinterpretI32AsU32 } from '../../../../util/reinterpret.js';
+import { makeCaseCache } from '../case_cache.js';
+
+export const d = makeCaseCache('unary/u32_conversion', {
+ bool: () => {
+ return [
+ { input: bool(true), expected: u32(1) },
+ { input: bool(false), expected: u32(0) },
+ ];
+ },
+ abstractInt: () => {
+ return fullU32Range().map(u => {
+ return { input: abstractInt(BigInt(u)), expected: u32(u) };
+ });
+ },
+ u32: () => {
+ return fullU32Range().map(u => {
+ return { input: u32(u), expected: u32(u) };
+ });
+ },
+ i32: () => {
+ return fullI32Range().map(i => {
+ return { input: i32(i), expected: u32(reinterpretI32AsU32(i)) };
+ });
+ },
+ abstractFloat: () => {
+ return [...scalarF64Range(), -1].map(f => {
+ // Handles zeros, subnormals, and negatives
+ if (f < 1.0) {
+ return { input: abstractFloat(f), expected: u32(0) };
+ }
+
+ if (f >= kValue.u32.max) {
+ return { input: abstractFloat(f), expected: u32(kValue.u32.max) };
+ }
+
+ // All u32s are representable as f64s and number is a f64 internally, so
+ // no need for special handling like is done for f32 and f16 below.
+ return { input: abstractFloat(f), expected: u32(Math.floor(f)) };
+ });
+ },
+ f32: () => {
+ return scalarF32Range().map(f => {
+ // Handles zeros, subnormals, and negatives
+ if (f < 1.0) {
+ return { input: f32(f), expected: u32(0) };
+ }
+
+ if (f >= kValue.u32.max) {
+ return { input: f32(f), expected: u32(kValue.u32.max) };
+ }
+
+ // All f32 no larger than 2^24 has a precise integer part and a fractional
+ // part, just need to trunc towards 0 for the result integer.
+ if (f <= 2 ** 24) {
+ return { input: f32(f), expected: u32(Math.floor(f)) };
+ }
+
+ // All f32s between 2 ** 24 and kValue.u32.max are integers, so in theory
+ // one could use them directly, expect that number is actually f64
+ // internally, so they need to be quantized to f32 first.
+ // Cannot just use floor here, since that might produce a u32 value that
+ // is precise in f64, but not in f32.
+ return { input: f32(f), expected: u32(quantizeToF32(f)) };
+ });
+ },
+ f16: () => {
+ // Note that all positive finite f16 values are in range of u32.
+ return scalarF16Range().map(f => {
+ // Handles zeros, subnormals, and negatives
+ if (f < 1.0) {
+ return { input: f16(f), expected: u32(0) };
+ }
+
+ // All f16 no larger than <= 2^12 has a precise integer part and a
+ // fractional part, just need to trunc towards 0 for the result integer.
+ if (f <= 2 ** 12) {
+ return { input: f16(f), expected: u32(Math.trunc(f)) };
+ }
+
+ // All f16s larger than 2 ** 12 are integers, so in theory one could use
+ // them directly, expect that number is actually f64 internally, so they
+ // need to be quantized to f16 first.Cannot just use trunc here, since
+ // that might produce a u32 value that is precise in f64, but not in f16.
+ return { input: f16(f), expected: u32(quantizeToF16(f)) };
+ });
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_conversion.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_conversion.spec.ts
index 87dc6e7a5d..6d342afffb 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_conversion.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_conversion.spec.ts
@@ -4,100 +4,14 @@ Execution Tests for the u32 conversion operations
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { kValue } from '../../../../util/constants.js';
-import {
- bool,
- f32,
- f16,
- i32,
- TypeBool,
- TypeF32,
- TypeF16,
- TypeI32,
- TypeU32,
- u32,
-} from '../../../../util/conversion.js';
-import {
- fullF32Range,
- fullF16Range,
- fullI32Range,
- fullU32Range,
- quantizeToF32,
- quantizeToF16,
-} from '../../../../util/math.js';
-import { reinterpretI32AsU32 } from '../../../../util/reinterpret.js';
-import { makeCaseCache } from '../case_cache.js';
-import { allInputSources, run, ShaderBuilder } from '../expression.js';
+import { Type } from '../../../../util/conversion.js';
+import { ShaderBuilder, allInputSources, run, onlyConstInputSource } from '../expression.js';
+import { d } from './u32_conversion.cache.js';
import { unary } from './unary.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('unary/u32_conversion', {
- bool: () => {
- return [
- { input: bool(true), expected: u32(1) },
- { input: bool(false), expected: u32(0) },
- ];
- },
- u32: () => {
- return fullU32Range().map(u => {
- return { input: u32(u), expected: u32(u) };
- });
- },
- i32: () => {
- return fullI32Range().map(i => {
- return { input: i32(i), expected: u32(reinterpretI32AsU32(i)) };
- });
- },
- f32: () => {
- return fullF32Range().map(f => {
- // Handles zeros, subnormals, and negatives
- if (f < 1.0) {
- return { input: f32(f), expected: u32(0) };
- }
-
- if (f >= kValue.u32.max) {
- return { input: f32(f), expected: u32(kValue.u32.max) };
- }
-
- // All f32 no larger than 2^24 has a precise interger part and a fractional part, just need
- // to trunc towards 0 for the result integer.
- if (f <= 2 ** 24) {
- return { input: f32(f), expected: u32(Math.floor(f)) };
- }
-
- // All f32s between 2 ** 24 and kValue.u32.max are integers, so in theory
- // one could use them directly, expect that number is actually f64
- // internally, so they need to be quantized to f32 first.
- // Cannot just use floor here, since that might produce a u32 value that
- // is precise in f64, but not in f32.
- return { input: f32(f), expected: u32(quantizeToF32(f)) };
- });
- },
- f16: () => {
- // Note that all positive finite f16 values are in range of u32.
- return fullF16Range().map(f => {
- // Handles zeros, subnormals, and negatives
- if (f < 1.0) {
- return { input: f16(f), expected: u32(0) };
- }
-
- // All f16 no larger than <= 2^12 has a precise interger part and a fractional part, just need
- // to trunc towards 0 for the result integer.
- if (f <= 2 ** 12) {
- return { input: f16(f), expected: u32(Math.trunc(f)) };
- }
-
- // All f16s larger than 2 ** 12 are integers, so in theory one could use them directly, expect
- // that number is actually f64 internally, so they need to be quantized to f16 first.
- // Cannot just use trunc here, since that might produce a u32 value that is precise in f64,
- // but not in f16.
- return { input: f16(f), expected: u32(quantizeToF16(f)) };
- });
- },
-});
-
/** Generate a ShaderBuilder based on how the test case is to be vectorized */
function vectorizeToExpression(vectorize: undefined | 2 | 3 | 4): ShaderBuilder {
return vectorize === undefined ? unary('u32') : unary(`vec${vectorize}<u32>`);
@@ -117,7 +31,7 @@ The result is 1u if e is true and 0u otherwise
)
.fn(async t => {
const cases = await d.get('bool');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeBool], TypeU32, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.bool], Type.u32, t.params, cases);
});
g.test('u32')
@@ -134,7 +48,7 @@ Identity operation
)
.fn(async t => {
const cases = await d.get('u32');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeU32], TypeU32, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.u32], Type.u32, t.params, cases);
});
g.test('i32')
@@ -151,7 +65,7 @@ Reinterpretation of bits
)
.fn(async t => {
const cases = await d.get('i32');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeI32], TypeU32, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.i32], Type.u32, t.params, cases);
});
g.test('f32')
@@ -168,7 +82,7 @@ e is converted to u32, rounding towards zero
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeF32], TypeU32, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.f32], Type.u32, t.params, cases);
});
g.test('f16')
@@ -188,7 +102,7 @@ e is converted to u32, rounding towards zero
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeF16], TypeU32, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.f16], Type.u32, t.params, cases);
});
g.test('abstract_int')
@@ -201,6 +115,44 @@ Identity operation if the e can be represented in u32, otherwise it produces a s
`
)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('abstractInt');
+ await run(
+ t,
+ vectorizeToExpression(t.params.vectorize),
+ [Type.abstractInt],
+ Type.u32,
+ t.params,
+ cases
+ );
+ });
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(
+ `
+u32(e), where e is an AbstractFloat
+
+e is converted to u32, rounding towards zero
+`
)
- .unimplemented();
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('abstractFloat');
+ await run(
+ t,
+ vectorizeToExpression(t.params.vectorize),
+ [Type.abstractFloat],
+ Type.u32,
+ t.params,
+ cases
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/unary.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/unary.ts
index 160e465178..109e6c1421 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/unary.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/unary.ts
@@ -1,5 +1,6 @@
import {
abstractFloatShaderBuilder,
+ abstractIntShaderBuilder,
basicExpressionBuilder,
ShaderBuilder,
} from '../expression.js';
@@ -10,6 +11,11 @@ export function unary(op: string): ShaderBuilder {
}
/* @returns a ShaderBuilder that evaluates a prefix unary operation that returns AbstractFloats */
-export function abstractUnary(op: string): ShaderBuilder {
+export function abstractFloatUnary(op: string): ShaderBuilder {
return abstractFloatShaderBuilder(value => `${op}(${value})`);
}
+
+/* @returns a ShaderBuilder that evaluates a prefix unary operation that returns AbstractInts */
+export function abstractIntUnary(op: string): ShaderBuilder {
+ return abstractIntShaderBuilder(value => `${op}(${value})`);
+}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/call.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/call.spec.ts
index b86134a58c..a146f5742f 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/call.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/call.spec.ts
@@ -81,3 +81,116 @@ fn c() {
}`,
}));
});
+
+g.test('arg_eval')
+ .desc('Test that arguments are evaluated left to right')
+ .params(u => u.combine('preventValueOptimizations', [true, false]))
+ .fn(t => {
+ runFlowControlTest(t, f => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ a(b(), c(), d());
+ ${f.expect_order(5)}
+`,
+ extra: `
+fn a(p1 : u32, p2 : u32, p3 : u32) {
+ ${f.expect_order(4)}
+}
+fn b() -> u32 {
+ ${f.expect_order(1)}
+ return 0;
+}
+fn c() -> u32 {
+ ${f.expect_order(2)}
+ return 0;
+}
+fn d() -> u32 {
+ ${f.expect_order(3)}
+ return 0;
+}`,
+ }));
+ });
+
+g.test('arg_eval_logical_and')
+ .desc('Test that arguments are evaluated left to right')
+ .params(u => u.combine('preventValueOptimizations', [true, false]))
+ .fn(t => {
+ runFlowControlTest(t, f => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ a(b(${f.value(1)}) && c());
+ a(b(${f.value(0)}) && c());
+ ${f.expect_order(6)}
+`,
+ extra: `
+fn a(p : bool) {
+ ${f.expect_order(3, 5)}
+}
+fn b(x : i32) -> bool {
+ ${f.expect_order(1, 4)}
+ return x == 1;
+}
+fn c() -> bool {
+ ${f.expect_order(2)}
+ return true;
+}`,
+ }));
+ });
+
+g.test('arg_eval_logical_or')
+ .desc('Test that arguments are evaluated left to right')
+ .params(u => u.combine('preventValueOptimizations', [true, false]))
+ .fn(t => {
+ runFlowControlTest(t, f => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ a(b(${f.value(1)}) || c());
+ a(b(${f.value(0)}) || c());
+ ${f.expect_order(6)}
+`,
+ extra: `
+fn a(p : bool) {
+ ${f.expect_order(3, 5)}
+}
+fn b(x : i32) -> bool {
+ ${f.expect_order(1, 4)}
+ return x == 0;
+}
+fn c() -> bool {
+ ${f.expect_order(2)}
+ return true;
+}`,
+ }));
+ });
+
+g.test('arg_eval_pointers')
+ .desc('Test that arguments are evaluated left to right')
+ .params(u => u.combine('preventValueOptimizations', [true, false]))
+ .fn(t => {
+ runFlowControlTest(t, f => ({
+ entrypoint: `
+ var x : i32 = ${f.value(0)};
+ ${f.expect_order(0)}
+ _ = c(&x);
+ a(b(&x), c(&x));
+ ${f.expect_order(5)}
+`,
+ extra: `
+fn a(p1 : i32, p2 : i32) {
+ ${f.expect_order(4)}
+}
+fn b(p : ptr<function, i32>) -> i32 {
+ (*p)++;
+ ${f.expect_order(2)}
+ return 0;
+}
+fn c(p : ptr<function, i32>) -> i32 {
+ if (*p == 1) {
+ ${f.expect_order(3)}
+ } else {
+ ${f.expect_order(1)}
+ }
+ return 0;
+}`,
+ }));
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/for.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/for.spec.ts
index 898b8a0e04..5cb7de66c1 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/for.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/for.spec.ts
@@ -269,3 +269,53 @@ g.test('nested_for_continue')
`
);
});
+
+g.test('for_logical_and_condition')
+ .desc('Test flow control for a for-loop with a logical and condition')
+ .params(u => u.combine('preventValueOptimizations', [true, false]))
+ .fn(t => {
+ runFlowControlTest(t, f => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ for (var i = ${f.value(0)}; a(i) && b(i); i++) {
+ ${f.expect_order(3, 6)}
+ }
+ ${f.expect_order(8)}
+ `,
+ extra: `
+fn a(i : i32) -> bool {
+ ${f.expect_order(1, 4, 7)}
+ return i < ${f.value(2)};
+}
+fn b(i : i32) -> bool {
+ ${f.expect_order(2, 5)}
+ return i < ${f.value(5)};
+}
+ `,
+ }));
+ });
+
+g.test('for_logical_or_condition')
+ .desc('Test flow control for a for-loop with a logical or condition')
+ .params(u => u.combine('preventValueOptimizations', [true, false]))
+ .fn(t => {
+ runFlowControlTest(t, f => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ for (var i = ${f.value(0)}; a(i) || b(i); i++) {
+ ${f.expect_order(2, 4, 7, 10)}
+ }
+ ${f.expect_order(13)}
+ `,
+ extra: `
+fn a(i : i32) -> bool {
+ ${f.expect_order(1, 3, 5, 8, 11)}
+ return i < ${f.value(2)};
+}
+fn b(i : i32) -> bool {
+ ${f.expect_order(6, 9, 12)}
+ return i < ${f.value(4)};
+}
+ `,
+ }));
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/loop.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/loop.spec.ts
index 18d0e5d1ee..6dd99d137d 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/loop.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/loop.spec.ts
@@ -123,3 +123,63 @@ g.test('nested_loops')
`
);
});
+
+g.test('loop_break_if_logical_and_condition')
+ .desc('Test flow control for a loop with a logical and break if')
+ .params(u => u.combine('preventValueOptimizations', [true, false]))
+ .fn(t => {
+ runFlowControlTest(t, f => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ var i = ${f.value(0)};
+ loop {
+ ${f.expect_order(1, 4, 7)}
+ continuing {
+ i++;
+ break if !(a(i) && b(i));
+ }
+ }
+ ${f.expect_order(9)}
+ `,
+ extra: `
+fn a(i : i32) -> bool {
+ ${f.expect_order(2, 5, 8)}
+ return i < ${f.value(3)};
+}
+fn b(i : i32) -> bool {
+ ${f.expect_order(3, 6)}
+ return i < ${f.value(5)};
+}
+ `,
+ }));
+ });
+
+g.test('loop_break_if_logical_or_condition')
+ .desc('Test flow control for a loop with a logical or break if')
+ .params(u => u.combine('preventValueOptimizations', [true, false]))
+ .fn(t => {
+ runFlowControlTest(t, f => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ var i = ${f.value(0)};
+ loop {
+ ${f.expect_order(1, 3, 6, 9)}
+ continuing {
+ i++;
+ break if !(a(i) || b(i));
+ }
+ }
+ ${f.expect_order(12)}
+ `,
+ extra: `
+fn a(i : i32) -> bool {
+ ${f.expect_order(2, 4, 7, 10)}
+ return i < ${f.value(2)};
+}
+fn b(i : i32) -> bool {
+ ${f.expect_order(5, 8, 11)}
+ return i < ${f.value(4)};
+}
+ `,
+ }));
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/switch.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/switch.spec.ts
index e500729614..772cd6a875 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/switch.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/switch.spec.ts
@@ -154,3 +154,36 @@ ${f.expect_order(2)}
`
);
});
+
+g.test('switch_inside_loop_with_continue')
+ .desc('Test that flow control executes correct for a switch calling continue inside a loop')
+ .params(u => u.combine('preventValueOptimizations', [true, false]))
+ .fn(t => {
+ runFlowControlTest(
+ t,
+ f => `
+${f.expect_order(0)}
+var i = ${f.value(0)};
+loop {
+ switch (i) {
+ case 1: {
+ ${f.expect_order(4)}
+ continue;
+ }
+ default: {
+ ${f.expect_order(1)}
+ break;
+ }
+ }
+ ${f.expect_order(2)}
+
+ continuing {
+ ${f.expect_order(3, 5)}
+ i++;
+ break if i >= 2;
+ }
+}
+${f.expect_order(6)}
+`
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/while.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/while.spec.ts
index 88ce6838a5..5ce6097a3a 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/while.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/while.spec.ts
@@ -138,3 +138,57 @@ g.test('while_nested_continue')
`
);
});
+
+g.test('while_logical_and_condition')
+ .desc('Test flow control for a while-loop with a logical and condition')
+ .params(u => u.combine('preventValueOptimizations', [true, false]))
+ .fn(t => {
+ runFlowControlTest(t, f => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ var i = ${f.value(0)};
+ while (a(i) && b(i)) {
+ ${f.expect_order(3, 6)}
+ i++;
+ }
+ ${f.expect_order(8)}
+ `,
+ extra: `
+fn a(i : i32) -> bool {
+ ${f.expect_order(1, 4, 7)}
+ return i < ${f.value(2)};
+}
+fn b(i : i32) -> bool {
+ ${f.expect_order(2, 5)}
+ return i < ${f.value(5)};
+}
+ `,
+ }));
+ });
+
+g.test('while_logical_or_condition')
+ .desc('Test flow control for a while-loop with a logical or condition')
+ .params(u => u.combine('preventValueOptimizations', [true, false]))
+ .fn(t => {
+ runFlowControlTest(t, f => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ var i = ${f.value(0)};
+ while (a(i) || b(i)) {
+ ${f.expect_order(2, 4, 7, 10)}
+ i++;
+ }
+ ${f.expect_order(13)}
+ `,
+ extra: `
+fn a(i : i32) -> bool {
+ ${f.expect_order(1, 3, 5, 8, 11)}
+ return i < ${f.value(2)};
+}
+fn b(i : i32) -> bool {
+ ${f.expect_order(6, 9, 12)}
+ return i < ${f.value(4)};
+}
+ `,
+ }));
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/memory_layout.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/memory_layout.spec.ts
new file mode 100644
index 0000000000..a58a31449f
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/memory_layout.spec.ts
@@ -0,0 +1,1059 @@
+export const description = `Test memory layout requirements`;
+
+import { makeTestGroup } from '../../../common/framework/test_group.js';
+import { keysOf } from '../../../common/util/data_tables.js';
+import { iterRange } from '../../../common/util/util.js';
+import { GPUTest } from '../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+interface LayoutCase {
+ type: string;
+ decl?: string;
+ read_assign: string;
+ write_assign: string;
+ offset: number;
+ f16?: boolean;
+ f32?: boolean;
+ skip_uniform?: boolean;
+}
+
+const kLayoutCases: Record<string, LayoutCase> = {
+ vec2u_align8: {
+ type: `S_vec2u_align`,
+ decl: `struct S_vec2u_align {
+ x : u32,
+ y : vec2u,
+ }`,
+ read_assign: `out = in.y[1]`,
+ write_assign: `out.y[1] = in`,
+ offset: 12,
+ },
+ vec3u_align16: {
+ type: `S_vec3u_align`,
+ decl: `struct S_vec3u_align {
+ x : u32,
+ y : vec3u,
+ }`,
+ read_assign: `out = in.y[2]`,
+ write_assign: `out.y[2] = in`,
+ offset: 24,
+ },
+ vec4u_align16: {
+ type: `S_vec4u_align`,
+ decl: `struct S_vec4u_align {
+ x : u32,
+ y : vec4u,
+ }`,
+ read_assign: `out = in.y[0]`,
+ write_assign: `out.y[0] = in`,
+ offset: 16,
+ },
+ struct_align32: {
+ type: `S_align32`,
+ decl: `struct S_align32 {
+ x : u32,
+ @align(32) y : u32,
+ }`,
+ read_assign: `out = in.y;`,
+ write_assign: `out.y = in`,
+ offset: 32,
+ },
+ vec2h_align4: {
+ type: `S_vec2h_align`,
+ decl: `struct S_vec2h_align {
+ x : f16,
+ y : vec2h,
+ }`,
+ read_assign: `out = u32(in.y[0])`,
+ write_assign: `out.y[0] = f16(in)`,
+ offset: 4,
+ f16: true,
+ },
+ vec3h_align8: {
+ type: `S_vec3h_align`,
+ decl: `struct S_vec3h_align {
+ x : f16,
+ y : vec3h,
+ }`,
+ read_assign: `out = u32(in.y[2])`,
+ write_assign: `out.y[2] = f16(in)`,
+ offset: 12,
+ f16: true,
+ },
+ vec4h_align8: {
+ type: `S_vec4h_align`,
+ decl: `struct S_vec4h_align {
+ x : f16,
+ y : vec4h,
+ }`,
+ read_assign: `out = u32(in.y[2])`,
+ write_assign: `out.y[2] = f16(in)`,
+ offset: 12,
+ f16: true,
+ },
+ vec2f_align8: {
+ type: `S_vec2f_align`,
+ decl: `struct S_vec2f_align {
+ x : u32,
+ y : vec2f,
+ }`,
+ read_assign: `out = u32(in.y[1])`,
+ write_assign: `out.y[1] = f32(in)`,
+ offset: 12,
+ f32: true,
+ },
+ vec3f_align16: {
+ type: `S_vec3f_align`,
+ decl: `struct S_vec3f_align {
+ x : u32,
+ y : vec3f,
+ }`,
+ read_assign: `out = u32(in.y[2])`,
+ write_assign: `out.y[2] = f32(in)`,
+ offset: 24,
+ f32: true,
+ },
+ vec4f_align16: {
+ type: `S_vec4f_align`,
+ decl: `struct S_vec4f_align {
+ x : u32,
+ y : vec4f,
+ }`,
+ read_assign: `out = u32(in.y[0])`,
+ write_assign: `out.y[0] = f32(in)`,
+ offset: 16,
+ f32: true,
+ },
+ vec3i_size12: {
+ type: `S_vec3i_size`,
+ decl: `struct S_vec3i_size {
+ x : vec3i,
+ y : u32,
+ }`,
+ read_assign: `out = in.y`,
+ write_assign: `out.y = in`,
+ offset: 12,
+ },
+ vec3h_size6: {
+ type: `S_vec3h_size`,
+ decl: `struct S_vec3h_size {
+ x : vec3h,
+ y : f16,
+ z : f16,
+ }`,
+ read_assign: `out = u32(in.z)`,
+ write_assign: `out.z = f16(in)`,
+ offset: 8,
+ f16: true,
+ },
+ size80: {
+ type: `S_size80`,
+ decl: `struct S_size80 {
+ @size(80) x : u32,
+ y : u32,
+ }`,
+ read_assign: `out = in.y`,
+ write_assign: `out.y = in`,
+ offset: 80,
+ },
+ atomic_align4: {
+ type: `S_atomic_align`,
+ decl: `struct S_atomic_align {
+ x : u32,
+ y : atomic<u32>,
+ }`,
+ read_assign: `out = atomicLoad(&in.y)`,
+ write_assign: `atomicStore(&out.y, in)`,
+ offset: 4,
+ },
+ atomic_size4: {
+ type: `S_atomic_size`,
+ decl: `struct S_atomic_size {
+ x : atomic<u32>,
+ y : u32,
+ }`,
+ read_assign: `out = in.y`,
+ write_assign: `out.y = in`,
+ offset: 4,
+ },
+ mat2x2f_align8: {
+ type: `S_mat2x2f_align`,
+ decl: `struct S_mat2x2f_align {
+ x : u32,
+ y : mat2x2f,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f32(in)`,
+ offset: 8,
+ f32: true,
+ },
+ mat3x2f_align8: {
+ type: `S_mat3x2f_align`,
+ decl: `struct S_mat3x2f_align {
+ x : u32,
+ y : mat3x2f,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f32(in)`,
+ offset: 8,
+ f32: true,
+ },
+ mat4x2f_align8: {
+ type: `S_mat4x2f_align`,
+ decl: `struct S_mat4x2f_align {
+ x : u32,
+ y : mat4x2f,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f32(in)`,
+ offset: 8,
+ f32: true,
+ },
+ mat2x3f_align16: {
+ type: `S_mat2x3f_align`,
+ decl: `struct S_mat2x3f_align {
+ x : u32,
+ y : mat2x3f,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f32(in)`,
+ offset: 16,
+ f32: true,
+ },
+ mat3x3f_align16: {
+ type: `S_mat3x3f_align`,
+ decl: `struct S_mat3x3f_align {
+ x : u32,
+ y : mat3x3f,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f32(in)`,
+ offset: 16,
+ f32: true,
+ },
+ mat4x3f_align16: {
+ type: `S_mat4x3f_align`,
+ decl: `struct S_mat4x3f_align {
+ x : u32,
+ y : mat4x3f,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f32(in)`,
+ offset: 16,
+ f32: true,
+ },
+ mat2x4f_align16: {
+ type: `S_mat2x4f_align`,
+ decl: `struct S_mat2x4f_align {
+ x : u32,
+ y : mat2x4f,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f32(in)`,
+ offset: 16,
+ f32: true,
+ },
+ mat3x4f_align16: {
+ type: `S_mat3x4f_align`,
+ decl: `struct S_mat3x4f_align {
+ x : u32,
+ y : mat3x4f,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f32(in)`,
+ offset: 16,
+ f32: true,
+ },
+ mat4x4f_align16: {
+ type: `S_mat4x4f_align`,
+ decl: `struct S_mat4x4f_align {
+ x : u32,
+ y : mat4x4f,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f32(in)`,
+ offset: 16,
+ f32: true,
+ },
+ mat2x2h_align4: {
+ type: `S_mat2x2h_align`,
+ decl: `struct S_mat2x2h_align {
+ x : u32,
+ y : mat2x2h,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f16(in)`,
+ offset: 4,
+ f16: true,
+ },
+ mat3x2h_align4: {
+ type: `S_mat3x2h_align`,
+ decl: `struct S_mat3x2h_align {
+ x : u32,
+ y : mat3x2h,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f16(in)`,
+ offset: 4,
+ f16: true,
+ },
+ mat4x2h_align4: {
+ type: `S_mat4x2h_align`,
+ decl: `struct S_mat4x2h_align {
+ x : u32,
+ y : mat4x2h,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f16(in)`,
+ offset: 4,
+ f16: true,
+ },
+ mat2x3h_align8: {
+ type: `S_mat2x3h_align`,
+ decl: `struct S_mat2x3h_align {
+ x : u32,
+ y : mat2x3h,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f16(in)`,
+ offset: 8,
+ f16: true,
+ },
+ mat3x3h_align8: {
+ type: `S_mat3x3h_align`,
+ decl: `struct S_mat3x3h_align {
+ x : u32,
+ y : mat2x3h,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f16(in)`,
+ offset: 8,
+ f16: true,
+ },
+ mat4x3h_align8: {
+ type: `S_mat4x3h_align`,
+ decl: `struct S_mat4x3h_align {
+ x : u32,
+ y : mat4x3h,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f16(in)`,
+ offset: 8,
+ f16: true,
+ },
+ mat2x4h_align8: {
+ type: `S_mat2x4h_align`,
+ decl: `struct S_mat2x4h_align {
+ x : u32,
+ y : mat2x4h,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f16(in)`,
+ offset: 8,
+ f16: true,
+ },
+ mat3x4h_align8: {
+ type: `S_mat3x4h_align`,
+ decl: `struct S_mat3x4h_align {
+ x : u32,
+ y : mat3x4h,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f16(in)`,
+ offset: 8,
+ f16: true,
+ },
+ mat4x4h_align8: {
+ type: `S_mat4x4h_align`,
+ decl: `struct S_mat4x4h_align {
+ x : u32,
+ y : mat4x4h,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f16(in)`,
+ offset: 8,
+ f16: true,
+ },
+ mat2x2f_size: {
+ type: `S_mat2x2f_size`,
+ decl: `struct S_mat2x2f_size {
+ x : mat2x2f,
+ y : u32,
+ }`,
+ read_assign: `out = in.y`,
+ write_assign: `out.y = in`,
+ offset: 16,
+ },
+ mat3x2f_size: {
+ type: `S_mat3x2f_size`,
+ decl: `struct S_mat3x2f_size {
+ x : mat3x2f,
+ y : u32,
+ }`,
+ read_assign: `out = in.y`,
+ write_assign: `out.y = in`,
+ offset: 24,
+ },
+ mat4x2f_size: {
+ type: `S_mat4x2f_size`,
+ decl: `struct S_mat4x2f_size {
+ x : mat4x2f,
+ y : u32,
+ }`,
+ read_assign: `out = in.y`,
+ write_assign: `out.y = in`,
+ offset: 32,
+ },
+ mat2x3f_size: {
+ type: `S_mat2x3f_size`,
+ decl: `struct S_mat2x3f_size {
+ x : mat2x3f,
+ y : u32,
+ }`,
+ read_assign: `out = in.y`,
+ write_assign: `out.y = in`,
+ offset: 32,
+ },
+ mat3x3f_size: {
+ type: `S_mat3x3f_size`,
+ decl: `struct S_mat3x3f_size {
+ x : mat3x3f,
+ y : u32,
+ }`,
+ read_assign: `out = in.y`,
+ write_assign: `out.y = in`,
+ offset: 48,
+ },
+ mat4x3f_size: {
+ type: `S_mat4x3f_size`,
+ decl: `struct S_mat4x3f_size {
+ x : mat4x3f,
+ y : u32,
+ }`,
+ read_assign: `out = in.y`,
+ write_assign: `out.y = in`,
+ offset: 64,
+ },
+ mat2x4f_size: {
+ type: `S_mat2x4f_size`,
+ decl: `struct S_mat2x4f_size {
+ x : mat2x4f,
+ y : u32,
+ }`,
+ read_assign: `out = in.y`,
+ write_assign: `out.y = in`,
+ offset: 32,
+ },
+ mat3x4f_size: {
+ type: `S_mat3x4f_size`,
+ decl: `struct S_mat3x4f_size {
+ x : mat3x4f,
+ y : u32,
+ }`,
+ read_assign: `out = in.y`,
+ write_assign: `out.y = in`,
+ offset: 48,
+ },
+ mat4x4f_size: {
+ type: `S_mat4x4f_size`,
+ decl: `struct S_mat4x4f_size {
+ x : mat4x4f,
+ y : u32,
+ }`,
+ read_assign: `out = in.y`,
+ write_assign: `out.y = in`,
+ offset: 64,
+ },
+ mat2x2h_size: {
+ type: `S_mat2x2h_size`,
+ decl: `struct S_mat2x2h_size {
+ x : mat2x2h,
+ y : f16,
+ }`,
+ read_assign: `out = u32(in.y)`,
+ write_assign: `out.y = f16(in)`,
+ offset: 8,
+ f16: true,
+ },
+ mat3x2h_size: {
+ type: `S_mat3x2h_size`,
+ decl: `struct S_mat3x2h_size {
+ x : mat3x2h,
+ y : f16,
+ }`,
+ read_assign: `out = u32(in.y)`,
+ write_assign: `out.y = f16(in)`,
+ offset: 12,
+ f16: true,
+ },
+ mat4x2h_size: {
+ type: `S_mat4x2h_size`,
+ decl: `struct S_mat4x2h_size {
+ x : mat4x2h,
+ y : f16,
+ }`,
+ read_assign: `out = u32(in.y)`,
+ write_assign: `out.y = f16(in)`,
+ offset: 16,
+ f16: true,
+ },
+ mat2x3h_size: {
+ type: `S_mat2x3h_size`,
+ decl: `struct S_mat2x3h_size {
+ x : mat2x3h,
+ y : f16,
+ }`,
+ read_assign: `out = u32(in.y)`,
+ write_assign: `out.y = f16(in)`,
+ offset: 16,
+ f16: true,
+ },
+ mat3x3h_size: {
+ type: `S_mat3x3h_size`,
+ decl: `struct S_mat3x3h_size {
+ x : mat3x3h,
+ y : f16,
+ }`,
+ read_assign: `out = u32(in.y)`,
+ write_assign: `out.y = f16(in)`,
+ offset: 24,
+ f16: true,
+ },
+ mat4x3h_size: {
+ type: `S_mat4x3h_size`,
+ decl: `struct S_mat4x3h_size {
+ x : mat4x3h,
+ y : f16,
+ }`,
+ read_assign: `out = u32(in.y)`,
+ write_assign: `out.y = f16(in)`,
+ offset: 32,
+ f16: true,
+ },
+ mat2x4h_size: {
+ type: `S_mat2x4h_size`,
+ decl: `struct S_mat2x4h_size {
+ x : mat2x4h,
+ y : f16,
+ }`,
+ read_assign: `out = u32(in.y)`,
+ write_assign: `out.y = f16(in)`,
+ offset: 16,
+ f16: true,
+ },
+ mat3x4h_size: {
+ type: `S_mat3x4h_size`,
+ decl: `struct S_mat3x4h_size {
+ x : mat3x4h,
+ y : f16,
+ }`,
+ read_assign: `out = u32(in.y)`,
+ write_assign: `out.y = f16(in)`,
+ offset: 24,
+ f16: true,
+ },
+ mat4x4h_size: {
+ type: `S_mat4x4h_size`,
+ decl: `struct S_mat4x4h_size {
+ x : mat4x4h,
+ y : f16,
+ }`,
+ read_assign: `out = u32(in.y)`,
+ write_assign: `out.y = f16(in)`,
+ offset: 32,
+ f16: true,
+ },
+ struct_align_vec2i: {
+ type: `S_struct_align_vec2i`,
+ decl: `struct Inner {
+ x : u32,
+ y : vec2i,
+ }
+ struct S_struct_align_vec2i {
+ x : u32,
+ y : Inner,
+ }`,
+ read_assign: `out = in.y.x`,
+ write_assign: `out.y.x = in`,
+ offset: 8,
+ skip_uniform: true,
+ },
+ struct_align_vec3i: {
+ type: `S_struct_align_vec3i`,
+ decl: `struct Inner {
+ x : u32,
+ y : vec3i,
+ }
+ struct S_struct_align_vec3i {
+ x : u32,
+ y : Inner,
+ }`,
+ read_assign: `out = in.y.x`,
+ write_assign: `out.y.x = in`,
+ offset: 16,
+ },
+ struct_align_vec4i: {
+ type: `S_struct_align_vec4i`,
+ decl: `struct Inner {
+ x : u32,
+ y : vec4i,
+ }
+ struct S_struct_align_vec4i {
+ x : u32,
+ y : Inner,
+ }`,
+ read_assign: `out = in.y.x`,
+ write_assign: `out.y.x = in`,
+ offset: 16,
+ },
+ struct_align_vec2h: {
+ type: `S_struct_align_vec2h`,
+ decl: `struct Inner {
+ x : f16,
+ y : vec2h,
+ }
+ struct S_struct_align_vec2h {
+ x : f16,
+ y : Inner,
+ }`,
+ read_assign: `out = u32(in.y.x)`,
+ write_assign: `out.y.x = f16(in)`,
+ offset: 4,
+ f16: true,
+ skip_uniform: true,
+ },
+ struct_align_vec3h: {
+ type: `S_struct_align_vec3h`,
+ decl: `struct Inner {
+ x : f16,
+ y : vec3h,
+ }
+ struct S_struct_align_vec3h {
+ x : f16,
+ y : Inner,
+ }`,
+ read_assign: `out = u32(in.y.x)`,
+ write_assign: `out.y.x = f16(in)`,
+ offset: 8,
+ f16: true,
+ skip_uniform: true,
+ },
+ struct_align_vec4h: {
+ type: `S_struct_align_vec4h`,
+ decl: `struct Inner {
+ x : f16,
+ y : vec4h,
+ }
+ struct S_struct_align_vec4h {
+ x : f16,
+ y : Inner,
+ }`,
+ read_assign: `out = u32(in.y.x)`,
+ write_assign: `out.y.x = f16(in)`,
+ offset: 8,
+ f16: true,
+ skip_uniform: true,
+ },
+ struct_size_roundup: {
+ type: `S_struct_size_roundup`,
+ decl: `struct Inner {
+ x : vec3u,
+ }
+ struct S_struct_size_roundup {
+ x : Inner,
+ y : u32,
+ }`,
+ read_assign: `out = in.y`,
+ write_assign: `out.y = in`,
+ offset: 16,
+ },
+ struct_inner_size: {
+ type: `S_struct_inner_size`,
+ decl: `struct Inner {
+ @size(112) x : u32,
+ }
+ struct S_struct_inner_size {
+ x : Inner,
+ y : u32,
+ }`,
+ read_assign: `out = in.y`,
+ write_assign: `out.y = in`,
+ offset: 112,
+ },
+ struct_inner_align: {
+ type: `S_struct_inner_align`,
+ decl: `struct Inner {
+ @align(64) x : u32,
+ }
+ struct S_struct_inner_align {
+ x : u32,
+ y : Inner,
+ }`,
+ read_assign: `out = in.y.x`,
+ write_assign: `out.y.x = in`,
+ offset: 64,
+ },
+ struct_inner_size_and_align: {
+ type: `S_struct_inner_size_and_align`,
+ decl: `struct Inner {
+ @align(32) @size(33) x : u32,
+ }
+ struct S_struct_inner_size_and_align {
+ x : Inner,
+ y : Inner,
+ }`,
+ read_assign: `out = in.y.x`,
+ write_assign: `out.y.x = in`,
+ offset: 64,
+ },
+ struct_override_size: {
+ type: `S_struct_override_size`,
+ decl: `struct Inner {
+ @size(32) x : u32,
+ }
+ struct S_struct_override_size {
+ @size(64) x : Inner,
+ y : u32,
+ }`,
+ read_assign: `out = in.y`,
+ write_assign: `out.y = in`,
+ offset: 64,
+ },
+ struct_double_align: {
+ type: `S_struct_double_align`,
+ decl: `struct Inner {
+ x : u32,
+ @align(32) y : u32,
+ }
+ struct S_struct_double_align {
+ x : u32,
+ @align(64) y : Inner,
+ }`,
+ read_assign: `out = in.y.y`,
+ write_assign: `out.y.y = in`,
+ offset: 96,
+ },
+ array_vec3u_align: {
+ type: `S_array_vec3u_align`,
+ decl: `struct S_array_vec3u_align {
+ x : u32,
+ y : array<vec3u, 2>,
+ }`,
+ read_assign: `out = in.y[0][0]`,
+ write_assign: `out.y[0][0] = in`,
+ offset: 16,
+ },
+ array_vec3h_align: {
+ type: `S_array_vec3h_align`,
+ decl: `struct S_array_vec3h_align {
+ x : f16,
+ y : array<vec3h, 2>,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f16(in)`,
+ offset: 8,
+ f16: true,
+ skip_uniform: true,
+ },
+ array_vec3u_stride: {
+ type: `S_array_vec3u_stride`,
+ decl: `struct S_array_vec3u_stride {
+ x : array<vec3u, 4>,
+ }`,
+ read_assign: `out = in.x[1][0]`,
+ write_assign: `out.x[1][0] = in`,
+ offset: 16,
+ },
+ array_vec3h_stride: {
+ type: `S_array_vec3h_stride`,
+ decl: `struct S_array_vec3h_stride {
+ x : array<vec3h, 4>,
+ }`,
+ read_assign: `out = u32(in.x[1][0])`,
+ write_assign: `out.x[1][0] = f16(in)`,
+ offset: 8,
+ f16: true,
+ skip_uniform: true,
+ },
+ array_stride_size: {
+ type: `array<S_stride, 4>`,
+ decl: `struct S_stride {
+ @size(16) x : u32,
+ }`,
+ read_assign: `out = in[2].x`,
+ write_assign: `out[2].x = in`,
+ offset: 32,
+ },
+};
+
+g.test('read_layout')
+ .desc('Test reading memory layouts')
+ .params(u =>
+ u
+ .combine('case', keysOf(kLayoutCases))
+ .combine('aspace', ['storage', 'uniform', 'workgroup', 'function', 'private'] as const)
+ .beginSubcases()
+ )
+ .beforeAllSubcases(t => {
+ const testcase = kLayoutCases[t.params.case];
+ if (testcase.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ // Don't test atomics in workgroup due to initialization boilerplate.
+ t.skipIf(
+ testcase.type.includes('atomic') && t.params.aspace !== 'storage',
+ `Skipping atomic test for non-storage address space`
+ );
+
+ t.skipIf(
+ testcase.skip_uniform === true && t.params.aspace === 'uniform',
+ `Uniform requires 16 byte alignment`
+ );
+ })
+ .fn(t => {
+ const testcase = kLayoutCases[t.params.case];
+ let code = `
+${testcase.f16 ? 'enable f16;' : ''}
+${testcase.decl}
+
+@group(0) @binding(1)
+var<storage, read_write> out : u32;
+`;
+
+ if (t.params.aspace === 'uniform') {
+ code += `@group(0) @binding(0)
+ var<${t.params.aspace}> in : ${testcase.type};`;
+ } else if (t.params.aspace === 'storage') {
+ // Use read_write for input data to support atomics.
+ code += `@group(0) @binding(0)
+ var<${t.params.aspace}, read_write> in : ${testcase.type};`;
+ } else {
+ code += `@group(0) @binding(0)
+ var<storage> pre_in : ${testcase.type};`;
+ if (t.params.aspace === 'workgroup') {
+ code += `
+ var<workgroup> in : ${testcase.type};`;
+ } else if (t.params.aspace === 'private') {
+ code += `
+ var<private> in : ${testcase.type};`;
+ }
+ }
+
+ code += `
+@compute @workgroup_size(1,1,1)
+fn main() {
+`;
+
+ if (
+ t.params.aspace === 'workgroup' ||
+ t.params.aspace === 'function' ||
+ t.params.aspace === 'private'
+ ) {
+ if (t.params.aspace === 'function') {
+ code += `var in : ${testcase.type};\n`;
+ }
+ code += `in = pre_in;`;
+ if (t.params.aspace === 'workgroup') {
+ code += `workgroupBarrier();\n`;
+ }
+ }
+
+ code += `\n${testcase.read_assign};\n}`;
+
+ let usage = GPUBufferUsage.COPY_SRC;
+ if (t.params.aspace === 'uniform') {
+ usage |= GPUBufferUsage.UNIFORM;
+ } else {
+ usage |= GPUBufferUsage.STORAGE;
+ }
+
+ // Magic number is 42 in various representations.
+ const inMagicNumber = testcase.f16 ? 0x5140 : testcase.f32 ? 0x42280000 : 42;
+ const in_buffer = t.makeBufferWithContents(
+ new Uint32Array([
+ ...iterRange(128, x => {
+ if (x * 4 === testcase.offset) {
+ return inMagicNumber;
+ } else {
+ return 0;
+ }
+ }),
+ ]),
+ usage
+ );
+ t.trackForCleanup(in_buffer);
+
+ const out_buffer = t.makeBufferWithContents(
+ new Uint32Array([...iterRange(1, x => 0)]),
+ GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ );
+ t.trackForCleanup(out_buffer);
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code,
+ }),
+ entryPoint: 'main',
+ },
+ });
+
+ const bg = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: {
+ buffer: in_buffer,
+ },
+ },
+ {
+ binding: 1,
+ resource: {
+ buffer: out_buffer,
+ },
+ },
+ ],
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bg);
+ pass.dispatchWorkgroups(1, 1, 1);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ t.expectGPUBufferValuesEqual(out_buffer, new Uint32Array([42]));
+ });
+
+g.test('write_layout')
+ .desc('Test writing memory layouts')
+ .params(u =>
+ u
+ .combine('case', keysOf(kLayoutCases))
+ .combine('aspace', ['storage', 'workgroup', 'function', 'private'] as const)
+ .beginSubcases()
+ )
+ .beforeAllSubcases(t => {
+ const testcase = kLayoutCases[t.params.case];
+ if (testcase.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ // Don't test atomics in workgroup due to initialization boilerplate.
+ t.skipIf(
+ testcase.type.includes('atomic') && t.params.aspace !== 'storage',
+ `Skipping atomic test for non-storage address space`
+ );
+ })
+ .fn(t => {
+ const testcase = kLayoutCases[t.params.case];
+ let code = `
+${testcase.f16 ? 'enable f16;' : ''}
+${testcase.decl}
+
+@group(0) @binding(0)
+var<storage> in : u32;
+`;
+
+ if (t.params.aspace === 'storage') {
+ code += `@group(0) @binding(1)
+ var<storage, read_write> out : ${testcase.type};\n`;
+ } else {
+ code += `@group(0) @binding(1)
+ var<storage, read_write> post_out : ${testcase.type};\n`;
+
+ if (t.params.aspace === 'workgroup') {
+ code += `var<workgroup> out : ${testcase.type};\n`;
+ } else if (t.params.aspace === 'private') {
+ code += `var<private> out : ${testcase.type};\n`;
+ }
+ }
+
+ code += `
+@compute @workgroup_size(1,1,1)
+fn main() {
+`;
+
+ if (t.params.aspace === 'function') {
+ code += `var out : ${testcase.type};\n`;
+ }
+
+ code += `${testcase.write_assign};\n`;
+ if (
+ t.params.aspace === 'workgroup' ||
+ t.params.aspace === 'function' ||
+ t.params.aspace === 'private'
+ ) {
+ if (t.params.aspace === 'workgroup') {
+ code += `workgroupBarrier();\n`;
+ }
+ code += `post_out = out;`;
+ }
+
+ code += `\n}`;
+
+ const in_buffer = t.makeBufferWithContents(
+ new Uint32Array([42]),
+ GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE
+ );
+ t.trackForCleanup(in_buffer);
+
+ const out_buffer = t.makeBufferWithContents(
+ new Uint32Array([...iterRange(128, x => 0)]),
+ GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ );
+ t.trackForCleanup(out_buffer);
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code,
+ }),
+ entryPoint: 'main',
+ },
+ });
+
+ const bg = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: {
+ buffer: in_buffer,
+ },
+ },
+ {
+ binding: 1,
+ resource: {
+ buffer: out_buffer,
+ },
+ },
+ ],
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bg);
+ pass.dispatchWorkgroups(1, 1, 1);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Magic number is 42 in various representations.
+ const outMagicNumber = testcase.f16 ? 0x5140 : testcase.f32 ? 0x42280000 : 42;
+ const expect = new Uint32Array([
+ ...iterRange(128, x => {
+ if (x * 4 === testcase.offset) {
+ return outMagicNumber;
+ } else {
+ return 0;
+ }
+ }),
+ ]);
+
+ t.expectGPUBufferValuesEqual(out_buffer, expect);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/memory_model/barrier.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/memory_model/barrier.spec.ts
index 478ae28a7a..72fb69e293 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/memory_model/barrier.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/memory_model/barrier.spec.ts
@@ -43,22 +43,33 @@ const memoryModelTestParams: MemoryModelTestParams = {
numBehaviors: 2,
};
-// The two kinds of non-atomic accesses tested.
+// The three kinds of non-atomic accesses tested.
// rw: read -> barrier -> write
// wr: write -> barrier -> read
// ww: write -> barrier -> write
type AccessPair = 'rw' | 'wr' | 'ww';
// Test the non-atomic memory types.
-const kMemTypes = [MemoryType.NonAtomicStorageClass, MemoryType.NonAtomicWorkgroupClass] as const;
+const kMemTypes = [
+ MemoryType.NonAtomicStorageClass,
+ MemoryType.NonAtomicWorkgroupClass,
+ MemoryType.NonAtomicTextureClass,
+] as const;
const storageMemoryBarrierStoreLoadTestCode = `
test_locations.value[x_0] = 1;
- workgroupBarrier();
+ storageBarrier();
let r0 = u32(test_locations.value[x_1]);
atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r0, r0);
`;
+const textureMemoryBarrierStoreLoadTestCode = `
+ textureStore(texture_locations, indexToCoord(x_0), vec4u(1));
+ textureBarrier();
+ let r0 = textureLoad(texture_locations, indexToCoord(x_1)).x;
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r0, r0);
+`;
+
const workgroupMemoryBarrierStoreLoadTestCode = `
wg_test_locations[x_0] = 1;
workgroupBarrier();
@@ -66,13 +77,27 @@ const workgroupMemoryBarrierStoreLoadTestCode = `
atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r0, r0);
`;
+const workgroupUniformLoadMemoryBarrierStoreLoadTestCode = `
+ wg_test_locations[x_0] = 1;
+ _ = workgroupUniformLoad(&placeholder_wg_var);
+ let r0 = u32(wg_test_locations[x_1]);
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r0, r0);
+`;
+
const storageMemoryBarrierLoadStoreTestCode = `
let r0 = u32(test_locations.value[x_0]);
- workgroupBarrier();
+ storageBarrier();
test_locations.value[x_1] = 1;
atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_0].r0, r0);
`;
+const textureMemoryBarrierLoadStoreTestCode = `
+ let r0 = textureLoad(texture_locations, indexToCoord(x_0)).x;
+ textureBarrier();
+ textureStore(texture_locations, indexToCoord(x_1), vec4u(1));
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_0].r0, r0);
+`;
+
const workgroupMemoryBarrierLoadStoreTestCode = `
let r0 = u32(wg_test_locations[x_0]);
workgroupBarrier();
@@ -80,12 +105,27 @@ const workgroupMemoryBarrierLoadStoreTestCode = `
atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_0].r0, r0);
`;
+const workgroupUniformLoadMemoryBarrierLoadStoreTestCode = `
+ let r0 = u32(wg_test_locations[x_0]);
+ _ = workgroupUniformLoad(&placeholder_wg_var);
+ wg_test_locations[x_1] = 1;
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_0].r0, r0);
+`;
+
const storageMemoryBarrierStoreStoreTestCode = `
test_locations.value[x_0] = 1;
storageBarrier();
test_locations.value[x_1] = 2;
`;
+const textureMemoryBarrierStoreStoreTestCode = `
+ textureStore(texture_locations, indexToCoord(x_0), vec4u(1));
+ textureBarrier();
+ textureStore(texture_locations, indexToCoord(x_1), vec4u(2));
+ textureBarrier();
+ test_locations.value[x_1] = textureLoad(texture_locations, indexToCoord(x_1)).x;
+`;
+
const workgroupMemoryBarrierStoreStoreTestCode = `
wg_test_locations[x_0] = 1;
workgroupBarrier();
@@ -94,20 +134,56 @@ const workgroupMemoryBarrierStoreStoreTestCode = `
test_locations.value[shuffled_workgroup * workgroupXSize * stress_params.mem_stride * 2u + x_1] = wg_test_locations[x_1];
`;
-function getTestCode(p: { memType: MemoryType; accessPair: AccessPair }): string {
+const workgroupUniformLoadMemoryBarrierStoreStoreTestCode = `
+ wg_test_locations[x_0] = 1;
+ _ = workgroupUniformLoad(&placeholder_wg_var);
+ wg_test_locations[x_1] = 2;
+ _ = workgroupUniformLoad(&placeholder_wg_var);
+ test_locations.value[shuffled_workgroup * workgroupXSize * stress_params.mem_stride * 2u + x_1] = wg_test_locations[x_1];
+`;
+
+function getTestCode(p: {
+ memType: MemoryType;
+ accessPair: AccessPair;
+ normalBarrier: boolean;
+}): string {
switch (p.accessPair) {
- case 'rw':
- return p.memType === MemoryType.NonAtomicStorageClass
- ? storageMemoryBarrierLoadStoreTestCode
- : workgroupMemoryBarrierLoadStoreTestCode;
- case 'wr':
- return p.memType === MemoryType.NonAtomicStorageClass
- ? storageMemoryBarrierStoreLoadTestCode
- : workgroupMemoryBarrierStoreLoadTestCode;
- case 'ww':
- return p.memType === MemoryType.NonAtomicStorageClass
- ? storageMemoryBarrierStoreStoreTestCode
- : workgroupMemoryBarrierStoreStoreTestCode;
+ case 'rw': {
+ switch (p.memType) {
+ case MemoryType.NonAtomicStorageClass:
+ return storageMemoryBarrierLoadStoreTestCode;
+ case MemoryType.NonAtomicTextureClass:
+ return textureMemoryBarrierLoadStoreTestCode;
+ default:
+ return p.normalBarrier
+ ? workgroupMemoryBarrierLoadStoreTestCode
+ : workgroupUniformLoadMemoryBarrierLoadStoreTestCode;
+ }
+ }
+ case 'wr': {
+ switch (p.memType) {
+ case MemoryType.NonAtomicStorageClass:
+ return storageMemoryBarrierStoreLoadTestCode;
+ case MemoryType.NonAtomicTextureClass:
+ return textureMemoryBarrierStoreLoadTestCode;
+ default:
+ return p.normalBarrier
+ ? workgroupMemoryBarrierStoreLoadTestCode
+ : workgroupUniformLoadMemoryBarrierStoreLoadTestCode;
+ }
+ }
+ case 'ww': {
+ switch (p.memType) {
+ case MemoryType.NonAtomicStorageClass:
+ return storageMemoryBarrierStoreStoreTestCode;
+ case MemoryType.NonAtomicTextureClass:
+ return textureMemoryBarrierStoreStoreTestCode;
+ default:
+ return p.normalBarrier
+ ? workgroupMemoryBarrierStoreStoreTestCode
+ : workgroupUniformLoadMemoryBarrierStoreStoreTestCode;
+ }
+ }
}
}
@@ -123,13 +199,28 @@ g.test('workgroup_barrier_store_load')
.combine('accessValueType', kAccessValueTypes)
.combine('memType', kMemTypes)
.combine('accessPair', ['wr'] as const)
+ .combine('normalBarrier', [true, false] as const)
)
.beforeAllSubcases(t => {
if (t.params.accessValueType === 'f16') {
t.selectDeviceOrSkipTestCase('shader-f16');
}
+ t.skipIf(
+ !t.params.normalBarrier && t.params.memType !== MemoryType.NonAtomicWorkgroupClass,
+ 'workgroupUniformLoad does not have storage memory semantics'
+ );
+ t.skipIf(
+ t.params.memType === MemoryType.NonAtomicTextureClass && t.params.accessValueType === 'f16',
+ 'textures do not support f16 access'
+ );
})
.fn(async t => {
+ t.skipIf(
+ t.params.memType === MemoryType.NonAtomicTextureClass &&
+ !t.hasLanguageFeature('readonly_and_readwrite_storage_textures'),
+ 'requires RW storage textures feature'
+ );
+
const resultCode = `
if (r0 == 1u) {
atomicAdd(&test_results.seq, 1u);
@@ -137,11 +228,14 @@ g.test('workgroup_barrier_store_load')
atomicAdd(&test_results.weak, 1u);
}
`;
- const testShader = buildTestShader(
+ let testShader = buildTestShader(
getTestCode(t.params),
t.params.memType,
TestType.IntraWorkgroup
);
+ if (!t.params.normalBarrier) {
+ testShader += '\nvar<workgroup> placeholder_wg_var : u32;\n';
+ }
const resultShader = buildResultShader(
resultCode,
TestType.IntraWorkgroup,
@@ -152,7 +246,8 @@ g.test('workgroup_barrier_store_load')
memoryModelTestParams,
testShader,
resultShader,
- t.params.accessValueType
+ t.params.accessValueType,
+ t.params.memType === MemoryType.NonAtomicTextureClass
);
await memModelTester.run(15, 1);
});
@@ -169,13 +264,28 @@ g.test('workgroup_barrier_load_store')
.combine('accessValueType', kAccessValueTypes)
.combine('memType', kMemTypes)
.combine('accessPair', ['rw'] as const)
+ .combine('normalBarrier', [true, false] as const)
)
.beforeAllSubcases(t => {
if (t.params.accessValueType === 'f16') {
t.selectDeviceOrSkipTestCase('shader-f16');
}
+ t.skipIf(
+ !t.params.normalBarrier && t.params.memType !== MemoryType.NonAtomicWorkgroupClass,
+ 'workgroupUniformLoad does not have storage memory semantics'
+ );
+ t.skipIf(
+ t.params.memType === MemoryType.NonAtomicTextureClass && t.params.accessValueType === 'f16',
+ 'textures do not support f16 access'
+ );
})
.fn(async t => {
+ t.skipIf(
+ t.params.memType === MemoryType.NonAtomicTextureClass &&
+ !t.hasLanguageFeature('readonly_and_readwrite_storage_textures'),
+ 'requires RW storage textures feature'
+ );
+
const resultCode = `
if (r0 == 0u) {
atomicAdd(&test_results.seq, 1u);
@@ -183,11 +293,14 @@ g.test('workgroup_barrier_load_store')
atomicAdd(&test_results.weak, 1u);
}
`;
- const testShader = buildTestShader(
+ let testShader = buildTestShader(
getTestCode(t.params),
t.params.memType,
TestType.IntraWorkgroup
);
+ if (!t.params.normalBarrier) {
+ testShader += '\nvar<workgroup> placeholder_wg_var : u32;\n';
+ }
const resultShader = buildResultShader(
resultCode,
TestType.IntraWorkgroup,
@@ -198,7 +311,8 @@ g.test('workgroup_barrier_load_store')
memoryModelTestParams,
testShader,
resultShader,
- t.params.accessValueType
+ t.params.accessValueType,
+ t.params.memType === MemoryType.NonAtomicTextureClass
);
await memModelTester.run(12, 1);
});
@@ -215,13 +329,28 @@ g.test('workgroup_barrier_store_store')
.combine('accessValueType', kAccessValueTypes)
.combine('memType', kMemTypes)
.combine('accessPair', ['ww'] as const)
+ .combine('normalBarrier', [true, false] as const)
)
.beforeAllSubcases(t => {
if (t.params.accessValueType === 'f16') {
t.selectDeviceOrSkipTestCase('shader-f16');
}
+ t.skipIf(
+ !t.params.normalBarrier && t.params.memType !== MemoryType.NonAtomicWorkgroupClass,
+ 'workgroupUniformLoad does not have storage memory semantics'
+ );
+ t.skipIf(
+ t.params.memType === MemoryType.NonAtomicTextureClass && t.params.accessValueType === 'f16',
+ 'textures do not support f16 access'
+ );
})
.fn(async t => {
+ t.skipIf(
+ t.params.memType === MemoryType.NonAtomicTextureClass &&
+ !t.hasLanguageFeature('readonly_and_readwrite_storage_textures'),
+ 'requires RW storage textures feature'
+ );
+
const resultCode = `
if (mem_x_0 == 2u) {
atomicAdd(&test_results.seq, 1u);
@@ -229,11 +358,14 @@ g.test('workgroup_barrier_store_store')
atomicAdd(&test_results.weak, 1u);
}
`;
- const testShader = buildTestShader(
+ let testShader = buildTestShader(
getTestCode(t.params),
t.params.memType,
TestType.IntraWorkgroup
);
+ if (!t.params.normalBarrier) {
+ testShader += '\nvar<workgroup> placeholder_wg_var : u32;\n';
+ }
const resultShader = buildResultShader(
resultCode,
TestType.IntraWorkgroup,
@@ -244,7 +376,8 @@ g.test('workgroup_barrier_store_store')
memoryModelTestParams,
testShader,
resultShader,
- t.params.accessValueType
+ t.params.accessValueType,
+ t.params.memType === MemoryType.NonAtomicTextureClass
);
await memModelTester.run(10, 1);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/memory_model/memory_model_setup.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/memory_model/memory_model_setup.ts
index f8e5b9034c..8dee32b72d 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/memory_model/memory_model_setup.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/memory_model/memory_model_setup.ts
@@ -1,5 +1,7 @@
-import { GPUTest } from '../../../gpu_test';
+import { GPUTest } from '../../../gpu_test.js';
import { checkElementsPassPredicate } from '../../../util/check_contents.js';
+import { align } from '../../../util/math.js';
+import { PRNG } from '../../../util/prng.js';
/* All buffer sizes are counted in units of 4-byte words. */
@@ -15,6 +17,9 @@ import { checkElementsPassPredicate } from '../../../util/check_contents.js';
export type AccessValueType = 'f16' | 'u32';
export const kAccessValueTypes = ['f16', 'u32'] as const;
+/** The width used for textures (default compat limit in WebGPU). */
+const kWidth = 4096;
+
/* Parameter values are set heuristically, typically by a time-intensive search. */
export type MemoryModelTestParams = {
/* Number of invocations per workgroup. The workgroups are 1-dimensional. */
@@ -76,12 +81,33 @@ const numReadOutputs = 2;
type BufferWithSource = {
/** Buffer used by shader code. */
deviceBuf: GPUBuffer;
- /** Buffer populated from the host size, data is copied to device buffer for use by shader. */
+ /** Buffer populated from the host side, data is copied to device buffer for use by shader. */
srcBuf: GPUBuffer;
/** Size in bytes of the buffer. */
size: number;
};
+/** Represents a device texture and a utility buffer for resetting memory and copying parameters. */
+type TextureWithSource = {
+ /** Texture used by shader code. */
+ deviceTex: GPUTexture;
+ /** Buffer populated from the host side, data is copied to device buffer for use by shader. */
+ srcBuf: GPUBuffer;
+ /** Size in bytes of the buffer. */
+ size: number;
+};
+
+type SubBufferWithSource = {
+ /** Buffer used by shader code. This buffer is shared for multiple used */
+ deviceBuf: GPUBuffer;
+ /** Buffer populated from the host side, data is copied to device buffer for use by shader. */
+ srcBuf: GPUBuffer;
+ /** Size in bytes of this portion of the buffer. */
+ size: number;
+ /** Offset in bytes of this portion of the buffer */
+ offset: number;
+};
+
/** Specifies the buffers used during a memory model test. */
type MemoryModelBuffers = {
/** This is the memory region that testing threads read from and write to. */
@@ -102,6 +128,10 @@ type MemoryModelBuffers = {
stressParams: BufferWithSource;
};
+type MemoryModelTextures = {
+ testLocations: TextureWithSource;
+};
+
/** The number of stress params to add to the stress params buffer. */
const numStressParams = 12;
const barrierParamIndex = 0;
@@ -128,11 +158,11 @@ const bytesPerWord = 4;
* - enable directives, if necessary
* - the type alias for AccessValueType
*/
-function shaderPreamble(accessValueType: AccessValueType): string {
+function shaderPreamble(accessValueType: AccessValueType, constants: string): string {
if (accessValueType === 'f16') {
- return 'enable f16;\nalias AccessValueTy = f16;\n';
+ return `enable f16;\nalias AccessValueTy = f16;\n${constants}\n`;
}
- return `alias AccessValueTy = ${accessValueType};\n`;
+ return `alias AccessValueTy = ${accessValueType};\n${constants}\n`;
}
/**
@@ -175,10 +205,14 @@ export class MemoryModelTester {
protected test: GPUTest;
protected params: MemoryModelTestParams;
protected buffers: MemoryModelBuffers;
+ protected textures: MemoryModelTextures | undefined;
protected testPipeline: GPUComputePipeline;
protected testBindGroup: GPUBindGroup;
+ protected textureBindGroup: GPUBindGroup | undefined;
protected resultPipeline: GPUComputePipeline;
protected resultBindGroup: GPUBindGroup;
+ protected prng: PRNG;
+ protected useTexture: boolean;
/** Sets up a memory model test by initializing buffers and pipeline layouts. */
constructor(
@@ -186,24 +220,36 @@ export class MemoryModelTester {
params: MemoryModelTestParams,
testShader: string,
resultShader: string,
- accessValueType: AccessValueType = 'u32'
+ accessValueType: AccessValueType = 'u32',
+ useTexture: boolean = false
) {
+ this.prng = new PRNG(1);
this.test = t;
this.params = params;
-
- testShader = shaderPreamble(accessValueType) + testShader;
- resultShader = shaderPreamble(accessValueType) + resultShader;
+ this.useTexture = useTexture;
+
+ const workgroupXSize = Math.min(params.workgroupSize, t.device.limits.maxComputeWorkgroupSizeX);
+ const constants = `
+ const kNumBarriers = 1u; // MAINTENANCE_TODO: make barrier not an array
+ const kMaxWorkgroups = ${params.maxWorkgroups}u;
+ const kScratchMemorySize = ${params.scratchMemorySize}u;
+ const kWorkgroupXSize = ${workgroupXSize}u;
+ `;
+ testShader = shaderPreamble(accessValueType, constants) + testShader;
+ resultShader = shaderPreamble(accessValueType, constants) + resultShader;
// set up buffers
- const testingThreads = this.params.workgroupSize * this.params.testingWorkgroups;
+ const testingThreads = workgroupXSize * this.params.testingWorkgroups;
const testLocationsSize =
testingThreads * numMemLocations * this.params.memStride * bytesPerWord;
const testLocationsBuffer: BufferWithSource = {
deviceBuf: this.test.device.createBuffer({
+ label: 'testLocationsBuffer',
size: testLocationsSize,
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE,
}),
srcBuf: this.test.device.createBuffer({
+ label: 'testLocationsSrcBuf',
size: testLocationsSize,
usage: GPUBufferUsage.COPY_SRC,
}),
@@ -213,10 +259,12 @@ export class MemoryModelTester {
const readResultsSize = testingThreads * numReadOutputs * bytesPerWord;
const readResultsBuffer: BufferWithSource = {
deviceBuf: this.test.device.createBuffer({
+ label: 'readResultsBuffer',
size: readResultsSize,
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE,
}),
srcBuf: this.test.device.createBuffer({
+ label: 'readResultsSrcBuf',
size: readResultsSize,
usage: GPUBufferUsage.COPY_SRC,
}),
@@ -226,10 +274,12 @@ export class MemoryModelTester {
const testResultsSize = this.params.numBehaviors * bytesPerWord;
const testResultsBuffer: BufferWithSource = {
deviceBuf: this.test.device.createBuffer({
+ label: 'testResultsBuffer',
size: testResultsSize,
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
}),
srcBuf: this.test.device.createBuffer({
+ label: 'testResultsSrcBuffer',
size: testResultsSize,
usage: GPUBufferUsage.COPY_SRC,
}),
@@ -249,52 +299,87 @@ export class MemoryModelTester {
size: shuffledWorkgroupsSize,
};
- const barrierSize = bytesPerWord;
- const barrierBuffer: BufferWithSource = {
- deviceBuf: this.test.device.createBuffer({
- size: barrierSize,
- usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE,
- }),
+ if (this.useTexture) {
+ const numTexels = testLocationsSize / bytesPerWord;
+ const width = kWidth;
+ const height = numTexels / width;
+ const textureSize: GPUExtent3D = { width, height };
+ const textureLocations: TextureWithSource = {
+ deviceTex: this.test.device.createTexture({
+ format: 'r32uint',
+ dimension: '2d',
+ size: textureSize,
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.STORAGE_BINDING,
+ }),
+ srcBuf: testLocationsBuffer.srcBuf,
+ size: testLocationsSize,
+ };
+ this.textures = {
+ testLocations: textureLocations,
+ };
+ }
+
+ // Combine 3 arrays into 1 buffer as we need to keep the number of storage buffers to 4 for compat.
+ const falseSharingAvoidanceQuantum = 4096;
+ const barrierSize = align(bytesPerWord, falseSharingAvoidanceQuantum);
+ const scratchpadSize = align(
+ this.params.scratchMemorySize * bytesPerWord,
+ falseSharingAvoidanceQuantum
+ );
+ const scratchMemoryLocationsSize = align(
+ this.params.maxWorkgroups * bytesPerWord,
+ falseSharingAvoidanceQuantum
+ );
+ const comboSize = barrierSize + scratchpadSize + scratchMemoryLocationsSize;
+
+ const comboBuffer = this.test.device.createBuffer({
+ label: 'comboBuffer',
+ size: comboSize,
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE,
+ });
+
+ const barrierBuffer: SubBufferWithSource = {
+ deviceBuf: comboBuffer,
srcBuf: this.test.device.createBuffer({
+ label: 'barrierSrcBuf',
size: barrierSize,
usage: GPUBufferUsage.COPY_SRC,
}),
size: barrierSize,
+ offset: 0,
};
- const scratchpadSize = this.params.scratchMemorySize * bytesPerWord;
- const scratchpadBuffer: BufferWithSource = {
- deviceBuf: this.test.device.createBuffer({
- size: scratchpadSize,
- usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE,
- }),
+ const scratchpadBuffer: SubBufferWithSource = {
+ deviceBuf: comboBuffer,
srcBuf: this.test.device.createBuffer({
+ label: 'scratchpadSrcBuf',
size: scratchpadSize,
usage: GPUBufferUsage.COPY_SRC,
}),
size: scratchpadSize,
+ offset: barrierSize,
};
- const scratchMemoryLocationsSize = this.params.maxWorkgroups * bytesPerWord;
- const scratchMemoryLocationsBuffer: BufferWithSource = {
- deviceBuf: this.test.device.createBuffer({
- size: scratchMemoryLocationsSize,
- usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE,
- }),
+ const scratchMemoryLocationsBuffer: SubBufferWithSource = {
+ deviceBuf: comboBuffer,
srcBuf: this.test.device.createBuffer({
+ label: 'scratchMemoryLocationsSrcBuf',
size: scratchMemoryLocationsSize,
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.MAP_WRITE,
}),
size: scratchMemoryLocationsSize,
+ offset: barrierSize + scratchpadSize,
};
const stressParamsSize = numStressParams * bytesPerWord;
const stressParamsBuffer: BufferWithSource = {
deviceBuf: this.test.device.createBuffer({
+ label: 'stressParamsBuffer',
size: stressParamsSize,
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.UNIFORM,
}),
srcBuf: this.test.device.createBuffer({
+ label: 'stressParamsSrcBuf',
size: stressParamsSize,
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.MAP_WRITE,
}),
@@ -314,19 +399,50 @@ export class MemoryModelTester {
// set up pipeline layouts
const testLayout = this.test.device.createBindGroupLayout({
+ label: 'testLayout',
entries: [
{ binding: 0, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },
{ binding: 1, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },
{ binding: 2, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'read-only-storage' } },
{ binding: 3, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },
- { binding: 4, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },
- { binding: 5, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },
- { binding: 6, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'uniform' } },
+ { binding: 4, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'uniform' } },
],
});
+
+ let layouts: GPUBindGroupLayout[] = [testLayout];
+ if (this.useTexture) {
+ const textureLayout = this.test.device.createBindGroupLayout({
+ label: 'textureLayout',
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.COMPUTE,
+ storageTexture: {
+ access: 'read-write',
+ format: 'r32uint',
+ viewDimension: '2d',
+ },
+ },
+ ],
+ });
+ layouts = [testLayout, textureLayout];
+
+ const texLocations = (this.textures as MemoryModelTextures).testLocations.deviceTex;
+ this.textureBindGroup = this.test.device.createBindGroup({
+ label: 'textureBindGroup',
+ entries: [
+ {
+ binding: 0,
+ resource: texLocations.createView(),
+ },
+ ],
+ layout: textureLayout,
+ });
+ }
this.testPipeline = this.test.device.createComputePipeline({
+ label: 'testPipeline',
layout: this.test.device.createPipelineLayout({
- bindGroupLayouts: [testLayout],
+ bindGroupLayouts: layouts,
}),
compute: {
module: this.test.device.createShaderModule({
@@ -336,19 +452,19 @@ export class MemoryModelTester {
},
});
this.testBindGroup = this.test.device.createBindGroup({
+ label: 'testBindGroup',
entries: [
{ binding: 0, resource: { buffer: this.buffers.testLocations.deviceBuf } },
{ binding: 1, resource: { buffer: this.buffers.readResults.deviceBuf } },
{ binding: 2, resource: { buffer: this.buffers.shuffledWorkgroups.deviceBuf } },
- { binding: 3, resource: { buffer: this.buffers.barrier.deviceBuf } },
- { binding: 4, resource: { buffer: this.buffers.scratchpad.deviceBuf } },
- { binding: 5, resource: { buffer: this.buffers.scratchMemoryLocations.deviceBuf } },
- { binding: 6, resource: { buffer: this.buffers.stressParams.deviceBuf } },
+ { binding: 3, resource: { buffer: comboBuffer } },
+ { binding: 4, resource: { buffer: this.buffers.stressParams.deviceBuf } },
],
layout: testLayout,
});
const resultLayout = this.test.device.createBindGroupLayout({
+ label: 'resultLayout',
entries: [
{ binding: 0, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },
{ binding: 1, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },
@@ -357,6 +473,7 @@ export class MemoryModelTester {
],
});
this.resultPipeline = this.test.device.createComputePipeline({
+ label: 'resultPipeline',
layout: this.test.device.createPipelineLayout({
bindGroupLayouts: [resultLayout],
}),
@@ -368,6 +485,7 @@ export class MemoryModelTester {
},
});
this.resultBindGroup = this.test.device.createBindGroup({
+ label: 'resultBindGroup',
entries: [
{ binding: 0, resource: { buffer: this.buffers.testLocations.deviceBuf } },
{ binding: 1, resource: { buffer: this.buffers.readResults.deviceBuf } },
@@ -402,10 +520,16 @@ export class MemoryModelTester {
this.copyBufferToBuffer(encoder, this.buffers.scratchpad);
this.copyBufferToBuffer(encoder, this.buffers.scratchMemoryLocations);
this.copyBufferToBuffer(encoder, this.buffers.stressParams);
+ if (this.useTexture) {
+ this.copyBufferToTexture(encoder, (this.textures as MemoryModelTextures).testLocations);
+ }
const testPass = encoder.beginComputePass();
testPass.setPipeline(this.testPipeline);
testPass.setBindGroup(0, this.testBindGroup);
+ if (this.useTexture) {
+ testPass.setBindGroup(1, this.textureBindGroup as GPUBindGroup);
+ }
testPass.dispatchWorkgroups(numWorkgroups);
testPass.end();
@@ -443,8 +567,8 @@ export class MemoryModelTester {
* If the weak index's value is not 0, it means the test has observed a behavior disallowed by the memory model and
* is considered a test failure.
*/
- protected checkResult(weakIndex: number): (i: number, v: number) => boolean {
- return function (i: number, v: number): boolean {
+ protected checkResult(weakIndex: number): (i: number, v: number | bigint) => boolean {
+ return function (i: number, v: number | bigint): boolean {
if (i === weakIndex && v > 0) {
return false;
}
@@ -453,7 +577,7 @@ export class MemoryModelTester {
}
/** Returns a printer function that visualizes the results of checking the test results. */
- protected resultPrinter(weakIndex: number): (i: number) => string | number {
+ protected resultPrinter(weakIndex: number): (i: number) => string | number | bigint {
return function (i: number): string | number {
if (i === weakIndex) {
return 0;
@@ -464,16 +588,42 @@ export class MemoryModelTester {
}
/** Utility method that simplifies copying source buffers to device buffers. */
- protected copyBufferToBuffer(encoder: GPUCommandEncoder, buffer: BufferWithSource): void {
- encoder.copyBufferToBuffer(buffer.srcBuf, 0, buffer.deviceBuf, 0, buffer.size);
+ protected copyBufferToBuffer(
+ encoder: GPUCommandEncoder,
+ buffer: BufferWithSource | SubBufferWithSource
+ ): void {
+ encoder.copyBufferToBuffer(
+ buffer.srcBuf,
+ 0,
+ buffer.deviceBuf,
+ (buffer as SubBufferWithSource).offset || 0,
+ buffer.size
+ );
}
- /** Returns a random integer between 0 and the max. */
+ /** Utility method that simplifies copying source buffers to device textures. */
+ protected copyBufferToTexture(encoder: GPUCommandEncoder, texture: TextureWithSource): void {
+ const bytesPerWord = 4; // always uses r32uint format.
+ const numTexels = texture.size / bytesPerWord;
+ const size: GPUExtent3D = { width: kWidth, height: numTexels / kWidth };
+ encoder.copyBufferToTexture(
+ {
+ buffer: texture.srcBuf,
+ offset: 0,
+ bytesPerRow: kWidth * bytesPerWord,
+ rowsPerImage: size.height,
+ },
+ { texture: texture.deviceTex },
+ size
+ );
+ }
+
+ /** Returns a random integer in the range [0, max). */
protected getRandomInt(max: number): number {
- return Math.floor(Math.random() * max);
+ return this.prng.randomU32() % max;
}
- /** Returns a random number in between the min and max values. */
+ /** Returns a random number in the range [min, max). */
protected getRandomInRange(min: number, max: number): number {
if (min === max) {
return min;
@@ -626,7 +776,19 @@ const shaderMemStructures = `
};
struct IndexMemory {
- value: array<u32>
+ value: array<u32>,
+ };
+
+ struct AtomicMemoryBarrier {
+ value: array<atomic<u32>, kNumBarriers>
+ };
+
+ struct IndexMemoryScratchpad {
+ value: array<u32, kMaxWorkgroups>,
+ };
+
+ struct IndexMemoryScratchLocations {
+ value: array<u32, kScratchMemorySize>,
};
struct ReadResult {
@@ -635,7 +797,14 @@ const shaderMemStructures = `
};
struct ReadResults {
- value: array<ReadResult>
+ value: array<ReadResult>,
+ };
+
+ // These arrays are combine into 1 buffer because compat mode only supports 4 storage buffers by default.
+ struct CombinedData {
+ barrier: AtomicMemoryBarrier,
+ scratchpad: IndexMemoryScratchpad,
+ scratch_locations: IndexMemoryScratchLocations,
};
struct StressParamsMemory {
@@ -687,10 +856,8 @@ const twoBehaviorTestResultStructure = `
const commonTestShaderBindings = `
@group(0) @binding(1) var<storage, read_write> results : ReadResults;
@group(0) @binding(2) var<storage, read> shuffled_workgroups : IndexMemory;
- @group(0) @binding(3) var<storage, read_write> barrier : AtomicMemory;
- @group(0) @binding(4) var<storage, read_write> scratchpad : IndexMemory;
- @group(0) @binding(5) var<storage, read_write> scratch_locations : IndexMemory;
- @group(0) @binding(6) var<uniform> stress_params : StressParamsMemory;
+ @group(0) @binding(3) var<storage, read_write> combo : CombinedData;
+ @group(0) @binding(4) var<uniform> stress_params : StressParamsMemory;
`;
/** The combined bindings for a test on atomic memory. */
@@ -709,6 +876,11 @@ const nonAtomicTestShaderBindings = [
commonTestShaderBindings,
].join('\n');
+/** The extra binding for texture non-atomic texture tests. */
+const textureBindings = `
+@group(1) @binding(0) var texture_locations : texture_storage_2d<r32uint, read_write>;
+`;
+
/** Bindings used in the result aggregation phase of the test. */
const resultShaderBindings = `
@group(0) @binding(0) var<storage, read_write> test_locations : Memory;
@@ -750,6 +922,16 @@ const memoryLocationFunctions = `
}
`;
+/**
+ * Function to convert an index into an equivalent 2D coordinate for the texture.
+ */
+const textureFunctions = `
+ const kWidth = ${kWidth};
+ fn indexToCoord(idx : u32) -> vec2u {
+ return vec2u(idx % kWidth, idx / kWidth);
+ }
+`;
+
/** Functions that help add stress to the test. */
const testShaderFunctions = `
//Force the invocations in the workgroup to wait for each other, but without the general memory ordering
@@ -758,12 +940,12 @@ const testShaderFunctions = `
// the barrier but does not overly reduce testing throughput.
fn spin(limit: u32) {
var i : u32 = 0u;
- var bar_val : u32 = atomicAdd(&barrier.value[0], 1u);
+ var bar_val : u32 = atomicAdd(&combo.barrier.value[0], 1u);
loop {
if (i == 1024u || bar_val >= limit) {
break;
}
- bar_val = atomicAdd(&barrier.value[0], 0u);
+ bar_val = atomicAdd(&combo.barrier.value[0], 0u);
i = i + 1u;
}
}
@@ -773,44 +955,44 @@ const testShaderFunctions = `
// the compiler optimizing out unused loads, where 100,000 is larger than the maximum number of stress iterations used
// in any test.
fn do_stress(iterations: u32, pattern: u32, workgroup_id: u32) {
- let addr = scratch_locations.value[workgroup_id];
+ let addr = combo.scratch_locations.value[workgroup_id];
switch(pattern) {
case 0u: {
for(var i: u32 = 0u; i < iterations; i = i + 1u) {
- scratchpad.value[addr] = i;
- scratchpad.value[addr] = i + 1u;
+ combo.scratchpad.value[addr] = i;
+ combo.scratchpad.value[addr] = i + 1u;
}
}
case 1u: {
for(var i: u32 = 0u; i < iterations; i = i + 1u) {
- scratchpad.value[addr] = i;
- let tmp1: u32 = scratchpad.value[addr];
+ combo.scratchpad.value[addr] = i;
+ let tmp1: u32 = combo.scratchpad.value[addr];
if (tmp1 > 100000u) {
- scratchpad.value[addr] = i;
+ combo.scratchpad.value[addr] = i;
break;
}
}
}
case 2u: {
for(var i: u32 = 0u; i < iterations; i = i + 1u) {
- let tmp1: u32 = scratchpad.value[addr];
+ let tmp1: u32 = combo.scratchpad.value[addr];
if (tmp1 > 100000u) {
- scratchpad.value[addr] = i;
+ combo.scratchpad.value[addr] = i;
break;
}
- scratchpad.value[addr] = i;
+ combo.scratchpad.value[addr] = i;
}
}
case 3u: {
for(var i: u32 = 0u; i < iterations; i = i + 1u) {
- let tmp1: u32 = scratchpad.value[addr];
+ let tmp1: u32 = combo.scratchpad.value[addr];
if (tmp1 > 100000u) {
- scratchpad.value[addr] = i;
+ combo.scratchpad.value[addr] = i;
break;
}
- let tmp2: u32 = scratchpad.value[addr];
+ let tmp2: u32 = combo.scratchpad.value[addr];
if (tmp2 > 100000u) {
- scratchpad.value[addr] = i;
+ combo.scratchpad.value[addr] = i;
break;
}
}
@@ -827,7 +1009,7 @@ const testShaderFunctions = `
*/
const shaderEntryPoint = `
// Change to pipeline overridable constant when possible.
- const workgroupXSize = 256u;
+ const workgroupXSize = kWorkgroupXSize;
@compute @workgroup_size(workgroupXSize) fn main(
@builtin(local_invocation_id) local_invocation_id : vec3<u32>,
@builtin(workgroup_id) workgroup_id : vec3<u32>) {
@@ -980,6 +1162,18 @@ const storageMemoryNonAtomicTestShaderCode = [
testShaderCommonHeader,
].join('\n');
+/** The common shader code for the test shaders that perform non-atomic texture memory litmus tests. */
+const textureMemoryNonAtomicTestShaderCode = [
+ shaderMemStructures,
+ nonAtomicTestShaderBindings,
+ textureBindings,
+ memoryLocationFunctions,
+ textureFunctions,
+ testShaderFunctions,
+ shaderEntryPoint,
+ testShaderCommonHeader,
+].join('\n');
+
/** The common shader code for test shaders that perform atomic workgroup class memory litmus tests. */
const workgroupMemoryAtomicTestShaderCode = [
shaderMemStructures,
@@ -1023,6 +1217,8 @@ export enum MemoryType {
AtomicWorkgroupClass = 'atomic_workgroup',
/** Non-atomic memory in the workgroup address space. */
NonAtomicWorkgroupClass = 'non_atomic_workgroup',
+ /** Non-atomic memory in a texture. */
+ NonAtomicTextureClass = 'non_atomic_texture',
}
/**
@@ -1052,21 +1248,26 @@ export function buildTestShader(
testType: TestType
): string {
let memoryTypeCode;
- let isStorageAS = false;
+ let isGlobalSpace = false;
switch (memoryType) {
case MemoryType.AtomicStorageClass:
memoryTypeCode = storageMemoryAtomicTestShaderCode;
- isStorageAS = true;
+ isGlobalSpace = true;
break;
case MemoryType.NonAtomicStorageClass:
memoryTypeCode = storageMemoryNonAtomicTestShaderCode;
- isStorageAS = true;
+ isGlobalSpace = true;
break;
case MemoryType.AtomicWorkgroupClass:
memoryTypeCode = workgroupMemoryAtomicTestShaderCode;
break;
case MemoryType.NonAtomicWorkgroupClass:
memoryTypeCode = workgroupMemoryNonAtomicTestShaderCode;
+ break;
+ case MemoryType.NonAtomicTextureClass:
+ memoryTypeCode = textureMemoryNonAtomicTestShaderCode;
+ isGlobalSpace = true;
+ break;
}
let testTypeCode;
switch (testType) {
@@ -1074,7 +1275,7 @@ export function buildTestShader(
testTypeCode = interWorkgroupTestShaderCode;
break;
case TestType.IntraWorkgroup:
- if (isStorageAS) {
+ if (isGlobalSpace) {
testTypeCode = storageIntraWorkgroupTestShaderCode;
} else {
testTypeCode = intraWorkgroupTestShaderCode;
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/memory_model/texture_intra_invocation_coherence.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/memory_model/texture_intra_invocation_coherence.spec.ts
new file mode 100644
index 0000000000..1ab8c3d5b4
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/memory_model/texture_intra_invocation_coherence.spec.ts
@@ -0,0 +1,333 @@
+export const description = `
+Test that read/write storage textures are coherent within an invocation.
+
+Each invocation is assigned several random writing indices and a single
+read index from among those. Writes are randomly predicated (except the
+one corresponding to the read). Checks that an invocation can read data
+it has written to the texture previously.
+Does not test coherence between invocations
+
+Some platform (e.g. Metal) require a fence call to make writes visible
+to reads performed by the same invocation. These tests attempt to ensure
+WebGPU implementations emit correct fence calls.`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { unreachable, iterRange } from '../../../../common/util/util.js';
+import { GPUTest } from '../../../gpu_test.js';
+import { PRNG } from '../../../util/prng.js';
+
+const kRWStorageFormats: GPUTextureFormat[] = ['r32uint', 'r32sint', 'r32float'];
+const kDimensions: GPUTextureViewDimension[] = ['1d', '2d', '2d-array', '3d'];
+
+export const g = makeTestGroup(GPUTest);
+
+function indexToCoord(dim: GPUTextureViewDimension): string {
+ switch (dim) {
+ case '1d': {
+ return `
+fn indexToCoord(idx : u32) -> u32 {
+ return idx;
+}`;
+ }
+ case '2d':
+ case '2d-array': {
+ return `
+fn indexToCoord(idx : u32) -> vec2u {
+ return vec2u(idx % (wgx * num_wgs_x), idx / (wgx * num_wgs_x));
+}`;
+ }
+ case '3d': {
+ return `
+fn indexToCoord(idx : u32) -> vec3u {
+ return vec3u(idx % (wgx * num_wgs_x), idx / (wgx * num_wgs_x), 0);
+}`;
+ }
+ default: {
+ unreachable(`unhandled dimension: ${dim}`);
+ }
+ }
+ return ``;
+}
+
+function textureType(format: GPUTextureFormat, dim: GPUTextureViewDimension): string {
+ let t = `texture_storage_`;
+ switch (dim) {
+ case '1d': {
+ t += '1d';
+ break;
+ }
+ case '2d': {
+ t += '2d';
+ break;
+ }
+ case '2d-array': {
+ t += '2d_array';
+ break;
+ }
+ case '3d': {
+ t += '3d';
+ break;
+ }
+ default: {
+ unreachable(`unhandled dim: ${dim}`);
+ }
+ }
+ t += `<${format}, read_write>`;
+ return t;
+}
+
+function textureStore(dim: GPUTextureViewDimension, index: string): string {
+ let code = `textureStore(t, indexToCoord(${index}), `;
+ if (dim === '2d-array') {
+ code += `0, `;
+ }
+ code += `texel)`;
+ return code;
+}
+
+function textureLoad(dim: GPUTextureViewDimension, format: GPUTextureFormat): string {
+ let code = `textureLoad(t, indexToCoord(read_index[global_index])`;
+ if (dim === '2d-array') {
+ code += `, 0`;
+ }
+ code += `).x`;
+ if (format !== 'r32uint') {
+ code = `u32(${code})`;
+ }
+ return code;
+}
+
+function texel(format: GPUTextureFormat): string {
+ switch (format) {
+ case 'r32uint': {
+ return 'vec4u(global_index,0,0,0)';
+ }
+ case 'r32sint': {
+ return 'vec4i(i32(global_index),0,0,0)';
+ }
+ case 'r32float': {
+ return 'vec4f(f32(global_index),0,0,0)';
+ }
+ default: {
+ unreachable('unhandled format: ${format}');
+ }
+ }
+ return '';
+}
+
+function getTextureSize(numTexels: number, dim: GPUTextureViewDimension): GPUExtent3D {
+ const size: GPUExtent3D = { width: 1, height: 1, depthOrArrayLayers: 1 };
+ switch (dim) {
+ case '1d': {
+ size.width = numTexels;
+ break;
+ }
+ case '2d':
+ case '2d-array':
+ case '3d': {
+ size.width = numTexels / 2;
+ size.height = numTexels / 2;
+ // depthOrArrayLayers defaults to 1
+ break;
+ }
+ default: {
+ unreachable(`unhandled dim: ${dim}`);
+ }
+ }
+ return size;
+}
+
+g.test('texture_intra_invocation_coherence')
+ .desc(`Tests writes from an invocation are visible to reads from the same invocation`)
+ .params(u => u.combine('format', kRWStorageFormats).combine('dim', kDimensions))
+ .beforeAllSubcases(t => {
+ t.selectDeviceForTextureFormatOrSkipTestCase(t.params.format);
+ })
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported('readonly_and_readwrite_storage_textures');
+
+ const wgx = 16;
+ const wgy = t.device.limits.maxComputeInvocationsPerWorkgroup / wgx;
+ const num_wgs_x = 2;
+ const num_wgs_y = 2;
+ const invocations = wgx * wgy * num_wgs_x * num_wgs_y;
+ const num_writes_per_invocation = 4;
+
+ const code = `
+requires readonly_and_readwrite_storage_textures;
+
+@group(0) @binding(0)
+var t : ${textureType(t.params.format, t.params.dim)};
+
+@group(1) @binding(0)
+var<storage> write_indices : array<vec4u>;
+
+@group(1) @binding(1)
+var<storage> read_index : array<u32>;
+
+@group(1) @binding(2)
+var<storage> write_mask : array<vec4u>;
+
+@group(1) @binding(3)
+var<storage, read_write> output : array<u32>;
+
+const wgx = ${wgx}u;
+const wgy = ${wgy}u;
+const num_wgs_x = ${num_wgs_x}u;
+const num_wgs_y = ${num_wgs_y}u;
+
+${indexToCoord(t.params.dim)}
+
+@compute @workgroup_size(wgx, wgy, 1)
+fn main(@builtin(global_invocation_id) gid : vec3u) {
+ let global_index = gid.x + gid.y * num_wgs_x * wgx;
+
+ let write_index = write_indices[global_index];
+ let mask = write_mask[global_index];
+ let texel = ${texel(t.params.format)};
+
+ if mask.x != 0 {
+ ${textureStore(t.params.dim, 'write_index.x')};
+ }
+ if mask.y != 0 {
+ ${textureStore(t.params.dim, 'write_index.y')};
+ }
+ if mask.z != 0 {
+ ${textureStore(t.params.dim, 'write_index.z')};
+ }
+ if mask.w != 0 {
+ ${textureStore(t.params.dim, 'write_index.w')};
+ }
+ output[global_index] = ${textureLoad(t.params.dim, t.params.format)};
+}`;
+
+ // To get a variety of testing, seed the random number generator based on which case this is.
+ // This means subcases will not execute the same code.
+ const seed =
+ kRWStorageFormats.indexOf(t.params.format) * kRWStorageFormats.length +
+ kDimensions.indexOf(t.params.dim);
+ const prng = new PRNG(seed);
+
+ const num_write_indices = invocations * num_writes_per_invocation;
+ const write_indices = new Uint32Array([...iterRange(num_write_indices, x => x)]);
+ const write_masks = new Uint32Array([...iterRange(num_write_indices, x => 0)]);
+ // Shuffle the indices.
+ for (let i = 0; i < num_write_indices; i++) {
+ const remaining = num_write_indices - i;
+ const swapIdx = (prng.randomU32() % remaining) + i;
+ const tmp = write_indices[swapIdx];
+ write_indices[swapIdx] = write_indices[i];
+ write_indices[i] = tmp;
+
+ // Assign random write masks
+ const mask = prng.randomU32() % 2;
+ write_masks[i] = mask;
+ }
+ const num_read_indices = invocations;
+ const read_indices = new Uint32Array(num_read_indices);
+ for (let i = 0; i < num_read_indices; i++) {
+ // Pick a random index from index from this invocation's writes to read from.
+ // Ensure that write is not masked out.
+ const readIdx = prng.randomU32() % num_writes_per_invocation;
+ read_indices[i] = write_indices[num_writes_per_invocation * i + readIdx];
+ write_masks[num_writes_per_invocation * i + readIdx] = 1;
+ }
+
+ // Buffers
+ const write_index_buffer = t.makeBufferWithContents(
+ write_indices,
+ GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE
+ );
+ t.trackForCleanup(write_index_buffer);
+ const read_index_buffer = t.makeBufferWithContents(
+ read_indices,
+ GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE
+ );
+ t.trackForCleanup(read_index_buffer);
+ const write_mask_buffer = t.makeBufferWithContents(
+ write_masks,
+ GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE
+ );
+ t.trackForCleanup(write_mask_buffer);
+ const output_buffer = t.makeBufferWithContents(
+ new Uint32Array([...iterRange(invocations, x => 0)]),
+ GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE
+ );
+ t.trackForCleanup(output_buffer);
+
+ // Texture
+ const texture_size = getTextureSize(invocations * num_writes_per_invocation, t.params.dim);
+ const texture = t.device.createTexture({
+ format: t.params.format,
+ dimension: t.params.dim === '2d-array' ? '2d' : (t.params.dim as GPUTextureDimension),
+ size: texture_size,
+ usage: GPUTextureUsage.STORAGE_BINDING,
+ });
+ t.trackForCleanup(texture);
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code,
+ }),
+ entryPoint: 'main',
+ },
+ });
+
+ const bg0 = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: texture.createView({
+ format: t.params.format,
+ dimension: t.params.dim,
+ mipLevelCount: 1,
+ arrayLayerCount: 1,
+ }),
+ },
+ ],
+ });
+ const bg1 = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(1),
+ entries: [
+ {
+ binding: 0,
+ resource: {
+ buffer: write_index_buffer,
+ },
+ },
+ {
+ binding: 1,
+ resource: {
+ buffer: read_index_buffer,
+ },
+ },
+ {
+ binding: 2,
+ resource: {
+ buffer: write_mask_buffer,
+ },
+ },
+ {
+ binding: 3,
+ resource: {
+ buffer: output_buffer,
+ },
+ },
+ ],
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bg0);
+ pass.setBindGroup(1, bg1);
+ pass.dispatchWorkgroups(num_wgs_x, num_wgs_y, 1);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ const expectedOutput = new Uint32Array([...iterRange(num_read_indices, x => x)]);
+ t.expectGPUBufferValuesEqual(output_buffer, expectedOutput);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/robust_access.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/robust_access.spec.ts
index 965dd283dd..70d3438fcf 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/robust_access.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/robust_access.spec.ts
@@ -7,6 +7,7 @@ TODO: add tests to check that textureLoad operations stay in-bounds.
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { assert } from '../../../common/util/util.js';
+import { Float16Array } from '../../../external/petamoriken/float16/float16.js';
import { GPUTest } from '../../gpu_test.js';
import { align } from '../../util/math.js';
import { generateTypes, supportedScalarTypes, supportsAtomics } from '../types.js';
@@ -25,6 +26,7 @@ const kMinI32 = -0x8000_0000;
*/
async function runShaderTest(
t: GPUTest,
+ enables: string,
stage: GPUShaderStageFlags,
testSource: string,
layout: GPUPipelineLayout,
@@ -41,7 +43,7 @@ async function runShaderTest(
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE,
});
- const source = `
+ const source = `${enables}
struct Constants {
zero: u32
};
@@ -96,10 +98,12 @@ fn main() {
/** Fill an ArrayBuffer with sentinel values, except clear a region to zero. */
function testFillArrayBuffer(
array: ArrayBuffer,
- type: 'u32' | 'i32' | 'f32',
+ type: 'u32' | 'i32' | 'f16' | 'f32',
{ zeroByteStart, zeroByteCount }: { zeroByteStart: number; zeroByteCount: number }
) {
- const constructor = { u32: Uint32Array, i32: Int32Array, f32: Float32Array }[type];
+ const constructor = { u32: Uint32Array, i32: Int32Array, f16: Float16Array, f32: Float32Array }[
+ type
+ ];
assert(zeroByteCount % constructor.BYTES_PER_ELEMENT === 0);
new constructor(array).fill(42);
new constructor(array, zeroByteStart, zeroByteCount / constructor.BYTES_PER_ELEMENT).fill(0);
@@ -122,6 +126,7 @@ g.test('linear_memory')
TODO: Test types like vec2<atomic<i32>>, if that's allowed.
TODO: Test exprIndexAddon as constexpr.
TODO: Test exprIndexAddon as pipeline-overridable constant expression.
+ TODO: Adjust test logic to support array of f16 in the uniform address space
`
)
.params(u =>
@@ -168,10 +173,15 @@ g.test('linear_memory')
{ shadowingMode: 'function-scope' },
])
.expand('isAtomic', p => (supportsAtomics(p) ? [false, true] : [false]))
- .beginSubcases()
.expand('baseType', supportedScalarTypes)
+ .beginSubcases()
.expandWithParams(generateTypes)
)
+ .beforeAllSubcases(t => {
+ if (t.params.baseType === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
.fn(async t => {
const {
addressSpace,
@@ -189,6 +199,13 @@ g.test('linear_memory')
assert(_kTypeInfo !== undefined, 'not an indexable type');
assert('arrayLength' in _kTypeInfo);
+ if (baseType === 'f16' && addressSpace === 'uniform' && containerType === 'array') {
+ // Array elements must be aligned to 16 bytes, but the logic in generateTypes
+ // creates an array of vec4 of the baseType. But for f16 that's only 8 bytes.
+ // We would need to write more complex logic for that.
+ t.skip('Test logic does not handle array of f16 in the uniform address space');
+ }
+
let usesCanary = false;
let globalSource = '';
let testFunctionSource = '';
@@ -429,6 +446,8 @@ fn runTest() -> u32 {
],
});
+ const enables = t.params.baseType === 'f16' ? 'enable f16;' : '';
+
// Run it.
if (bufferBindingSize !== undefined && baseType !== 'bool') {
const expectedData = new ArrayBuffer(testBufferSize);
@@ -450,6 +469,7 @@ fn runTest() -> u32 {
// Run the shader, accessing the buffer.
await runShaderTest(
t,
+ enables,
GPUShaderStage.COMPUTE,
testSource,
layout,
@@ -475,6 +495,6 @@ fn runTest() -> u32 {
bufferBindingEnd
);
} else {
- await runShaderTest(t, GPUShaderStage.COMPUTE, testSource, layout, []);
+ await runShaderTest(t, enables, GPUShaderStage.COMPUTE, testSource, layout, []);
}
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/robust_access_vertex.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/robust_access_vertex.spec.ts
index de90301592..32a4c243df 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/robust_access_vertex.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/robust_access_vertex.spec.ts
@@ -545,6 +545,7 @@ g.test('vertex_buffer_access')
.combine('additionalBuffers', [0, 4])
.combine('partialLastNumber', [false, true])
.combine('offsetVertexBuffer', [false, true])
+ .beginSubcases()
.combine('errorScale', [0, 1, 4, 10 ** 2, 10 ** 4, 10 ** 6])
.unless(p => p.drawCallTestParameter === 'instanceCount' && p.errorScale > 10 ** 4) // To avoid timeout
)
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/shader_io/compute_builtins.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/shader_io/compute_builtins.spec.ts
index fcf3159c64..a40b426332 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/shader_io/compute_builtins.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/shader_io/compute_builtins.spec.ts
@@ -1,7 +1,6 @@
export const description = `Test compute shader builtin variables`;
import { makeTestGroup } from '../../../../common/framework/test_group.js';
-import { iterRange } from '../../../../common/util/util.js';
import { GPUTest } from '../../../gpu_test.js';
export const g = makeTestGroup(GPUTest);
@@ -98,17 +97,14 @@ g.test('inputs')
// WGSL shader that stores every builtin value to a buffer, for every invocation in the grid.
const wgsl = `
- struct S {
- data : array<u32>
+ struct Outputs {
+ local_id: vec3u,
+ local_index: u32,
+ global_id: vec3u,
+ group_id: vec3u,
+ num_groups: vec3u,
};
- struct V {
- data : array<vec3<u32>>
- };
- @group(0) @binding(0) var<storage, read_write> local_id_out : V;
- @group(0) @binding(1) var<storage, read_write> local_index_out : S;
- @group(0) @binding(2) var<storage, read_write> global_id_out : V;
- @group(0) @binding(3) var<storage, read_write> group_id_out : V;
- @group(0) @binding(4) var<storage, read_write> num_groups_out : V;
+ @group(0) @binding(0) var<storage, read_write> outputs : array<Outputs>;
${structures}
@@ -122,11 +118,13 @@ g.test('inputs')
) {
let group_index = ((${group_id}.z * ${num_groups}.y) + ${group_id}.y) * ${num_groups}.x + ${group_id}.x;
let global_index = group_index * ${invocationsPerGroup}u + ${local_index};
- local_id_out.data[global_index] = ${local_id};
- local_index_out.data[global_index] = ${local_index};
- global_id_out.data[global_index] = ${global_id};
- group_id_out.data[global_index] = ${group_id};
- num_groups_out.data[global_index] = ${num_groups};
+ var o: Outputs;
+ o.local_id = ${local_id};
+ o.local_index = ${local_index};
+ o.global_id = ${global_id};
+ o.group_id = ${group_id};
+ o.num_groups = ${num_groups};
+ outputs[global_index] = o;
}
`;
@@ -140,35 +138,24 @@ g.test('inputs')
},
});
- // Helper to create a `size`-byte buffer with binding number `binding`.
- function createBuffer(size: number, binding: number) {
- const buffer = t.device.createBuffer({
- size,
- usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
- });
- t.trackForCleanup(buffer);
-
- bindGroupEntries.push({
- binding,
- resource: {
- buffer,
- },
- });
-
- return buffer;
- }
+ // Offsets are in u32 size units
+ const kLocalIdOffset = 0;
+ const kLocalIndexOffset = 3;
+ const kGlobalIdOffset = 4;
+ const kGroupIdOffset = 8;
+ const kNumGroupsOffset = 12;
+ const kOutputElementSize = 16;
// Create the output buffers.
- const bindGroupEntries: GPUBindGroupEntry[] = [];
- const localIdBuffer = createBuffer(totalInvocations * 16, 0);
- const localIndexBuffer = createBuffer(totalInvocations * 4, 1);
- const globalIdBuffer = createBuffer(totalInvocations * 16, 2);
- const groupIdBuffer = createBuffer(totalInvocations * 16, 3);
- const numGroupsBuffer = createBuffer(totalInvocations * 16, 4);
+ const outputBuffer = t.device.createBuffer({
+ size: totalInvocations * kOutputElementSize * 4,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ });
+ t.trackForCleanup(outputBuffer);
const bindGroup = t.device.createBindGroup({
layout: pipeline.getBindGroupLayout(0),
- entries: bindGroupEntries,
+ entries: [{ binding: 0, resource: { buffer: outputBuffer } }],
});
// Run the shader.
@@ -204,11 +191,7 @@ g.test('inputs')
// Helper to check that the vec3<u32> value at each index of the provided `output` buffer
// matches the expected value for that invocation, as generated by the `getBuiltinValue`
// function. The `name` parameter is the builtin name, used for error messages.
- const checkEachIndex = (
- output: Uint32Array,
- name: string,
- getBuiltinValue: (groupId: vec3, localId: vec3) => vec3
- ) => {
+ const checkEachIndex = (output: Uint32Array) => {
// Loop over workgroups.
for (let gz = 0; gz < t.params.numGroups.z; gz++) {
for (let gy = 0; gy < t.params.numGroups.y; gy++) {
@@ -220,30 +203,44 @@ g.test('inputs')
const groupIndex = (gz * t.params.numGroups.y + gy) * t.params.numGroups.x + gx;
const localIndex = (lz * t.params.groupSize.y + ly) * t.params.groupSize.x + lx;
const globalIndex = groupIndex * invocationsPerGroup + localIndex;
- const expected = getBuiltinValue(
- { x: gx, y: gy, z: gz },
- { x: lx, y: ly, z: lz }
- );
- if (output[globalIndex * 4 + 0] !== expected.x) {
- return new Error(
- `${name}.x failed at group(${gx},${gy},${gz}) local(${lx},${ly},${lz}))\n` +
- ` expected: ${expected.x}\n` +
- ` got: ${output[globalIndex * 4 + 0]}`
- );
- }
- if (output[globalIndex * 4 + 1] !== expected.y) {
- return new Error(
- `${name}.y failed at group(${gx},${gy},${gz}) local(${lx},${ly},${lz}))\n` +
- ` expected: ${expected.y}\n` +
- ` got: ${output[globalIndex * 4 + 1]}`
+ const globalOffset = globalIndex * kOutputElementSize;
+
+ const expectEqual = (name: string, expected: number, actual: number) => {
+ if (actual !== expected) {
+ return new Error(
+ `${name} failed at group(${gx},${gy},${gz}) local(${lx},${ly},${lz}))\n` +
+ ` expected: ${expected}\n` +
+ ` got: ${actual}`
+ );
+ }
+ return undefined;
+ };
+
+ const checkVec3Value = (name: string, fieldOffset: number, expected: vec3) => {
+ const offset = globalOffset + fieldOffset;
+ return (
+ expectEqual(`${name}.x`, expected.x, output[offset + 0]) ||
+ expectEqual(`${name}.y`, expected.y, output[offset + 1]) ||
+ expectEqual(`${name}.z`, expected.z, output[offset + 2])
);
- }
- if (output[globalIndex * 4 + 2] !== expected.z) {
- return new Error(
- `${name}.z failed at group(${gx},${gy},${gz}) local(${lx},${ly},${lz}))\n` +
- ` expected: ${expected.z}\n` +
- ` got: ${output[globalIndex * 4 + 2]}`
+ };
+
+ const error =
+ checkVec3Value('local_id', kLocalIdOffset, { x: lx, y: ly, z: lz }) ||
+ checkVec3Value('global_id', kGlobalIdOffset, {
+ x: gx * t.params.groupSize.x + lx,
+ y: gy * t.params.groupSize.y + ly,
+ z: gz * t.params.groupSize.z + lz,
+ }) ||
+ checkVec3Value('group_id', kGroupIdOffset, { x: gx, y: gy, z: gz }) ||
+ checkVec3Value('num_groups', kNumGroupsOffset, t.params.numGroups) ||
+ expectEqual(
+ 'local_index',
+ localIndex,
+ output[globalOffset + kLocalIndexOffset]
);
+ if (error) {
+ return error;
}
}
}
@@ -254,44 +251,8 @@ g.test('inputs')
return undefined;
};
- // Check @builtin(local_invocation_index) values.
- t.expectGPUBufferValuesEqual(
- localIndexBuffer,
- new Uint32Array([...iterRange(totalInvocations, x => x % invocationsPerGroup)])
- );
-
- // Check @builtin(local_invocation_id) values.
- t.expectGPUBufferValuesPassCheck(
- localIdBuffer,
- outputData => checkEachIndex(outputData, 'local_invocation_id', (_, localId) => localId),
- { type: Uint32Array, typedLength: totalInvocations * 4 }
- );
-
- // Check @builtin(global_invocation_id) values.
- const getGlobalId = (groupId: vec3, localId: vec3) => {
- return {
- x: groupId.x * t.params.groupSize.x + localId.x,
- y: groupId.y * t.params.groupSize.y + localId.y,
- z: groupId.z * t.params.groupSize.z + localId.z,
- };
- };
- t.expectGPUBufferValuesPassCheck(
- globalIdBuffer,
- outputData => checkEachIndex(outputData, 'global_invocation_id', getGlobalId),
- { type: Uint32Array, typedLength: totalInvocations * 4 }
- );
-
- // Check @builtin(workgroup_id) values.
- t.expectGPUBufferValuesPassCheck(
- groupIdBuffer,
- outputData => checkEachIndex(outputData, 'workgroup_id', (groupId, _) => groupId),
- { type: Uint32Array, typedLength: totalInvocations * 4 }
- );
-
- // Check @builtin(num_workgroups) values.
- t.expectGPUBufferValuesPassCheck(
- numGroupsBuffer,
- outputData => checkEachIndex(outputData, 'num_workgroups', () => t.params.numGroups),
- { type: Uint32Array, typedLength: totalInvocations * 4 }
- );
+ t.expectGPUBufferValuesPassCheck(outputBuffer, outputData => checkEachIndex(outputData), {
+ type: Uint32Array,
+ typedLength: outputBuffer.size / 4,
+ });
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/shader_io/fragment_builtins.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/shader_io/fragment_builtins.spec.ts
new file mode 100644
index 0000000000..2bb03dab8d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/shader_io/fragment_builtins.spec.ts
@@ -0,0 +1,1410 @@
+export const description = `Test fragment shader builtin variables and inter-stage variables
+
+* test builtin(position)
+* test @interpolate
+* test builtin(sample_index)
+* test builtin(front_facing)
+* test builtin(sample_mask)
+
+Note: @interpolate settings and sample_index affect whether or not the fragment shader
+is evaluated per-fragment or per-sample. With @interpolate(, sample) or usage of
+@builtin(sample_index) the fragment shader should be executed per-sample.
+
+* sample_mask output is tested in
+ src/webgpu/api/operation/render_pipeline/sample_mask.spec.ts
+
+* frag_depth output is tested in
+ src/webgpu/api/operation/rendering/depth_clip_clamp.spec.ts
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { ErrorWithExtra, assert, range, unreachable } from '../../../../common/util/util.js';
+import { InterpolationSampling, InterpolationType } from '../../../constants.js';
+import { GPUTest } from '../../../gpu_test.js';
+import { getMultisampleFragmentOffsets } from '../../../multisample_info.js';
+import { dotProduct, subtractVectors } from '../../../util/math.js';
+import { TexelView } from '../../../util/texture/texel_view.js';
+import { findFailedPixels } from '../../../util/texture/texture_ok.js';
+
+export const g = makeTestGroup(GPUTest);
+
+const s_deviceToPipelineMap = new WeakMap<
+ GPUDevice,
+ {
+ texture_2d?: GPUComputePipeline;
+ texture_multisampled_2d?: GPUComputePipeline;
+ }
+>();
+
+/**
+ * Returns an object of pipelines associated
+ * by weakmap to a device so we can cache pipelines.
+ */
+function getPipelinesForDevice(device: GPUDevice) {
+ let pipelines = s_deviceToPipelineMap.get(device);
+ if (!pipelines) {
+ pipelines = {};
+ s_deviceToPipelineMap.set(device, pipelines);
+ }
+ return pipelines;
+}
+
+/**
+ * Gets a compute pipeline that will copy the given texture if passed
+ * a dispatch size of texture.width, texture.height
+ * @param device a device
+ * @param texture texture the pipeline is needed for.
+ * @returns A GPUComputePipeline
+ */
+function getCopyMultisamplePipelineForDevice(device: GPUDevice, textures: GPUTexture[]) {
+ assert(textures.length === 4);
+ assert(textures[0].sampleCount === textures[1].sampleCount);
+ assert(textures[0].sampleCount === textures[2].sampleCount);
+ assert(textures[0].sampleCount === textures[3].sampleCount);
+
+ const pipelineType = textures[0].sampleCount > 1 ? 'texture_multisampled_2d' : 'texture_2d';
+ const pipelines = getPipelinesForDevice(device);
+ let pipeline = pipelines[pipelineType];
+ if (!pipeline) {
+ const isMultisampled = pipelineType === 'texture_multisampled_2d';
+ const numSamples = isMultisampled ? 'textureNumSamples(texture0)' : '1u';
+ const sampleIndex = isMultisampled ? 'sampleIndex' : '0';
+ const module = device.createShaderModule({
+ code: `
+ @group(0) @binding(0) var texture0: ${pipelineType}<f32>;
+ @group(0) @binding(1) var texture1: ${pipelineType}<f32>;
+ @group(0) @binding(2) var texture2: ${pipelineType}<f32>;
+ @group(0) @binding(3) var texture3: ${pipelineType}<f32>;
+ @group(0) @binding(4) var<storage, read_write> buffer: array<f32>;
+
+ @compute @workgroup_size(1) fn cs(@builtin(global_invocation_id) id: vec3u) {
+ let numSamples = ${numSamples};
+ let dimensions = textureDimensions(texture0);
+ let sampleIndex = id.x % numSamples;
+ let tx = id.x / numSamples;
+ let offset = ((id.y * dimensions.x + tx) * numSamples + sampleIndex) * 4;
+ let r = vec4u(textureLoad(texture0, vec2u(tx, id.y), ${sampleIndex}) * 255.0);
+ let g = vec4u(textureLoad(texture1, vec2u(tx, id.y), ${sampleIndex}) * 255.0);
+ let b = vec4u(textureLoad(texture2, vec2u(tx, id.y), ${sampleIndex}) * 255.0);
+ let a = vec4u(textureLoad(texture3, vec2u(tx, id.y), ${sampleIndex}) * 255.0);
+
+ // expand rgba8unorm values back to their byte form, add them together
+ // and cast them to an f32 so we can recover the f32 values we encoded
+ // in the rgba8unorm texture.
+ buffer[offset + 0] = bitcast<f32>(dot(r, vec4u(0x1000000, 0x10000, 0x100, 0x1)));
+ buffer[offset + 1] = bitcast<f32>(dot(g, vec4u(0x1000000, 0x10000, 0x100, 0x1)));
+ buffer[offset + 2] = bitcast<f32>(dot(b, vec4u(0x1000000, 0x10000, 0x100, 0x1)));
+ buffer[offset + 3] = bitcast<f32>(dot(a, vec4u(0x1000000, 0x10000, 0x100, 0x1)));
+ }
+ `,
+ });
+
+ pipeline = device.createComputePipeline({
+ label: 'copy multisampled texture pipeline',
+ layout: 'auto',
+ compute: {
+ module,
+ entryPoint: 'cs',
+ },
+ });
+
+ pipelines[pipelineType] = pipeline;
+ }
+ return pipeline;
+}
+
+function isTextureSameDimensions(a: GPUTexture, b: GPUTexture) {
+ return (
+ a.sampleCount === b.sampleCount &&
+ a.width === b.width &&
+ a.height === b.height &&
+ a.depthOrArrayLayers === b.depthOrArrayLayers
+ );
+}
+
+/**
+ * Copies a texture (even if multisampled) to a buffer
+ * @param t a gpu test
+ * @param texture texture to copy
+ * @returns buffer with copy of texture, mip level 0, array layer 0.
+ */
+function copyRGBA8EncodedFloatTexturesToBufferIncludingMultisampledTextures(
+ t: GPUTest,
+ textures: GPUTexture[]
+) {
+ assert(textures.length === 4);
+ assert(isTextureSameDimensions(textures[0], textures[1]));
+ assert(isTextureSameDimensions(textures[0], textures[2]));
+ assert(isTextureSameDimensions(textures[0], textures[3]));
+ const { width, height, sampleCount } = textures[0];
+
+ const copyBuffer = t.device.createBuffer({
+ size: width * height * sampleCount * 4 * 4,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ });
+ t.trackForCleanup(copyBuffer);
+
+ const buffer = t.device.createBuffer({
+ size: copyBuffer.size,
+ usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
+ });
+ t.trackForCleanup(buffer);
+
+ const pipeline = getCopyMultisamplePipelineForDevice(t.device, textures);
+ const encoder = t.device.createCommandEncoder();
+
+ const textureEntries = textures.map(
+ (texture, i) => ({ binding: i, resource: texture.createView() }) as GPUBindGroupEntry
+ );
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [...textureEntries, { binding: 4, resource: { buffer: copyBuffer } }],
+ });
+
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(width * sampleCount, height);
+ pass.end();
+
+ encoder.copyBufferToBuffer(copyBuffer, 0, buffer, 0, buffer.size);
+
+ t.device.queue.submit([encoder.finish()]);
+
+ return buffer;
+}
+
+/* column constants */
+const kX = 0;
+const kY = 1;
+const kZ = 2;
+const kW = 3;
+
+/**
+ * Gets a column of values from an array of arrays.
+ */
+function getColumn(values: readonly number[][], colNum: number) {
+ return values.map(v => v[colNum]);
+}
+
+/**
+ * Computes the linear interpolation of 3 values from 3 vertices of a triangle
+ * based on barycentric coordinates
+ */
+function linearInterpolation(baryCoords: readonly number[], interCoords: readonly number[]) {
+ return dotProduct(baryCoords, interCoords);
+}
+
+/**
+ * Computes the perspective interpolation of 3 values from 3 vertices of a
+ * triangle based on barycentric coordinates and their corresponding clip space
+ * W coordinates.
+ */
+function perspectiveInterpolation(
+ barycentricCoords: readonly number[],
+ clipSpaceTriangleCoords: readonly number[][],
+ interCoords: readonly number[]
+) {
+ const [a, b, c] = barycentricCoords;
+ const [fa, fb, fc] = interCoords;
+ const wa = clipSpaceTriangleCoords[0][kW];
+ const wb = clipSpaceTriangleCoords[1][kW];
+ const wc = clipSpaceTriangleCoords[2][kW];
+
+ return ((a * fa) / wa + (b * fb) / wb + (c * fc) / wc) / (a / wa + b / wb + c / wc);
+}
+
+/**
+ * Converts clip space coords to NDC coords
+ */
+function clipSpaceToNDC(point: readonly number[]) {
+ return point.map(v => v / point[kW]);
+}
+
+/**
+ * Converts NDC coords to window coords.
+ */
+function ndcToWindow(ndcPoint: readonly number[], viewport: readonly number[]) {
+ const [xd, yd, zd] = ndcPoint;
+ const px = viewport[2];
+ const py = viewport[3];
+ const ox = viewport[0] + px / 2;
+ const oy = viewport[1] + py / 2;
+ const zNear = viewport[4];
+ const zFar = viewport[5];
+ // prettier-ignore
+ return [
+ px / 2 * xd + ox,
+ -py / 2 * yd + oy,
+ zd * (zFar - zNear) + zNear,
+ ];
+}
+
+/**
+ * Computes barycentric coordinates of triangle for point p.
+ * @param trianglePoints points for triangle
+ * @param p point in triangle (or relative to it)
+ * @returns barycentric coords of p
+ */
+function calcBarycentricCoordinates(trianglePoints: number[][], p: number[]) {
+ const [a, b, c] = trianglePoints;
+
+ const v0 = subtractVectors(b, a);
+ const v1 = subtractVectors(c, a);
+ const v2 = subtractVectors(p, a);
+
+ const dot00 = dotProduct(v0, v0);
+ const dot01 = dotProduct(v0, v1);
+ const dot11 = dotProduct(v1, v1);
+ const dot20 = dotProduct(v2, v0);
+ const dot21 = dotProduct(v2, v1);
+
+ const denom = 1 / (dot00 * dot11 - dot01 * dot01);
+ const v = (dot11 * dot20 - dot01 * dot21) * denom;
+ const w = (dot00 * dot21 - dot01 * dot20) * denom;
+ const u = 1 - v - w;
+
+ return [u, v, w];
+}
+
+/**
+ * Returns true if point is inside triangle
+ */
+function isInsideTriangle(barycentricCoords: number[]) {
+ for (const v of barycentricCoords) {
+ if (v < 0 || v > 1) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * Returns true if windowPoints define a clockwise triangle
+ */
+function isTriangleClockwise(windowPoints: readonly number[][]) {
+ let sum = 0;
+ for (let i = 0; i < 3; ++i) {
+ const p0 = windowPoints[i];
+ const p1 = windowPoints[(i + 1) % 3];
+ sum += p0[kX] * p1[kY] - p1[kX] * p0[kY];
+ }
+ return sum >= 0;
+}
+
+type FragData = {
+ baseVertexIndex: number;
+ fragmentPoint: readonly number[];
+ fragmentBarycentricCoords: readonly number[];
+ sampleBarycentricCoords: readonly number[];
+ clipSpacePoints: readonly number[][];
+ ndcPoints: readonly number[][];
+ windowPoints: readonly number[][];
+ sampleIndex: number;
+ sampleMask: number;
+ frontFacing: boolean;
+};
+
+/**
+ * For each sample in texture, computes the values that would be provided
+ * to the shader as `@builtin(position)` if the texture was a render target
+ * and every point in the texture was inside the triangle.
+ * @param texture The texture
+ * @param clipSpacePoints triangle points in clip space
+ * @returns the expected values for each sample
+ */
+function generateFragmentInputs({
+ width,
+ height,
+ nearFar,
+ sampleCount,
+ frontFace,
+ clipSpacePoints,
+ interpolateFn,
+}: {
+ width: number;
+ height: number;
+ nearFar: readonly number[];
+ sampleCount: number;
+ frontFace?: GPUFrontFace;
+ clipSpacePoints: readonly number[][];
+ interpolateFn: (fragData: FragData) => number[];
+}) {
+ const expected = new Float32Array(width * height * sampleCount * 4);
+
+ const viewport = [0, 0, width, height, ...nearFar];
+
+ // For each triangle
+ for (let vertexIndex = 0; vertexIndex < clipSpacePoints.length; vertexIndex += 3) {
+ const ndcPoints = clipSpacePoints.slice(vertexIndex, vertexIndex + 3).map(clipSpaceToNDC);
+ const windowPoints = ndcPoints.map(p => ndcToWindow(p, viewport));
+ const windowPoints2D = windowPoints.map(p => p.slice(0, 2));
+
+ const cw = isTriangleClockwise(windowPoints2D);
+ const frontFacing = frontFace === 'cw' ? cw : !cw;
+ const fragmentOffsets = getMultisampleFragmentOffsets(sampleCount)!;
+
+ for (let y = 0; y < height; ++y) {
+ for (let x = 0; x < width; ++x) {
+ let sampleMask = 0;
+ for (let sampleIndex = 0; sampleIndex < sampleCount; ++sampleIndex) {
+ const localSampleMask = 1 << sampleIndex;
+ const multisampleOffset = fragmentOffsets[sampleIndex];
+ const sampleFragmentPoint = [x + multisampleOffset[0], y + multisampleOffset[1]];
+ const sampleBarycentricCoords = calcBarycentricCoordinates(
+ windowPoints2D,
+ sampleFragmentPoint
+ );
+
+ const inside = isInsideTriangle(sampleBarycentricCoords);
+ if (inside) {
+ sampleMask |= localSampleMask;
+ }
+ }
+
+ for (let sampleIndex = 0; sampleIndex < sampleCount; ++sampleIndex) {
+ const fragmentPoint = [x + 0.5, y + 0.5];
+ const multisampleOffset = fragmentOffsets[sampleIndex];
+ const sampleFragmentPoint = [x + multisampleOffset[0], y + multisampleOffset[1]];
+ const fragmentBarycentricCoords = calcBarycentricCoordinates(
+ windowPoints2D,
+ fragmentPoint
+ );
+ const sampleBarycentricCoords = calcBarycentricCoordinates(
+ windowPoints2D,
+ sampleFragmentPoint
+ );
+
+ const inside = isInsideTriangle(sampleBarycentricCoords);
+ if (inside) {
+ const output = interpolateFn({
+ baseVertexIndex: vertexIndex,
+ fragmentPoint,
+ fragmentBarycentricCoords,
+ sampleBarycentricCoords,
+ clipSpacePoints,
+ ndcPoints,
+ windowPoints,
+ sampleIndex,
+ sampleMask,
+ frontFacing,
+ });
+
+ const offset = ((y * width + x) * sampleCount + sampleIndex) * 4;
+ expected.set(output, offset);
+ }
+ }
+ }
+ }
+ }
+ return expected;
+}
+
+/**
+ * Computes 'builtin(position)`
+ */
+function computeFragmentPosition({
+ fragmentPoint,
+ fragmentBarycentricCoords,
+ clipSpacePoints,
+ windowPoints,
+}: FragData) {
+ return [
+ fragmentPoint[0],
+ fragmentPoint[1],
+ linearInterpolation(fragmentBarycentricCoords, getColumn(windowPoints, kZ)),
+ 1 /
+ perspectiveInterpolation(
+ fragmentBarycentricCoords,
+ clipSpacePoints,
+ getColumn(clipSpacePoints, kW)
+ ),
+ ];
+}
+
+/**
+ * Creates a function that will compute the interpolation of an inter-stage variable.
+ */
+function createInterStageInterpolationFn(
+ interStagePoints: number[][],
+ type: InterpolationType,
+ sampling: InterpolationSampling | undefined
+) {
+ return function ({
+ baseVertexIndex,
+ fragmentBarycentricCoords,
+ sampleBarycentricCoords,
+ clipSpacePoints,
+ }: FragData) {
+ const triangleInterStagePoints = interStagePoints.slice(baseVertexIndex, baseVertexIndex + 3);
+ const barycentricCoords =
+ sampling === 'center' ? fragmentBarycentricCoords : sampleBarycentricCoords;
+ switch (type) {
+ case 'perspective':
+ return triangleInterStagePoints[0].map((_, colNum: number) =>
+ perspectiveInterpolation(
+ barycentricCoords,
+ clipSpacePoints,
+ getColumn(triangleInterStagePoints, colNum)
+ )
+ );
+ break;
+ case 'linear':
+ return triangleInterStagePoints[0].map((_, colNum: number) =>
+ linearInterpolation(barycentricCoords, getColumn(triangleInterStagePoints, colNum))
+ );
+ break;
+ case 'flat':
+ return triangleInterStagePoints[0];
+ break;
+ default:
+ unreachable();
+ }
+ };
+}
+
+/**
+ * Creates a function that will compute the interpolation of an inter-stage variable
+ * and then return [1, 0, 0, 0] if all interpolated values are between 0.0 and 1.0 inclusive
+ * or [-1, 0, 0, 0] otherwise.
+ */
+function createInterStageInterpolationBetween0And1TestFn(
+ interStagePoints: number[][],
+ type: InterpolationType,
+ sampling: InterpolationSampling | undefined
+) {
+ const interpolateFn = createInterStageInterpolationFn(interStagePoints, type, sampling);
+ return function (fragData: FragData) {
+ const interpolatedValues = interpolateFn(fragData);
+ const allTrue = interpolatedValues.reduce((all, v) => all && v >= 0 && v <= 1, true);
+ return [allTrue ? 1 : -1, 0, 0, 0];
+ };
+}
+
+/**
+ * Computes 'builtin(sample_index)'
+ */
+function computeFragmentSampleIndex({ sampleIndex }: FragData) {
+ return [sampleIndex, 0, 0, 0];
+}
+
+/**
+ * Computes 'builtin(front_facing)'
+ */
+function computeFragmentFrontFacing({ frontFacing }: FragData) {
+ return [frontFacing ? 1 : 0, 0, 0, 0];
+}
+
+/**
+ * Computes 'builtin(sample_mask)'
+ */
+function computeSampleMask({ sampleMask }: FragData) {
+ return [sampleMask, 0, 0, 0];
+}
+
+/**
+ * Renders float32 fragment shader inputs values to 4 rgba8unorm textures that
+ * can be multisampled textures. It stores each of the channels, r, g, b, a of
+ * the shader input to a separate texture, doing the math required to store the
+ * float32 value into an rgba8unorm texel.
+ *
+ * Note: We could try to store the output to an vec4f storage buffer.
+ * Unfortunately, using a storage buffer has the issue that we need to compute
+ * an index with the very thing we're trying to test. Similarly, if we used a
+ * storage texture we would need to compute texture locations with the things
+ * we're trying to test. Also, using a storage buffer seems to affect certain
+ * backends like M1 Mac so it seems better to stick to rgba8unorm here and test
+ * using a storage buffer in a fragment shader separately.
+ *
+ * We can't use rgba32float because it's optional. We can't use rgba16float
+ * because it's optional in compat. We can't we use rgba32uint as that can't be
+ * multisampled.
+ */
+async function renderFragmentShaderInputsTo4TexturesAndReadbackValues(
+ t: GPUTest,
+ {
+ interpolationType,
+ interpolationSampling,
+ sampleCount,
+ width,
+ height,
+ nearFar,
+ frontFace,
+ clipSpacePoints,
+ interStagePoints,
+ fragInCode,
+ outputCode,
+ }: {
+ interpolationType: InterpolationType;
+ interpolationSampling?: InterpolationSampling;
+ width: number;
+ height: number;
+ sampleCount: number;
+ frontFace?: GPUFrontFace;
+ nearFar: readonly number[];
+ clipSpacePoints: readonly number[][];
+ interStagePoints: readonly number[][];
+ fragInCode: string;
+ outputCode: string;
+ }
+) {
+ const interpolate = `${interpolationType}${
+ interpolationSampling ? `, ${interpolationSampling}` : ''
+ }`;
+ const module = t.device.createShaderModule({
+ code: `
+ struct Uniforms {
+ resolution: vec2f,
+ };
+
+ @group(0) @binding(0) var<uniform> uni: Uniforms;
+
+ struct VertexOut {
+ @builtin(position) position: vec4f,
+ @location(0) @interpolate(${interpolate}) interpolatedValue: vec4f,
+ };
+
+ @vertex fn vs(@builtin(vertex_index) vNdx: u32) -> VertexOut {
+ let pos = array(
+ ${clipSpacePoints.map(p => `vec4f(${p.join(', ')})`).join(', ')}
+ );
+ let interStage = array(
+ ${interStagePoints.map(p => `vec4f(${p.join(', ')})`).join(', ')}
+ );
+ var v: VertexOut;
+ v.position = pos[vNdx];
+ v.interpolatedValue = interStage[vNdx];
+ _ = uni;
+ return v;
+ }
+
+ struct FragmentIn {
+ @builtin(position) position: vec4f,
+ @location(0) @interpolate(${interpolate}) interpolatedValue: vec4f,
+ ${fragInCode}
+ };
+
+ struct FragOut {
+ @location(0) out0: vec4f,
+ @location(1) out1: vec4f,
+ @location(2) out2: vec4f,
+ @location(3) out3: vec4f,
+ };
+
+ fn u32ToRGBAUnorm(u: u32) -> vec4f {
+ return vec4f(
+ f32((u >> 24) & 0xFF) / 255.0,
+ f32((u >> 16) & 0xFF) / 255.0,
+ f32((u >> 8) & 0xFF) / 255.0,
+ f32((u >> 0) & 0xFF) / 255.0,
+ );
+ }
+
+ @fragment fn fs(fin: FragmentIn) -> FragOut {
+ var f: FragOut;
+ let v = ${outputCode};
+ let u = bitcast<vec4u>(v);
+ f.out0 = u32ToRGBAUnorm(u[0]);
+ f.out1 = u32ToRGBAUnorm(u[1]);
+ f.out2 = u32ToRGBAUnorm(u[2]);
+ f.out3 = u32ToRGBAUnorm(u[3]);
+ _ = fin.interpolatedValue;
+ return f;
+ }
+ `,
+ });
+
+ const textures = range(4, () => {
+ const texture = t.device.createTexture({
+ size: [width, height],
+ usage:
+ GPUTextureUsage.RENDER_ATTACHMENT |
+ GPUTextureUsage.TEXTURE_BINDING |
+ GPUTextureUsage.COPY_SRC,
+ format: 'rgba8unorm',
+ sampleCount,
+ });
+ t.trackForCleanup(texture);
+ return texture;
+ });
+
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module,
+ entryPoint: 'vs',
+ },
+ fragment: {
+ module,
+ entryPoint: 'fs',
+ targets: textures.map(() => ({ format: 'rgba8unorm' })),
+ },
+ ...(frontFace && {
+ primitive: {
+ frontFace,
+ },
+ }),
+ multisample: {
+ count: sampleCount,
+ },
+ });
+
+ const uniformBuffer = t.device.createBuffer({
+ size: 8,
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
+ });
+ t.trackForCleanup(uniformBuffer);
+ t.device.queue.writeBuffer(uniformBuffer, 0, new Float32Array([width, height]));
+
+ const viewport = [0, 0, width, height, ...nearFar] as const;
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [{ binding: 0, resource: { buffer: uniformBuffer } }],
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: textures.map(texture => ({
+ view: texture.createView(),
+ loadOp: 'clear',
+ storeOp: 'store',
+ })),
+ });
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.setViewport(viewport[0], viewport[1], viewport[2], viewport[3], viewport[4], viewport[5]);
+ pass.draw(clipSpacePoints.length);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ const buffer = copyRGBA8EncodedFloatTexturesToBufferIncludingMultisampledTextures(t, textures);
+ await buffer.mapAsync(GPUMapMode.READ);
+ return new Float32Array(buffer.getMappedRange());
+}
+
+function checkSampleRectsApproximatelyEqual({
+ width,
+ height,
+ sampleCount,
+ actual,
+ expected,
+ maxDiffULPsForFloatFormat,
+}: {
+ width: number;
+ height: number;
+ sampleCount: number;
+ actual: Float32Array;
+ expected: Float32Array;
+ maxDiffULPsForFloatFormat: number;
+}) {
+ const subrectOrigin = [0, 0, 0];
+ const subrectSize = [width * sampleCount, height, 1];
+ const areaDesc = {
+ bytesPerRow: width * sampleCount * 4 * 4,
+ rowsPerImage: height,
+ subrectOrigin,
+ subrectSize,
+ };
+
+ const format = 'rgba32float';
+ const actTexelView = TexelView.fromTextureDataByReference(
+ format,
+ new Uint8Array(actual.buffer),
+ areaDesc
+ );
+ const expTexelView = TexelView.fromTextureDataByReference(
+ format,
+ new Uint8Array(expected.buffer),
+ areaDesc
+ );
+
+ const failedPixelsMessage = findFailedPixels(
+ format,
+ { x: 0, y: 0, z: 0 },
+ { width: width * sampleCount, height, depthOrArrayLayers: 1 },
+ { actTexelView, expTexelView },
+ { maxDiffULPsForFloatFormat }
+ );
+
+ if (failedPixelsMessage !== undefined) {
+ const msg = 'Texture level had unexpected contents:\n' + failedPixelsMessage;
+ return new ErrorWithExtra(msg, () => ({
+ expTexelView,
+ actTexelView,
+ }));
+ }
+
+ return undefined;
+}
+
+g.test('inputs,position')
+ .desc(
+ `
+ Test fragment shader builtin(position) values.
+
+ Note: @builtin(position) is always a fragment position, never a sample position.
+ `
+ )
+ .params(u =>
+ u //
+ .combine('nearFar', [[0, 1] as const, [0.25, 0.75] as const] as const)
+ .combine('sampleCount', [1, 4] as const)
+ .combine('interpolation', [
+ { type: 'perspective', sampling: 'center' },
+ { type: 'perspective', sampling: 'centroid' },
+ { type: 'perspective', sampling: 'sample' },
+ { type: 'linear', sampling: 'center' },
+ { type: 'linear', sampling: 'centroid' },
+ { type: 'linear', sampling: 'sample' },
+ { type: 'flat' },
+ ] as const)
+ )
+ .beforeAllSubcases(t => {
+ const {
+ interpolation: { type, sampling },
+ } = t.params;
+ t.skipIfInterpolationTypeOrSamplingNotSupported({ type, sampling });
+ })
+ .fn(async t => {
+ const {
+ nearFar,
+ sampleCount,
+ interpolation: { type, sampling },
+ } = t.params;
+ // prettier-ignore
+ const clipSpacePoints = [ // ndc values
+ [0.333, 0.333, 0.333, 0.333], // 1, 1, 1
+ [ 1.0, -3.0, 0.25, 1.0 ], // 1, -3, 0.25
+ [-1.5, 0.5, 0.25, 0.5 ], // -3, 1, 0.5
+ ];
+
+ const interStagePoints = [
+ [1, 2, 3, 4],
+ [5, 6, 7, 8],
+ [9, 10, 11, 12],
+ ];
+
+ const width = 4;
+ const height = 4;
+ const actual = await renderFragmentShaderInputsTo4TexturesAndReadbackValues(t, {
+ interpolationType: type,
+ interpolationSampling: sampling,
+ sampleCount,
+ width,
+ height,
+ nearFar,
+ clipSpacePoints,
+ interStagePoints,
+ fragInCode: '',
+ outputCode: 'fin.position',
+ });
+
+ const expected = generateFragmentInputs({
+ width,
+ height,
+ nearFar,
+ sampleCount,
+ clipSpacePoints,
+ interpolateFn: computeFragmentPosition,
+ });
+
+ // Since @builtin(position) is always a fragment position, never a sample position, check
+ // the first coordinate. It should be 0.5, 0.5 always. This is just to double check
+ // that computeFragmentPosition is generating the correct values.
+ assert(expected[0] === 0.5);
+ assert(expected[1] === 0.5);
+
+ t.expectOK(
+ checkSampleRectsApproximatelyEqual({
+ width,
+ height,
+ sampleCount,
+ actual,
+ expected,
+ maxDiffULPsForFloatFormat: 4,
+ })
+ );
+ });
+
+g.test('inputs,interStage')
+ .desc(
+ `
+ Test fragment shader inter-stage variable values except for centroid interpolation.
+ `
+ )
+ .params(u =>
+ u //
+ .combine('nearFar', [[0, 1] as const, [0.25, 0.75] as const] as const)
+ .combine('sampleCount', [1, 4] as const)
+ .combine('interpolation', [
+ { type: 'perspective', sampling: 'center' },
+ { type: 'perspective', sampling: 'sample' },
+ { type: 'linear', sampling: 'center' },
+ { type: 'linear', sampling: 'sample' },
+ { type: 'flat' },
+ ] as const)
+ )
+ .beforeAllSubcases(t => {
+ const {
+ interpolation: { type, sampling },
+ } = t.params;
+ t.skipIfInterpolationTypeOrSamplingNotSupported({ type, sampling });
+ })
+ .fn(async t => {
+ const {
+ nearFar,
+ sampleCount,
+ interpolation: { type, sampling },
+ } = t.params;
+ // prettier-ignore
+ const clipSpacePoints = [ // ndc values
+ [0.333, 0.333, 0.333, 0.333], // 1, 1, 1
+ [ 1.0, -3.0, 0.25, 1.0 ], // 1, -3, 0.25
+ [-1.5, 0.5, 0.25, 0.5 ], // -3, 1, 0.5
+ ];
+
+ const interStagePoints = [
+ [1, 2, 3, 4],
+ [5, 6, 7, 8],
+ [9, 10, 11, 12],
+ ];
+
+ const width = 4;
+ const height = 4;
+ const actual = await renderFragmentShaderInputsTo4TexturesAndReadbackValues(t, {
+ interpolationType: type,
+ interpolationSampling: sampling,
+ sampleCount,
+ width,
+ height,
+ nearFar,
+ clipSpacePoints,
+ interStagePoints,
+ fragInCode: '',
+ outputCode: 'fin.interpolatedValue',
+ });
+
+ const expected = generateFragmentInputs({
+ width,
+ height,
+ nearFar,
+ sampleCount,
+ clipSpacePoints,
+ interpolateFn: createInterStageInterpolationFn(interStagePoints, type, sampling),
+ });
+
+ t.expectOK(
+ checkSampleRectsApproximatelyEqual({
+ width,
+ height,
+ sampleCount,
+ actual,
+ expected,
+ maxDiffULPsForFloatFormat: 4,
+ })
+ );
+ });
+
+g.test('inputs,interStage,centroid')
+ .desc(
+ `
+ Test fragment shader inter-stage variable values in centroid sampling mode.
+
+ Centroid sampling mode is trying to solve the following issue
+
+ +-------------+
+ |....s1|/ |
+ |......| |
+ |...../| s2 |
+ +------C------+
+ |s3./ | |
+ |../ | |
+ |./ |s4 |
+ +-------------+
+
+ Above is a diagram of a texel where s1, s2, s3, s4 are sample points,
+ C is the center of the texel and the diagonal line is some edge of
+ a triangle. s1 and s3 are inside the triangle. In sampling = 'center'
+ modes, the interpolated value will be relative to C. The problem is,
+ C is outside of the triangle. In sample = 'centroid' mode, the
+ interpolated value will be computed relative to some point inside the
+ portion of the triangle inside the texel. While ideally it would be
+ the actual centroid, the specs from the various APIs suggest the only
+ guarantee is it's inside the triangle.
+
+ So, we set the interStage values to barycentric coords. We expect
+ that when sampling mode is 'center', some interpolated values
+ will be outside of the triangle (ie, one or more of their values will
+ be outside the 0 to 1 range). In sampling mode = 'centroid' mode, none
+ of the values will be outside of the 0 to 1 range.
+
+ Note: generateFragmentInputs below generates "expected". Values not
+ rendered to will be 0. Values rendered to outside the triangle will
+ be -1. Values rendered to inside the triangle will be 1. Manually
+ checking, "expected" for sampling = 'center' should have a couple of
+ -1 values where as "expected" for sampling = 'centroid' should not.
+ This was verified with manual testing.
+
+ Since we only care about inside vs outside of the triangle, having
+ createInterStageInterpolationFn use the interpolated value relative
+ to the sample point when sampling = 'centroid' will give us a value
+ inside the triangle, which is good enough for our test.
+ `
+ )
+ .params(u =>
+ u //
+ .combine('nearFar', [[0, 1] as const, [0.25, 0.75] as const] as const)
+ .combine('sampleCount', [1, 4] as const)
+ .combine('interpolation', [
+ { type: 'perspective', sampling: 'center' },
+ { type: 'perspective', sampling: 'centroid' },
+ { type: 'linear', sampling: 'center' },
+ { type: 'linear', sampling: 'centroid' },
+ ] as const)
+ )
+ .beforeAllSubcases(t => {
+ const {
+ interpolation: { type, sampling },
+ } = t.params;
+ t.skipIfInterpolationTypeOrSamplingNotSupported({ type, sampling });
+ })
+ .fn(async t => {
+ const {
+ nearFar,
+ sampleCount,
+ interpolation: { type, sampling },
+ } = t.params;
+ //
+ // We're drawing 1 triangle that cut the viewport
+ //
+ // -1 0 1
+ // +===+===+ 2
+ // |\..|...|
+ // +---+---+ 1 <---
+ // | \|...| |
+ // +---+---+ 0 | viewport
+ // | |\..| |
+ // +---+---+ -1 <---
+ // | | \|
+ // +===+===+ -2
+
+ // prettier-ignore
+ const clipSpacePoints = [ // ndc values
+ [ 1, -2, 0, 1],
+ [-1, 2, 0, 1],
+ [ 1, 2, 0, 1],
+ ];
+
+ // prettier-ignore
+ const interStagePoints = [
+ [ 1, 0, 0, 0],
+ [ 0, 1, 0, 0],
+ [ 0, 0, 1, 0],
+ ];
+
+ const width = 4;
+ const height = 4;
+ const actual = await renderFragmentShaderInputsTo4TexturesAndReadbackValues(t, {
+ interpolationType: type,
+ interpolationSampling: sampling,
+ sampleCount,
+ width,
+ height,
+ nearFar,
+ clipSpacePoints,
+ interStagePoints,
+ fragInCode: '',
+ outputCode:
+ 'vec4f(select(-1.0, 1.0, all(fin.interpolatedValue >= vec4f(0)) && all(fin.interpolatedValue <= vec4f(1))), 0, 0, 0)',
+ });
+
+ const expected = generateFragmentInputs({
+ width,
+ height,
+ nearFar,
+ sampleCount,
+ clipSpacePoints,
+ interpolateFn: createInterStageInterpolationBetween0And1TestFn(
+ interStagePoints,
+ type,
+ sampling
+ ),
+ });
+
+ t.expectOK(
+ checkSampleRectsApproximatelyEqual({
+ width,
+ height,
+ sampleCount,
+ actual,
+ expected,
+ maxDiffULPsForFloatFormat: 3,
+ })
+ );
+ });
+
+g.test('inputs,sample_index')
+ .desc(
+ `
+ Test fragment shader builtin(sample_index) values.
+ `
+ )
+ .params(u =>
+ u //
+ .combine('nearFar', [[0, 1] as const, [0.25, 0.75] as const] as const)
+ .combine('sampleCount', [1, 4] as const)
+ .combine('interpolation', [
+ { type: 'perspective', sampling: 'center' },
+ { type: 'perspective', sampling: 'centroid' },
+ { type: 'perspective', sampling: 'sample' },
+ { type: 'linear', sampling: 'center' },
+ { type: 'linear', sampling: 'centroid' },
+ { type: 'linear', sampling: 'sample' },
+ { type: 'flat' },
+ ] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.skipIf(t.isCompatibility, 'sample_index is not supported in compatibility mode');
+ })
+ .fn(async t => {
+ const {
+ nearFar,
+ sampleCount,
+ interpolation: { type, sampling },
+ } = t.params;
+ // prettier-ignore
+ const clipSpacePoints = [ // ndc values
+ [0.333, 0.333, 0.333, 0.333], // 1, 1, 1
+ [ 1.0, -3.0, 0.25, 1.0 ], // 1, -3, 0.25
+ [-1.5, 0.5, 0.25, 0.5 ], // -3, 1, 0.5
+ ];
+
+ const interStagePoints = [
+ [1, 2, 3, 4],
+ [5, 6, 7, 8],
+ [9, 10, 11, 12],
+ ];
+
+ const width = 4;
+ const height = 4;
+ const actual = await renderFragmentShaderInputsTo4TexturesAndReadbackValues(t, {
+ interpolationType: type,
+ interpolationSampling: sampling,
+ sampleCount,
+ width,
+ height,
+ nearFar,
+ clipSpacePoints,
+ interStagePoints,
+ fragInCode: `@builtin(sample_index) sampleIndex: u32,`,
+ outputCode: 'vec4f(f32(fin.sampleIndex), 0, 0, 0)',
+ });
+
+ const expected = generateFragmentInputs({
+ width,
+ height,
+ nearFar,
+ sampleCount,
+ clipSpacePoints,
+ interpolateFn: computeFragmentSampleIndex,
+ });
+
+ t.expectOK(
+ checkSampleRectsApproximatelyEqual({
+ width,
+ height,
+ sampleCount,
+ actual,
+ expected,
+ maxDiffULPsForFloatFormat: 1,
+ })
+ );
+ });
+
+g.test('inputs,front_facing')
+ .desc(
+ `
+ Test fragment shader builtin(front_facing) values.
+
+ Draws a quad from 2 triangles that entirely cover clip space. (see diagram below in code)
+ One triangle is clockwise, the other is counter clockwise. The triangles
+ bisect pixels so that different samples are covered by each triangle so that some
+ samples should get different values for front_facing for the same fragment.
+ `
+ )
+ .params(u =>
+ u //
+ .combine('nearFar', [[0, 1] as const, [0.25, 0.75] as const] as const)
+ .combine('sampleCount', [1, 4] as const)
+ .combine('frontFace', ['cw', 'ccw'] as const)
+ .combine('interpolation', [
+ { type: 'perspective', sampling: 'center' },
+ { type: 'perspective', sampling: 'centroid' },
+ { type: 'perspective', sampling: 'sample' },
+ { type: 'linear', sampling: 'center' },
+ { type: 'linear', sampling: 'centroid' },
+ { type: 'linear', sampling: 'sample' },
+ { type: 'flat' },
+ ] as const)
+ )
+ .beforeAllSubcases(t => {
+ const {
+ interpolation: { type, sampling },
+ } = t.params;
+ t.skipIfInterpolationTypeOrSamplingNotSupported({ type, sampling });
+ })
+ .fn(async t => {
+ const {
+ nearFar,
+ sampleCount,
+ frontFace,
+ interpolation: { type, sampling },
+ } = t.params;
+ //
+ // We're drawing 2 triangles starting at y = -2 to y = +2
+ //
+ // -1 0 1
+ // +===+===+ 2
+ // |\ | |
+ // +---+---+ 1 <---
+ // | \| | |
+ // +---+---+ 0 | viewport
+ // | |\ | |
+ // +---+---+ -1 <---
+ // | | \|
+ // +===+===+ -2
+
+ // prettier-ignore
+ const clipSpacePoints = [
+ // ccw
+ [-1, -2, 0, 1],
+ [ 1, -2, 0, 1],
+ [-1, 2, 0, 1],
+
+ // cw
+ [ 1, -2, 0, 1],
+ [-1, 2, 0, 1],
+ [ 1, 2, 0, 1],
+ ];
+
+ const interStagePoints = [
+ [1, 2, 3, 4],
+ [5, 6, 7, 8],
+ [9, 10, 11, 12],
+
+ [13, 14, 15, 16],
+ [17, 18, 19, 20],
+ [21, 22, 23, 24],
+ ];
+
+ const width = 4;
+ const height = 4;
+ const actual = await renderFragmentShaderInputsTo4TexturesAndReadbackValues(t, {
+ interpolationType: type,
+ interpolationSampling: sampling,
+ frontFace,
+ sampleCount,
+ width,
+ height,
+ nearFar,
+ clipSpacePoints,
+ interStagePoints,
+ fragInCode: '@builtin(front_facing) frontFacing: bool,',
+ outputCode: 'vec4f(select(0.0, 1.0, fin.frontFacing), 0, 0, 0)',
+ });
+
+ const expected = generateFragmentInputs({
+ width,
+ height,
+ nearFar,
+ sampleCount,
+ clipSpacePoints,
+ frontFace,
+ interpolateFn: computeFragmentFrontFacing,
+ });
+
+ assert(expected.indexOf(0) >= 0, 'expect some values to be 0');
+ assert(expected.findIndex(v => v !== 0) >= 0, 'expect some values to be non 0');
+
+ t.expectOK(
+ checkSampleRectsApproximatelyEqual({
+ width,
+ height,
+ sampleCount,
+ actual,
+ expected,
+ maxDiffULPsForFloatFormat: 0,
+ })
+ );
+ });
+
+g.test('inputs,sample_mask')
+ .desc(
+ `
+ Test fragment shader builtin(sample_mask) values.
+
+ Draws various triangles that should trigger different sample_mask values.
+ Checks that sample_mask matches what's expected. Note: the triangles
+ are selected so they do not intersect sample points as we don't want
+ to test precision issues on whether or not a sample point is inside
+ or outside the triangle when right on the edge.
+
+ Example: x=-1, y=2, it draws the following triangle
+
+ [ -0.8, -2 ]
+ [ 1.2, 2 ]
+ [ -0.8, 2 ]
+
+ On to a 4x4 pixel texture
+
+ -0.8, 2
+ .----------------------. 1.2 2
+ |...................../
+ |..................../
+ |.................../
+ |................../
+ |................./
+ +-|---+-----+-----+/----+ ---
+ | |...|.....|...../ | ^
+ | |...|.....|..../| | |
+ +-|---+-----+---/-+-----+ |
+ | |...|.....|../ | | |
+ | |...|.....|./ | | |
+ +-|---+-----+/----+-----+ texture / clip space
+ | |...|...../ | | |
+ | |...|..../| | | |
+ +-|---+---/-+-----+-----+ |
+ | |...|../ | | | |
+ | |...|./ | | | V
+ +-|---+/----+-----+-----+ ---
+ |.../
+ |../
+ |./
+ |/
+ /
+ .
+ -0.8, -2
+
+ Inside an individual pixel you might see this situation
+
+ +-------------+
+ |....s1|/ |
+ |......| |
+ |...../| s2 |
+ +------C------+
+ |s3./ | |
+ |../ | |
+ |./ |s4 |
+ +-------------+
+
+ where s1, s2, s3, s4, are sample points and C is the center. For a sampleCount = 4 texture
+ the sample_mask is expected to emit sample_mask = 0b0101
+
+ ref: https://learn.microsoft.com/en-us/windows/win32/api/d3d11/ne-d3d11-d3d11_standard_multisample_quality_levels
+ `
+ )
+ .params(u =>
+ u //
+ .combine('nearFar', [[0, 1] as const, [0.25, 0.75] as const] as const)
+ .combine('sampleCount', [1, 4] as const)
+ .combine('interpolation', [
+ // given that 'sample' effects whether things are run per-sample or per-fragment
+ // we test all of these to make sure they don't affect the result differently than expected.
+ { type: 'perspective', sampling: 'center' },
+ { type: 'perspective', sampling: 'centroid' },
+ { type: 'perspective', sampling: 'sample' },
+ { type: 'linear', sampling: 'center' },
+ { type: 'linear', sampling: 'centroid' },
+ { type: 'linear', sampling: 'sample' },
+ { type: 'flat' },
+ ] as const)
+ .beginSubcases()
+ .combineWithParams([
+ { x: -1, y: -1 },
+ { x: -1, y: -2 },
+ { x: -1, y: 1 },
+ { x: -1, y: 3 },
+ { x: -2, y: -1 },
+ { x: -2, y: 3 },
+ { x: -3, y: -1 },
+ { x: -3, y: -2 },
+ { x: -3, y: 1 },
+ { x: 1, y: -1 },
+ { x: 1, y: -3 },
+ { x: 1, y: 1 },
+ { x: 1, y: 2 },
+ { x: 2, y: -2 },
+ { x: 2, y: -3 },
+ { x: 2, y: 1 },
+ { x: 2, y: 2 },
+ { x: 3, y: -1 },
+ { x: 3, y: -3 },
+ { x: 3, y: 1 },
+ { x: 3, y: 2 },
+ { x: 3, y: 3 },
+ ])
+ )
+ .beforeAllSubcases(t => {
+ const {
+ interpolation: { type, sampling },
+ } = t.params;
+ t.skipIfInterpolationTypeOrSamplingNotSupported({ type, sampling });
+ })
+ .fn(async t => {
+ const {
+ x,
+ y,
+ nearFar,
+ sampleCount,
+ interpolation: { type, sampling },
+ } = t.params;
+ // prettier-ignore
+ const clipSpacePoints = [
+ [ x + 0.2, -y, 0, 1],
+ [-x + 0.2, y, 0, 1],
+ [ x + 0.2, y, 0, 1],
+ ];
+
+ const interStagePoints = [
+ [13, 14, 15, 16],
+ [17, 18, 19, 20],
+ [21, 22, 23, 24],
+ ];
+
+ const width = 4;
+ const height = 4;
+ const actual = await renderFragmentShaderInputsTo4TexturesAndReadbackValues(t, {
+ interpolationType: type,
+ interpolationSampling: sampling,
+ sampleCount,
+ width,
+ height,
+ nearFar,
+ clipSpacePoints,
+ interStagePoints,
+ fragInCode: '@builtin(sample_mask) sample_mask: u32,',
+ outputCode: 'vec4f(f32(fin.sample_mask), 0, 0, 0)',
+ });
+
+ const expected = generateFragmentInputs({
+ width,
+ height,
+ nearFar,
+ sampleCount,
+ clipSpacePoints,
+ interpolateFn: computeSampleMask,
+ });
+
+ t.expectOK(
+ checkSampleRectsApproximatelyEqual({
+ width,
+ height,
+ sampleCount,
+ actual,
+ expected,
+ maxDiffULPsForFloatFormat: 0,
+ })
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/shader_io/user_io.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/shader_io/user_io.spec.ts
new file mode 100644
index 0000000000..0c26f89872
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/shader_io/user_io.spec.ts
@@ -0,0 +1,213 @@
+export const description = `
+Test for user-defined shader I/O.
+
+passthrough:
+ * Data passed into vertex shader as uints and converted to test type
+ * Passed from vertex to fragment as test type
+ * Output from fragment shader as uint
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { range } from '../../../../common/util/util.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+function generateInterstagePassthroughCode(type: string): string {
+ return `
+${type === 'f16' ? 'enable f16;' : ''}
+struct IOData {
+ @builtin(position) pos : vec4f,
+ @location(0) @interpolate(flat) user0 : ${type},
+ @location(1) @interpolate(flat) user1 : vec2<${type}>,
+ @location(2) @interpolate(flat) user2 : vec4<${type}>,
+}
+
+struct VertexInput {
+ @builtin(vertex_index) idx : u32,
+ @location(0) in0 : u32,
+ @location(1) in1 : vec2u,
+ @location(2) in2 : vec4u,
+}
+
+@vertex
+fn vsMain(input : VertexInput) -> IOData {
+ const vertices = array(
+ vec4f(-1, -1, 0, 1),
+ vec4f(-1, 1, 0, 1),
+ vec4f( 1, -1, 0, 1),
+ );
+ var data : IOData;
+ data.pos = vertices[input.idx];
+ data.user0 = ${type}(input.in0);
+ data.user1 = vec2<${type}>(input.in1);
+ data.user2 = vec4<${type}>(input.in2);
+ return data;
+}
+
+struct FragOutput {
+ @location(0) out0 : u32,
+ @location(1) out1 : vec2u,
+ @location(2) out2 : vec4u,
+}
+
+@fragment
+fn fsMain(input : IOData) -> FragOutput {
+ var out : FragOutput;
+ out.out0 = u32(input.user0);
+ out.out1 = vec2u(input.user1);
+ out.out2 = vec4u(input.user2);
+ return out;
+}
+`;
+}
+
+function drawPassthrough(t: GPUTest, code: string) {
+ // Default limit is 32 bytes of color attachments.
+ // These attachments use 28 bytes (which is why vec3 is skipped).
+ const formats: GPUTextureFormat[] = ['r32uint', 'rg32uint', 'rgba32uint'];
+ const components = [1, 2, 4];
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({ code }),
+ entryPoint: 'vsMain',
+ buffers: [
+ {
+ arrayStride: 4,
+ attributes: [
+ {
+ format: 'uint32',
+ offset: 0,
+ shaderLocation: 0,
+ },
+ ],
+ },
+ {
+ arrayStride: 8,
+ attributes: [
+ {
+ format: 'uint32x2',
+ offset: 0,
+ shaderLocation: 1,
+ },
+ ],
+ },
+ {
+ arrayStride: 16,
+ attributes: [
+ {
+ format: 'uint32x4',
+ offset: 0,
+ shaderLocation: 2,
+ },
+ ],
+ },
+ ],
+ },
+ fragment: {
+ module: t.device.createShaderModule({ code }),
+ entryPoint: 'fsMain',
+ targets: formats.map(x => {
+ return { format: x };
+ }),
+ },
+ primitive: {
+ topology: 'triangle-list',
+ },
+ });
+
+ const vertexBuffer = t.makeBufferWithContents(
+ new Uint32Array([
+ // scalar: offset 0
+ 1, 1, 1, 0,
+ // vec2: offset 16
+ 2, 2, 2, 2, 2, 2, 0, 0,
+ // vec4: offset 48
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ ]),
+ GPUBufferUsage.COPY_SRC | GPUBufferUsage.VERTEX
+ );
+
+ const bytesPerComponent = 4;
+ // 256 is the minimum bytes per row for texture to buffer copies.
+ const width = 256 / bytesPerComponent;
+ const height = 2;
+ const copyWidth = 4;
+ const outputTextures = range(3, i => {
+ const texture = t.device.createTexture({
+ size: [width, height],
+ usage:
+ GPUTextureUsage.COPY_SRC |
+ GPUTextureUsage.RENDER_ATTACHMENT |
+ GPUTextureUsage.TEXTURE_BINDING,
+ format: formats[i],
+ });
+ t.trackForCleanup(texture);
+ return texture;
+ });
+
+ let bufferSize = 1;
+ for (const comp of components) {
+ bufferSize *= comp;
+ }
+ bufferSize *= outputTextures.length * bytesPerComponent * copyWidth;
+ const outputBuffer = t.device.createBuffer({
+ size: bufferSize,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
+ });
+ t.trackForCleanup(outputBuffer);
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: outputTextures.map(t => ({
+ view: t.createView(),
+ loadOp: 'clear',
+ storeOp: 'store',
+ })),
+ });
+ pass.setPipeline(pipeline);
+ pass.setVertexBuffer(0, vertexBuffer, 0, 12);
+ pass.setVertexBuffer(1, vertexBuffer, 16, 24);
+ pass.setVertexBuffer(2, vertexBuffer, 48, 48);
+ pass.draw(3);
+ pass.end();
+
+ // Copy 'copyWidth' samples from each attachment into a buffer to check the results.
+ let offset = 0;
+ let expectArray: number[] = [];
+ for (let i = 0; i < outputTextures.length; i++) {
+ encoder.copyTextureToBuffer(
+ { texture: outputTextures[i] },
+ {
+ buffer: outputBuffer,
+ offset,
+ bytesPerRow: bytesPerComponent * components[i] * width,
+ rowsPerImage: height,
+ },
+ { width: copyWidth, height: 1 }
+ );
+ offset += components[i] * bytesPerComponent * copyWidth;
+ for (let j = 0; j < components[i]; j++) {
+ const value = i + 1;
+ expectArray = expectArray.concat([value, value, value, value]);
+ }
+ }
+ t.queue.submit([encoder.finish()]);
+
+ const expect = new Uint32Array(expectArray);
+ t.expectGPUBufferValuesEqual(outputBuffer, expect);
+}
+
+g.test('passthrough')
+ .desc('Tests passing user-defined data from vertex input through fragment output')
+ .params(u => u.combine('type', ['f32', 'f16', 'i32', 'u32'] as const))
+ .beforeAllSubcases(t => {
+ if (t.params.type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const code = generateInterstagePassthroughCode(t.params.type);
+ drawPassthrough(t, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/shader_io/workgroup_size.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/shader_io/workgroup_size.spec.ts
new file mode 100644
index 0000000000..278265b7ad
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/shader_io/workgroup_size.spec.ts
@@ -0,0 +1,150 @@
+export const description = `Test that workgroup size is set correctly`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { iterRange } from '../../../../common/util/util.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+function checkResults(
+ sizeX: number,
+ sizeY: number,
+ sizeZ: number,
+ numWGs: number,
+ data: Uint32Array
+): Error | undefined {
+ const totalInvocations = sizeX * sizeY * sizeZ;
+ for (let i = 0; i < numWGs; i++) {
+ const wgx_data = data[4 * i + 0];
+ const wgy_data = data[4 * i + 1];
+ const wgz_data = data[4 * i + 2];
+ const total_data = data[4 * i + 3];
+ if (wgx_data !== sizeX) {
+ let msg = `Incorrect workgroup size x dimension for wg ${i}:\n`;
+ msg += `- expected: ${wgx_data}\n`;
+ msg += `- got: ${sizeX}`;
+ return Error(msg);
+ }
+ if (wgy_data !== sizeY) {
+ let msg = `Incorrect workgroup size y dimension for wg ${i}:\n`;
+ msg += `- expected: ${wgy_data}\n`;
+ msg += `- got: ${sizeY}`;
+ return Error(msg);
+ }
+ if (wgz_data !== sizeZ) {
+ let msg = `Incorrect workgroup size y dimension for wg ${i}:\n`;
+ msg += `- expected: ${wgz_data}\n`;
+ msg += `- got: ${sizeZ}`;
+ return Error(msg);
+ }
+ if (total_data !== totalInvocations) {
+ let msg = `Incorrect workgroup total invocations for wg ${i}:\n`;
+ msg += `- expected: ${total_data}\n`;
+ msg += `- got: ${totalInvocations}`;
+ return Error(msg);
+ }
+ }
+ return undefined;
+}
+
+g.test('workgroup_size')
+ .desc(`Test workgroup size is set correctly`)
+ .params(u =>
+ u
+ .combine('wgx', [1, 3, 4, 8, 11, 16, 51, 64, 128, 256] as const)
+ .combine('wgy', [1, 3, 4, 8, 16, 51, 64, 256] as const)
+ .combine('wgz', [1, 3, 11, 16, 128, 256] as const)
+ .beginSubcases()
+ )
+ .fn(async t => {
+ const {
+ maxComputeWorkgroupSizeX,
+ maxComputeWorkgroupSizeY,
+ maxComputeWorkgroupSizeZ,
+ maxComputeInvocationsPerWorkgroup,
+ } = t.device.limits;
+ t.skipIf(
+ t.params.wgx > maxComputeWorkgroupSizeX,
+ `workgroup size x: ${t.params.wgx} > limit: ${maxComputeWorkgroupSizeX}`
+ );
+ t.skipIf(
+ t.params.wgy > maxComputeWorkgroupSizeY,
+ `workgroup size x: ${t.params.wgy} > limit: ${maxComputeWorkgroupSizeY}`
+ );
+ t.skipIf(
+ t.params.wgz > maxComputeWorkgroupSizeZ,
+ `workgroup size x: ${t.params.wgz} > limit: ${maxComputeWorkgroupSizeZ}`
+ );
+ const totalInvocations = t.params.wgx * t.params.wgy * t.params.wgz;
+ t.skipIf(
+ totalInvocations > maxComputeInvocationsPerWorkgroup,
+ `workgroup size: ${totalInvocations} > limit: ${maxComputeInvocationsPerWorkgroup}`
+ );
+
+ const code = `
+struct Values {
+ x : atomic<u32>,
+ y : atomic<u32>,
+ z : atomic<u32>,
+ total : atomic<u32>,
+}
+
+@group(0) @binding(0)
+var<storage, read_write> v : array<Values>;
+
+@compute @workgroup_size(${t.params.wgx}, ${t.params.wgy}, ${t.params.wgz})
+fn main(@builtin(local_invocation_id) lid : vec3u,
+ @builtin(workgroup_id) wgid : vec3u) {
+ atomicMax(&v[wgid.x].x, lid.x + 1);
+ atomicMax(&v[wgid.x].y, lid.y + 1);
+ atomicMax(&v[wgid.x].z, lid.z + 1);
+ atomicAdd(&v[wgid.x].total, 1);
+}`;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code,
+ }),
+ entryPoint: 'main',
+ },
+ });
+
+ const numWorkgroups = totalInvocations < 256 ? 5 : 3;
+ const buffer = t.makeBufferWithContents(
+ new Uint32Array([...iterRange(numWorkgroups * 4, _i => 0)]),
+ GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ );
+ t.trackForCleanup(buffer);
+
+ const bg = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: {
+ buffer,
+ },
+ },
+ ],
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bg);
+ pass.dispatchWorkgroups(numWorkgroups, 1, 1);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ const bufferReadback = await t.readGPUBufferRangeTyped(buffer, {
+ srcByteOffset: 0,
+ type: Uint32Array,
+ typedLength: 4 * numWorkgroups,
+ method: 'copy',
+ });
+ const data: Uint32Array = bufferReadback.data;
+
+ t.expectOK(checkResults(t.params.wgx, t.params.wgy, t.params.wgz, numWorkgroups, data));
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/stage.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/stage.spec.ts
new file mode 100644
index 0000000000..6e06e67e37
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/stage.spec.ts
@@ -0,0 +1,133 @@
+export const description = `Test trivial shaders for each shader stage kind`;
+
+// There are many many more shaders executed in other tests.
+
+import { makeTestGroup } from '../../../common/framework/test_group.js';
+import { GPUTest } from '../../gpu_test.js';
+import { checkElementsEqual } from '../../util/check_contents.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('basic_compute')
+ .desc(`Test a trivial compute shader`)
+ .fn(async t => {
+ const code = `
+
+@group(0) @binding(0)
+var<storage, read_write> v : vec4u;
+
+@compute @workgroup_size(1)
+fn main() {
+ v = vec4u(1,2,3,42);
+}`;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code,
+ }),
+ entryPoint: 'main',
+ },
+ });
+
+ const buffer = t.makeBufferWithContents(
+ new Uint32Array([0, 0, 0, 0]),
+ GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ );
+ t.trackForCleanup(buffer);
+
+ const bg = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: {
+ buffer,
+ },
+ },
+ ],
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bg);
+ pass.dispatchWorkgroups(1, 1, 1);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ const bufferReadback = await t.readGPUBufferRangeTyped(buffer, {
+ srcByteOffset: 0,
+ type: Uint32Array,
+ typedLength: 4,
+ method: 'copy',
+ });
+ const got: Uint32Array = bufferReadback.data;
+ const expected = new Uint32Array([1, 2, 3, 42]);
+
+ t.expectOK(checkElementsEqual(got, expected));
+ });
+
+g.test('basic_render')
+ .desc(`Test trivial vertex and fragment shaders`)
+ .fn(t => {
+ const code = `
+@vertex
+fn vert_main(@builtin(vertex_index) idx: u32) -> @builtin(position) vec4f {
+ // A right triangle covering the whole framebuffer.
+ const pos = array(
+ vec2f(-1,-3),
+ vec2f(3,1),
+ vec2f(-1,1));
+ return vec4f(pos[idx], 0, 1);
+}
+
+@fragment
+fn frag_main() -> @location(0) vec4f {
+ return vec4(0, 1, 0, 1); // green
+}
+`;
+ const module = t.device.createShaderModule({ code });
+
+ const [width, height] = [8, 8] as const;
+ const format = 'rgba8unorm' as const;
+ const texture = t.device.createTexture({
+ size: { width, height },
+ usage:
+ GPUTextureUsage.RENDER_ATTACHMENT |
+ GPUTextureUsage.TEXTURE_BINDING |
+ GPUTextureUsage.COPY_SRC,
+ format,
+ });
+
+ // We'll copy one pixel only.
+ const dst = t.device.createBuffer({
+ size: 4,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
+ });
+
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: { module, entryPoint: 'vert_main' },
+ fragment: { module, entryPoint: 'frag_main', targets: [{ format }] },
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [{ view: texture.createView(), loadOp: 'clear', storeOp: 'store' }],
+ });
+ pass.setPipeline(pipeline);
+ pass.draw(3);
+ pass.end();
+
+ encoder.copyTextureToBuffer(
+ { texture, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
+ { buffer: dst, bytesPerRow: 256 },
+ { width: 1, height: 1, depthOrArrayLayers: 1 }
+ );
+ t.queue.submit([encoder.finish()]);
+
+ // Expect one green pixel.
+ t.expectGPUBufferValuesEqual(dst, new Uint8Array([0x00, 0xff, 0x00, 0xff]));
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/statement/compound.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/statement/compound.spec.ts
new file mode 100644
index 0000000000..aed0cc2245
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/statement/compound.spec.ts
@@ -0,0 +1,137 @@
+export const description = `
+Compound statement execution.
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { TypedArrayBufferView } from '../../../../common/util/util.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+/**
+ * Builds, runs then checks the output of a statement shader test.
+ *
+ * @param t The test object
+ * @param ty The WGSL scalar type to be written
+ * @param values The expected output values of type ty
+ * @param wgsl_main The body of the WGSL entry point.
+ */
+export function runStatementTest(
+ t: GPUTest,
+ ty: string,
+ values: TypedArrayBufferView,
+ wgsl_main: string
+) {
+ const wgsl = `
+struct Outputs {
+ data : array<${ty}>,
+};
+var<private> count: u32 = 0;
+
+@group(0) @binding(1) var<storage, read_write> outputs : Outputs;
+
+fn put(value : ${ty}) {
+ outputs.data[count] = value;
+ count += 1;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ _ = &outputs;
+ ${wgsl_main}
+}
+`;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main',
+ },
+ });
+
+ const maxOutputValues = 1000;
+ const outputBuffer = t.device.createBuffer({
+ size: 4 * (1 + maxOutputValues),
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ });
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [{ binding: 1, resource: { buffer: outputBuffer } }],
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(1);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ t.expectGPUBufferValuesEqual(outputBuffer, values);
+}
+
+// Consider a declaration X of identifier 'x' inside a compound statement.
+// Check the value of 'x' at various places relative to X:
+// a { b; X=c; d; { e; } } f;
+
+const kTests = {
+ uses: {
+ // Observe values without conflicting declarations.
+ src: `let x = 1;
+ put(x);
+ {
+ put(x);
+ let x = x+1; // The declaration in question
+ put(x);
+ {
+ put(x);
+ }
+ put(x);
+ }
+ put(x);`,
+ values: [1, 1, 2, 2, 2, 1],
+ },
+ shadowed: {
+ // Observe values when shadowed
+ src: `let x = 1;
+ put(x);
+ {
+ put(x);
+ let x = x+1; // The declaration in question
+ put(x);
+ {
+ let x = x+1; // A shadow
+ put(x);
+ }
+ put(x);
+ }
+ put(x);`,
+ values: [1, 1, 2, 3, 2, 1],
+ },
+ gone: {
+ // The declaration goes out of scope.
+ src: `{
+ let x = 2; // The declaration in question
+ put(x);
+ }
+ let x = 1;
+ put(x);`,
+ values: [2, 1],
+ },
+} as const;
+
+g.test('decl')
+ .desc('Tests the value of a declared value in a compound statment.')
+ .params(u => u.combine('case', keysOf(kTests)))
+ .fn(t => {
+ runStatementTest(
+ t,
+ 'i32',
+ new Int32Array(kTests[t.params.case].values),
+ kTests[t.params.case].src
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/statement/discard.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/statement/discard.spec.ts
new file mode 100644
index 0000000000..058ff50f17
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/statement/discard.spec.ts
@@ -0,0 +1,645 @@
+export const description = `
+Execution tests for discard.
+
+The discard statement converts invocations into helpers.
+This results in the following conditions:
+ * No outputs are written
+ * No resources are written
+ * Atomics are undefined
+
+Conditions that still occur:
+ * Derivative calculations are correct
+ * Reads
+ * Writes to non-external memory
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { iterRange } from '../../../../common/util/util.js';
+import { GPUTest } from '../../../gpu_test.js';
+import { checkElementsPassPredicate } from '../../../util/check_contents.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// Framebuffer dimensions
+const kWidth = 64;
+const kHeight = 64;
+
+const kSharedCode = `
+@group(0) @binding(0) var<storage, read_write> output: array<vec2f>;
+@group(0) @binding(1) var<storage, read_write> atomicIndex : atomic<u32>;
+@group(0) @binding(2) var<storage> uniformValues : array<u32, 5>;
+
+@vertex
+fn vsMain(@builtin(vertex_index) index : u32) -> @builtin(position) vec4f {
+ const vertices = array(
+ vec2(-1, -1), vec2(-1, 0), vec2( 0, -1),
+ vec2(-1, 0), vec2( 0, 0), vec2( 0, -1),
+
+ vec2( 0, -1), vec2( 0, 0), vec2( 1, -1),
+ vec2( 0, 0), vec2( 1, 0), vec2( 1, -1),
+
+ vec2(-1, 0), vec2(-1, 1), vec2( 0, 0),
+ vec2(-1, 1), vec2( 0, 1), vec2( 0, 0),
+
+ vec2( 0, 0), vec2( 0, 1), vec2( 1, 0),
+ vec2( 0, 1), vec2( 1, 1), vec2( 1, 0),
+ );
+ return vec4f(vec2f(vertices[index]), 0, 1);
+}
+`;
+
+function drawFullScreen(
+ t: GPUTest,
+ code: string,
+ dataChecker: (a: Float32Array) => Error | undefined,
+ framebufferChecker: (a: Uint32Array) => Error | undefined
+) {
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({ code }),
+ entryPoint: 'vsMain',
+ },
+ fragment: {
+ module: t.device.createShaderModule({ code }),
+ entryPoint: 'fsMain',
+ targets: [{ format: 'r32uint' }],
+ },
+ primitive: {
+ topology: 'triangle-list',
+ },
+ });
+
+ const bytesPerWord = 4;
+ const framebuffer = t.device.createTexture({
+ size: [kWidth, kHeight],
+ usage:
+ GPUTextureUsage.COPY_SRC |
+ GPUTextureUsage.COPY_DST |
+ GPUTextureUsage.RENDER_ATTACHMENT |
+ GPUTextureUsage.TEXTURE_BINDING,
+ format: 'r32uint',
+ });
+ t.trackForCleanup(framebuffer);
+
+ // Create a buffer to copy the framebuffer contents into.
+ // Initialize with a sentinel value and load this buffer to detect unintended writes.
+ const fbBuffer = t.makeBufferWithContents(
+ new Uint32Array([...iterRange(kWidth * kHeight, x => kWidth * kHeight)]),
+ GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ );
+
+ // Create a buffer to hold the storage shader resources.
+ // (0,0) = vec2u width * height
+ // (0,1) = u32
+ const dataSize = 2 * kWidth * kHeight * bytesPerWord;
+ const dataBufferSize = dataSize + bytesPerWord;
+ const dataBuffer = t.device.createBuffer({
+ size: dataBufferSize,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE,
+ });
+
+ const uniformSize = bytesPerWord * 5;
+ const uniformBuffer = t.makeBufferWithContents(
+ // Loop bound, [derivative constants].
+ new Uint32Array([4, 1, 4, 4, 7]),
+ GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE
+ );
+
+ // 'atomicIndex' packed at the end of the buffer.
+ const bg = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: {
+ buffer: dataBuffer,
+ offset: 0,
+ size: dataSize,
+ },
+ },
+ {
+ binding: 1,
+ resource: {
+ buffer: dataBuffer,
+ offset: dataSize,
+ size: bytesPerWord,
+ },
+ },
+ {
+ binding: 2,
+ resource: {
+ buffer: uniformBuffer,
+ offset: 0,
+ size: uniformSize,
+ },
+ },
+ ],
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ encoder.copyBufferToTexture(
+ {
+ buffer: fbBuffer,
+ offset: 0,
+ bytesPerRow: kWidth * bytesPerWord,
+ rowsPerImage: kHeight,
+ },
+ { texture: framebuffer },
+ { width: kWidth, height: kHeight }
+ );
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: framebuffer.createView(),
+ loadOp: 'load',
+ storeOp: 'store',
+ },
+ ],
+ });
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bg);
+ pass.draw(24);
+ pass.end();
+ encoder.copyTextureToBuffer(
+ { texture: framebuffer },
+ {
+ buffer: fbBuffer,
+ offset: 0,
+ bytesPerRow: kWidth * bytesPerWord,
+ rowsPerImage: kHeight,
+ },
+ { width: kWidth, height: kHeight }
+ );
+ t.queue.submit([encoder.finish()]);
+
+ t.expectGPUBufferValuesPassCheck(dataBuffer, dataChecker, {
+ type: Float32Array,
+ typedLength: dataSize / bytesPerWord,
+ });
+
+ t.expectGPUBufferValuesPassCheck(fbBuffer, framebufferChecker, {
+ type: Uint32Array,
+ typedLength: kWidth * kHeight,
+ });
+}
+
+g.test('all')
+ .desc('Test a shader that discards all fragments')
+ .fn(t => {
+ const code = `
+${kSharedCode}
+
+@fragment
+fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 {
+ _ = uniformValues[0];
+ discard;
+ let idx = atomicAdd(&atomicIndex, 1);
+ output[idx] = pos.xy;
+ return 1;
+}
+`;
+
+ // No storage writes occur.
+ const dataChecker = (a: Float32Array) => {
+ return checkElementsPassPredicate(
+ a,
+ (idx: number, value: number | bigint) => {
+ return value === 0;
+ },
+ {
+ predicatePrinter: [
+ {
+ leftHeader: 'data exp ==',
+ getValueForCell: (idx: number) => {
+ return 0;
+ },
+ },
+ ],
+ }
+ );
+ };
+
+ // No fragment outputs occur.
+ const fbChecker = (a: Uint32Array) => {
+ return checkElementsPassPredicate(
+ a,
+ (idx: number, value: number | bigint) => {
+ return value === kWidth * kHeight;
+ },
+ {
+ predicatePrinter: [
+ {
+ leftHeader: 'fb exp ==',
+ getValueForCell: (idx: number) => {
+ return 0;
+ },
+ },
+ ],
+ }
+ );
+ };
+
+ drawFullScreen(t, code, dataChecker, fbChecker);
+ });
+
+g.test('three_quarters')
+ .desc('Test a shader that discards all but the upper-left quadrant fragments')
+ .fn(t => {
+ const code = `
+${kSharedCode}
+
+@fragment
+fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 {
+ _ = uniformValues[0];
+ if (pos.x >= 0.5 * ${kWidth} || pos.y >= 0.5 * ${kHeight}) {
+ discard;
+ }
+ let idx = atomicAdd(&atomicIndex, 1);
+ output[idx] = pos.xy;
+ return idx;
+}
+`;
+
+ // Only the the upper left quadrant is kept.
+ const dataChecker = (a: Float32Array) => {
+ return checkElementsPassPredicate(
+ a,
+ (idx: number, value: number | bigint) => {
+ const is_x = idx % 2 === 0;
+ if (is_x) {
+ return value < 0.5 * kWidth;
+ } else {
+ return value < 0.5 * kHeight;
+ }
+ },
+ {
+ predicatePrinter: [
+ {
+ leftHeader: 'data exp ==',
+ getValueForCell: (idx: number): number | string => {
+ const is_x = idx % 2 === 0;
+ if (is_x) {
+ const x = Math.floor(idx / 2) % kWidth;
+ if (x >= kWidth / 2) {
+ return 0;
+ }
+ } else {
+ const y = Math.floor((idx - 1) / kWidth);
+ if (y >= kHeight / 2) {
+ return 0;
+ }
+ }
+ if (is_x) {
+ return `< ${0.5 * kWidth}`;
+ } else {
+ return `< ${0.5 * kHeight}`;
+ }
+ },
+ },
+ ],
+ }
+ );
+ };
+ const fbChecker = (a: Uint32Array) => {
+ return checkElementsPassPredicate(
+ a,
+ (idx: number, value: number | bigint) => {
+ const x = idx % kWidth;
+ const y = Math.floor(idx / kWidth);
+ if (x < kWidth / 2 && y < kHeight / 2) {
+ return value < (kWidth * kHeight) / 4;
+ } else {
+ return value === kWidth * kHeight;
+ }
+ return value < (kWidth * kHeight) / 4;
+ },
+ {
+ predicatePrinter: [
+ {
+ leftHeader: 'fb exp ==',
+ getValueForCell: (idx: number) => {
+ const x = idx % kWidth;
+ const y = Math.floor(idx / kWidth);
+ if (x < kWidth / 2 && y < kHeight / 2) {
+ return 'any';
+ } else {
+ return 0;
+ }
+ },
+ },
+ ],
+ }
+ );
+ };
+
+ drawFullScreen(t, code, dataChecker, fbChecker);
+ });
+
+g.test('function_call')
+ .desc('Test discards happening in a function call')
+ .fn(t => {
+ const code = `
+${kSharedCode}
+
+fn foo(pos : vec2f) {
+ let p = vec2i(pos);
+ if p.x <= ${kWidth} / 2 && p.y <= ${kHeight} / 2 {
+ discard;
+ }
+ if p.x >= ${kWidth} / 2 && p.y >= ${kHeight} / 2 {
+ discard;
+ }
+}
+
+@fragment
+fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 {
+ _ = uniformValues[0];
+ foo(pos.xy);
+ let idx = atomicAdd(&atomicIndex, 1);
+ output[idx] = pos.xy;
+ return idx;
+}
+`;
+
+ // Only the upper right and bottom left quadrants are kept.
+ const dataChecker = (a: Float32Array) => {
+ return checkElementsPassPredicate(
+ a,
+ (idx: number, value: number | bigint) => {
+ const is_x = idx % 2 === 0;
+ if (value === 0.0) {
+ return is_x ? a[idx + 1] === 0 : a[idx - 1] === 0;
+ }
+
+ let expect = is_x ? kWidth : kHeight;
+ expect = 0.5 * expect;
+ if (value < expect) {
+ return is_x ? a[idx + 1] > 0.5 * kWidth : a[idx - 1] > 0.5 * kHeight;
+ } else {
+ return is_x ? a[idx + 1] < 0.5 * kWidth : a[idx - 1] < 0.5 * kHeight;
+ }
+ },
+ {
+ predicatePrinter: [
+ {
+ leftHeader: 'data exp ==',
+ getValueForCell: (idx: number): number | string => {
+ if (idx < (kWidth * kHeight) / 2) {
+ return 'any';
+ } else {
+ return 0;
+ }
+ },
+ },
+ ],
+ }
+ );
+ };
+ const fbChecker = (a: Uint32Array) => {
+ return checkElementsPassPredicate(
+ a,
+ (idx: number, value: number | bigint) => {
+ const x = idx % kWidth;
+ const y = Math.floor(idx / kWidth);
+ if ((x >= kWidth / 2 && y >= kHeight / 2) || (x <= kWidth / 2 && y <= kHeight / 2)) {
+ return value === kWidth * kHeight;
+ } else {
+ return value < (kWidth * kHeight) / 2;
+ }
+ },
+ {
+ predicatePrinter: [
+ {
+ leftHeader: 'fb exp ==',
+ getValueForCell: (idx: number) => {
+ const x = idx % kWidth;
+ const y = Math.floor(idx / kWidth);
+ if (
+ (x <= kWidth / 2 && y <= kHeight / 2) ||
+ (x >= kWidth / 2 && y >= kHeight / 2)
+ ) {
+ return kWidth * kHeight;
+ }
+ return 'any';
+ },
+ },
+ ],
+ }
+ );
+ };
+
+ drawFullScreen(t, code, dataChecker, fbChecker);
+ });
+
+g.test('loop')
+ .desc('Test discards in a loop')
+ .fn(t => {
+ const code = `
+${kSharedCode}
+
+@fragment
+fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 {
+ _ = uniformValues[0];
+ for (var i = 0; i < 2; i++) {
+ if i > 0 {
+ discard;
+ }
+ }
+ let idx = atomicAdd(&atomicIndex, 1);
+ output[idx] = pos.xy;
+ return 1;
+}
+`;
+
+ // No storage writes occur.
+ const dataChecker = (a: Float32Array) => {
+ return checkElementsPassPredicate(
+ a,
+ (idx: number, value: number | bigint) => {
+ return value === 0;
+ },
+ {
+ predicatePrinter: [
+ {
+ leftHeader: 'data exp ==',
+ getValueForCell: (idx: number) => {
+ return 0;
+ },
+ },
+ ],
+ }
+ );
+ };
+
+ // No fragment outputs occur.
+ const fbChecker = (a: Uint32Array) => {
+ return checkElementsPassPredicate(
+ a,
+ (idx: number, value: number | bigint) => {
+ return value === kWidth * kHeight;
+ },
+ {
+ predicatePrinter: [
+ {
+ leftHeader: 'fb exp ==',
+ getValueForCell: (idx: number) => {
+ return kWidth * kHeight;
+ },
+ },
+ ],
+ }
+ );
+ };
+
+ drawFullScreen(t, code, dataChecker, fbChecker);
+ });
+
+g.test('uniform_read_loop')
+ .desc('Test that helpers read a uniform value in a loop')
+ .fn(t => {
+ const code = `
+${kSharedCode}
+
+@fragment
+fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 {
+ discard;
+ for (var i = 0u; i < uniformValues[0]; i++) {
+ }
+ let idx = atomicAdd(&atomicIndex, 1);
+ output[idx] = pos.xy;
+ return 1;
+}
+`;
+
+ // No storage writes occur.
+ const dataChecker = (a: Float32Array) => {
+ return checkElementsPassPredicate(
+ a,
+ (idx: number, value: number | bigint) => {
+ return value === 0;
+ },
+ {
+ predicatePrinter: [
+ {
+ leftHeader: 'data exp ==',
+ getValueForCell: (idx: number) => {
+ return 0;
+ },
+ },
+ ],
+ }
+ );
+ };
+
+ // No fragment outputs occur.
+ const fbChecker = (a: Uint32Array) => {
+ return checkElementsPassPredicate(
+ a,
+ (idx: number, value: number | bigint) => {
+ return value === kWidth * kHeight;
+ },
+ {
+ predicatePrinter: [
+ {
+ leftHeader: 'fb exp ==',
+ getValueForCell: (idx: number) => {
+ return kWidth * kHeight;
+ },
+ },
+ ],
+ }
+ );
+ };
+
+ drawFullScreen(t, code, dataChecker, fbChecker);
+ });
+
+g.test('derivatives')
+ .desc('Test that derivatives are correct in the presence of discard')
+ .fn(t => {
+ const code = `
+${kSharedCode}
+
+@fragment
+fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 {
+ let ipos = vec2i(pos.xy);
+ let lsb = ipos & vec2(0x1);
+ let left_sel = select(2, 4, lsb.y == 1);
+ let right_sel = select(1, 3, lsb.y == 1);
+ let uidx = select(left_sel, right_sel, lsb.x == 1);
+ if ((lsb.x | lsb.y) & 0x1) == 0 {
+ discard;
+ }
+
+ let v = uniformValues[uidx];
+ let idx = atomicAdd(&atomicIndex, 1);
+ let dx = dpdx(f32(v));
+ let dy = dpdy(f32(v));
+ output[idx] = vec2(dx, dy);
+ return idx;
+}
+`;
+
+ // One pixel per quad is discarded. The derivatives values are always the same +/- 3.
+ const dataChecker = (a: Float32Array) => {
+ return checkElementsPassPredicate(
+ a,
+ (idx: number, value: number | bigint) => {
+ if (idx < (3 * (2 * kWidth * kHeight)) / 4) {
+ return value === -3 || value === 3;
+ } else {
+ return value === 0;
+ }
+ },
+ {
+ predicatePrinter: [
+ {
+ leftHeader: 'data exp ==',
+ getValueForCell: (idx: number) => {
+ if (idx < (3 * (2 * kWidth * kHeight)) / 4) {
+ return '+/- 3';
+ } else {
+ return 0;
+ }
+ },
+ },
+ ],
+ }
+ );
+ };
+
+ // 3/4 of the fragments are written.
+ const fbChecker = (a: Uint32Array) => {
+ return checkElementsPassPredicate(
+ a,
+ (idx: number, value: number | bigint) => {
+ const x = idx % kWidth;
+ const y = Math.floor(idx / kWidth);
+ if (((x | y) & 0x1) === 0) {
+ return value === kWidth * kHeight;
+ } else {
+ return value < (3 * (kWidth * kHeight)) / 4;
+ }
+ },
+ {
+ predicatePrinter: [
+ {
+ leftHeader: 'fb exp ==',
+ getValueForCell: (idx: number) => {
+ const x = idx % kWidth;
+ const y = Math.floor(idx / kWidth);
+ if (((x | y) & 0x1) === 0) {
+ return kWidth * kHeight;
+ } else {
+ return 'any';
+ }
+ },
+ },
+ ],
+ }
+ );
+ };
+
+ drawFullScreen(t, code, dataChecker, fbChecker);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/zero_init.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/zero_init.spec.ts
index e03a72f8df..1c6c9bc4a3 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/zero_init.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/zero_init.spec.ts
@@ -107,6 +107,10 @@ g.test('compute,zero_init')
? [true, false]
: [false]) {
for (const scalarType of supportedScalarTypes({ isAtomic, ...p })) {
+ // Fewer subcases: supportedScalarTypes was expanded to include f16
+ // but that may take too much time. It would require more complex code.
+ if (scalarType === 'f16') continue;
+
// Fewer subcases: For nested types, skip atomic u32 and non-atomic i32.
if (p._containerDepth > 0) {
if (scalarType === 'u32' && isAtomic) continue;
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/types.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/types.ts
index 799ea3affb..76b094310d 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/types.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/types.ts
@@ -2,19 +2,32 @@ import { keysOf } from '../../common/util/data_tables.js';
import { assert } from '../../common/util/util.js';
import { align } from '../util/math.js';
-const kArrayLength = 3;
+const kDefaultArrayLength = 3;
export type Requirement = 'never' | 'may' | 'must'; // never is the same as "must not"
-export type ContainerType = 'scalar' | 'vector' | 'matrix' | 'atomic' | 'array';
-export type ScalarType = 'i32' | 'u32' | 'f32' | 'bool';
+export type ContainerType = 'scalar' | 'vector' | 'matrix' | 'array';
+export type ScalarType = 'i32' | 'u32' | 'f16' | 'f32' | 'bool';
-export const HostSharableTypes = ['i32', 'u32', 'f32'] as const;
+export const HostSharableTypes = ['i32', 'u32', 'f16', 'f32'] as const;
+
+// The alignment and size of a host shareable type.
+// See "Alignment and Size" in the WGSL spec. https://w3.org/TR/WGSL/#alignment-and-size
+// Note this is independent of the address space that values of this type might appear in.
+// See RequiredAlignOf(...) for the 16-byte granularity requirement when
+// values of a type are placed in the uniform address space.
+type AlignmentAndSize = {
+ // AlignOf(T) for generated type T
+ alignment: number;
+ // SizeOf(T) for generated type T
+ size: number;
+};
/** Info for each plain scalar type. */
export const kScalarTypeInfo =
/* prettier-ignore */ {
'i32': { layout: { alignment: 4, size: 4 }, supportsAtomics: true, arrayLength: 1, innerLength: 0 },
'u32': { layout: { alignment: 4, size: 4 }, supportsAtomics: true, arrayLength: 1, innerLength: 0 },
+ 'f16': { layout: { alignment: 2, size: 2 }, supportsAtomics: false, arrayLength: 1, innerLength: 0, feature: 'shader-f16' },
'f32': { layout: { alignment: 4, size: 4 }, supportsAtomics: false, arrayLength: 1, innerLength: 0 },
'bool': { layout: undefined, supportsAtomics: false, arrayLength: 1, innerLength: 0 },
} as const;
@@ -24,29 +37,71 @@ export const kScalarTypes = keysOf(kScalarTypeInfo);
/** Info for each vecN<> container type. */
export const kVectorContainerTypeInfo =
/* prettier-ignore */ {
- 'vec2': { layout: { alignment: 8, size: 8 }, arrayLength: 2 , innerLength: 0 },
- 'vec3': { layout: { alignment: 16, size: 12 }, arrayLength: 3 , innerLength: 0 },
- 'vec4': { layout: { alignment: 16, size: 16 }, arrayLength: 4 , innerLength: 0 },
+ 'vec2': { arrayLength: 2 , innerLength: 0 },
+ 'vec3': { arrayLength: 3 , innerLength: 0 },
+ 'vec4': { arrayLength: 4 , innerLength: 0 },
} as const;
/** List of all vecN<> container types. */
export const kVectorContainerTypes = keysOf(kVectorContainerTypeInfo);
+/** Returns the vector layout for a given vector container and base type, or undefined if that base type has no layout */
+function vectorLayout(
+ vectorContainer: 'vec2' | 'vec3' | 'vec4',
+ baseType: ScalarType
+): undefined | AlignmentAndSize {
+ const n = kVectorContainerTypeInfo[vectorContainer].arrayLength;
+ const scalarLayout = kScalarTypeInfo[baseType].layout;
+ if (scalarLayout === undefined) {
+ return undefined;
+ }
+ if (n === 3) {
+ return { alignment: scalarLayout.alignment * 4, size: scalarLayout.size * 3 };
+ }
+ return { alignment: scalarLayout.alignment * n, size: scalarLayout.size * n };
+}
+
/** Info for each matNxN<> container type. */
export const kMatrixContainerTypeInfo =
/* prettier-ignore */ {
- 'mat2x2': { layout: { alignment: 8, size: 16 }, arrayLength: 2, innerLength: 2 },
- 'mat3x2': { layout: { alignment: 8, size: 24 }, arrayLength: 3, innerLength: 2 },
- 'mat4x2': { layout: { alignment: 8, size: 32 }, arrayLength: 4, innerLength: 2 },
- 'mat2x3': { layout: { alignment: 16, size: 32 }, arrayLength: 2, innerLength: 3 },
- 'mat3x3': { layout: { alignment: 16, size: 48 }, arrayLength: 3, innerLength: 3 },
- 'mat4x3': { layout: { alignment: 16, size: 64 }, arrayLength: 4, innerLength: 3 },
- 'mat2x4': { layout: { alignment: 16, size: 32 }, arrayLength: 2, innerLength: 4 },
- 'mat3x4': { layout: { alignment: 16, size: 48 }, arrayLength: 3, innerLength: 4 },
- 'mat4x4': { layout: { alignment: 16, size: 64 }, arrayLength: 4, innerLength: 4 },
+ 'mat2x2': { arrayLength: 2, innerLength: 2 },
+ 'mat3x2': { arrayLength: 3, innerLength: 2 },
+ 'mat4x2': { arrayLength: 4, innerLength: 2 },
+ 'mat2x3': { arrayLength: 2, innerLength: 3 },
+ 'mat3x3': { arrayLength: 3, innerLength: 3 },
+ 'mat4x3': { arrayLength: 4, innerLength: 3 },
+ 'mat2x4': { arrayLength: 2, innerLength: 4 },
+ 'mat3x4': { arrayLength: 3, innerLength: 4 },
+ 'mat4x4': { arrayLength: 4, innerLength: 4 },
} as const;
/** List of all matNxN<> container types. */
export const kMatrixContainerTypes = keysOf(kMatrixContainerTypeInfo);
+export const kMatrixContainerTypeLayoutInfo =
+ /* prettier-ignore */ {
+ 'f16': {
+ 'mat2x2': { layout: { alignment: 4, size: 8 } },
+ 'mat3x2': { layout: { alignment: 4, size: 12 } },
+ 'mat4x2': { layout: { alignment: 4, size: 16 } },
+ 'mat2x3': { layout: { alignment: 8, size: 16 } },
+ 'mat3x3': { layout: { alignment: 8, size: 24 } },
+ 'mat4x3': { layout: { alignment: 8, size: 32 } },
+ 'mat2x4': { layout: { alignment: 8, size: 16 } },
+ 'mat3x4': { layout: { alignment: 8, size: 24 } },
+ 'mat4x4': { layout: { alignment: 8, size: 32 } },
+ },
+ 'f32': {
+ 'mat2x2': { layout: { alignment: 8, size: 16 } },
+ 'mat3x2': { layout: { alignment: 8, size: 24 } },
+ 'mat4x2': { layout: { alignment: 8, size: 32 } },
+ 'mat2x3': { layout: { alignment: 16, size: 32 } },
+ 'mat3x3': { layout: { alignment: 16, size: 48 } },
+ 'mat4x3': { layout: { alignment: 16, size: 64 } },
+ 'mat2x4': { layout: { alignment: 16, size: 32 } },
+ 'mat3x4': { layout: { alignment: 16, size: 48 } },
+ 'mat4x4': { layout: { alignment: 16, size: 64 } },
+ }
+} as const;
+
export type AddressSpace = 'storage' | 'uniform' | 'private' | 'function' | 'workgroup' | 'handle';
export type AccessMode = 'read' | 'write' | 'read_write';
export type Scope = 'module' | 'function';
@@ -161,10 +216,39 @@ export function* generateTypes({
containerType: ContainerType;
/** Whether to wrap the baseType in `atomic<>`. */
isAtomic?: boolean;
-}) {
+}): Generator<
+ {
+ /** WGSL name for the generated type */
+ type: string;
+ _kTypeInfo: {
+ /**
+ * WGSL name for:
+ * - the generated type if it is scalar or atomic
+ * - the column vector type if the generated type is a matrix
+ * - the base type if the generated type is an array
+ */
+ elementBaseType: string;
+ /** Layout details if host-shareable, and undefined otherwise. */
+ layout: undefined | AlignmentAndSize;
+ supportsAtomics: boolean;
+ /** The number of elementBaseType items in the container. */
+ arrayLength: number;
+ /**
+ * 0 for scalar and vector.
+ * For a matrix type, this is the number of rows in the matrix.
+ */
+ innerLength?: number;
+ };
+ },
+ void
+> {
const scalarInfo = kScalarTypeInfo[baseType];
if (isAtomic) {
assert(scalarInfo.supportsAtomics, 'type does not support atomics');
+ assert(
+ containerType === 'scalar' || containerType === 'array',
+ "can only generate atomic inner types with containerType 'scalar' or 'array'"
+ );
}
const scalarType = isAtomic ? `atomic<${baseType}>` : baseType;
@@ -189,21 +273,29 @@ export function* generateTypes({
for (const vectorType of kVectorContainerTypes) {
yield {
type: `${vectorType}<${scalarType}>`,
- _kTypeInfo: { elementBaseType: baseType, ...kVectorContainerTypeInfo[vectorType] },
+ _kTypeInfo: {
+ elementBaseType: baseType,
+ ...kVectorContainerTypeInfo[vectorType],
+ layout: vectorLayout(vectorType, scalarType as ScalarType),
+ supportsAtomics: false,
+ },
};
}
}
if (containerType === 'matrix') {
- // Matrices can only be f32.
- if (baseType === 'f32') {
+ // Matrices can only be f16 or f32.
+ if (baseType === 'f16' || baseType === 'f32') {
for (const matrixType of kMatrixContainerTypes) {
- const matrixInfo = kMatrixContainerTypeInfo[matrixType];
+ const matrixDimInfo = kMatrixContainerTypeInfo[matrixType];
+ const matrixLayoutInfo = kMatrixContainerTypeLayoutInfo[baseType][matrixType];
yield {
type: `${matrixType}<${scalarType}>`,
_kTypeInfo: {
- elementBaseType: `vec${matrixInfo.innerLength}<${scalarType}>`,
- ...matrixInfo,
+ elementBaseType: `vec${matrixDimInfo.innerLength}<${scalarType}>`,
+ ...matrixDimInfo,
+ ...matrixLayoutInfo,
+ supportsAtomics: false,
},
};
}
@@ -212,41 +304,57 @@ export function* generateTypes({
// Array types
if (containerType === 'array') {
+ let arrayElemType: string = scalarType;
+ let arrayElementCount: number = kDefaultArrayLength;
+ let supportsAtomics = scalarInfo.supportsAtomics;
+ let layout: undefined | AlignmentAndSize = undefined;
+ if (scalarInfo.layout) {
+ // Compute the layout of the array type.
+ // Adjust the array element count or element type as needed.
+ if (addressSpace === 'uniform') {
+ // Use a vec4 of the scalar type, to achieve a 16 byte alignment without internal padding.
+ // This works for 4-byte scalar types, and does not work for f16.
+ // It is the caller's responsibility to filter out the f16 case.
+ assert(!isAtomic, 'the uniform case is making vec4 of scalar, which cannot handle atomics');
+ arrayElemType = `vec4<${baseType}>`;
+ supportsAtomics = false;
+ const arrayElemLayout = vectorLayout('vec4', baseType) as AlignmentAndSize;
+ // assert(arrayElemLayout.alignment % 16 === 0); // Callers responsibility to avoid
+ arrayElementCount = align(arrayElementCount, 4) / 4;
+ const arrayByteSize = arrayElementCount * arrayElemLayout.size;
+ layout = { alignment: arrayElemLayout.alignment, size: arrayByteSize };
+ } else {
+ // The ordinary case. Use scalarType as the element type.
+ const stride = arrayStride(scalarInfo.layout);
+ let arrayByteSize = arrayElementCount * stride;
+ if (addressSpace === 'storage') {
+ // The buffer effective binding size must be a multiple of 4.
+ // Adjust the array element count as needed.
+ while (arrayByteSize % 4 > 0) {
+ arrayElementCount++;
+ arrayByteSize = arrayElementCount * stride;
+ }
+ }
+ layout = { alignment: scalarInfo.layout.alignment, size: arrayByteSize };
+ }
+ }
+
const arrayTypeInfo = {
elementBaseType: `${baseType}`,
- arrayLength: kArrayLength,
- layout: scalarInfo.layout
- ? {
- alignment: scalarInfo.layout.alignment,
- size:
- addressSpace === 'uniform'
- ? // Uniform storage class must have array elements aligned to 16.
- kArrayLength *
- arrayStride({
- ...scalarInfo.layout,
- alignment: 16,
- })
- : kArrayLength * arrayStride(scalarInfo.layout),
- }
- : undefined,
+ arrayLength: arrayElementCount,
+ layout,
+ supportsAtomics,
};
// Sized
- if (addressSpace === 'uniform') {
- yield {
- type: `array<vec4<${scalarType}>,${kArrayLength}>`,
- _kTypeInfo: arrayTypeInfo,
- };
- } else {
- yield { type: `array<${scalarType},${kArrayLength}>`, _kTypeInfo: arrayTypeInfo };
- }
+ yield { type: `array<${arrayElemType},${arrayElementCount}>`, _kTypeInfo: arrayTypeInfo };
// Unsized
if (addressSpace === 'storage') {
- yield { type: `array<${scalarType}>`, _kTypeInfo: arrayTypeInfo };
+ yield { type: `array<${arrayElemType}>`, _kTypeInfo: arrayTypeInfo };
}
}
- function arrayStride(elementLayout: { size: number; alignment: number }) {
+ function arrayStride(elementLayout: AlignmentAndSize) {
return align(elementLayout.size, elementLayout.alignment);
}
@@ -272,7 +380,7 @@ export function supportsAtomics(p: {
);
}
-/** Generates an iterator of supported base types (i32/u32/f32/bool) */
+/** Generates an iterator of supported base types (i32/u32/f16/f32/bool) */
export function* supportedScalarTypes(p: { isAtomic: boolean; addressSpace: string }) {
for (const scalarType of kScalarTypes) {
const info = kScalarTypeInfo[scalarType];
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/const_assert/const_assert.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/const_assert/const_assert.spec.ts
index caaabc54d3..6c747f74a7 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/const_assert/const_assert.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/const_assert/const_assert.spec.ts
@@ -59,9 +59,9 @@ g.test('constant_expression_no_assert')
.desc(`Test that const_assert does not assert on a true conditional expression`)
.params(u =>
u
- .combine('case', keysOf(kConditionCases))
.combine('scope', ['module', 'function'] as const)
.beginSubcases()
+ .combine('case', keysOf(kConditionCases))
)
.fn(t => {
const expr = kConditionCases[t.params.case].expr;
@@ -76,9 +76,9 @@ g.test('constant_expression_assert')
.desc(`Test that const_assert does assert on a false conditional expression`)
.params(u =>
u
- .combine('case', keysOf(kConditionCases))
.combine('scope', ['module', 'function'] as const)
.beginSubcases()
+ .combine('case', keysOf(kConditionCases))
)
.fn(t => {
const expr = kConditionCases[t.params.case].expr;
@@ -95,10 +95,10 @@ g.test('constant_expression_logical_or_no_assert')
)
.params(u =>
u
- .combine('lhs', keysOf(kConditionCases))
- .combine('rhs', keysOf(kConditionCases))
.combine('scope', ['module', 'function'] as const)
.beginSubcases()
+ .combine('lhs', keysOf(kConditionCases))
+ .combine('rhs', keysOf(kConditionCases))
)
.fn(t => {
const expr = `(${kConditionCases[t.params.lhs].expr}) || (${
@@ -117,10 +117,10 @@ g.test('constant_expression_logical_or_assert')
)
.params(u =>
u
- .combine('lhs', keysOf(kConditionCases))
- .combine('rhs', keysOf(kConditionCases))
.combine('scope', ['module', 'function'] as const)
.beginSubcases()
+ .combine('lhs', keysOf(kConditionCases))
+ .combine('rhs', keysOf(kConditionCases))
)
.fn(t => {
const expr = `(${kConditionCases[t.params.lhs].expr}) || (${
@@ -139,10 +139,10 @@ g.test('constant_expression_logical_and_no_assert')
)
.params(u =>
u
- .combine('lhs', keysOf(kConditionCases))
- .combine('rhs', keysOf(kConditionCases))
.combine('scope', ['module', 'function'] as const)
.beginSubcases()
+ .combine('lhs', keysOf(kConditionCases))
+ .combine('rhs', keysOf(kConditionCases))
)
.fn(t => {
const expr = `(${kConditionCases[t.params.lhs].expr}) && (${
@@ -161,10 +161,10 @@ g.test('constant_expression_logical_and_assert')
)
.params(u =>
u
- .combine('lhs', keysOf(kConditionCases))
- .combine('rhs', keysOf(kConditionCases))
.combine('scope', ['module', 'function'] as const)
.beginSubcases()
+ .combine('lhs', keysOf(kConditionCases))
+ .combine('rhs', keysOf(kConditionCases))
)
.fn(t => {
const expr = `(${kConditionCases[t.params.lhs].expr}) && (${
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/compound_statement.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/compound_statement.spec.ts
new file mode 100644
index 0000000000..8ad89f48a8
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/compound_statement.spec.ts
@@ -0,0 +1,98 @@
+export const description = `
+Validation tests for declarations in compound statements.
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+// 9.1 Compound Statements
+// When a declaration is one of those statements, its identifier is in scope from
+// the start of the next statement until the end of the compound statement.
+//
+// Enumerating cases: Consider a declaration X inside a compound statement.
+// The X declaration should be tested with potential uses and potentially
+// conflicting declarations in positions [a, b, c, d, e], in the following:
+// a { b; X; c; { d; } } e;
+
+const kConflictTests = {
+ a: {
+ src: 'let x = 1; { let x = 1; }',
+ pass: true,
+ },
+ bc: {
+ src: '{let x = 1; let x = 1; }',
+ pass: false,
+ },
+ d: {
+ src: '{let x = 1; { let x = 1; }}',
+ pass: true,
+ },
+ e: {
+ src: '{let x = 1; } let x = 1;',
+ pass: true,
+ },
+};
+
+g.test('decl_conflict')
+ .desc(
+ 'Test a potentially conflicting declaration relative to a declaration in a compound statement'
+ )
+ .params(u => u.combine('case', keysOf(kConflictTests)))
+ .fn(t => {
+ const wgsl = `
+@vertex fn vtx() -> @builtin(position) vec4f {
+ ${kConflictTests[t.params.case].src}
+ return vec4f(1);
+}`;
+ t.expectCompileResult(kConflictTests[t.params.case].pass, wgsl);
+ });
+
+const kUseTests = {
+ a: {
+ src: 'let y = x; { let x = 1; }',
+ pass: false, // not visible
+ },
+ b: {
+ src: '{ let y = x; let x = 1; }',
+ pass: false, // not visible
+ },
+ self: {
+ src: '{ let x = (x);}',
+ pass: false, // not visible
+ },
+ c_yes: {
+ src: '{ const x = 1; const_assert x == 1; }',
+ pass: true,
+ },
+ c_no: {
+ src: '{ const x = 1; const_assert x == 2; }',
+ pass: false,
+ },
+ d_yes: {
+ src: '{ const x = 1; { const_assert x == 1; }}',
+ pass: true,
+ },
+ d_no: {
+ src: '{ const x = 1; { const_assert x == 2; }}',
+ pass: false,
+ },
+ e: {
+ src: '{ const x = 1; } let y = x;',
+ pass: false, // not visible
+ },
+};
+
+g.test('decl_use')
+ .desc('Test a use of a declaration in a compound statement')
+ .params(u => u.combine('case', keysOf(kUseTests)))
+ .fn(t => {
+ const wgsl = `
+@vertex fn vtx() -> @builtin(position) vec4f {
+ ${kUseTests[t.params.case].src}
+ return vec4f(1);
+}`;
+ t.expectCompileResult(kUseTests[t.params.case].pass, wgsl);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/const.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/const.spec.ts
index 6ded2480c7..38ac76fa04 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/const.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/const.spec.ts
@@ -3,6 +3,7 @@ Validation tests for const declarations
`;
import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
import { ShaderValidationTest } from '../shader_validation_test.js';
export const g = makeTestGroup(ShaderValidationTest);
@@ -59,3 +60,160 @@ const b = S(4).a;
`;
t.expectCompileResult(t.params.target === 'a', wgsl);
});
+
+const kTypeCases = {
+ bool: {
+ code: `const x : bool = true;`,
+ valid: true,
+ },
+ i32: {
+ code: `const x : i32 = 1i;`,
+ valid: true,
+ },
+ u32: {
+ code: `const x : u32 = 1u;`,
+ valid: true,
+ },
+ f32: {
+ code: `const x : f32 = 1f;`,
+ valid: true,
+ },
+ f16: {
+ code: `enable f16;\nconst x : f16 = 1h;`,
+ valid: true,
+ },
+ abstract_int: {
+ code: `
+ const x = 0xffffffffff;
+ const_assert x == 0xffffffffff;`,
+ valid: true,
+ },
+ abstract_float: {
+ code: `
+ const x = 3937509.87755102;
+ const_assert x != 3937510.0;
+ const_assert x != 3937509.75;`,
+ valid: true,
+ },
+ vec2i: {
+ code: `const x : vec2i = vec2i();`,
+ valid: true,
+ },
+ vec3u: {
+ code: `const x : vec3u = vec3u();`,
+ valid: true,
+ },
+ vec4f: {
+ code: `const x : vec4f = vec4f();`,
+ valid: true,
+ },
+ mat2x2: {
+ code: `const x : mat2x2f = mat2x2f();`,
+ valid: true,
+ },
+ mat4x3f: {
+ code: `const x : mat4x3<f32> = mat4x3<f32>();`,
+ valid: true,
+ },
+ array_sized: {
+ code: `const x : array<u32, 4> = array(1,2,3,4);`,
+ valid: true,
+ },
+ array_runtime: {
+ code: `const x : array<u32> = array(1,2,3);`,
+ valid: false,
+ },
+ struct: {
+ code: `struct S { x : u32 }\nconst x : S = S(0);`,
+ valid: true,
+ },
+ atomic: {
+ code: `const x : atomic<u32> = 0;`,
+ valid: false,
+ },
+ vec_abstract_int: {
+ code: `
+ const x = vec2(0xffffffffff,0xfffffffff0);
+ const_assert x.x == 0xffffffffff;
+ const_assert x.y == 0xfffffffff0;`,
+ valid: true,
+ },
+ array_abstract_int: {
+ code: `
+ const x = array(0xffffffffff,0xfffffffff0);
+ const_assert x[0] == 0xffffffffff;
+ const_assert x[1] == 0xfffffffff0;`,
+ valid: true,
+ },
+};
+
+g.test('type')
+ .desc('Test const types')
+ .params(u => u.combine('case', keysOf(kTypeCases)))
+ .beforeAllSubcases(t => {
+ if (t.params.case === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const testcase = kTypeCases[t.params.case];
+ const code = testcase.code;
+ const expect = testcase.valid;
+ t.expectCompileResult(expect, code);
+ });
+
+const kInitCases = {
+ no_init: {
+ code: `const x : u32;`,
+ valid: false,
+ },
+ no_type: {
+ code: `const x = 0;`,
+ valid: true,
+ },
+ init_matching_type: {
+ code: `const x : i32 = 1i;`,
+ valid: true,
+ },
+ init_mismatch_type: {
+ code: `const x : u32 = 1i;`,
+ valid: false,
+ },
+ abs_int_init_convert: {
+ code: `const x : u32 = 1;`,
+ valid: true,
+ },
+ abs_float_init_convert: {
+ code: `const x : f32 = 1.0;`,
+ valid: true,
+ },
+ init_const_expr: {
+ code: `const x = 0;\nconst y = x + 2;`,
+ valid: true,
+ },
+ init_override_expr: {
+ code: `override x : u32;\nconst y = x * 2;`,
+ valid: false,
+ },
+ init_runtime_expr: {
+ code: `var<private> x = 1i;\nconst y = x - 1;`,
+ valid: false,
+ },
+};
+
+g.test('initializer')
+ .desc('Test const initializers')
+ .params(u => u.combine('case', keysOf(kInitCases)))
+ .fn(t => {
+ const testcase = kInitCases[t.params.case];
+ const code = testcase.code;
+ const expect = testcase.valid;
+ t.expectCompileResult(expect, code);
+ });
+
+g.test('function_scope')
+ .desc('Test that const declarations are allowed in functions')
+ .fn(t => {
+ const code = `fn foo() { const x = 0; }`;
+ t.expectCompileResult(true, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/context_dependent_resolution.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/context_dependent_resolution.spec.ts
new file mode 100644
index 0000000000..aedef043cd
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/context_dependent_resolution.spec.ts
@@ -0,0 +1,338 @@
+export const description = `
+Tests that context dependent names do not participate in name resolution.
+That is, a declaration named the same as a context dependent name will not interfere.
+
+Context-dependent names:
+ * Attribute names
+ * Built-in value names
+ * Diagnostic severity control
+ * Diagnostic triggering rules
+ * Enable extensions
+ * Language extensions
+ * Swizzles
+ * Interpolation type
+ * Interpolation sampling
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kAttributeCases = {
+ align: `struct S { @align(16) x : u32 }`,
+ binding: `@group(0) @binding(0) var s : sampler;`,
+ builtin: `@vertex fn main() -> @builtin(position) vec4f { return vec4f(); }`,
+ // const is not writable
+ // diagnostic is a keyword
+ group: `@group(0) @binding(0) var s : sampler;`,
+ id: `@id(1) override x : i32;`,
+ interpolate: `@fragment fn main(@location(0) @interpolate(flat) x : i32) { }`,
+ invariant: `@fragment fn main(@builtin(position) @invariant pos : vec4f) { }`,
+ location: `@fragment fn main(@location(0) x : f32) { }`,
+ must_use: `@must_use fn foo() -> u32 { return 0; }`,
+ size: `struct S { @size(4) x : u32 }`,
+ workgroup_size: `@compute @workgroup_size(1) fn main() { }`,
+ compute: `@compute @workgroup_size(1) fn main() { }`,
+ fragment: `@fragment fn main() { }`,
+ vertex: `@vertex fn main() -> @builtin(position) vec4f { return vec4f(); }`,
+};
+
+g.test('attribute_names')
+ .desc('Tests attribute names do not use name resolution')
+ .params(u =>
+ u
+ .combine('case', keysOf(kAttributeCases))
+ .beginSubcases()
+ .combine('decl', ['override', 'const', 'var<private>'] as const)
+ )
+ .fn(t => {
+ const code = `
+ ${t.params.decl} ${t.params.case} : u32 = 0;
+ ${kAttributeCases[t.params.case]}
+ fn use_var() -> u32 {
+ return ${t.params.case};
+ }
+ `;
+
+ t.expectCompileResult(true, code);
+ });
+
+const kBuiltinCases = {
+ vertex_index: `
+ @vertex
+ fn main(@builtin(vertex_index) idx : u32) -> @builtin(position) vec4f
+ { return vec4f(); }`,
+ instance_index: `
+ @vertex
+ fn main(@builtin(instance_index) idx : u32) -> @builtin(position) vec4f
+ { return vec4f(); }`,
+ position_vertex: `
+ @vertex fn main() -> @builtin(position) vec4f
+ { return vec4f(); }`,
+ position_fragment: `@fragment fn main(@builtin(position) pos : vec4f) { }`,
+ front_facing: `@fragment fn main(@builtin(front_facing) x : bool) { }`,
+ frag_depth: `@fragment fn main() -> @builtin(frag_depth) f32 { return 0; }`,
+ sample_index: `@fragment fn main(@builtin(sample_index) x : u32) { }`,
+ sample_mask_input: `@fragment fn main(@builtin(sample_mask) x : u32) { }`,
+ sample_mask_output: `@fragment fn main() -> @builtin(sample_mask) u32 { return 0; }`,
+ local_invocation_id: `
+ @compute @workgroup_size(1)
+ fn main(@builtin(local_invocation_id) id : vec3u) { }`,
+ local_invocation_index: `
+ @compute @workgroup_size(1)
+ fn main(@builtin(local_invocation_index) id : u32) { }`,
+ global_invocation_id: `
+ @compute @workgroup_size(1)
+ fn main(@builtin(global_invocation_id) id : vec3u) { }`,
+ workgroup_id: `
+ @compute @workgroup_size(1)
+ fn main(@builtin(workgroup_id) id : vec3u) { }`,
+ num_workgroups: `
+ @compute @workgroup_size(1)
+ fn main(@builtin(num_workgroups) id : vec3u) { }`,
+};
+
+g.test('builtin_value_names')
+ .desc('Tests builtin value names do not use name resolution')
+ .params(u =>
+ u
+ .combine('case', keysOf(kBuiltinCases))
+ .beginSubcases()
+ .combine('decl', ['override', 'const', 'var<private>'] as const)
+ )
+ .fn(t => {
+ const code = `
+ ${t.params.decl} ${t.params.case} : u32 = 0;
+ ${kBuiltinCases[t.params.case]}
+ fn use_var() -> u32 {
+ return ${t.params.case};
+ }
+ `;
+
+ t.expectCompileResult(true, code);
+ });
+
+const kDiagnosticSeverityCases = {
+ error: `
+ diagnostic(error, derivative_uniformity);
+ @diagnostic(error, derivative_uniformity) fn foo() { }
+ `,
+ warning: `
+ diagnostic(warning, derivative_uniformity);
+ @diagnostic(warning, derivative_uniformity) fn foo() { }
+ `,
+ off: `
+ diagnostic(off, derivative_uniformity);
+ @diagnostic(off, derivative_uniformity) fn foo() { }
+ `,
+ info: `
+ diagnostic(info, derivative_uniformity);
+ @diagnostic(info, derivative_uniformity) fn foo() { }
+ `,
+};
+
+g.test('diagnostic_severity_names')
+ .desc('Tests diagnostic severity names do not use name resolution')
+ .params(u =>
+ u
+ .combine('case', keysOf(kDiagnosticSeverityCases))
+ .beginSubcases()
+ .combine('decl', ['override', 'const', 'var<private>'] as const)
+ )
+ .fn(t => {
+ const code = `
+ ${kDiagnosticSeverityCases[t.params.case]}
+ ${t.params.decl} ${t.params.case} : u32 = 0;
+ fn use_var() -> u32 {
+ return ${t.params.case};
+ }
+ `;
+
+ t.expectCompileResult(true, code);
+ });
+
+const kDiagnosticRuleCases = {
+ derivative_uniformity: `
+ diagnostic(off, derivative_uniformity);
+ @diagnostic(warning, derivative_uniformity) fn foo() { }`,
+ unknown_rule: `
+ diagnostic(off, unknown_rule);
+ @diagnostic(warning, unknown_rule) fn foo() { }`,
+ unknown: `
+ diagnostic(off, unknown.rule);
+ @diagnostic(warning, unknown.rule) fn foo() { }`,
+ rule: `
+ diagnostic(off, unknown.rule);
+ @diagnostic(warning, unknown.rule) fn foo() { }`,
+};
+
+g.test('diagnostic_rule_names')
+ .desc('Tests diagnostic rule names do not use name resolution')
+ .params(u =>
+ u
+ .combine('case', keysOf(kDiagnosticRuleCases))
+ .beginSubcases()
+ .combine('decl', ['override', 'const', 'var<private>'] as const)
+ )
+ .fn(t => {
+ const code = `
+ ${kDiagnosticRuleCases[t.params.case]}
+ ${t.params.decl} ${t.params.case} : u32 = 0;
+ fn use_var() -> u32 {
+ return ${t.params.case};
+ }
+ `;
+
+ t.expectCompileResult(true, code);
+ });
+
+const kEnableCases = {
+ f16: `enable f16;`,
+};
+
+g.test('enable_names')
+ .desc('Tests enable extension names do not use name resolution')
+ .params(u =>
+ u
+ .combine('case', keysOf(kEnableCases))
+ .beginSubcases()
+ .combine('decl', ['override', 'const', 'var<private>'] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(t => {
+ const code = `
+ ${kEnableCases[t.params.case]}
+ ${t.params.decl} ${t.params.case} : u32 = 0;
+ fn use_var() -> u32 {
+ return ${t.params.case};
+ }
+ `;
+
+ t.expectCompileResult(true, code);
+ });
+
+const kLanguageCases = {
+ readonly_and_readwrite_storage_textures: `requires readonly_and_readwrite_storage_textures;`,
+ packed_4x8_integer_dot_product: `requires packed_4x8_integer_dot_product;`,
+ unrestricted_pointer_parameters: `requires unrestricted_pointer_parameters;`,
+ pointer_composite_access: `requires pointer_composite_access;`,
+};
+
+g.test('language_names')
+ .desc('Tests language extension names do not use name resolution')
+ .params(u =>
+ u
+ .combine('case', keysOf(kLanguageCases))
+ .beginSubcases()
+ .combine('decl', ['override', 'const', 'var<private>'] as const)
+ )
+ .fn(t => {
+ t.skipIf(!t.hasLanguageFeature(t.params.case), 'Missing language feature');
+ const code = `
+ ${kLanguageCases[t.params.case]}
+ ${t.params.decl} ${t.params.case} : u32 = 0;
+ fn use_var() -> u32 {
+ return ${t.params.case};
+ }
+ `;
+
+ t.expectCompileResult(true, code);
+ });
+
+const kSwizzleCases = [
+ 'x',
+ 'y',
+ 'z',
+ 'w',
+ 'xy',
+ 'yxz',
+ 'wxyz',
+ 'xyxy',
+ 'r',
+ 'g',
+ 'b',
+ 'a',
+ 'rgb',
+ 'arr',
+ 'bgra',
+ 'agra',
+];
+
+g.test('swizzle_names')
+ .desc('Tests swizzle names do not use name resolution')
+ .params(u =>
+ u
+ .combine('case', kSwizzleCases)
+ .beginSubcases()
+ .combine('decl', ['override', 'const', 'var<private>'] as const)
+ )
+ .fn(t => {
+ let code = `${t.params.decl} ${t.params.case} : u32 = 0;\n`;
+ if (t.params.case.length === 1) {
+ for (let i = 2; i <= 4; i++) {
+ code += `${t.params.decl} ${t.params.case.padEnd(i, t.params.case[0])} : u32 = 0;\n`;
+ }
+ }
+ code += `fn foo() {
+ var x : vec4f;
+ _ = x.${t.params.case};
+ `;
+ if (t.params.case.length === 1) {
+ for (let i = 2; i <= 4; i++) {
+ code += `_ = x.${t.params.case.padEnd(i, t.params.case[0])};\n`;
+ }
+ }
+ code += `}
+ fn use_var() -> u32 {
+ return ${t.params.case};
+ }`;
+ t.expectCompileResult(true, code);
+ });
+
+const kInterpolationTypeCases = ['perspective', 'linear', 'flat'];
+
+g.test('interpolation_type_names')
+ .desc('Tests interpolation type names do not use name resolution')
+ .params(u =>
+ u
+ .combine('case', kInterpolationTypeCases)
+ .beginSubcases()
+ .combine('decl', ['override', 'const', 'var<private>'] as const)
+ )
+ .fn(t => {
+ const code = `
+ ${t.params.decl} ${t.params.case} : u32 = 0;
+ @fragment fn main(@location(0) @interpolate(${t.params.case}) x : f32) { }
+ fn use_var() -> u32 {
+ return ${t.params.case};
+ }
+ `;
+
+ t.expectCompileResult(true, code);
+ });
+
+const kInterpolationSamplingCases = ['center', 'centroid', 'sample'];
+
+g.test('interpolation_sampling_names')
+ .desc('Tests interpolation type names do not use name resolution')
+ .params(u =>
+ u
+ .combine('case', kInterpolationSamplingCases)
+ .beginSubcases()
+ .combine('decl', ['override', 'const', 'var<private>'] as const)
+ )
+ .fn(t => {
+ const code = `
+ ${t.params.decl} ${t.params.case} : u32 = 0;
+ @fragment fn main(@location(0) @interpolate(perspective, ${t.params.case}) x : f32) { }
+ fn use_var() -> u32 {
+ return ${t.params.case};
+ }
+ `;
+
+ t.expectCompileResult(true, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/let.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/let.spec.ts
new file mode 100644
index 0000000000..0e116a0ef4
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/let.spec.ts
@@ -0,0 +1,180 @@
+export const description = `
+Validation tests for let declarations
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+interface Case {
+ code: string;
+ valid: boolean;
+ decls?: string;
+}
+
+const kTypeCases: Record<string, Case> = {
+ bool: {
+ code: `let x : bool = true;`,
+ valid: true,
+ },
+ i32: {
+ code: `let x : i32 = 1i;`,
+ valid: true,
+ },
+ u32: {
+ code: `let x : u32 = 1u;`,
+ valid: true,
+ },
+ f32: {
+ code: `let x : f32 = 1f;`,
+ valid: true,
+ },
+ f16: {
+ code: `let x : f16 = 1h;`,
+ valid: true,
+ },
+ vec2i: {
+ code: `let x : vec2i = vec2i();`,
+ valid: true,
+ },
+ vec3u: {
+ code: `let x : vec3u = vec3u();`,
+ valid: true,
+ },
+ vec4f: {
+ code: `let x : vec4f = vec4f();`,
+ valid: true,
+ },
+ mat2x2: {
+ code: `let x : mat2x2f = mat2x2f();`,
+ valid: true,
+ },
+ mat4x3f: {
+ code: `let x : mat4x3<f32> = mat4x3<f32>();`,
+ valid: true,
+ },
+ array_sized: {
+ code: `let x : array<u32, 4> = array(1,2,3,4);`,
+ valid: true,
+ },
+ array_runtime: {
+ code: `let x : array<u32> = array(1,2,3);`,
+ valid: false,
+ },
+ struct: {
+ code: `let x : S = S(0);`,
+ valid: true,
+ decls: `struct S { x : u32 }`,
+ },
+ atomic: {
+ code: `let x : atomic<u32> = 0;`,
+ valid: false,
+ },
+ ptr_function: {
+ code: `
+ var x : i32;
+ let y : ptr<function, i32> = &x;`,
+ valid: true,
+ },
+ ptr_storage: {
+ code: `let y : ptr<storage, i32> = &x[0];`,
+ valid: true,
+ decls: `@group(0) @binding(0) var<storage> x : array<i32, 4>;`,
+ },
+ load_rule: {
+ code: `
+ var x : i32 = 1;
+ let y : i32 = x;`,
+ valid: true,
+ },
+};
+
+g.test('type')
+ .desc('Test let types')
+ .params(u => u.combine('case', keysOf(kTypeCases)))
+ .beforeAllSubcases(t => {
+ if (t.params.case === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const testcase = kTypeCases[t.params.case];
+ const code = `
+${t.params.case === 'f16' ? 'enable f16;' : ''}
+${testcase.decls ?? ''}
+fn foo() {
+ ${testcase.code}
+}`;
+ const expect = testcase.valid;
+ t.expectCompileResult(expect, code);
+ });
+
+const kInitCases: Record<string, Case> = {
+ no_init: {
+ code: `let x : u32;`,
+ valid: false,
+ },
+ no_type: {
+ code: `let x = 1;`,
+ valid: true,
+ },
+ init_matching_type: {
+ code: `let x : u32 = 1u;`,
+ valid: true,
+ },
+ init_mismatch_type: {
+ code: `let x : u32 = 1i;`,
+ valid: false,
+ },
+ ptr_type_mismatch: {
+ code: `var x : i32;\nlet y : ptr<function, u32> = &x;`,
+ valid: false,
+ },
+ ptr_access_mismatch: {
+ code: `let y : ptr<storage, u32, read> = &x;`,
+ valid: false,
+ decls: `@group(0) @binding(0) var<storage, read_write> x : u32;`,
+ },
+ ptr_addrspace_mismatch: {
+ code: `let y = ptr<storage, u32> = &x;`,
+ valid: false,
+ decls: `@group(0) @binding(0) var<uniform> x : u32;`,
+ },
+ init_const_expr: {
+ code: `let y = x * 2;`,
+ valid: true,
+ decls: `const x = 1;`,
+ },
+ init_override_expr: {
+ code: `let y = x + 1;`,
+ valid: true,
+ decls: `override x = 1;`,
+ },
+ init_runtime_expr: {
+ code: `var x = 1;\nlet y = x << 1;`,
+ valid: true,
+ },
+};
+
+g.test('initializer')
+ .desc('Test let initializers')
+ .params(u => u.combine('case', keysOf(kInitCases)))
+ .fn(t => {
+ const testcase = kInitCases[t.params.case];
+ const code = `
+${testcase.decls ?? ''}
+fn foo() {
+ ${testcase.code}
+}`;
+ const expect = testcase.valid;
+ t.expectCompileResult(expect, code);
+ });
+
+g.test('module_scope')
+ .desc('Test that let declarations are disallowed module scope')
+ .fn(t => {
+ const code = `let x = 0;`;
+ t.expectCompileResult(false, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/override.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/override.spec.ts
index 82a35a2f59..41dd1391b2 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/override.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/override.spec.ts
@@ -3,6 +3,7 @@ Validation tests for override declarations
`;
import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
import { ShaderValidationTest } from '../shader_validation_test.js';
export const g = makeTestGroup(ShaderValidationTest);
@@ -29,3 +30,180 @@ override c : i32 = ${t.params.target};
`;
t.expectCompileResult(t.params.target === 'a', wgsl);
});
+
+const kIdCases = {
+ min: {
+ code: `@id(0) override x = 1;`,
+ valid: true,
+ },
+ max: {
+ code: `@id(65535) override x = 1;`,
+ valid: true,
+ },
+ neg: {
+ code: `@id(-1) override x = 1;`,
+ valid: false,
+ },
+ too_large: {
+ code: `@id(65536) override x = 1;`,
+ valid: false,
+ },
+ duplicate: {
+ code: `
+ @id(1) override x = 1;
+ @id(1) override y = 1;`,
+ valid: false,
+ },
+};
+
+g.test('id')
+ .desc('Test id attributes')
+ .params(u => u.combine('case', keysOf(kIdCases)))
+ .fn(t => {
+ const testcase = kIdCases[t.params.case];
+ const code = testcase.code;
+ const expect = testcase.valid;
+ t.expectCompileResult(expect, code);
+ });
+
+const kTypeCases = {
+ bool: {
+ code: `override x : bool;`,
+ valid: true,
+ },
+ i32: {
+ code: `override x : i32;`,
+ valid: true,
+ },
+ u32: {
+ code: `override x : u32;`,
+ valid: true,
+ },
+ f32: {
+ code: `override x : f32;`,
+ valid: true,
+ },
+ f16: {
+ code: `enable f16;\noverride x : f16;`,
+ valid: true,
+ },
+ abs_int_conversion: {
+ code: `override x = 1;`,
+ valid: true,
+ },
+ abs_float_conversion: {
+ code: `override x = 1.0;`,
+ valid: true,
+ },
+ vec2_bool: {
+ code: `override x : vec2<bool>;`,
+ valid: false,
+ },
+ vec2i: {
+ code: `override x : vec2i;`,
+ valid: false,
+ },
+ vec3u: {
+ code: `override x : vec3u;`,
+ valid: false,
+ },
+ vec4f: {
+ code: `override x : vec4f;`,
+ valid: false,
+ },
+ mat2x2f: {
+ code: `override x : mat2x2f;`,
+ valid: false,
+ },
+ matrix: {
+ code: `override x : mat4x3<f32>;`,
+ valid: false,
+ },
+ array: {
+ code: `override x : array<u32, 4>;`,
+ valid: false,
+ },
+ struct: {
+ code: `struct S { x : u32 }\noverride x : S;`,
+ valid: false,
+ },
+ atomic: {
+ code: `override x : atomic<u32>;`,
+ valid: false,
+ },
+};
+
+g.test('type')
+ .desc('Test override types')
+ .params(u => u.combine('case', keysOf(kTypeCases)))
+ .beforeAllSubcases(t => {
+ if (t.params.case === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const testcase = kTypeCases[t.params.case];
+ const code = testcase.code;
+ const expect = testcase.valid;
+ t.expectCompileResult(expect, code);
+ });
+
+const kInitCases = {
+ no_init_no_type: {
+ code: `override x;`,
+ valid: false,
+ },
+ no_init: {
+ code: `override x : u32;`,
+ valid: true,
+ },
+ no_type: {
+ code: `override x = 1;`,
+ valid: true,
+ },
+ init_matching_type: {
+ code: `override x : u32 = 1u;`,
+ valid: true,
+ },
+ init_mismatch_type: {
+ code: `override x : u32 = 1i;`,
+ valid: false,
+ },
+ abs_int_init_convert: {
+ code: `override x : f32 = 1;`,
+ valid: true,
+ },
+ abs_float_init_convert: {
+ code: `override x : f32 = 1.0;`,
+ valid: true,
+ },
+ init_const_expr: {
+ code: `const x = 1;\noverride y = 2 * x;`,
+ valid: true,
+ },
+ init_override_expr: {
+ code: `override x = 1;\noverride y = x + 2;`,
+ valid: true,
+ },
+ init_runtime_expr: {
+ code: `var<private> x = 2;\noverride y = x;`,
+ valid: false,
+ },
+};
+
+g.test('initializer')
+ .desc('Test override initializers')
+ .params(u => u.combine('case', keysOf(kInitCases)))
+ .fn(t => {
+ const testcase = kInitCases[t.params.case];
+ const code = testcase.code;
+ const expect = testcase.valid;
+ t.expectCompileResult(expect, code);
+ });
+
+g.test('function_scope')
+ .desc('Test that override declarations are disallowed in functions')
+ .fn(t => {
+ const code = `fn foo() { override x : u32; }`;
+ t.expectCompileResult(false, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/var.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/var.spec.ts
new file mode 100644
index 0000000000..95e7417d23
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/var.spec.ts
@@ -0,0 +1,529 @@
+export const description = `
+Validation tests for host-shareable types.
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+// The set of types and their properties.
+const kTypes = {
+ // Scalars.
+ bool: {
+ isHostShareable: false,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+ i32: {
+ isHostShareable: true,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+ u32: {
+ isHostShareable: true,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+ f32: {
+ isHostShareable: true,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+ f16: {
+ isHostShareable: true,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: true,
+ },
+
+ // Vectors.
+ 'vec2<bool>': {
+ isHostShareable: false,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+ vec3i: {
+ isHostShareable: true,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+ vec4u: {
+ isHostShareable: true,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+ vec2f: {
+ isHostShareable: true,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+ vec3h: {
+ isHostShareable: true,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: true,
+ },
+
+ // Matrices.
+ mat2x2f: {
+ isHostShareable: true,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+ mat3x4h: {
+ isHostShareable: true,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: true,
+ },
+
+ // Atomics.
+ 'atomic<i32>': {
+ isHostShareable: true,
+ isConstructible: false,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+ 'atomic<u32>': {
+ isHostShareable: true,
+ isConstructible: false,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+
+ // Arrays.
+ 'array<vec4<bool>>': {
+ isHostShareable: false,
+ isConstructible: false,
+ isFixedFootprint: false,
+ requiresF16: false,
+ },
+ 'array<vec4<bool>, 4>': {
+ isHostShareable: false,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+ 'array<vec4u>': {
+ isHostShareable: true,
+ isConstructible: false,
+ isFixedFootprint: false,
+ requiresF16: false,
+ },
+ 'array<vec4u, 4>': {
+ isHostShareable: true,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+ 'array<vec4u, array_size_const>': {
+ isHostShareable: true,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+ 'array<vec4u, array_size_override>': {
+ isHostShareable: false,
+ isConstructible: false,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+
+ // Structures.
+ S_u32: {
+ isHostShareable: true,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+ S_bool: {
+ isHostShareable: false,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+ S_S_bool: {
+ isHostShareable: false,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+ S_array_vec4u: {
+ isHostShareable: true,
+ isConstructible: false,
+ isFixedFootprint: false,
+ requiresF16: false,
+ },
+ S_array_vec4u_4: {
+ isHostShareable: true,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+ S_array_bool_4: {
+ isHostShareable: false,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+
+ // Misc.
+ 'ptr<function, u32>': {
+ isHostShareable: false,
+ isConstructible: false,
+ isFixedFootprint: false,
+ requiresF16: false,
+ },
+ sampler: {
+ isHostShareable: false,
+ isConstructible: false,
+ isFixedFootprint: false,
+ requiresF16: false,
+ },
+ 'texture_2d<f32>': {
+ isHostShareable: false,
+ isConstructible: false,
+ isFixedFootprint: false,
+ requiresF16: false,
+ },
+};
+
+g.test('module_scope_types')
+ .desc('Test that only types that are allowed for a given address space are accepted.')
+ .params(u =>
+ u
+ .combine('type', keysOf(kTypes))
+ .combine('kind', [
+ 'comment',
+ 'handle',
+ 'private',
+ 'storage_ro',
+ 'storage_rw',
+ 'uniform',
+ 'workgroup',
+ ])
+ .combine('via_alias', [false, true])
+ )
+ .beforeAllSubcases(t => {
+ if (kTypes[t.params.type].requiresF16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const type = kTypes[t.params.type];
+ const isAtomic = t.params.type.indexOf('atomic') > -1;
+
+ let decl = '<>';
+ let shouldPass = false;
+ switch (t.params.kind) {
+ case 'comment':
+ // Control case to make sure all types are spelled correctly.
+ // We always emit an alias to the target type.
+ decl = '// ';
+ shouldPass = true;
+ break;
+ case 'handle':
+ decl = '@group(0) @binding(0) var foo : ';
+ shouldPass = t.params.type.indexOf('texture') > -1 || t.params.type.indexOf('sampler') > -1;
+ break;
+ case 'private':
+ decl = 'var<private> foo : ';
+ shouldPass = type.isConstructible;
+ break;
+ case 'storage_ro':
+ decl = '@group(0) @binding(0) var<storage, read> foo : ';
+ shouldPass = type.isHostShareable && !isAtomic;
+ break;
+ case 'storage_rw':
+ decl = '@group(0) @binding(0) var<storage, read_write> foo : ';
+ shouldPass = type.isHostShareable;
+ break;
+ case 'uniform':
+ decl = '@group(0) @binding(0) var<uniform> foo : ';
+ shouldPass = type.isHostShareable && type.isConstructible;
+ break;
+ case 'workgroup':
+ decl = 'var<workgroup> foo : ';
+ shouldPass = type.isFixedFootprint;
+ break;
+ }
+
+ const wgsl = `${type.requiresF16 ? 'enable f16;' : ''}
+ const array_size_const = 4;
+ override array_size_override = 4;
+
+ struct S_u32 { a : u32 }
+ struct S_bool { a : bool }
+ struct S_S_bool { a : S_bool }
+ struct S_array_vec4u { a : array<u32> }
+ struct S_array_vec4u_4 { a : array<vec4u, 4> }
+ struct S_array_bool_4 { a : array<bool, 4> }
+
+ alias MyType = ${t.params.type};
+
+ ${decl} ${t.params.via_alias ? 'MyType' : t.params.type};
+ `;
+
+ t.expectCompileResult(shouldPass, wgsl);
+ });
+
+g.test('function_scope_types')
+ .desc('Test that only types that are allowed for a given address space are accepted.')
+ .params(u =>
+ u
+ .combine('type', keysOf(kTypes))
+ .combine('kind', ['comment', 'var'])
+ .combine('via_alias', [false, true])
+ )
+ .beforeAllSubcases(t => {
+ if (kTypes[t.params.type].requiresF16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const type = kTypes[t.params.type];
+
+ let decl = '<>';
+ let shouldPass = false;
+ switch (t.params.kind) {
+ case 'comment':
+ // Control case to make sure all types are spelled correctly.
+ // We always emit an alias to the target type.
+ decl = '// ';
+ shouldPass = true;
+ break;
+ case 'var':
+ decl = 'var foo : ';
+ shouldPass = type.isConstructible;
+ break;
+ }
+
+ const wgsl = `${type.requiresF16 ? 'enable f16;' : ''}
+ const array_size_const = 4;
+ override array_size_override = 4;
+
+ struct S_u32 { a : u32 }
+ struct S_bool { a : bool }
+ struct S_S_bool { a : S_bool }
+ struct S_array_vec4u { a : array<u32> }
+ struct S_array_vec4u_4 { a : array<vec4u, 4> }
+ struct S_array_bool_4 { a : array<bool, 4> }
+
+ alias MyType = ${t.params.type};
+
+ fn foo() {
+ ${decl} ${t.params.via_alias ? 'MyType' : t.params.type};
+ }`;
+
+ t.expectCompileResult(shouldPass, wgsl);
+ });
+
+g.test('module_scope_initializers')
+ .desc('Test that initializers are only supported on address spaces that allow them.')
+ .params(u =>
+ u
+ .combine('initializer', [false, true])
+ .combine('kind', ['private', 'storage_ro', 'storage_rw', 'uniform', 'workgroup'])
+ )
+ .fn(t => {
+ let decl = '<>';
+ switch (t.params.kind) {
+ case 'private':
+ decl = 'var<private> foo : ';
+ break;
+ case 'storage_ro':
+ decl = '@group(0) @binding(0) var<storage, read> foo : ';
+ break;
+ case 'storage_rw':
+ decl = '@group(0) @binding(0) var<storage, read_write> foo : ';
+ break;
+ case 'uniform':
+ decl = '@group(0) @binding(0) var<uniform> foo : ';
+ break;
+ case 'workgroup':
+ decl = 'var<workgroup> foo : ';
+ break;
+ }
+
+ const wgsl = `${decl} u32${t.params.initializer ? ' = 42u' : ''};`;
+ t.expectCompileResult(t.params.kind === 'private' || !t.params.initializer, wgsl);
+ });
+
+g.test('handle_initializer')
+ .desc('Test that initializers are not allowed for handle types')
+ .params(u =>
+ u.combine('initializer', [false, true]).combine('type', ['sampler', 'texture_2d<f32>'])
+ )
+ .fn(t => {
+ const wgsl = `
+ @group(0) @binding(0) var foo : ${t.params.type};
+ @group(0) @binding(1) var bar : ${t.params.type}${t.params.initializer ? ' = foo' : ''};`;
+ t.expectCompileResult(!t.params.initializer, wgsl);
+ });
+
+// A list of u32 initializers and their validity for the private address space.
+const kInitializers = {
+ 'u32()': true,
+ '42u': true,
+ 'u32(sqrt(42.0))': true,
+ 'user_func()': false,
+ my_const_42u: true,
+ my_override_42u: true,
+ another_private_var: false,
+ 'vec4u(1, 2, 3, 4)[my_const_42u / 20]': true,
+ 'vec4u(1, 2, 3, 4)[my_override_42u / 20]': true,
+ 'vec4u(1, 2, 3, 4)[another_private_var / 20]': false,
+};
+
+g.test('initializer_kind')
+ .desc(
+ 'Test that initializers must be const-expression or override-expression for the private address space.'
+ )
+ .params(u =>
+ u.combine('initializer', keysOf(kInitializers)).combine('addrspace', ['private', 'function'])
+ )
+ .fn(t => {
+ let wgsl = `
+ const my_const_42u = 42u;
+ override my_override_42u : u32;
+ var<private> another_private_var = 42u;
+
+ fn user_func() -> u32 {
+ return 42u;
+ }
+ `;
+
+ if (t.params.addrspace === 'private') {
+ wgsl += `
+ var<private> foo : u32 = ${t.params.initializer};`;
+ } else {
+ wgsl += `
+ fn foo() {
+ var bar : u32 = ${t.params.initializer};
+ }`;
+ }
+ t.expectCompileResult(
+ t.params.addrspace === 'function' || kInitializers[t.params.initializer],
+ wgsl
+ );
+ });
+
+g.test('function_addrspace_at_module_scope')
+ .desc('Test that the function address space is not allowed at module scope.')
+ .params(u => u.combine('addrspace', ['private', 'function']))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.addrspace === 'private',
+ `var<${t.params.addrspace}> foo : i32;`
+ );
+ });
+
+// A list of resource variable declarations.
+const kResourceDecls = {
+ uniform: 'var<uniform> buffer : vec4f;',
+ storage: 'var<storage> buffer : vec4f;',
+ texture: 'var t : texture_2d<f32>;',
+ sampler: 'var s : sampler;',
+};
+
+g.test('binding_point_on_resources')
+ .desc('Test that resource variables must have both @group and @binding attributes.')
+ .params(u =>
+ u
+ .combine('decl', keysOf(kResourceDecls))
+ .combine('group', ['', '@group(0)'])
+ .combine('binding', ['', '@binding(0)'])
+ )
+ .fn(t => {
+ const shouldPass = t.params.group !== '' && t.params.binding !== '';
+ const wgsl = `${t.params.group} ${t.params.binding} ${kResourceDecls[t.params.decl]}`;
+ t.expectCompileResult(shouldPass, wgsl);
+ });
+
+g.test('binding_point_on_non_resources')
+ .desc('Test that non-resource variables cannot have either @group or @binding attributes.')
+ .params(u =>
+ u
+ .combine('addrspace', ['private', 'workgroup'])
+ .combine('group', ['', '@group(0)'])
+ .combine('binding', ['', '@binding(0)'])
+ )
+ .fn(t => {
+ const shouldPass = t.params.group === '' && t.params.binding === '';
+ const wgsl = `${t.params.group} ${t.params.binding} var<${t.params.addrspace}> foo : i32;`;
+ t.expectCompileResult(shouldPass, wgsl);
+ });
+
+g.test('binding_point_on_function_var')
+ .desc('Test that function variables cannot have either @group or @binding attributes.')
+ .params(u => u.combine('group', ['', '@group(0)']).combine('binding', ['', '@binding(0)']))
+ .fn(t => {
+ const shouldPass = t.params.group === '' && t.params.binding === '';
+ const wgsl = `
+ fn foo() {
+ ${t.params.group} ${t.params.binding} var bar : i32;
+ }`;
+ t.expectCompileResult(shouldPass, wgsl);
+ });
+
+g.test('binding_collisions')
+ .desc('Test that binding points can collide iff they are not used by the same entry point.')
+ .params(u =>
+ u
+ .combine('a_group', [0, 1])
+ .combine('b_group', [0, 1])
+ .combine('a_binding', [0, 1])
+ .combine('b_binding', [0, 1])
+ .combine('b_use', ['same', 'different'])
+ )
+ .fn(t => {
+ const wgsl = `
+ @group(${t.params.a_group}) @binding(${t.params.a_binding}) var<uniform> a : vec4f;
+ @group(${t.params.b_group}) @binding(${t.params.b_binding}) var<uniform> b : vec4f;
+
+ @fragment
+ fn main1() {
+ _ = a;
+ ${
+ t.params.b_use === 'same'
+ ? ''
+ : `
+ }
+
+ @fragment
+ fn main2() {`
+ }
+ _ = b;
+ }`;
+
+ const collision =
+ t.params.a_group === t.params.b_group && t.params.a_binding === t.params.b_binding;
+ const shouldFail = collision && t.params.b_use === 'same';
+ t.expectCompileResult(!shouldFail, wgsl);
+ });
+
+g.test('binding_collision_unused_helper')
+ .desc('Test that binding points can collide in an unused helper function.')
+ .fn(t => {
+ const wgsl = `
+ @group(0) @binding(0) var<uniform> a : vec4f;
+ @group(0) @binding(0) var<uniform> b : vec4f;
+
+ fn foo() {
+ _ = a;
+ _ = b;
+ }`;
+
+ t.expectCompileResult(true, wgsl);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/access/vector.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/access/vector.spec.ts
index 0294fc2d56..766adc0fb8 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/access/vector.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/access/vector.spec.ts
@@ -178,10 +178,11 @@ g.test('vector')
.desc('Tests validation of vector indexed and swizzles')
.params(u =>
u
- .combine('case', keysOf(kCases)) //
.combine('vector_decl', ['const', 'let', 'var', 'param'] as const)
.combine('vector_width', [2, 3, 4] as const)
.combine('element_type', ['i32', 'u32', 'f32', 'f16', 'bool'] as const)
+ .beginSubcases()
+ .combine('case', keysOf(kCases))
)
.beforeAllSubcases(t => {
if (t.params.element_type === 'f16') {
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/add_sub_mul.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/add_sub_mul.spec.ts
new file mode 100644
index 0000000000..3755eb7386
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/add_sub_mul.spec.ts
@@ -0,0 +1,320 @@
+export const description = `
+Validation tests for add/sub/mul expressions.
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../common/util/data_tables.js';
+import { assert } from '../../../../../common/util/util.js';
+import { kBit } from '../../../../util/constants.js';
+import {
+ kAllScalarsAndVectors,
+ kConcreteNumericScalarsAndVectors,
+ kConvertableToFloatScalar,
+ ScalarType,
+ scalarTypeOf,
+ Type,
+ Value,
+ VectorType,
+} from '../../../../util/conversion.js';
+import { nextAfterF16, nextAfterF32 } from '../../../../util/math.js';
+import { reinterpretU16AsF16, reinterpretU32AsF32 } from '../../../../util/reinterpret.js';
+import { ShaderValidationTest } from '../../shader_validation_test.js';
+import {
+ kConstantAndOverrideStages,
+ validateConstOrOverrideBinaryOpEval,
+} from '../call/builtin/const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+// A list of operators tested in this file.
+const kOperators = {
+ add: { op: '+' },
+ sub: { op: '-' },
+ mul: { op: '*' },
+};
+
+// A list of scalar and vector types.
+const kScalarAndVectorTypes = objectsToRecord(kAllScalarsAndVectors);
+const kConcreteNumericScalarAndVectorTypes = objectsToRecord(kConcreteNumericScalarsAndVectors);
+
+g.test('scalar_vector')
+ .desc(
+ `
+ Validates that scalar and vector expressions are only accepted for compatible numeric types.
+ `
+ )
+ .params(u =>
+ u
+ .combine('lhs', keysOf(kScalarAndVectorTypes))
+ .combine(
+ 'rhs',
+ // Skip vec3 and vec4 on the RHS to keep the number of subcases down.
+ // vec3 + vec3 and vec4 + vec4 is tested in execution tests.
+ keysOf(kScalarAndVectorTypes).filter(
+ value => !(value.startsWith('vec3') || value.startsWith('vec4'))
+ )
+ )
+ .beginSubcases()
+ .combine('op', keysOf(kOperators))
+ )
+ .beforeAllSubcases(t => {
+ if (
+ scalarTypeOf(kScalarAndVectorTypes[t.params.lhs]) === Type.f16 ||
+ scalarTypeOf(kScalarAndVectorTypes[t.params.rhs]) === Type.f16
+ ) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const op = kOperators[t.params.op];
+ const lhs = kScalarAndVectorTypes[t.params.lhs];
+ const rhs = kScalarAndVectorTypes[t.params.rhs];
+ const lhsElement = scalarTypeOf(lhs);
+ const rhsElement = scalarTypeOf(rhs);
+ const hasF16 = lhsElement === Type.f16 || rhsElement === Type.f16;
+ const code = `
+${hasF16 ? 'enable f16;' : ''}
+const lhs = ${lhs.create(0).wgsl()};
+const rhs = ${rhs.create(0).wgsl()};
+const foo = lhs ${op.op} rhs;
+`;
+
+ let elementsCompatible = lhsElement === rhsElement;
+ const elementTypes = [lhsElement, rhsElement];
+
+ // Booleans are not allowed for arithmetic expressions.
+ if (elementTypes.includes(Type.bool)) {
+ elementsCompatible = false;
+
+ // AbstractInt is allowed with everything but booleans which are already checked above.
+ } else if (elementTypes.includes(Type.abstractInt)) {
+ elementsCompatible = true;
+
+ // AbstractFloat is allowed with AbstractInt (checked above) or float types
+ } else if (elementTypes.includes(Type.abstractFloat)) {
+ elementsCompatible = elementTypes.every(e => kConvertableToFloatScalar.includes(e));
+ }
+
+ // Determine if the full type is compatible. The only invalid case is mixed vector sizes.
+ let valid = elementsCompatible;
+ if (lhs instanceof VectorType && rhs instanceof VectorType) {
+ valid = valid && lhs.width === rhs.width;
+ }
+
+ t.expectCompileResult(valid, code);
+ });
+
+g.test('scalar_vector_out_of_range')
+ .desc(
+ `
+ Checks that constant or override evaluation of add/sub/mul operations on scalar/vectors that produce out of range values cause validation errors.
+ - Checks for all concrete numeric scalar and vector types, including scalar * vector and vector * scalar.
+ - Checks for all vector elements that could cause the out of range to happen.
+ - Checks for pairs of values at one ULP difference at the boundary of the out of range.
+ `
+ )
+ .params(u =>
+ u
+ .combine('op', keysOf(kOperators))
+ .combine('lhs', keysOf(kConcreteNumericScalarAndVectorTypes))
+ .expand('rhs', p => {
+ if (kScalarAndVectorTypes[p.lhs] instanceof VectorType) {
+ return [p.lhs, scalarTypeOf(kScalarAndVectorTypes[p.lhs]).toString()];
+ }
+ return [p.lhs];
+ })
+ .beginSubcases()
+ .expand('swap', p => {
+ if (p.lhs === p.rhs) {
+ return [false];
+ }
+ return [false, true];
+ })
+ .combine('nonZeroIndex', [0, 1, 2, 3])
+ .filter(p => {
+ const lType = kScalarAndVectorTypes[p.lhs];
+ if (lType instanceof VectorType) {
+ return lType.width > p.nonZeroIndex;
+ }
+ return p.nonZeroIndex === 0;
+ })
+ .combine('valueCase', ['halfmax', 'halfmax+ulp', 'sqrtmax', 'sqrtmax+ulp'] as const)
+ .combine('stage', kConstantAndOverrideStages)
+ )
+ .beforeAllSubcases(t => {
+ if (
+ scalarTypeOf(kScalarAndVectorTypes[t.params.lhs]) === Type.f16 ||
+ scalarTypeOf(kScalarAndVectorTypes[t.params.rhs]) === Type.f16
+ ) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const { op, valueCase, nonZeroIndex, swap } = t.params;
+ let { lhs, rhs } = t.params;
+
+ const elementType = scalarTypeOf(kScalarAndVectorTypes[lhs]);
+
+ // Handle the swapping of LHS and RHS to test all cases of scalar * vector.
+ if (swap) {
+ [rhs, lhs] = [lhs, rhs];
+ }
+
+ // What is the maximum representable value for the type? Also how do we add a ULP?
+ let maxValue = 0;
+ let nextAfter: (v: number) => number = v => v + 1;
+ let outOfRangeIsError = false;
+ switch (elementType) {
+ case Type.f16:
+ maxValue = reinterpretU16AsF16(kBit.f16.positive.max);
+ nextAfter = v => nextAfterF16(v, 'positive', 'no-flush');
+ outOfRangeIsError = true;
+ break;
+ case Type.f32:
+ maxValue = reinterpretU32AsF32(kBit.f32.positive.max);
+ nextAfter = v => nextAfterF32(v, 'positive', 'no-flush');
+ outOfRangeIsError = true;
+ break;
+ case Type.u32:
+ maxValue = kBit.u32.max;
+ break;
+ case Type.i32:
+ maxValue = kBit.i32.positive.max;
+ break;
+ }
+
+ // Decide on the test value that may or may not do an out of range computation.
+ let value;
+ switch (valueCase) {
+ case 'halfmax':
+ value = Math.floor(maxValue / 2);
+ break;
+ case 'halfmax+ulp':
+ value = nextAfter(Math.ceil(maxValue / 2));
+ break;
+ case 'sqrtmax':
+ value = Math.floor(Math.sqrt(maxValue));
+ break;
+ case 'sqrtmax+ulp':
+ value = nextAfter(Math.ceil(Math.sqrt(maxValue)));
+ break;
+ }
+
+ // What value will be computed by the test?
+ let computedValue;
+ let leftValue = value;
+ const rightValue = value;
+ switch (op) {
+ case 'add':
+ computedValue = value + value;
+ break;
+ case 'sub':
+ computedValue = -value - value;
+ leftValue = -value;
+ break;
+ case 'mul':
+ computedValue = value * value;
+ break;
+ }
+
+ // Creates either a scalar with the value, or a vector with the value only at a specific index.
+ const create = (type: ScalarType | VectorType, index: number, value: number): Value => {
+ if (type instanceof ScalarType) {
+ return type.create(value);
+ } else {
+ assert(type instanceof VectorType);
+ const values = new Array(type.width);
+ values.fill(0);
+ values[index] = value;
+ return type.create(values);
+ }
+ };
+
+ // Check if there is overflow
+ const success = Math.abs(computedValue) <= maxValue || !outOfRangeIsError;
+ validateConstOrOverrideBinaryOpEval(
+ t,
+ kOperators[op].op,
+ success,
+ t.params.stage,
+ create(kScalarAndVectorTypes[lhs], nonZeroIndex, leftValue),
+ t.params.stage,
+ create(kScalarAndVectorTypes[rhs], nonZeroIndex, rightValue)
+ );
+ });
+
+interface InvalidTypeConfig {
+ // An expression that produces a value of the target type.
+ expr: string;
+ // A function that converts an expression of the target type into a valid integer operand.
+ control: (x: string) => string;
+}
+const kInvalidTypes: Record<string, InvalidTypeConfig> = {
+ array: {
+ expr: 'arr',
+ control: e => `${e}[0]`,
+ },
+
+ ptr: {
+ expr: '(&u)',
+ control: e => `*${e}`,
+ },
+
+ atomic: {
+ expr: 'a',
+ control: e => `atomicLoad(&${e})`,
+ },
+
+ texture: {
+ expr: 't',
+ control: e => `i32(textureLoad(${e}, vec2(), 0).x)`,
+ },
+
+ sampler: {
+ expr: 's',
+ control: e => `i32(textureSampleLevel(t, ${e}, vec2(), 0).x)`,
+ },
+
+ struct: {
+ expr: 'str',
+ control: e => `${e}.u`,
+ },
+};
+
+g.test('invalid_type_with_itself')
+ .desc(
+ `
+ Validates that expressions are never accepted for non-scalar, non-vector, and non-matrix types.
+ `
+ )
+ .params(u =>
+ u
+ .combine('op', keysOf(kOperators))
+ .combine('type', keysOf(kInvalidTypes))
+ .combine('control', [true, false])
+ .beginSubcases()
+ )
+ .fn(t => {
+ const op = kOperators[t.params.op];
+ const type = kInvalidTypes[t.params.type];
+ const expr = t.params.control ? type.control(type.expr) : type.expr;
+ const code = `
+@group(0) @binding(0) var t : texture_2d<f32>;
+@group(0) @binding(1) var s : sampler;
+@group(0) @binding(2) var<storage, read_write> a : atomic<i32>;
+
+struct S { u : u32 }
+
+var<private> u : u32;
+var<private> m : mat2x2f;
+var<private> arr : array<i32, 4>;
+var<private> str : S;
+
+@compute @workgroup_size(1)
+fn main() {
+ let foo = ${expr} ${op.op} ${expr};
+}
+`;
+
+ t.expectCompileResult(t.params.control, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/and_or_xor.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/and_or_xor.spec.ts
new file mode 100644
index 0000000000..3dd3607365
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/and_or_xor.spec.ts
@@ -0,0 +1,182 @@
+export const description = `
+Validation tests for logical and bitwise and/or/xor expressions.
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../common/util/data_tables.js';
+import {
+ kAllScalarsAndVectors,
+ ScalarType,
+ scalarTypeOf,
+ Type,
+ VectorType,
+} from '../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+// A list of operators and a flag for whether they support boolean values or not.
+const kOperators = {
+ and: { op: '&', supportsBool: true },
+ or: { op: '|', supportsBool: true },
+ xor: { op: '^', supportsBool: false },
+};
+
+// A list of scalar and vector types.
+const kScalarAndVectorTypes = objectsToRecord(kAllScalarsAndVectors);
+
+g.test('scalar_vector')
+ .desc(
+ `
+ Validates that scalar and vector expressions are only accepted for bool or compatible integer types.
+ `
+ )
+ .params(u =>
+ u
+ .combine('lhs', keysOf(kScalarAndVectorTypes))
+ .combine(
+ 'rhs',
+ // Skip vec3 and vec4 on the RHS to keep the number of subcases down.
+ // vec3 + vec3 and vec4 + vec4 is tested in execution tests.
+ keysOf(kScalarAndVectorTypes).filter(
+ value => !(value.startsWith('vec3') || value.startsWith('vec4'))
+ )
+ )
+ .beginSubcases()
+ .combine('op', keysOf(kOperators))
+ )
+ .beforeAllSubcases(t => {
+ if (
+ scalarTypeOf(kScalarAndVectorTypes[t.params.lhs]) === Type.f16 ||
+ scalarTypeOf(kScalarAndVectorTypes[t.params.rhs]) === Type.f16
+ ) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const op = kOperators[t.params.op];
+ const lhs = kScalarAndVectorTypes[t.params.lhs];
+ const rhs = kScalarAndVectorTypes[t.params.rhs];
+ const lhsElement = scalarTypeOf(lhs);
+ const rhsElement = scalarTypeOf(rhs);
+ const hasF16 = lhsElement === Type.f16 || rhsElement === Type.f16;
+ const code = `
+${hasF16 ? 'enable f16;' : ''}
+const lhs = ${lhs.create(0).wgsl()};
+const rhs = ${rhs.create(0).wgsl()};
+const foo = lhs ${op.op} rhs;
+`;
+
+ // Determine if the element types are compatible.
+ const kIntegerTypes = [Type.abstractInt, Type.i32, Type.u32];
+ let elementIsCompatible = false;
+ if (lhsElement === Type.abstractInt) {
+ // Abstract integers are compatible with any other integer type.
+ elementIsCompatible = kIntegerTypes.includes(rhsElement);
+ } else if (rhsElement === Type.abstractInt) {
+ // Abstract integers are compatible with any other numeric type.
+ elementIsCompatible = kIntegerTypes.includes(lhsElement);
+ } else if (kIntegerTypes.includes(lhsElement)) {
+ // Concrete integers are only compatible with values with the exact same type.
+ elementIsCompatible = lhsElement === rhsElement;
+ } else if (lhsElement === Type.bool) {
+ // Booleans are only compatible with other booleans.
+ elementIsCompatible = rhsElement === Type.bool;
+ }
+
+ // Determine if the full type is compatible.
+ let valid = false;
+ if (lhs instanceof ScalarType && rhs instanceof ScalarType) {
+ valid = elementIsCompatible;
+ } else if (lhs instanceof VectorType && rhs instanceof VectorType) {
+ // Vectors are only compatible with if the vector widths match.
+ valid = lhs.width === rhs.width && elementIsCompatible;
+ }
+
+ if (lhsElement === Type.bool) {
+ valid &&= op.supportsBool;
+ }
+
+ t.expectCompileResult(valid, code);
+ });
+
+interface InvalidTypeConfig {
+ // An expression that produces a value of the target type.
+ expr: string;
+ // A function that converts an expression of the target type into a valid integer operand.
+ control: (x: string) => string;
+}
+const kInvalidTypes: Record<string, InvalidTypeConfig> = {
+ mat2x2f: {
+ expr: 'm',
+ control: e => `i32(${e}[0][0])`,
+ },
+
+ array: {
+ expr: 'arr',
+ control: e => `${e}[0]`,
+ },
+
+ ptr: {
+ expr: '(&u)',
+ control: e => `*${e}`,
+ },
+
+ atomic: {
+ expr: 'a',
+ control: e => `atomicLoad(&${e})`,
+ },
+
+ texture: {
+ expr: 't',
+ control: e => `i32(textureLoad(${e}, vec2(), 0).x)`,
+ },
+
+ sampler: {
+ expr: 's',
+ control: e => `i32(textureSampleLevel(t, ${e}, vec2(), 0).x)`,
+ },
+
+ struct: {
+ expr: 'str',
+ control: e => `${e}.u`,
+ },
+};
+
+g.test('invalid_types')
+ .desc(
+ `
+ Validates that expressions are never accepted for non-scalar and non-vector types.
+ `
+ )
+ .params(u =>
+ u
+ .combine('op', keysOf(kOperators))
+ .combine('type', keysOf(kInvalidTypes))
+ .combine('control', [true, false])
+ .beginSubcases()
+ )
+ .fn(t => {
+ const op = kOperators[t.params.op];
+ const type = kInvalidTypes[t.params.type];
+ const expr = t.params.control ? type.control(type.expr) : type.expr;
+ const code = `
+@group(0) @binding(0) var t : texture_2d<f32>;
+@group(0) @binding(1) var s : sampler;
+@group(0) @binding(2) var<storage, read_write> a : atomic<i32>;
+
+struct S { u : u32 }
+
+var<private> u : u32;
+var<private> m : mat2x2f;
+var<private> arr : array<i32, 4>;
+var<private> str : S;
+
+@compute @workgroup_size(1)
+fn main() {
+ let foo = ${expr} ${op.op} ${expr};
+}
+`;
+
+ t.expectCompileResult(t.params.control, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/bitwise_shift.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/bitwise_shift.spec.ts
index 5f7b995ded..e3f13d6813 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/bitwise_shift.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/bitwise_shift.spec.ts
@@ -3,6 +3,13 @@ Validation tests for the bitwise shift binary expression operations
`;
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kAllScalarsAndVectors,
+ numElementsOf,
+ scalarTypeOf,
+} from '../../../../util/conversion.js';
import { ShaderValidationTest } from '../../shader_validation_test.js';
export const g = makeTestGroup(ShaderValidationTest);
@@ -21,6 +28,137 @@ function vectorize(v: string, size: number | undefined): string {
return v;
}
+// A list of scalar and vector types.
+const kScalarAndVectorTypes = objectsToRecord(kAllScalarsAndVectors);
+
+g.test('scalar_vector')
+ .desc(
+ `
+ Validates that scalar and vector expressions are only accepted when the LHS is an integer and the RHS is abstract or unsigned.
+ `
+ )
+ .params(u =>
+ u
+ .combine('lhs', keysOf(kScalarAndVectorTypes))
+ .combine(
+ 'rhs',
+ // Skip vec3 and vec4 on the RHS to keep the number of subcases down.
+ keysOf(kScalarAndVectorTypes).filter(
+ value => !(value.startsWith('vec3') || value.startsWith('vec4'))
+ )
+ )
+ .beginSubcases()
+ .combine('op', ['<<', '>>'])
+ )
+ .beforeAllSubcases(t => {
+ if (
+ scalarTypeOf(kScalarAndVectorTypes[t.params.lhs]) === Type.f16 ||
+ scalarTypeOf(kScalarAndVectorTypes[t.params.rhs]) === Type.f16
+ ) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const lhs = kScalarAndVectorTypes[t.params.lhs];
+ const rhs = kScalarAndVectorTypes[t.params.rhs];
+ const lhsElement = scalarTypeOf(lhs);
+ const rhsElement = scalarTypeOf(rhs);
+ const hasF16 = lhsElement === Type.f16 || rhsElement === Type.f16;
+ const code = `
+${hasF16 ? 'enable f16;' : ''}
+const lhs = ${lhs.create(0).wgsl()};
+const rhs = ${rhs.create(0).wgsl()};
+const foo = lhs ${t.params.op} rhs;
+`;
+
+ // The LHS must be an integer, and the RHS must be an abstract/unsigned integer.
+ // The vector widths must also match.
+ const lhs_valid = [Type.abstractInt, Type.i32, Type.u32].includes(lhsElement);
+ const rhs_valid = [Type.abstractInt, Type.u32].includes(rhsElement);
+ const valid = lhs_valid && rhs_valid && numElementsOf(lhs) === numElementsOf(rhs);
+ t.expectCompileResult(valid, code);
+ });
+
+interface InvalidTypeConfig {
+ // An expression that produces a value of the target type.
+ expr: string;
+ // A function that converts an expression of the target type into a valid u32 operand.
+ control: (x: string) => string;
+}
+const kInvalidTypes: Record<string, InvalidTypeConfig> = {
+ mat2x2f: {
+ expr: 'm',
+ control: e => `u32(${e}[0][0])`,
+ },
+
+ array: {
+ expr: 'arr',
+ control: e => `${e}[0]`,
+ },
+
+ ptr: {
+ expr: '(&u)',
+ control: e => `*${e}`,
+ },
+
+ atomic: {
+ expr: 'a',
+ control: e => `atomicLoad(&${e})`,
+ },
+
+ texture: {
+ expr: 't',
+ control: e => `u32(textureLoad(${e}, vec2(), 0).x)`,
+ },
+
+ sampler: {
+ expr: 's',
+ control: e => `u32(textureSampleLevel(t, ${e}, vec2(), 0).x)`,
+ },
+
+ struct: {
+ expr: 'str',
+ control: e => `${e}.u`,
+ },
+};
+
+g.test('invalid_types')
+ .desc(
+ `
+ Validates that expressions are never accepted for non-scalar and non-vector types.
+ `
+ )
+ .params(u =>
+ u
+ .combine('op', ['<<', '>>'])
+ .combine('type', keysOf(kInvalidTypes))
+ .combine('control', [true, false])
+ .beginSubcases()
+ )
+ .fn(t => {
+ const type = kInvalidTypes[t.params.type];
+ const expr = t.params.control ? type.control(type.expr) : type.expr;
+ const code = `
+@group(0) @binding(0) var t : texture_2d<f32>;
+@group(0) @binding(1) var s : sampler;
+@group(0) @binding(2) var<storage, read_write> a : atomic<u32>;
+
+struct S { u : u32 }
+
+var<private> u : u32;
+var<private> m : mat2x2f;
+var<private> arr : array<u32, 4>;
+var<private> str : S;
+
+@compute @workgroup_size(1)
+fn main() {
+ let foo = ${expr} ${t.params.op} ${expr};
+}
+`;
+
+ t.expectCompileResult(t.params.control, code);
+ });
+
const kLeftShiftCases = [
// rhs >= bitwidth fails
{ lhs: `0u`, rhs: `31u`, pass: true },
@@ -80,28 +218,6 @@ fn main() {
t.expectCompileResult(t.params.case.pass, code);
});
-g.test('shift_left_vec_size_mismatch')
- .desc('Tests validation of binary left shift of vectors with mismatched sizes')
- .params(u =>
- u
- .combine('vectorize_lhs', [2, 3, 4]) //
- .combine('vectorize_rhs', [2, 3, 4])
- )
- .fn(t => {
- const lhs = `1`;
- const rhs = `1`;
- const lhs_vec_size = t.params.vectorize_lhs;
- const rhs_vec_size = t.params.vectorize_rhs;
- const code = `
-@compute @workgroup_size(1)
-fn main() {
- const r = ${vectorize(lhs, lhs_vec_size)} << ${vectorize(rhs, rhs_vec_size)};
-}
- `;
- const pass = lhs_vec_size === rhs_vec_size;
- t.expectCompileResult(pass, code);
- });
-
const kRightShiftCases = [
// rhs >= bitwidth fails
{ lhs: `0u`, rhs: `31u`, pass: true },
@@ -142,25 +258,3 @@ fn main() {
`;
t.expectCompileResult(t.params.case.pass, code);
});
-
-g.test('shift_right_vec_size_mismatch')
- .desc('Tests validation of binary right shift of vectors with mismatched sizes')
- .params(u =>
- u
- .combine('vectorize_lhs', [2, 3, 4]) //
- .combine('vectorize_rhs', [2, 3, 4])
- )
- .fn(t => {
- const lhs = `1`;
- const rhs = `1`;
- const lhs_vec_size = t.params.vectorize_lhs;
- const rhs_vec_size = t.params.vectorize_rhs;
- const code = `
-@compute @workgroup_size(1)
-fn main() {
- const r = ${vectorize(lhs, lhs_vec_size)} >> ${vectorize(rhs, rhs_vec_size)};
-}
- `;
- const pass = lhs_vec_size === rhs_vec_size;
- t.expectCompileResult(pass, code);
- });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/comparison.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/comparison.spec.ts
new file mode 100644
index 0000000000..6115874a10
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/comparison.spec.ts
@@ -0,0 +1,186 @@
+export const description = `
+Validation tests for comparison expressions.
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../common/util/data_tables.js';
+import {
+ isFloatType,
+ kAllScalarsAndVectors,
+ ScalarType,
+ scalarTypeOf,
+ Type,
+ VectorType,
+} from '../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+// A list of scalar and vector types.
+const kScalarAndVectorTypes = objectsToRecord(kAllScalarsAndVectors);
+
+// A list of comparison operators and a flag for whether they support boolean values or not.
+const kComparisonOperators = {
+ eq: { op: '==', supportsBool: true },
+ ne: { op: '!=', supportsBool: true },
+ gt: { op: '>', supportsBool: false },
+ ge: { op: '>=', supportsBool: false },
+ lt: { op: '<', supportsBool: false },
+ le: { op: '<=', supportsBool: false },
+};
+
+g.test('scalar_vector')
+ .desc(
+ `
+ Validates that scalar and vector comparison expressions are only accepted for compatible types.
+ `
+ )
+ .params(u =>
+ u
+ .combine('lhs', keysOf(kScalarAndVectorTypes))
+ .combine(
+ 'rhs',
+ // Skip vec3 and vec4 on the RHS to keep the number of subcases down.
+ keysOf(kScalarAndVectorTypes).filter(
+ value => !(value.startsWith('vec3') || value.startsWith('vec4'))
+ )
+ )
+ .beginSubcases()
+ .combine('op', keysOf(kComparisonOperators))
+ )
+ .beforeAllSubcases(t => {
+ if (
+ scalarTypeOf(kScalarAndVectorTypes[t.params.lhs]) === Type.f16 ||
+ scalarTypeOf(kScalarAndVectorTypes[t.params.rhs]) === Type.f16
+ ) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const lhs = kScalarAndVectorTypes[t.params.lhs];
+ const rhs = kScalarAndVectorTypes[t.params.rhs];
+ const lhsElement = scalarTypeOf(lhs);
+ const rhsElement = scalarTypeOf(rhs);
+ const hasF16 = lhsElement === Type.f16 || rhsElement === Type.f16;
+ const code = `
+${hasF16 ? 'enable f16;' : ''}
+const lhs = ${lhs.create(0).wgsl()};
+const rhs = ${rhs.create(0).wgsl()};
+const foo = lhs ${kComparisonOperators[t.params.op].op} rhs;
+`;
+
+ let valid = false;
+
+ // Determine if the element types are comparable.
+ let elementIsCompatible = false;
+ if (lhsElement === Type.abstractInt) {
+ // Abstract integers are comparable to any other numeric type.
+ elementIsCompatible = rhsElement !== Type.bool;
+ } else if (rhsElement === Type.abstractInt) {
+ // Abstract integers are comparable to any other numeric type.
+ elementIsCompatible = lhsElement !== Type.bool;
+ } else if (lhsElement === Type.abstractFloat) {
+ // Abstract floats are comparable to any other float type.
+ elementIsCompatible = isFloatType(rhsElement);
+ } else if (rhsElement === Type.abstractFloat) {
+ // Abstract floats are comparable to any other float type.
+ elementIsCompatible = isFloatType(lhsElement);
+ } else {
+ // Non-abstract types are only comparable to values with the exact same type.
+ elementIsCompatible = lhsElement === rhsElement;
+ }
+
+ // Determine if the full type is comparable.
+ if (lhs instanceof ScalarType && rhs instanceof ScalarType) {
+ valid = elementIsCompatible;
+ } else if (lhs instanceof VectorType && rhs instanceof VectorType) {
+ // Vectors are only comparable if the vector widths match.
+ valid = lhs.width === rhs.width && elementIsCompatible;
+ }
+
+ if (lhsElement === Type.bool) {
+ valid &&= kComparisonOperators[t.params.op].supportsBool;
+ }
+
+ t.expectCompileResult(valid, code);
+ });
+
+interface InvalidTypeConfig {
+ // An expression that produces a value of the target type.
+ expr: string;
+ // A function that converts an expression of the target type into a valid comparison operand.
+ control: (x: string) => string;
+}
+const kInvalidTypes: Record<string, InvalidTypeConfig> = {
+ mat2x2f: {
+ expr: 'm',
+ control: e => `${e}[0]`,
+ },
+
+ array: {
+ expr: 'arr',
+ control: e => `${e}[0]`,
+ },
+
+ ptr: {
+ expr: '(&u)',
+ control: e => `*${e}`,
+ },
+
+ atomic: {
+ expr: 'a',
+ control: e => `atomicLoad(&${e})`,
+ },
+
+ texture: {
+ expr: 't',
+ control: e => `textureLoad(${e}, vec2(), 0)`,
+ },
+
+ sampler: {
+ expr: 's',
+ control: e => `textureSampleLevel(t, ${e}, vec2(), 0)`,
+ },
+
+ struct: {
+ expr: 'str',
+ control: e => `${e}.u`,
+ },
+};
+
+g.test('invalid_types')
+ .desc(
+ `
+ Validates that comparison expressions are never accepted for non-scalar and non-vector types.
+ `
+ )
+ .params(u =>
+ u
+ .combine('op', keysOf(kComparisonOperators))
+ .combine('type', keysOf(kInvalidTypes))
+ .combine('control', [true, false])
+ .beginSubcases()
+ )
+ .fn(t => {
+ const type = kInvalidTypes[t.params.type];
+ const expr = t.params.control ? type.control(type.expr) : type.expr;
+ const code = `
+@group(0) @binding(0) var t : texture_2d<f32>;
+@group(0) @binding(1) var s : sampler;
+@group(0) @binding(2) var<storage, read_write> a : atomic<i32>;
+
+struct S { u : u32 }
+
+var<private> u : u32;
+var<private> m : mat2x2f;
+var<private> arr : array<i32, 4>;
+var<private> str : S;
+
+@compute @workgroup_size(1)
+fn main() {
+ let foo = ${expr} ${kComparisonOperators[t.params.op].op} ${expr};
+}
+`;
+
+ t.expectCompileResult(t.params.control, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/div_rem.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/div_rem.spec.ts
new file mode 100644
index 0000000000..0f07abe3ae
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/div_rem.spec.ts
@@ -0,0 +1,279 @@
+export const description = `
+Validation tests for division and remainder expressions.
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../common/util/data_tables.js';
+import { assert } from '../../../../../common/util/util.js';
+import { kBit } from '../../../../util/constants.js';
+import {
+ ScalarType,
+ Type,
+ Value,
+ VectorType,
+ kAllScalarsAndVectors,
+ kConcreteNumericScalarsAndVectors,
+ kConvertableToFloatScalar,
+ scalarTypeOf,
+} from '../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../shader_validation_test.js';
+import {
+ kConstantAndOverrideStages,
+ validateConstOrOverrideBinaryOpEval,
+} from '../call/builtin/const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+// A list of operators tested in this file.
+const kOperators = {
+ div: { op: '/' },
+ rem: { op: '%' },
+} as const;
+
+// A list of scalar and vector types.
+const kScalarAndVectorTypes = objectsToRecord(kAllScalarsAndVectors);
+const kConcreteNumericScalarAndVectorTypes = objectsToRecord(kConcreteNumericScalarsAndVectors);
+
+g.test('scalar_vector')
+ .desc(
+ `
+ Validates that scalar and vector expressions are only accepted for compatible numeric types.
+ `
+ )
+ .params(u =>
+ u
+ .combine('lhs', keysOf(kScalarAndVectorTypes))
+ .combine(
+ 'rhs',
+ // Skip vec3 and vec4 on the RHS to keep the number of subcases down.
+ // vec3 + vec3 and vec4 + vec4 is tested in execution tests.
+ keysOf(kScalarAndVectorTypes).filter(
+ value => !(value.startsWith('vec3') || value.startsWith('vec4'))
+ )
+ )
+ .beginSubcases()
+ .combine('op', keysOf(kOperators))
+ )
+ .beforeAllSubcases(t => {
+ if (
+ scalarTypeOf(kScalarAndVectorTypes[t.params.lhs]) === Type.f16 ||
+ scalarTypeOf(kScalarAndVectorTypes[t.params.rhs]) === Type.f16
+ ) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const op = kOperators[t.params.op];
+ const lhs = kScalarAndVectorTypes[t.params.lhs];
+ const rhs = kScalarAndVectorTypes[t.params.rhs];
+ const lhsElement = scalarTypeOf(lhs);
+ const rhsElement = scalarTypeOf(rhs);
+ const hasF16 = lhsElement === Type.f16 || rhsElement === Type.f16;
+ const code = `
+${hasF16 ? 'enable f16;' : ''}
+const lhs = ${lhs.create(1).wgsl()};
+const rhs = ${rhs.create(1).wgsl()};
+const foo = lhs ${op.op} rhs;
+`;
+
+ let elementsCompatible = lhsElement === rhsElement;
+ const elementTypes = [lhsElement, rhsElement];
+
+ // Booleans are not allowed for arithmetic expressions.
+ if (elementTypes.includes(Type.bool)) {
+ elementsCompatible = false;
+
+ // AbstractInt is allowed with everything but booleans which are already checked above.
+ } else if (elementTypes.includes(Type.abstractInt)) {
+ elementsCompatible = true;
+
+ // AbstractFloat is allowed with AbstractInt (checked above) or float types
+ } else if (elementTypes.includes(Type.abstractFloat)) {
+ elementsCompatible = elementTypes.every(e => kConvertableToFloatScalar.includes(e));
+ }
+
+ // Determine if the full type is compatible. The only invalid case is mixed vector sizes.
+ let valid = elementsCompatible;
+ if (lhs instanceof VectorType && rhs instanceof VectorType) {
+ valid = valid && lhs.width === rhs.width;
+ }
+
+ t.expectCompileResult(valid, code);
+ });
+
+g.test('scalar_vector_out_of_range')
+ .desc(
+ `
+ Checks that constant or override evaluation of div/rem operations on scalar/vectors that produce out of division by 0 or out of range values cause validation errors.
+ - Checks for all concrete numeric scalar and vector types, including scalar * vector and vector * scalar.
+ - Checks for all vector elements that could cause the out of range to happen.
+ - Checks for valid small cases and 0, also the minimum i32.
+ `
+ )
+ .params(u =>
+ u
+ .combine('op', keysOf(kOperators))
+ .combine('lhs', keysOf(kConcreteNumericScalarAndVectorTypes))
+ .expand('rhs', p => {
+ if (kScalarAndVectorTypes[p.lhs] instanceof VectorType) {
+ return [p.lhs, scalarTypeOf(kScalarAndVectorTypes[p.lhs]).toString()];
+ }
+ return [p.lhs];
+ })
+ .beginSubcases()
+ .expand('swap', p => {
+ if (p.lhs === p.rhs) {
+ return [false];
+ }
+ return [false, true];
+ })
+ .combine('nonOneIndex', [0, 1, 2, 3])
+ .filter(p => {
+ const lType = kScalarAndVectorTypes[p.lhs];
+ if (lType instanceof VectorType) {
+ return lType.width > p.nonOneIndex;
+ }
+ return p.nonOneIndex === 0;
+ })
+ .expandWithParams(p => {
+ const cases = [
+ { leftValue: 42, rightValue: 0, error: true, leftRuntime: false },
+ { leftValue: 42, rightValue: 0, error: true, leftRuntime: true },
+ { leftValue: 0, rightValue: 0, error: true, leftRuntime: true },
+ { leftValue: 0, rightValue: 42, error: false, leftRuntime: false },
+ ];
+ if (p.lhs === 'i32') {
+ cases.push({
+ leftValue: -kBit.i32.negative.min,
+ rightValue: -1,
+ error: true,
+ leftRuntime: false,
+ });
+ cases.push({
+ leftValue: -kBit.i32.negative.min + 1,
+ rightValue: -1,
+ error: false,
+ leftRuntime: false,
+ });
+ }
+ return cases;
+ })
+ .combine('stage', kConstantAndOverrideStages)
+ )
+ .beforeAllSubcases(t => {
+ if (
+ scalarTypeOf(kScalarAndVectorTypes[t.params.lhs]) === Type.f16 ||
+ scalarTypeOf(kScalarAndVectorTypes[t.params.rhs]) === Type.f16
+ ) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const { op, leftValue, rightValue, error, leftRuntime, nonOneIndex, swap } = t.params;
+ let { lhs, rhs } = t.params;
+
+ // Handle the swapping of LHS and RHS to test all cases of scalar * vector.
+ if (swap) {
+ [rhs, lhs] = [lhs, rhs];
+ }
+
+ // Creates either a scalar with the value, or a vector with the value only at a specific index.
+ const create = (type: ScalarType | VectorType, index: number, value: number): Value => {
+ if (type instanceof ScalarType) {
+ return type.create(value);
+ } else {
+ assert(type instanceof VectorType);
+ const values = new Array(type.width);
+ values.fill(1);
+ values[index] = value;
+ return type.create(values);
+ }
+ };
+
+ // Check if there is overflow
+ validateConstOrOverrideBinaryOpEval(
+ t,
+ kOperators[op].op,
+ !error,
+ leftRuntime ? 'runtime' : t.params.stage,
+ create(kScalarAndVectorTypes[lhs], nonOneIndex, leftValue),
+ t.params.stage,
+ create(kScalarAndVectorTypes[rhs], nonOneIndex, rightValue)
+ );
+ });
+
+interface InvalidTypeConfig {
+ // An expression that produces a value of the target type.
+ expr: string;
+ // A function that converts an expression of the target type into a valid integer operand.
+ control: (x: string) => string;
+}
+const kInvalidTypes: Record<string, InvalidTypeConfig> = {
+ array: {
+ expr: 'arr',
+ control: e => `${e}[0]`,
+ },
+
+ ptr: {
+ expr: '(&u)',
+ control: e => `*${e}`,
+ },
+
+ atomic: {
+ expr: 'a',
+ control: e => `atomicLoad(&${e})`,
+ },
+
+ texture: {
+ expr: 't',
+ control: e => `i32(textureLoad(${e}, vec2(), 0).x)`,
+ },
+
+ sampler: {
+ expr: 's',
+ control: e => `i32(textureSampleLevel(t, ${e}, vec2(), 0).x)`,
+ },
+
+ struct: {
+ expr: 'str',
+ control: e => `${e}.u`,
+ },
+};
+
+g.test('invalid_type_with_itself')
+ .desc(
+ `
+ Validates that expressions are never accepted for non-scalar, non-vector, and non-matrix types.
+ `
+ )
+ .params(u =>
+ u
+ .combine('op', keysOf(kOperators))
+ .combine('type', keysOf(kInvalidTypes))
+ .combine('control', [true, false])
+ .beginSubcases()
+ )
+ .fn(t => {
+ const op = kOperators[t.params.op];
+ const type = kInvalidTypes[t.params.type];
+ const expr = t.params.control ? type.control(type.expr) : type.expr;
+ const code = `
+@group(0) @binding(0) var t : texture_2d<f32>;
+@group(0) @binding(1) var s : sampler;
+@group(0) @binding(2) var<storage, read_write> a : atomic<i32>;
+
+struct S { u : u32 }
+
+var<private> u : u32;
+var<private> m : mat2x2f;
+var<private> arr : array<i32, 4>;
+var<private> str : S;
+
+@compute @workgroup_size(1)
+fn main() {
+ let foo = ${expr} ${op.op} ${expr};
+}
+`;
+
+ t.expectCompileResult(t.params.control, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/abs.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/abs.spec.ts
index 32ecb0cbc8..2b0375d783 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/abs.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/abs.spec.ts
@@ -6,9 +6,9 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- elementType,
- kAllFloatAndIntegerScalarsAndVectors,
+ Type,
+ kAllNumericScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -21,7 +21,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatAndIntegerScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kAllNumericScalarsAndVectors);
g.test('values')
.desc(
@@ -38,7 +38,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() never
.expand('value', u => fullRangeForType(kValuesTypes[u.type]))
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
@@ -52,3 +52,107 @@ Validates that constant evaluation and override evaluation of ${builtin}() never
t.params.stage
);
});
+
+const kTests = {
+ valid: {
+ src: `_ = abs(1);`,
+ pass: true,
+ },
+ alias: {
+ src: `_ = abs(i32_alias(1));`,
+ pass: true,
+ },
+
+ bool: {
+ src: `_ = abs(false);`,
+ pass: false,
+ },
+ vec_bool: {
+ src: `_ = abs(vec2<bool>(false, true));`,
+ pass: false,
+ },
+ matrix: {
+ src: `_ = abs(mat2x2(1, 1, 1, 1));`,
+ pass: false,
+ },
+ atomic: {
+ src: ` _ = abs(a);`,
+ pass: false,
+ },
+ array: {
+ src: `var a: array<u32, 5>;
+ _ = abs(a);`,
+ pass: false,
+ },
+ array_runtime: {
+ src: `_ = abs(k.arry);`,
+ pass: false,
+ },
+ struct: {
+ src: `var a: A;
+ _ = abs(a);`,
+ pass: false,
+ },
+ enumerant: {
+ src: `_ = abs(read_write);`,
+ pass: false,
+ },
+ ptr: {
+ src: `var<function> a = 1u;
+ let p: ptr<function, u32> = &a;
+ _ = abs(p);`,
+ pass: false,
+ },
+ ptr_deref: {
+ src: `var<function> a = 1u;
+ let p: ptr<function, u32> = &a;
+ _ = abs(*p);`,
+ pass: true,
+ },
+ sampler: {
+ src: `_ = abs(s);`,
+ pass: false,
+ },
+ texture: {
+ src: `_ = abs(t);`,
+ pass: false,
+ },
+ no_params: {
+ src: `_ = abs();`,
+ pass: false,
+ },
+ too_many_params: {
+ src: `_ = abs(1, 2);`,
+ pass: false,
+ },
+};
+
+g.test('parameters')
+ .desc(`Test that ${builtin} is validated correctly.`)
+ .params(u => u.combine('test', keysOf(kTests)))
+ .fn(t => {
+ const src = kTests[t.params.test].src;
+ const code = `
+alias i32_alias = i32;
+
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: texture_2d<f32>;
+
+var<workgroup> a: atomic<u32>;
+
+struct A {
+ i: u32,
+}
+struct B {
+ arry: array<u32>,
+}
+@group(0) @binding(3) var<storage> k: B;
+
+
+@vertex
+fn main() -> @builtin(position) vec4<f32> {
+ ${src}
+ return vec4<f32>(.4, .2, .3, .1);
+}`;
+ t.expectCompileResult(kTests[t.params.test].pass, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/acos.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/acos.spec.ts
index 82171ed4b1..d76bb470b0 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/acos.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/acos.spec.ts
@@ -6,18 +6,18 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
+import { absBigInt } from '../../../../../util/math.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
import {
fullRangeForType,
kConstantAndOverrideStages,
- kMinusTwoToTwo,
+ minusTwoToTwoRangeForType,
stageSupportsType,
unique,
validateConstOrOverrideBuiltinEval,
@@ -25,7 +25,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -39,15 +39,23 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
.combine('type', keysOf(kValuesTypes))
.filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
.beginSubcases()
- .expand('value', u => unique(kMinusTwoToTwo, fullRangeForType(kValuesTypes[u.type])))
+ .expand('value', u =>
+ unique(
+ minusTwoToTwoRangeForType(kValuesTypes[u.type]),
+ fullRangeForType(kValuesTypes[u.type])
+ )
+ )
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
.fn(t => {
- const expectedResult = Math.abs(t.params.value) <= 1;
+ const expectedResult =
+ typeof t.params.value === 'bigint'
+ ? absBigInt(t.params.value) <= 1n
+ : Math.abs(t.params.value) <= 1;
validateConstOrOverrideBuiltinEval(
t,
builtin,
@@ -57,7 +65,8 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+// f32 is included here to confirm that validation is failing due to a type issue and not something else.
+const kIntegerArgumentTypes = objectsToRecord([Type.f32, ...kConcreteIntegerScalarsAndVectors]);
g.test('integer_argument')
.desc(
@@ -71,8 +80,137 @@ Validates that scalar and vector integer arguments are rejected by ${builtin}()
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ type === TypeF32,
+ /* expectedResult */ type === Type.f32,
[type.create(0)],
'constant'
);
});
+
+const kTests = {
+ valid: {
+ src: `_ = acos(1);`,
+ pass: true,
+ },
+ alias: {
+ src: `_ = acos(f32_alias(1));`,
+ pass: true,
+ },
+
+ bool: {
+ src: `_ = acos(false);`,
+ pass: false,
+ },
+ i32: {
+ src: `_ = acos(1i);`,
+ pass: false,
+ },
+ u32: {
+ src: `_ = acos(1u);`,
+ pass: false,
+ },
+ vec_bool: {
+ src: `_ = acos(vec2<bool>(false, true));`,
+ pass: false,
+ },
+ vec_i32: {
+ src: `_ = acos(vec2<i32>(1, 1));`,
+ pass: false,
+ },
+ vec_u32: {
+ src: `_ = acos(vec2<u32>(1, 1));`,
+ pass: false,
+ },
+ matrix: {
+ src: `_ = acos(mat2x2(1, 1, 1, 1));`,
+ pass: false,
+ },
+ atomic: {
+ src: ` _ = acos(a);`,
+ pass: false,
+ },
+ array: {
+ src: `var a: array<u32, 5>;
+ _ = acos(a);`,
+ pass: false,
+ },
+ array_runtime: {
+ src: `_ = acos(k.arry);`,
+ pass: false,
+ },
+ struct: {
+ src: `var a: A;
+ _ = acos(a);`,
+ pass: false,
+ },
+ enumerant: {
+ src: `_ = acos(read_write);`,
+ pass: false,
+ },
+ ptr: {
+ src: `var<function> a = 1f;
+ let p: ptr<function, f32> = &a;
+ _ = acos(p);`,
+ pass: false,
+ },
+ ptr_deref: {
+ src: `var<function> a = 1f;
+ let p: ptr<function, f32> = &a;
+ _ = acos(*p);`,
+ pass: true,
+ },
+ sampler: {
+ src: `_ = acos(s);`,
+ pass: false,
+ },
+ texture: {
+ src: `_ = acos(t);`,
+ pass: false,
+ },
+ no_params: {
+ src: `_ = acos();`,
+ pass: false,
+ },
+ too_many_params: {
+ src: `_ = acos(1, 2);`,
+ pass: false,
+ },
+
+ greater_then_one: {
+ src: `_ = acos(1.1f);`,
+ pass: false,
+ },
+ less_then_negative_one: {
+ src: `_ = acos(-1.1f);`,
+ pass: false,
+ },
+};
+
+g.test('parameters')
+ .desc(`Test that ${builtin} is validated correctly.`)
+ .params(u => u.combine('test', keysOf(kTests)))
+ .fn(t => {
+ const src = kTests[t.params.test].src;
+ const code = `
+alias f32_alias = f32;
+
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: texture_2d<f32>;
+
+var<workgroup> a: atomic<u32>;
+
+struct A {
+ i: u32,
+}
+struct B {
+ arry: array<u32>,
+}
+@group(0) @binding(3) var<storage> k: B;
+
+
+@vertex
+fn main() -> @builtin(position) vec4<f32> {
+ ${src}
+ return vec4<f32>(.4, .2, .3, .1);
+}`;
+ t.expectCompileResult(kTests[t.params.test].pass, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/acosh.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/acosh.spec.ts
index a7ab8d83f9..82da85fdde 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/acosh.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/acosh.spec.ts
@@ -6,11 +6,10 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { isRepresentable } from '../../../../../util/floating_point.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -18,7 +17,7 @@ import { ShaderValidationTest } from '../../../shader_validation_test.js';
import {
fullRangeForType,
kConstantAndOverrideStages,
- kMinusTwoToTwo,
+ minusTwoToTwoRangeForType,
stageSupportsType,
unique,
validateConstOrOverrideBuiltinEval,
@@ -26,7 +25,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -40,16 +39,25 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
.combine('type', keysOf(kValuesTypes))
.filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
.beginSubcases()
- .expand('value', u => unique(fullRangeForType(kValuesTypes[u.type]), kMinusTwoToTwo))
+ .expand('value', u =>
+ unique(
+ minusTwoToTwoRangeForType(kValuesTypes[u.type]),
+ fullRangeForType(kValuesTypes[u.type])
+ )
+ )
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
.fn(t => {
const type = kValuesTypes[t.params.type];
- const expectedResult = isRepresentable(Math.acosh(t.params.value), elementType(type));
+ const expectedResult = isRepresentable(
+ Math.acosh(Number(t.params.value)),
+ // AbstractInt is converted to AbstractFloat before calling into the builtin
+ scalarTypeOf(type).kind === 'abstract-int' ? Type.abstractFloat : scalarTypeOf(type)
+ );
validateConstOrOverrideBuiltinEval(
t,
builtin,
@@ -59,7 +67,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kIntegerArgumentTypes = objectsToRecord(kConcreteIntegerScalarsAndVectors);
g.test('integer_argument')
.desc(
@@ -73,8 +81,132 @@ Validates that scalar and vector integer arguments are rejected by ${builtin}()
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ type === TypeF32,
- [type.create(1)],
+ /* expectedResult */ type === Type.f32,
+ [type.create(type === Type.abstractInt ? 1n : 1)],
'constant'
);
});
+
+const kTests = {
+ valid: {
+ src: `_ = acosh(1);`,
+ pass: true,
+ },
+ alias: {
+ src: `_ = acosh(f32_alias(1));`,
+ pass: true,
+ },
+
+ bool: {
+ src: `_ = acosh(false);`,
+ pass: false,
+ },
+ i32: {
+ src: `_ = acosh(1i);`,
+ pass: false,
+ },
+ u32: {
+ src: `_ = acosh(1u);`,
+ pass: false,
+ },
+ vec_bool: {
+ src: `_ = acosh(vec2<bool>(false, true));`,
+ pass: false,
+ },
+ vec_i32: {
+ src: `_ = acosh(vec2<i32>(1, 1));`,
+ pass: false,
+ },
+ vec_u32: {
+ src: `_ = acosh(vec2<u32>(1, 1));`,
+ pass: false,
+ },
+ matrix: {
+ src: `_ = acosh(mat2x2(1, 1, 1, 1));`,
+ pass: false,
+ },
+ atomic: {
+ src: ` _ = acosh(a);`,
+ pass: false,
+ },
+ array: {
+ src: `var a: array<u32, 5>;
+ _ = acosh(a);`,
+ pass: false,
+ },
+ array_runtime: {
+ src: `_ = acosh(k.arry);`,
+ pass: false,
+ },
+ struct: {
+ src: `var a: A;
+ _ = acosh(a);`,
+ pass: false,
+ },
+ enumerant: {
+ src: `_ = acosh(read_write);`,
+ pass: false,
+ },
+ ptr: {
+ src: `var<function> a = 1f;
+ let p: ptr<function, f32> = &a;
+ _ = acosh(p);`,
+ pass: false,
+ },
+ ptr_deref: {
+ src: `var<function> a = 1f;
+ let p: ptr<function, f32> = &a;
+ _ = acosh(*p);`,
+ pass: true,
+ },
+ sampler: {
+ src: `_ = acosh(s);`,
+ pass: false,
+ },
+ texture: {
+ src: `_ = acosh(t);`,
+ pass: false,
+ },
+ no_params: {
+ src: `_ = acosh();`,
+ pass: false,
+ },
+ too_many_params: {
+ src: `_ = acosh(1, 2);`,
+ pass: false,
+ },
+
+ less_then_one: {
+ src: `_ = acosh(.9f);`,
+ pass: false,
+ },
+};
+
+g.test('parameters')
+ .desc(`Test that ${builtin} is validated correctly.`)
+ .params(u => u.combine('test', keysOf(kTests)))
+ .fn(t => {
+ const src = kTests[t.params.test].src;
+ const code = `
+alias f32_alias = f32;
+
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: texture_2d<f32>;
+
+var<workgroup> a: atomic<u32>;
+
+struct A {
+ i: u32,
+}
+struct B {
+ arry: array<u32>,
+}
+@group(0) @binding(3) var<storage> k: B;
+
+@vertex
+fn main() -> @builtin(position) vec4<f32> {
+ ${src}
+ return vec4<f32>(.4, .2, .3, .1);
+}`;
+ t.expectCompileResult(kTests[t.params.test].pass, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/all.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/all.spec.ts
new file mode 100644
index 0000000000..f52d24e0cc
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/all.spec.ts
@@ -0,0 +1,191 @@
+const builtin = 'all';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import { Type, elementTypeOf, kAllScalarsAndVectors } from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import { validateConstOrOverrideBuiltinEval } from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kArgumentTypes = objectsToRecord(kAllScalarsAndVectors);
+
+g.test('argument_types')
+ .desc(
+ `
+Validates that scalar and vector arguments are rejected by ${builtin}() if not bool or vecN<bool>
+`
+ )
+ .params(u => u.combine('type', keysOf(kArgumentTypes)))
+ .fn(t => {
+ const type = kArgumentTypes[t.params.type];
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ /* expectedResult */ elementTypeOf(type) === Type.bool,
+ [type.create(0)],
+ 'constant',
+ /* returnType */ Type.bool
+ );
+ });
+
+const kTests = {
+ valid: {
+ src: `_ = ${builtin}(true);`,
+ pass: true,
+ },
+ alias: {
+ src: `_ = ${builtin}(bool_alias(true));`,
+ pass: true,
+ },
+ bool: {
+ src: `_ = ${builtin}(false);`,
+ pass: true,
+ },
+ i32: {
+ src: `_ = ${builtin}(1i);`,
+ pass: false,
+ },
+ u32: {
+ src: `_ = ${builtin}(1u);`,
+ pass: false,
+ },
+ f32: {
+ src: `_ = ${builtin}(1.0f);`,
+ pass: false,
+ },
+ f16: {
+ src: `_ = ${builtin}(1.0h);`,
+ pass: false,
+ },
+ vec_bool: {
+ src: `_ = ${builtin}(vec2<bool>(false, true));`,
+ pass: true,
+ },
+ vec2_bool_implicit: {
+ src: `_ = ${builtin}(vec2(false, true));`,
+ pass: true,
+ },
+ vec3_bool_implicit: {
+ src: `_ = ${builtin}(vec3(true));`,
+ pass: true,
+ },
+ vec_i32: {
+ src: `_ = ${builtin}(vec2<i32>(1, 1));`,
+ pass: false,
+ },
+ vec_u32: {
+ src: `_ = ${builtin}(vec2<u32>(1, 1));`,
+ pass: false,
+ },
+ vec_f32: {
+ src: `_ = ${builtin}(vec2<f32>(1, 1));`,
+ pass: false,
+ },
+ vec_f16: {
+ src: `_ = ${builtin}(vec2<f16>(1, 1));`,
+ pass: false,
+ },
+ matrix: {
+ src: `_ = ${builtin}(mat2x2(1, 1, 1, 1));`,
+ pass: false,
+ },
+ atomic: {
+ src: ` _ = ${builtin}(a);`,
+ pass: false,
+ },
+ array: {
+ src: `var a: array<bool, 5>;
+ _ = ${builtin}(a);`,
+ pass: false,
+ },
+ array_runtime: {
+ src: `_ = ${builtin}(k.arry);`,
+ pass: false,
+ },
+ struct: {
+ src: `var a: A;
+ _ = ${builtin}(a);`,
+ pass: false,
+ },
+ enumerant: {
+ src: `_ = ${builtin}(read_write);`,
+ pass: false,
+ },
+ ptr: {
+ src: `var<function> a = true;
+ let p: ptr<function, bool> = &a;
+ _ = ${builtin}(p);`,
+ pass: false,
+ },
+ ptr_deref: {
+ src: `var<function> a = true;
+ let p: ptr<function, bool> = &a;
+ _ = ${builtin}(*p);`,
+ pass: true,
+ },
+ sampler: {
+ src: `_ = ${builtin}(s);`,
+ pass: false,
+ },
+ texture: {
+ src: `_ = ${builtin}(t);`,
+ pass: false,
+ },
+ no_args: {
+ src: `_ = ${builtin}();`,
+ pass: false,
+ },
+ too_many_args: {
+ src: `_ = ${builtin}(true, true);`,
+ pass: false,
+ },
+};
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}(true); }`);
+ });
+
+g.test('arguments')
+ .desc(`Test that ${builtin} is validated correctly.`)
+ .params(u => u.combine('test', keysOf(kTests)))
+ .beforeAllSubcases(t => {
+ if (t.params.test.includes('f16')) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const src = kTests[t.params.test].src;
+ const enables = t.params.test.includes('f16') ? 'enable f16;' : '';
+ const code = `
+ ${enables}
+ alias bool_alias = bool;
+
+ @group(0) @binding(0) var s: sampler;
+ @group(0) @binding(1) var t: texture_2d<f32>;
+
+ var<workgroup> a: atomic<u32>;
+
+ struct A {
+ i: bool,
+ }
+ struct B {
+ arry: array<u32>,
+ }
+ @group(0) @binding(3) var<storage> k: B;
+
+ @vertex
+ fn main() -> @builtin(position) vec4<f32> {
+ ${src}
+ return vec4<f32>(.4, .2, .3, .1);
+ }`;
+ t.expectCompileResult(kTests[t.params.test].pass, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/any.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/any.spec.ts
new file mode 100644
index 0000000000..9f328bbbf3
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/any.spec.ts
@@ -0,0 +1,191 @@
+const builtin = 'any';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import { Type, elementTypeOf, kAllScalarsAndVectors } from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import { validateConstOrOverrideBuiltinEval } from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kArgumentTypes = objectsToRecord(kAllScalarsAndVectors);
+
+g.test('argument_types')
+ .desc(
+ `
+Validates that scalar and vector arguments are rejected by ${builtin}() if not bool or vecN<bool>
+`
+ )
+ .params(u => u.combine('type', keysOf(kArgumentTypes)))
+ .fn(t => {
+ const type = kArgumentTypes[t.params.type];
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ /* expectedResult */ elementTypeOf(type) === Type.bool,
+ [type.create(0)],
+ 'constant',
+ /* returnType */ Type.bool
+ );
+ });
+
+const kTests = {
+ valid: {
+ src: `_ = ${builtin}(true);`,
+ pass: true,
+ },
+ alias: {
+ src: `_ = ${builtin}(bool_alias(true));`,
+ pass: true,
+ },
+ bool: {
+ src: `_ = ${builtin}(false);`,
+ pass: true,
+ },
+ i32: {
+ src: `_ = ${builtin}(1i);`,
+ pass: false,
+ },
+ u32: {
+ src: `_ = ${builtin}(1u);`,
+ pass: false,
+ },
+ f32: {
+ src: `_ = ${builtin}(1.0f);`,
+ pass: false,
+ },
+ f16: {
+ src: `_ = ${builtin}(1.0h);`,
+ pass: false,
+ },
+ vec_bool: {
+ src: `_ = ${builtin}(vec2<bool>(false, true));`,
+ pass: true,
+ },
+ vec2_bool_implicit: {
+ src: `_ = ${builtin}(vec2(false, true));`,
+ pass: true,
+ },
+ vec3_bool_implicit: {
+ src: `_ = ${builtin}(vec3(true));`,
+ pass: true,
+ },
+ vec_i32: {
+ src: `_ = ${builtin}(vec2<i32>(1, 1));`,
+ pass: false,
+ },
+ vec_u32: {
+ src: `_ = ${builtin}(vec2<u32>(1, 1));`,
+ pass: false,
+ },
+ vec_f32: {
+ src: `_ = ${builtin}(vec2<f32>(1, 1));`,
+ pass: false,
+ },
+ vec_f16: {
+ src: `_ = ${builtin}(vec2<f16>(1, 1));`,
+ pass: false,
+ },
+ matrix: {
+ src: `_ = ${builtin}(mat2x2(1, 1, 1, 1));`,
+ pass: false,
+ },
+ atomic: {
+ src: ` _ = ${builtin}(a);`,
+ pass: false,
+ },
+ array: {
+ src: `var a: array<bool, 5>;
+ _ = ${builtin}(a);`,
+ pass: false,
+ },
+ array_runtime: {
+ src: `_ = ${builtin}(k.arry);`,
+ pass: false,
+ },
+ struct: {
+ src: `var a: A;
+ _ = ${builtin}(a);`,
+ pass: false,
+ },
+ enumerant: {
+ src: `_ = ${builtin}(read_write);`,
+ pass: false,
+ },
+ ptr: {
+ src: `var<function> a = true;
+ let p: ptr<function, bool> = &a;
+ _ = ${builtin}(p);`,
+ pass: false,
+ },
+ ptr_deref: {
+ src: `var<function> a = true;
+ let p: ptr<function, bool> = &a;
+ _ = ${builtin}(*p);`,
+ pass: true,
+ },
+ sampler: {
+ src: `_ = ${builtin}(s);`,
+ pass: false,
+ },
+ texture: {
+ src: `_ = ${builtin}(t);`,
+ pass: false,
+ },
+ no_args: {
+ src: `_ = ${builtin}();`,
+ pass: false,
+ },
+ too_many_args: {
+ src: `_ = ${builtin}(true, true);`,
+ pass: false,
+ },
+};
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}(true); }`);
+ });
+
+g.test('arguments')
+ .desc(`Test that ${builtin} is validated correctly.`)
+ .params(u => u.combine('test', keysOf(kTests)))
+ .beforeAllSubcases(t => {
+ if (t.params.test.includes('f16')) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const src = kTests[t.params.test].src;
+ const enables = t.params.test.includes('f16') ? 'enable f16;' : '';
+ const code = `
+ ${enables}
+ alias bool_alias = bool;
+
+ @group(0) @binding(0) var s: sampler;
+ @group(0) @binding(1) var t: texture_2d<f32>;
+
+ var<workgroup> a: atomic<u32>;
+
+ struct A {
+ i: bool,
+ }
+ struct B {
+ arry: array<u32>,
+ }
+ @group(0) @binding(3) var<storage> k: B;
+
+ @vertex
+ fn main() -> @builtin(position) vec4<f32> {
+ ${src}
+ return vec4<f32>(.4, .2, .3, .1);
+ }`;
+ t.expectCompileResult(kTests[t.params.test].pass, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/arrayLength.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/arrayLength.spec.ts
new file mode 100644
index 0000000000..27d8814b14
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/arrayLength.spec.ts
@@ -0,0 +1,109 @@
+export const description = `
+Validation tests for arrayLength builtins.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('bool_type')
+ .specURL('https://www.w3.org/TR/WGSL/#arrayLength-builtin')
+ .desc(
+ `
+arrayLength accepts only runtime-sized arrays
+`
+ )
+ .fn(t => {
+ const code = `
+@compute @workgroup_size(1)
+fn main() {
+ var b = true;
+ _ = arrayLength(&b);
+}`;
+
+ t.expectCompileResult(false, code);
+ });
+
+const atomic_types = ['u32', 'i32'].map(j => `atomic<${j}>`);
+const vec_types = [2, 3, 4]
+ .map(i => ['i32', 'u32', 'f32', 'f16'].map(j => `vec${i}<${j}>`))
+ .reduce((a, c) => a.concat(c), []);
+const f32_matrix_types = [2, 3, 4]
+ .map(i => [2, 3, 4].map(j => `mat${i}x${j}f`))
+ .reduce((a, c) => a.concat(c), []);
+const f16_matrix_types = [2, 3, 4]
+ .map(i => [2, 3, 4].map(j => `mat${i}x${j}<f16>`))
+ .reduce((a, c) => a.concat(c), []);
+
+g.test('type')
+ .specURL('https://www.w3.org/TR/WGSL/#arrayLength-builtin')
+ .desc(
+ `
+arrayLength accepts only runtime-sized arrays
+`
+ )
+ .params(u =>
+ u.combine('type', [
+ 'i32',
+ 'u32',
+ 'f32',
+ 'f16',
+ ...f32_matrix_types,
+ ...f16_matrix_types,
+ ...vec_types,
+ ...atomic_types,
+ 'T',
+ 'array<i32, 2>',
+ 'array<i32>',
+ ])
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.type.includes('f16')) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const code = `
+struct T {
+ b: i32,
+}
+struct S {
+ ary: ${t.params.type}
+}
+
+@group(0) @binding(0) var<storage, read_write> items: S;
+
+@compute @workgroup_size(1)
+fn main() {
+ _ = arrayLength(&items.ary);
+}`;
+
+ t.expectCompileResult(t.params.type === 'array<i32>', code);
+ });
+
+// Note, the `write` case actually fails because you can't declare a storage buffer of
+// access_mode `write`.
+g.test('access_mode')
+ .specURL('https://www.w3.org/TR/WGSL/#arrayLength-builtin')
+ .desc(
+ `
+arrayLength runtime-sized array must have an access_mode of read or read_write
+`
+ )
+ .params(u => u.combine('mode', ['read', 'read_write', 'write']))
+ .fn(t => {
+ const code = `
+struct S {
+ ary: array<i32>,
+}
+
+@group(0) @binding(0) var<storage, ${t.params.mode}> items: S;
+
+@compute @workgroup_size(1)
+fn main() {
+ _ = arrayLength(&items.ary);
+}`;
+
+ t.expectCompileResult(t.params.mode !== 'write', code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/asin.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/asin.spec.ts
index 8af7706169..16a193aec4 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/asin.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/asin.spec.ts
@@ -6,18 +6,18 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
+import { absBigInt } from '../../../../../util/math.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
import {
fullRangeForType,
kConstantAndOverrideStages,
- kMinusTwoToTwo,
+ minusTwoToTwoRangeForType,
stageSupportsType,
unique,
validateConstOrOverrideBuiltinEval,
@@ -25,7 +25,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -39,15 +39,23 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
.combine('type', keysOf(kValuesTypes))
.filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
.beginSubcases()
- .expand('value', u => unique(kMinusTwoToTwo, fullRangeForType(kValuesTypes[u.type])))
+ .expand('value', u =>
+ unique(
+ minusTwoToTwoRangeForType(kValuesTypes[u.type]),
+ fullRangeForType(kValuesTypes[u.type])
+ )
+ )
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
.fn(t => {
- const expectedResult = Math.abs(t.params.value) <= 1;
+ const expectedResult =
+ typeof t.params.value === 'bigint'
+ ? absBigInt(t.params.value) <= 1n
+ : Math.abs(t.params.value) <= 1;
validateConstOrOverrideBuiltinEval(
t,
builtin,
@@ -57,7 +65,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kIntegerArgumentTypes = objectsToRecord([Type.f32, ...kConcreteIntegerScalarsAndVectors]);
g.test('integer_argument')
.desc(
@@ -71,8 +79,137 @@ Validates that scalar and vector integer arguments are rejected by ${builtin}()
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ type === TypeF32,
+ /* expectedResult */ type === Type.f32,
[type.create(0)],
'constant'
);
});
+
+const kTests = {
+ valid: {
+ src: `_ = asin(1);`,
+ pass: true,
+ },
+ alias: {
+ src: `_ = asin(f32_alias(1));`,
+ pass: true,
+ },
+
+ bool: {
+ src: `_ = asin(false);`,
+ pass: false,
+ },
+ i32: {
+ src: `_ = asin(1i);`,
+ pass: false,
+ },
+ u32: {
+ src: `_ = asin(1u);`,
+ pass: false,
+ },
+ vec_bool: {
+ src: `_ = asin(vec2<bool>(false, true));`,
+ pass: false,
+ },
+ vec_i32: {
+ src: `_ = asin(vec2<i32>(1, 1));`,
+ pass: false,
+ },
+ vec_u32: {
+ src: `_ = asin(vec2<u32>(1, 1));`,
+ pass: false,
+ },
+ matrix: {
+ src: `_ = asin(mat2x2(1, 1, 1, 1));`,
+ pass: false,
+ },
+ atomic: {
+ src: ` _ = asin(a);`,
+ pass: false,
+ },
+ array: {
+ src: `var a: array<u32, 5>;
+ _ = asin(a);`,
+ pass: false,
+ },
+ array_runtime: {
+ src: `_ = asin(k.arry);`,
+ pass: false,
+ },
+ struct: {
+ src: `var a: A;
+ _ = asin(a);`,
+ pass: false,
+ },
+ enumerant: {
+ src: `_ = asin(read_write);`,
+ pass: false,
+ },
+ ptr: {
+ src: `var<function> a = 1f;
+ let p: ptr<function, f32> = &a;
+ _ = asin(p);`,
+ pass: false,
+ },
+ ptr_deref: {
+ src: `var<function> a = 1f;
+ let p: ptr<function, f32> = &a;
+ _ = asin(*p);`,
+ pass: true,
+ },
+ sampler: {
+ src: `_ = asin(s);`,
+ pass: false,
+ },
+ texture: {
+ src: `_ = asin(t);`,
+ pass: false,
+ },
+ no_params: {
+ src: `_ = asin();`,
+ pass: false,
+ },
+ too_many_params: {
+ src: `_ = asin(1, 2);`,
+ pass: false,
+ },
+
+ greater_then_one: {
+ src: `_ = asin(1.1f);`,
+ pass: false,
+ },
+ less_then_negative_one: {
+ src: `_ = asin(-1.1f);`,
+ pass: false,
+ },
+};
+
+g.test('parameters')
+ .desc(`Test that ${builtin} is validated correctly.`)
+ .params(u => u.combine('test', keysOf(kTests)))
+ .fn(t => {
+ const src = kTests[t.params.test].src;
+ const code = `
+alias f32_alias = f32;
+
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: texture_2d<f32>;
+
+var<workgroup> a: atomic<u32>;
+
+struct A {
+ i: u32,
+}
+struct B {
+ arry: array<u32>,
+}
+@group(0) @binding(3) var<storage> k: B;
+
+
+@vertex
+fn main() -> @builtin(position) vec4<f32> {
+ ${src}
+ return vec4<f32>(.4, .2, .3, .1);
+}`;
+ t.expectCompileResult(kTests[t.params.test].pass, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/asinh.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/asinh.spec.ts
index 4558d30966..285ff2dcd2 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/asinh.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/asinh.spec.ts
@@ -6,19 +6,19 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
+ Type,
} from '../../../../../util/conversion.js';
import { isRepresentable } from '../../../../../util/floating_point.js';
-import { linearRange } from '../../../../../util/math.js';
+import { linearRange, linearRangeBigInt } from '../../../../../util/math.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
import {
fullRangeForType,
kConstantAndOverrideStages,
+ rangeForType,
stageSupportsType,
unique,
validateConstOrOverrideBuiltinEval,
@@ -26,7 +26,12 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
+
+const additionalRangeForType = rangeForType(
+ linearRange(-2000, 2000, 10),
+ linearRangeBigInt(-2000n, 2000n, 10)
+);
g.test('values')
.desc(
@@ -41,17 +46,21 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
.filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
.beginSubcases()
.expand('value', u =>
- unique(fullRangeForType(kValuesTypes[u.type]), linearRange(-2000, 2000, 10))
+ unique(fullRangeForType(kValuesTypes[u.type]), additionalRangeForType(kValuesTypes[u.type]))
)
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
.fn(t => {
const type = kValuesTypes[t.params.type];
- const expectedResult = isRepresentable(Math.asinh(t.params.value), elementType(type));
+ const expectedResult = isRepresentable(
+ Math.asinh(Number(t.params.value)),
+ // AbstractInt is converted to AbstractFloat before calling into the builtin
+ scalarTypeOf(type).kind === 'abstract-int' ? Type.abstractFloat : scalarTypeOf(type)
+ );
validateConstOrOverrideBuiltinEval(
t,
builtin,
@@ -61,7 +70,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kIntegerArgumentTypes = objectsToRecord([Type.f32, ...kConcreteIntegerScalarsAndVectors]);
g.test('integer_argument')
.desc(
@@ -75,8 +84,128 @@ Validates that scalar and vector integer arguments are rejected by ${builtin}()
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ type === TypeF32,
+ /* expectedResult */ type === Type.f32,
[type.create(1)],
'constant'
);
});
+
+const kTests = {
+ valid: {
+ src: `_ = asinh(1);`,
+ pass: true,
+ },
+ alias: {
+ src: `_ = asinh(f32_alias(1));`,
+ pass: true,
+ },
+
+ bool: {
+ src: `_ = asinh(false);`,
+ pass: false,
+ },
+ i32: {
+ src: `_ = asinh(1i);`,
+ pass: false,
+ },
+ u32: {
+ src: `_ = asinh(1u);`,
+ pass: false,
+ },
+ vec_bool: {
+ src: `_ = asinh(vec2<bool>(false, true));`,
+ pass: false,
+ },
+ vec_i32: {
+ src: `_ = asinh(vec2<i32>(1, 1));`,
+ pass: false,
+ },
+ vec_u32: {
+ src: `_ = asinh(vec2<u32>(1, 1));`,
+ pass: false,
+ },
+ matrix: {
+ src: `_ = asinh(mat2x2(1, 1, 1, 1));`,
+ pass: false,
+ },
+ atomic: {
+ src: ` _ = asinh(a);`,
+ pass: false,
+ },
+ array: {
+ src: `var a: array<u32, 5>;
+ _ = asinh(a);`,
+ pass: false,
+ },
+ array_runtime: {
+ src: `_ = asinh(k.arry);`,
+ pass: false,
+ },
+ struct: {
+ src: `var a: A;
+ _ = asinh(a);`,
+ pass: false,
+ },
+ enumerant: {
+ src: `_ = asinh(read_write);`,
+ pass: false,
+ },
+ ptr: {
+ src: `var<function> a = 1f;
+ let p: ptr<function, f32> = &a;
+ _ = asinh(p);`,
+ pass: false,
+ },
+ ptr_deref: {
+ src: `var<function> a = 1f;
+ let p: ptr<function, f32> = &a;
+ _ = asinh(*p);`,
+ pass: true,
+ },
+ sampler: {
+ src: `_ = asinh(s);`,
+ pass: false,
+ },
+ texture: {
+ src: `_ = asinh(t);`,
+ pass: false,
+ },
+ no_params: {
+ src: `_ = asinh();`,
+ pass: false,
+ },
+ too_many_params: {
+ src: `_ = asinh(1, 2);`,
+ pass: false,
+ },
+};
+
+g.test('parameters')
+ .desc(`Test that ${builtin} is validated correctly.`)
+ .params(u => u.combine('test', keysOf(kTests)))
+ .fn(t => {
+ const src = kTests[t.params.test].src;
+ const code = `
+alias f32_alias = f32;
+
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: texture_2d<f32>;
+
+var<workgroup> a: atomic<u32>;
+
+struct A {
+ i: u32,
+}
+struct B {
+ arry: array<u32>,
+}
+@group(0) @binding(3) var<storage> k: B;
+
+
+@vertex
+fn main() -> @builtin(position) vec4<f32> {
+ ${src}
+ return vec4<f32>(.4, .2, .3, .1);
+}`;
+ t.expectCompileResult(kTests[t.params.test].pass, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atan.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atan.spec.ts
index 3080f4e971..c5e964084d 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atan.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atan.spec.ts
@@ -6,18 +6,17 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
import {
fullRangeForType,
kConstantAndOverrideStages,
- kMinus3PiTo3Pi,
+ minusThreePiToThreePiRangeForType,
stageSupportsType,
unique,
validateConstOrOverrideBuiltinEval,
@@ -25,7 +24,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -39,10 +38,15 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
.combine('type', keysOf(kValuesTypes))
.filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
.beginSubcases()
- .expand('value', u => unique(kMinus3PiTo3Pi, fullRangeForType(kValuesTypes[u.type])))
+ .expand('value', u =>
+ unique(
+ minusThreePiToThreePiRangeForType(kValuesTypes[u.type]),
+ fullRangeForType(kValuesTypes[u.type])
+ )
+ )
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
@@ -58,7 +62,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kIntegerArgumentTypes = objectsToRecord([Type.f32, ...kConcreteIntegerScalarsAndVectors]);
g.test('integer_argument')
.desc(
@@ -72,8 +76,128 @@ Validates that scalar and vector integer arguments are rejected by ${builtin}()
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ type === TypeF32,
+ /* expectedResult */ type === Type.f32,
[type.create(0)],
'constant'
);
});
+
+const kTests = {
+ valid: {
+ src: `_ = atan(1);`,
+ pass: true,
+ },
+ alias: {
+ src: `_ = atan(f32_alias(1));`,
+ pass: true,
+ },
+
+ bool: {
+ src: `_ = atan(false);`,
+ pass: false,
+ },
+ i32: {
+ src: `_ = atan(1i);`,
+ pass: false,
+ },
+ u32: {
+ src: `_ = atan(1u);`,
+ pass: false,
+ },
+ vec_bool: {
+ src: `_ = atan(vec2<bool>(false, true));`,
+ pass: false,
+ },
+ vec_i32: {
+ src: `_ = atan(vec2<i32>(1, 1));`,
+ pass: false,
+ },
+ vec_u32: {
+ src: `_ = atan(vec2<u32>(1, 1));`,
+ pass: false,
+ },
+ matrix: {
+ src: `_ = atan(mat2x2(1, 1, 1, 1));`,
+ pass: false,
+ },
+ atomic: {
+ src: ` _ = atan(a);`,
+ pass: false,
+ },
+ array: {
+ src: `var a: array<u32, 5>;
+ _ = atan(a);`,
+ pass: false,
+ },
+ array_runtime: {
+ src: `_ = atan(k.arry);`,
+ pass: false,
+ },
+ struct: {
+ src: `var a: A;
+ _ = atan(a);`,
+ pass: false,
+ },
+ enumerant: {
+ src: `_ = atan(read_write);`,
+ pass: false,
+ },
+ ptr: {
+ src: `var<function> a = 1f;
+ let p: ptr<function, f32> = &a;
+ _ = atan(p);`,
+ pass: false,
+ },
+ ptr_deref: {
+ src: `var<function> a = 1f;
+ let p: ptr<function, f32> = &a;
+ _ = atan(*p);`,
+ pass: true,
+ },
+ sampler: {
+ src: `_ = atan(s);`,
+ pass: false,
+ },
+ texture: {
+ src: `_ = atan(t);`,
+ pass: false,
+ },
+ no_params: {
+ src: `_ = atan();`,
+ pass: false,
+ },
+ too_many_params: {
+ src: `_ = atan(1, 2);`,
+ pass: false,
+ },
+};
+
+g.test('parameters')
+ .desc(`Test that ${builtin} is validated correctly.`)
+ .params(u => u.combine('test', keysOf(kTests)))
+ .fn(t => {
+ const src = kTests[t.params.test].src;
+ const code = `
+alias f32_alias = f32;
+
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: texture_2d<f32>;
+
+var<workgroup> a: atomic<u32>;
+
+struct A {
+ i: u32,
+}
+struct B {
+ arry: array<u32>,
+}
+@group(0) @binding(3) var<storage> k: B;
+
+
+@vertex
+fn main() -> @builtin(position) vec4<f32> {
+ ${src}
+ return vec4<f32>(.4, .2, .3, .1);
+}`;
+ t.expectCompileResult(kTests[t.params.test].pass, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atan2.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atan2.spec.ts
index 33f1970697..20998bfe76 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atan2.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atan2.spec.ts
@@ -6,13 +6,13 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- Vector,
- VectorType,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ VectorValue,
+ kFloatScalarsAndVectors,
+ kConcreteIntegerScalarsAndVectors,
+ kAllMatrices,
+ kAllBoolScalarsAndVectors,
+ scalarTypeOf,
+ Type,
} from '../../../../../util/conversion.js';
import { isRepresentable } from '../../../../../util/floating_point.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -20,7 +20,7 @@ import { ShaderValidationTest } from '../../../shader_validation_test.js';
import {
fullRangeForType,
kConstantAndOverrideStages,
- kSparseMinus3PiTo3Pi,
+ sparseMinusThreePiToThreePiRangeForType,
stageSupportsType,
unique,
validateConstOrOverrideBuiltinEval,
@@ -28,7 +28,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -42,19 +42,29 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
.combine('type', keysOf(kValuesTypes))
.filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
.beginSubcases()
- .expand('y', u => unique(kSparseMinus3PiTo3Pi, fullRangeForType(kValuesTypes[u.type], 4)))
- .expand('x', u => unique(kSparseMinus3PiTo3Pi, fullRangeForType(kValuesTypes[u.type], 4)))
+ .expand('y', u =>
+ unique(
+ sparseMinusThreePiToThreePiRangeForType(kValuesTypes[u.type]),
+ fullRangeForType(kValuesTypes[u.type], 4)
+ )
+ )
+ .expand('x', u =>
+ unique(
+ sparseMinusThreePiToThreePiRangeForType(kValuesTypes[u.type]),
+ fullRangeForType(kValuesTypes[u.type], 4)
+ )
+ )
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
.fn(t => {
const type = kValuesTypes[t.params.type];
const expectedResult = isRepresentable(
- Math.abs(Math.atan2(t.params.y, t.params.x)),
- elementType(type)
+ Math.abs(Math.atan2(Number(t.params.x), Number(t.params.y))),
+ scalarTypeOf(type)
);
validateConstOrOverrideBuiltinEval(
t,
@@ -65,42 +75,275 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kInvalidArgumentTypes = objectsToRecord([
+ Type.f32,
+ ...kConcreteIntegerScalarsAndVectors,
+ ...kAllBoolScalarsAndVectors,
+ ...kAllMatrices,
+]);
-g.test('integer_argument_y')
+g.test('invalid_argument_y')
.desc(
`
Validates that scalar and vector integer arguments are rejected by ${builtin}()
`
)
- .params(u => u.combine('type', keysOf(kIntegerArgumentTypes)))
+ .params(u => u.combine('type', keysOf(kInvalidArgumentTypes)))
.fn(t => {
- const yTy = kIntegerArgumentTypes[t.params.type];
- const xTy = yTy instanceof Vector ? new VectorType(yTy.size, TypeF32) : TypeF32;
+ const yTy = kInvalidArgumentTypes[t.params.type];
+ const xTy = yTy instanceof VectorValue ? Type.vec(yTy.size, Type.f32) : Type.f32;
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ yTy === TypeF32,
+ /* expectedResult */ yTy === Type.f32,
[yTy.create(1), xTy.create(1)],
'constant'
);
});
-g.test('integer_argument_x')
+g.test('invalid_argument_x')
.desc(
`
Validates that scalar and vector integer arguments are rejected by ${builtin}()
`
)
- .params(u => u.combine('type', keysOf(kIntegerArgumentTypes)))
+ .params(u => u.combine('type', keysOf(kInvalidArgumentTypes)))
.fn(t => {
- const xTy = kIntegerArgumentTypes[t.params.type];
- const yTy = xTy instanceof Vector ? new VectorType(xTy.size, TypeF32) : TypeF32;
+ const xTy = kInvalidArgumentTypes[t.params.type];
+ const yTy = xTy instanceof VectorValue ? Type.vec(xTy.size, Type.f32) : Type.f32;
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ xTy === TypeF32,
+ /* expectedResult */ xTy === Type.f32,
[yTy.create(1), xTy.create(1)],
'constant'
);
});
+
+const kTests = {
+ af: {
+ src: `_ = atan2(1.2, 2.2);`,
+ pass: true,
+ is_f16: false,
+ },
+ ai: {
+ src: `_ = atan2(1, 2);`,
+ pass: true,
+ is_f16: false,
+ },
+ ai_af: {
+ src: `_ = atan2(1, 2.1);`,
+ pass: true,
+ is_f16: false,
+ },
+ af_ai: {
+ src: `_ = atan2(1.2, 2);`,
+ pass: true,
+ is_f16: false,
+ },
+ ai_f32: {
+ src: `_ = atan2(1, 1.2f);`,
+ pass: true,
+ is_f16: false,
+ },
+ f32_ai: {
+ src: `_ = atan2(1.2f, 1);`,
+ pass: true,
+ is_f16: false,
+ },
+ af_f32: {
+ src: `_ = atan2(1.2, 2.2f);`,
+ pass: true,
+ is_f16: false,
+ },
+ f32_af: {
+ src: `_ = atan2(2.2f, 1.2);`,
+ pass: true,
+ is_f16: false,
+ },
+ f16_ai: {
+ src: `_ = atan2(1.2h, 1);`,
+ pass: true,
+ is_f16: true,
+ },
+ ai_f16: {
+ src: `_ = atan2(1, 1.2h);`,
+ pass: true,
+ is_f16: true,
+ },
+ af_f16: {
+ src: `_ = atan2(1.2, 1.2h);`,
+ pass: true,
+ is_f16: true,
+ },
+ f16_af: {
+ src: `_ = atan2(1.2h, 1.2);`,
+ pass: true,
+ is_f16: true,
+ },
+
+ mixed_types: {
+ src: `_ = atan2(1.2f, vec2(1.2f));`,
+ pass: false,
+ is_f16: false,
+ },
+ mixed_types_2: {
+ src: `_ = atan2(vec2(1.2f), 1.2f);`,
+ pass: false,
+ is_f16: false,
+ },
+ f16_f32: {
+ src: `_ = atan2(1.2h, 1.2f);`,
+ pass: false,
+ is_f16: true,
+ },
+ u32_f32: {
+ src: `_ = atan2(1u, 1.2f);`,
+ pass: false,
+ is_f16: false,
+ },
+ f32_u32: {
+ src: `_ = atan2(1.2f, 1u);`,
+ pass: false,
+ is_f16: false,
+ },
+ f32_i32: {
+ src: `_ = atan2(1.2f, 1i);`,
+ pass: false,
+ is_f16: false,
+ },
+ i32_f32: {
+ src: `_ = atan2(1i, 1.2f);`,
+ pass: false,
+ is_f16: false,
+ },
+ f32_bool: {
+ src: `_ = atan2(1.2f, true);`,
+ pass: false,
+ is_f16: false,
+ },
+ bool_f32: {
+ src: `_ = atan2(false, 1.2f);`,
+ pass: false,
+ is_f16: false,
+ },
+ vec_f32: {
+ src: `_ = atan2(vec2(1i), vec2(1.2f));`,
+ pass: false,
+ is_f16: false,
+ },
+ f32_vec: {
+ src: `_ = atan2(vec2(1.2f), vec2(1i));`,
+ pass: false,
+ is_f16: false,
+ },
+ matrix: {
+ src: `_ = atan2(mat2x2(1, 1, 1, 1), mat2x2(1, 1, 1, 1));`,
+ pass: false,
+ is_f16: false,
+ },
+ atomic: {
+ src: ` _ = atan2(a, a);`,
+ pass: false,
+ is_f16: false,
+ },
+ array: {
+ src: `var a: array<u32, 5>;
+ _ = atan2(a, a);`,
+ pass: false,
+ is_f16: false,
+ },
+ array_runtime: {
+ src: `_ = atan2(k.arry, k.arry);`,
+ pass: false,
+ is_f16: false,
+ },
+ struct: {
+ src: `var a: A;
+ _ = atan2(a, a);`,
+ pass: false,
+ is_f16: false,
+ },
+ enumerant: {
+ src: `_ = atan2(read_write, read_write);`,
+ pass: false,
+ is_f16: false,
+ },
+ ptr: {
+ src: `var<function> a = 1f;
+ let p: ptr<function, f32> = &a;
+ _ = atan2(p, p);`,
+ pass: false,
+ is_f16: false,
+ },
+ ptr_deref: {
+ src: `var<function> a = 1f;
+ let p: ptr<function, f32> = &a;
+ _ = atan2(*p, *p);`,
+ pass: true,
+ is_f16: false,
+ },
+ sampler: {
+ src: `_ = atan2(s, s);`,
+ pass: false,
+ is_f16: false,
+ },
+ texture: {
+ src: `_ = atan2(t, t);`,
+ pass: false,
+ is_f16: false,
+ },
+ no_params: {
+ src: `_ = atan2();`,
+ pass: false,
+ is_f16: false,
+ },
+ too_many_params: {
+ src: `_ = atan2(1, 2, 3);`,
+ pass: false,
+ is_f16: false,
+ },
+};
+
+g.test('parameters')
+ .desc(`Test that ${builtin} is validated correctly.`)
+ .params(u => u.combine('test', keysOf(kTests)))
+ .beforeAllSubcases(t => {
+ if (kTests[t.params.test].is_f16 === true) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const src = kTests[t.params.test].src;
+ const code = `
+alias f32_alias = f32;
+
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: texture_2d<f32>;
+
+var<workgroup> a: atomic<u32>;
+
+struct A {
+ i: u32,
+}
+struct B {
+ arry: array<u32>,
+}
+@group(0) @binding(3) var<storage> k: B;
+
+
+@vertex
+fn main() -> @builtin(position) vec4<f32> {
+ ${src}
+ return vec4<f32>(.4, .2, .3, .1);
+}`;
+ t.expectCompileResult(kTests[t.params.test].pass, code);
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}(1, 2); }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atanh.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atanh.spec.ts
index 63a96f0f70..4eae8a71f0 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atanh.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atanh.spec.ts
@@ -6,18 +6,18 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
+import { absBigInt } from '../../../../../util/math.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
import {
fullRangeForType,
kConstantAndOverrideStages,
- kMinusTwoToTwo,
+ minusTwoToTwoRangeForType,
stageSupportsType,
unique,
validateConstOrOverrideBuiltinEval,
@@ -25,7 +25,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -39,15 +39,23 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
.combine('type', keysOf(kValuesTypes))
.filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
.beginSubcases()
- .expand('value', u => unique(kMinusTwoToTwo, fullRangeForType(kValuesTypes[u.type])))
+ .expand('value', u =>
+ unique(
+ minusTwoToTwoRangeForType(kValuesTypes[u.type]),
+ fullRangeForType(kValuesTypes[u.type])
+ )
+ )
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
.fn(t => {
- const expectedResult = Math.abs(t.params.value) < 1;
+ const expectedResult =
+ typeof t.params.value === 'bigint'
+ ? absBigInt(t.params.value) < 1n
+ : Math.abs(t.params.value) < 1;
validateConstOrOverrideBuiltinEval(
t,
builtin,
@@ -57,7 +65,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kIntegerArgumentTypes = objectsToRecord([Type.f32, ...kConcreteIntegerScalarsAndVectors]);
g.test('integer_argument')
.desc(
@@ -71,8 +79,145 @@ Validates that scalar and vector integer arguments are rejected by ${builtin}()
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ type === TypeF32,
+ /* expectedResult */ type === Type.f32,
[type.create(0)],
'constant'
);
});
+
+const kTests = {
+ valid: {
+ src: `_ = atanh(.1);`,
+ pass: true,
+ },
+ alias: {
+ src: `_ = atanh(f32_alias(.1));`,
+ pass: true,
+ },
+
+ bool: {
+ src: `_ = atanh(false);`,
+ pass: false,
+ },
+ i32: {
+ src: `_ = atanh(0i);`,
+ pass: false,
+ },
+ u32: {
+ src: `_ = atanh(0u);`,
+ pass: false,
+ },
+ vec_bool: {
+ src: `_ = atanh(vec2<bool>(false, true));`,
+ pass: false,
+ },
+ vec_i32: {
+ src: `_ = atanh(vec2<i32>(0, 0));`,
+ pass: false,
+ },
+ vec_u32: {
+ src: `_ = atanh(vec2<u32>(0, 0));`,
+ pass: false,
+ },
+ matrix: {
+ src: `_ = atanh(mat2x2(0, 0, 0, 0));`,
+ pass: false,
+ },
+ atomic: {
+ src: ` _ = atanh(a);`,
+ pass: false,
+ },
+ array: {
+ src: `var a: array<u32, 5>;
+ _ = atanh(a);`,
+ pass: false,
+ },
+ array_runtime: {
+ src: `_ = atanh(k.arry);`,
+ pass: false,
+ },
+ struct: {
+ src: `var a: A;
+ _ = atanh(a);`,
+ pass: false,
+ },
+ enumerant: {
+ src: `_ = atanh(read_write);`,
+ pass: false,
+ },
+ ptr: {
+ src: `var<function> a = 0f;
+ let p: ptr<function, f32> = &a;
+ _ = atanh(p);`,
+ pass: false,
+ },
+ ptr_deref: {
+ src: `var<function> a = 0f;
+ let p: ptr<function, f32> = &a;
+ _ = atanh(*p);`,
+ pass: true,
+ },
+ sampler: {
+ src: `_ = atanh(s);`,
+ pass: false,
+ },
+ texture: {
+ src: `_ = atanh(t);`,
+ pass: false,
+ },
+ no_params: {
+ src: `_ = atanh();`,
+ pass: false,
+ },
+ too_many_params: {
+ src: `_ = atanh(0, .2);`,
+ pass: false,
+ },
+
+ one: {
+ src: `_ = atanh(1f);`,
+ pass: false,
+ },
+ greater_then_one: {
+ src: `_ = atanh(1.1f);`,
+ pass: false,
+ },
+ negative_one: {
+ src: `_ = atanh(-1f);`,
+ pass: false,
+ },
+ less_then_negative_one: {
+ src: `_ = atanh(-1.1f);`,
+ pass: false,
+ },
+};
+
+g.test('parameters')
+ .desc(`Test that ${builtin} is validated correctly.`)
+ .params(u => u.combine('test', keysOf(kTests)))
+ .fn(t => {
+ const src = kTests[t.params.test].src;
+ const code = `
+alias f32_alias = f32;
+
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: texture_2d<f32>;
+
+var<workgroup> a: atomic<u32>;
+
+struct A {
+ i: u32,
+}
+struct B {
+ arry: array<u32>,
+}
+@group(0) @binding(3) var<storage> k: B;
+
+
+@vertex
+fn main() -> @builtin(position) vec4<f32> {
+ ${src}
+ return vec4<f32>(.4, .2, .3, .1);
+}`;
+ t.expectCompileResult(kTests[t.params.test].pass, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atomics.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atomics.spec.ts
index 57c5aae613..fdb85664d2 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atomics.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atomics.spec.ts
@@ -8,18 +8,44 @@ import { ShaderValidationTest } from '../../../shader_validation_test.js';
export const g = makeTestGroup(ShaderValidationTest);
-const kAtomicOps = {
- add: { src: 'atomicAdd(&a,1)' },
- sub: { src: 'atomicSub(&a,1)' },
- max: { src: 'atomicMax(&a,1)' },
- min: { src: 'atomicMin(&a,1)' },
- and: { src: 'atomicAnd(&a,1)' },
- or: { src: 'atomicOr(&a,1)' },
- xor: { src: 'atomicXor(&a,1)' },
- load: { src: 'atomicLoad(&a)' },
- store: { src: 'atomicStore(&a,1)' },
- exchange: { src: 'atomicExchange(&a,1)' },
- compareexchangeweak: { src: 'atomicCompareExchangeWeak(&a,1,1)' },
+interface stringToString {
+ (a: string): string;
+}
+
+const kAtomicOps: Record<string, stringToString> = {
+ add: (a: string): string => {
+ return `atomicAdd(${a},1)`;
+ },
+ sub: (a: string): string => {
+ return `atomicSub(${a},1)`;
+ },
+ max: (a: string): string => {
+ return `atomicMax(${a},1)`;
+ },
+ min: (a: string): string => {
+ return `atomicMin(${a},1)`;
+ },
+ and: (a: string): string => {
+ return `atomicAnd(${a},1)`;
+ },
+ or: (a: string): string => {
+ return `atomicOr(${a},1)`;
+ },
+ xor: (a: string): string => {
+ return `atomicXor(${a},1)`;
+ },
+ load: (a: string): string => {
+ return `atomicLoad(${a})`;
+ },
+ store: (a: string): string => {
+ return `atomicStore(${a},1)`;
+ },
+ exchange: (a: string): string => {
+ return `atomicExchange(${a},1)`;
+ },
+ compareexchangeweak: (a: string): string => {
+ return `atomicCompareExchangeWeak(${a},1,1)`;
+ },
};
g.test('stage')
@@ -35,7 +61,7 @@ Atomic built-in functions must not be used in a vertex shader stage.
.combine('atomicOp', keysOf(kAtomicOps))
)
.fn(t => {
- const atomicOp = kAtomicOps[t.params.atomicOp].src;
+ const atomicOp = kAtomicOps[t.params.atomicOp](`&a`);
let code = `
@group(0) @binding(0) var<storage, read_write> a: atomic<i32>;
`;
@@ -68,3 +94,186 @@ Atomic built-in functions must not be used in a vertex shader stage.
const pass = t.params.stage !== 'vertex';
t.expectCompileResult(pass, code);
});
+
+function generateAtomicCode(
+ type: string,
+ access: string,
+ aspace: string,
+ style: string,
+ op: string
+): string {
+ let moduleVar = ``;
+ let functionVar = ``;
+ let param = ``;
+ let aParam = ``;
+ if (style === 'var') {
+ aParam = `&a`;
+ switch (aspace) {
+ case 'storage':
+ moduleVar = `@group(0) @binding(0) var<storage, ${access}> a : atomic<${type}>;\n`;
+ break;
+ case 'workgroup':
+ moduleVar = `var<workgroup> a : atomic<${type}>;\n`;
+ break;
+ case 'uniform':
+ moduleVar = `@group(0) @binding(0) var<uniform> a : atomic<${type}>;\n`;
+ break;
+ case 'private':
+ moduleVar = `var<private> a : atomic<${type}>;\n`;
+ break;
+ case 'function':
+ functionVar = `var a : atomic<${type}>;\n`;
+ break;
+ default:
+ break;
+ }
+ } else {
+ const aspaceParam = aspace === 'storage' ? `, ${access}` : ``;
+ param = `p : ptr<${aspace}, atomic<${type}>${aspaceParam}>`;
+ aParam = `p`;
+ }
+
+ return `
+${moduleVar}
+fn foo(${param}) {
+ ${functionVar}
+ ${kAtomicOps[op](aParam)};
+}
+`;
+}
+
+g.test('atomic_parameterization')
+ .desc('Tests the valid atomic parameters')
+ .params(u =>
+ u
+ .combine('op', keysOf(kAtomicOps))
+ .beginSubcases()
+ .combine('aspace', ['storage', 'workgroup', 'private', 'uniform', 'function'] as const)
+ .combine('access', ['read', 'read_write'] as const)
+ .combine('type', ['i32', 'u32'] as const)
+ .combine('style', ['param', 'var'] as const)
+ .filter(t => {
+ switch (t.aspace) {
+ case 'uniform':
+ return t.style === 'param' && t.access === 'read';
+ case 'workgroup':
+ return t.access === 'read_write';
+ case 'function':
+ case 'private':
+ return t.style === 'param' && t.access === 'read_write';
+ default:
+ return true;
+ }
+ })
+ )
+ .fn(t => {
+ if (
+ t.params.style === 'param' &&
+ !(t.params.aspace === 'function' || t.params.aspace === 'private')
+ ) {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+ }
+
+ const aspaceOK = t.params.aspace === 'storage' || t.params.aspace === 'workgroup';
+ const accessOK = t.params.access === 'read_write';
+ t.expectCompileResult(
+ aspaceOK && accessOK,
+ generateAtomicCode(
+ t.params.type,
+ t.params.access,
+ t.params.aspace,
+ t.params.style,
+ t.params.op
+ )
+ );
+ });
+
+g.test('data_parameters')
+ .desc('Validates that data parameters must match atomic type (or be implicitly convertible)')
+ .params(u =>
+ u
+ .combine('op', [
+ 'atomicStore',
+ 'atomicAdd',
+ 'atomicSub',
+ 'atomicMax',
+ 'atomicMin',
+ 'atomicAnd',
+ 'atomicOr',
+ 'atomicXor',
+ 'atomicExchange',
+ 'atomicCompareExchangeWeak1',
+ 'atomicCompareExchangeWeak2',
+ ] as const)
+ .beginSubcases()
+ .combine('atomicType', ['i32', 'u32'] as const)
+ .combine('dataType', ['i32', 'u32', 'f32', 'AbstractInt'] as const)
+ )
+ .fn(t => {
+ let dataValue = '';
+ switch (t.params.dataType) {
+ case 'i32':
+ dataValue = '1i';
+ break;
+ case 'u32':
+ dataValue = '1u';
+ break;
+ case 'f32':
+ dataValue = '1f';
+ break;
+ case 'AbstractInt':
+ dataValue = '1';
+ break;
+ }
+ let op = '';
+ switch (t.params.op) {
+ case 'atomicCompareExchangeWeak1':
+ op = `atomicCompareExchangeWeak(&a, ${dataValue}, 1)`;
+ break;
+ case 'atomicCompareExchangeWeak2':
+ op = `atomicCompareExchangeWeak(&a, 1, ${dataValue})`;
+ break;
+ default:
+ op = `${t.params.op}(&a, ${dataValue})`;
+ break;
+ }
+ const code = `
+var<workgroup> a : atomic<${t.params.atomicType}>;
+fn foo() {
+ ${op};
+}
+`;
+
+ const expect = t.params.atomicType === t.params.dataType || t.params.dataType === 'AbstractInt';
+ t.expectCompileResult(expect, code);
+ });
+
+g.test('return_types')
+ .desc('Validates return types of atomics')
+ .params(u =>
+ u
+ .combine('op', keysOf(kAtomicOps))
+ .beginSubcases()
+ .combine('atomicType', ['i32', 'u32'] as const)
+ .combine('returnType', ['i32', 'u32', 'f32'] as const)
+ )
+ .fn(t => {
+ let op = `${kAtomicOps[t.params.op]('&a')}`;
+ switch (t.params.op) {
+ case 'compareexchangeweak':
+ op = `let tmp : ${t.params.returnType} = ${op}.old_value`;
+ break;
+ default:
+ op = `let tmp : ${t.params.returnType} = ${op}`;
+ break;
+ }
+ const code = `
+var<workgroup> a : atomic<${t.params.atomicType}>;
+fn foo() {
+ ${op};
+}
+`;
+
+ const expect = t.params.atomicType === t.params.returnType && t.params.op !== 'store';
+ t.expectCompileResult(expect, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/barriers.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/barriers.spec.ts
new file mode 100644
index 0000000000..5a4ef14657
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/barriers.spec.ts
@@ -0,0 +1,109 @@
+export const description = `
+Validation tests for {storage,texture,workgroup}Barrier() builtins.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kEntryPoints = {
+ none: { supportsBarrier: true, code: `` },
+ compute: {
+ supportsBarrier: true,
+ code: `@compute @workgroup_size(1)
+fn main() {
+ foo();
+}`,
+ },
+ vertex: {
+ supportsBarrier: false,
+ code: `@vertex
+fn main() -> @builtin(position) vec4f {
+ foo();
+ return vec4f();
+}`,
+ },
+ fragment: {
+ supportsBarrier: false,
+ code: `@fragment
+fn main() {
+ foo();
+}`,
+ },
+ compute_and_fragment: {
+ supportsBarrier: false,
+ code: `@compute @workgroup_size(1)
+fn main1() {
+ foo();
+}
+
+@fragment
+fn main2() {
+ foo();
+}
+`,
+ },
+ fragment_without_call: {
+ supportsBarrier: true,
+ code: `@fragment
+fn main() {
+}
+`,
+ },
+};
+
+g.test('only_in_compute')
+ .specURL('https://www.w3.org/TR/WGSL/#sync-builtin-functions')
+ .desc(
+ `
+Synchronization functions must only be used in the compute shader stage.
+`
+ )
+ .params(u =>
+ u
+ .combine('entry_point', keysOf(kEntryPoints))
+ .combine('call', ['bar', 'storageBarrier', 'textureBarrier', 'workgroupBarrier'])
+ )
+ .fn(t => {
+ if (t.params.call.startsWith('textureBarrier')) {
+ t.skipIfLanguageFeatureNotSupported('readonly_and_readwrite_storage_textures');
+ }
+
+ const config = kEntryPoints[t.params.entry_point];
+ const code = `
+${config.code}
+fn bar() {}
+
+fn foo() {
+ ${t.params.call}();
+}`;
+ t.expectCompileResult(t.params.call === 'bar' || config.supportsBarrier, code);
+ });
+
+g.test('no_return_value')
+ .specURL('https://www.w3.org/TR/WGSL/#sync-builtin-functions')
+ .desc(
+ `
+Barrier functions do not return a value.
+`
+ )
+ .params(u =>
+ u
+ .combine('assign', [false, true])
+ .combine('rhs', ['bar', 'storageBarrier', 'textureBarrier', 'workgroupBarrier'])
+ )
+ .fn(t => {
+ if (t.params.rhs.startsWith('textureBarrier')) {
+ t.skipIfLanguageFeatureNotSupported('readonly_and_readwrite_storage_textures');
+ }
+
+ const code = `
+fn bar() {}
+
+fn foo() {
+ ${t.params.assign ? '_ = ' : ''} ${t.params.rhs}();
+}`;
+ t.expectCompileResult(!t.params.assign || t.params.rhs === 'bar()', code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/ceil.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/ceil.spec.ts
index 0f287907f8..951958d02c 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/ceil.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/ceil.spec.ts
@@ -6,11 +6,10 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -23,7 +22,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -36,10 +35,11 @@ Validates that constant evaluation and override evaluation of ${builtin}() never
.combine('stage', kConstantAndOverrideStages)
.combine('type', keysOf(kValuesTypes))
.filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
+ .beginSubcases()
.expand('value', u => fullRangeForType(kValuesTypes[u.type]))
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
@@ -54,7 +54,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() never
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kIntegerArgumentTypes = objectsToRecord([Type.f32, ...kConcreteIntegerScalarsAndVectors]);
g.test('integer_argument')
.desc(
@@ -68,7 +68,7 @@ Validates that scalar and vector integer arguments are rejected by ${builtin}()
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ type === TypeF32,
+ /* expectedResult */ type === Type.f32,
[type.create(0)],
'constant'
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/clamp.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/clamp.spec.ts
index 1cf28ffc2b..9a23328d65 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/clamp.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/clamp.spec.ts
@@ -6,9 +6,10 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- elementType,
- kAllFloatAndIntegerScalarsAndVectors,
+ Type,
+ kFloatScalarsAndVectors,
+ kConcreteIntegerScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -21,7 +22,10 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatAndIntegerScalarsAndVectors);
+const kValuesTypes = objectsToRecord([
+ ...kFloatScalarsAndVectors,
+ ...kConcreteIntegerScalarsAndVectors,
+]);
g.test('values')
.desc(
@@ -40,7 +44,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
.expand('high', u => fullRangeForType(kValuesTypes[u.type], 4))
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/const_override_validation.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/const_override_validation.ts
index 86b88cb159..f3b964a6e3 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/const_override_validation.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/const_override_validation.ts
@@ -2,100 +2,138 @@ import { assert, unreachable } from '../../../../../../common/util/util.js';
import { kValue } from '../../../../../util/constants.js';
import {
Type,
- TypeF16,
Value,
- elementType,
- elementsOf,
+ elementTypeOf,
isAbstractType,
+ scalarElementsOf,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
-import { fullF16Range, fullF32Range, fullF64Range, linearRange } from '../../../../../util/math.js';
+import {
+ scalarF16Range,
+ scalarF32Range,
+ scalarF64Range,
+ linearRange,
+ linearRangeBigInt,
+} from '../../../../../util/math.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
-/// A linear sweep between -2 to 2
-export const kMinusTwoToTwo = linearRange(-2, 2, 10);
-
-/// An array of values ranging from -3π to 3π, with a focus on multiples of π
-export const kMinus3PiTo3Pi = [
- -3 * Math.PI,
- -2.999 * Math.PI,
-
- -2.501 * Math.PI,
- -2.5 * Math.PI,
- -2.499 * Math.PI,
-
- -2.001 * Math.PI,
- -2.0 * Math.PI,
- -1.999 * Math.PI,
-
- -1.501 * Math.PI,
- -1.5 * Math.PI,
- -1.499 * Math.PI,
-
- -1.001 * Math.PI,
- -1.0 * Math.PI,
- -0.999 * Math.PI,
-
- -0.501 * Math.PI,
- -0.5 * Math.PI,
- -0.499 * Math.PI,
-
- -0.001,
- 0,
- 0.001,
-
- 0.499 * Math.PI,
- 0.5 * Math.PI,
- 0.501 * Math.PI,
-
- 0.999 * Math.PI,
- 1.0 * Math.PI,
- 1.001 * Math.PI,
-
- 1.499 * Math.PI,
- 1.5 * Math.PI,
- 1.501 * Math.PI,
-
- 1.999 * Math.PI,
- 2.0 * Math.PI,
- 2.001 * Math.PI,
-
- 2.499 * Math.PI,
- 2.5 * Math.PI,
- 2.501 * Math.PI,
-
- 2.999 * Math.PI,
- 3 * Math.PI,
-] as const;
-
-/// A minimal array of values ranging from -3π to 3π, with a focus on multiples
-/// of π. Used when multiple parameters are being passed in, so the number of
-/// cases becomes the square or more of this list.
-export const kSparseMinus3PiTo3Pi = [
- -3 * Math.PI,
- -2.5 * Math.PI,
- -2.0 * Math.PI,
- -1.5 * Math.PI,
- -1.0 * Math.PI,
- -0.5 * Math.PI,
- 0,
- 0.5 * Math.PI,
- Math.PI,
- 1.5 * Math.PI,
- 2.0 * Math.PI,
- 2.5 * Math.PI,
- 3 * Math.PI,
-] as const;
+/** @returns a function that can select between ranges depending on type */
+export function rangeForType(
+ number_range: readonly number[],
+ bigint_range: readonly bigint[]
+): (type: Type) => readonly (number | bigint)[] {
+ return (type: Type): readonly (number | bigint)[] => {
+ switch (scalarTypeOf(type).kind) {
+ case 'abstract-float':
+ case 'f32':
+ case 'f16':
+ return number_range;
+ case 'abstract-int':
+ return bigint_range;
+ }
+ unreachable(`Received unexpected type '${type}'`);
+ };
+}
+
+/* @returns a linear sweep between -2 to 2 for type */
+// prettier-ignore
+export const minusTwoToTwoRangeForType = rangeForType(
+ linearRange(-2, 2, 10),
+ [ -2n, -1n, 0n, 1n, 2n ]
+);
+
+/* @returns array of values ranging from -3π to 3π, with a focus on multiples of π */
+export const minusThreePiToThreePiRangeForType = rangeForType(
+ [
+ -3 * Math.PI,
+ -2.999 * Math.PI,
+
+ -2.501 * Math.PI,
+ -2.5 * Math.PI,
+ -2.499 * Math.PI,
+
+ -2.001 * Math.PI,
+ -2.0 * Math.PI,
+ -1.999 * Math.PI,
+
+ -1.501 * Math.PI,
+ -1.5 * Math.PI,
+ -1.499 * Math.PI,
+
+ -1.001 * Math.PI,
+ -1.0 * Math.PI,
+ -0.999 * Math.PI,
+
+ -0.501 * Math.PI,
+ -0.5 * Math.PI,
+ -0.499 * Math.PI,
+
+ -0.001,
+ 0,
+ 0.001,
+
+ 0.499 * Math.PI,
+ 0.5 * Math.PI,
+ 0.501 * Math.PI,
+
+ 0.999 * Math.PI,
+ 1.0 * Math.PI,
+ 1.001 * Math.PI,
+
+ 1.499 * Math.PI,
+ 1.5 * Math.PI,
+ 1.501 * Math.PI,
+
+ 1.999 * Math.PI,
+ 2.0 * Math.PI,
+ 2.001 * Math.PI,
+
+ 2.499 * Math.PI,
+ 2.5 * Math.PI,
+ 2.501 * Math.PI,
+
+ 2.999 * Math.PI,
+ 3 * Math.PI,
+ ],
+ [-2n, -1n, 0n, 1n, 2n]
+);
+
+/**
+ * @returns a minimal array of values ranging from -3π to 3π, with a focus on
+ * multiples of π.
+ *
+ * Used when multiple parameters are being passed in, so the number of cases
+ * becomes the square or more of this list. */
+export const sparseMinusThreePiToThreePiRangeForType = rangeForType(
+ [
+ -3 * Math.PI,
+ -2.5 * Math.PI,
+ -2.0 * Math.PI,
+ -1.5 * Math.PI,
+ -1.0 * Math.PI,
+ -0.5 * Math.PI,
+ 0,
+ 0.5 * Math.PI,
+ Math.PI,
+ 1.5 * Math.PI,
+ 2.0 * Math.PI,
+ 2.5 * Math.PI,
+ 3 * Math.PI,
+ ],
+ [-2n, -1n, 0n, 1n, 2n]
+);
/// The evaluation stages to test
export const kConstantAndOverrideStages = ['constant', 'override'] as const;
export type ConstantOrOverrideStage = 'constant' | 'override';
+export type ExecutionStage = 'constant' | 'override' | 'runtime';
/**
* @returns true if evaluation stage `stage` supports expressions of type @p.
*/
export function stageSupportsType(stage: ConstantOrOverrideStage, type: Type) {
- if (stage === 'override' && isAbstractType(elementType(type)!)) {
+ if (stage === 'override' && isAbstractType(elementTypeOf(type)!)) {
// Abstract numerics are concretized before being used in an override expression.
return false;
}
@@ -110,23 +148,26 @@ export function stageSupportsType(stage: ConstantOrOverrideStage, type: Type) {
* @param expectedResult false if an error is expected, true if no error is expected
* @param args the arguments to pass to the builtin
* @param stage the evaluation stage
+ * @param returnType the explicit return type of the result variable, if provided (implicit otherwise)
*/
export function validateConstOrOverrideBuiltinEval(
t: ShaderValidationTest,
builtin: string,
expectedResult: boolean,
args: Value[],
- stage: ConstantOrOverrideStage
+ stage: ConstantOrOverrideStage,
+ returnType?: Type
) {
- const elTys = args.map(arg => elementType(arg.type)!);
- const enables = elTys.some(ty => ty === TypeF16) ? 'enable f16;' : '';
+ const elTys = args.map(arg => elementTypeOf(arg.type)!);
+ const enables = elTys.some(ty => ty === Type.f16) ? 'enable f16;' : '';
+ const optionalVarType = returnType ? `: ${returnType.toString()}` : '';
switch (stage) {
case 'constant': {
t.expectCompileResult(
expectedResult,
`${enables}
-const v = ${builtin}(${args.map(arg => arg.wgsl()).join(', ')});`
+const v ${optionalVarType} = ${builtin}(${args.map(arg => arg.wgsl()).join(', ')});`
);
break;
}
@@ -138,7 +179,7 @@ const v = ${builtin}(${args.map(arg => arg.wgsl()).join(', ')});`
let numOverrides = 0;
for (const arg of args) {
const argOverrides: string[] = [];
- for (const el of elementsOf(arg)) {
+ for (const el of scalarElementsOf(arg)) {
const name = `o${numOverrides++}`;
overrideDecls.push(`override ${name} : ${el.type};`);
argOverrides.push(name);
@@ -150,7 +191,7 @@ const v = ${builtin}(${args.map(arg => arg.wgsl()).join(', ')});`
expectedResult,
code: `${enables}
${overrideDecls.join('\n')}
-var<private> v = ${builtin}(${callArgs.join(', ')});`,
+var<private> v ${optionalVarType} = ${builtin}(${callArgs.join(', ')});`,
constants,
reference: ['v'],
});
@@ -159,24 +200,92 @@ var<private> v = ${builtin}(${callArgs.join(', ')});`,
}
}
+/**
+ * Runs a validation test to check that evaluation of `binaryOp` either evaluates with or without
+ * error at shader creation time or pipeline creation time.
+ * @param t the ShaderValidationTest
+ * @param binaryOp the symbol of the binary operator
+ * @param expectedResult false if an error is expected, true if no error is expected
+ * @param leftStage the evaluation stage for the left argument
+ * @param left the left-hand side of the binary operation
+ * @param rightStage the evaluation stage for the right argument
+ * @param right the right-hand side of the binary operation
+ */
+export function validateConstOrOverrideBinaryOpEval(
+ t: ShaderValidationTest,
+ binaryOp: string,
+ expectedResult: boolean,
+ leftStage: ExecutionStage,
+ left: Value,
+ rightStage: ExecutionStage,
+ right: Value
+) {
+ const allArgs = [left, right];
+ const elTys = allArgs.map(arg => elementTypeOf(arg.type)!);
+ const enables = elTys.some(ty => ty === Type.f16) ? 'enable f16;' : '';
+
+ const codeLines = [enables];
+ const constants: Record<string, number> = {};
+ let numOverrides = 0;
+
+ function addOperand(name: string, stage: ExecutionStage, value: Value) {
+ switch (stage) {
+ case 'runtime':
+ assert(!isAbstractType(value.type));
+ codeLines.push(`var<private> ${name} = ${value.wgsl()};`);
+ return name;
+
+ case 'constant':
+ codeLines.push(`const ${name} = ${value.wgsl()};`);
+ return name;
+
+ case 'override': {
+ assert(!isAbstractType(value.type));
+ const argOverrides: string[] = [];
+ for (const el of scalarElementsOf(value)) {
+ const elName = `o${numOverrides++}`;
+ codeLines.push(`override ${elName} : ${el.type};`);
+ constants[elName] = Number(el.value);
+ argOverrides.push(elName);
+ }
+ return `${value.type}(${argOverrides.join(', ')})`;
+ }
+ }
+ }
+
+ const leftOperand = addOperand('left', leftStage, left);
+ const rightOperand = addOperand('right', rightStage, right);
+
+ if (leftStage === 'override' || rightStage === 'override') {
+ t.expectPipelineResult({
+ expectedResult,
+ code: codeLines.join('\n'),
+ constants,
+ reference: [`${leftOperand} ${binaryOp} ${rightOperand}`],
+ });
+ } else {
+ codeLines.push(`fn f() { _ = ${leftOperand} ${binaryOp} ${rightOperand}; }`);
+ t.expectCompileResult(expectedResult, codeLines.join('\n'));
+ }
+}
/** @returns a sweep of the representable values for element type of `type` */
-export function fullRangeForType(type: Type, count?: number) {
+export function fullRangeForType(type: Type, count?: number): readonly (number | bigint)[] {
if (count === undefined) {
count = 25;
}
- switch (elementType(type)?.kind) {
+ switch (scalarTypeOf(type)?.kind) {
case 'abstract-float':
- return fullF64Range({
+ return scalarF64Range({
pos_sub: Math.ceil((count * 1) / 5),
pos_norm: Math.ceil((count * 4) / 5),
});
case 'f32':
- return fullF32Range({
+ return scalarF32Range({
pos_sub: Math.ceil((count * 1) / 5),
pos_norm: Math.ceil((count * 4) / 5),
});
case 'f16':
- return fullF16Range({
+ return scalarF16Range({
pos_sub: Math.ceil((count * 1) / 5),
pos_norm: Math.ceil((count * 4) / 5),
});
@@ -186,6 +295,9 @@ export function fullRangeForType(type: Type, count?: number) {
);
case 'u32':
return linearRange(0, kValue.u32.max, count).map(f => Math.floor(f));
+ case 'abstract-int':
+ // Returned values are already ints, so don't need to be floored.
+ return linearRangeBigInt(kValue.i64.negative.min, kValue.i64.positive.max, count);
}
unreachable();
}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/cos.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/cos.spec.ts
index b65593ccaa..361cb8ed99 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/cos.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/cos.spec.ts
@@ -6,18 +6,17 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
import {
fullRangeForType,
kConstantAndOverrideStages,
- kMinus3PiTo3Pi,
+ minusThreePiToThreePiRangeForType,
stageSupportsType,
unique,
validateConstOrOverrideBuiltinEval,
@@ -25,7 +24,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -39,10 +38,15 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
.combine('type', keysOf(kValuesTypes))
.filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
.beginSubcases()
- .expand('value', u => unique(kMinus3PiTo3Pi, fullRangeForType(kValuesTypes[u.type])))
+ .expand('value', u =>
+ unique(
+ minusThreePiToThreePiRangeForType(kValuesTypes[u.type]),
+ fullRangeForType(kValuesTypes[u.type])
+ )
+ )
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
@@ -56,7 +60,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kIntegerArgumentTypes = objectsToRecord([Type.f32, ...kConcreteIntegerScalarsAndVectors]);
g.test('integer_argument')
.desc(
@@ -70,8 +74,41 @@ Validates that scalar and vector integer arguments are rejected by ${builtin}()
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ type === TypeF32,
+ /* expectedResult */ type === Type.f32,
[type.create(0)],
'constant'
);
});
+
+const kArgCases = {
+ good: '(1.1)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_2args: '(1.0,2.0)',
+ // Bad value type for arg 0
+ bad_0i32: '(1i)',
+ bad_0u32: '(1u)',
+ bad_0bool: '(false)',
+ bad_0vec2u: '(vec2u())',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/cosh.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/cosh.spec.ts
index 126fc19e7e..aeb5457675 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/cosh.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/cosh.spec.ts
@@ -6,11 +6,9 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { isRepresentable } from '../../../../../util/floating_point.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -24,7 +22,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -41,13 +39,17 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
.expand('value', u => fullRangeForType(kValuesTypes[u.type]))
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
.fn(t => {
const type = kValuesTypes[t.params.type];
- const expectedResult = isRepresentable(Math.cosh(t.params.value), elementType(type));
+ const expectedResult = isRepresentable(
+ Math.cosh(Number(t.params.value)),
+ // AbstractInt is converted to AbstractFloat before calling into the builtin
+ scalarTypeOf(type).kind === 'abstract-int' ? Type.abstractFloat : scalarTypeOf(type)
+ );
validateConstOrOverrideBuiltinEval(
t,
builtin,
@@ -57,22 +59,40 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kArgCases = {
+ good: '(1.2)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_2arg: '(1.2, 2.3)',
+ // Bad value for arg 0
+ bad_0bool: '(false)',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+ bad_0uint: '(1u)',
+ bad_0int: '(1i)',
+ bad_0vec2i: '(vec2i())',
+ bad_0vec2u: '(vec2u())',
+ bad_0vec3i: '(vec3i())',
+ bad_0vec3u: '(vec3u())',
+ bad_0vec4i: '(vec4i())',
+ bad_0vec4u: '(vec4u())',
+};
-g.test('integer_argument')
- .desc(
- `
-Validates that scalar and vector integer arguments are rejected by ${builtin}()
-`
- )
- .params(u => u.combine('type', keysOf(kIntegerArgumentTypes)))
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
.fn(t => {
- const type = kIntegerArgumentTypes[t.params.type];
- validateConstOrOverrideBuiltinEval(
- t,
- builtin,
- /* expectedResult */ type === TypeF32,
- [type.create(0)],
- 'constant'
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
);
});
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/countLeadingZeros.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/countLeadingZeros.spec.ts
new file mode 100644
index 0000000000..15791edc80
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/countLeadingZeros.spec.ts
@@ -0,0 +1,198 @@
+const builtin = 'countLeadingZeros';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kFloatScalarsAndVectors,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kConcreteIntegerScalarsAndVectors);
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() never errors
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValuesTypes))
+ .filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
+ .beginSubcases()
+ .expand('value', u => fullRangeForType(kValuesTypes[u.type]))
+ )
+ .fn(t => {
+ const expectedResult = true; // countLeadingZeros() should never error
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [kValuesTypes[t.params.type].create(t.params.value)],
+ t.params.stage
+ );
+ });
+
+// u32 is included here to confirm that validation is failing due to a type issue and not something else.
+const kFloatTypes = objectsToRecord([Type.u32, ...kFloatScalarsAndVectors]);
+
+g.test('float_argument')
+ .desc(
+ `
+Validates that float arguments are rejected by ${builtin}()
+`
+ )
+ .params(u => u.combine('type', keysOf(kFloatTypes)))
+ .fn(t => {
+ const type = kFloatTypes[t.params.type];
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ /* expectedResult */ type === Type.u32,
+ [type.create(0)],
+ 'constant'
+ );
+ });
+
+const kTests: {
+ readonly [name: string]: {
+ /** Arguments to pass to the builtin with parentheses. */
+ readonly args: string;
+ /** Should the test case pass. */
+ readonly pass: boolean;
+ /** Additional setup code in the function scope. */
+ readonly preamble?: string;
+ };
+} = {
+ valid: {
+ args: '(1u)',
+ pass: true,
+ },
+ // Number of arguments.
+ no_parens: {
+ args: '',
+ pass: false,
+ },
+ too_few_args: {
+ args: '()',
+ pass: false,
+ },
+ too_many_args: {
+ args: '(1u,2u)',
+ pass: false,
+ },
+ // Arguments types (only 1 argument for this builtin).
+ alias: {
+ args: '(u32_alias(1))',
+ pass: true,
+ },
+ bool: {
+ args: '(false)',
+ pass: false,
+ },
+ vec_bool: {
+ args: '(vec2<bool>(false,true))',
+ pass: false,
+ },
+ matrix: {
+ args: '(mat2x2(1,1,1,1))',
+ pass: false,
+ },
+ atomic: {
+ args: '(a)',
+ pass: false,
+ },
+ array: {
+ preamble: 'var arry: array<u32, 5>;',
+ args: '(arry)',
+ pass: false,
+ },
+ array_runtime: {
+ args: '(k.arry)',
+ pass: false,
+ },
+ struct: {
+ preamble: 'var x: A;',
+ args: '(x)',
+ pass: false,
+ },
+ enumerant: {
+ args: '(read_write)',
+ pass: false,
+ },
+ ptr: {
+ preamble: `var<function> f = 1u;
+ let p: ptr<function, u32> = &f;`,
+ args: '(p)',
+ pass: false,
+ },
+ ptr_deref: {
+ preamble: `var<function> f = 1u;
+ let p: ptr<function, u32> = &f;`,
+ args: '(*p)',
+ pass: true,
+ },
+ sampler: {
+ args: '(s)',
+ pass: false,
+ },
+ texture: {
+ args: '(t)',
+ pass: false,
+ },
+};
+
+g.test('arguments')
+ .desc(`Test compilation validation of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('test', keysOf(kTests)))
+ .fn(t => {
+ const test = kTests[t.params.test];
+ t.expectCompileResult(
+ test.pass,
+ `alias u32_alias = u32;
+
+ @group(0) @binding(0) var s: sampler;
+ @group(0) @binding(1) var t: texture_2d<f32>;
+
+ var<workgroup> a: atomic<u32>;
+
+ struct A {
+ i: u32,
+ }
+ struct B {
+ arry: array<u32>,
+ }
+ @group(0) @binding(3) var<storage> k: B;
+
+
+ @vertex
+ fn main() -> @builtin(position) vec4<f32> {
+ ${test.preamble ? test.preamble : ''}
+ _ = ${builtin}${test.args};
+ return vec4<f32>(.4, .2, .3, .1);
+ }`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}(1u); }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/countOneBits.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/countOneBits.spec.ts
new file mode 100644
index 0000000000..b083c7ca4b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/countOneBits.spec.ts
@@ -0,0 +1,198 @@
+const builtin = 'countOneBits';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kFloatScalarsAndVectors,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kConcreteIntegerScalarsAndVectors);
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() never errors
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValuesTypes))
+ .filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
+ .beginSubcases()
+ .expand('value', u => fullRangeForType(kValuesTypes[u.type]))
+ )
+ .fn(t => {
+ const expectedResult = true; // countOneBits() should never error
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [kValuesTypes[t.params.type].create(t.params.value)],
+ t.params.stage
+ );
+ });
+
+// u32 is included here to confirm that validation is failing due to a type issue and not something else.
+const kFloatTypes = objectsToRecord([Type.u32, ...kFloatScalarsAndVectors]);
+
+g.test('float_argument')
+ .desc(
+ `
+Validates that float arguments are rejected by ${builtin}()
+`
+ )
+ .params(u => u.combine('type', keysOf(kFloatTypes)))
+ .fn(t => {
+ const type = kFloatTypes[t.params.type];
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ /* expectedResult */ type === Type.u32,
+ [type.create(0)],
+ 'constant'
+ );
+ });
+
+const kTests: {
+ readonly [name: string]: {
+ /** Arguments to pass to the builtin with parentheses. */
+ readonly args: string;
+ /** Should the test case pass. */
+ readonly pass: boolean;
+ /** Additional setup code in the function scope. */
+ readonly preamble?: string;
+ };
+} = {
+ valid: {
+ args: '(1u)',
+ pass: true,
+ },
+ // Number of arguments.
+ no_parens: {
+ args: '',
+ pass: false,
+ },
+ too_few_args: {
+ args: '()',
+ pass: false,
+ },
+ too_many_args: {
+ args: '(1u,2u)',
+ pass: false,
+ },
+ // Arguments types (only 1 argument for this builtin).
+ alias: {
+ args: '(u32_alias(1))',
+ pass: true,
+ },
+ bool: {
+ args: '(false)',
+ pass: false,
+ },
+ vec_bool: {
+ args: '(vec2<bool>(false,true))',
+ pass: false,
+ },
+ matrix: {
+ args: '(mat2x2(1,1,1,1))',
+ pass: false,
+ },
+ atomic: {
+ args: '(a)',
+ pass: false,
+ },
+ array: {
+ preamble: 'var arry: array<u32, 5>;',
+ args: '(arry)',
+ pass: false,
+ },
+ array_runtime: {
+ args: '(k.arry)',
+ pass: false,
+ },
+ struct: {
+ preamble: 'var x: A;',
+ args: '(x)',
+ pass: false,
+ },
+ enumerant: {
+ args: '(read_write)',
+ pass: false,
+ },
+ ptr: {
+ preamble: `var<function> f = 1u;
+ let p: ptr<function, u32> = &f;`,
+ args: '(p)',
+ pass: false,
+ },
+ ptr_deref: {
+ preamble: `var<function> f = 1u;
+ let p: ptr<function, u32> = &f;`,
+ args: '(*p)',
+ pass: true,
+ },
+ sampler: {
+ args: '(s)',
+ pass: false,
+ },
+ texture: {
+ args: '(t)',
+ pass: false,
+ },
+};
+
+g.test('arguments')
+ .desc(`Test compilation validation of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('test', keysOf(kTests)))
+ .fn(t => {
+ const test = kTests[t.params.test];
+ t.expectCompileResult(
+ test.pass,
+ `alias u32_alias = u32;
+
+ @group(0) @binding(0) var s: sampler;
+ @group(0) @binding(1) var t: texture_2d<f32>;
+
+ var<workgroup> a: atomic<u32>;
+
+ struct A {
+ i: u32,
+ }
+ struct B {
+ arry: array<u32>,
+ }
+ @group(0) @binding(3) var<storage> k: B;
+
+
+ @vertex
+ fn main() -> @builtin(position) vec4<f32> {
+ ${test.preamble ? test.preamble : ''}
+ _ = ${builtin}${test.args};
+ return vec4<f32>(.4, .2, .3, .1);
+ }`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}(1u); }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/countTrailingZeros.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/countTrailingZeros.spec.ts
new file mode 100644
index 0000000000..800537d348
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/countTrailingZeros.spec.ts
@@ -0,0 +1,198 @@
+const builtin = 'countTrailingZeros';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kFloatScalarsAndVectors,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kConcreteIntegerScalarsAndVectors);
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() never errors
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValuesTypes))
+ .filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
+ .beginSubcases()
+ .expand('value', u => fullRangeForType(kValuesTypes[u.type]))
+ )
+ .fn(t => {
+ const expectedResult = true; // countTrailingZeros() should never error
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [kValuesTypes[t.params.type].create(t.params.value)],
+ t.params.stage
+ );
+ });
+
+// u32 is included here to confirm that validation is failing due to a type issue and not something else.
+const kFloatTypes = objectsToRecord([Type.u32, ...kFloatScalarsAndVectors]);
+
+g.test('float_argument')
+ .desc(
+ `
+Validates that float arguments are rejected by ${builtin}()
+`
+ )
+ .params(u => u.combine('type', keysOf(kFloatTypes)))
+ .fn(t => {
+ const type = kFloatTypes[t.params.type];
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ /* expectedResult */ type === Type.u32,
+ [type.create(0)],
+ 'constant'
+ );
+ });
+
+const kTests: {
+ readonly [name: string]: {
+ /** Arguments to pass to the builtin with parentheses. */
+ readonly args: string;
+ /** Should the test case pass. */
+ readonly pass: boolean;
+ /** Additional setup code in the function scope. */
+ readonly preamble?: string;
+ };
+} = {
+ valid: {
+ args: '(1u)',
+ pass: true,
+ },
+ // Number of arguments.
+ no_parens: {
+ args: '',
+ pass: false,
+ },
+ too_few_args: {
+ args: '()',
+ pass: false,
+ },
+ too_many_args: {
+ args: '(1u,2u)',
+ pass: false,
+ },
+ // Arguments types (only 1 argument for this builtin).
+ alias: {
+ args: '(u32_alias(1))',
+ pass: true,
+ },
+ bool: {
+ args: '(false)',
+ pass: false,
+ },
+ vec_bool: {
+ args: '(vec2<bool>(false,true))',
+ pass: false,
+ },
+ matrix: {
+ args: '(mat2x2(1,1,1,1))',
+ pass: false,
+ },
+ atomic: {
+ args: '(a)',
+ pass: false,
+ },
+ array: {
+ preamble: 'var arry: array<u32, 5>;',
+ args: '(arry)',
+ pass: false,
+ },
+ array_runtime: {
+ args: '(k.arry)',
+ pass: false,
+ },
+ struct: {
+ preamble: 'var x: A;',
+ args: '(x)',
+ pass: false,
+ },
+ enumerant: {
+ args: '(read_write)',
+ pass: false,
+ },
+ ptr: {
+ preamble: `var<function> f = 1u;
+ let p: ptr<function, u32> = &f;`,
+ args: '(p)',
+ pass: false,
+ },
+ ptr_deref: {
+ preamble: `var<function> f = 1u;
+ let p: ptr<function, u32> = &f;`,
+ args: '(*p)',
+ pass: true,
+ },
+ sampler: {
+ args: '(s)',
+ pass: false,
+ },
+ texture: {
+ args: '(t)',
+ pass: false,
+ },
+};
+
+g.test('arguments')
+ .desc(`Test compilation validation of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('test', keysOf(kTests)))
+ .fn(t => {
+ const test = kTests[t.params.test];
+ t.expectCompileResult(
+ test.pass,
+ `alias u32_alias = u32;
+
+ @group(0) @binding(0) var s: sampler;
+ @group(0) @binding(1) var t: texture_2d<f32>;
+
+ var<workgroup> a: atomic<u32>;
+
+ struct A {
+ i: u32,
+ }
+ struct B {
+ arry: array<u32>,
+ }
+ @group(0) @binding(3) var<storage> k: B;
+
+
+ @vertex
+ fn main() -> @builtin(position) vec4<f32> {
+ ${test.preamble ? test.preamble : ''}
+ _ = ${builtin}${test.args};
+ return vec4<f32>(.4, .2, .3, .1);
+ }`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}(1u); }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/cross.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/cross.spec.ts
new file mode 100644
index 0000000000..35dacb65d8
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/cross.spec.ts
@@ -0,0 +1,122 @@
+const builtin = 'cross';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConvertableToFloatVec3,
+ scalarTypeOf,
+ ScalarType,
+} from '../../../../../util/conversion.js';
+import { QuantizeFunc, quantizeToF16, quantizeToF32 } from '../../../../../util/math.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValidArgumentTypes = objectsToRecord(kConvertableToFloatVec3);
+
+function quantizeFunctionForScalarType(type: ScalarType): QuantizeFunc<number> {
+ switch (type) {
+ case Type.f32:
+ return quantizeToF32;
+ case Type.f16:
+ return quantizeToF16;
+ default:
+ return (v: number) => v;
+ }
+}
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() never errors
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValidArgumentTypes))
+ .filter(u => stageSupportsType(u.stage, kValidArgumentTypes[u.type]))
+ .beginSubcases()
+ .expand('a', u => fullRangeForType(kValidArgumentTypes[u.type], 5))
+ .expand('b', u => fullRangeForType(kValidArgumentTypes[u.type], 5))
+ )
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kValidArgumentTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ let expectedResult = true;
+
+ const scalarType = scalarTypeOf(kValidArgumentTypes[t.params.type]);
+ const quantizeFn = quantizeFunctionForScalarType(scalarType);
+
+ // Should be invalid if the cross product calculations result in intermediate
+ // values that exceed the maximum representable float value for the given type.
+ const a = Number(t.params.a);
+ const b = Number(t.params.b);
+ const ab = quantizeFn(a * b);
+ if (ab === Infinity || ab === -Infinity) {
+ expectedResult = false;
+ }
+
+ const type = kValidArgumentTypes[t.params.type];
+
+ // Validates cross(vec3(a, a, a), vec3(b, b, b));
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.a), type.create(t.params.b)],
+ t.params.stage
+ );
+ });
+
+const kArgCases = {
+ good: '(vec3(0), vec3(1))',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_1arg: '(1.0)',
+ bad_3arg: '(1.0, 2.0, 3.0)',
+ // Wrong vector size
+ bad_vec2: '(vec2(0), vec2(1))',
+ bad_vec4: '(vec4(0), vec4(1))',
+ // Bad value for arg 0
+ bad_0bool: '(false, vec3(1))',
+ bad_0array: '(array(1.1,2.2), vec3(1))',
+ bad_0struct: '(modf(2.2), vec3(1))',
+ // Bad value type for arg 1
+ bad_1bool: '(vec3(0), true)',
+ bad_1array: '(vec3(0), array(1.1,2.2))',
+ bad_1struct: '(vec3(0), modf(2.2))',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/degrees.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/degrees.spec.ts
index 154455857d..058f6ffa6c 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/degrees.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/degrees.spec.ts
@@ -6,11 +6,10 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { isRepresentable } from '../../../../../util/floating_point.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -24,7 +23,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -41,13 +40,17 @@ Validates that constant evaluation and override evaluation of ${builtin}() input
.expand('value', u => fullRangeForType(kValuesTypes[u.type]))
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
.fn(t => {
const type = kValuesTypes[t.params.type];
- const expectedResult = isRepresentable((t.params.value * 180) / Math.PI, elementType(type));
+ const expectedResult = isRepresentable(
+ (Number(t.params.value) * 180) / Math.PI,
+ // AbstractInt is converted to AbstractFloat before calling into the builtin
+ scalarTypeOf(type).kind === 'abstract-int' ? Type.abstractFloat : scalarTypeOf(type)
+ );
validateConstOrOverrideBuiltinEval(
t,
builtin,
@@ -57,7 +60,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() input
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kIntegerArgumentTypes = objectsToRecord([Type.f32, ...kConcreteIntegerScalarsAndVectors]);
g.test('integer_argument')
.desc(
@@ -71,8 +74,41 @@ Validates that scalar and vector integer arguments are rejected by ${builtin}()
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ type === TypeF32,
+ /* expectedResult */ type === Type.f32,
[type.create(1)],
'constant'
);
});
+
+const kArgCases = {
+ good: '(1.1)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_too_few: '()',
+ bad_too_many: '(1.0,2.0)',
+ // Bad value type for arg 0
+ bad_0i32: '(1i)',
+ bad_0u32: '(1u)',
+ bad_0bool: '(false)',
+ bad_0vec2u: '(vec2u())',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/derivatives.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/derivatives.spec.ts
new file mode 100644
index 0000000000..54620ce179
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/derivatives.spec.ts
@@ -0,0 +1,129 @@
+export const description = `
+Validation tests for derivative builtins.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConcreteF16ScalarsAndVectors,
+ scalarTypeOf,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kDerivativeBuiltins = [
+ 'dpdx',
+ 'dpdxCoarse',
+ 'dpdxFine',
+ 'dpdy',
+ 'dpdyCoarse',
+ 'dpdyFine',
+ 'fwidth',
+ 'fwidthCoarse',
+ 'fwidthFine',
+];
+
+const kEntryPoints = {
+ none: { supportsDerivative: true, code: `` },
+ fragment: {
+ supportsDerivative: true,
+ code: `@fragment
+fn main() {
+ foo();
+}`,
+ },
+ vertex: {
+ supportsDerivative: false,
+ code: `@vertex
+fn main() -> @builtin(position) vec4f {
+ foo();
+ return vec4f();
+}`,
+ },
+ compute: {
+ supportsDerivative: false,
+ code: `@compute @workgroup_size(1)
+fn main() {
+ foo();
+}`,
+ },
+ fragment_and_compute: {
+ supportsDerivative: false,
+ code: `@fragment
+fn main1() {
+ foo();
+}
+
+@compute @workgroup_size(1)
+fn main2() {
+ foo();
+}
+`,
+ },
+ compute_without_call: {
+ supportsDerivative: true,
+ code: `@compute @workgroup_size(1)
+fn main() {
+}
+`,
+ },
+};
+
+g.test('only_in_fragment')
+ .specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions')
+ .desc(
+ `
+Derivative functions must only be used in the fragment shader stage.
+`
+ )
+ .params(u =>
+ u.combine('entry_point', keysOf(kEntryPoints)).combine('call', ['bar', ...kDerivativeBuiltins])
+ )
+ .fn(t => {
+ const config = kEntryPoints[t.params.entry_point];
+ const code = `
+${config.code}
+fn bar(f : f32) -> f32 { return f; }
+
+fn foo() {
+ _ = ${t.params.call}(1.0);
+}`;
+ t.expectCompileResult(t.params.call === 'bar' || config.supportsDerivative, code);
+ });
+
+// The list of invalid argument types to test, with an f32 control case.
+const kArgumentTypes = objectsToRecord([
+ Type.f32,
+ ...kConcreteIntegerScalarsAndVectors,
+ ...kConcreteF16ScalarsAndVectors,
+ Type.mat2x2f,
+]);
+
+g.test('invalid_argument_types')
+ .specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions')
+ .desc(
+ `
+Derivative builtins only accept f32 scalar and vector types.
+`
+ )
+ .params(u =>
+ u.combine('type', keysOf(kArgumentTypes)).combine('call', ['', ...kDerivativeBuiltins])
+ )
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kArgumentTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const type = kArgumentTypes[t.params.type];
+ const code = `
+${scalarTypeOf(kArgumentTypes[t.params.type]) === Type.f16 ? 'enable f16;' : ''}
+
+fn foo() {
+ let x: ${type.toString()} = ${t.params.call}(${type.create(1).wgsl()});
+}`;
+ t.expectCompileResult(kArgumentTypes[t.params.type] === Type.f32 || t.params.call === '', code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/determinant.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/determinant.spec.ts
new file mode 100644
index 0000000000..1974e7ef99
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/determinant.spec.ts
@@ -0,0 +1,95 @@
+const builtin = 'determinant';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+// Generate a dictionary mapping each matrix type variation (columns,rows,
+// floating point type) to a nontrivial matrix value of that type.
+const kMatrixCases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ (['abstract-int', 'abstract-float', 'f32', 'f16'] as const).map(type => ({
+ [`mat${cols}x${rows}_${type}`]: (() => {
+ const suffix = (() => {
+ switch (type) {
+ case 'abstract-int':
+ return '';
+ case 'abstract-float':
+ return '.0';
+ case 'f32':
+ return 'f';
+ case 'f16':
+ return 'h';
+ }
+ })();
+ return `(mat${cols}x${rows}(${[...Array(cols * rows).keys()]
+ .map(e => `${e}${suffix}`)
+ .join(', ')}))`;
+ })(),
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+g.test('matrix_args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped matrices`)
+ .params(u =>
+ u
+ .combine('cols', [2, 3, 4] as const)
+ .combine('rows', [2, 3, 4] as const)
+ .combine('type', ['abstract-int', 'abstract-float', 'f32', 'f16'] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const cols = t.params.cols;
+ const rows = t.params.rows;
+ const type = t.params.type;
+ const arg = kMatrixCases[`mat${cols}x${rows}_${type}`];
+ t.expectCompileResult(
+ cols === rows,
+ t.wrapInEntryPoint(`const c = ${builtin}${arg};`, type === 'f16' ? ['f16'] : [])
+ );
+ });
+
+const kArgCases = {
+ good: '(mat2x2(0.0, 2.0, 3.0, 4.0))', // Included to check test implementation
+ bad_no_parens: '',
+ // Bad number of args
+ bad_too_few: '()',
+ bad_too_many: '(mat2x2(0.0, 2.0, 3.0, 4.0), mat2x2(0.0, 2.0, 3.0, 4.0))',
+ // Bad value type for arg 0
+ bad_0i32: '(1i)',
+ bad_0u32: '(1u)',
+ bad_0bool: '(false)',
+ bad_0vec2u: '(vec2u())',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/distance.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/distance.spec.ts
new file mode 100644
index 0000000000..e41a8dd31c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/distance.spec.ts
@@ -0,0 +1,149 @@
+const builtin = 'distance';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
+ ScalarType,
+} from '../../../../../util/conversion.js';
+import { QuantizeFunc, quantizeToF16, quantizeToF32 } from '../../../../../util/math.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValidArgumentTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
+
+function quantizeFunctionForScalarType(type: ScalarType): QuantizeFunc<number> {
+ switch (type) {
+ case Type.f32:
+ return quantizeToF32;
+ case Type.f16:
+ return quantizeToF16;
+ default:
+ return (v: number) => v;
+ }
+}
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() never errors
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValidArgumentTypes))
+ .filter(u => stageSupportsType(u.stage, kValidArgumentTypes[u.type]))
+ .beginSubcases()
+ .expand('a', u => fullRangeForType(kValidArgumentTypes[u.type], 5))
+ .expand('b', u => fullRangeForType(kValidArgumentTypes[u.type], 5))
+ )
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kValidArgumentTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ let expectedResult = true;
+
+ const scalarType = scalarTypeOf(kValidArgumentTypes[t.params.type]);
+ const quantizeFn = quantizeFunctionForScalarType(scalarType);
+
+ // Distance equation: length(a - b)
+ // Should be invalid if the calculations result in intermediate values that
+ // exceed the maximum representable float value for the given type.
+ const a = Number(t.params.a);
+ const b = Number(t.params.b);
+ const ab = quantizeFn(a - b);
+
+ if (!Number.isFinite(ab)) {
+ expectedResult = false;
+ }
+
+ // Only calculates the full length if the type is a vector. Otherwise abs(a-b) is used.
+ if (kValidArgumentTypes[t.params.type].width > 1) {
+ const ab2 = quantizeFn(ab * ab);
+ const sqrLen = quantizeFn(ab2 * kValidArgumentTypes[t.params.type].width);
+ // Square root does not need to be calculated because it can never fail if
+ // the previous results are finite.
+
+ if (!Number.isFinite(ab2) || !Number.isFinite(sqrLen)) {
+ expectedResult = false;
+ }
+ }
+
+ const type = kValidArgumentTypes[t.params.type];
+
+ // Validates distance(vecN(a), vecN(b)) or distance(a, b);
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.a), type.create(t.params.b)],
+ t.params.stage
+ );
+ });
+
+const kArgCases = {
+ good: '(vec3(0), vec3(1))',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_1arg: '(vec3(0))',
+ bad_3arg: '(vec3(0), vec3(1), vec3(2))',
+ // Bad value for arg 0
+ bad_0bool: '(false, vec3(1))',
+ bad_0array: '(array(1.1,2.2), vec3(1))',
+ bad_0struct: '(modf(2.2), vec3(1))',
+ bad_0int: '(0i, vec3(1))',
+ bad_0vec2i: '(vec2i(), vec3(1))',
+ bad_0vec3i: '(vec3i(), vec3(1))',
+ bad_0vec4i: '(vec4i(), vec3(1))',
+ bad_0uint: '(0u, vec3(1))',
+ bad_0vec2u: '(vec2u(), vec3(1))',
+ bad_0vec3u: '(vec3u(), vec3(1))',
+ bad_0vec4u: '(vec4u(), vec3(1))',
+ // Bad value type for arg 1
+ bad_1bool: '(vec3(0), true)',
+ bad_1array: '(vec3(0), array(1.1,2.2))',
+ bad_1struct: '(vec3(0), modf(2.2))',
+ bad_1int: '(vec3(0), 0i)',
+ bad_1vec2i: '(vec3(0), vec2i())',
+ bad_1vec3i: '(vec3(0), vec3i())',
+ bad_1vec4i: '(vec3(0), vec4i())',
+ bad_1uint: '(vec3(0), 0u)',
+ bad_1vec2u: '(vec3(0), vec2u())',
+ bad_1vec3u: '(vec3(0), vec3u())',
+ bad_1vec4u: '(vec3(0), vec4u())',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/dot4I8Packed.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/dot4I8Packed.spec.ts
new file mode 100644
index 0000000000..c079e08cb1
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/dot4I8Packed.spec.ts
@@ -0,0 +1,66 @@
+export const description = `Validate dot4I8Packed`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kFeature = 'packed_4x8_integer_dot_product';
+const kFn = 'dot4I8Packed';
+const kArgCases = {
+ good: '(1u,2u)',
+ bad_0args: '()',
+ bad_1args: '(1u)',
+ bad_3args: '(1u,2u,3u)',
+ bad_0i32: '(1i,2u)',
+ bad_0f32: '(1f,2u)',
+ bad_0bool: '(false,2u)',
+ bad_0vec2u: '(vec2u(),2u)',
+ bad_1i32: '(1u,2i)',
+ bad_1f32: '(1u,2f)',
+ bad_1bool: '(1u,true)',
+ bad_1vec2u: '(1u,vec2u())',
+ bad_bool_bool: '(false,true)',
+ bad_bool2_bool2: '(vec2<bool>(),vec2(false,true))',
+ bad_0array: '(array(1))',
+ bad_0struct: '(modf(1.1))',
+};
+const kGoodArgs = kArgCases['good'];
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('unsupported')
+ .desc(`Test absence of ${kFn} when ${kFeature} is not supported.`)
+ .params(u => u.combine('requires', [false, true]))
+ .fn(t => {
+ t.skipIfLanguageFeatureSupported(kFeature);
+ const preamble = t.params.requires ? `requires ${kFeature}; ` : '';
+ const code = `${preamble}const c = ${kFn}${kGoodArgs};`;
+ t.expectCompileResult(false, code);
+ });
+
+g.test('supported')
+ .desc(`Test presence of ${kFn} when ${kFeature} is supported.`)
+ .params(u => u.combine('requires', [false, true]))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ const preamble = t.params.requires ? `requires ${kFeature}; ` : '';
+ const code = `${preamble}const c = ${kFn}${kGoodArgs};`;
+ t.expectCompileResult(true, code);
+ });
+
+g.test('args')
+ .desc(`Test compilation failure of ${kFn} with various numbers of and types of arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ t.expectCompileResult(t.params.arg === 'good', `const c = ${kFn}${kArgCases[t.params.arg]};`);
+ });
+
+g.test('must_use')
+ .desc(`Result of ${kFn} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${kFn}${kGoodArgs}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/dot4U8Packed.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/dot4U8Packed.spec.ts
new file mode 100644
index 0000000000..bd9356777e
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/dot4U8Packed.spec.ts
@@ -0,0 +1,66 @@
+export const description = `Validate dot4U8Packed`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kFeature = 'packed_4x8_integer_dot_product';
+const kFn = 'dot4U8Packed';
+const kArgCases = {
+ good: '(1u,2u)',
+ bad_0args: '()',
+ bad_1args: '(1u)',
+ bad_3args: '(1u,2u,3u)',
+ bad_0i32: '(1i,2u)',
+ bad_0f32: '(1f,2u)',
+ bad_0bool: '(false,2u)',
+ bad_0vec2u: '(vec2u(),2u)',
+ bad_1i32: '(1u,2i)',
+ bad_1f32: '(1u,2f)',
+ bad_1bool: '(1u,true)',
+ bad_1vec2u: '(1u,vec2u())',
+ bad_bool_bool: '(false,true)',
+ bad_bool2_bool2: '(vec2<bool>(),vec2(false,true))',
+ bad_0array: '(array(1))',
+ bad_0struct: '(modf(1.1))',
+};
+const kGoodArgs = kArgCases['good'];
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('unsupported')
+ .desc(`Test absence of ${kFn} when ${kFeature} is not supported.`)
+ .params(u => u.combine('requires', [false, true]))
+ .fn(t => {
+ t.skipIfLanguageFeatureSupported(kFeature);
+ const preamble = t.params.requires ? `requires ${kFeature}; ` : '';
+ const code = `${preamble}const c = ${kFn}${kGoodArgs};`;
+ t.expectCompileResult(false, code);
+ });
+
+g.test('supported')
+ .desc(`Test presence of ${kFn} when ${kFeature} is supported.`)
+ .params(u => u.combine('requires', [false, true]))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ const preamble = t.params.requires ? `requires ${kFeature}; ` : '';
+ const code = `${preamble}const c = ${kFn}${kGoodArgs};`;
+ t.expectCompileResult(true, code);
+ });
+
+g.test('args')
+ .desc(`Test compilation failure of ${kFn} with various numbers of and types of arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ t.expectCompileResult(t.params.arg === 'good', `const c = ${kFn}${kArgCases[t.params.arg]};`);
+ });
+
+g.test('must_use')
+ .desc(`Result of ${kFn} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${kFn}${kGoodArgs}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/exp.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/exp.spec.ts
index 244e91f2ae..f0a9b217b1 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/exp.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/exp.spec.ts
@@ -7,24 +7,52 @@ import { makeTestGroup } from '../../../../../../common/framework/test_group.js'
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import { kValue } from '../../../../../util/constants.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { isRepresentable } from '../../../../../util/floating_point.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
import {
kConstantAndOverrideStages,
+ rangeForType,
stageSupportsType,
validateConstOrOverrideBuiltinEval,
} from './const_override_validation.js';
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
+
+const valueForType = rangeForType(
+ [
+ -1e2,
+ -1e3,
+ -4,
+ -3,
+ -2,
+ -1,
+ -1e-1,
+ -1e-2,
+ -1e-3,
+ 0,
+ 1e-3,
+ 1e-2,
+ 1e-1,
+ 1,
+ 2,
+ 3,
+ 4,
+ 1e2,
+ 1e3,
+ Math.log2(kValue.f16.positive.max) - 0.1,
+ Math.log2(kValue.f16.positive.max) + 0.1,
+ Math.log2(kValue.f32.positive.max) - 0.1,
+ Math.log2(kValue.f32.positive.max) + 0.1,
+ ],
+ [-100n, -1000n, -4n, -3n, -2n, -1n, 0n, 1n, 2n, 3n, 4n, 100n, 1000n]
+);
g.test('values')
.desc(
@@ -38,40 +66,20 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
.combine('type', keysOf(kValuesTypes))
.filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
.beginSubcases()
- .combine('value', [
- -1e2,
- -1e3,
- -4,
- -3,
- -2,
- -1,
- -1e-1,
- -1e-2,
- -1e-3,
- 0,
- 1e-3,
- 1e-2,
- 1e-1,
- 1,
- 2,
- 3,
- 4,
- 1e2,
- 1e3,
- Math.log2(kValue.f16.positive.max) - 0.1,
- Math.log2(kValue.f16.positive.max) + 0.1,
- Math.log2(kValue.f32.positive.max) - 0.1,
- Math.log2(kValue.f32.positive.max) + 0.1,
- ])
+ .expand('value', u => valueForType(kValuesTypes[u.type]))
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
.fn(t => {
const type = kValuesTypes[t.params.type];
- const expectedResult = isRepresentable(Math.exp(t.params.value), elementType(type));
+ const expectedResult = isRepresentable(
+ Math.exp(Number(t.params.value)),
+ // AbstractInt is converted to AbstractFloat before calling into the builtin
+ scalarTypeOf(type).kind === 'abstract-int' ? Type.abstractFloat : scalarTypeOf(type)
+ );
validateConstOrOverrideBuiltinEval(
t,
builtin,
@@ -81,22 +89,40 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kArgCases = {
+ good: '(1.2)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_2arg: '(1.2, 2.3)',
+ // Bad value for arg 0
+ bad_0bool: '(false)',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+ bad_0uint: '(1u)',
+ bad_0int: '(1i)',
+ bad_0vec2i: '(vec2i())',
+ bad_0vec2u: '(vec2u())',
+ bad_0vec3i: '(vec3i())',
+ bad_0vec3u: '(vec3u())',
+ bad_0vec4i: '(vec4i())',
+ bad_0vec4u: '(vec4u())',
+};
-g.test('integer_argument')
- .desc(
- `
-Validates that scalar and vector integer arguments are rejected by ${builtin}()
-`
- )
- .params(u => u.combine('type', keysOf(kIntegerArgumentTypes)))
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
.fn(t => {
- const type = kIntegerArgumentTypes[t.params.type];
- validateConstOrOverrideBuiltinEval(
- t,
- builtin,
- /* expectedResult */ type === TypeF32,
- [type.create(0)],
- 'constant'
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
);
});
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/exp2.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/exp2.spec.ts
index 9addbc076b..0cf3d03542 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/exp2.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/exp2.spec.ts
@@ -7,24 +7,52 @@ import { makeTestGroup } from '../../../../../../common/framework/test_group.js'
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import { kValue } from '../../../../../util/constants.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { isRepresentable } from '../../../../../util/floating_point.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
import {
kConstantAndOverrideStages,
+ rangeForType,
stageSupportsType,
validateConstOrOverrideBuiltinEval,
} from './const_override_validation.js';
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
+
+const valueForType = rangeForType(
+ [
+ -1e2,
+ -1e3,
+ -4,
+ -3,
+ -2,
+ -1,
+ -1e-1,
+ -1e-2,
+ -1e-3,
+ 0,
+ 1e-3,
+ 1e-2,
+ 1e-1,
+ 1,
+ 2,
+ 3,
+ 4,
+ 1e2,
+ 1e3,
+ Math.log2(kValue.f16.positive.max) - 0.1,
+ Math.log2(kValue.f16.positive.max) + 0.1,
+ Math.log2(kValue.f32.positive.max) - 0.1,
+ Math.log2(kValue.f32.positive.max) + 0.1,
+ ],
+ [-100n, -1000n, -4n, -3n, -2n, -1n, 0n, 1n, 2n, 3n, 4n, 100n, 1000n]
+);
g.test('values')
.desc(
@@ -38,40 +66,20 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
.combine('type', keysOf(kValuesTypes))
.filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
.beginSubcases()
- .combine('value', [
- -1e2,
- -1e3,
- -4,
- -3,
- -2,
- -1,
- -1e-1,
- -1e-2,
- -1e-3,
- 0,
- 1e-3,
- 1e-2,
- 1e-1,
- 1,
- 2,
- 3,
- 4,
- 1e2,
- 1e3,
- Math.log2(kValue.f16.positive.max) - 0.1,
- Math.log2(kValue.f16.positive.max) + 0.1,
- Math.log2(kValue.f32.positive.max) - 0.1,
- Math.log2(kValue.f32.positive.max) + 0.1,
- ])
+ .expand('value', u => valueForType(kValuesTypes[u.type]))
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
.fn(t => {
const type = kValuesTypes[t.params.type];
- const expectedResult = isRepresentable(Math.pow(2, t.params.value), elementType(type));
+ const expectedResult = isRepresentable(
+ Math.pow(2, Number(t.params.value)),
+ // AbstractInt is converted to AbstractFloat before calling into the builtin
+ scalarTypeOf(type).kind === 'abstract-int' ? Type.abstractFloat : scalarTypeOf(type)
+ );
validateConstOrOverrideBuiltinEval(
t,
builtin,
@@ -81,22 +89,40 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kArgCases = {
+ good: '(1.2)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_2arg: '(1.2, 2.3)',
+ // Bad value for arg 0
+ bad_0bool: '(false)',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+ bad_0uint: '(1u)',
+ bad_0int: '(1i)',
+ bad_0vec2i: '(vec2i())',
+ bad_0vec2u: '(vec2u())',
+ bad_0vec3i: '(vec3i())',
+ bad_0vec3u: '(vec3u())',
+ bad_0vec4i: '(vec4i())',
+ bad_0vec4u: '(vec4u())',
+};
-g.test('integer_argument')
- .desc(
- `
-Validates that scalar and vector integer arguments are rejected by ${builtin}()
-`
- )
- .params(u => u.combine('type', keysOf(kIntegerArgumentTypes)))
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
.fn(t => {
- const type = kIntegerArgumentTypes[t.params.type];
- validateConstOrOverrideBuiltinEval(
- t,
- builtin,
- /* expectedResult */ type === TypeF32,
- [type.create(0)],
- 'constant'
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
);
});
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/extractBits.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/extractBits.spec.ts
new file mode 100644
index 0000000000..921e157408
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/extractBits.spec.ts
@@ -0,0 +1,218 @@
+const builtin = 'extractBits';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kFloatScalarsAndVectors,
+ u32,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kConcreteIntegerScalarsAndVectors);
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() never errors on valid inputs
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValuesTypes))
+ .filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
+ .beginSubcases()
+ .expand('value', u => fullRangeForType(kValuesTypes[u.type]))
+ .combineWithParams([
+ { offset: 0, count: 0 },
+ { offset: 0, count: 31 },
+ { offset: 0, count: 32 },
+ { offset: 4, count: 0 },
+ { offset: 4, count: 27 },
+ { offset: 4, count: 28 },
+ { offset: 16, count: 0 },
+ { offset: 16, count: 15 },
+ { offset: 16, count: 16 },
+ { offset: 32, count: 0 },
+ ] as const)
+ )
+ .fn(t => {
+ const expectedResult = true; // extractBits() should never error
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [
+ kValuesTypes[t.params.type].create(t.params.value),
+ u32(t.params.offset),
+ u32(t.params.count),
+ ],
+ t.params.stage
+ );
+ });
+
+g.test('count_offset')
+ .desc(
+ `
+Validates that count and offset must be smaller than the size of the primitive.
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .beginSubcases()
+ .combineWithParams([
+ // offset + count < 32
+ { offset: 0, count: 31 },
+ { offset: 1, count: 30 },
+ { offset: 31, count: 0 },
+ { offset: 30, count: 1 },
+ // offset + count == 32
+ { offset: 0, count: 32 },
+ { offset: 1, count: 31 },
+ { offset: 16, count: 16 },
+ { offset: 31, count: 1 },
+ { offset: 32, count: 0 },
+ // offset + count > 32
+ { offset: 2, count: 31 },
+ { offset: 31, count: 2 },
+ // offset > 32
+ { offset: 33, count: 0 },
+ { offset: 33, count: 1 },
+ // count > 32
+ { offset: 0, count: 33 },
+ { offset: 1, count: 33 },
+ ] as const)
+ )
+ .fn(t => {
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ /* expectedResult */ t.params.offset + t.params.count <= 32,
+ [u32(1), u32(t.params.offset), u32(t.params.count)],
+ t.params.stage
+ );
+ });
+
+interface Argument {
+ /** Argument as a string. */
+ readonly arg: string;
+ /** Is this a valid argument type. Note that all args must be valid for the call to be valid. */
+ readonly pass: boolean;
+ /** Additional setup code necessary for this arg in the function scope. */
+ readonly preamble?: string;
+}
+
+function typesToArguments(types: readonly Type[], pass: boolean): Record<string, Argument> {
+ return types.reduce(
+ (res, type) => ({
+ ...res,
+ [type.toString()]: { arg: type.create(0).wgsl(), pass },
+ }),
+ {}
+ );
+}
+
+// u32 is included here to confirm that validation is failing due to a type issue and not something else.
+const kInputArgTypes: { readonly [name: string]: Argument } = {
+ ...typesToArguments([Type.u32], true),
+ ...typesToArguments([...kFloatScalarsAndVectors, Type.bool, Type.mat2x2f], false),
+ alias: { arg: 'u32_alias(1)', pass: true },
+ vec_bool: { arg: 'vec2<bool>(false,true)', pass: false },
+ atomic: { arg: 'a', pass: false },
+ array: {
+ preamble: 'var arry: array<u32, 5>;',
+ arg: 'arry',
+ pass: false,
+ },
+ array_runtime: { arg: 'k.arry', pass: false },
+ struct: {
+ preamble: 'var x: A;',
+ arg: 'x',
+ pass: false,
+ },
+ enumerant: { arg: 'read_write', pass: false },
+ ptr: {
+ preamble: `var<function> f = 1u;
+ let p: ptr<function, u32> = &f;`,
+ arg: 'p',
+ pass: false,
+ },
+ ptr_deref: {
+ preamble: `var<function> f = 1u;
+ let p: ptr<function, u32> = &f;`,
+ arg: '*p',
+ pass: true,
+ },
+ sampler: { arg: 's', pass: false },
+ texture: { arg: 't', pass: false },
+};
+
+g.test('typed_arguments')
+ .desc(
+ `
+Test compilation validation of ${builtin} with variously typed arguments
+ - For failing input types, just use the same type for offset and count to reduce combinations.
+`
+ )
+ .params(u =>
+ u
+ .combine('input', keysOf(kInputArgTypes))
+ .beginSubcases()
+ .combine('offset', keysOf(kInputArgTypes))
+ .expand('count', u => (kInputArgTypes[u.input].pass ? keysOf(kInputArgTypes) : [u.offset]))
+ )
+ .fn(t => {
+ const input = kInputArgTypes[t.params.input];
+ const offset = kInputArgTypes[t.params.offset];
+ const count = kInputArgTypes[t.params.count];
+ t.expectCompileResult(
+ input.pass && offset.pass && count.pass,
+ `alias u32_alias = u32;
+
+ @group(0) @binding(0) var s: sampler;
+ @group(0) @binding(1) var t: texture_2d<f32>;
+
+ var<workgroup> a: atomic<u32>;
+
+ struct A {
+ i: u32,
+ }
+ struct B {
+ arry: array<u32>,
+ }
+ @group(0) @binding(3) var<storage> k: B;
+
+
+ @vertex
+ fn main() -> @builtin(position) vec4<f32> {
+ ${input.preamble ? input.preamble : ''}
+ ${offset.preamble && offset !== input ? offset.preamble : ''}
+ ${count.preamble && count !== input && count !== offset ? count.preamble : ''}
+ _ = ${builtin}(${input.arg},${offset.arg},${count.arg});
+ return vec4<f32>(.4, .2, .3, .1);
+ }`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}(1u,0u,1u); }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/faceForward.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/faceForward.spec.ts
new file mode 100644
index 0000000000..4a87b5cacc
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/faceForward.spec.ts
@@ -0,0 +1,152 @@
+const builtin = 'faceForward';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConvertableToFloatVectors,
+ scalarTypeOf,
+ ScalarType,
+} from '../../../../../util/conversion.js';
+import { QuantizeFunc, quantizeToF16, quantizeToF32 } from '../../../../../util/math.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValidArgumentTypes = objectsToRecord(kConvertableToFloatVectors);
+
+function quantizeFunctionForScalarType(type: ScalarType): QuantizeFunc<number> {
+ switch (type) {
+ case Type.f32:
+ return quantizeToF32;
+ case Type.f16:
+ return quantizeToF16;
+ default:
+ return (v: number) => v;
+ }
+}
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() never errors
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValidArgumentTypes))
+ .filter(u => stageSupportsType(u.stage, kValidArgumentTypes[u.type]))
+ .beginSubcases()
+ .expand('a', u => fullRangeForType(kValidArgumentTypes[u.type], 5))
+ .expand('b', u => fullRangeForType(kValidArgumentTypes[u.type], 5))
+ .expand('c', u => fullRangeForType(kValidArgumentTypes[u.type], 5))
+ )
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kValidArgumentTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ let expectedResult = true;
+
+ const scalarType = scalarTypeOf(kValidArgumentTypes[t.params.type]);
+ const quantizeFn = quantizeFunctionForScalarType(scalarType);
+
+ // Face Forward equation: dot(b, c) < 0 ? -a : a
+ // Should be invalid if the calculations result in intermediate values that
+ // exceed the maximum representable float value for the given type.
+ const b = Number(t.params.b);
+ const c = Number(t.params.c);
+ const bc = quantizeFn(b * c);
+ const dp = quantizeFn(bc * kValidArgumentTypes[t.params.type].width);
+
+ if (!Number.isFinite(dp)) {
+ expectedResult = false;
+ }
+
+ const type = kValidArgumentTypes[t.params.type];
+
+ // Validates faceForward(vecN(a), vecN(b), vecN(c));
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.a), type.create(t.params.b), type.create(t.params.c)],
+ t.params.stage
+ );
+ });
+
+const kArgCases = {
+ good: '(vec3(0), vec3(1), vec3(0.5))',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_1arg: '(vec3(0))',
+ bad_2arg: '(vec3(0), vec3(1))',
+ bad_4arg: '(vec3(0), vec3(1), vec3(0.5), vec3(3))',
+ // Bad value for arg 0
+ bad_0bool: '(false, vec3(1), vec3(0.5))',
+ bad_0array: '(array(1.1,2.2), vec3(1), vec3(0.5))',
+ bad_0struct: '(modf(2.2), vec3(1), vec3(0.5))',
+ bad_0int: '(1i, vec3(1), vec3(0.5))',
+ bad_0uint: '(1u, vec3(1), vec3(0.5))',
+ bad_0vec2i: '(vec2i(0), vec2(1), vec2(0.5))',
+ bad_0vec3i: '(vec3i(0), vec3(1), vec3(0.5))',
+ bad_0vec4i: '(vec4i(0), vec4(1), vec4(0.5))',
+ bad_0vec2u: '(vec2u(0), vec2(1), vec2(0.5))',
+ bad_0vec3u: '(vec3u(0), vec3(1), vec3(0.5))',
+ bad_0vec4u: '(vec4u(0), vec4(1), vec4(0.5))',
+ // Bad value type for arg 1
+ bad_1bool: '(vec3(0), true, vec3(0.5))',
+ bad_1array: '(vec3(0), array(1.1,2.2), vec3(0.5))',
+ bad_1struct: '(vec3(0), modf(2.2), vec3(0.5))',
+ bad_1int: '(vec3(0), 1i, vec3(0.5))',
+ bad_1uint: '(vec3(0), 1u, vec3(0.5))',
+ bad_1vec2i: '(vec2(1), vec2i(1), vec2(0.5))',
+ bad_1vec3i: '(vec3(1), vec3i(1), vec3(0.5))',
+ bad_1vec4i: '(vec4(1), vec4i(1), vec4(0.5))',
+ bad_1vec2u: '(vec2(1), vec2u(1), vec2(0.5))',
+ bad_1vec3u: '(vec3(1), vec3u(1), vec3(0.5))',
+ bad_1vec4u: '(vec4(1), vec4u(1), vec4(0.5))',
+ // Bad value type for arg 2
+ bad_2bool: '(vec3(0), vec3(1), true)',
+ bad_2array: '(vec3(0), vec3(1), array(1.1,2.2))',
+ bad_2struct: '(vec3(0), vec3(1), modf(2.2))',
+ bad_2int: '(vec3(0), vec3(1), 1i)',
+ bad_2uint: '(vec3(0), vec3(1), 1u)',
+ bad_2vec2i: '(vec2(1), vec2(1), vec2i(1))',
+ bad_2vec3i: '(vec3(1), vec3(1), vec3i(1))',
+ bad_2vec4i: '(vec4(1), vec4(1), vec4i(1))',
+ bad_2vec2u: '(vec2(1), vec2(1), vec2u(1))',
+ bad_2vec3u: '(vec3(1), vec3(1), vec3u(1))',
+ bad_2vec4u: '(vec4(1), vec4(1), vec4u(1))',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/firstLeadingBit.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/firstLeadingBit.spec.ts
new file mode 100644
index 0000000000..4aeb7e8bd2
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/firstLeadingBit.spec.ts
@@ -0,0 +1,198 @@
+const builtin = 'firstLeadingBit';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kFloatScalarsAndVectors,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kConcreteIntegerScalarsAndVectors);
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() never errors
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValuesTypes))
+ .filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
+ .beginSubcases()
+ .expand('value', u => fullRangeForType(kValuesTypes[u.type]))
+ )
+ .fn(t => {
+ const expectedResult = true; // firstLeadingBit() should never error
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [kValuesTypes[t.params.type].create(t.params.value)],
+ t.params.stage
+ );
+ });
+
+// u32 is included here to confirm that validation is failing due to a type issue and not something else.
+const kFloatTypes = objectsToRecord([Type.u32, ...kFloatScalarsAndVectors]);
+
+g.test('float_argument')
+ .desc(
+ `
+Validates that float arguments are rejected by ${builtin}()
+`
+ )
+ .params(u => u.combine('type', keysOf(kFloatTypes)))
+ .fn(t => {
+ const type = kFloatTypes[t.params.type];
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ /* expectedResult */ type === Type.u32,
+ [type.create(0)],
+ 'constant'
+ );
+ });
+
+const kTests: {
+ readonly [name: string]: {
+ /** Arguments to pass to the builtin with parentheses. */
+ readonly args: string;
+ /** Should the test case pass. */
+ readonly pass: boolean;
+ /** Additional setup code in the function scope. */
+ readonly preamble?: string;
+ };
+} = {
+ valid: {
+ args: '(1u)',
+ pass: true,
+ },
+ // Number of arguments.
+ no_parens: {
+ args: '',
+ pass: false,
+ },
+ too_few_args: {
+ args: '()',
+ pass: false,
+ },
+ too_many_args: {
+ args: '(1u,2u)',
+ pass: false,
+ },
+ // Arguments types (only 1 argument for this builtin).
+ alias: {
+ args: '(u32_alias(1))',
+ pass: true,
+ },
+ bool: {
+ args: '(false)',
+ pass: false,
+ },
+ vec_bool: {
+ args: '(vec2<bool>(false,true))',
+ pass: false,
+ },
+ matrix: {
+ args: '(mat2x2(1,1,1,1))',
+ pass: false,
+ },
+ atomic: {
+ args: '(a)',
+ pass: false,
+ },
+ array: {
+ preamble: 'var arry: array<u32, 5>;',
+ args: '(arry)',
+ pass: false,
+ },
+ array_runtime: {
+ args: '(k.arry)',
+ pass: false,
+ },
+ struct: {
+ preamble: 'var x: A;',
+ args: '(x)',
+ pass: false,
+ },
+ enumerant: {
+ args: '(read_write)',
+ pass: false,
+ },
+ ptr: {
+ preamble: `var<function> f = 1u;
+ let p: ptr<function, u32> = &f;`,
+ args: '(p)',
+ pass: false,
+ },
+ ptr_deref: {
+ preamble: `var<function> f = 1u;
+ let p: ptr<function, u32> = &f;`,
+ args: '(*p)',
+ pass: true,
+ },
+ sampler: {
+ args: '(s)',
+ pass: false,
+ },
+ texture: {
+ args: '(t)',
+ pass: false,
+ },
+};
+
+g.test('arguments')
+ .desc(`Test compilation validation of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('test', keysOf(kTests)))
+ .fn(t => {
+ const test = kTests[t.params.test];
+ t.expectCompileResult(
+ test.pass,
+ `alias u32_alias = u32;
+
+ @group(0) @binding(0) var s: sampler;
+ @group(0) @binding(1) var t: texture_2d<f32>;
+
+ var<workgroup> a: atomic<u32>;
+
+ struct A {
+ i: u32,
+ }
+ struct B {
+ arry: array<u32>,
+ }
+ @group(0) @binding(3) var<storage> k: B;
+
+
+ @vertex
+ fn main() -> @builtin(position) vec4<f32> {
+ ${test.preamble ? test.preamble : ''}
+ _ = ${builtin}${test.args};
+ return vec4<f32>(.4, .2, .3, .1);
+ }`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}(1u); }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/firstTrailingBit.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/firstTrailingBit.spec.ts
new file mode 100644
index 0000000000..897a213fb8
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/firstTrailingBit.spec.ts
@@ -0,0 +1,198 @@
+const builtin = 'firstTrailingBit';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kFloatScalarsAndVectors,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kConcreteIntegerScalarsAndVectors);
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() never errors
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValuesTypes))
+ .filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
+ .beginSubcases()
+ .expand('value', u => fullRangeForType(kValuesTypes[u.type]))
+ )
+ .fn(t => {
+ const expectedResult = true; // firstTrailingBit() should never error
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [kValuesTypes[t.params.type].create(t.params.value)],
+ t.params.stage
+ );
+ });
+
+// u32 is included here to confirm that validation is failing due to a type issue and not something else.
+const kFloatTypes = objectsToRecord([Type.u32, ...kFloatScalarsAndVectors]);
+
+g.test('float_argument')
+ .desc(
+ `
+Validates that float arguments are rejected by ${builtin}()
+`
+ )
+ .params(u => u.combine('type', keysOf(kFloatTypes)))
+ .fn(t => {
+ const type = kFloatTypes[t.params.type];
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ /* expectedResult */ type === Type.u32,
+ [type.create(0)],
+ 'constant'
+ );
+ });
+
+const kTests: {
+ readonly [name: string]: {
+ /** Arguments to pass to the builtin with parentheses. */
+ readonly args: string;
+ /** Should the test case pass. */
+ readonly pass: boolean;
+ /** Additional setup code in the function scope. */
+ readonly preamble?: string;
+ };
+} = {
+ valid: {
+ args: '(1u)',
+ pass: true,
+ },
+ // Number of arguments.
+ no_parens: {
+ args: '',
+ pass: false,
+ },
+ too_few_args: {
+ args: '()',
+ pass: false,
+ },
+ too_many_args: {
+ args: '(1u,2u)',
+ pass: false,
+ },
+ // Arguments types (only 1 argument for this builtin).
+ alias: {
+ args: '(u32_alias(1))',
+ pass: true,
+ },
+ bool: {
+ args: '(false)',
+ pass: false,
+ },
+ vec_bool: {
+ args: '(vec2<bool>(false,true))',
+ pass: false,
+ },
+ matrix: {
+ args: '(mat2x2(1,1,1,1))',
+ pass: false,
+ },
+ atomic: {
+ args: '(a)',
+ pass: false,
+ },
+ array: {
+ preamble: 'var arry: array<u32, 5>;',
+ args: '(arry)',
+ pass: false,
+ },
+ array_runtime: {
+ args: '(k.arry)',
+ pass: false,
+ },
+ struct: {
+ preamble: 'var x: A;',
+ args: '(x)',
+ pass: false,
+ },
+ enumerant: {
+ args: '(read_write)',
+ pass: false,
+ },
+ ptr: {
+ preamble: `var<function> f = 1u;
+ let p: ptr<function, u32> = &f;`,
+ args: '(p)',
+ pass: false,
+ },
+ ptr_deref: {
+ preamble: `var<function> f = 1u;
+ let p: ptr<function, u32> = &f;`,
+ args: '(*p)',
+ pass: true,
+ },
+ sampler: {
+ args: '(s)',
+ pass: false,
+ },
+ texture: {
+ args: '(t)',
+ pass: false,
+ },
+};
+
+g.test('arguments')
+ .desc(`Test compilation validation of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('test', keysOf(kTests)))
+ .fn(t => {
+ const test = kTests[t.params.test];
+ t.expectCompileResult(
+ test.pass,
+ `alias u32_alias = u32;
+
+ @group(0) @binding(0) var s: sampler;
+ @group(0) @binding(1) var t: texture_2d<f32>;
+
+ var<workgroup> a: atomic<u32>;
+
+ struct A {
+ i: u32,
+ }
+ struct B {
+ arry: array<u32>,
+ }
+ @group(0) @binding(3) var<storage> k: B;
+
+
+ @vertex
+ fn main() -> @builtin(position) vec4<f32> {
+ ${test.preamble ? test.preamble : ''}
+ _ = ${builtin}${test.args};
+ return vec4<f32>(.4, .2, .3, .1);
+ }`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}(1u); }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/floor.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/floor.spec.ts
new file mode 100644
index 0000000000..ca699c6414
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/floor.spec.ts
@@ -0,0 +1,108 @@
+const builtin = 'floor';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() never errors
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValuesTypes))
+ .filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
+ .beginSubcases()
+ .expand('value', u => fullRangeForType(kValuesTypes[u.type]))
+ )
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const expectedResult = true; // floor() should never error
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [kValuesTypes[t.params.type].create(t.params.value)],
+ t.params.stage
+ );
+ });
+
+const kIntegerArgumentTypes = objectsToRecord([Type.f32, ...kConcreteIntegerScalarsAndVectors]);
+
+g.test('integer_argument')
+ .desc(
+ `
+Validates that scalar and vector integer arguments are rejected by ${builtin}()
+`
+ )
+ .params(u => u.combine('type', keysOf(kIntegerArgumentTypes)))
+ .fn(t => {
+ const type = kIntegerArgumentTypes[t.params.type];
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ /* expectedResult */ type === Type.f32,
+ [type.create(0)],
+ 'constant'
+ );
+ });
+
+const kArgCases = {
+ good: '(1.1)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_2args: '(1.0,2.0)',
+ // Bad value type for arg 0
+ bad_0i32: '(1i)',
+ bad_0u32: '(1u)',
+ bad_0bool: '(false)',
+ bad_0vec2u: '(vec2u())',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/fract.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/fract.spec.ts
new file mode 100644
index 0000000000..c930c06ac4
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/fract.spec.ts
@@ -0,0 +1,94 @@
+const builtin = 'fract';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValidArgumentTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() error on invalid inputs.
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValidArgumentTypes))
+ .filter(u => stageSupportsType(u.stage, kValidArgumentTypes[u.type]))
+ .beginSubcases()
+ .expand('value', u => fullRangeForType(kValidArgumentTypes[u.type]))
+ )
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kValidArgumentTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const expectedResult = true;
+
+ const type = kValidArgumentTypes[t.params.type];
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.value)],
+ t.params.stage
+ );
+ });
+
+const kArgCases = {
+ good: '(1.2)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_2arg: '(1.2, 2.3)',
+ // Bad value for arg 0
+ bad_0bool: '(false)',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+ bad_0uint: '(1u)',
+ bad_0int: '(1i)',
+ bad_0vec2i: '(vec2i())',
+ bad_0vec2u: '(vec2u())',
+ bad_0vec3i: '(vec3i())',
+ bad_0vec3u: '(vec3u())',
+ bad_0vec4i: '(vec4i())',
+ bad_0vec4u: '(vec4u())',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/frexp.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/frexp.spec.ts
new file mode 100644
index 0000000000..16eb29f9ea
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/frexp.spec.ts
@@ -0,0 +1,94 @@
+const builtin = 'frexp';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValidArgumentTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() error on invalid inputs.
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValidArgumentTypes))
+ .filter(u => stageSupportsType(u.stage, kValidArgumentTypes[u.type]))
+ .beginSubcases()
+ .expand('value', u => fullRangeForType(kValidArgumentTypes[u.type]))
+ )
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kValidArgumentTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const expectedResult = true;
+
+ const type = kValidArgumentTypes[t.params.type];
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.value)],
+ t.params.stage
+ );
+ });
+
+const kArgCases = {
+ good: '(1.2)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_2arg: '(1.2, 2.3)',
+ // Bad value for arg 0
+ bad_0bool: '(false)',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+ bad_0uint: '(1u)',
+ bad_0int: '(1i)',
+ bad_0vec2i: '(vec2i())',
+ bad_0vec2u: '(vec2u())',
+ bad_0vec3i: '(vec3i())',
+ bad_0vec3u: '(vec3u())',
+ bad_0vec4i: '(vec4i())',
+ bad_0vec4u: '(vec4u())',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/insertBits.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/insertBits.spec.ts
new file mode 100644
index 0000000000..17065e33ae
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/insertBits.spec.ts
@@ -0,0 +1,241 @@
+const builtin = 'insertBits';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kFloatScalarsAndVectors,
+ u32,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kConcreteIntegerScalarsAndVectors);
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() never errors on valid inputs
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValuesTypes))
+ .filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
+ .beginSubcases()
+ .expand('value', u => fullRangeForType(kValuesTypes[u.type], 5))
+ .expand('newbits', u => fullRangeForType(kValuesTypes[u.type], 5))
+ .combineWithParams([
+ { offset: 0, count: 0 },
+ { offset: 0, count: 31 },
+ { offset: 0, count: 32 },
+ { offset: 4, count: 0 },
+ { offset: 4, count: 27 },
+ { offset: 4, count: 28 },
+ { offset: 16, count: 0 },
+ { offset: 16, count: 15 },
+ { offset: 16, count: 16 },
+ { offset: 32, count: 0 },
+ ] as const)
+ )
+ .fn(t => {
+ const expectedResult = true; // insertBits() should never error
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [
+ kValuesTypes[t.params.type].create(t.params.value),
+ kValuesTypes[t.params.type].create(t.params.newbits),
+ u32(t.params.offset),
+ u32(t.params.count),
+ ],
+ t.params.stage
+ );
+ });
+
+g.test('mismatched')
+ .desc(
+ `
+Validates that even with valid types, if arg0 and arg1 do not match types ${builtin}() errors
+`
+ )
+ .params(u => u.combine('arg0', keysOf(kValuesTypes)).combine('arg1', keysOf(kValuesTypes)))
+ .fn(t => {
+ const arg0 = kValuesTypes[t.params.arg0];
+ const arg1 = kValuesTypes[t.params.arg1];
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ /* expectedResult */ t.params.arg0 === t.params.arg1,
+ [arg0.create(0), arg1.create(1), u32(0), u32(32)],
+ 'constant'
+ );
+ });
+
+g.test('count_offset')
+ .desc(
+ `
+Validates that count and offset must be smaller than the size of the primitive.
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .beginSubcases()
+ .combineWithParams([
+ // offset + count < 32
+ { offset: 0, count: 31 },
+ { offset: 1, count: 30 },
+ { offset: 31, count: 0 },
+ { offset: 30, count: 1 },
+ // offset + count == 32
+ { offset: 0, count: 32 },
+ { offset: 1, count: 31 },
+ { offset: 16, count: 16 },
+ { offset: 31, count: 1 },
+ { offset: 32, count: 0 },
+ // offset + count > 32
+ { offset: 2, count: 31 },
+ { offset: 31, count: 2 },
+ // offset > 32
+ { offset: 33, count: 0 },
+ { offset: 33, count: 1 },
+ // count > 32
+ { offset: 0, count: 33 },
+ { offset: 1, count: 33 },
+ ] as const)
+ )
+ .fn(t => {
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ /* expectedResult */ t.params.offset + t.params.count <= 32,
+ [u32(0), u32(1), u32(t.params.offset), u32(t.params.count)],
+ t.params.stage
+ );
+ });
+
+interface Argument {
+ /** Argument as a string. */
+ readonly arg: string;
+ /** Is this a valid argument type. Note that all args must be valid for the call to be valid. */
+ readonly pass: boolean;
+ /** Additional setup code necessary for this arg in the function scope. */
+ readonly preamble?: string;
+}
+
+function typesToArguments(types: readonly Type[], pass: boolean): Record<string, Argument> {
+ return types.reduce(
+ (res, type) => ({
+ ...res,
+ [type.toString()]: { arg: type.create(0).wgsl(), pass },
+ }),
+ {}
+ );
+}
+
+// u32 is included here to confirm that validation is failing due to a type issue and not something else.
+const kInputArgTypes: { readonly [name: string]: Argument } = {
+ ...typesToArguments([Type.u32], true),
+ ...typesToArguments([...kFloatScalarsAndVectors, Type.bool, Type.mat2x2f], false),
+ alias: { arg: 'u32_alias(1)', pass: true },
+ vec_bool: { arg: 'vec2<bool>(false,true)', pass: false },
+ atomic: { arg: 'a', pass: false },
+ array: {
+ preamble: 'var arry: array<u32, 5>;',
+ arg: 'arry',
+ pass: false,
+ },
+ array_runtime: { arg: 'k.arry', pass: false },
+ struct: {
+ preamble: 'var x: A;',
+ arg: 'x',
+ pass: false,
+ },
+ enumerant: { arg: 'read_write', pass: false },
+ ptr: {
+ preamble: `var<function> f = 1u;
+ let p: ptr<function, u32> = &f;`,
+ arg: 'p',
+ pass: false,
+ },
+ ptr_deref: {
+ preamble: `var<function> f = 1u;
+ let p: ptr<function, u32> = &f;`,
+ arg: '*p',
+ pass: true,
+ },
+ sampler: { arg: 's', pass: false },
+ texture: { arg: 't', pass: false },
+};
+
+g.test('typed_arguments')
+ .desc(
+ `
+Test compilation validation of ${builtin} with variously typed arguments
+ - The input types are matching to reduce testing permutations. Mismatching input types are
+ validated in 'mismatched' test above.
+ - For failing input types, just use the same type for offset and count to reduce combinations.
+`
+ )
+ .params(u =>
+ u
+ .combine('input', keysOf(kInputArgTypes))
+ .beginSubcases()
+ .combine('offset', keysOf(kInputArgTypes))
+ .expand('count', u => (kInputArgTypes[u.input].pass ? keysOf(kInputArgTypes) : [u.offset]))
+ )
+ .fn(t => {
+ const input = kInputArgTypes[t.params.input];
+ const offset = kInputArgTypes[t.params.offset];
+ const count = kInputArgTypes[t.params.count];
+ t.expectCompileResult(
+ input.pass && offset.pass && count.pass,
+ `alias u32_alias = u32;
+
+ @group(0) @binding(0) var s: sampler;
+ @group(0) @binding(1) var t: texture_2d<f32>;
+
+ var<workgroup> a: atomic<u32>;
+
+ struct A {
+ i: u32,
+ }
+ struct B {
+ arry: array<u32>,
+ }
+ @group(0) @binding(3) var<storage> k: B;
+
+
+ @vertex
+ fn main() -> @builtin(position) vec4<f32> {
+ ${input.preamble ? input.preamble : ''}
+ ${offset.preamble && offset !== input ? offset.preamble : ''}
+ ${count.preamble && count !== input && count !== offset ? count.preamble : ''}
+ _ = ${builtin}(${input.arg},${input.arg},${offset.arg},${count.arg});
+ return vec4<f32>(.4, .2, .3, .1);
+ }`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}(0u,1u,0u,1u); }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/inverseSqrt.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/inverseSqrt.spec.ts
index b2813cbe0a..806500d937 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/inverseSqrt.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/inverseSqrt.spec.ts
@@ -6,11 +6,9 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { isRepresentable } from '../../../../../util/floating_point.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -18,7 +16,7 @@ import { ShaderValidationTest } from '../../../shader_validation_test.js';
import {
fullRangeForType,
kConstantAndOverrideStages,
- kMinusTwoToTwo,
+ minusTwoToTwoRangeForType,
stageSupportsType,
unique,
validateConstOrOverrideBuiltinEval,
@@ -26,7 +24,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -40,17 +38,27 @@ Validates that constant evaluation and override evaluation of ${builtin}() input
.combine('type', keysOf(kValuesTypes))
.filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
.beginSubcases()
- .expand('value', u => unique(kMinusTwoToTwo, fullRangeForType(kValuesTypes[u.type])))
+ .expand('value', u =>
+ unique(
+ minusTwoToTwoRangeForType(kValuesTypes[u.type]),
+ fullRangeForType(kValuesTypes[u.type])
+ )
+ )
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
.fn(t => {
const type = kValuesTypes[t.params.type];
const expectedResult =
- t.params.value > 0 && isRepresentable(1 / Math.sqrt(t.params.value), elementType(type));
+ t.params.value > 0 &&
+ isRepresentable(
+ 1 / Math.sqrt(Number(t.params.value)),
+ // AbstractInt is converted to AbstractFloat before calling into the builtin
+ scalarTypeOf(type).kind === 'abstract-int' ? Type.abstractFloat : scalarTypeOf(type)
+ );
validateConstOrOverrideBuiltinEval(
t,
builtin,
@@ -60,22 +68,40 @@ Validates that constant evaluation and override evaluation of ${builtin}() input
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kArgCases = {
+ good: '(1.2)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_2arg: '(1.2, 2.3)',
+ // Bad value for arg 0
+ bad_0bool: '(false)',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+ bad_0uint: '(1u)',
+ bad_0int: '(1i)',
+ bad_0vec2i: '(vec2i())',
+ bad_0vec2u: '(vec2u())',
+ bad_0vec3i: '(vec3i())',
+ bad_0vec3u: '(vec3u())',
+ bad_0vec4i: '(vec4i())',
+ bad_0vec4u: '(vec4u())',
+};
-g.test('integer_argument')
- .desc(
- `
-Validates that scalar and vector integer arguments are rejected by ${builtin}()
-`
- )
- .params(u => u.combine('type', keysOf(kIntegerArgumentTypes)))
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
.fn(t => {
- const type = kIntegerArgumentTypes[t.params.type];
- validateConstOrOverrideBuiltinEval(
- t,
- builtin,
- /* expectedResult */ type === TypeF32,
- [type.create(1)],
- 'constant'
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
);
});
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/length.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/length.spec.ts
index 60fbe6e285..003ad6811d 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/length.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/length.spec.ts
@@ -7,14 +7,13 @@ import { makeTestGroup } from '../../../../../../common/framework/test_group.js'
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
ScalarType,
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalars,
- kAllFloatVector2,
- kAllFloatVector3,
- kAllFloatVector4,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalar,
+ kConvertableToFloatVec2,
+ kConvertableToFloatVec3,
+ kConvertableToFloatVec4,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { isRepresentable } from '../../../../../util/floating_point.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -33,7 +32,7 @@ export const g = makeTestGroup(ShaderValidationTest);
* formed from `vec` of the element type `type`.
*/
function calculate(
- vec: number[],
+ vec: (number | bigint)[],
type: ScalarType
): {
/**
@@ -49,16 +48,25 @@ function calculate(
/** The computed value of length(). */
result: number;
} {
- const squareSum = vec.reduce((prev, curr) => prev + curr * curr, 0);
+ const vec_number = vec.map(e => Number(e));
+ const squareSum = vec_number.reduce((prev, curr) => prev + Number(curr) * Number(curr), 0);
const result = Math.sqrt(squareSum);
return {
- isIntermediateRepresentable: isRepresentable(squareSum, type),
- isResultRepresentable: isRepresentable(result, type),
+ isIntermediateRepresentable: isRepresentable(
+ squareSum,
+ // AbstractInt is converted to AbstractFloat before calling into the builtin
+ scalarTypeOf(type).kind === 'abstract-int' ? Type.abstractFloat : scalarTypeOf(type)
+ ),
+ isResultRepresentable: isRepresentable(
+ result,
+ // AbstractInt is converted to AbstractFloat before calling into the builtin
+ scalarTypeOf(type).kind === 'abstract-int' ? Type.abstractFloat : scalarTypeOf(type)
+ ),
result,
};
}
-const kScalarTypes = objectsToRecord(kAllFloatScalars);
+const kScalarTypes = objectsToRecord(kConvertableToFloatScalar);
g.test('scalar')
.desc(
@@ -76,7 +84,7 @@ the input scalar value always compiles without error
.expand('value', u => fullRangeForType(kScalarTypes[u.type]))
)
.beforeAllSubcases(t => {
- if (elementType(kScalarTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kScalarTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
@@ -92,7 +100,7 @@ the input scalar value always compiles without error
);
});
-const kVec2Types = objectsToRecord(kAllFloatVector2);
+const kVec2Types = objectsToRecord(kConvertableToFloatVec2);
g.test('vec2')
.desc(
@@ -108,11 +116,11 @@ Validates that constant evaluation and override evaluation of ${builtin}() with
.beginSubcases()
.expand('x', u => fullRangeForType(kVec2Types[u.type], 5))
.expand('y', u => fullRangeForType(kVec2Types[u.type], 5))
- .expand('_result', u => [calculate([u.x, u.y], elementType(kVec2Types[u.type]))])
+ .expand('_result', u => [calculate([u.x, u.y], scalarTypeOf(kVec2Types[u.type]))])
.filter(u => u._result.isResultRepresentable === u._result.isIntermediateRepresentable)
)
.beforeAllSubcases(t => {
- if (elementType(kVec2Types[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kVec2Types[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
@@ -127,7 +135,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() with
);
});
-const kVec3Types = objectsToRecord(kAllFloatVector3);
+const kVec3Types = objectsToRecord(kConvertableToFloatVec3);
g.test('vec3')
.desc(
@@ -144,11 +152,11 @@ Validates that constant evaluation and override evaluation of ${builtin}() with
.expand('x', u => fullRangeForType(kVec3Types[u.type], 4))
.expand('y', u => fullRangeForType(kVec3Types[u.type], 4))
.expand('z', u => fullRangeForType(kVec3Types[u.type], 4))
- .expand('_result', u => [calculate([u.x, u.y, u.z], elementType(kVec3Types[u.type]))])
+ .expand('_result', u => [calculate([u.x, u.y, u.z], scalarTypeOf(kVec3Types[u.type]))])
.filter(u => u._result.isResultRepresentable === u._result.isIntermediateRepresentable)
)
.beforeAllSubcases(t => {
- if (elementType(kVec3Types[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kVec3Types[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
@@ -163,7 +171,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() with
);
});
-const kVec4Types = objectsToRecord(kAllFloatVector4);
+const kVec4Types = objectsToRecord(kConvertableToFloatVec4);
g.test('vec4')
.desc(
@@ -181,11 +189,11 @@ Validates that constant evaluation and override evaluation of ${builtin}() with
.expand('y', u => fullRangeForType(kVec4Types[u.type], 3))
.expand('z', u => fullRangeForType(kVec4Types[u.type], 3))
.expand('w', u => fullRangeForType(kVec4Types[u.type], 3))
- .expand('_result', u => [calculate([u.x, u.y, u.z, u.w], elementType(kVec4Types[u.type]))])
+ .expand('_result', u => [calculate([u.x, u.y, u.z, u.w], scalarTypeOf(kVec4Types[u.type]))])
.filter(u => u._result.isResultRepresentable === u._result.isIntermediateRepresentable)
)
.beforeAllSubcases(t => {
- if (elementType(kVec4Types[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kVec4Types[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
@@ -200,7 +208,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() with
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kIntegerArgumentTypes = objectsToRecord([Type.f32, ...kConcreteIntegerScalarsAndVectors]);
g.test('integer_argument')
.desc(
@@ -214,7 +222,7 @@ Validates that scalar and vector integer arguments are rejected by ${builtin}()
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ type === TypeF32,
+ /* expectedResult */ type === Type.f32,
[type.create(1)],
'constant'
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/log.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/log.spec.ts
index 5d84d0c0be..4ef0d553c9 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/log.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/log.spec.ts
@@ -6,11 +6,10 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -23,7 +22,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -40,7 +39,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() input
.expand('value', u => fullRangeForType(kValuesTypes[u.type]))
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
@@ -55,7 +54,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() input
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kIntegerArgumentTypes = objectsToRecord([Type.f32, ...kConcreteIntegerScalarsAndVectors]);
g.test('integer_argument')
.desc(
@@ -69,8 +68,41 @@ Validates that scalar and vector integer arguments are rejected by ${builtin}()
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ type === TypeF32,
+ /* expectedResult */ type === Type.f32,
[type.create(1)],
'constant'
);
});
+
+const kArgCases = {
+ good: '(1.1)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_2args: '(1.0,2.0)',
+ // Bad value type for arg 0
+ bad_0i32: '(1i)',
+ bad_0u32: '(1u)',
+ bad_0bool: '(false)',
+ bad_0vec2u: '(vec2u())',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/log2.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/log2.spec.ts
index 60f32d99c7..d242e1a410 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/log2.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/log2.spec.ts
@@ -6,11 +6,10 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -23,7 +22,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -40,7 +39,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() input
.expand('value', u => fullRangeForType(kValuesTypes[u.type]))
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
@@ -55,7 +54,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() input
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kIntegerArgumentTypes = objectsToRecord([Type.f32, ...kConcreteIntegerScalarsAndVectors]);
g.test('integer_argument')
.desc(
@@ -69,8 +68,41 @@ Validates that scalar and vector integer arguments are rejected by ${builtin}()
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ type === TypeF32,
+ /* expectedResult */ type === Type.f32,
[type.create(1)],
'constant'
);
});
+
+const kArgCases = {
+ good: '(1.1)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_2args: '(1.0,2.0)',
+ // Bad value type for arg 0
+ bad_0i32: '(1i)',
+ bad_0u32: '(1u)',
+ bad_0bool: '(false)',
+ bad_0vec2u: '(vec2u())',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/max.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/max.spec.ts
new file mode 100644
index 0000000000..f32589fcf6
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/max.spec.ts
@@ -0,0 +1,91 @@
+const builtin = 'max';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kAllNumericScalarsAndVectors,
+ scalarTypeOf,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kAllNumericScalarsAndVectors);
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() never errors
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValuesTypes))
+ .filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
+ .beginSubcases()
+ .expand('a', u => fullRangeForType(kValuesTypes[u.type], 5))
+ .expand('b', u => fullRangeForType(kValuesTypes[u.type], 5))
+ )
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const type = kValuesTypes[t.params.type];
+ const expectedResult = true; // should never error
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.a), type.create(t.params.b)],
+ t.params.stage
+ );
+ });
+
+const kArgCases = {
+ good: '(1.1, 2.2)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_1arg: '(1.0)',
+ bad_3arg: '(1.0, 2.0, 3.0)',
+ // Bad value for arg 0
+ bad_0bool: '(false, 1.0)',
+ bad_0array: '(array(1.1,2.2), 1.0)',
+ bad_0struct: '(modf(2.2), 1.0)',
+ // Bad value type for arg 1
+ bad_1bool: '(1.0, true)',
+ bad_1array: '(1.0, array(1.1,2.2))',
+ bad_1struct: '(1.0, modf(2.2))',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/min.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/min.spec.ts
new file mode 100644
index 0000000000..2222c44e92
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/min.spec.ts
@@ -0,0 +1,91 @@
+const builtin = 'min';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kAllNumericScalarsAndVectors,
+ scalarTypeOf,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kAllNumericScalarsAndVectors);
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() never errors
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValuesTypes))
+ .filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
+ .beginSubcases()
+ .expand('a', u => fullRangeForType(kValuesTypes[u.type], 5))
+ .expand('b', u => fullRangeForType(kValuesTypes[u.type], 5))
+ )
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const type = kValuesTypes[t.params.type];
+ const expectedResult = true; // should never error
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.a), type.create(t.params.b)],
+ t.params.stage
+ );
+ });
+
+const kArgCases = {
+ good: '(1.1, 2.2)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_1arg: '(1.0)',
+ bad_3arg: '(1.0, 2.0, 3.0)',
+ // Bad value for arg 0
+ bad_0bool: '(false, 1.0)',
+ bad_0array: '(array(1.1,2.2), 1.0)',
+ bad_0struct: '(modf(2.2), 1.0)',
+ // Bad value type for arg 1
+ bad_1bool: '(1.0, true)',
+ bad_1array: '(1.0, array(1.1,2.2))',
+ bad_1struct: '(1.0, modf(2.2))',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/modf.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/modf.spec.ts
index b890f9026e..2a90fa878e 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/modf.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/modf.spec.ts
@@ -6,11 +6,10 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -23,7 +22,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -40,7 +39,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
.expand('value', u => fullRangeForType(kValuesTypes[u.type]))
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
@@ -55,7 +54,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kIntegerArgumentTypes = objectsToRecord([Type.f32, ...kConcreteIntegerScalarsAndVectors]);
g.test('integer_argument')
.desc(
@@ -69,7 +68,7 @@ Validates that scalar and vector integer arguments are rejected by ${builtin}()
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ type === TypeF32,
+ /* expectedResult */ type === Type.f32,
[type.create(0)],
'constant'
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/normalize.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/normalize.spec.ts
new file mode 100644
index 0000000000..28e1d9cdc6
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/normalize.spec.ts
@@ -0,0 +1,146 @@
+const builtin = 'normalize';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatVectors,
+ scalarTypeOf,
+ ScalarType,
+} from '../../../../../util/conversion.js';
+import { QuantizeFunc, quantizeToF16, quantizeToF32 } from '../../../../../util/math.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValidArgumentTypes = objectsToRecord(kConvertableToFloatVectors);
+
+function quantizeFunctionForScalarType(type: ScalarType): QuantizeFunc<number> {
+ switch (type) {
+ case Type.f32:
+ return quantizeToF32;
+ case Type.f16:
+ return quantizeToF16;
+ default:
+ return (v: number) => v;
+ }
+}
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() rejects invalid values
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValidArgumentTypes))
+ .filter(u => stageSupportsType(u.stage, kValidArgumentTypes[u.type]))
+ .beginSubcases()
+ .expand('value', u => fullRangeForType(kValidArgumentTypes[u.type]))
+ )
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kValidArgumentTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ let expectedResult = true;
+
+ const scalarType = scalarTypeOf(kValidArgumentTypes[t.params.type]);
+ const quantizeFn = quantizeFunctionForScalarType(scalarType);
+
+ // Should be invalid if the normalization calculations result in intermediate
+ // values that exceed the maximum representable float value for the given type,
+ // or if the length is smaller than the smallest representable float value.
+ const v = Number(t.params.value);
+ const vv = quantizeFn(v * v);
+ const dp = quantizeFn(vv * kValidArgumentTypes[t.params.type].width);
+ const len = quantizeFn(Math.sqrt(dp));
+ if (vv === Infinity || dp === Infinity || len === 0) {
+ expectedResult = false;
+ }
+
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [kValidArgumentTypes[t.params.type].create(t.params.value)],
+ t.params.stage
+ );
+ });
+
+const kInvalidArgumentTypes = objectsToRecord([
+ Type.f32,
+ Type.f16,
+ Type.abstractInt,
+ Type.bool,
+ Type.vec(2, Type.bool),
+ Type.vec(3, Type.bool),
+ Type.vec(4, Type.bool),
+ ...kConcreteIntegerScalarsAndVectors,
+]);
+
+g.test('invalid_argument')
+ .desc(
+ `
+Validates that all scalar arguments and vector integer or boolean arguments are rejected by ${builtin}()
+`
+ )
+ .params(u => u.combine('type', keysOf(kInvalidArgumentTypes)))
+ .beforeAllSubcases(t => {
+ if (kInvalidArgumentTypes[t.params.type] === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const expectedResult = false; // should always error with invalid argument types
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [kInvalidArgumentTypes[t.params.type].create(0)],
+ 'constant'
+ );
+ });
+
+const kArgCases = {
+ good: '(vec3f(1, 0, 0))',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_2args: '(vec3f(),vec3f())',
+ // Bad value for arg 0
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack2x16snorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack2x16snorm.spec.ts
new file mode 100644
index 0000000000..f7b3718ca4
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack2x16snorm.spec.ts
@@ -0,0 +1,58 @@
+const kFn = 'pack2x16snorm';
+export const description = `Validate ${kFn}`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kArgCases = {
+ good: '(vec2f())',
+ good_vec2_abstract_float: '(vec2(0.1))',
+ bad_0args: '()',
+ bad_2args: '(vec2f(),vec2f())',
+ bad_abstract_int: '(1)',
+ bad_i32: '(1i)',
+ bad_f32: '(1f)',
+ bad_u32: '(1u)',
+ bad_abstract_float: '(0.1)',
+ bad_bool: '(false)',
+ bad_vec4f: '(vec4f())',
+ bad_vec4u: '(vec4u())',
+ bad_vec4i: '(vec4i())',
+ bad_vec4b: '(vec4<bool>())',
+ bad_vec3f: '(vec3f())',
+ bad_array: '(array(1.0, 2.0, 3.0, 4.0))',
+ bad_struct: '(modf(1.1))',
+};
+const kGoodArgs = kArgCases['good'];
+const kReturnType = 'u32';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('args')
+ .desc(`Test compilation failure of ${kFn} with various numbers of and types of arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good' || t.params.arg === 'good_vec2_abstract_float',
+ `const c = ${kFn}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('return')
+ .desc(`Test ${kFn} return value type`)
+ .params(u => u.combine('type', ['u32', 'i32', 'f32', 'bool', 'vec2u']))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.type === kReturnType,
+ `const c: ${t.params.type} = ${kFn}${kGoodArgs};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${kFn} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${kFn}${kGoodArgs}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack2x16unorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack2x16unorm.spec.ts
new file mode 100644
index 0000000000..4b6c22f02f
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack2x16unorm.spec.ts
@@ -0,0 +1,58 @@
+const kFn = 'pack2x16unorm';
+export const description = `Validate ${kFn}`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kArgCases = {
+ good: '(vec2f())',
+ good_vec2_abstract_float: '(vec2(0.1))',
+ bad_0args: '()',
+ bad_2args: '(vec2f(),vec2f())',
+ bad_abstract_int: '(1)',
+ bad_i32: '(1i)',
+ bad_f32: '(1f)',
+ bad_u32: '(1u)',
+ bad_abstract_float: '(0.1)',
+ bad_bool: '(false)',
+ bad_vec4f: '(vec4f())',
+ bad_vec4u: '(vec4u())',
+ bad_vec4i: '(vec4i())',
+ bad_vec4b: '(vec4<bool>())',
+ bad_vec3f: '(vec3f())',
+ bad_array: '(array(1.0, 2.0, 3.0, 4.0))',
+ bad_struct: '(modf(1.1))',
+};
+const kGoodArgs = kArgCases['good'];
+const kReturnType = 'u32';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('args')
+ .desc(`Test compilation failure of ${kFn} with various numbers of and types of arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good' || t.params.arg === 'good_vec2_abstract_float',
+ `const c = ${kFn}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('return')
+ .desc(`Test ${kFn} return value type`)
+ .params(u => u.combine('type', ['u32', 'i32', 'f32', 'bool', 'vec2u']))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.type === kReturnType,
+ `const c: ${t.params.type} = ${kFn}${kGoodArgs};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${kFn} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${kFn}${kGoodArgs}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4x8snorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4x8snorm.spec.ts
new file mode 100644
index 0000000000..ea7e196769
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4x8snorm.spec.ts
@@ -0,0 +1,58 @@
+const kFn = 'pack4x8snorm';
+export const description = `Validate ${kFn}`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kArgCases = {
+ good: '(vec4f())',
+ good_vec4_abstract_float: '(vec4(0.1))',
+ bad_0args: '()',
+ bad_2args: '(vec4f(),vec4f())',
+ bad_abstract_int: '(1)',
+ bad_i32: '(1i)',
+ bad_f32: '(1f)',
+ bad_u32: '(1u)',
+ bad_abstract_float: '(0.1)',
+ bad_bool: '(false)',
+ bad_vec4u: '(vec4u())',
+ bad_vec4i: '(vec4i())',
+ bad_vec4b: '(vec4<bool>())',
+ bad_vec2f: '(vec2f())',
+ bad_vec3f: '(vec3f())',
+ bad_array: '(array(1.0, 2.0, 3.0, 4.0))',
+ bad_struct: '(modf(1.1))',
+};
+const kGoodArgs = kArgCases['good'];
+const kReturnType = 'u32';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('args')
+ .desc(`Test compilation failure of ${kFn} with various numbers of and types of arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good' || t.params.arg === 'good_vec4_abstract_float',
+ `const c = ${kFn}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('return')
+ .desc(`Test ${kFn} return value type`)
+ .params(u => u.combine('type', ['u32', 'i32', 'f32', 'bool', 'vec2u']))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.type === kReturnType,
+ `const c: ${t.params.type} = ${kFn}${kGoodArgs};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${kFn} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${kFn}${kGoodArgs}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4x8unorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4x8unorm.spec.ts
new file mode 100644
index 0000000000..46aafcec88
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4x8unorm.spec.ts
@@ -0,0 +1,58 @@
+const kFn = 'pack4x8unorm';
+export const description = `Validate ${kFn}`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kArgCases = {
+ good: '(vec4f())',
+ good_vec4_abstract_float: '(vec4(0.1))',
+ bad_0args: '()',
+ bad_2args: '(vec4f(),vec4f())',
+ bad_abstract_int: '(1)',
+ bad_i32: '(1i)',
+ bad_f32: '(1f)',
+ bad_u32: '(1u)',
+ bad_abstract_float: '(0.1)',
+ bad_bool: '(false)',
+ bad_vec4u: '(vec4u())',
+ bad_vec4i: '(vec4i())',
+ bad_vec4b: '(vec4<bool>())',
+ bad_vec2f: '(vec2f())',
+ bad_vec3f: '(vec3f())',
+ bad_array: '(array(1.0, 2.0, 3.0, 4.0))',
+ bad_struct: '(modf(1.1))',
+};
+const kGoodArgs = kArgCases['good'];
+const kReturnType = 'u32';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('args')
+ .desc(`Test compilation failure of ${kFn} with various numbers of and types of arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good' || t.params.arg === 'good_vec4_abstract_float',
+ `const c = ${kFn}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('return')
+ .desc(`Test ${kFn} return value type`)
+ .params(u => u.combine('type', ['u32', 'i32', 'f32', 'bool', 'vec2u']))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.type === kReturnType,
+ `const c: ${t.params.type} = ${kFn}${kGoodArgs};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${kFn} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${kFn}${kGoodArgs}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4xI8.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4xI8.spec.ts
new file mode 100644
index 0000000000..21b7b706cf
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4xI8.spec.ts
@@ -0,0 +1,62 @@
+export const description = `Validate pack4xI8`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kFeature = 'packed_4x8_integer_dot_product';
+const kFn = 'pack4xI8';
+const kArgCases = {
+ good: '(vec4i())',
+ bad_0args: '()',
+ bad_2args: '(vec4i(),vec4i())',
+ bad_0i32: '(1i)',
+ bad_0f32: '(1f)',
+ bad_0bool: '(false)',
+ bad_0vec4u: '(vec4u())',
+ bad_0vec4f: '(vec4f())',
+ bad_0vec4b: '(vec4<bool>())',
+ bad_0vec2i: '(vec2i())',
+ bad_0vec3i: '(vec3i())',
+ bad_0array: '(array(1))',
+ bad_0struct: '(modf(1.1))',
+};
+const kGoodArgs = kArgCases['good'];
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('unsupported')
+ .desc(`Test absence of ${kFn} when ${kFeature} is not supported.`)
+ .params(u => u.combine('requires', [false, true]))
+ .fn(t => {
+ t.skipIfLanguageFeatureSupported(kFeature);
+ const preamble = t.params.requires ? `requires ${kFeature}; ` : '';
+ const code = `${preamble}const c = ${kFn}${kGoodArgs};`;
+ t.expectCompileResult(false, code);
+ });
+
+g.test('supported')
+ .desc(`Test presence of ${kFn} when ${kFeature} is supported.`)
+ .params(u => u.combine('requires', [false, true]))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ const preamble = t.params.requires ? `requires ${kFeature}; ` : '';
+ const code = `${preamble}const c = ${kFn}${kGoodArgs};`;
+ t.expectCompileResult(true, code);
+ });
+
+g.test('args')
+ .desc(`Test compilation failure of ${kFn} with various numbers of and types of arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ t.expectCompileResult(t.params.arg === 'good', `const c = ${kFn}${kArgCases[t.params.arg]};`);
+ });
+
+g.test('must_use')
+ .desc(`Result of ${kFn} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${kFn}${kGoodArgs}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4xI8Clamp.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4xI8Clamp.spec.ts
new file mode 100644
index 0000000000..7af8958bb1
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4xI8Clamp.spec.ts
@@ -0,0 +1,62 @@
+export const description = `Validate pack4xI8Clamp`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kFeature = 'packed_4x8_integer_dot_product';
+const kFn = 'pack4xI8Clamp';
+const kArgCases = {
+ good: '(vec4i())',
+ bad_0args: '()',
+ bad_2args: '(vec4i(),vec4i())',
+ bad_0i32: '(1i)',
+ bad_0f32: '(1f)',
+ bad_0bool: '(false)',
+ bad_0vec4u: '(vec4u())',
+ bad_0vec4f: '(vec4f())',
+ bad_0vec4b: '(vec4<bool>())',
+ bad_0vec2i: '(vec2i())',
+ bad_0vec3i: '(vec3i())',
+ bad_0array: '(array(1))',
+ bad_0struct: '(modf(1.1))',
+};
+const kGoodArgs = kArgCases['good'];
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('unsupported')
+ .desc(`Test absence of ${kFn} when ${kFeature} is not supported.`)
+ .params(u => u.combine('requires', [false, true]))
+ .fn(t => {
+ t.skipIfLanguageFeatureSupported(kFeature);
+ const preamble = t.params.requires ? `requires ${kFeature}; ` : '';
+ const code = `${preamble}const c = ${kFn}${kGoodArgs};`;
+ t.expectCompileResult(false, code);
+ });
+
+g.test('supported')
+ .desc(`Test presence of ${kFn} when ${kFeature} is supported.`)
+ .params(u => u.combine('requires', [false, true]))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ const preamble = t.params.requires ? `requires ${kFeature}; ` : '';
+ const code = `${preamble}const c = ${kFn}${kGoodArgs};`;
+ t.expectCompileResult(true, code);
+ });
+
+g.test('args')
+ .desc(`Test compilation failure of ${kFn} with various numbers of and types of arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ t.expectCompileResult(t.params.arg === 'good', `const c = ${kFn}${kArgCases[t.params.arg]};`);
+ });
+
+g.test('must_use')
+ .desc(`Result of ${kFn} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${kFn}${kGoodArgs}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4xU8.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4xU8.spec.ts
new file mode 100644
index 0000000000..89daf34f84
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4xU8.spec.ts
@@ -0,0 +1,62 @@
+export const description = `Validate pack4xU8`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kFeature = 'packed_4x8_integer_dot_product';
+const kFn = 'pack4xU8';
+const kArgCases = {
+ good: '(vec4u())',
+ bad_0args: '()',
+ bad_2args: '(vec4u(),vec4u())',
+ bad_0i32: '(1i)',
+ bad_0f32: '(1f)',
+ bad_0bool: '(false)',
+ bad_0vec4i: '(vec4i())',
+ bad_0vec4f: '(vec4f())',
+ bad_0vec4b: '(vec4<bool>())',
+ bad_0vec2u: '(vec2u())',
+ bad_0vec3u: '(vec3u())',
+ bad_0array: '(array(1))',
+ bad_0struct: '(modf(1.1))',
+};
+const kGoodArgs = kArgCases['good'];
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('unsupported')
+ .desc(`Test absence of ${kFn} when ${kFeature} is not supported.`)
+ .params(u => u.combine('requires', [false, true]))
+ .fn(t => {
+ t.skipIfLanguageFeatureSupported(kFeature);
+ const preamble = t.params.requires ? `requires ${kFeature}; ` : '';
+ const code = `${preamble}const c = ${kFn}${kGoodArgs};`;
+ t.expectCompileResult(false, code);
+ });
+
+g.test('supported')
+ .desc(`Test presence of ${kFn} when ${kFeature} is supported.`)
+ .params(u => u.combine('requires', [false, true]))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ const preamble = t.params.requires ? `requires ${kFeature}; ` : '';
+ const code = `${preamble}const c = ${kFn}${kGoodArgs};`;
+ t.expectCompileResult(true, code);
+ });
+
+g.test('args')
+ .desc(`Test compilation failure of ${kFn} with various numbers of and types of arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ t.expectCompileResult(t.params.arg === 'good', `const c = ${kFn}${kArgCases[t.params.arg]};`);
+ });
+
+g.test('must_use')
+ .desc(`Result of ${kFn} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${kFn}${kGoodArgs}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4xU8Clamp.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4xU8Clamp.spec.ts
new file mode 100644
index 0000000000..9d7bd0353b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4xU8Clamp.spec.ts
@@ -0,0 +1,62 @@
+export const description = `Validate pack4xU8Clamp`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kFeature = 'packed_4x8_integer_dot_product';
+const kFn = 'pack4xU8Clamp';
+const kArgCases = {
+ good: '(vec4u())',
+ bad_0args: '()',
+ bad_2args: '(vec4u(),vec4u())',
+ bad_0i32: '(1i)',
+ bad_0f32: '(1f)',
+ bad_0bool: '(false)',
+ bad_0vec4i: '(vec4i())',
+ bad_0vec4f: '(vec4f())',
+ bad_0vec4b: '(vec4<bool>())',
+ bad_0vec2u: '(vec2u())',
+ bad_0vec3u: '(vec3u())',
+ bad_0array: '(array(1))',
+ bad_0struct: '(modf(1.1))',
+};
+const kGoodArgs = kArgCases['good'];
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('unsupported')
+ .desc(`Test absence of ${kFn} when ${kFeature} is not supported.`)
+ .params(u => u.combine('requires', [false, true]))
+ .fn(t => {
+ t.skipIfLanguageFeatureSupported(kFeature);
+ const preamble = t.params.requires ? `requires ${kFeature}; ` : '';
+ const code = `${preamble}const c = ${kFn}${kGoodArgs};`;
+ t.expectCompileResult(false, code);
+ });
+
+g.test('supported')
+ .desc(`Test presence of ${kFn} when ${kFeature} is supported.`)
+ .params(u => u.combine('requires', [false, true]))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ const preamble = t.params.requires ? `requires ${kFeature}; ` : '';
+ const code = `${preamble}const c = ${kFn}${kGoodArgs};`;
+ t.expectCompileResult(true, code);
+ });
+
+g.test('args')
+ .desc(`Test compilation failure of ${kFn} with various numbers of and types of arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ t.expectCompileResult(t.params.arg === 'good', `const c = ${kFn}${kArgCases[t.params.arg]};`);
+ });
+
+g.test('must_use')
+ .desc(`Result of ${kFn} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${kFn}${kGoodArgs}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/quantizeToF16.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/quantizeToF16.spec.ts
new file mode 100644
index 0000000000..4cad84e78c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/quantizeToF16.spec.ts
@@ -0,0 +1,113 @@
+const builtin = 'quantizeToF16';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import { Type, kConcreteF32ScalarsAndVectors } from '../../../../../util/conversion.js';
+import { quantizeToF16 } from '../../../../../util/math.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValidArgumentTypes = objectsToRecord([
+ Type.abstractFloat,
+ Type.vec(2, Type.abstractFloat),
+ Type.vec(3, Type.abstractFloat),
+ Type.vec(4, Type.abstractFloat),
+ ...kConcreteF32ScalarsAndVectors,
+]);
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() error on invalid inputs.
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValidArgumentTypes))
+ .filter(u => stageSupportsType(u.stage, kValidArgumentTypes[u.type]))
+ .beginSubcases()
+ .expand('value', u => fullRangeForType(kValidArgumentTypes[u.type]))
+ )
+ .fn(t => {
+ let expectedResult = true;
+
+ // Should be invalid if the quantized value exceeds the maximum representable
+ // 16-bit float value.
+ const f16Value = quantizeToF16(Number(t.params.value));
+ if (f16Value === Infinity || f16Value === -Infinity) {
+ expectedResult = false;
+ }
+
+ const type = kValidArgumentTypes[t.params.type];
+
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.value)],
+ t.params.stage
+ );
+ });
+
+const kArgCasesF16 = {
+ bad_0f16: '(1h)',
+ bad_0vec2h: '(vec2h())',
+ bad_0vec3h: '(vec3h())',
+ bad_0vec4h: '(vec4h())',
+};
+
+const kArgCases = {
+ good: '(vec3f())',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_2arg: '(1.0, 2.0)',
+ // Bad value for arg 0
+ bad_0bool: '(false)',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+ bad_0uint: '(1u)',
+ bad_0int: '(1i)',
+ bad_0vec2i: '(vec2i())',
+ bad_0vec2u: '(vec2u())',
+ bad_0vec3i: '(vec3i())',
+ bad_0vec3u: '(vec3u())',
+ bad_0vec4i: '(vec4i())',
+ bad_0vec4u: '(vec4u())',
+ ...kArgCasesF16,
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .beforeAllSubcases(t => {
+ if (t.params.arg in kArgCasesF16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/radians.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/radians.spec.ts
index dd432ac194..9017231b69 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/radians.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/radians.spec.ts
@@ -6,11 +6,10 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -23,7 +22,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -40,7 +39,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() input
.expand('value', u => fullRangeForType(kValuesTypes[u.type]))
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
@@ -55,7 +54,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() input
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kIntegerArgumentTypes = objectsToRecord([Type.f32, ...kConcreteIntegerScalarsAndVectors]);
g.test('integer_argument')
.desc(
@@ -69,8 +68,41 @@ Validates that scalar and vector integer arguments are rejected by ${builtin}()
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ type === TypeF32,
+ /* expectedResult */ type === Type.f32,
[type.create(1)],
'constant'
);
});
+
+const kArgCases = {
+ good: '(1.1)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_too_few: '()',
+ bad_too_many: '(1.0,2.0)',
+ // Bad value type for arg 0
+ bad_0i32: '(1i)',
+ bad_0u32: '(1u)',
+ bad_0bool: '(false)',
+ bad_0vec2u: '(vec2u())',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/reflect.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/reflect.spec.ts
new file mode 100644
index 0000000000..c71f37f895
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/reflect.spec.ts
@@ -0,0 +1,131 @@
+const builtin = 'reflect';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConvertableToFloatVectors,
+ scalarTypeOf,
+ ScalarType,
+} from '../../../../../util/conversion.js';
+import { QuantizeFunc, quantizeToF16, quantizeToF32 } from '../../../../../util/math.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValidArgumentTypes = objectsToRecord(kConvertableToFloatVectors);
+
+function quantizeFunctionForScalarType(type: ScalarType): QuantizeFunc<number> {
+ switch (type) {
+ case Type.f32:
+ return quantizeToF32;
+ case Type.f16:
+ return quantizeToF16;
+ default:
+ return (v: number) => v;
+ }
+}
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() never errors
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValidArgumentTypes))
+ .filter(u => stageSupportsType(u.stage, kValidArgumentTypes[u.type]))
+ .beginSubcases()
+ .expand('a', u => fullRangeForType(kValidArgumentTypes[u.type], 5))
+ .expand('b', u => fullRangeForType(kValidArgumentTypes[u.type], 5))
+ )
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kValidArgumentTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ let expectedResult = true;
+
+ const scalarType = scalarTypeOf(kValidArgumentTypes[t.params.type]);
+ const quantizeFn = quantizeFunctionForScalarType(scalarType);
+
+ // Reflect equation: a - 2 * dot(b, a) * b
+ // Should be invalid if the reflect calculations result in intermediate
+ // values that exceed the maximum representable float value for the given type.
+ const a = Number(t.params.a);
+ const b = Number(t.params.b);
+ const ab = quantizeFn(a * b);
+ const dp = quantizeFn(ab * kValidArgumentTypes[t.params.type].width);
+ const dp2 = quantizeFn(dp * 2);
+ const dp2b = quantizeFn(dp2 * b);
+ const a_dp2b = quantizeFn(a - dp2b);
+
+ if (
+ !Number.isFinite(ab) ||
+ !Number.isFinite(dp) ||
+ !Number.isFinite(dp2) ||
+ !Number.isFinite(dp2b) ||
+ !Number.isFinite(a_dp2b)
+ ) {
+ expectedResult = false;
+ }
+
+ const type = kValidArgumentTypes[t.params.type];
+
+ // Validates reflect(vecN(a, a, a), vecN(b, b, b));
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.a), type.create(t.params.b)],
+ t.params.stage
+ );
+ });
+
+const kArgCases = {
+ good: '(vec3(0), vec3(1))',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_1arg: '(vec3(0))',
+ bad_3arg: '(vec3(0), vec3(1), vec3(2))',
+ // Bad value for arg 0
+ bad_0bool: '(false, vec3(1))',
+ bad_0array: '(array(1.1,2.2), vec3(1))',
+ bad_0struct: '(modf(2.2), vec3(1))',
+ // Bad value type for arg 1
+ bad_1bool: '(vec3(0), true)',
+ bad_1array: '(vec3(0), array(1.1,2.2))',
+ bad_1struct: '(vec3(0), modf(2.2))',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/reverseBits.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/reverseBits.spec.ts
new file mode 100644
index 0000000000..8b901bb595
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/reverseBits.spec.ts
@@ -0,0 +1,198 @@
+const builtin = 'reverseBits';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kFloatScalarsAndVectors,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kConcreteIntegerScalarsAndVectors);
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() never errors
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValuesTypes))
+ .filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
+ .beginSubcases()
+ .expand('value', u => fullRangeForType(kValuesTypes[u.type]))
+ )
+ .fn(t => {
+ const expectedResult = true; // reverseBits() should never error
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [kValuesTypes[t.params.type].create(t.params.value)],
+ t.params.stage
+ );
+ });
+
+// u32 is included here to confirm that validation is failing due to a type issue and not something else.
+const kFloatTypes = objectsToRecord([Type.u32, ...kFloatScalarsAndVectors]);
+
+g.test('float_argument')
+ .desc(
+ `
+Validates that float arguments are rejected by ${builtin}()
+`
+ )
+ .params(u => u.combine('type', keysOf(kFloatTypes)))
+ .fn(t => {
+ const type = kFloatTypes[t.params.type];
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ /* expectedResult */ type === Type.u32,
+ [type.create(0)],
+ 'constant'
+ );
+ });
+
+const kTests: {
+ readonly [name: string]: {
+ /** Arguments to pass to the builtin with parentheses. */
+ readonly args: string;
+ /** Should the test case pass. */
+ readonly pass: boolean;
+ /** Additional setup code in the function scope. */
+ readonly preamble?: string;
+ };
+} = {
+ valid: {
+ args: '(1u)',
+ pass: true,
+ },
+ // Number of arguments.
+ no_parens: {
+ args: '',
+ pass: false,
+ },
+ too_few_args: {
+ args: '()',
+ pass: false,
+ },
+ too_many_args: {
+ args: '(1u,2u)',
+ pass: false,
+ },
+ // Arguments types (only 1 argument for this builtin).
+ alias: {
+ args: '(u32_alias(1))',
+ pass: true,
+ },
+ bool: {
+ args: '(false)',
+ pass: false,
+ },
+ vec_bool: {
+ args: '(vec2<bool>(false,true))',
+ pass: false,
+ },
+ matrix: {
+ args: '(mat2x2(1,1,1,1))',
+ pass: false,
+ },
+ atomic: {
+ args: '(a)',
+ pass: false,
+ },
+ array: {
+ preamble: 'var arry: array<u32, 5>;',
+ args: '(arry)',
+ pass: false,
+ },
+ array_runtime: {
+ args: '(k.arry)',
+ pass: false,
+ },
+ struct: {
+ preamble: 'var x: A;',
+ args: '(x)',
+ pass: false,
+ },
+ enumerant: {
+ args: '(read_write)',
+ pass: false,
+ },
+ ptr: {
+ preamble: `var<function> f = 1u;
+ let p: ptr<function, u32> = &f;`,
+ args: '(p)',
+ pass: false,
+ },
+ ptr_deref: {
+ preamble: `var<function> f = 1u;
+ let p: ptr<function, u32> = &f;`,
+ args: '(*p)',
+ pass: true,
+ },
+ sampler: {
+ args: '(s)',
+ pass: false,
+ },
+ texture: {
+ args: '(t)',
+ pass: false,
+ },
+};
+
+g.test('arguments')
+ .desc(`Test compilation validation of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('test', keysOf(kTests)))
+ .fn(t => {
+ const test = kTests[t.params.test];
+ t.expectCompileResult(
+ test.pass,
+ `alias u32_alias = u32;
+
+ @group(0) @binding(0) var s: sampler;
+ @group(0) @binding(1) var t: texture_2d<f32>;
+
+ var<workgroup> a: atomic<u32>;
+
+ struct A {
+ i: u32,
+ }
+ struct B {
+ arry: array<u32>,
+ }
+ @group(0) @binding(3) var<storage> k: B;
+
+
+ @vertex
+ fn main() -> @builtin(position) vec4<f32> {
+ ${test.preamble ? test.preamble : ''}
+ _ = ${builtin}${test.args};
+ return vec4<f32>(.4, .2, .3, .1);
+ }`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}(1u); }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/round.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/round.spec.ts
index 3a4ea0408a..dae7482c18 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/round.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/round.spec.ts
@@ -6,11 +6,10 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { fpTraitsFor } from '../../../../../util/floating_point.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -25,7 +24,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -40,15 +39,19 @@ Validates that constant evaluation and override evaluation of ${builtin}() input
.filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
.beginSubcases()
.expand('value', u => {
- const constants = fpTraitsFor(elementType(kValuesTypes[u.type])).constants();
- return unique(fullRangeForType(kValuesTypes[u.type]), [
- constants.negative.min + 0.1,
- constants.positive.max - 0.1,
- ]);
+ if (scalarTypeOf(kValuesTypes[u.type]).kind === 'abstract-int') {
+ return fullRangeForType(kValuesTypes[u.type]);
+ } else {
+ const constants = fpTraitsFor(scalarTypeOf(kValuesTypes[u.type])).constants();
+ return unique(fullRangeForType(kValuesTypes[u.type]), [
+ constants.negative.min + 0.1,
+ constants.positive.max - 0.1,
+ ]);
+ }
})
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
@@ -63,7 +66,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() input
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kIntegerArgumentTypes = objectsToRecord([Type.f32, ...kConcreteIntegerScalarsAndVectors]);
g.test('integer_argument')
.desc(
@@ -77,7 +80,7 @@ Validates that scalar and vector integer arguments are rejected by ${builtin}()
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ type === TypeF32,
+ /* expectedResult */ type === Type.f32,
[type.create(1)],
'constant'
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/saturate.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/saturate.spec.ts
index 1c7aa66a65..cbd1b3f369 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/saturate.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/saturate.spec.ts
@@ -6,11 +6,10 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -23,7 +22,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -40,7 +39,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() input
.expand('value', u => fullRangeForType(kValuesTypes[u.type]))
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
@@ -55,7 +54,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() input
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kIntegerArgumentTypes = objectsToRecord([Type.f32, ...kConcreteIntegerScalarsAndVectors]);
g.test('integer_argument')
.desc(
@@ -69,7 +68,7 @@ Validates that scalar and vector integer arguments are rejected by ${builtin}()
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ type === TypeF32,
+ /* expectedResult */ type === Type.f32,
[type.create(1)],
'constant'
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/select.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/select.spec.ts
new file mode 100644
index 0000000000..b03ec66b7d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/select.spec.ts
@@ -0,0 +1,250 @@
+const builtin = 'select';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ concreteTypeOf,
+ isConvertible,
+ kAllScalarsAndVectors,
+ scalarTypeOf,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import { validateConstOrOverrideBuiltinEval } from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kArgumentTypes = objectsToRecord(kAllScalarsAndVectors);
+
+g.test('argument_types_1_and_2')
+ .desc(
+ `
+Validates that scalar and vector arguments are not rejected by ${builtin}() for args 1 and 2
+`
+ )
+ .params(u => u.combine('type1', keysOf(kArgumentTypes)).combine('type2', keysOf(kArgumentTypes)))
+ .beforeAllSubcases(t => {
+ if (
+ scalarTypeOf(kArgumentTypes[t.params.type1]) === Type.f16 ||
+ scalarTypeOf(kArgumentTypes[t.params.type2]) === Type.f16
+ ) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const type1 = kArgumentTypes[t.params.type1];
+ const type2 = kArgumentTypes[t.params.type2];
+ // First and second arg must be the same or one convertible to the other.
+ // Note that we specify a concrete return type even if both args are abstract.
+ const returnType = isConvertible(type1, type2)
+ ? concreteTypeOf(type2)
+ : isConvertible(type2, type1)
+ ? concreteTypeOf(type1)
+ : undefined;
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ /* expectedResult */ returnType !== undefined,
+ [type1.create(0), type2.create(0), Type.bool.create(0)],
+ 'constant',
+ returnType
+ );
+ });
+
+g.test('argument_types_3')
+ .desc(
+ `
+Validates that third argument must be bool for ${builtin}()
+`
+ )
+ .params(u => u.combine('type', keysOf(kArgumentTypes)))
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kArgumentTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const type = kArgumentTypes[t.params.type];
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ /* expectedResult */ type === Type.bool,
+ [Type.i32.create(0), Type.i32.create(0), type.create(0)],
+ 'constant',
+ /*return_type*/ Type.i32
+ );
+ });
+
+const kTests = {
+ valid: {
+ src: `_ = ${builtin}(1, 2, true);`,
+ pass: true,
+ },
+ alias: {
+ src: `_ = ${builtin}(i32_alias(1), i32_alias(2), bool_alias(true));`,
+ pass: true,
+ },
+ bool: {
+ src: `_ = ${builtin}(false, false, true);`,
+ pass: true,
+ },
+ i32: {
+ src: `_ = ${builtin}(1i, 1i, true);`,
+ pass: true,
+ },
+ u32: {
+ src: `_ = ${builtin}(1u, 1u, true);`,
+ pass: true,
+ },
+ f32: {
+ src: `_ = ${builtin}(1.0f, 1.0f, true);`,
+ pass: true,
+ },
+ f16: {
+ src: `_ = ${builtin}(1.0h, 1.0h, true);`,
+ pass: true,
+ },
+ mixed_aint_afloat: {
+ src: `_ = ${builtin}(1, 1.0, true);`,
+ pass: true,
+ },
+ mixed_i32_u32: {
+ src: `_ = ${builtin}(1i, 1u, true);`,
+ pass: false,
+ },
+ vec_bool: {
+ src: `_ = ${builtin}(vec2<bool>(false, true), vec2<bool>(false, true), true);`,
+ pass: true,
+ },
+ vec2_bool_implicit: {
+ src: `_ = ${builtin}(vec2(false, true), vec2(false, true), true);`,
+ pass: true,
+ },
+ vec3_bool_implicit: {
+ src: `_ = ${builtin}(vec3(false), vec3(true), true);`,
+ pass: true,
+ },
+ vec_i32: {
+ src: `_ = ${builtin}(vec2<i32>(1, 1), vec2<i32>(1, 1), true);`,
+ pass: true,
+ },
+ vec_u32: {
+ src: `_ = ${builtin}(vec2<u32>(1, 1), vec2<u32>(1, 1), true);`,
+ pass: true,
+ },
+ vec_f32: {
+ src: `_ = ${builtin}(vec2<f32>(1, 1), vec2<f32>(1, 1), true);`,
+ pass: true,
+ },
+ vec_f16: {
+ src: `_ = ${builtin}(vec2<f16>(1, 1), vec2<f16>(1, 1), true);`,
+ pass: true,
+ },
+ matrix: {
+ src: `_ = ${builtin}(mat2x2(1, 1, 1, 1), mat2x2(1, 1, 1, 1), true);`,
+ pass: false,
+ },
+ atomic: {
+ src: ` _ = ${builtin}(a, a, true);`,
+ pass: false,
+ },
+ array: {
+ src: `var a: array<bool, 5>;
+ _ = ${builtin}(a, a, true);`,
+ pass: false,
+ },
+ array_runtime: {
+ src: `_ = ${builtin}(k.arry, k.arry, true);`,
+ pass: false,
+ },
+ struct: {
+ src: `var a: A;
+ _ = ${builtin}(a, a, true);`,
+ pass: false,
+ },
+ enumerant: {
+ src: `_ = ${builtin}(read_write, read_write, true);`,
+ pass: false,
+ },
+ ptr: {
+ src: `var<function> a = true;
+ let p: ptr<function, bool> = &a;
+ _ = ${builtin}(p, p, true);`,
+ pass: false,
+ },
+ ptr_deref: {
+ src: `var<function> a = true;
+ let p: ptr<function, bool> = &a;
+ _ = ${builtin}(*p, *p, true);`,
+ pass: true,
+ },
+ sampler: {
+ src: `_ = ${builtin}(s, s, true);`,
+ pass: false,
+ },
+ texture: {
+ src: `_ = ${builtin}(t, t, true);`,
+ pass: false,
+ },
+ no_args: {
+ src: `_ = ${builtin}();`,
+ pass: false,
+ },
+ too_few_args: {
+ src: `_ = ${builtin}(1, true);`,
+ pass: false,
+ },
+ too_many_args: {
+ src: `_ = ${builtin}(1, 1, 1, true);`,
+ pass: false,
+ },
+};
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}(1, 2, true); }`);
+ });
+
+g.test('arguments')
+ .desc(`Test that ${builtin} is validated correctly.`)
+ .params(u => u.combine('test', keysOf(kTests)))
+ .beforeAllSubcases(t => {
+ if (t.params.test.includes('f16')) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const src = kTests[t.params.test].src;
+ const enables = t.params.test.includes('f16') ? 'enable f16;' : '';
+ const code = `
+ ${enables}
+ alias bool_alias = bool;
+ alias i32_alias = i32;
+
+ @group(0) @binding(0) var s: sampler;
+ @group(0) @binding(1) var t: texture_2d<f32>;
+
+ var<workgroup> a: atomic<u32>;
+
+ struct A {
+ i: bool,
+ }
+ struct B {
+ arry: array<u32>,
+ }
+ @group(0) @binding(3) var<storage> k: B;
+
+ @vertex
+ fn main() -> @builtin(position) vec4<f32> {
+ ${src}
+ return vec4<f32>(.4, .2, .3, .1);
+ }`;
+ t.expectCompileResult(kTests[t.params.test].pass, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/shader_stage_utils.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/shader_stage_utils.ts
new file mode 100644
index 0000000000..34e5bc530d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/shader_stage_utils.ts
@@ -0,0 +1,64 @@
+/**
+ * Use to test that certain WGSL builtins are only available in the fragment stage.
+ * Create WGSL that defines a function "foo" and its required variables that uses
+ * the builtin being tested. Append it to these code strings then compile. It should
+ * succeed or fail based on the value `expectSuccess`.
+ *
+ * See ./textureSample.spec.ts was one example
+ */
+export const kEntryPointsToValidateFragmentOnlyBuiltins = {
+ none: {
+ expectSuccess: true,
+ code: ``,
+ },
+ fragment: {
+ expectSuccess: true,
+ code: `
+ @fragment
+ fn main() {
+ foo();
+ }
+ `,
+ },
+ vertex: {
+ expectSuccess: false,
+ code: `
+ @vertex
+ fn main() -> @builtin(position) vec4f {
+ foo();
+ return vec4f();
+ }
+ `,
+ },
+ compute: {
+ expectSuccess: false,
+ code: `
+ @compute @workgroup_size(1)
+ fn main() {
+ foo();
+ }
+ `,
+ },
+ fragment_and_compute: {
+ expectSuccess: false,
+ code: `
+ @fragment
+ fn main1() {
+ foo();
+ }
+
+ @compute @workgroup_size(1)
+ fn main2() {
+ foo();
+ }
+ `,
+ },
+ compute_without_call: {
+ expectSuccess: true,
+ code: `
+ @compute @workgroup_size(1)
+ fn main() {
+ }
+ `,
+ },
+};
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sign.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sign.spec.ts
index f844961aee..2f1e33b101 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sign.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sign.spec.ts
@@ -6,11 +6,10 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatAndSignedIntegerScalarsAndVectors,
- kAllUnsignedIntegerScalarsAndVectors,
+ Type,
+ kFloatScalarsAndVectors,
+ kConcreteSignedIntegerScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -23,7 +22,10 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatAndSignedIntegerScalarsAndVectors);
+const kValuesTypes = objectsToRecord([
+ ...kFloatScalarsAndVectors,
+ ...kConcreteSignedIntegerScalarsAndVectors,
+]);
g.test('values')
.desc(
@@ -40,7 +42,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() input
.expand('value', u => fullRangeForType(kValuesTypes[u.type]))
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
@@ -55,25 +57,36 @@ Validates that constant evaluation and override evaluation of ${builtin}() input
);
});
-const kUnsignedIntegerArgumentTypes = objectsToRecord([
- TypeF32,
- ...kAllUnsignedIntegerScalarsAndVectors,
-]);
+const kArgCases = {
+ good: '(1.0)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_2arg: '(1.0, 1.0)',
+ // Bad value for arg 0
+ bad_0bool: '(false)',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+ bad_0uint: '(1u)',
+ bad_0vec2u: '(vec2u(1))',
+ bad_0vec3u: '(vec3u(1))',
+ bad_0vec4u: '(vec4u(1))',
+};
-g.test('unsigned_integer_argument')
- .desc(
- `
-Validates that scalar and vector integer arguments are rejected by ${builtin}()
-`
- )
- .params(u => u.combine('type', keysOf(kUnsignedIntegerArgumentTypes)))
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
.fn(t => {
- const type = kUnsignedIntegerArgumentTypes[t.params.type];
- validateConstOrOverrideBuiltinEval(
- t,
- builtin,
- /* expectedResult */ type === TypeF32,
- [type.create(1)],
- 'constant'
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
);
});
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sin.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sin.spec.ts
index 3822fccd3a..6be01123cc 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sin.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sin.spec.ts
@@ -6,18 +6,17 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
import {
fullRangeForType,
kConstantAndOverrideStages,
- kMinus3PiTo3Pi,
+ minusThreePiToThreePiRangeForType,
stageSupportsType,
unique,
validateConstOrOverrideBuiltinEval,
@@ -25,7 +24,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -39,10 +38,15 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
.combine('type', keysOf(kValuesTypes))
.filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
.beginSubcases()
- .expand('value', u => unique(kMinus3PiTo3Pi, fullRangeForType(kValuesTypes[u.type])))
+ .expand('value', u =>
+ unique(
+ minusThreePiToThreePiRangeForType(kValuesTypes[u.type]),
+ fullRangeForType(kValuesTypes[u.type])
+ )
+ )
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
@@ -56,7 +60,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kIntegerArgumentTypes = objectsToRecord([Type.f32, ...kConcreteIntegerScalarsAndVectors]);
g.test('integer_argument')
.desc(
@@ -70,8 +74,42 @@ Validates that scalar and vector integer arguments are rejected by ${builtin}()
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ type === TypeF32,
+ /* expectedResult */ type === Type.f32,
+
[type.create(0)],
'constant'
);
});
+
+const kArgCases = {
+ good: '(1.1)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_2args: '(1.0,2.0)',
+ // Bad value type for arg 0
+ bad_0i32: '(1i)',
+ bad_0u32: '(1u)',
+ bad_0bool: '(false)',
+ bad_0vec2u: '(vec2u())',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sinh.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sinh.spec.ts
index 09f48751fc..fc43d23cdd 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sinh.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sinh.spec.ts
@@ -6,11 +6,9 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { isRepresentable } from '../../../../../util/floating_point.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -24,7 +22,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -41,13 +39,17 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
.expand('value', u => fullRangeForType(kValuesTypes[u.type]))
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
.fn(t => {
const type = kValuesTypes[t.params.type];
- const expectedResult = isRepresentable(Math.sinh(t.params.value), elementType(type));
+ const expectedResult = isRepresentable(
+ Math.sinh(Number(t.params.value)),
+ // AbstractInt is converted to AbstractFloat before calling into the builtin
+ scalarTypeOf(type).kind === 'abstract-int' ? Type.abstractFloat : scalarTypeOf(type)
+ );
validateConstOrOverrideBuiltinEval(
t,
builtin,
@@ -57,22 +59,40 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kArgCases = {
+ good: '(1.2)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_2arg: '(1.2, 2.3)',
+ // Bad value for arg 0
+ bad_0bool: '(false)',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+ bad_0uint: '(1u)',
+ bad_0int: '(1i)',
+ bad_0vec2i: '(vec2i())',
+ bad_0vec2u: '(vec2u())',
+ bad_0vec3i: '(vec3i())',
+ bad_0vec3u: '(vec3u())',
+ bad_0vec4i: '(vec4i())',
+ bad_0vec4u: '(vec4u())',
+};
-g.test('integer_argument')
- .desc(
- `
-Validates that scalar and vector integer arguments are rejected by ${builtin}()
-`
- )
- .params(u => u.combine('type', keysOf(kIntegerArgumentTypes)))
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
.fn(t => {
- const type = kIntegerArgumentTypes[t.params.type];
- validateConstOrOverrideBuiltinEval(
- t,
- builtin,
- /* expectedResult */ type === TypeF32,
- [type.create(0)],
- 'constant'
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
);
});
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/smoothstep.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/smoothstep.spec.ts
new file mode 100644
index 0000000000..643e5df09e
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/smoothstep.spec.ts
@@ -0,0 +1,241 @@
+const builtin = 'smoothstep';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ concreteTypeOf,
+ elementTypeOf,
+ isConvertibleToFloatType,
+ kAllScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
+const kArgumentTypes = objectsToRecord(kAllScalarsAndVectors);
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() rejects invalid values
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValuesTypes))
+ .filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
+ .beginSubcases()
+ .expand('value1', u => [-1000, -10, 0, 10, 1000])
+ .expand('value2', u => [-1000, -10, 0, 10, 1000])
+ )
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const type = kValuesTypes[t.params.type];
+
+ // We expect to fail if low == high as it results in a DBZ
+ const expectedResult = t.params.value1 !== t.params.value2;
+
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.value1), type.create(t.params.value2), type.create(0)],
+ t.params.stage,
+ /* returnType */ concreteTypeOf(type, [Type.f32])
+ );
+ });
+
+g.test('argument_types')
+ .desc(
+ `
+Validates that scalar and vector arguments are rejected by ${builtin}() if not float type or vecN<float type>
+`
+ )
+ .params(u => u.combine('type', keysOf(kArgumentTypes)))
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kArgumentTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const type = kArgumentTypes[t.params.type];
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ /* expectedResult */ isConvertibleToFloatType(elementTypeOf(type)),
+ [type.create(0), type.create(1), type.create(2)],
+ 'constant',
+ /* returnType */ concreteTypeOf(type, [Type.f32])
+ );
+ });
+
+const kTests = {
+ valid: {
+ src: `_ = ${builtin}(0.0, 42.0, 0.5);`,
+ pass: true,
+ },
+ alias: {
+ src: `_ = ${builtin}(f32_alias(0), f32_alias(42), f32_alias(0.5));`,
+ pass: true,
+ },
+ bool: {
+ src: `_ = ${builtin}(false, false, false);`,
+ pass: false,
+ },
+ i32: {
+ src: `_ = ${builtin}(1i, 2i, 1i);`,
+ pass: false,
+ },
+ u32: {
+ src: `_ = ${builtin}(1u, 2u, 1u);`,
+ pass: false,
+ },
+ f32: {
+ src: `_ = ${builtin}(1.0f, 2.0f, 1.0f);`,
+ pass: true,
+ },
+ f16: {
+ src: `_ = ${builtin}(1h, 2h, 1h);`,
+ pass: true,
+ },
+ mixed_aint_afloat: {
+ src: `_ = ${builtin}(1.0, 2, 1);`,
+ pass: true,
+ },
+ mixed_f32_afloat: {
+ src: `_ = ${builtin}(1.0f, 2.0, 1.0);`,
+ pass: true,
+ },
+ mixed_f16_afloat: {
+ src: `_ = ${builtin}(1.0h, 2.0, 1.0);`,
+ pass: true,
+ },
+ vec_bool: {
+ src: `_ = ${builtin}(vec2<bool>(false, true), vec2<bool>(false, true), vec2<bool>(false, true));`,
+ pass: false,
+ },
+ vec_i32: {
+ src: `_ = ${builtin}(vec2<i32>(1, 1), vec2<i32>(1, 1), vec2<i32>(1, 1));`,
+ pass: false,
+ },
+ vec_u32: {
+ src: `_ = ${builtin}(vec2<u32>(1, 1), vec2<u32>(1, 1), vec2<u32>(1, 1));`,
+ pass: false,
+ },
+ vec_f32: {
+ src: `_ = ${builtin}(vec2<f32>(0, 0), vec2<f32>(1, 1), vec2<f32>(1, 1));`,
+ pass: true,
+ },
+ matrix: {
+ src: `_ = ${builtin}(mat2x2(1, 1, 1, 1), mat2x2(1, 1, 1, 1), mat2x2(1, 1, 1, 1));`,
+ pass: false,
+ },
+ atomic: {
+ src: ` _ = ${builtin}(a, a, a);`,
+ pass: false,
+ },
+ array: {
+ src: `var a: array<bool, 5>;
+ _ = ${builtin}(a, a, a);`,
+ pass: false,
+ },
+ array_runtime: {
+ src: `_ = ${builtin}(k.arry, k.arry, k.arry);`,
+ pass: false,
+ },
+ struct: {
+ src: `var a: A;
+ _ = ${builtin}(a, a, a);`,
+ pass: false,
+ },
+ enumerant: {
+ src: `_ = ${builtin}(read_write, read_write, read_write);`,
+ pass: false,
+ },
+ ptr: {
+ src: `var<function> a = 1.0;
+ let p: ptr<function, f32> = &a;
+ _ = ${builtin}(p, p, p);`,
+ pass: false,
+ },
+ ptr_deref: {
+ src: `var<function> a = 1.0;
+ let p: ptr<function, f32> = &a;
+ _ = ${builtin}(*p, *p, *p);`,
+ pass: true,
+ },
+ sampler: {
+ src: `_ = ${builtin}(s, s, s);`,
+ pass: false,
+ },
+ texture: {
+ src: `_ = ${builtin}(t, t, t);`,
+ pass: false,
+ },
+ no_args: {
+ src: `_ = ${builtin}();`,
+ pass: false,
+ },
+ too_few_args: {
+ src: `_ = ${builtin}(1.0, 2.0);`,
+ pass: false,
+ },
+ too_many_args: {
+ src: `_ = ${builtin}(1.0, 2.0, 3.0, 4.0);`,
+ pass: false,
+ },
+};
+
+g.test('arguments')
+ .desc(`Test that ${builtin} is validated correctly when called with different arguments.`)
+ .params(u => u.combine('test', keysOf(kTests)))
+ .beforeAllSubcases(t => {
+ if (t.params.test.includes('f16')) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const src = kTests[t.params.test].src;
+ const enables = t.params.test.includes('f16') ? 'enable f16;' : '';
+ const code = `
+ ${enables}
+ alias f32_alias = f32;
+
+ @group(0) @binding(0) var s: sampler;
+ @group(0) @binding(1) var t: texture_2d<f32>;
+
+ var<workgroup> a: atomic<u32>;
+
+ struct A {
+ i: bool,
+ }
+ struct B {
+ arry: array<u32>,
+ }
+ @group(0) @binding(3) var<storage> k: B;
+
+ @vertex
+ fn main() -> @builtin(position) vec4<f32> {
+ ${src}
+ return vec4<f32>(.4, .2, .3, .1);
+ }`;
+ t.expectCompileResult(kTests[t.params.test].pass, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sqrt.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sqrt.spec.ts
index a570ce4bc0..cabb0d59fb 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sqrt.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sqrt.spec.ts
@@ -6,11 +6,10 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { isRepresentable } from '../../../../../util/floating_point.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -18,7 +17,7 @@ import { ShaderValidationTest } from '../../../shader_validation_test.js';
import {
fullRangeForType,
kConstantAndOverrideStages,
- kMinusTwoToTwo,
+ minusTwoToTwoRangeForType,
stageSupportsType,
unique,
validateConstOrOverrideBuiltinEval,
@@ -26,7 +25,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -40,17 +39,27 @@ Validates that constant evaluation and override evaluation of ${builtin}() input
.combine('type', keysOf(kValuesTypes))
.filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
.beginSubcases()
- .expand('value', u => unique(kMinusTwoToTwo, fullRangeForType(kValuesTypes[u.type])))
+ .expand('value', u =>
+ unique(
+ minusTwoToTwoRangeForType(kValuesTypes[u.type]),
+ fullRangeForType(kValuesTypes[u.type])
+ )
+ )
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
.fn(t => {
const type = kValuesTypes[t.params.type];
const expectedResult =
- t.params.value >= 0 && isRepresentable(Math.sqrt(t.params.value), elementType(type));
+ t.params.value >= 0 &&
+ isRepresentable(
+ Math.sqrt(Number(t.params.value)),
+ // AbstractInt is converted to AbstractFloat before calling into the builtin
+ scalarTypeOf(type).kind === 'abstract-int' ? Type.abstractFloat : scalarTypeOf(type)
+ );
validateConstOrOverrideBuiltinEval(
t,
builtin,
@@ -60,7 +69,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() input
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kIntegerArgumentTypes = objectsToRecord([Type.f32, ...kConcreteIntegerScalarsAndVectors]);
g.test('integer_argument')
.desc(
@@ -74,8 +83,41 @@ Validates that scalar and vector integer arguments are rejected by ${builtin}()
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ type === TypeF32,
+ /* expectedResult */ type === Type.f32,
[type.create(1)],
'constant'
);
});
+
+const kArgCases = {
+ good: '(1.1)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_too_few: '()',
+ bad_too_many: '(1.0,2.0)',
+ // Bad value type for arg 0
+ bad_0i32: '(1i)',
+ bad_0u32: '(1u)',
+ bad_0bool: '(false)',
+ bad_0vec2u: '(vec2u())',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/step.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/step.spec.ts
new file mode 100644
index 0000000000..a10ef20c7b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/step.spec.ts
@@ -0,0 +1,108 @@
+const builtin = 'step';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValidArgumentTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() error on invalid inputs.
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValidArgumentTypes))
+ .filter(u => stageSupportsType(u.stage, kValidArgumentTypes[u.type]))
+ .beginSubcases()
+ .expand('a', u => fullRangeForType(kValidArgumentTypes[u.type], 5))
+ .expand('b', u => fullRangeForType(kValidArgumentTypes[u.type], 5))
+ )
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kValidArgumentTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const expectedResult = true;
+
+ const type = kValidArgumentTypes[t.params.type];
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.a), type.create(t.params.b)],
+ t.params.stage
+ );
+ });
+
+const kArgCases = {
+ good: '(1.2, 2.3)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_1arg: '(1.2)',
+ bad_3arg: '(1.2, 2.3, 4.5)',
+ // Bad value for arg 0
+ bad_0bool: '(false, 2.3)',
+ bad_0array: '(array(1.1,2.2), 2.3)',
+ bad_0struct: '(modf(2.2), 2.3)',
+ bad_0uint: '(1u, 2.3)',
+ bad_0int: '(1i, 2.3)',
+ bad_0vec2i: '(vec2i(), 2.3)',
+ bad_0vec2u: '(vec2u(), 2.3)',
+ bad_0vec3i: '(vec3i(), 2.3)',
+ bad_0vec3u: '(vec3u(), 2.3)',
+ bad_0vec4i: '(vec4i(), 2.3)',
+ bad_0vec4u: '(vec4u(), 2.3)',
+ // Bad value for arg 1
+ bad_1bool: '(1.2, false)',
+ bad_1array: '(1.2, array(1.1,2.2))',
+ bad_1struct: '(1.2, modf(2.2))',
+ bad_1uint: '(1.2, 1u)',
+ bad_1int: '(1.2, 1i)',
+ bad_1vec2i: '(1.2, vec2i())',
+ bad_1vec2u: '(1.2, vec2u())',
+ bad_1vec3i: '(1.2, vec3i())',
+ bad_1vec3u: '(1.2, vec3u())',
+ bad_1vec4i: '(1.2, vec4i())',
+ bad_1vec4u: '(1.2, vec4u())',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/tan.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/tan.spec.ts
index b9744643f6..9384585dd5 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/tan.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/tan.spec.ts
@@ -6,11 +6,9 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { fpTraitsFor } from '../../../../../util/floating_point.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -18,7 +16,7 @@ import { ShaderValidationTest } from '../../../shader_validation_test.js';
import {
fullRangeForType,
kConstantAndOverrideStages,
- kMinus3PiTo3Pi,
+ minusThreePiToThreePiRangeForType,
stageSupportsType,
unique,
validateConstOrOverrideBuiltinEval,
@@ -26,7 +24,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -40,18 +38,26 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
.combine('type', keysOf(kValuesTypes))
.filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
.beginSubcases()
- .expand('value', u => unique(kMinus3PiTo3Pi, fullRangeForType(kValuesTypes[u.type])))
+ .expand('value', u =>
+ unique(
+ minusThreePiToThreePiRangeForType(kValuesTypes[u.type]),
+ fullRangeForType(kValuesTypes[u.type])
+ )
+ )
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
.fn(t => {
const type = kValuesTypes[t.params.type];
- const fp = fpTraitsFor(elementType(type));
+ const fp = fpTraitsFor(
+ // AbstractInt is converted to AbstractFloat before calling into the builtin
+ scalarTypeOf(type).kind === 'abstract-int' ? Type.abstractFloat : scalarTypeOf(type)
+ );
const smallestPositive = fp.constants().positive.min;
- const v = fp.quantize(t.params.value);
+ const v = fp.quantize(Number(t.params.value));
const expectedResult = Math.abs(Math.cos(v)) > smallestPositive;
validateConstOrOverrideBuiltinEval(
t,
@@ -62,22 +68,40 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kArgCases = {
+ good: '(1.2)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_2arg: '(1.2, 2.3)',
+ // Bad value for arg 0
+ bad_0bool: '(false)',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+ bad_0uint: '(1u)',
+ bad_0int: '(1i)',
+ bad_0vec2i: '(vec2i())',
+ bad_0vec2u: '(vec2u())',
+ bad_0vec3i: '(vec3i())',
+ bad_0vec3u: '(vec3u())',
+ bad_0vec4i: '(vec4i())',
+ bad_0vec4u: '(vec4u())',
+};
-g.test('integer_argument')
- .desc(
- `
-Validates that scalar and vector integer arguments are rejected by ${builtin}()
-`
- )
- .params(u => u.combine('type', keysOf(kIntegerArgumentTypes)))
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
.fn(t => {
- const type = kIntegerArgumentTypes[t.params.type];
- validateConstOrOverrideBuiltinEval(
- t,
- builtin,
- /* expectedResult */ type === TypeF32,
- [type.create(0)],
- 'constant'
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
);
});
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/tanh.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/tanh.spec.ts
new file mode 100644
index 0000000000..965eb85111
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/tanh.spec.ts
@@ -0,0 +1,98 @@
+const builtin = 'tanh';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
+} from '../../../../../util/conversion.js';
+import { isRepresentable } from '../../../../../util/floating_point.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() rejects invalid values
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValuesTypes))
+ .filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
+ .beginSubcases()
+ .expand('value', u => fullRangeForType(kValuesTypes[u.type]))
+ )
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const type = kValuesTypes[t.params.type];
+ const expectedResult = isRepresentable(
+ Math.tanh(Number(t.params.value)),
+ // AbstractInt is converted to AbstractFloat before calling into the builtin
+ scalarTypeOf(type).kind === 'abstract-int' ? Type.abstractFloat : scalarTypeOf(type)
+ );
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.value)],
+ t.params.stage
+ );
+ });
+
+const kArgCases = {
+ good: '(1.2)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_2arg: '(1.2, 2.3)',
+ // Bad value for arg 0
+ bad_0bool: '(false)',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+ bad_0uint: '(1u)',
+ bad_0int: '(1i)',
+ bad_0vec2i: '(vec2i())',
+ bad_0vec2u: '(vec2u())',
+ bad_0vec3i: '(vec3i())',
+ bad_0vec3u: '(vec3u())',
+ bad_0vec4i: '(vec4i())',
+ bad_0vec4u: '(vec4u())',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureGather.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureGather.spec.ts
new file mode 100644
index 0000000000..35a2be1d48
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureGather.spec.ts
@@ -0,0 +1,335 @@
+const builtin = 'textureGather';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+
+* test textureGather component parameter must be correct type
+* test textureGather component parameter must be between 0 and 3 inclusive
+* test textureGather component parameter must be a const expression
+* test textureGather coords parameter must be correct type
+* test textureGather array_index parameter must be correct type
+* test textureGather offset parameter must be correct type
+* test textureGather offset parameter must be a const-expression
+* test textureGather offset parameter must be between -8 and +7 inclusive
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kAllScalarsAndVectors,
+ isConvertible,
+ ScalarType,
+ VectorType,
+ isUnsignedType,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+type TextureGatherArguments = {
+ hasComponentArg?: boolean;
+ coordsArgType: ScalarType | VectorType;
+ hasArrayIndexArg?: boolean;
+ offsetArgType?: VectorType;
+};
+
+const kValidTextureGatherParameterTypes: { [n: string]: TextureGatherArguments } = {
+ 'texture_2d<f32>': {
+ hasComponentArg: true,
+ coordsArgType: Type.vec2f,
+ offsetArgType: Type.vec2i,
+ },
+ 'texture_2d_array<f32>': {
+ hasComponentArg: true,
+ coordsArgType: Type.vec2f,
+ hasArrayIndexArg: true,
+ offsetArgType: Type.vec2i,
+ },
+ 'texture_cube<f32>': { hasComponentArg: true, coordsArgType: Type.vec3f },
+ 'texture_cube_array<f32>': {
+ hasComponentArg: true,
+ coordsArgType: Type.vec3f,
+ hasArrayIndexArg: true,
+ },
+ texture_depth_2d: { coordsArgType: Type.vec2f, offsetArgType: Type.vec2i },
+ texture_depth_2d_array: {
+ coordsArgType: Type.vec2f,
+ hasArrayIndexArg: true,
+ offsetArgType: Type.vec2i,
+ },
+ texture_depth_cube: { coordsArgType: Type.vec3f },
+ texture_depth_cube_array: { coordsArgType: Type.vec3f, hasArrayIndexArg: true },
+} as const;
+
+const kTextureTypes = keysOf(kValidTextureGatherParameterTypes);
+const kValuesTypes = objectsToRecord(kAllScalarsAndVectors);
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('component_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturegather')
+ .desc(
+ `
+Validates that only incorrect components arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', keysOf(kValidTextureGatherParameterTypes))
+ // filter out types with no component argument
+ .filter(t => !!kValidTextureGatherParameterTypes[t.textureType].hasComponentArg)
+ .combine('componentType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-1, 0, 1, 2, 3, 4] as const)
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.componentType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureGatherParameterTypes[t.textureType].offsetArgType ? [false, true] : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, componentType, offset, value } = t.params;
+ const componentArgType = kValuesTypes[componentType];
+ const { offsetArgType, coordsArgType, hasArrayIndexArg } =
+ kValidTextureGatherParameterTypes[textureType];
+
+ const componentWGSL = componentArgType.create(value).wgsl();
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = offset ? `, ${offsetArgType?.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureGather(${componentWGSL}, t, s, ${coordWGSL}${arrayWGSL}${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ (isConvertible(componentArgType, Type.i32) || isConvertible(componentArgType, Type.u32)) &&
+ value >= 0 &&
+ value <= 3;
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('component_argument,non_const')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturegather')
+ .desc(
+ `
+Validates that only non-const components arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', keysOf(kValidTextureGatherParameterTypes))
+ // filter out types with no component argument
+ .filter(t => !!kValidTextureGatherParameterTypes[t.textureType].hasComponentArg)
+ .combine('varType', ['c', 'u', 'l'])
+ .beginSubcases()
+ .expand('offset', t =>
+ kValidTextureGatherParameterTypes[t.textureType].offsetArgType ? [false, true] : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, varType, offset } = t.params;
+ const componentArgType = Type.u32;
+ const { coordsArgType, hasArrayIndexArg, offsetArgType } =
+ kValidTextureGatherParameterTypes[textureType];
+
+ const componentWGSL = `${componentArgType}(${varType})`;
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = offset ? `, ${offsetArgType?.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@group(0) @binding(2) var<uniform> u: ${componentArgType};
+
+@fragment fn fs() -> @location(0) vec4f {
+ const c = 1;
+ let l = 1;
+ let v = textureGather(${componentWGSL}, t, s, ${coordWGSL}${arrayWGSL}${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = varType === 'c';
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('coords_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturegather')
+ .desc(
+ `
+Validates that only incorrect coords arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', keysOf(kValidTextureGatherParameterTypes))
+ .combine('coordType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-1, 0, 1] as const)
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.coordType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureGatherParameterTypes[t.textureType].offsetArgType ? [false, true] : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, coordType, offset, value } = t.params;
+ const coordArgType = kValuesTypes[coordType];
+ const {
+ hasComponentArg,
+ offsetArgType,
+ coordsArgType: coordsRequiredType,
+ hasArrayIndexArg,
+ } = kValidTextureGatherParameterTypes[textureType];
+
+ const componentWGSL = hasComponentArg ? '0, ' : '';
+ const coordWGSL = coordArgType.create(value).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = offset ? `, ${offsetArgType?.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureGather(${componentWGSL}t, s, ${coordWGSL}${arrayWGSL}${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = isConvertible(coordArgType, coordsRequiredType);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('array_index_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturegather')
+ .desc(
+ `
+Validates that only incorrect array_index arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ // filter out types with no array_index
+ .filter(t => !!kValidTextureGatherParameterTypes[t.textureType].hasArrayIndexArg)
+ .combine('arrayIndexType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-9, -8, 0, 7, 8])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.arrayIndexType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureGatherParameterTypes[t.textureType].offsetArgType ? [false, true] : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, arrayIndexType, value, offset } = t.params;
+ const arrayIndexArgType = kValuesTypes[arrayIndexType];
+ const args = [arrayIndexArgType.create(value)];
+ const { hasComponentArg, coordsArgType, offsetArgType } =
+ kValidTextureGatherParameterTypes[textureType];
+
+ const componentWGSL = hasComponentArg ? '0, ' : '';
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = args.map(arg => arg.wgsl()).join(', ');
+ const offsetWGSL = offset ? `, ${offsetArgType!.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureGather(${componentWGSL}t, s, ${coordWGSL}, ${arrayWGSL}${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(arrayIndexArgType, Type.i32) || isConvertible(arrayIndexArgType, Type.u32);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('offset_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturegather')
+ .desc(
+ `
+Validates that only incorrect offset arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ // filter out types with no offset
+ .filter(t => !!kValidTextureGatherParameterTypes[t.textureType].offsetArgType)
+ .combine('offsetType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-9, -8, 0, 7, 8])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.offsetType]) || t.value >= 0)
+ )
+ .fn(t => {
+ const { textureType, offsetType, value } = t.params;
+ const offsetArgType = kValuesTypes[offsetType];
+ const args = [offsetArgType.create(value)];
+ const {
+ hasComponentArg,
+ coordsArgType,
+ hasArrayIndexArg,
+ offsetArgType: offsetRequiredType,
+ } = kValidTextureGatherParameterTypes[textureType];
+
+ const componentWGSL = hasComponentArg ? '0, ' : '';
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = args.map(arg => arg.wgsl()).join(', ');
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureGather(${componentWGSL}t, s, ${coordWGSL}${arrayWGSL}, ${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(offsetArgType, offsetRequiredType!) && value >= -8 && value <= 7;
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('offset_argument,non_const')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturegather')
+ .desc(
+ `
+Validates that only non-const offset arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ .combine('varType', ['c', 'u', 'l'])
+ // filter out types with no offset
+ .filter(t => !!kValidTextureGatherParameterTypes[t.textureType].offsetArgType)
+ )
+ .fn(t => {
+ const { textureType, varType } = t.params;
+ const { hasComponentArg, coordsArgType, hasArrayIndexArg, offsetArgType } =
+ kValidTextureGatherParameterTypes[textureType];
+
+ const componentWGSL = hasComponentArg ? '0, ' : '';
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = `${offsetArgType}(${varType})`;
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@group(0) @binding(2) var<uniform> u: ${offsetArgType};
+@fragment fn fs() -> @location(0) vec4f {
+ const c = 1;
+ let l = ${offsetArgType!.create(0).wgsl()};
+ let v = textureGather(${componentWGSL}t, s, ${coordWGSL}${arrayWGSL}, ${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = varType === 'c';
+ t.expectCompileResult(expectSuccess, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureGatherCompare.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureGatherCompare.spec.ts
new file mode 100644
index 0000000000..84ba2ad95d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureGatherCompare.spec.ts
@@ -0,0 +1,264 @@
+const builtin = 'textureGatherCompare';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+
+* test textureGatherCompare coords parameter must be correct type
+* test textureGatherCompare array_index parameter must be correct type
+* test textureGatherCompare depth_ref parameter must be correct type
+* test textureGatherCompare offset parameter must be correct type
+* test textureGatherCompare offset parameter must be a const-expression
+* test textureGatherCompare offset parameter must be between -8 and +7 inclusive
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kAllScalarsAndVectors,
+ isConvertible,
+ ScalarType,
+ VectorType,
+ isUnsignedType,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+type TextureGatherCompareArguments = {
+ coordsArgType: ScalarType | VectorType;
+ hasArrayIndexArg?: boolean;
+ offsetArgType?: VectorType;
+};
+
+const kValidTextureGatherCompareParameterTypes: { [n: string]: TextureGatherCompareArguments } = {
+ texture_depth_2d: { coordsArgType: Type.vec2f, offsetArgType: Type.vec2i },
+ texture_depth_2d_array: {
+ coordsArgType: Type.vec2f,
+ hasArrayIndexArg: true,
+ offsetArgType: Type.vec2i,
+ },
+ texture_depth_cube: { coordsArgType: Type.vec3f },
+ texture_depth_cube_array: { coordsArgType: Type.vec3f, hasArrayIndexArg: true },
+} as const;
+
+const kTextureTypes = keysOf(kValidTextureGatherCompareParameterTypes);
+const kValuesTypes = objectsToRecord(kAllScalarsAndVectors);
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('coords_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturegathercompare')
+ .desc(
+ `
+Validates that only incorrect coords arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', keysOf(kValidTextureGatherCompareParameterTypes))
+ .combine('coordType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-1, 0, 1] as const)
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.coordType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureGatherCompareParameterTypes[t.textureType].offsetArgType
+ ? [false, true]
+ : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, coordType, offset, value } = t.params;
+ const coordArgType = kValuesTypes[coordType];
+ const {
+ offsetArgType,
+ coordsArgType: coordsRequiredType,
+ hasArrayIndexArg,
+ } = kValidTextureGatherCompareParameterTypes[textureType];
+
+ const coordWGSL = coordArgType.create(value).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = offset ? `, ${offsetArgType?.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler_comparison;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureGatherCompare(t, s, ${coordWGSL}${arrayWGSL}, 0${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = isConvertible(coordArgType, coordsRequiredType);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('array_index_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturegathercompare')
+ .desc(
+ `
+Validates that only incorrect array_index arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ // filter out types with no array_index
+ .filter(t => !!kValidTextureGatherCompareParameterTypes[t.textureType].hasArrayIndexArg)
+ .combine('arrayIndexType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-9, -8, 0, 7, 8])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.arrayIndexType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureGatherCompareParameterTypes[t.textureType].offsetArgType
+ ? [false, true]
+ : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, arrayIndexType, value, offset } = t.params;
+ const arrayIndexArgType = kValuesTypes[arrayIndexType];
+ const args = [arrayIndexArgType.create(value)];
+ const { coordsArgType, offsetArgType } = kValidTextureGatherCompareParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = args.map(arg => arg.wgsl()).join(', ');
+ const offsetWGSL = offset ? `, ${offsetArgType!.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler_comparison;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureGatherCompare(t, s, ${coordWGSL}, ${arrayWGSL}, 0${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(arrayIndexArgType, Type.i32) || isConvertible(arrayIndexArgType, Type.u32);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('depth_ref_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturegathercompare')
+ .desc(
+ `
+Validates that only incorrect depth_ref arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', keysOf(kValidTextureGatherCompareParameterTypes))
+ .combine('depthRefType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-1, 0, 1] as const)
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.depthRefType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureGatherCompareParameterTypes[t.textureType].offsetArgType
+ ? [false, true]
+ : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, depthRefType, offset, value } = t.params;
+ const depthRefArgType = kValuesTypes[depthRefType];
+ const { offsetArgType, coordsArgType, hasArrayIndexArg } =
+ kValidTextureGatherCompareParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const depthRefWGSL = depthRefArgType.create(value).wgsl();
+ const offsetWGSL = offset ? `, ${offsetArgType?.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler_comparison;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureGatherCompare(t, s, ${coordWGSL}${arrayWGSL}, ${depthRefWGSL}${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = isConvertible(depthRefArgType, Type.f32);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('offset_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturegathercompare')
+ .desc(
+ `
+Validates that only incorrect offset arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ // filter out types with no offset
+ .filter(t => !!kValidTextureGatherCompareParameterTypes[t.textureType].offsetArgType)
+ .combine('offsetType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-9, -8, 0, 7, 8])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.offsetType]) || t.value >= 0)
+ )
+ .fn(t => {
+ const { textureType, offsetType, value } = t.params;
+ const offsetArgType = kValuesTypes[offsetType];
+ const args = [offsetArgType.create(value)];
+ const {
+ coordsArgType,
+ hasArrayIndexArg,
+ offsetArgType: offsetRequiredType,
+ } = kValidTextureGatherCompareParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = args.map(arg => arg.wgsl()).join(', ');
+
+ const code = `
+@group(0) @binding(0) var s: sampler_comparison;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureGatherCompare(t, s, ${coordWGSL}${arrayWGSL}, 0, ${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(offsetArgType, offsetRequiredType!) && value >= -8 && value <= 7;
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('offset_argument,non_const')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturegathercompare')
+ .desc(
+ `
+Validates that only non-const offset arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ .combine('varType', ['c', 'u', 'l'])
+ // filter out types with no offset
+ .filter(t => !!kValidTextureGatherCompareParameterTypes[t.textureType].offsetArgType)
+ )
+ .fn(t => {
+ const { textureType, varType } = t.params;
+ const { coordsArgType, hasArrayIndexArg, offsetArgType } =
+ kValidTextureGatherCompareParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = `${offsetArgType}(${varType})`;
+
+ const code = `
+@group(0) @binding(0) var s: sampler_comparison;
+@group(0) @binding(1) var t: ${textureType};
+@group(0) @binding(2) var<uniform> u: ${offsetArgType};
+@fragment fn fs() -> @location(0) vec4f {
+ const c = 1;
+ let l = ${offsetArgType!.create(0).wgsl()};
+ let v = textureGatherCompare(t, s, ${coordWGSL}${arrayWGSL}, 0, ${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = varType === 'c';
+ t.expectCompileResult(expectSuccess, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureLoad.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureLoad.spec.ts
new file mode 100644
index 0000000000..9138b2ecc6
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureLoad.spec.ts
@@ -0,0 +1,370 @@
+const builtin = 'textureLoad';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+
+* test textureLoad coords parameter must be correct type
+* test textureLoad array_index parameter must be correct type
+* test textureLoad level parameter must be correct type
+* test textureLoad sample_index parameter must be correct type
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import { assert } from '../../../../../../common/util/util.js';
+import { kAllTextureFormats, kTextureFormatInfo } from '../../../../../format_info.js';
+import {
+ Type,
+ kAllScalarsAndVectors,
+ isConvertible,
+ ScalarType,
+ VectorType,
+ isUnsignedType,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+type TextureLoadArguments = {
+ coordsArgTypes: readonly [ScalarType | VectorType, ScalarType | VectorType];
+ usesMultipleTypes?: boolean; // texture can use f32, i32, u32
+ hasArrayIndexArg?: boolean;
+ hasLevelArg?: boolean;
+ hasSampleIndexArg?: boolean;
+};
+
+const kCoords1DTypes = [Type.i32, Type.u32] as const;
+const kCoords2DTypes = [Type.vec2i, Type.vec2u] as const;
+const kCoords3DTypes = [Type.vec3i, Type.vec3u] as const;
+
+const kValidTextureLoadParameterTypesForNonStorageTextures: { [n: string]: TextureLoadArguments } =
+ {
+ texture_1d: { usesMultipleTypes: true, coordsArgTypes: kCoords1DTypes, hasLevelArg: true },
+ texture_2d: { usesMultipleTypes: true, coordsArgTypes: kCoords2DTypes, hasLevelArg: true },
+ texture_2d_array: {
+ usesMultipleTypes: true,
+ coordsArgTypes: kCoords2DTypes,
+ hasArrayIndexArg: true,
+ hasLevelArg: true,
+ },
+ texture_3d: { usesMultipleTypes: true, coordsArgTypes: kCoords3DTypes, hasLevelArg: true },
+ texture_multisampled_2d: {
+ usesMultipleTypes: true,
+ coordsArgTypes: kCoords2DTypes,
+ hasSampleIndexArg: true,
+ },
+ texture_depth_2d: { coordsArgTypes: kCoords2DTypes, hasLevelArg: true },
+ texture_depth_2d_array: {
+ coordsArgTypes: kCoords2DTypes,
+ hasArrayIndexArg: true,
+ hasLevelArg: true,
+ },
+ texture_depth_multisampled_2d: { coordsArgTypes: kCoords2DTypes, hasSampleIndexArg: true },
+ texture_external: { coordsArgTypes: kCoords2DTypes },
+ };
+
+const kValidTextureLoadParameterTypesForStorageTextures: { [n: string]: TextureLoadArguments } = {
+ texture_storage_1d: { coordsArgTypes: [Type.i32, Type.u32] },
+ texture_storage_2d: { coordsArgTypes: [Type.vec2i, Type.vec2u] },
+ texture_storage_2d_array: {
+ coordsArgTypes: [Type.vec2i, Type.vec2u],
+ hasArrayIndexArg: true,
+ },
+ texture_storage_3d: { coordsArgTypes: [Type.vec3i, Type.vec3u] },
+} as const;
+
+const kNonStorageTextureTypes = keysOf(kValidTextureLoadParameterTypesForNonStorageTextures);
+const kStorageTextureTypes = keysOf(kValidTextureLoadParameterTypesForStorageTextures);
+const kValuesTypes = objectsToRecord(kAllScalarsAndVectors);
+
+const kTexelType: { [n: string]: Type } = {
+ f32: Type.vec4f,
+ i32: Type.vec4i,
+ u32: Type.vec4u,
+} as const;
+
+const kTexelTypes = keysOf(kTexelType);
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('coords_argument,non_storage')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#textureload')
+ .desc(
+ `
+Validates that only incorrect coords arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kNonStorageTextureTypes)
+ .combine('coordType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .expand('texelType', t =>
+ kValidTextureLoadParameterTypesForNonStorageTextures[t.textureType].usesMultipleTypes
+ ? kTexelTypes
+ : ['']
+ )
+ .combine('value', [-1, 0, 1] as const)
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.coordType]) || t.value >= 0)
+ )
+ .fn(t => {
+ const { textureType, coordType, texelType, value } = t.params;
+ const coordArgType = kValuesTypes[coordType];
+ const { coordsArgTypes, hasArrayIndexArg, hasLevelArg, hasSampleIndexArg } =
+ kValidTextureLoadParameterTypesForNonStorageTextures[textureType];
+
+ const texelTypeWGSL = texelType ? `<${texelType}>` : '';
+ const coordWGSL = coordArgType.create(value).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const levelWGSL = hasLevelArg ? ', 0' : '';
+ const sampleIndexWGSL = hasSampleIndexArg ? ', 0' : '';
+
+ const code = `
+@group(0) @binding(0) var t: ${textureType}${texelTypeWGSL};
+@fragment fn fs() -> @location(0) vec4f {
+ _ = textureLoad(t, ${coordWGSL}${arrayWGSL}${levelWGSL}${sampleIndexWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(coordArgType, coordsArgTypes[0]) ||
+ isConvertible(coordArgType, coordsArgTypes[1]);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('coords_argument,storage')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#textureload')
+ .desc(
+ `
+Validates that only incorrect coords arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kStorageTextureTypes)
+ .combine('coordType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('format', kAllTextureFormats)
+ // filter to only storage texture formats.
+ .filter(t => !!kTextureFormatInfo[t.format].color?.storage)
+ .combine('value', [-1, 0, 1] as const)
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.coordType]) || t.value >= 0)
+ )
+ .beforeAllSubcases(t =>
+ t.skipIfLanguageFeatureNotSupported('readonly_and_readwrite_storage_textures')
+ )
+ .fn(t => {
+ const { textureType, coordType, format, value } = t.params;
+ const coordArgType = kValuesTypes[coordType];
+ const { coordsArgTypes, hasArrayIndexArg } =
+ kValidTextureLoadParameterTypesForStorageTextures[textureType];
+
+ const coordWGSL = coordArgType.create(value).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+
+ const code = `
+@group(0) @binding(0) var t: ${textureType}<${format}, read>;
+@fragment fn fs() -> @location(0) vec4f {
+ _ = textureLoad(t, ${coordWGSL}${arrayWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(coordArgType, coordsArgTypes[0]) ||
+ isConvertible(coordArgType, coordsArgTypes[1]);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('array_index_argument,non_storage')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#textureload')
+ .desc(
+ `
+Validates that only incorrect array_index arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kNonStorageTextureTypes)
+ // filter out types with no array_index
+ .filter(
+ t => !!kValidTextureLoadParameterTypesForNonStorageTextures[t.textureType].hasArrayIndexArg
+ )
+ .combine('arrayIndexType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .expand('texelType', t =>
+ kValidTextureLoadParameterTypesForNonStorageTextures[t.textureType].usesMultipleTypes
+ ? kTexelTypes
+ : ['']
+ )
+ .combine('value', [-1, 0, 1])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.arrayIndexType]) || t.value >= 0)
+ )
+ .fn(t => {
+ const { textureType, arrayIndexType, texelType, value } = t.params;
+ const arrayIndexArgType = kValuesTypes[arrayIndexType];
+ const args = [arrayIndexArgType.create(value)];
+ const { coordsArgTypes, hasLevelArg } =
+ kValidTextureLoadParameterTypesForNonStorageTextures[textureType];
+
+ const texelTypeWGSL = texelType ? `<${texelType}>` : '';
+ const coordWGSL = coordsArgTypes[0].create(0).wgsl();
+ const arrayWGSL = args.map(arg => arg.wgsl()).join(', ');
+ const levelWGSL = hasLevelArg ? ', 0' : '';
+
+ const code = `
+@group(0) @binding(0) var t: ${textureType}${texelTypeWGSL};
+@fragment fn fs() -> @location(0) vec4f {
+ _ = textureLoad(t, ${coordWGSL}, ${arrayWGSL}${levelWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(arrayIndexArgType, Type.i32) || isConvertible(arrayIndexArgType, Type.u32);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('array_index_argument,storage')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#textureload')
+ .desc(
+ `
+Validates that only incorrect array_index arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kStorageTextureTypes)
+ // filter out types with no array_index
+ .filter(
+ t => !!kValidTextureLoadParameterTypesForStorageTextures[t.textureType].hasArrayIndexArg
+ )
+ .combine('arrayIndexType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('format', kAllTextureFormats)
+ // filter to only storage texture formats.
+ .filter(t => !!kTextureFormatInfo[t.format].color?.storage)
+ .combine('value', [-1, 0, 1])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.arrayIndexType]) || t.value >= 0)
+ )
+ .beforeAllSubcases(t =>
+ t.skipIfLanguageFeatureNotSupported('readonly_and_readwrite_storage_textures')
+ )
+ .fn(t => {
+ const { textureType, arrayIndexType, format, value } = t.params;
+ const arrayIndexArgType = kValuesTypes[arrayIndexType];
+ const args = [arrayIndexArgType.create(value)];
+ const { coordsArgTypes, hasLevelArg } =
+ kValidTextureLoadParameterTypesForStorageTextures[textureType];
+
+ const coordWGSL = coordsArgTypes[0].create(0).wgsl();
+ const arrayWGSL = args.map(arg => arg.wgsl()).join(', ');
+ const levelWGSL = hasLevelArg ? ', 0' : '';
+
+ const code = `
+@group(0) @binding(0) var t: ${textureType}<${format}, read>;
+@fragment fn fs() -> @location(0) vec4f {
+ _ = textureLoad(t, ${coordWGSL}, ${arrayWGSL}${levelWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(arrayIndexArgType, Type.i32) || isConvertible(arrayIndexArgType, Type.u32);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('level_argument,non_storage')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#textureload')
+ .desc(
+ `
+Validates that only incorrect level arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kNonStorageTextureTypes)
+ // filter out types with no level
+ .filter(
+ t => !!kValidTextureLoadParameterTypesForNonStorageTextures[t.textureType].hasLevelArg
+ )
+ .combine('levelType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .expand('texelType', t =>
+ kValidTextureLoadParameterTypesForNonStorageTextures[t.textureType].usesMultipleTypes
+ ? kTexelTypes
+ : ['']
+ )
+ .combine('value', [-1, 0, 1])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.levelType]) || t.value >= 0)
+ )
+ .fn(t => {
+ const { textureType, levelType, texelType, value } = t.params;
+ const levelArgType = kValuesTypes[levelType];
+ const { coordsArgTypes, hasArrayIndexArg } =
+ kValidTextureLoadParameterTypesForNonStorageTextures[textureType];
+
+ const texelTypeWGSL = texelType ? `<${texelType}>` : '';
+ const coordWGSL = coordsArgTypes[0].create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const levelWGSL = levelArgType.create(value).wgsl();
+
+ const code = `
+@group(0) @binding(0) var t: ${textureType}${texelTypeWGSL};
+@fragment fn fs() -> @location(0) vec4f {
+ _ = textureLoad(t, ${coordWGSL}${arrayWGSL}, ${levelWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(levelArgType, Type.i32) || isConvertible(levelArgType, Type.u32);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('sample_index_argument,non_storage')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#textureload')
+ .desc(
+ `
+Validates that only incorrect sample_index arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kNonStorageTextureTypes)
+ // filter out types with no sample_index
+ .filter(
+ t => !!kValidTextureLoadParameterTypesForNonStorageTextures[t.textureType].hasSampleIndexArg
+ )
+ .combine('sampleIndexType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .expand('texelType', t =>
+ kValidTextureLoadParameterTypesForNonStorageTextures[t.textureType].usesMultipleTypes
+ ? kTexelTypes
+ : ['']
+ )
+ .combine('value', [-1, 0, 1])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.sampleIndexType]) || t.value >= 0)
+ )
+ .fn(t => {
+ const { textureType, sampleIndexType, texelType, value } = t.params;
+ const sampleIndexArgType = kValuesTypes[sampleIndexType];
+ const { coordsArgTypes, hasArrayIndexArg, hasLevelArg } =
+ kValidTextureLoadParameterTypesForNonStorageTextures[textureType];
+ assert(!hasLevelArg);
+
+ const texelTypeWGSL = texelType ? `<${texelType}>` : '';
+ const coordWGSL = coordsArgTypes[0].create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const sampleIndexWGSL = sampleIndexArgType.create(value).wgsl();
+
+ const code = `
+@group(0) @binding(0) var t: ${textureType}${texelTypeWGSL};
+@fragment fn fs() -> @location(0) vec4f {
+ _ = textureLoad(t, ${coordWGSL}${arrayWGSL}, ${sampleIndexWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(sampleIndexArgType, Type.i32) || isConvertible(sampleIndexArgType, Type.u32);
+ t.expectCompileResult(expectSuccess, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSample.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSample.spec.ts
new file mode 100644
index 0000000000..bdd3cbd8e8
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSample.spec.ts
@@ -0,0 +1,267 @@
+const builtin = 'textureSample';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+
+* test textureSample coords parameter must be correct type
+* test textureSample array_index parameter must be correct type
+* test textureSample coords parameter must be correct type
+* test textureSample offset parameter must be correct type
+* test textureSample offset parameter must be a const-expression
+* test textureSample offset parameter must be between -8 and +7 inclusive
+* test textureSample not usable in a compute or vertex shader
+
+note: uniformity validation is covered in src/webgpu/shader/validation/uniformity/uniformity.spec.ts
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kAllScalarsAndVectors,
+ isConvertible,
+ ScalarType,
+ VectorType,
+ isUnsignedType,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import { kEntryPointsToValidateFragmentOnlyBuiltins } from './shader_stage_utils.js';
+
+type TextureSampleArguments = {
+ coordsArgType: ScalarType | VectorType;
+ hasArrayIndexArg?: boolean;
+ offsetArgType?: VectorType;
+};
+
+const kValidTextureSampleParameterTypes: { [n: string]: TextureSampleArguments } = {
+ 'texture_1d<f32>': { coordsArgType: Type.f32 },
+ 'texture_2d<f32>': { coordsArgType: Type.vec2f, offsetArgType: Type.vec2i },
+ 'texture_2d_array<f32>': {
+ coordsArgType: Type.vec2f,
+ hasArrayIndexArg: true,
+ offsetArgType: Type.vec2i,
+ },
+ 'texture_3d<f32>': { coordsArgType: Type.vec3f, offsetArgType: Type.vec3i },
+ 'texture_cube<f32>': { coordsArgType: Type.vec3f },
+ 'texture_cube_array<f32>': { coordsArgType: Type.vec3f, hasArrayIndexArg: true },
+ texture_depth_2d: { coordsArgType: Type.vec2f, offsetArgType: Type.vec2i },
+ texture_depth_2d_array: {
+ coordsArgType: Type.vec2f,
+ hasArrayIndexArg: true,
+ offsetArgType: Type.vec2i,
+ },
+ texture_depth_cube: { coordsArgType: Type.vec3f },
+ texture_depth_cube_array: { coordsArgType: Type.vec3f, hasArrayIndexArg: true },
+} as const;
+
+const kTextureTypes = keysOf(kValidTextureSampleParameterTypes);
+const kValuesTypes = objectsToRecord(kAllScalarsAndVectors);
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('coords_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesample')
+ .desc(
+ `
+Validates that only incorrect coords arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', keysOf(kValidTextureSampleParameterTypes))
+ .combine('coordType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-1, 0, 1] as const)
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.coordType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureSampleParameterTypes[t.textureType].offsetArgType ? [false, true] : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, coordType, offset, value } = t.params;
+ const coordArgType = kValuesTypes[coordType];
+ const {
+ offsetArgType,
+ coordsArgType: coordsRequiredType,
+ hasArrayIndexArg,
+ } = kValidTextureSampleParameterTypes[textureType];
+
+ const coordWGSL = coordArgType.create(value).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = offset ? `, ${offsetArgType?.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSample(t, s, ${coordWGSL}${arrayWGSL}${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = isConvertible(coordArgType, coordsRequiredType);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('array_index_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesample')
+ .desc(
+ `
+Validates that only incorrect array_index arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ // filter out types with no array_index
+ .filter(t => !!kValidTextureSampleParameterTypes[t.textureType].hasArrayIndexArg)
+ .combine('arrayIndexType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-9, -8, 0, 7, 8])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.arrayIndexType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureSampleParameterTypes[t.textureType].offsetArgType ? [false, true] : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, arrayIndexType, value, offset } = t.params;
+ const arrayIndexArgType = kValuesTypes[arrayIndexType];
+ const args = [arrayIndexArgType.create(value)];
+ const { coordsArgType, offsetArgType } = kValidTextureSampleParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = args.map(arg => arg.wgsl()).join(', ');
+ const offsetWGSL = offset ? `, ${offsetArgType!.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSample(t, s, ${coordWGSL}, ${arrayWGSL}${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(arrayIndexArgType, Type.i32) || isConvertible(arrayIndexArgType, Type.u32);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('offset_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesample')
+ .desc(
+ `
+Validates that only incorrect offset arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ // filter out types with no offset
+ .filter(t => !!kValidTextureSampleParameterTypes[t.textureType].offsetArgType)
+ .combine('offsetType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-9, -8, 0, 7, 8])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.offsetType]) || t.value >= 0)
+ )
+ .fn(t => {
+ const { textureType, offsetType, value } = t.params;
+ const offsetArgType = kValuesTypes[offsetType];
+ const args = [offsetArgType.create(value)];
+ const {
+ coordsArgType,
+ hasArrayIndexArg,
+ offsetArgType: offsetRequiredType,
+ } = kValidTextureSampleParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = args.map(arg => arg.wgsl()).join(', ');
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSample(t, s, ${coordWGSL}${arrayWGSL}, ${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(offsetArgType, offsetRequiredType!) && value >= -8 && value <= 7;
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('offset_argument,non_const')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesample')
+ .desc(
+ `
+Validates that only non-const offset arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ .combine('varType', ['c', 'u', 'l'])
+ // filter out types with no offset
+ .filter(t => !!kValidTextureSampleParameterTypes[t.textureType].offsetArgType)
+ )
+ .fn(t => {
+ const { textureType, varType } = t.params;
+ const { coordsArgType, hasArrayIndexArg, offsetArgType } =
+ kValidTextureSampleParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = `${offsetArgType}(${varType})`;
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@group(0) @binding(2) var<uniform> u: ${offsetArgType};
+@fragment fn fs() -> @location(0) vec4f {
+ const c = 1;
+ let l = ${offsetArgType!.create(0).wgsl()};
+ let v = textureSample(t, s, ${coordWGSL}${arrayWGSL}, ${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = varType === 'c';
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('only_in_fragment')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesample')
+ .desc(
+ `
+Validates that ${builtin} must not be used in a compute or vertex shader.
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ .combine('entryPoint', keysOf(kEntryPointsToValidateFragmentOnlyBuiltins))
+ .expand('offset', t =>
+ kValidTextureSampleParameterTypes[t.textureType].offsetArgType ? [false, true] : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, entryPoint, offset } = t.params;
+ const { coordsArgType, hasArrayIndexArg, offsetArgType } =
+ kValidTextureSampleParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = offset ? `, ${offsetArgType?.create(0).wgsl()}` : '';
+
+ const config = kEntryPointsToValidateFragmentOnlyBuiltins[entryPoint];
+ const code = `
+${config.code}
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+
+fn foo() {
+ _ = textureSample(t, s, ${coordWGSL}${arrayWGSL}${offsetWGSL});
+}`;
+ t.expectCompileResult(config.expectSuccess, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleBaseClampToEdge.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleBaseClampToEdge.spec.ts
new file mode 100644
index 0000000000..4c3a7cb9b7
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleBaseClampToEdge.spec.ts
@@ -0,0 +1,54 @@
+const builtin = 'textureSampleBaseClampToEdge';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+
+* test textureSampleBaseClampToEdge coords parameter must be correct type
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kAllScalarsAndVectors,
+ isConvertible,
+ isUnsignedType,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kTextureSampleBaseClampToEdgeTextureTypes = ['texture_2d<f32>', 'texture_external'] as const;
+const kValuesTypes = objectsToRecord(kAllScalarsAndVectors);
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('coords_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplebaseclamptoedge')
+ .desc(
+ `
+Validates that only incorrect coords arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureSampleBaseClampToEdgeTextureTypes)
+ .combine('coordType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-1, 0, 1] as const)
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.coordType]) || t.value >= 0)
+ )
+ .fn(t => {
+ const { textureType, coordType, value } = t.params;
+ const coordArgType = kValuesTypes[coordType];
+ const coordWGSL = coordArgType.create(value).wgsl();
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSampleBaseClampToEdge(t, s, ${coordWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = isConvertible(coordArgType, Type.vec2f);
+ t.expectCompileResult(expectSuccess, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleBias.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleBias.spec.ts
new file mode 100644
index 0000000000..549add20e1
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleBias.spec.ts
@@ -0,0 +1,309 @@
+const builtin = 'textureSampleBias';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+
+* test textureSampleBias coords parameter must be correct type
+* test textureSampleBias array_index parameter must be correct type
+* test textureSampleBias bias parameter must be correct type
+* test textureSampleBias bias parameter must be between -16.0 and 15.99 inclusive if it's a constant
+* test textureSampleBias offset parameter must be correct type
+* test textureSampleBias offset parameter must be a const-expression
+* test textureSampleBias offset parameter must be between -8 and +7 inclusive
+
+note: uniformity validation is covered in src/webgpu/shader/validation/uniformity/uniformity.spec.ts
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kAllScalarsAndVectors,
+ isConvertible,
+ ScalarType,
+ VectorType,
+ isUnsignedType,
+ scalarTypeOf,
+ isFloatType,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import { kEntryPointsToValidateFragmentOnlyBuiltins } from './shader_stage_utils.js';
+
+type TextureSampleBiasArguments = {
+ coordsArgType: ScalarType | VectorType;
+ hasArrayIndexArg?: boolean;
+ offsetArgType?: VectorType;
+};
+
+const kValidTextureSampleBiasParameterTypes: { [n: string]: TextureSampleBiasArguments } = {
+ 'texture_2d<f32>': { coordsArgType: Type.vec2f, offsetArgType: Type.vec2i },
+ 'texture_2d_array<f32>': {
+ coordsArgType: Type.vec2f,
+ hasArrayIndexArg: true,
+ offsetArgType: Type.vec2i,
+ },
+ 'texture_3d<f32>': { coordsArgType: Type.vec3f, offsetArgType: Type.vec3i },
+ 'texture_cube<f32>': { coordsArgType: Type.vec3f },
+ 'texture_cube_array<f32>': { coordsArgType: Type.vec3f, hasArrayIndexArg: true },
+} as const;
+
+const kTextureTypes = keysOf(kValidTextureSampleBiasParameterTypes);
+const kValuesTypes = objectsToRecord(kAllScalarsAndVectors);
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('coords_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplebias')
+ .desc(
+ `
+Validates that only incorrect coords arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', keysOf(kValidTextureSampleBiasParameterTypes))
+ .combine('coordType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-1, 0, 1] as const)
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.coordType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureSampleBiasParameterTypes[t.textureType].offsetArgType ? [false, true] : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, coordType, offset, value } = t.params;
+ const coordArgType = kValuesTypes[coordType];
+ const {
+ offsetArgType,
+ coordsArgType: coordsRequiredType,
+ hasArrayIndexArg,
+ } = kValidTextureSampleBiasParameterTypes[textureType];
+
+ const coordWGSL = coordArgType.create(value).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = offset ? `, ${offsetArgType?.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSampleBias(t, s, ${coordWGSL}${arrayWGSL}, 0${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = isConvertible(coordArgType, coordsRequiredType);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('array_index_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplebias')
+ .desc(
+ `
+Validates that only incorrect array_index arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ // filter out types with no array_index
+ .filter(t => !!kValidTextureSampleBiasParameterTypes[t.textureType].hasArrayIndexArg)
+ .combine('arrayIndexType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-9, -8, 0, 7, 8])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.arrayIndexType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureSampleBiasParameterTypes[t.textureType].offsetArgType ? [false, true] : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, arrayIndexType, value, offset } = t.params;
+ const arrayIndexArgType = kValuesTypes[arrayIndexType];
+ const args = [arrayIndexArgType.create(value)];
+ const { coordsArgType, offsetArgType } = kValidTextureSampleBiasParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = args.map(arg => arg.wgsl()).join(', ');
+ const offsetWGSL = offset ? `, ${offsetArgType!.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSampleBias(t, s, ${coordWGSL}, ${arrayWGSL}, 0${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(arrayIndexArgType, Type.i32) || isConvertible(arrayIndexArgType, Type.u32);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('bias_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplebias')
+ .desc(
+ `
+Validates that only incorrect bias arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ // filter out types with no offset
+ .filter(t => !!kValidTextureSampleBiasParameterTypes[t.textureType].offsetArgType)
+ .combine('biasType', keysOf(kValuesTypes))
+ .beginSubcases()
+ // The spec mentions limits of > -16 and < 15.99 so pass some values around there
+ // No error is mentioned for out of range values so make sure no error is generated.
+ .combine('value', [-17, -16, -8, 0, 7, 15.99, 16])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.biasType]) || t.value >= 0)
+ // filter out non-integer values passed to integer types.
+ .filter(t => Number.isInteger(t.value) || isFloatType(scalarTypeOf(kValuesTypes[t.biasType])))
+ .expand('offset', t =>
+ kValidTextureSampleBiasParameterTypes[t.textureType].offsetArgType ? [false, true] : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, biasType, value, offset } = t.params;
+ const biasArgType = kValuesTypes[biasType];
+ const args = [biasArgType.create(value)];
+ const { coordsArgType, hasArrayIndexArg, offsetArgType } =
+ kValidTextureSampleBiasParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const biasWGSL = args.map(arg => arg.wgsl()).join(', ');
+ const offsetWGSL = offset ? `, ${offsetArgType!.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSampleBias(t, s, ${coordWGSL}${arrayWGSL}, ${biasWGSL}${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = isConvertible(biasArgType, Type.f32);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('offset_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplebias')
+ .desc(
+ `
+Validates that only incorrect offset arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ // filter out types with no offset
+ .filter(t => !!kValidTextureSampleBiasParameterTypes[t.textureType].offsetArgType)
+ .combine('offsetType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-9, -8, 0, 7, 8])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.offsetType]) || t.value >= 0)
+ )
+ .fn(t => {
+ const { textureType, offsetType, value } = t.params;
+ const offsetArgType = kValuesTypes[offsetType];
+ const args = [offsetArgType.create(value)];
+ const {
+ coordsArgType,
+ hasArrayIndexArg,
+ offsetArgType: offsetRequiredType,
+ } = kValidTextureSampleBiasParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = args.map(arg => arg.wgsl()).join(', ');
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSampleBias(t, s, ${coordWGSL}${arrayWGSL}, 0, ${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(offsetArgType, offsetRequiredType!) && value >= -8 && value <= 7;
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('offset_argument,non_const')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplebias')
+ .desc(
+ `
+Validates that only non-const offset arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ .combine('varType', ['c', 'u', 'l'])
+ // filter out types with no offset
+ .filter(t => !!kValidTextureSampleBiasParameterTypes[t.textureType].offsetArgType)
+ )
+ .fn(t => {
+ const { textureType, varType } = t.params;
+ const { coordsArgType, hasArrayIndexArg, offsetArgType } =
+ kValidTextureSampleBiasParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = `${offsetArgType}(${varType})`;
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@group(0) @binding(2) var<uniform> u: ${offsetArgType};
+@fragment fn fs() -> @location(0) vec4f {
+ const c = 1;
+ let l = ${offsetArgType!.create(0).wgsl()};
+ let v = textureSampleBias(t, s, ${coordWGSL}${arrayWGSL}, 0, ${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = varType === 'c';
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('only_in_fragment')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesample')
+ .desc(
+ `
+Validates that ${builtin} must not be used in a compute or vertex shader.
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ .combine('entryPoint', keysOf(kEntryPointsToValidateFragmentOnlyBuiltins))
+ .expand('offset', t =>
+ kValidTextureSampleBiasParameterTypes[t.textureType].offsetArgType ? [false, true] : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, entryPoint, offset } = t.params;
+ const { coordsArgType, hasArrayIndexArg, offsetArgType } =
+ kValidTextureSampleBiasParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = offset ? `, ${offsetArgType?.create(0).wgsl()}` : '';
+
+ const config = kEntryPointsToValidateFragmentOnlyBuiltins[entryPoint];
+ const code = `
+${config.code}
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+
+fn foo() {
+ _ = textureSampleBias(t, s, ${coordWGSL}${arrayWGSL}, 0${offsetWGSL});
+}`;
+ t.expectCompileResult(config.expectSuccess, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleCompare.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleCompare.spec.ts
new file mode 100644
index 0000000000..9c07d0354a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleCompare.spec.ts
@@ -0,0 +1,308 @@
+const builtin = 'textureSampleCompare';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+
+* test textureSampleCompare coords parameter must be correct type
+* test textureSampleCompare array_index parameter must be correct type
+* test textureSampleCompare depth_ref parameter must be correct type
+* test textureSampleCompare offset parameter must be correct type
+* test textureSampleCompare offset parameter must be a const-expression
+* test textureSampleCompare offset parameter must be between -8 and +7 inclusive
+* test textureSample not usable in a compute or vertex shader
+
+note: uniformity validation is covered in src/webgpu/shader/validation/uniformity/uniformity.spec.ts
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kAllScalarsAndVectors,
+ isConvertible,
+ ScalarType,
+ VectorType,
+ isUnsignedType,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import { kEntryPointsToValidateFragmentOnlyBuiltins } from './shader_stage_utils.js';
+
+type TextureSampleCompareArguments = {
+ coordsArgType: ScalarType | VectorType;
+ hasArrayIndexArg?: boolean;
+ offsetArgType?: VectorType;
+};
+
+const kValidTextureSampleCompareParameterTypes: { [n: string]: TextureSampleCompareArguments } = {
+ texture_depth_2d: { coordsArgType: Type.vec2f, offsetArgType: Type.vec2i },
+ texture_depth_2d_array: {
+ coordsArgType: Type.vec2f,
+ hasArrayIndexArg: true,
+ offsetArgType: Type.vec2i,
+ },
+ texture_depth_cube: { coordsArgType: Type.vec3f },
+ texture_depth_cube_array: { coordsArgType: Type.vec3f, hasArrayIndexArg: true },
+} as const;
+
+const kTextureTypes = keysOf(kValidTextureSampleCompareParameterTypes);
+const kValuesTypes = objectsToRecord(kAllScalarsAndVectors);
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('coords_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplecompare')
+ .desc(
+ `
+Validates that only incorrect coords arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', keysOf(kValidTextureSampleCompareParameterTypes))
+ .combine('coordType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-1, 0, 1] as const)
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.coordType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureSampleCompareParameterTypes[t.textureType].offsetArgType
+ ? [false, true]
+ : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, coordType, offset, value } = t.params;
+ const coordArgType = kValuesTypes[coordType];
+ const {
+ offsetArgType,
+ coordsArgType: coordsRequiredType,
+ hasArrayIndexArg,
+ } = kValidTextureSampleCompareParameterTypes[textureType];
+
+ const coordWGSL = coordArgType.create(value).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = offset ? `, ${offsetArgType?.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler_comparison;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ _ = textureSampleCompare(t, s, ${coordWGSL}${arrayWGSL}, 0${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = isConvertible(coordArgType, coordsRequiredType);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('array_index_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplecompare')
+ .desc(
+ `
+Validates that only incorrect array_index arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ // filter out types with no array_index
+ .filter(t => !!kValidTextureSampleCompareParameterTypes[t.textureType].hasArrayIndexArg)
+ .combine('arrayIndexType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-9, -8, 0, 7, 8])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.arrayIndexType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureSampleCompareParameterTypes[t.textureType].offsetArgType
+ ? [false, true]
+ : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, arrayIndexType, value, offset } = t.params;
+ const arrayIndexArgType = kValuesTypes[arrayIndexType];
+ const args = [arrayIndexArgType.create(value)];
+ const { coordsArgType, offsetArgType } = kValidTextureSampleCompareParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = args.map(arg => arg.wgsl()).join(', ');
+ const offsetWGSL = offset ? `, ${offsetArgType!.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler_comparison;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ _ = textureSampleCompare(t, s, ${coordWGSL}, ${arrayWGSL}, 0${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(arrayIndexArgType, Type.i32) || isConvertible(arrayIndexArgType, Type.u32);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('depth_ref_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplecompare')
+ .desc(
+ `
+Validates that only incorrect depth_ref arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ .combine('depthRefType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-1, 0, 1])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.depthRefType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureSampleCompareParameterTypes[t.textureType].offsetArgType
+ ? [false, true]
+ : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, depthRefType, value, offset } = t.params;
+ const depthRefArgType = kValuesTypes[depthRefType];
+ const args = [depthRefArgType.create(value)];
+ const { coordsArgType, hasArrayIndexArg, offsetArgType } =
+ kValidTextureSampleCompareParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const depthRefWGSL = args.map(arg => arg.wgsl()).join(', ');
+ const offsetWGSL = offset ? `, ${offsetArgType!.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler_comparison;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ _ = textureSampleCompare(t, s, ${coordWGSL}${arrayWGSL}, ${depthRefWGSL}${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = isConvertible(depthRefArgType, Type.f32);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('offset_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplecompare')
+ .desc(
+ `
+Validates that only incorrect offset arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ // filter out types with no offset
+ .filter(t => !!kValidTextureSampleCompareParameterTypes[t.textureType].offsetArgType)
+ .combine('offsetType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-9, -8, 0, 7, 8])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.offsetType]) || t.value >= 0)
+ )
+ .fn(t => {
+ const { textureType, offsetType, value } = t.params;
+ const offsetArgType = kValuesTypes[offsetType];
+ const args = [offsetArgType.create(value)];
+ const {
+ coordsArgType,
+ hasArrayIndexArg,
+ offsetArgType: offsetRequiredType,
+ } = kValidTextureSampleCompareParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = args.map(arg => arg.wgsl()).join(', ');
+
+ const code = `
+@group(0) @binding(0) var s: sampler_comparison;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ _ = textureSampleCompare(t, s, ${coordWGSL}${arrayWGSL}, 0, ${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(offsetArgType, offsetRequiredType!) && value >= -8 && value <= 7;
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('offset_argument,non_const')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplecompare')
+ .desc(
+ `
+Validates that only non-const offset arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ .combine('varType', ['c', 'u', 'l'])
+ // filter out types with no offset
+ .filter(t => !!kValidTextureSampleCompareParameterTypes[t.textureType].offsetArgType)
+ )
+ .fn(t => {
+ const { textureType, varType } = t.params;
+ const { coordsArgType, hasArrayIndexArg, offsetArgType } =
+ kValidTextureSampleCompareParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = `${offsetArgType}(${varType})`;
+
+ const code = `
+@group(0) @binding(0) var s: sampler_comparison;
+@group(0) @binding(1) var t: ${textureType};
+@group(0) @binding(2) var<uniform> u: ${offsetArgType};
+@fragment fn fs() -> @location(0) vec4f {
+ const c = 1;
+ let l = ${offsetArgType?.create(0).wgsl()};
+ _ = textureSampleCompare(t, s, ${coordWGSL}${arrayWGSL}, 0, ${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = varType === 'c';
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('only_in_fragment')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesample')
+ .desc(
+ `
+Validates that ${builtin} must not be used in a compute or vertex shader.
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ .combine('entryPoint', keysOf(kEntryPointsToValidateFragmentOnlyBuiltins))
+ .expand('offset', t =>
+ kValidTextureSampleCompareParameterTypes[t.textureType].offsetArgType
+ ? [false, true]
+ : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, entryPoint, offset } = t.params;
+ const { coordsArgType, hasArrayIndexArg, offsetArgType } =
+ kValidTextureSampleCompareParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = offset ? `, ${offsetArgType?.create(0).wgsl()}` : '';
+
+ const config = kEntryPointsToValidateFragmentOnlyBuiltins[entryPoint];
+ const code = `
+${config.code}
+@group(0) @binding(0) var s: sampler_comparison;
+@group(0) @binding(1) var t: ${textureType};
+
+fn foo() {
+ _ = textureSampleCompare(t, s, ${coordWGSL}${arrayWGSL}, 0${offsetWGSL});
+}`;
+ t.expectCompileResult(config.expectSuccess, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleCompareLevel.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleCompareLevel.spec.ts
new file mode 100644
index 0000000000..cfb2090dc7
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleCompareLevel.spec.ts
@@ -0,0 +1,268 @@
+const builtin = 'textureSampleCompareLevel';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+
+* test textureSampleCompareLevel coords parameter must be correct type
+* test textureSampleCompareLevel array_index parameter must be correct type
+* test textureSampleCompareLevel depth_ref parameter must be correct type
+* test textureSampleCompareLevel offset parameter must be correct type
+* test textureSampleCompareLevel offset parameter must be a const-expression
+* test textureSampleCompareLevel offset parameter must be between -8 and +7 inclusive
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kAllScalarsAndVectors,
+ isConvertible,
+ ScalarType,
+ VectorType,
+ isUnsignedType,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+type TextureSampleCompareLevelArguments = {
+ coordsArgType: ScalarType | VectorType;
+ hasArrayIndexArg?: boolean;
+ offsetArgType?: VectorType;
+};
+
+const kValidTextureSampleCompareLevelParameterTypes: {
+ [n: string]: TextureSampleCompareLevelArguments;
+} = {
+ texture_depth_2d: { coordsArgType: Type.vec2f, offsetArgType: Type.vec2i },
+ texture_depth_2d_array: {
+ coordsArgType: Type.vec2f,
+ hasArrayIndexArg: true,
+ offsetArgType: Type.vec2i,
+ },
+ texture_depth_cube: { coordsArgType: Type.vec3f },
+ texture_depth_cube_array: { coordsArgType: Type.vec3f, hasArrayIndexArg: true },
+} as const;
+
+const kTextureTypes = keysOf(kValidTextureSampleCompareLevelParameterTypes);
+const kValuesTypes = objectsToRecord(kAllScalarsAndVectors);
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('coords_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplecomparelevel')
+ .desc(
+ `
+Validates that only incorrect coords arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', keysOf(kValidTextureSampleCompareLevelParameterTypes))
+ .combine('coordType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-1, 0, 1] as const)
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.coordType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureSampleCompareLevelParameterTypes[t.textureType].offsetArgType
+ ? [false, true]
+ : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, coordType, offset, value } = t.params;
+ const coordArgType = kValuesTypes[coordType];
+ const {
+ offsetArgType,
+ coordsArgType: coordsRequiredType,
+ hasArrayIndexArg,
+ } = kValidTextureSampleCompareLevelParameterTypes[textureType];
+
+ const coordWGSL = coordArgType.create(value).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = offset ? `, ${offsetArgType?.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler_comparison;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSampleCompareLevel(t, s, ${coordWGSL}${arrayWGSL}, 0${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = isConvertible(coordArgType, coordsRequiredType);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('array_index_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplecomparelevel')
+ .desc(
+ `
+Validates that only incorrect array_index arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ // filter out types with no array_index
+ .filter(t => !!kValidTextureSampleCompareLevelParameterTypes[t.textureType].hasArrayIndexArg)
+ .combine('arrayIndexType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-9, -8, 0, 7, 8])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.arrayIndexType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureSampleCompareLevelParameterTypes[t.textureType].offsetArgType
+ ? [false, true]
+ : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, arrayIndexType, value, offset } = t.params;
+ const arrayIndexArgType = kValuesTypes[arrayIndexType];
+ const args = [arrayIndexArgType.create(value)];
+ const { coordsArgType, offsetArgType } =
+ kValidTextureSampleCompareLevelParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = args.map(arg => arg.wgsl()).join(', ');
+ const offsetWGSL = offset ? `, ${offsetArgType!.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler_comparison;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSampleCompareLevel(t, s, ${coordWGSL}, ${arrayWGSL}, 0${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(arrayIndexArgType, Type.i32) || isConvertible(arrayIndexArgType, Type.u32);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('depth_ref_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplecomparelevel')
+ .desc(
+ `
+Validates that only incorrect depth_ref arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ .combine('depthRefType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-1, 0, 1])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.depthRefType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureSampleCompareLevelParameterTypes[t.textureType].offsetArgType
+ ? [false, true]
+ : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, depthRefType, value, offset } = t.params;
+ const depthRefArgType = kValuesTypes[depthRefType];
+ const args = [depthRefArgType.create(value)];
+ const { coordsArgType, hasArrayIndexArg, offsetArgType } =
+ kValidTextureSampleCompareLevelParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const depthRefWGSL = args.map(arg => arg.wgsl()).join(', ');
+ const offsetWGSL = offset ? `, ${offsetArgType!.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler_comparison;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSampleCompareLevel(t, s, ${coordWGSL}${arrayWGSL}, ${depthRefWGSL}${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = isConvertible(depthRefArgType, Type.f32);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('offset_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplecomparelevel')
+ .desc(
+ `
+Validates that only incorrect offset arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ // filter out types with no offset
+ .filter(t => !!kValidTextureSampleCompareLevelParameterTypes[t.textureType].offsetArgType)
+ .combine('offsetType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-9, -8, 0, 7, 8])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.offsetType]) || t.value >= 0)
+ )
+ .fn(t => {
+ const { textureType, offsetType, value } = t.params;
+ const offsetArgType = kValuesTypes[offsetType];
+ const args = [offsetArgType.create(value)];
+ const {
+ coordsArgType,
+ hasArrayIndexArg,
+ offsetArgType: offsetRequiredType,
+ } = kValidTextureSampleCompareLevelParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = args.map(arg => arg.wgsl()).join(', ');
+
+ const code = `
+@group(0) @binding(0) var s: sampler_comparison;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSampleCompareLevel(t, s, ${coordWGSL}${arrayWGSL}, 0, ${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(offsetArgType, offsetRequiredType!) && value >= -8 && value <= 7;
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('offset_argument,non_const')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplecomparelevel')
+ .desc(
+ `
+Validates that only non-const offset arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ .combine('varType', ['c', 'u', 'l'])
+ // filter out types with no offset
+ .filter(t => !!kValidTextureSampleCompareLevelParameterTypes[t.textureType].offsetArgType)
+ )
+ .fn(t => {
+ const { textureType, varType } = t.params;
+ const { coordsArgType, hasArrayIndexArg, offsetArgType } =
+ kValidTextureSampleCompareLevelParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = `${offsetArgType}(${varType})`;
+
+ const code = `
+@group(0) @binding(0) var s: sampler_comparison;
+@group(0) @binding(1) var t: ${textureType};
+@group(0) @binding(2) var<uniform> u: ${offsetArgType};
+@fragment fn fs() -> @location(0) vec4f {
+ const c = 1;
+ let l = ${offsetArgType?.create(0).wgsl()};
+ let v = textureSampleCompareLevel(t, s, ${coordWGSL}${arrayWGSL}, 0, ${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = varType === 'c';
+ t.expectCompileResult(expectSuccess, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleGrad.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleGrad.spec.ts
new file mode 100644
index 0000000000..3d1b522f86
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleGrad.spec.ts
@@ -0,0 +1,317 @@
+const builtin = 'textureSampleGrad';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+
+* test textureSampleGrad coords parameter must be correct type
+* test textureSampleGrad array_index parameter must be correct type
+* test textureSampleGrad ddX parameter must be correct type
+* test textureSampleGrad ddY parameter must be correct type
+* test textureSampleGrad coords parameter must be correct type
+* test textureSampleGrad offset parameter must be correct type
+* test textureSampleGrad offset parameter must be a const-expression
+* test textureSampleGrad offset parameter must be between -8 and +7 inclusive
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kAllScalarsAndVectors,
+ isConvertible,
+ ScalarType,
+ VectorType,
+ isUnsignedType,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+// Note: ddX and ddy parameter types match coords so we'll use coordsArgType for ddX and ddY.
+type TextureSampleGradArguments = {
+ coordsArgType: ScalarType | VectorType;
+ hasArrayIndexArg?: boolean;
+ offsetArgType?: VectorType;
+};
+
+const kValidTextureSampleGradParameterTypes: { [n: string]: TextureSampleGradArguments } = {
+ 'texture_2d<f32>': {
+ coordsArgType: Type.vec2f,
+ offsetArgType: Type.vec2i,
+ },
+ 'texture_2d_array<f32>': {
+ coordsArgType: Type.vec2f,
+ hasArrayIndexArg: true,
+ offsetArgType: Type.vec2i,
+ },
+ 'texture_3d<f32>': { coordsArgType: Type.vec3f, offsetArgType: Type.vec3i },
+ 'texture_cube<f32>': { coordsArgType: Type.vec3f },
+ 'texture_cube_array<f32>': { coordsArgType: Type.vec3f, hasArrayIndexArg: true },
+} as const;
+
+const kTextureTypes = keysOf(kValidTextureSampleGradParameterTypes);
+const kValuesTypes = objectsToRecord(kAllScalarsAndVectors);
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('coords_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplegrad')
+ .desc(
+ `
+Validates that only incorrect coords arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', keysOf(kValidTextureSampleGradParameterTypes))
+ .combine('coordType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-1, 0, 1] as const)
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.coordType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureSampleGradParameterTypes[t.textureType].offsetArgType ? [false, true] : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, coordType, offset, value } = t.params;
+ const coordArgType = kValuesTypes[coordType];
+ const {
+ offsetArgType,
+ coordsArgType: coordsRequiredType,
+ hasArrayIndexArg,
+ } = kValidTextureSampleGradParameterTypes[textureType];
+
+ const coordWGSL = coordArgType.create(value).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const ddWGSL = coordsRequiredType.create(0).wgsl();
+ const offsetWGSL = offset ? `, ${offsetArgType?.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSampleGrad(t, s, ${coordWGSL}${arrayWGSL}, ${ddWGSL}, ${ddWGSL}${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = isConvertible(coordArgType, coordsRequiredType);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('array_index_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplegrad')
+ .desc(
+ `
+Validates that only incorrect array_index arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ // filter out types with no array_index
+ .filter(t => !!kValidTextureSampleGradParameterTypes[t.textureType].hasArrayIndexArg)
+ .combine('arrayIndexType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-9, -8, 0, 7, 8])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.arrayIndexType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureSampleGradParameterTypes[t.textureType].offsetArgType ? [false, true] : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, arrayIndexType, value, offset } = t.params;
+ const arrayIndexArgType = kValuesTypes[arrayIndexType];
+ const args = [arrayIndexArgType.create(value)];
+ const { coordsArgType, offsetArgType } = kValidTextureSampleGradParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = args.map(arg => arg.wgsl()).join(', ');
+ const ddWGSL = coordsArgType.create(0).wgsl();
+ const offsetWGSL = offset ? `, ${offsetArgType!.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSampleGrad(t, s, ${coordWGSL}, ${arrayWGSL}, ${ddWGSL}, ${ddWGSL}${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(arrayIndexArgType, Type.i32) || isConvertible(arrayIndexArgType, Type.u32);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('ddX_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplegrad')
+ .desc(
+ `
+Validates that only incorrect ddX arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ .combine('ddxType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-1, 0, 1])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.ddxType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureSampleGradParameterTypes[t.textureType].offsetArgType ? [false, true] : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, ddxType, value, offset } = t.params;
+ const ddxArgType = kValuesTypes[ddxType];
+ const args = [ddxArgType.create(value)];
+ const { coordsArgType, hasArrayIndexArg, offsetArgType } =
+ kValidTextureSampleGradParameterTypes[textureType];
+
+ const ddxRequiredType = coordsArgType;
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const ddXWGSL = args.map(arg => arg.wgsl()).join(', ');
+ const ddYWGSL = coordsArgType.create(0).wgsl();
+ const offsetWGSL = offset ? `, ${offsetArgType?.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSampleGrad(t, s, ${coordWGSL}${arrayWGSL}, ${ddXWGSL}, ${ddYWGSL}${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = isConvertible(ddxArgType, ddxRequiredType);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('ddY_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplegrad')
+ .desc(
+ `
+Validates that only incorrect ddY arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ .combine('ddyType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-1, 0, 1])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.ddyType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureSampleGradParameterTypes[t.textureType].offsetArgType ? [false, true] : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, ddyType, value, offset } = t.params;
+ const ddyArgType = kValuesTypes[ddyType];
+ const args = [ddyArgType.create(value)];
+ const { coordsArgType, hasArrayIndexArg, offsetArgType } =
+ kValidTextureSampleGradParameterTypes[textureType];
+
+ const ddyRequiredType = coordsArgType;
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const ddXWGSL = coordsArgType.create(0).wgsl();
+ const ddYWGSL = args.map(arg => arg.wgsl()).join(', ');
+ const offsetWGSL = offset ? `, ${offsetArgType?.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSampleGrad(t, s, ${coordWGSL}${arrayWGSL}, ${ddXWGSL}, ${ddYWGSL}${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = isConvertible(ddyArgType, ddyRequiredType);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('offset_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplegrad')
+ .desc(
+ `
+Validates that only incorrect offset arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ // filter out types with no offset
+ .filter(t => !!kValidTextureSampleGradParameterTypes[t.textureType].offsetArgType)
+ .combine('offsetType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-9, -8, 0, 7, 8])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.offsetType]) || t.value >= 0)
+ )
+ .fn(t => {
+ const { textureType, offsetType, value } = t.params;
+ const offsetArgType = kValuesTypes[offsetType];
+ const args = [offsetArgType.create(value)];
+ const {
+ coordsArgType,
+ hasArrayIndexArg,
+ offsetArgType: offsetRequiredType,
+ } = kValidTextureSampleGradParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const ddWGSL = coordsArgType.create(0).wgsl();
+ const offsetWGSL = args.map(arg => arg.wgsl()).join(', ');
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSampleGrad(t, s, ${coordWGSL}${arrayWGSL}, ${ddWGSL}, ${ddWGSL}, ${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(offsetArgType, offsetRequiredType!) && value >= -8 && value <= 7;
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('offset_argument,non_const')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplegrad')
+ .desc(
+ `
+Validates that only non-const offset arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ .combine('varType', ['c', 'u', 'l'])
+ // filter out types with no offset
+ .filter(t => !!kValidTextureSampleGradParameterTypes[t.textureType].offsetArgType)
+ )
+ .fn(t => {
+ const { textureType, varType } = t.params;
+ const { coordsArgType, hasArrayIndexArg, offsetArgType } =
+ kValidTextureSampleGradParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const ddWGSL = coordsArgType.create(0).wgsl();
+ const offsetWGSL = `${offsetArgType}(${varType})`;
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@group(0) @binding(2) var<uniform> u: ${offsetArgType};
+@fragment fn fs() -> @location(0) vec4f {
+ const c = 1;
+ let l = ${offsetArgType!.create(0).wgsl()};
+ let v = textureSampleGrad(t, s, ${coordWGSL}${arrayWGSL}, ${ddWGSL}, ${ddWGSL}, ${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = varType === 'c';
+ t.expectCompileResult(expectSuccess, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleLevel.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleLevel.spec.ts
new file mode 100644
index 0000000000..9a6701421c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleLevel.spec.ts
@@ -0,0 +1,282 @@
+const builtin = 'textureSampleLevel';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+
+* test textureSampleLevel coords parameter must be correct type
+* test textureSampleLevel array_index parameter must be correct type
+* test textureSampleLevel level parameter must be correct type
+* test textureSampleLevel offset parameter must be correct type
+* test textureSampleLevel offset parameter must be a const-expression
+* test textureSampleLevel offset parameter must be between -8 and +7 inclusive
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kAllScalarsAndVectors,
+ isConvertible,
+ ScalarType,
+ VectorType,
+ isUnsignedType,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+type TextureSampleLevelArguments = {
+ coordsArgType: ScalarType | VectorType;
+ hasArrayIndexArg?: boolean;
+ levelIsF32?: boolean;
+ offsetArgType?: VectorType;
+};
+
+const kValidTextureSampleLevelParameterTypes: { [n: string]: TextureSampleLevelArguments } = {
+ 'texture_2d<f32>': { coordsArgType: Type.vec2f, levelIsF32: true, offsetArgType: Type.vec2i },
+ 'texture_2d_array<f32>': {
+ coordsArgType: Type.vec2f,
+ hasArrayIndexArg: true,
+ levelIsF32: true,
+ offsetArgType: Type.vec2i,
+ },
+ 'texture_3d<f32>': { coordsArgType: Type.vec3f, levelIsF32: true, offsetArgType: Type.vec3i },
+ 'texture_cube<f32>': { coordsArgType: Type.vec3f, levelIsF32: true },
+ 'texture_cube_array<f32>': {
+ coordsArgType: Type.vec3f,
+ hasArrayIndexArg: true,
+ levelIsF32: true,
+ },
+ texture_depth_2d: { coordsArgType: Type.vec2f, offsetArgType: Type.vec2i },
+ texture_depth_2d_array: {
+ coordsArgType: Type.vec2f,
+ hasArrayIndexArg: true,
+ offsetArgType: Type.vec2i,
+ },
+ texture_depth_cube: { coordsArgType: Type.vec3f },
+ texture_depth_cube_array: { coordsArgType: Type.vec3f, hasArrayIndexArg: true },
+} as const;
+
+const kTextureTypes = keysOf(kValidTextureSampleLevelParameterTypes);
+const kValuesTypes = objectsToRecord(kAllScalarsAndVectors);
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('coords_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplelevel')
+ .desc(
+ `
+Validates that only incorrect coords arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', keysOf(kValidTextureSampleLevelParameterTypes))
+ .combine('coordType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-1, 0, 1] as const)
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.coordType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureSampleLevelParameterTypes[t.textureType].offsetArgType
+ ? [false, true]
+ : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, coordType, offset, value } = t.params;
+ const coordArgType = kValuesTypes[coordType];
+ const {
+ offsetArgType,
+ coordsArgType: coordsRequiredType,
+ hasArrayIndexArg,
+ } = kValidTextureSampleLevelParameterTypes[textureType];
+
+ const coordWGSL = coordArgType.create(value).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = offset ? `, ${offsetArgType?.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSampleLevel(t, s, ${coordWGSL}${arrayWGSL}, 0${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = isConvertible(coordArgType, coordsRequiredType);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('array_index_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplelevel')
+ .desc(
+ `
+Validates that only incorrect array_index arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ // filter out types with no array_index
+ .filter(t => !!kValidTextureSampleLevelParameterTypes[t.textureType].hasArrayIndexArg)
+ .combine('arrayIndexType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-9, -8, 0, 7, 8])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.arrayIndexType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureSampleLevelParameterTypes[t.textureType].offsetArgType
+ ? [false, true]
+ : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, arrayIndexType, value, offset } = t.params;
+ const arrayIndexArgType = kValuesTypes[arrayIndexType];
+ const args = [arrayIndexArgType.create(value)];
+ const { coordsArgType, offsetArgType } = kValidTextureSampleLevelParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = args.map(arg => arg.wgsl()).join(', ');
+ const offsetWGSL = offset ? `, ${offsetArgType!.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSampleLevel(t, s, ${coordWGSL}, ${arrayWGSL}, 0${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(arrayIndexArgType, Type.i32) || isConvertible(arrayIndexArgType, Type.u32);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('level_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplelevel')
+ .desc(
+ `
+Validates that only incorrect level arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ .combine('levelType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-1, 0, 1])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.levelType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureSampleLevelParameterTypes[t.textureType].offsetArgType
+ ? [false, true]
+ : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, levelType, value, offset } = t.params;
+ const levelArgType = kValuesTypes[levelType];
+ const args = [levelArgType.create(value)];
+ const { coordsArgType, hasArrayIndexArg, offsetArgType, levelIsF32 } =
+ kValidTextureSampleLevelParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const levelWGSL = args.map(arg => arg.wgsl()).join(', ');
+ const offsetWGSL = offset ? `, ${offsetArgType!.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSampleLevel(t, s, ${coordWGSL}${arrayWGSL}, ${levelWGSL}${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = levelIsF32
+ ? isConvertible(levelArgType, Type.f32)
+ : isConvertible(levelArgType, Type.i32) || isConvertible(levelArgType, Type.u32);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('offset_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplelevel')
+ .desc(
+ `
+Validates that only incorrect offset arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ // filter out types with no offset
+ .filter(t => !!kValidTextureSampleLevelParameterTypes[t.textureType].offsetArgType)
+ .combine('offsetType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-9, -8, 0, 7, 8])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.offsetType]) || t.value >= 0)
+ )
+ .fn(t => {
+ const { textureType, offsetType, value } = t.params;
+ const offsetArgType = kValuesTypes[offsetType];
+ const args = [offsetArgType.create(value)];
+ const {
+ coordsArgType,
+ hasArrayIndexArg,
+ offsetArgType: offsetRequiredType,
+ } = kValidTextureSampleLevelParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = args.map(arg => arg.wgsl()).join(', ');
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSampleLevel(t, s, ${coordWGSL}${arrayWGSL}, 0, ${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(offsetArgType, offsetRequiredType!) && value >= -8 && value <= 7;
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('offset_argument,non_const')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplelevel')
+ .desc(
+ `
+Validates that only non-const offset arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ .combine('varType', ['c', 'u', 'l'])
+ // filter out types with no offset
+ .filter(t => !!kValidTextureSampleLevelParameterTypes[t.textureType].offsetArgType)
+ )
+ .fn(t => {
+ const { textureType, varType } = t.params;
+ const { coordsArgType, hasArrayIndexArg, offsetArgType } =
+ kValidTextureSampleLevelParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = `${offsetArgType}(${varType})`;
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@group(0) @binding(2) var<uniform> u: ${offsetArgType};
+@fragment fn fs() -> @location(0) vec4f {
+ const c = 1;
+ let l = ${offsetArgType!.create(0).wgsl()};
+ let v = textureSampleLevel(t, s, ${coordWGSL}${arrayWGSL}, 0, ${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = varType === 'c';
+ t.expectCompileResult(expectSuccess, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureStore.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureStore.spec.ts
new file mode 100644
index 0000000000..6613377732
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureStore.spec.ts
@@ -0,0 +1,168 @@
+const builtin = 'textureStore';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+
+* test textureStore coords parameter must be correct type
+* test textureStore array_index parameter must be correct type
+* test textureStore value parameter must be correct type
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import { kAllTextureFormats, kTextureFormatInfo } from '../../../../../format_info.js';
+import {
+ Type,
+ kAllScalarsAndVectors,
+ isConvertible,
+ ScalarType,
+ VectorType,
+ isUnsignedType,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kTextureColorTypeToType = {
+ sint: Type.vec4i,
+ uint: Type.vec4u,
+ float: Type.vec4f,
+ 'unfilterable-float': Type.vec4f,
+};
+
+type TextureStoreArguments = {
+ coordsArgTypes: readonly [ScalarType | VectorType, ScalarType | VectorType];
+ hasArrayIndexArg?: boolean;
+};
+
+const kValidTextureStoreParameterTypes: { [n: string]: TextureStoreArguments } = {
+ texture_storage_1d: { coordsArgTypes: [Type.i32, Type.u32] },
+ texture_storage_2d: { coordsArgTypes: [Type.vec2i, Type.vec2u] },
+ texture_storage_2d_array: {
+ coordsArgTypes: [Type.vec2i, Type.vec2u],
+ hasArrayIndexArg: true,
+ },
+ texture_storage_3d: { coordsArgTypes: [Type.vec3i, Type.vec3u] },
+} as const;
+
+const kTextureTypes = keysOf(kValidTextureStoreParameterTypes);
+const kValuesTypes = objectsToRecord(kAllScalarsAndVectors);
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('coords_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturestore')
+ .desc(
+ `
+Validates that only incorrect coords arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', keysOf(kValidTextureStoreParameterTypes))
+ .combine('coordType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-1, 0, 1] as const)
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.coordType]) || t.value >= 0)
+ )
+ .fn(t => {
+ const { textureType, coordType, value } = t.params;
+ const coordArgType = kValuesTypes[coordType];
+ const { coordsArgTypes, hasArrayIndexArg } = kValidTextureStoreParameterTypes[textureType];
+
+ const coordWGSL = coordArgType.create(value).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const format = 'rgba8unorm';
+ const valueWGSL = 'vec4f(0)';
+
+ const code = `
+@group(0) @binding(0) var t: ${textureType}<${format},write>;
+@fragment fn fs() -> @location(0) vec4f {
+ textureStore(t, ${coordWGSL}${arrayWGSL}, ${valueWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(coordArgType, coordsArgTypes[0]) ||
+ isConvertible(coordArgType, coordsArgTypes[1]);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('array_index_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturestore')
+ .desc(
+ `
+Validates that only incorrect array_index arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ // filter out types with no array_index
+ .filter(t => !!kValidTextureStoreParameterTypes[t.textureType].hasArrayIndexArg)
+ .combine('arrayIndexType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-9, -8, 0, 7, 8])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.arrayIndexType]) || t.value >= 0)
+ )
+ .fn(t => {
+ const { textureType, arrayIndexType, value } = t.params;
+ const arrayIndexArgType = kValuesTypes[arrayIndexType];
+ const args = [arrayIndexArgType.create(value)];
+ const { coordsArgTypes } = kValidTextureStoreParameterTypes[textureType];
+
+ const coordWGSL = coordsArgTypes[0].create(0).wgsl();
+ const arrayWGSL = args.map(arg => arg.wgsl()).join(', ');
+ const format = 'rgba8unorm';
+ const valueWGSL = 'vec4f(0)';
+
+ const code = `
+@group(0) @binding(0) var t: ${textureType}<${format}, write>;
+@fragment fn fs() -> @location(0) vec4f {
+ textureStore(t, ${coordWGSL}, ${arrayWGSL}, ${valueWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(arrayIndexArgType, Type.i32) || isConvertible(arrayIndexArgType, Type.u32);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('value_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturestore')
+ .desc(
+ `
+Validates that only incorrect value arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ .combine('valueType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('format', kAllTextureFormats)
+ // filter to only storage texture formats.
+ .filter(t => !!kTextureFormatInfo[t.format].color?.storage)
+ .combine('value', [0, 1, 2])
+ )
+ .fn(t => {
+ const { textureType, valueType, format, value } = t.params;
+ const valueArgType = kValuesTypes[valueType];
+ const args = [valueArgType.create(value)];
+ const { coordsArgTypes, hasArrayIndexArg } = kValidTextureStoreParameterTypes[textureType];
+
+ const coordWGSL = coordsArgTypes[0].create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const valueWGSL = args.map(arg => arg.wgsl()).join(', ');
+
+ const code = `
+@group(0) @binding(0) var t: ${textureType}<${format}, write>;
+@fragment fn fs() -> @location(0) vec4f {
+ textureStore(t, ${coordWGSL}${arrayWGSL}, ${valueWGSL});
+ return vec4f(0);
+}
+`;
+ const colorType = kTextureFormatInfo[format].color?.type;
+ const requiredValueType = kTextureColorTypeToType[colorType!];
+ const expectSuccess = isConvertible(valueArgType, requiredValueType);
+ t.expectCompileResult(expectSuccess, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/trunc.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/trunc.spec.ts
new file mode 100644
index 0000000000..e0bed7dc5e
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/trunc.spec.ts
@@ -0,0 +1,94 @@
+const builtin = 'trunc';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValidArgumentTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() error on invalid inputs.
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValidArgumentTypes))
+ .filter(u => stageSupportsType(u.stage, kValidArgumentTypes[u.type]))
+ .beginSubcases()
+ .expand('value', u => fullRangeForType(kValidArgumentTypes[u.type]))
+ )
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kValidArgumentTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const expectedResult = true;
+
+ const type = kValidArgumentTypes[t.params.type];
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.value)],
+ t.params.stage
+ );
+ });
+
+const kArgCases = {
+ good: '(1.2)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_2arg: '(1.2, 2.3)',
+ // Bad value for arg 0
+ bad_0bool: '(false)',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+ bad_0uint: '(1u)',
+ bad_0int: '(1i)',
+ bad_0vec2i: '(vec2i())',
+ bad_0vec2u: '(vec2u())',
+ bad_0vec3i: '(vec3i())',
+ bad_0vec3u: '(vec3u())',
+ bad_0vec4i: '(vec4i())',
+ bad_0vec4u: '(vec4u())',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack2x16float.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack2x16float.spec.ts
new file mode 100644
index 0000000000..bac245f99a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack2x16float.spec.ts
@@ -0,0 +1,62 @@
+const kFn = 'unpack2x16float';
+export const description = `Validate ${kFn}`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kArgCases = {
+ good_u32: '(1u)',
+ good_aint: '(1)',
+ bad_0args: '()',
+ bad_2args: '(1u,2u)',
+ bad_i32: '(1i)',
+ bad_f32: '(1f)',
+ bad_f16: '(1h)',
+ bad_bool: '(false)',
+ bad_vec2u: '(vec2u())',
+ bad_vec3u: '(vec3u())',
+ bad_vec4u: '(vec4u())',
+ bad_array: '(array(1))',
+ bad_struct: '(modf(1.1))',
+};
+const kGoodArgs = kArgCases['good_u32'];
+const kReturnType = 'vec2f';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('args')
+ .desc(`Test compilation failure of ${kFn} with various numbers of and types of arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .beforeAllSubcases(t => {
+ if (t.params.arg === 'bad_f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ let code = '';
+ if (t.params.arg === 'bad_f16') {
+ code += 'enable f16;\n';
+ }
+ code += `const c = ${kFn}${kArgCases[t.params.arg]};`;
+
+ t.expectCompileResult(t.params.arg.startsWith('good'), code);
+ });
+
+g.test('return')
+ .desc(`Test ${kFn} return value type ${kReturnType}`)
+ .params(u => u.combine('type', ['vec2u', 'vec2i', 'vec2f', 'vec2h', 'vec4f', 'vec3f', 'f32']))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.type === kReturnType,
+ `const c: ${t.params.type} = ${kFn}${kGoodArgs};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${kFn} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${kFn}${kGoodArgs}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack2x16snorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack2x16snorm.spec.ts
new file mode 100644
index 0000000000..d1cd6c5c4d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack2x16snorm.spec.ts
@@ -0,0 +1,62 @@
+const kFn = 'unpack2x16snorm';
+export const description = `Validate ${kFn}`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kArgCases = {
+ good_u32: '(1u)',
+ good_aint: '(1)',
+ bad_0args: '()',
+ bad_2args: '(1u,2u)',
+ bad_i32: '(1i)',
+ bad_f32: '(1f)',
+ bad_f16: '(1h)',
+ bad_bool: '(false)',
+ bad_vec2u: '(vec2u())',
+ bad_vec3u: '(vec3u())',
+ bad_vec4u: '(vec4u())',
+ bad_array: '(array(1))',
+ bad_struct: '(modf(1.1))',
+};
+const kGoodArgs = kArgCases['good_u32'];
+const kReturnType = 'vec2f';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('args')
+ .desc(`Test compilation failure of ${kFn} with various numbers of and types of arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .beforeAllSubcases(t => {
+ if (t.params.arg === 'bad_f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ let code = '';
+ if (t.params.arg === 'bad_f16') {
+ code += 'enable f16;\n';
+ }
+ code += `const c = ${kFn}${kArgCases[t.params.arg]};`;
+
+ t.expectCompileResult(t.params.arg.startsWith('good'), code);
+ });
+
+g.test('return')
+ .desc(`Test ${kFn} return value type ${kReturnType}`)
+ .params(u => u.combine('type', ['vec2u', 'vec2i', 'vec2f', 'vec2h', 'vec4f', 'vec3f', 'f32']))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.type === kReturnType,
+ `const c: ${t.params.type} = ${kFn}${kGoodArgs};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${kFn} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${kFn}${kGoodArgs}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack2x16unorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack2x16unorm.spec.ts
new file mode 100644
index 0000000000..7fcc96f22f
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack2x16unorm.spec.ts
@@ -0,0 +1,62 @@
+const kFn = 'unpack2x16unorm';
+export const description = `Validate ${kFn}`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kArgCases = {
+ good_u32: '(1u)',
+ good_aint: '(1)',
+ bad_0args: '()',
+ bad_2args: '(1u,2u)',
+ bad_i32: '(1i)',
+ bad_f32: '(1f)',
+ bad_f16: '(1h)',
+ bad_bool: '(false)',
+ bad_vec2u: '(vec2u())',
+ bad_vec3u: '(vec3u())',
+ bad_vec4u: '(vec4u())',
+ bad_array: '(array(1))',
+ bad_struct: '(modf(1.1))',
+};
+const kGoodArgs = kArgCases['good_u32'];
+const kReturnType = 'vec2f';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('args')
+ .desc(`Test compilation failure of ${kFn} with various numbers of and types of arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .beforeAllSubcases(t => {
+ if (t.params.arg === 'bad_f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ let code = '';
+ if (t.params.arg === 'bad_f16') {
+ code += 'enable f16;\n';
+ }
+ code += `const c = ${kFn}${kArgCases[t.params.arg]};`;
+
+ t.expectCompileResult(t.params.arg.startsWith('good'), code);
+ });
+
+g.test('return')
+ .desc(`Test ${kFn} return value type ${kReturnType}`)
+ .params(u => u.combine('type', ['vec2u', 'vec2i', 'vec2f', 'vec2h', 'vec4f', 'vec3f', 'f32']))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.type === kReturnType,
+ `const c: ${t.params.type} = ${kFn}${kGoodArgs};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${kFn} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${kFn}${kGoodArgs}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack4x8snorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack4x8snorm.spec.ts
new file mode 100644
index 0000000000..98052f4494
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack4x8snorm.spec.ts
@@ -0,0 +1,62 @@
+const kFn = 'unpack4x8snorm';
+export const description = `Validate ${kFn}`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kArgCases = {
+ good_u32: '(1u)',
+ good_aint: '(1)',
+ bad_0args: '()',
+ bad_2args: '(1u,2u)',
+ bad_i32: '(1i)',
+ bad_f32: '(1f)',
+ bad_f16: '(1h)',
+ bad_bool: '(false)',
+ bad_vec2u: '(vec2u())',
+ bad_vec3u: '(vec3u())',
+ bad_vec4u: '(vec4u())',
+ bad_array: '(array(1))',
+ bad_struct: '(modf(1.1))',
+};
+const kGoodArgs = kArgCases['good_u32'];
+const kReturnType = 'vec4f';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('args')
+ .desc(`Test compilation failure of ${kFn} with various numbers of and types of arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .beforeAllSubcases(t => {
+ if (t.params.arg === 'bad_f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ let code = '';
+ if (t.params.arg === 'bad_f16') {
+ code += 'enable f16;\n';
+ }
+ code += `const c = ${kFn}${kArgCases[t.params.arg]};`;
+
+ t.expectCompileResult(t.params.arg.startsWith('good'), code);
+ });
+
+g.test('return')
+ .desc(`Test ${kFn} return value type ${kReturnType}`)
+ .params(u => u.combine('type', ['vec4u', 'vec4i', 'vec4f', 'vec4h', 'vec3f', 'vec2f', 'f32']))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.type === kReturnType,
+ `const c: ${t.params.type} = ${kFn}${kGoodArgs};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${kFn} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${kFn}${kGoodArgs}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack4x8unorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack4x8unorm.spec.ts
new file mode 100644
index 0000000000..746443641a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack4x8unorm.spec.ts
@@ -0,0 +1,62 @@
+const kFn = 'unpack4x8unorm';
+export const description = `Validate ${kFn}`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kArgCases = {
+ good_u32: '(1u)',
+ good_aint: '(1)',
+ bad_0args: '()',
+ bad_2args: '(1u,2u)',
+ bad_i32: '(1i)',
+ bad_f32: '(1f)',
+ bad_f16: '(1h)',
+ bad_bool: '(false)',
+ bad_vec2u: '(vec2u())',
+ bad_vec3u: '(vec3u())',
+ bad_vec4u: '(vec4u())',
+ bad_array: '(array(1))',
+ bad_struct: '(modf(1.1))',
+};
+const kGoodArgs = kArgCases['good_u32'];
+const kReturnType = 'vec4f';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('args')
+ .desc(`Test compilation failure of ${kFn} with various numbers of and types of arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .beforeAllSubcases(t => {
+ if (t.params.arg === 'bad_f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ let code = '';
+ if (t.params.arg === 'bad_f16') {
+ code += 'enable f16;\n';
+ }
+ code += `const c = ${kFn}${kArgCases[t.params.arg]};`;
+
+ t.expectCompileResult(t.params.arg.startsWith('good'), code);
+ });
+
+g.test('return')
+ .desc(`Test ${kFn} return value type ${kReturnType}`)
+ .params(u => u.combine('type', ['vec4u', 'vec4i', 'vec4f', 'vec4h', 'vec3f', 'vec2f', 'f32']))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.type === kReturnType,
+ `const c: ${t.params.type} = ${kFn}${kGoodArgs};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${kFn} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${kFn}${kGoodArgs}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack4xI8.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack4xI8.spec.ts
new file mode 100644
index 0000000000..9736818b71
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack4xI8.spec.ts
@@ -0,0 +1,61 @@
+export const description = `Validate unpack4xI8`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kFeature = 'packed_4x8_integer_dot_product';
+const kFn = 'unpack4xI8';
+const kArgCases = {
+ good: '(1u)',
+ bad_0args: '()',
+ bad_2args: '(1u,2u)',
+ bad_0i32: '(1i)',
+ bad_0f32: '(1f)',
+ bad_0bool: '(false)',
+ bad_0vec2u: '(vec2u())',
+ bad_0vec3u: '(vec3u())',
+ bad_0vec4u: '(vec4u())',
+ bad_0array: '(array(1))',
+ bad_0struct: '(modf(1.1))',
+};
+const kGoodArgs = kArgCases['good'];
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('unsupported')
+ .desc(`Test absence of ${kFn} when ${kFeature} is not supported.`)
+ .params(u => u.combine('requires', [false, true]))
+ .fn(t => {
+ t.skipIfLanguageFeatureSupported(kFeature);
+ const preamble = t.params.requires ? `requires ${kFeature}; ` : '';
+ const code = `${preamble}const c = ${kFn}${kGoodArgs};`;
+ t.expectCompileResult(false, code);
+ });
+
+g.test('supported')
+ .desc(`Test presence of ${kFn} when ${kFeature} is supported.`)
+ .params(u => u.combine('requires', [false, true]))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ const preamble = t.params.requires ? `requires ${kFeature}; ` : '';
+ const code = `${preamble}const c = ${kFn}${kGoodArgs};`;
+ t.expectCompileResult(true, code);
+ });
+
+g.test('args')
+ .desc(`Test compilation failure of ${kFn} with various numbers of and types of arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ t.expectCompileResult(t.params.arg === 'good', `const c = ${kFn}${kArgCases[t.params.arg]};`);
+ });
+
+g.test('must_use')
+ .desc(`Result of ${kFn} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${kFn}${kGoodArgs}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack4xU8.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack4xU8.spec.ts
new file mode 100644
index 0000000000..34b96a4615
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack4xU8.spec.ts
@@ -0,0 +1,61 @@
+export const description = `Validate unpack4xU8`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kFeature = 'packed_4x8_integer_dot_product';
+const kFn = 'unpack4xU8';
+const kArgCases = {
+ good: '(1u)',
+ bad_0args: '()',
+ bad_2args: '(1u,2u)',
+ bad_0i32: '(1i)',
+ bad_0f32: '(1f)',
+ bad_0bool: '(false)',
+ bad_0vec2u: '(vec2u())',
+ bad_0vec3u: '(vec3u())',
+ bad_0vec4u: '(vec4u())',
+ bad_0array: '(array(1))',
+ bad_0struct: '(modf(1.1))',
+};
+const kGoodArgs = kArgCases['good'];
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('unsupported')
+ .desc(`Test absence of ${kFn} when ${kFeature} is not supported.`)
+ .params(u => u.combine('requires', [false, true]))
+ .fn(t => {
+ t.skipIfLanguageFeatureSupported(kFeature);
+ const preamble = t.params.requires ? `requires ${kFeature}; ` : '';
+ const code = `${preamble}const c = ${kFn}${kGoodArgs};`;
+ t.expectCompileResult(false, code);
+ });
+
+g.test('supported')
+ .desc(`Test presence of ${kFn} when ${kFeature} is supported.`)
+ .params(u => u.combine('requires', [false, true]))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ const preamble = t.params.requires ? `requires ${kFeature}; ` : '';
+ const code = `${preamble}const c = ${kFn}${kGoodArgs};`;
+ t.expectCompileResult(true, code);
+ });
+
+g.test('args')
+ .desc(`Test compilation failure of ${kFn} with various numbers of and types of arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ t.expectCompileResult(t.params.arg === 'good', `const c = ${kFn}${kArgCases[t.params.arg]};`);
+ });
+
+g.test('must_use')
+ .desc(`Result of ${kFn} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${kFn}${kGoodArgs}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/workgroupUniformLoad.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/workgroupUniformLoad.spec.ts
new file mode 100644
index 0000000000..ac7fd042eb
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/workgroupUniformLoad.spec.ts
@@ -0,0 +1,122 @@
+export const description = `
+Validation tests for the workgroupUniformLoad() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kEntryPoints = {
+ none: { supportsBarrier: true, code: `` },
+ compute: {
+ supportsBarrier: true,
+ code: `@compute @workgroup_size(1)
+fn main() {
+ foo();
+}`,
+ },
+ vertex: {
+ supportsBarrier: false,
+ code: `@vertex
+fn main() -> @builtin(position) vec4f {
+ foo();
+ return vec4f();
+}`,
+ },
+ fragment: {
+ supportsBarrier: false,
+ code: `@fragment
+fn main() {
+ foo();
+}`,
+ },
+ compute_and_fragment: {
+ supportsBarrier: false,
+ code: `@compute @workgroup_size(1)
+fn main1() {
+ foo();
+}
+
+@fragment
+fn main2() {
+ foo();
+}
+`,
+ },
+ fragment_without_call: {
+ supportsBarrier: true,
+ code: `@fragment
+fn main() {
+}
+`,
+ },
+};
+
+g.test('only_in_compute')
+ .specURL('https://www.w3.org/TR/WGSL/#sync-builtin-functions')
+ .desc(
+ `
+Synchronization functions must only be used in the compute shader stage.
+`
+ )
+ .params(u =>
+ u
+ .combine('entry_point', keysOf(kEntryPoints))
+ .combine('call', ['bar()', 'workgroupUniformLoad(&wgvar)'])
+ )
+ .fn(t => {
+ const config = kEntryPoints[t.params.entry_point];
+ const code = `
+${config.code}
+
+var<workgroup> wgvar : u32;
+
+fn bar() -> u32 {
+ return 0;
+}
+
+fn foo() {
+ _ = ${t.params.call};
+}`;
+ t.expectCompileResult(t.params.call === 'bar()' || config.supportsBarrier, code);
+ });
+
+// A list of types that contains atomics, with a single control case.
+const kAtomicTypes: string[] = [
+ 'bool', // control case
+ 'atomic<i32>',
+ 'atomic<u32>',
+ 'array<atomic<i32>, 4>',
+ 'AtomicStruct',
+];
+
+g.test('no_atomics')
+ .desc(
+ `
+The argument passed to workgroupUniformLoad cannot contain any atomic types.
+
+NOTE: Various other valid types are tested via execution tests, so we only check for invalid types here.
+`
+ )
+ .params(u =>
+ u.combine('type', kAtomicTypes).combine('call', ['bar()', 'workgroupUniformLoad(&wgvar)'])
+ )
+ .fn(t => {
+ const code = `
+struct AtomicStruct {
+ a : atomic<u32>
+}
+
+var<workgroup> wgvar : ${t.params.type};
+
+fn bar() -> bool {
+ return true;
+}
+
+fn foo() {
+ _ = ${t.params.call};
+}`;
+ t.expectCompileResult(t.params.type === 'bool' || t.params.call === 'bar()', code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/overload_resolution.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/overload_resolution.spec.ts
new file mode 100644
index 0000000000..65ba9b0f35
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/overload_resolution.spec.ts
@@ -0,0 +1,268 @@
+export const description = `Validation tests for implicit conversions and overload resolution`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../common/util/data_tables.js';
+import {
+ kAllNumericScalarsAndVectors,
+ isConvertible,
+ VectorType,
+} from '../../../util/conversion.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+interface Case {
+ expr: string;
+ valid: boolean;
+ f16?: boolean;
+}
+
+const kImplicitConversionCases: Record<string, Case> = {
+ absint_to_bool: {
+ expr: `any(1)`,
+ valid: false,
+ },
+ absint_to_u32: {
+ expr: `1 == 1u`,
+ valid: true,
+ },
+ absint_to_i32: {
+ expr: `1 == 1i`,
+ valid: true,
+ },
+ absint_to_f32: {
+ expr: `1 == 1f`,
+ valid: true,
+ },
+ absint_to_f16: {
+ expr: `1 == 1h`,
+ valid: true,
+ f16: true,
+ },
+ absfloat_to_bool: {
+ expr: `any(1.0)`,
+ valid: false,
+ },
+ absfloat_to_u32: {
+ expr: `1.0 == 1u`,
+ valid: false,
+ },
+ absfloat_to_i32: {
+ expr: `1.0 == 1i`,
+ valid: false,
+ },
+ absfloat_to_f32: {
+ expr: `1.0 == 1f`,
+ valid: true,
+ },
+ absfloat_to_f16: {
+ expr: `1.0 == 1h`,
+ valid: true,
+ f16: true,
+ },
+ vector_absint_to_bool: {
+ expr: `any(vec2(1))`,
+ valid: false,
+ },
+ vector_absint_to_u32: {
+ expr: `all(vec2(1) == vec2u(1u))`,
+ valid: true,
+ },
+ vector_absint_to_i32: {
+ expr: `all(vec3(1) == vec3i(1i))`,
+ valid: true,
+ },
+ vector_absint_to_f32: {
+ expr: `all(vec4(1) == vec4f(1f))`,
+ valid: true,
+ },
+ vector_absint_to_f16: {
+ expr: `all(vec2(1) == vec2h(1h))`,
+ valid: true,
+ f16: true,
+ },
+ vector_absfloat_to_bool: {
+ expr: `any(vec2(1.0))`,
+ valid: false,
+ },
+ vector_absfloat_to_u32: {
+ expr: `all(vec2(1.0) == vec2u(1u))`,
+ valid: false,
+ },
+ vector_absfloat_to_i32: {
+ expr: `all(vec3(1.0) == vec2i(1i))`,
+ valid: false,
+ },
+ vector_absfloat_to_f32: {
+ expr: `all(vec4(1.0) == vec4f(1f))`,
+ valid: true,
+ },
+ vector_absfloat_to_f16: {
+ expr: `all(vec2(1.0) == vec2h(1h))`,
+ valid: true,
+ f16: true,
+ },
+ vector_swizzle_integer: {
+ expr: `vec2(1).x == 1i`,
+ valid: true,
+ },
+ vector_swizzle_float: {
+ expr: `vec2(1).y == 1f`,
+ valid: true,
+ },
+ vector_default_ctor_integer: {
+ expr: `all(vec3().xy == vec2i())`,
+ valid: true,
+ },
+ vector_default_ctor_abstract: {
+ expr: `all(vec3().xy == vec2())`,
+ valid: true,
+ },
+ vector_swizzle_abstract: {
+ expr: `vec4(1f).x == 1`,
+ valid: true,
+ },
+ vector_abstract_to_integer: {
+ expr: `all(vec4(1) == vec4i(1))`,
+ valid: true,
+ },
+ vector_wrong_result_i32: {
+ expr: `vec2(1,2f).x == 1i`,
+ valid: false,
+ },
+ vector_wrong_result_f32: {
+ expr: `vec2(1,2i).y == 2f`,
+ valid: false,
+ },
+ vector_wrong_result_splat: {
+ expr: `vec2(1.0).x == 1i`,
+ valid: false,
+ },
+ array_absint_to_bool: {
+ expr: `any(array(1)[0])`,
+ valid: false,
+ },
+ array_absint_to_u32: {
+ expr: `array(1)[0] == array<u32,1>(1u)[0]`,
+ valid: true,
+ },
+ array_absint_to_i32: {
+ expr: `array(1)[0] == array<i32,1>(1i)[0]`,
+ valid: true,
+ },
+ array_absint_to_f32: {
+ expr: `array(1)[0] == array<f32,1>(1f)[0]`,
+ valid: true,
+ },
+ array_absint_to_f16: {
+ expr: `array(1)[0] == array<f16,1>(1h)[0]`,
+ valid: true,
+ f16: true,
+ },
+ array_absfloat_to_bool: {
+ expr: `any(array(1.0)[0])`,
+ valid: false,
+ },
+ array_absfloat_to_u32: {
+ expr: `array(1.0)[0] == array<u32,1>(1u)[0]`,
+ valid: false,
+ },
+ array_absfloat_to_i32: {
+ expr: `array(1.0)[0] == array<i32,1>(1i)[0]`,
+ valid: false,
+ },
+ array_absfloat_to_f32: {
+ expr: `array(1.0)[0] == array<f32,1>(1f)[0]`,
+ valid: true,
+ },
+ array_absfloat_to_f16: {
+ expr: `array(1.0)[0] == array<f16,1>(1h)[0]`,
+ valid: true,
+ f16: true,
+ },
+ mat2x2_index_absint: {
+ expr: `all(mat2x2(1,2,3,4)[0] == vec2(1,2))`,
+ valid: true,
+ },
+ mat2x2_index_absfloat: {
+ expr: `all(mat2x2(1,2,3,4)[1] == vec2(3.0,4.0))`,
+ valid: true,
+ },
+ mat2x2_index_float: {
+ expr: `all(mat2x2(0,0,0,0)[1] == vec2f())`,
+ valid: true,
+ },
+ mat2x2_wrong_result: {
+ expr: `all(mat2x2(0f,0,0,0)[0] == vec2h())`,
+ valid: false,
+ f16: true,
+ },
+};
+
+g.test('implicit_conversions')
+ .desc('Test implicit conversions')
+ .params(u => u.combine('case', keysOf(kImplicitConversionCases)))
+ .beforeAllSubcases(t => {
+ if (kImplicitConversionCases[t.params.case].f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const testcase = kImplicitConversionCases[t.params.case];
+ const code = `${testcase.f16 ? 'enable f16;' : ''}
+ const_assert ${testcase.expr};`;
+ t.expectCompileResult(testcase.valid, code);
+ });
+
+const kTypes = objectsToRecord(kAllNumericScalarsAndVectors);
+const kTypeKeys = keysOf(kTypes);
+
+g.test('overload_resolution')
+ .desc('Test overload resolution')
+ .params(u =>
+ u
+ .combine('arg1', kTypeKeys)
+ .combine('arg2', kTypeKeys)
+ .beginSubcases()
+ .combine('op', ['min', 'max'] as const)
+ .filter(t => {
+ if (t.arg1 === t.arg2) {
+ return false;
+ }
+ const t1 = kTypes[t.arg1];
+ const t2 = kTypes[t.arg2];
+ const t1IsVector = t1 instanceof VectorType;
+ const t2IsVector = t2 instanceof VectorType;
+ if (t1IsVector !== t2IsVector) {
+ return false;
+ }
+ if (t1IsVector && t2IsVector && t1.size !== t2.size) {
+ return false;
+ }
+ return true;
+ })
+ )
+ .beforeAllSubcases(t => {
+ const t1 = kTypes[t.params.arg1];
+ const t2 = kTypes[t.params.arg2];
+ if (t1.requiresF16() || t2.requiresF16()) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const t1 = kTypes[t.params.arg1];
+ const t2 = kTypes[t.params.arg2];
+ const resTy = isConvertible(t1, t2) ? t2 : t1;
+ const enable = `${t1.requiresF16() || t2.requiresF16() ? 'enable f16;' : ''}`;
+ const min = 50;
+ const max = 100;
+ const res = t.params.op === 'min' ? min : max;
+ const v1 = t1.create(min).wgsl();
+ const v2 = t2.create(max).wgsl();
+ const resV = resTy.create(res).wgsl();
+ const expr = `${t.params.op}(${v1}, ${v2}) == ${resV}`;
+ const assertExpr = t1 instanceof VectorType ? `all(${expr})` : expr;
+ const code = `${enable}
+ const_assert ${assertExpr};`;
+ t.expectCompileResult(isConvertible(t1, t2) || isConvertible(t2, t1), code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/precedence.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/precedence.spec.ts
new file mode 100644
index 0000000000..15f7ad4a97
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/precedence.spec.ts
@@ -0,0 +1,188 @@
+export const description = `
+Validation tests for operator precedence.
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+// Bit set for the binary operator groups.
+const kMultiplicative = 1 << 0;
+const kAdditive = 1 << 1;
+const kShift = 1 << 2;
+const kRelational = 1 << 3;
+const kBinaryAnd = 1 << 4;
+const kBinaryXor = 1 << 5;
+const kBinaryOr = 1 << 6;
+const kLogical = 1 << 7;
+
+// Set of other operators that each operator can precede without any parentheses.
+const kCanPrecedeWithoutParens: Record<number, number> = {};
+kCanPrecedeWithoutParens[kMultiplicative] = kMultiplicative | kAdditive | kRelational;
+kCanPrecedeWithoutParens[kAdditive] = kMultiplicative | kAdditive | kRelational;
+kCanPrecedeWithoutParens[kShift] = kRelational | kLogical;
+kCanPrecedeWithoutParens[kRelational] = kMultiplicative | kAdditive | kShift | kLogical;
+kCanPrecedeWithoutParens[kBinaryAnd] = kBinaryAnd;
+kCanPrecedeWithoutParens[kBinaryXor] = kBinaryXor;
+kCanPrecedeWithoutParens[kBinaryOr] = kBinaryOr;
+kCanPrecedeWithoutParens[kLogical] = kRelational;
+
+// The list of binary operators.
+interface BinaryOperatorInfo {
+ op: string;
+ group: number;
+}
+const kBinaryOperators: Record<string, BinaryOperatorInfo> = {
+ mul: { op: '*', group: kMultiplicative },
+ div: { op: '/', group: kMultiplicative },
+ mod: { op: '%', group: kMultiplicative },
+
+ add: { op: '+', group: kAdditive },
+ sub: { op: '-', group: kAdditive },
+
+ shl: { op: '<<', group: kShift },
+ shr: { op: '>>', group: kShift },
+
+ lt: { op: '<', group: kRelational },
+ gt: { op: '>', group: kRelational },
+ le: { op: '<=', group: kRelational },
+ ge: { op: '>=', group: kRelational },
+ eq: { op: '==', group: kRelational },
+ ne: { op: '!=', group: kRelational },
+
+ bin_and: { op: '&', group: kBinaryAnd },
+ bin_xor: { op: '^', group: kBinaryXor },
+ bin_or: { op: '|', group: kBinaryOr },
+
+ log_and: { op: '&&', group: kLogical },
+ log_or: { op: '||', group: kLogical },
+};
+
+g.test('binary_requires_parentheses')
+ .desc(
+ `
+ Validates that certain binary operators require parentheses to bind correctly.
+ `
+ )
+ .params(u =>
+ u
+ .combine('op1', keysOf(kBinaryOperators))
+ .combine('op2', keysOf(kBinaryOperators))
+ .filter(p => {
+ // Skip expressions that would parse as template lists.
+ if (p.op1 === 'lt' && ['gt', 'ge', 'shr'].includes(p.op2)) {
+ return false;
+ }
+ // Only combine logical operators with relational operators.
+ if (kBinaryOperators[p.op1].group === kLogical) {
+ return kBinaryOperators[p.op2].group === kRelational;
+ }
+ if (kBinaryOperators[p.op2].group === kLogical) {
+ return kBinaryOperators[p.op1].group === kRelational;
+ }
+ return true;
+ })
+ )
+ .fn(t => {
+ const op1 = kBinaryOperators[t.params.op1];
+ const op2 = kBinaryOperators[t.params.op2];
+ const code = `
+var<private> a : ${op1.group === kLogical ? 'bool' : 'u32'};
+var<private> b : u32;
+var<private> c : ${op2.group === kLogical ? 'bool' : 'u32'};
+fn foo() {
+ let foo = a ${op1.op} b ${op2.op} c;
+}
+`;
+
+ const valid = (kCanPrecedeWithoutParens[op1.group] & op2.group) !== 0;
+ t.expectCompileResult(valid, code);
+ });
+
+g.test('mixed_logical_requires_parentheses')
+ .desc(
+ `
+ Validates that mixed logical operators require parentheses to bind correctly.
+ `
+ )
+ .params(u =>
+ u
+ .combine('op1', keysOf(kBinaryOperators))
+ .combine('op2', keysOf(kBinaryOperators))
+ .combine('parens', ['none', 'left', 'right'])
+ .filter(p => {
+ const group1 = kBinaryOperators[p.op1].group;
+ const group2 = kBinaryOperators[p.op2].group;
+ return group1 === kLogical && group2 === kLogical;
+ })
+ )
+ .fn(t => {
+ const op1 = kBinaryOperators[t.params.op1];
+ const op2 = kBinaryOperators[t.params.op2];
+ let expr = `a ${op1.op} b ${op2.op} c;`;
+ if (t.params.parens === 'left') {
+ expr = `(a ${op1.op} b) ${op2.op} c;`;
+ } else if (t.params.parens === 'right') {
+ expr = `a ${op1.op} (b ${op2.op} c);`;
+ }
+ const code = `
+var<private> a : bool;
+var<private> b : bool;
+var<private> c : bool;
+fn foo() {
+ let bar = ${expr};
+}
+`;
+ const valid = t.params.parens !== 'none' || t.params.op1 === t.params.op2;
+ t.expectCompileResult(valid, code);
+ });
+
+// The list of miscellaneous other test cases.
+interface Expression {
+ expr: string;
+ result: boolean;
+}
+const kExpressions: Record<string, Expression> = {
+ neg_member: { expr: '- str . a', result: true },
+ comp_member: { expr: '~ str . a', result: true },
+ addr_member: { expr: '& str . a', result: true },
+ log_and_member: { expr: 'false && str . b', result: true },
+ log_or_member: { expr: 'false || str . b', result: true },
+ and_addr: { expr: ' v & &str .a', result: false },
+ and_addr_paren: { expr: 'v & (&str).a', result: true },
+ deref_member: { expr: ' * ptr_str . a', result: false },
+ deref_member_paren: { expr: '(* ptr_str) . a', result: true },
+ deref_idx: { expr: ' * ptr_vec [0]', result: false },
+ deref_idx_paren: { expr: '(* ptr_vec) [1]', result: true },
+};
+
+g.test('other')
+ .desc(
+ `
+ Test that other operator precedence rules are correctly implemented.
+ `
+ )
+ .params(u => u.combine('expr', keysOf(kExpressions)))
+ .fn(t => {
+ const expr = kExpressions[t.params.expr];
+ const wgsl = `
+ struct S {
+ a: i32,
+ b: bool,
+ }
+
+ fn main() {
+ var v = 42;
+ var vec = vec4();
+ var str = S(42, false);
+ let ptr_vec = &vec;
+ let ptr_str = &str;
+
+ let foo = ${expr.expr};
+ }
+ `;
+
+ t.expectCompileResult(expr.result, wgsl);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/unary/address_of_and_indirection.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/unary/address_of_and_indirection.spec.ts
new file mode 100644
index 0000000000..7eca4286b9
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/unary/address_of_and_indirection.spec.ts
@@ -0,0 +1,243 @@
+export const description = `
+Validation tests for unary address-of and indirection (dereference)
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kAddressSpaces = ['function', 'private', 'workgroup', 'uniform', 'storage'];
+const kAccessModes = ['read', 'read_write'];
+const kStorageTypes = ['bool', 'u32', 'i32', 'f32', 'f16'];
+const kCompositeTypes = ['array', 'struct', 'vec', 'mat'];
+const kDerefTypes = {
+ deref_address_of_identifier: {
+ wgsl: `(*(&a))`,
+ requires_pointer_composite_access: false,
+ },
+ deref_pointer: {
+ wgsl: `(*p)`,
+ requires_pointer_composite_access: false,
+ },
+ address_of_identifier: {
+ wgsl: `(&a)`,
+ requires_pointer_composite_access: true,
+ },
+ pointer: {
+ wgsl: `p`,
+ requires_pointer_composite_access: true,
+ },
+};
+
+g.test('basic')
+ .desc(
+ `Validates address-of (&) every supported variable type, ensuring the type is correct by
+ assigning to an explicitly typed pointer. Also validates dereferencing the reference,
+ ensuring the type is correct by assigning to an explicitly typed variable.`
+ )
+ .params(u =>
+ u
+ .combine('addressSpace', kAddressSpaces)
+ .combine('accessMode', kAccessModes)
+ .combine('storageType', kStorageTypes)
+ .combine('derefType', keysOf(kDerefTypes))
+ .filter(t => {
+ if (t.storageType === 'bool') {
+ return t.addressSpace === 'function' || t.addressSpace === 'private';
+ }
+ return true;
+ })
+ .filter(t => {
+ // This test does not test composite access
+ return !kDerefTypes[t.derefType].requires_pointer_composite_access;
+ })
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.storageType === 'f16') {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+ }
+ })
+ .fn(t => {
+ const isLocal = t.params.addressSpace === 'function';
+ const deref = kDerefTypes[t.params.derefType];
+ // Only specify access mode for storage buffers
+ const commaAccessMode = t.params.addressSpace === 'storage' ? `, ${t.params.accessMode}` : '';
+
+ let varDecl = '';
+ if (t.params.addressSpace === 'uniform' || t.params.addressSpace === 'storage') {
+ varDecl += '@group(0) @binding(0) ';
+ }
+ varDecl += `var<${t.params.addressSpace}${commaAccessMode}> a : VarType;`;
+
+ const wgsl = `
+ ${t.params.storageType === 'f16' ? 'enable f16;' : ''}
+
+ alias VarType = ${t.params.storageType};
+ alias PtrType = ptr<${t.params.addressSpace}, VarType ${commaAccessMode}>;
+
+ ${isLocal ? '' : varDecl}
+
+ fn foo() {
+ ${isLocal ? varDecl : ''}
+ let p : PtrType = &a;
+ var deref : VarType = ${deref.wgsl};
+ }
+ `;
+
+ t.expectCompileResult(true, wgsl);
+ });
+
+g.test('composite')
+ .desc(
+ `Validates address-of (&) every supported variable type for composite types, ensuring the type
+ is correct by assigning to an explicitly typed pointer. Also validates dereferencing the
+ reference followed by member/index access, ensuring the type is correct by assigning to an
+ explicitly typed variable.`
+ )
+ .params(u =>
+ u
+ .combine('addressSpace', kAddressSpaces)
+ .combine('compositeType', kCompositeTypes)
+ .combine('storageType', kStorageTypes)
+ .beginSubcases()
+ .combine('derefType', keysOf(kDerefTypes))
+ .combine('accessMode', kAccessModes)
+ .filter(t => {
+ if (t.storageType === 'bool') {
+ return t.addressSpace === 'function' || t.addressSpace === 'private';
+ }
+ return true;
+ })
+ .filter(t => {
+ if (t.compositeType === 'mat') {
+ return t.storageType === 'f32' || t.storageType === 'f16';
+ }
+ return true;
+ })
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.storageType === 'f16') {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+ }
+ })
+ .fn(t => {
+ const isLocal = t.params.addressSpace === 'function';
+ const deref = kDerefTypes[t.params.derefType];
+ // Only specify access mode for storage buffers
+ const commaAccessMode = t.params.addressSpace === 'storage' ? `, ${t.params.accessMode}` : '';
+
+ let varDecl = '';
+ if (t.params.addressSpace === 'uniform' || t.params.addressSpace === 'storage') {
+ varDecl += '@group(0) @binding(0) ';
+ }
+ varDecl += `var<${t.params.addressSpace}${commaAccessMode}> a : VarType;`;
+
+ let wgsl = `
+ ${t.params.storageType === 'f16' ? 'enable f16;' : ''}`;
+
+ switch (t.params.compositeType) {
+ case 'array':
+ wgsl += `
+ struct S { @align(16) member : ${t.params.storageType} }
+ alias VarType = array<S, 10>;
+ alias PtrType = ptr<${t.params.addressSpace}, VarType ${commaAccessMode}>;
+ ${isLocal ? '' : varDecl}
+
+ fn foo() {
+ ${isLocal ? varDecl : ''}
+ let p : PtrType = &a;
+ var deref : ${t.params.storageType} = ${deref.wgsl}[0].member;
+ }`;
+ break;
+ case 'struct':
+ wgsl += `
+ struct S { member : ${t.params.storageType} }
+ alias VarType = S;
+ alias PtrType = ptr<${t.params.addressSpace}, VarType ${commaAccessMode}>;
+ ${isLocal ? '' : varDecl}
+
+ fn foo() {
+ ${isLocal ? varDecl : ''}
+ let p : PtrType = &a;
+ var deref : ${t.params.storageType} = ${deref.wgsl}.member;
+ }`;
+ break;
+ case 'vec':
+ wgsl += `
+ alias VarType = vec3<${t.params.storageType}>;
+ alias PtrType = ptr<${t.params.addressSpace}, VarType ${commaAccessMode}>;
+ ${isLocal ? '' : varDecl}
+
+ fn foo() {
+ ${isLocal ? varDecl : ''}
+ let p : PtrType = &a;
+ var deref_member : ${t.params.storageType} = ${deref.wgsl}.x;
+ var deref_index : ${t.params.storageType} = ${deref.wgsl}[0];
+ }`;
+ break;
+ case 'mat':
+ wgsl += `
+ alias VarType = mat2x3<${t.params.storageType}>;
+ alias PtrType = ptr<${t.params.addressSpace}, VarType ${commaAccessMode}>;
+ ${isLocal ? '' : varDecl}
+
+ fn foo() {
+ ${isLocal ? varDecl : ''}
+ let p : PtrType = &a;
+ var deref_vec : vec3<${t.params.storageType}> = ${deref.wgsl}[0];
+ var deref_elem : ${t.params.storageType} = ${deref.wgsl}[0][0];
+ }`;
+ break;
+ }
+
+ let shouldPass = true;
+ if (
+ kDerefTypes[t.params.derefType].requires_pointer_composite_access &&
+ !t.hasLanguageFeature('pointer_composite_access')
+ ) {
+ shouldPass = false;
+ }
+
+ t.expectCompileResult(shouldPass, wgsl);
+ });
+
+const kInvalidCases = {
+ address_of_let: `
+ let a = 1;
+ let p = &a;`,
+ address_of_texture: `
+ let p = &t;`,
+ address_of_sampler: `
+ let p = &s;`,
+ address_of_function: `
+ let p = &func;`,
+ address_of_vector_elem_via_member: `
+ var a : vec3<f32>();
+ let p = &a.x;`,
+ address_of_vector_elem_via_index: `
+ var a : vec3<f32>();
+ let p = &a[0];`,
+ address_of_matrix_elem: `
+ var a : mat2x3<f32>();
+ let p = &a[0][0];`,
+ deref_non_pointer: `
+ var a = 1;
+ let p = *a;
+ `,
+};
+g.test('invalid')
+ .desc('Test invalid cases of unary address-of and dereference')
+ .params(u => u.combine('case', keysOf(kInvalidCases)))
+ .fn(t => {
+ const wgsl = `
+ @group(0) @binding(0) var s : sampler;
+ @group(0) @binding(1) var t : texture_2d<f32>;
+ fn func() {}
+ fn main() {
+ ${kInvalidCases[t.params.case]}
+ }
+ `;
+ t.expectCompileResult(false, wgsl);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/unary/arithmetic_negation.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/unary/arithmetic_negation.spec.ts
new file mode 100644
index 0000000000..47a1a04990
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/unary/arithmetic_negation.spec.ts
@@ -0,0 +1,114 @@
+export const description = `
+Validation tests for arithmetic negation expressions.
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../common/util/data_tables.js';
+import { kAllScalarsAndVectors, scalarTypeOf, Type } from '../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+// A list of scalar and vector types.
+const kScalarAndVectorTypes = objectsToRecord(kAllScalarsAndVectors);
+
+g.test('scalar_vector')
+ .desc(
+ `
+ Validates that scalar and vector numeric negation expressions are accepted for numerical types that are signed.
+ `
+ )
+ .params(u => u.combine('type', keysOf(kScalarAndVectorTypes)).beginSubcases())
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kScalarAndVectorTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const type = kScalarAndVectorTypes[t.params.type];
+ const elementTy = scalarTypeOf(type);
+ const hasF16 = elementTy === Type.f16;
+ const code = `
+${hasF16 ? 'enable f16;' : ''}
+const rhs = ${type.create(0).wgsl()};
+const foo = -rhs;
+`;
+
+ t.expectCompileResult(elementTy.signed, code);
+ });
+
+interface InvalidTypeConfig {
+ // An expression that produces a value of the target type.
+ expr: string;
+ // A function that converts an expression of the target type into a valid negation operand.
+ control: (x: string) => string;
+}
+const kInvalidTypes: Record<string, InvalidTypeConfig> = {
+ mat2x2f: {
+ expr: 'm',
+ control: e => `${e}[0][0]`,
+ },
+
+ array: {
+ expr: 'arr',
+ control: e => `${e}[0]`,
+ },
+
+ ptr: {
+ expr: '(&b)',
+ control: e => `*${e}`,
+ },
+
+ atomic: {
+ expr: 'a',
+ control: e => `atomicLoad(&${e})`,
+ },
+
+ texture: {
+ expr: 't',
+ control: e => `textureLoad(${e}, vec2(), 0).x`,
+ },
+
+ sampler: {
+ expr: 's',
+ control: e => `textureSampleLevel(t, ${e}, vec2(), 0).x`,
+ },
+
+ struct: {
+ expr: 'str',
+ control: e => `${e}.b`,
+ },
+};
+
+g.test('invalid_types')
+ .desc(
+ `
+ Validates that arithmetic negation expressions are never accepted for non-scalar and non-vector types.
+ `
+ )
+ .params(u =>
+ u.combine('type', keysOf(kInvalidTypes)).combine('control', [true, false]).beginSubcases()
+ )
+ .fn(t => {
+ const type = kInvalidTypes[t.params.type];
+ const expr = t.params.control ? type.control(type.expr) : type.expr;
+ const code = `
+@group(0) @binding(0) var t : texture_2d<f32>;
+@group(0) @binding(1) var s : sampler;
+@group(0) @binding(2) var<storage, read_write> a : atomic<i32>;
+
+struct S { b : i32 }
+
+var<private> b : i32;
+var<private> m : mat2x2f;
+var<private> arr : array<i32, 4>;
+var<private> str : S;
+
+@compute @workgroup_size(1)
+fn main() {
+ let foo = -${expr};
+}
+`;
+
+ t.expectCompileResult(t.params.control, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/unary/bitwise_complement.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/unary/bitwise_complement.spec.ts
new file mode 100644
index 0000000000..b5b8556b9d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/unary/bitwise_complement.spec.ts
@@ -0,0 +1,114 @@
+export const description = `
+Validation tests for bitwise complement expressions.
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../common/util/data_tables.js';
+import { kAllScalarsAndVectors, scalarTypeOf, Type } from '../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+// A list of scalar and vector types.
+const kScalarAndVectorTypes = objectsToRecord(kAllScalarsAndVectors);
+
+g.test('scalar_vector')
+ .desc(
+ `
+ Validates that scalar and vector bitwise complement expressions are only accepted for integers.
+ `
+ )
+ .params(u => u.combine('type', keysOf(kScalarAndVectorTypes)).beginSubcases())
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kScalarAndVectorTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const type = kScalarAndVectorTypes[t.params.type];
+ const elementTy = scalarTypeOf(type);
+ const hasF16 = elementTy === Type.f16;
+ const code = `
+${hasF16 ? 'enable f16;' : ''}
+const rhs = ${type.create(0).wgsl()};
+const foo = ~rhs;
+`;
+
+ t.expectCompileResult([Type.abstractInt, Type.i32, Type.u32].includes(elementTy), code);
+ });
+
+interface InvalidTypeConfig {
+ // An expression that produces a value of the target type.
+ expr: string;
+ // A function that converts an expression of the target type into a valid complement operand.
+ control: (x: string) => string;
+}
+const kInvalidTypes: Record<string, InvalidTypeConfig> = {
+ mat2x2f: {
+ expr: 'm',
+ control: e => `i32(${e}[0][0])`,
+ },
+
+ array: {
+ expr: 'arr',
+ control: e => `${e}[0]`,
+ },
+
+ ptr: {
+ expr: '(&u)',
+ control: e => `*${e}`,
+ },
+
+ atomic: {
+ expr: 'a',
+ control: e => `atomicLoad(&${e})`,
+ },
+
+ texture: {
+ expr: 't',
+ control: e => `i32(textureLoad(${e}, vec2(), 0).x)`,
+ },
+
+ sampler: {
+ expr: 's',
+ control: e => `i32(textureSampleLevel(t, ${e}, vec2(), 0).x)`,
+ },
+
+ struct: {
+ expr: 'str',
+ control: e => `${e}.u`,
+ },
+};
+
+g.test('invalid_types')
+ .desc(
+ `
+ Validates that bitwise complement expressions are never accepted for non-scalar and non-vector types.
+ `
+ )
+ .params(u =>
+ u.combine('type', keysOf(kInvalidTypes)).combine('control', [true, false]).beginSubcases()
+ )
+ .fn(t => {
+ const type = kInvalidTypes[t.params.type];
+ const expr = t.params.control ? type.control(type.expr) : type.expr;
+ const code = `
+@group(0) @binding(0) var t : texture_2d<f32>;
+@group(0) @binding(1) var s : sampler;
+@group(0) @binding(2) var<storage, read_write> a : atomic<i32>;
+
+struct S { u : u32 }
+
+var<private> u : u32;
+var<private> m : mat2x2f;
+var<private> arr : array<u32, 4>;
+var<private> str : S;
+
+@compute @workgroup_size(1)
+fn main() {
+ let foo = ~${expr};
+}
+`;
+
+ t.expectCompileResult(t.params.control, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/unary/logical_negation.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/unary/logical_negation.spec.ts
new file mode 100644
index 0000000000..a85516ec3f
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/unary/logical_negation.spec.ts
@@ -0,0 +1,114 @@
+export const description = `
+Validation tests for logical negation expressions.
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../common/util/data_tables.js';
+import { kAllScalarsAndVectors, scalarTypeOf, Type } from '../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+// A list of scalar and vector types.
+const kScalarAndVectorTypes = objectsToRecord(kAllScalarsAndVectors);
+
+g.test('scalar_vector')
+ .desc(
+ `
+ Validates that scalar and vector logical negation expressions are only accepted for bool types.
+ `
+ )
+ .params(u => u.combine('type', keysOf(kScalarAndVectorTypes)).beginSubcases())
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kScalarAndVectorTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const type = kScalarAndVectorTypes[t.params.type];
+ const elementTy = scalarTypeOf(type);
+ const hasF16 = elementTy === Type.f16;
+ const code = `
+${hasF16 ? 'enable f16;' : ''}
+const rhs = ${type.create(0).wgsl()};
+const foo = !rhs;
+`;
+
+ t.expectCompileResult(elementTy === Type.bool, code);
+ });
+
+interface InvalidTypeConfig {
+ // An expression that produces a value of the target type.
+ expr: string;
+ // A function that converts an expression of the target type into a valid negation operand.
+ control: (x: string) => string;
+}
+const kInvalidTypes: Record<string, InvalidTypeConfig> = {
+ mat2x2f: {
+ expr: 'm',
+ control: e => `bool(${e}[0][0])`,
+ },
+
+ array: {
+ expr: 'arr',
+ control: e => `${e}[0]`,
+ },
+
+ ptr: {
+ expr: '(&b)',
+ control: e => `*${e}`,
+ },
+
+ atomic: {
+ expr: 'a',
+ control: e => `bool(atomicLoad(&${e}))`,
+ },
+
+ texture: {
+ expr: 't',
+ control: e => `bool(textureLoad(${e}, vec2(), 0).x)`,
+ },
+
+ sampler: {
+ expr: 's',
+ control: e => `bool(textureSampleLevel(t, ${e}, vec2(), 0).x)`,
+ },
+
+ struct: {
+ expr: 'str',
+ control: e => `${e}.b`,
+ },
+};
+
+g.test('invalid_types')
+ .desc(
+ `
+ Validates that logical negation expressions are never accepted for non-scalar and non-vector types.
+ `
+ )
+ .params(u =>
+ u.combine('type', keysOf(kInvalidTypes)).combine('control', [true, false]).beginSubcases()
+ )
+ .fn(t => {
+ const type = kInvalidTypes[t.params.type];
+ const expr = t.params.control ? type.control(type.expr) : type.expr;
+ const code = `
+@group(0) @binding(0) var t : texture_2d<f32>;
+@group(0) @binding(1) var s : sampler;
+@group(0) @binding(2) var<storage, read_write> a : atomic<i32>;
+
+struct S { b : bool }
+
+var<private> b : bool;
+var<private> m : mat2x2f;
+var<private> arr : array<bool, 4>;
+var<private> str : S;
+
+@compute @workgroup_size(1)
+fn main() {
+ let foo = !${expr};
+}
+`;
+
+ t.expectCompileResult(t.params.control, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/extension/pointer_composite_access.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/extension/pointer_composite_access.spec.ts
new file mode 100644
index 0000000000..6940dc6469
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/extension/pointer_composite_access.spec.ts
@@ -0,0 +1,130 @@
+export const description = `
+Validation tests for pointer_composite_access extension
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+function makeSource(module: string, init_expr: string, pointer_read_expr: string) {
+ return `
+ ${module}
+ fn f() {
+ var a = ${init_expr};
+ let p = &a;
+ let r = ${pointer_read_expr};
+ }`;
+}
+
+const kCases = {
+ // Via identifier 'a'
+ array_index_access_via_identifier: {
+ module: '',
+ init_expr: 'array<i32, 3>()',
+ via_deref: '(*(&a))[0]',
+ via_pointer: '(&a)[0]',
+ },
+ vector_index_access_via_identifier: {
+ module: '',
+ init_expr: 'vec3<i32>()',
+ via_deref: '(*(&a))[0]',
+ via_pointer: '(&a)[0]',
+ },
+ vector_member_access_via_identifier: {
+ module: '',
+ init_expr: 'vec3<i32>()',
+ via_deref: '(*(&a)).x',
+ via_pointer: '(&a).x',
+ },
+ matrix_index_access_via_identifier: {
+ module: '',
+ init_expr: 'mat2x3<f32>()',
+ via_deref: '(*(&a))[0]',
+ via_pointer: '(&a)[0]',
+ },
+ struct_member_access_via_identifier: {
+ module: 'struct S { a : i32, }',
+ init_expr: 'S()',
+ via_deref: '(*(&a)).a',
+ via_pointer: '(&a).a',
+ },
+ builtin_struct_modf_via_identifier: {
+ module: '',
+ init_expr: 'modf(1.5)',
+ via_deref: 'vec2((*(&a)).fract, (*(&a)).whole)',
+ via_pointer: 'vec2((&a).fract, (&a).whole)',
+ },
+ builtin_struct_frexp_via_identifier: {
+ module: '',
+ init_expr: 'frexp(1.5)',
+ via_deref: 'vec2((*(&a)).fract, f32((*(&a)).exp))',
+ via_pointer: 'vec2((&a).fract, f32((&a).exp))',
+ },
+
+ // Via pointer 'p'
+ array_index_access_via_pointer: {
+ module: '',
+ init_expr: 'array<i32, 3>()',
+ via_deref: '(*p)[0]',
+ via_pointer: 'p[0]',
+ },
+ vector_index_access_via_pointer: {
+ module: '',
+ init_expr: 'vec3<i32>()',
+ via_deref: '(*p)[0]',
+ via_pointer: 'p[0]',
+ },
+ vector_member_access_via_pointer: {
+ module: '',
+ init_expr: 'vec3<i32>()',
+ via_deref: '(*p).x',
+ via_pointer: 'p.x',
+ },
+ matrix_index_access_via_pointer: {
+ module: '',
+ init_expr: 'mat2x3<f32>()',
+ via_deref: '(*p)[0]',
+ via_pointer: 'p[0]',
+ },
+ struct_member_access_via_pointer: {
+ module: 'struct S { a : i32, }',
+ init_expr: 'S()',
+ via_deref: '(*p).a',
+ via_pointer: 'p.a',
+ },
+ builtin_struct_modf_via_pointer: {
+ module: '',
+ init_expr: 'modf(1.5)',
+ via_deref: 'vec2((*p).fract, (*p).whole)',
+ via_pointer: 'vec2(p.fract, p.whole)',
+ },
+ builtin_struct_frexp_via_pointer: {
+ module: '',
+ init_expr: 'frexp(1.5)',
+ via_deref: 'vec2((*p).fract, f32((*p).exp))',
+ via_pointer: 'vec2(p.fract, f32(p.exp))',
+ },
+};
+
+g.test('deref')
+ .desc('Baseline test: pointer deref is always valid')
+ .params(u => u.combine('case', keysOf(kCases)))
+ .fn(t => {
+ const curr = kCases[t.params.case];
+ const source = makeSource(curr.module, curr.init_expr, curr.via_deref);
+ t.expectCompileResult(true, source);
+ });
+
+g.test('pointer')
+ .desc(
+ 'Tests that direct pointer access is valid if pointer_composite_access is supported, else it should fail'
+ )
+ .params(u => u.combine('case', keysOf(kCases)))
+ .fn(t => {
+ const curr = kCases[t.params.case];
+ const source = makeSource(curr.module, curr.init_expr, curr.via_pointer);
+ const should_pass = t.hasLanguageFeature('pointer_composite_access');
+ t.expectCompileResult(should_pass, source);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/extension/readonly_and_readwrite_storage_textures.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/extension/readonly_and_readwrite_storage_textures.spec.ts
new file mode 100644
index 0000000000..c74694d4b5
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/extension/readonly_and_readwrite_storage_textures.spec.ts
@@ -0,0 +1,48 @@
+export const description = `
+Validation tests for the readonly_and_readwrite_storage_textures language feature
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { TexelFormats } from '../../types.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kFeatureName = 'readonly_and_readwrite_storage_textures';
+
+g.test('var_decl')
+ .desc(
+ `Checks that the read and read_write access modes are only allowed with the language feature present`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('type', [
+ 'texture_storage_1d',
+ 'texture_storage_2d',
+ 'texture_storage_2d_array',
+ 'texture_storage_3d',
+ ])
+ .combine('format', TexelFormats)
+ .combine('access', ['read', 'write', 'read_write'])
+ )
+ .fn(t => {
+ const { type, format, access } = t.params;
+ const source = `@group(0) @binding(0) var t : ${type}<${format.format}, ${access}>;`;
+ const requiresFeature = access !== 'write';
+ t.expectCompileResult(t.hasLanguageFeature(kFeatureName) || !requiresFeature, source);
+ });
+
+g.test('textureBarrier')
+ .desc(
+ `Checks that the textureBarrier() builtin is only allowed with the language feature present`
+ )
+ .fn(t => {
+ t.expectCompileResult(
+ t.hasLanguageFeature(kFeatureName),
+ `
+ @workgroup_size(1) @compute fn main() {
+ textureBarrier();
+ }
+ `
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/functions/alias_analysis.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/functions/alias_analysis.spec.ts
index ba39485449..7efad7e798 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/functions/alias_analysis.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/functions/alias_analysis.spec.ts
@@ -38,50 +38,284 @@ function shouldPass(aliased: boolean, ...uses: UseName[]): boolean {
return !aliased || !uses.some(u => kUses[u].is_write) || uses.includes('no_access');
}
+type AddressSpace = 'private' | 'function' | 'storage' | 'uniform' | 'workgroup';
+
+const kWritableAddressSpaces = ['private', 'function', 'storage', 'workgroup'] as const;
+
+function ptr(addressSpace: AddressSpace, type: string) {
+ switch (addressSpace) {
+ case 'function':
+ return `ptr<function, ${type}>`;
+ case 'private':
+ return `ptr<private, ${type}>`;
+ case 'storage':
+ return `ptr<storage, ${type}, read_write>`;
+ case 'uniform':
+ return `ptr<uniform, ${type}>`;
+ case 'workgroup':
+ return `ptr<workgroup, ${type}>`;
+ }
+}
+
+function declareModuleScopeVar(
+ name: string,
+ addressSpace: 'private' | 'storage' | 'uniform' | 'workgroup',
+ type: string
+) {
+ const binding = name === 'x' ? 0 : 1;
+ switch (addressSpace) {
+ case 'private':
+ return `var<private> ${name} : ${type};`;
+ case 'storage':
+ return `@binding(${binding}) @group(0) var<storage, read_write> ${name} : ${type};`;
+ case 'uniform':
+ return `@binding(${binding}) @group(0) var<uniform> ${name} : ${type};`;
+ case 'workgroup':
+ return `var<workgroup> ${name} : ${type};`;
+ }
+}
+
+function maybeDeclareModuleScopeVar(name: string, addressSpace: AddressSpace, type: string) {
+ if (addressSpace === 'function') {
+ return '';
+ }
+ return declareModuleScopeVar(name, addressSpace, type);
+}
+
+function maybeDeclareFunctionScopeVar(name: string, addressSpace: AddressSpace, type: string) {
+ switch (addressSpace) {
+ case 'function':
+ return `var ${name} : ${type};`;
+ default:
+ return ``;
+ }
+}
+
+/**
+ * @returns true if a pointer of the given address space requires the
+ * 'unrestricted_pointer_parameters' language feature.
+ */
+function requiresUnrestrictedPointerParameters(addressSpace: AddressSpace) {
+ return addressSpace !== 'function' && addressSpace !== 'private';
+}
+
g.test('two_pointers')
.desc(`Test aliasing of two pointers passed to a function.`)
.params(u =>
u
- .combine('address_space', ['private', 'function'] as const)
+ .combine('address_space', kWritableAddressSpaces)
+ .combine('aliased', [true, false])
+ .beginSubcases()
.combine('a_use', keysOf(kUses))
.combine('b_use', keysOf(kUses))
+ )
+ .fn(t => {
+ if (requiresUnrestrictedPointerParameters(t.params.address_space)) {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+ }
+
+ const code = `
+${maybeDeclareModuleScopeVar('x', t.params.address_space, 'i32')}
+${maybeDeclareModuleScopeVar('y', t.params.address_space, 'i32')}
+
+fn callee(pa : ${ptr(t.params.address_space, 'i32')},
+ pb : ${ptr(t.params.address_space, 'i32')}) -> i32 {
+ ${kUses[t.params.a_use].gen(`*pa`)}
+ ${kUses[t.params.b_use].gen(`*pb`)}
+ return 0;
+}
+
+fn caller() {
+ ${maybeDeclareFunctionScopeVar('x', t.params.address_space, 'i32')}
+ ${maybeDeclareFunctionScopeVar('y', t.params.address_space, 'i32')}
+ callee(&x, ${t.params.aliased ? `&x` : `&y`});
+}
+`;
+ t.expectCompileResult(shouldPass(t.params.aliased, t.params.a_use, t.params.b_use), code);
+ });
+
+g.test('two_pointers_to_array_elements')
+ .desc(`Test aliasing of two array element pointers passed to a function.`)
+ .params(u =>
+ u
+ .combine('address_space', kWritableAddressSpaces)
+ .combine('index', [0, 1])
.combine('aliased', [true, false])
.beginSubcases()
+ .combine('a_use', keysOf(kUses))
+ .combine('b_use', keysOf(kUses))
)
.fn(t => {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+
const code = `
-${t.params.address_space === 'private' ? `var<private> x : i32; var<private> y : i32;` : ``}
+${maybeDeclareModuleScopeVar('x', t.params.address_space, 'array<i32, 4>')}
+${maybeDeclareModuleScopeVar('y', t.params.address_space, 'array<i32, 4>')}
-fn callee(pa : ptr<${t.params.address_space}, i32>,
- pb : ptr<${t.params.address_space}, i32>) -> i32 {
+fn callee(pa : ${ptr(t.params.address_space, 'i32')},
+ pb : ${ptr(t.params.address_space, 'i32')}) -> i32 {
${kUses[t.params.a_use].gen(`*pa`)}
${kUses[t.params.b_use].gen(`*pb`)}
return 0;
}
fn caller() {
- ${t.params.address_space === 'function' ? `var x : i32; var y : i32;` : ``}
- callee(&x, ${t.params.aliased ? `&x` : `&y`});
+ ${maybeDeclareFunctionScopeVar('x', t.params.address_space, 'array<i32, 4>')}
+ ${maybeDeclareFunctionScopeVar('y', t.params.address_space, 'array<i32, 4>')}
+ callee(&x[${t.params.index}], ${t.params.aliased ? `&x[0]` : `&y[0]`});
}
`;
t.expectCompileResult(shouldPass(t.params.aliased, t.params.a_use, t.params.b_use), code);
});
-g.test('one_pointer_one_module_scope')
- .desc(`Test aliasing of a pointer with a direct access to a module-scope variable.`)
+g.test('two_pointers_to_array_elements_indirect')
+ .desc(
+ `Test aliasing of two array pointers passed to a function, which indexes those arrays and then
+passes the element pointers to another function.`
+ )
.params(u =>
u
+ .combine('address_space', kWritableAddressSpaces)
+ .combine('index', [0, 1])
+ .combine('aliased', [true, false])
+ .beginSubcases()
.combine('a_use', keysOf(kUses))
.combine('b_use', keysOf(kUses))
+ )
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+
+ const code = `
+${maybeDeclareModuleScopeVar('x', t.params.address_space, 'array<i32, 4>')}
+${maybeDeclareModuleScopeVar('y', t.params.address_space, 'array<i32, 4>')}
+
+fn callee(pa : ${ptr(t.params.address_space, 'i32')},
+ pb : ${ptr(t.params.address_space, 'i32')}) -> i32 {
+ ${kUses[t.params.a_use].gen(`*pa`)}
+ ${kUses[t.params.b_use].gen(`*pb`)}
+ return 0;
+}
+
+fn index(pa : ${ptr(t.params.address_space, 'array<i32, 4>')},
+ pb : ${ptr(t.params.address_space, 'array<i32, 4>')}) -> i32 {
+ return callee(&(*pa)[${t.params.index}], &(*pb)[0]);
+}
+
+fn caller() {
+ ${maybeDeclareFunctionScopeVar('x', t.params.address_space, 'array<i32, 4>')}
+ ${maybeDeclareFunctionScopeVar('y', t.params.address_space, 'array<i32, 4>')}
+ index(&x, ${t.params.aliased ? `&x` : `&y`});
+}
+`;
+ t.expectCompileResult(shouldPass(t.params.aliased, t.params.a_use, t.params.b_use), code);
+ });
+
+g.test('two_pointers_to_struct_members')
+ .desc(`Test aliasing of two struct member pointers passed to a function.`)
+ .params(u =>
+ u
+ .combine('address_space', kWritableAddressSpaces)
+ .combine('member', ['a', 'b'])
+ .combine('aliased', [true, false])
+ .beginSubcases()
+ .combine('a_use', keysOf(kUses))
+ .combine('b_use', keysOf(kUses))
+ )
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+
+ const code = `
+struct S {
+ a : i32,
+ b : i32,
+}
+
+${maybeDeclareModuleScopeVar('x', t.params.address_space, 'S')}
+${maybeDeclareModuleScopeVar('y', t.params.address_space, 'S')}
+
+fn callee(pa : ${ptr(t.params.address_space, 'i32')},
+ pb : ${ptr(t.params.address_space, 'i32')}) -> i32 {
+ ${kUses[t.params.a_use].gen(`*pa`)}
+ ${kUses[t.params.b_use].gen(`*pb`)}
+ return 0;
+}
+
+fn caller() {
+ ${maybeDeclareFunctionScopeVar('x', t.params.address_space, 'S')}
+ ${maybeDeclareFunctionScopeVar('y', t.params.address_space, 'S')}
+ callee(&x.${t.params.member}, ${t.params.aliased ? `&x.a` : `&y.a`});
+}
+`;
+ t.expectCompileResult(shouldPass(t.params.aliased, t.params.a_use, t.params.b_use), code);
+ });
+
+g.test('two_pointers_to_struct_members_indirect')
+ .desc(
+ `Test aliasing of two structure pointers passed to a function, which accesses members of those
+structures and then passes the member pointers to another function.`
+ )
+ .params(u =>
+ u
+ .combine('address_space', kWritableAddressSpaces)
+ .combine('member', ['a', 'b'])
+ .combine('aliased', [true, false])
+ .beginSubcases()
+ .combine('a_use', keysOf(kUses))
+ .combine('b_use', keysOf(kUses))
+ )
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+
+ const code = `
+struct S {
+ a : i32,
+ b : i32,
+}
+
+${maybeDeclareModuleScopeVar('x', t.params.address_space, 'S')}
+${maybeDeclareModuleScopeVar('y', t.params.address_space, 'S')}
+
+fn callee(pa : ${ptr(t.params.address_space, 'i32')},
+ pb : ${ptr(t.params.address_space, 'i32')}) -> i32 {
+ ${kUses[t.params.a_use].gen(`*pa`)}
+ ${kUses[t.params.b_use].gen(`*pb`)}
+ return 0;
+}
+
+fn access(pa : ${ptr(t.params.address_space, 'S')},
+ pb : ${ptr(t.params.address_space, 'S')}) -> i32 {
+ return callee(&(*pa).${t.params.member}, &(*pb).a);
+}
+
+fn caller() {
+ ${maybeDeclareFunctionScopeVar('x', t.params.address_space, 'S')}
+ ${maybeDeclareFunctionScopeVar('y', t.params.address_space, 'S')}
+ access(&x, ${t.params.aliased ? `&x` : `&y`});
+}
+`;
+ t.expectCompileResult(shouldPass(t.params.aliased, t.params.a_use, t.params.b_use), code);
+ });
+
+g.test('one_pointer_one_module_scope')
+ .desc(`Test aliasing of a pointer with a direct access to a module-scope variable.`)
+ .params(u =>
+ u
+ .combine('address_space', ['private', 'storage', 'workgroup'] as const)
.combine('aliased', [true, false])
.beginSubcases()
+ .combine('a_use', keysOf(kUses))
+ .combine('b_use', keysOf(kUses))
)
.fn(t => {
+ if (requiresUnrestrictedPointerParameters(t.params.address_space)) {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+ }
+
const code = `
-var<private> x : i32;
-var<private> y : i32;
+${declareModuleScopeVar('x', t.params.address_space, 'i32')}
+${declareModuleScopeVar('y', t.params.address_space, 'i32')}
-fn callee(pb : ptr<private, i32>) -> i32 {
+fn callee(pb : ${ptr(t.params.address_space, 'i32')}) -> i32 {
${kUses[t.params.a_use].gen(`x`)}
${kUses[t.params.b_use].gen(`*pb`)}
return 0;
@@ -98,29 +332,34 @@ g.test('subcalls')
.desc(`Test aliasing of two pointers passed to a function, and then passed to other functions.`)
.params(u =>
u
- .combine('a_use', ['no_access', 'assign', 'binary_lhs'] as UseName[])
- .combine('b_use', ['no_access', 'assign', 'binary_lhs'] as UseName[])
+ .combine('address_space', ['private', 'storage', 'workgroup'] as const)
.combine('aliased', [true, false])
.beginSubcases()
+ .combine('a_use', ['no_access', 'assign', 'binary_lhs'] as UseName[])
+ .combine('b_use', ['no_access', 'assign', 'binary_lhs'] as UseName[])
)
.fn(t => {
+ if (requiresUnrestrictedPointerParameters(t.params.address_space)) {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+ }
+ const ptr_i32 = ptr(t.params.address_space, 'i32');
const code = `
-var<private> x : i32;
-var<private> y : i32;
+${declareModuleScopeVar('x', t.params.address_space, 'i32')}
+${declareModuleScopeVar('y', t.params.address_space, 'i32')}
-fn subcall_no_access(p : ptr<private, i32>) {
+fn subcall_no_access(p : ${ptr_i32}) {
let pp = &*p;
}
-fn subcall_binary_lhs(p : ptr<private, i32>) -> i32 {
+fn subcall_binary_lhs(p : ${ptr_i32}) -> i32 {
return *p + 1;
}
-fn subcall_assign(p : ptr<private, i32>) {
+fn subcall_assign(p : ${ptr_i32}) {
*p = 42;
}
-fn callee(pa : ptr<private, i32>, pb : ptr<private, i32>) -> i32 {
+fn callee(pa : ${ptr_i32}, pb : ${ptr_i32}) -> i32 {
let new_pa = &*pa;
let new_pb = &*pb;
subcall_${t.params.a_use}(new_pa);
@@ -139,20 +378,25 @@ g.test('member_accessors')
.desc(`Test aliasing of two pointers passed to a function and used with member accessors.`)
.params(u =>
u
- .combine('a_use', ['no_access', 'assign', 'binary_lhs'] as UseName[])
- .combine('b_use', ['no_access', 'assign', 'binary_lhs'] as UseName[])
+ .combine('address_space', ['private', 'storage', 'workgroup'] as const)
.combine('aliased', [true, false])
.beginSubcases()
+ .combine('a_use', ['no_access', 'assign', 'binary_lhs'] as UseName[])
+ .combine('b_use', ['no_access', 'assign', 'binary_lhs'] as UseName[])
)
.fn(t => {
+ if (requiresUnrestrictedPointerParameters(t.params.address_space)) {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+ }
+
+ const ptr_S = ptr(t.params.address_space, 'S');
const code = `
struct S { a : i32 }
-var<private> x : S;
-var<private> y : S;
+${declareModuleScopeVar('x', t.params.address_space, 'S')}
+${declareModuleScopeVar('y', t.params.address_space, 'S')}
-fn callee(pa : ptr<private, S>,
- pb : ptr<private, S>) -> i32 {
+fn callee(pa : ${ptr_S}, pb : ${ptr_S}) -> i32 {
${kUses[t.params.a_use].gen(`(*pa).a`)}
${kUses[t.params.b_use].gen(`(*pb).a`)}
return 0;
@@ -167,12 +411,18 @@ fn caller() {
g.test('same_pointer_read_and_write')
.desc(`Test that we can read from and write to the same pointer.`)
- .params(u => u.beginSubcases())
+ .params(u =>
+ u.combine('address_space', ['private', 'storage', 'workgroup'] as const).beginSubcases()
+ )
.fn(t => {
+ if (requiresUnrestrictedPointerParameters(t.params.address_space)) {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+ }
+
const code = `
-var<private> v : i32;
+${declareModuleScopeVar('v', t.params.address_space, 'i32')}
-fn callee(p : ptr<private, i32>) {
+fn callee(p : ${ptr(t.params.address_space, 'i32')}) {
*p = *p + 1;
}
@@ -185,10 +435,12 @@ fn caller() {
g.test('aliasing_inside_function')
.desc(`Test that we can alias pointers inside a function.`)
- .params(u => u.beginSubcases())
+ .params(u =>
+ u.combine('address_space', ['private', 'storage', 'workgroup'] as const).beginSubcases()
+ )
.fn(t => {
const code = `
-var<private> v : i32;
+${declareModuleScopeVar('v', t.params.address_space, 'i32')}
fn foo() {
var v : i32;
@@ -200,3 +452,236 @@ fn foo() {
`;
t.expectCompileResult(true, code);
});
+
+const kAtomicBuiltins = [
+ 'atomicLoad',
+ 'atomicStore',
+ 'atomicAdd',
+ 'atomicSub',
+ 'atomicMax',
+ 'atomicMin',
+ 'atomicAnd',
+ 'atomicOr',
+ 'atomicXor',
+ 'atomicExchange',
+ 'atomicCompareExchangeWeak',
+] as const;
+
+type AtomicBuiltins = (typeof kAtomicBuiltins)[number];
+
+function isWrite(builtin: AtomicBuiltins) {
+ switch (builtin) {
+ case 'atomicLoad':
+ return false;
+ case 'atomicAdd':
+ case 'atomicSub':
+ case 'atomicMax':
+ case 'atomicMin':
+ case 'atomicAnd':
+ case 'atomicOr':
+ case 'atomicXor':
+ case 'atomicExchange':
+ case 'atomicCompareExchangeWeak':
+ case 'atomicStore':
+ return true;
+ }
+}
+
+function callAtomicBuiltin(builtin: AtomicBuiltins, ptr: string) {
+ switch (builtin) {
+ case 'atomicLoad':
+ return `i += ${builtin}(${ptr})`;
+ case 'atomicStore':
+ return `${builtin}(${ptr}, 42)`;
+ case 'atomicAdd':
+ case 'atomicSub':
+ case 'atomicMax':
+ case 'atomicMin':
+ case 'atomicAnd':
+ case 'atomicOr':
+ case 'atomicXor':
+ case 'atomicExchange':
+ return `i += ${builtin}(${ptr}, 42)`;
+ case 'atomicCompareExchangeWeak':
+ return `${builtin}(${ptr}, 10, 42)`;
+ }
+}
+
+g.test('two_atomic_pointers')
+ .desc(`Test aliasing of two atomic pointers passed to a function.`)
+ .params(u =>
+ u
+ .combine('builtin_a', kAtomicBuiltins)
+ .combine('builtin_b', ['atomicLoad', 'atomicStore'] as const)
+ .combine('address_space', ['storage', 'workgroup'] as const)
+ .combine('aliased', [true, false])
+ .beginSubcases()
+ )
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+
+ const ptr_atomic_i32 = ptr(t.params.address_space, 'atomic<i32>');
+ const code = `
+${declareModuleScopeVar('x', t.params.address_space, 'atomic<i32>')}
+${declareModuleScopeVar('y', t.params.address_space, 'atomic<i32>')}
+
+fn callee(pa : ${ptr_atomic_i32}, pb : ${ptr_atomic_i32}) {
+ var i : i32;
+ ${callAtomicBuiltin(t.params.builtin_a, 'pa')};
+ ${callAtomicBuiltin(t.params.builtin_b, 'pb')};
+}
+
+fn caller() {
+ callee(&x, &${t.params.aliased ? 'x' : 'y'});
+}
+`;
+ const shouldFail =
+ t.params.aliased && (isWrite(t.params.builtin_a) || isWrite(t.params.builtin_b));
+ t.expectCompileResult(!shouldFail, code);
+ });
+
+g.test('two_atomic_pointers_to_array_elements')
+ .desc(`Test aliasing of two atomic array element pointers passed to a function.`)
+ .params(u =>
+ u
+ .combine('builtin_a', kAtomicBuiltins)
+ .combine('builtin_b', ['atomicLoad', 'atomicStore'] as const)
+ .combine('address_space', ['storage', 'workgroup'] as const)
+ .combine('index', [0, 1])
+ .combine('aliased', [true, false])
+ .beginSubcases()
+ )
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+
+ const ptr_atomic_i32 = ptr(t.params.address_space, 'atomic<i32>');
+ const code = `
+${declareModuleScopeVar('x', t.params.address_space, 'array<atomic<i32>, 32>')}
+${declareModuleScopeVar('y', t.params.address_space, 'array<atomic<i32>, 32>')}
+
+fn callee(pa : ${ptr_atomic_i32}, pb : ${ptr_atomic_i32}) {
+ var i : i32;
+ ${callAtomicBuiltin(t.params.builtin_a, 'pa')};
+ ${callAtomicBuiltin(t.params.builtin_b, 'pb')};
+}
+
+fn caller() {
+ callee(&x[${t.params.index}], &${t.params.aliased ? 'x' : 'y'}[0]);
+}
+`;
+ const shouldFail =
+ t.params.aliased && (isWrite(t.params.builtin_a) || isWrite(t.params.builtin_b));
+ t.expectCompileResult(!shouldFail, code);
+ });
+
+g.test('two_atomic_pointers_to_struct_members')
+ .desc(`Test aliasing of two struct member atomic pointers passed to a function.`)
+ .params(u =>
+ u
+ .combine('builtin_a', kAtomicBuiltins)
+ .combine('builtin_b', ['atomicLoad', 'atomicStore'] as const)
+ .combine('address_space', ['storage', 'workgroup'] as const)
+ .combine('member', ['a', 'b'])
+ .combine('aliased', [true, false])
+ .beginSubcases()
+ )
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+
+ const ptr_atomic_i32 = ptr(t.params.address_space, 'atomic<i32>');
+ const code = `
+struct S {
+ a : atomic<i32>,
+ b : atomic<i32>,
+}
+
+${declareModuleScopeVar('x', t.params.address_space, 'S')}
+${declareModuleScopeVar('y', t.params.address_space, 'S')}
+
+fn callee(pa : ${ptr_atomic_i32}, pb : ${ptr_atomic_i32}) {
+ var i : i32;
+ ${callAtomicBuiltin(t.params.builtin_a, 'pa')};
+ ${callAtomicBuiltin(t.params.builtin_b, 'pb')};
+}
+
+fn caller() {
+ callee(&x.${t.params.member}, &${t.params.aliased ? 'x' : 'y'}.a);
+}
+`;
+ const shouldFail =
+ t.params.aliased && (isWrite(t.params.builtin_a) || isWrite(t.params.builtin_b));
+ t.expectCompileResult(!shouldFail, code);
+ });
+
+g.test('one_atomic_pointer_one_module_scope')
+ .desc(`Test aliasing of an atomic pointer with a direct access to a module-scope variable.`)
+ .params(u =>
+ u
+ .combine('builtin_a', kAtomicBuiltins)
+ .combine('builtin_b', ['atomicLoad', 'atomicStore'] as const)
+ .combine('address_space', ['storage', 'workgroup'] as const)
+ .combine('aliased', [true, false])
+ .beginSubcases()
+ )
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+
+ const ptr_atomic_i32 = ptr(t.params.address_space, 'atomic<i32>');
+ const code = `
+${declareModuleScopeVar('x', t.params.address_space, 'atomic<i32>')}
+${declareModuleScopeVar('y', t.params.address_space, 'atomic<i32>')}
+
+fn callee(p : ${ptr_atomic_i32}) {
+ var i : i32;
+ ${callAtomicBuiltin(t.params.builtin_a, 'p')};
+ ${callAtomicBuiltin(t.params.builtin_b, t.params.aliased ? '&x' : '&y')};
+}
+
+fn caller() {
+ callee(&x);
+}
+`;
+ const shouldFail =
+ t.params.aliased && (isWrite(t.params.builtin_a) || isWrite(t.params.builtin_b));
+ t.expectCompileResult(!shouldFail, code);
+ });
+
+g.test('workgroup_uniform_load')
+ .desc(`Test aliasing via workgroupUniformLoad.`)
+ .params(u =>
+ u
+ .combine('use', ['load', 'store', 'workgroupUniformLoad'] as const)
+ .combine('aliased', [true, false])
+ .beginSubcases()
+ )
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+
+ function emitUse() {
+ switch (t.params.use) {
+ case 'load':
+ return `v = *pa`;
+ case 'store':
+ return `*pa = 1`;
+ case 'workgroupUniformLoad':
+ return `v = workgroupUniformLoad(pa)`;
+ }
+ }
+
+ const code = `
+var<workgroup> x : i32;
+var<workgroup> y : i32;
+
+fn callee(pa : ptr<workgroup, i32>, pb : ptr<workgroup, i32>) -> i32 {
+ var v : i32;
+ ${emitUse()};
+ return v + workgroupUniformLoad(pb);
+}
+
+fn caller() {
+ callee(&x, &${t.params.aliased ? 'x' : 'y'});
+}
+`;
+ const shouldFail = t.params.aliased && t.params.use === 'store';
+ t.expectCompileResult(!shouldFail, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/functions/restrictions.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/functions/restrictions.spec.ts
index b6affd14d6..7c79e6c5ea 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/functions/restrictions.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/functions/restrictions.spec.ts
@@ -12,6 +12,30 @@ interface VertexPosCase {
valid: boolean;
}
+const kCCommonTypeDecls = `
+struct runtime_array_struct {
+ arr : array<u32>
+}
+
+struct constructible {
+ a : i32,
+ b : u32,
+ c : f32,
+ d : bool,
+}
+
+struct host_shareable {
+ a : i32,
+ b : u32,
+ c : f32,
+}
+
+struct struct_with_array {
+ a : array<constructible, 4>
+}
+
+`;
+
const kVertexPosCases: Record<string, VertexPosCase> = {
bare_position: { name: `@builtin(position) vec4f`, value: `vec4f()`, valid: true },
nested_position: { name: `pos_struct`, value: `pos_struct()`, valid: true },
@@ -145,20 +169,7 @@ g.test('function_return_types')
const code = `
${enable}
-struct runtime_array_struct {
- arr : array<u32>
-}
-
-struct constructible {
- a : i32,
- b : u32,
- c : f32,
- d : bool,
-}
-
-struct struct_with_array {
- a : array<constructible, 4>
-}
+${kCCommonTypeDecls}
struct atomic_struct {
a : atomic<u32>
@@ -192,7 +203,7 @@ fn foo() -> ${testcase.name} {
interface ParamTypeCase {
name: string;
- valid: boolean;
+ valid: boolean | 'with_unrestricted_pointer_parameters';
}
const kFunctionParamTypeCases: Record<string, ParamTypeCase> = {
@@ -246,21 +257,42 @@ const kFunctionParamTypeCases: Record<string, ParamTypeCase> = {
ptr3: { name: `ptr<private, u32>`, valid: true },
ptr4: { name: `ptr<private, constructible>`, valid: true },
+ // Pointers only valid with unrestricted_pointer_parameters
+ ptr5: { name: `ptr<storage, u32>`, valid: 'with_unrestricted_pointer_parameters' },
+ ptr6: { name: `ptr<storage, u32, read>`, valid: 'with_unrestricted_pointer_parameters' },
+ ptr7: { name: `ptr<storage, u32, read_write>`, valid: 'with_unrestricted_pointer_parameters' },
+ ptr8: { name: `ptr<uniform, u32>`, valid: 'with_unrestricted_pointer_parameters' },
+ ptr9: { name: `ptr<workgroup, u32>`, valid: 'with_unrestricted_pointer_parameters' },
+ ptr10: {
+ name: `ptr<storage, host_shareable, read_write>`,
+ valid: 'with_unrestricted_pointer_parameters',
+ },
+ ptr11: {
+ name: `ptr<storage, host_shareable, read>`,
+ valid: 'with_unrestricted_pointer_parameters',
+ },
+ ptr12: {
+ name: `ptr<uniform, host_shareable>`,
+ valid: 'with_unrestricted_pointer_parameters',
+ },
+ ptrWorkgroupAtomic: {
+ name: `ptr<workgroup, atomic<u32>>`,
+ valid: 'with_unrestricted_pointer_parameters',
+ },
+ ptrWorkgroupNestedAtomic: {
+ name: `ptr<workgroup, array<atomic<u32>,1>>`,
+ valid: 'with_unrestricted_pointer_parameters',
+ },
+
// Invalid pointers.
- ptr5: { name: `ptr<storage, u32>`, valid: false },
- ptr6: { name: `ptr<storage, u32, read>`, valid: false },
- ptr7: { name: `ptr<storage, u32, read_write>`, valid: false },
- ptr8: { name: `ptr<uniform, u32>`, valid: false },
- ptr9: { name: `ptr<workgroup, u32>`, valid: false },
- ptr10: { name: `ptr<handle, u32>`, valid: false }, // Can't spell handle address space
- ptr12: { name: `ptr<not_an_address_space, u32>`, valid: false },
- ptr13: { name: `ptr<storage>`, valid: false }, // No store type
- ptr14: { name: `ptr<private,clamp>`, valid: false }, // Invalid store type
- ptr15: { name: `ptr<private,u32,read>`, valid: false }, // Can't specify access mode
- ptr16: { name: `ptr<private,u32,write>`, valid: false }, // Can't specify access mode
- ptr17: { name: `ptr<private,u32,read_write>`, valid: false }, // Can't specify access mode
- ptrWorkgroupAtomic: { name: `ptr<workgroup, atomic<u32>>`, valid: false },
- ptrWorkgroupNestedAtomic: { name: `ptr<workgroup, array<atomic<u32>,1>>`, valid: false },
+ invalid_ptr1: { name: `ptr<handle, u32>`, valid: false }, // Can't spell handle address space
+ invalid_ptr2: { name: `ptr<not_an_address_space, u32>`, valid: false },
+ invalid_ptr3: { name: `ptr<storage>`, valid: false }, // No store type
+ invalid_ptr4: { name: `ptr<private,u32,read>`, valid: false }, // Can't specify access mode
+ invalid_ptr5: { name: `ptr<private,u32,write>`, valid: false }, // Can't specify access mode
+ invalid_ptr6: { name: `ptr<private,u32,read_write>`, valid: false }, // Can't specify access mode
+ invalid_ptr7: { name: `ptr<private,clamp>`, valid: false }, // Invalid store type
+ invalid_ptr8: { name: `ptr<function, texture_external>`, valid: false }, // non-constructable pointer type
};
g.test('function_parameter_types')
@@ -278,30 +310,23 @@ g.test('function_parameter_types')
const code = `
${enable}
-struct runtime_array_struct {
- arr : array<u32>
-}
-
-struct constructible {
- a : i32,
- b : u32,
- c : f32,
- d : bool,
-}
-
-struct struct_with_array {
- a : array<constructible, 4>
-}
+${kCCommonTypeDecls}
fn foo(param : ${testcase.name}) {
}`;
- t.expectCompileResult(testcase.valid, code);
+ let isValid = testcase.valid;
+ if (isValid === 'with_unrestricted_pointer_parameters') {
+ isValid = t.hasLanguageFeature('unrestricted_pointer_parameters');
+ }
+
+ t.expectCompileResult(isValid, code);
});
interface ParamValueCase {
value: string;
matches: string[];
+ needsUnrestrictedPointerParameters?: boolean;
}
const kFunctionParamValueCases: Record<string, ParamValueCase> = {
@@ -426,15 +451,47 @@ const kFunctionParamValueCases: Record<string, ParamValueCase> = {
ptr3: { value: `&g_u32`, matches: ['ptr3'] },
ptr4: { value: `&g_constructible`, matches: ['ptr4'] },
- // Invalid pointers
- ptr5: { value: `&f_constructible.b`, matches: [] },
- ptr6: { value: `&g_constructible.b`, matches: [] },
- ptr7: { value: `&f_struct_with_array.a[1].b`, matches: [] },
- ptr8: { value: `&g_struct_with_array.a[2]`, matches: [] },
- ptr9: { value: `&ro_constructible.b`, matches: [] },
- ptr10: { value: `&rw_constructible`, matches: [] },
- ptr11: { value: `&uniform_constructible`, matches: [] },
- ptr12: { value: `&ro_constructible`, matches: [] },
+ // Requires 'unrestricted_pointer_parameters' WGSL feature
+ ptr5: {
+ value: `&f_constructible.b`,
+ matches: ['ptr1'],
+ needsUnrestrictedPointerParameters: true,
+ },
+ ptr6: {
+ value: `&g_constructible.b`,
+ matches: ['ptr3'],
+ needsUnrestrictedPointerParameters: true,
+ },
+ ptr7: {
+ value: `&f_struct_with_array.a[1].b`,
+ matches: ['ptr1'],
+ needsUnrestrictedPointerParameters: true,
+ },
+ ptr8: {
+ value: `&g_struct_with_array.a[2]`,
+ matches: ['ptr4'],
+ needsUnrestrictedPointerParameters: true,
+ },
+ ptr9: {
+ value: `&ro_host_shareable.b`,
+ matches: ['ptr5', 'ptr6'],
+ needsUnrestrictedPointerParameters: true,
+ },
+ ptr10: {
+ value: `&rw_host_shareable`,
+ matches: ['ptr10'],
+ needsUnrestrictedPointerParameters: true,
+ },
+ ptr11: {
+ value: `&ro_host_shareable`,
+ matches: ['ptr11'],
+ needsUnrestrictedPointerParameters: true,
+ },
+ ptr12: {
+ value: `&uniform_host_shareable`,
+ matches: ['ptr12'],
+ needsUnrestrictedPointerParameters: true,
+ },
};
function parameterMatches(decl: string, matches: string[]): boolean {
@@ -454,10 +511,11 @@ g.test('function_parameter_matching')
.params(u =>
u
.combine('decl', keysOf(kFunctionParamTypeCases))
- .combine('arg', keysOf(kFunctionParamValueCases))
.filter(u => {
- return kFunctionParamTypeCases[u.decl].valid;
+ return kFunctionParamTypeCases[u.decl].valid !== false;
})
+ .beginSubcases()
+ .combine('arg', keysOf(kFunctionParamValueCases))
)
.beforeAllSubcases(t => {
if (kFunctionParamTypeCases[t.params.decl].name === 'f16') {
@@ -471,26 +529,7 @@ g.test('function_parameter_matching')
const code = `
${enable}
-struct runtime_array_struct {
- arr : array<u32>
-}
-
-struct constructible {
- a : i32,
- b : u32,
- c : f32,
- d : bool,
-}
-
-struct host_shareable {
- a : i32,
- b : u32,
- c : f32,
-}
-
-struct struct_with_array {
- a : array<constructible, 4>
-}
+${kCCommonTypeDecls}
@group(0) @binding(0)
var t : texture_2d<f32>;
@group(0) @binding(1)
@@ -507,11 +546,11 @@ var t_multisampled : texture_multisampled_2d<f32>;
var t_external : texture_external;
@group(1) @binding(0)
-var<storage> ro_constructible : host_shareable;
+var<storage> ro_host_shareable : host_shareable;
@group(1) @binding(1)
-var<storage, read_write> rw_constructible : host_shareable;
+var<storage, read_write> rw_host_shareable : host_shareable;
@group(1) @binding(2)
-var<uniform> uniform_constructible : host_shareable;
+var<uniform> uniform_host_shareable : host_shareable;
fn bar(param : ${param.name}) { }
@@ -568,7 +607,17 @@ fn foo() {
}
`;
- t.expectCompileResult(parameterMatches(t.params.decl, arg.matches), code);
+ const needsUnrestrictedPointerParameters =
+ (kFunctionParamTypeCases[t.params.decl].valid === 'with_unrestricted_pointer_parameters' ||
+ arg.needsUnrestrictedPointerParameters) ??
+ false;
+
+ let isValid = parameterMatches(t.params.decl, arg.matches);
+ if (isValid && needsUnrestrictedPointerParameters) {
+ isValid = t.hasLanguageFeature('unrestricted_pointer_parameters');
+ }
+
+ t.expectCompileResult(isValid, code);
});
g.test('no_direct_recursion')
@@ -691,67 +740,75 @@ function checkArgTypeMatch(param_type: string, arg_matches: string[]): boolean {
return false;
}
-g.test('call_arg_types_match_params')
+g.test('call_arg_types_match_1_param')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#function-calls')
+ .desc(`Test that the argument types match in order`)
+ .params(u =>
+ u
+ .combine('p1_type', kParamsTypes) //
+ .beginSubcases()
+ .combine('arg1_value', keysOf(kArgValues))
+ )
+ .fn(t => {
+ const code = `
+fn bar(p1 : ${t.params.p1_type}) { }
+fn foo() {
+ bar(${kArgValues[t.params.arg1_value].value});
+}`;
+
+ const res = checkArgTypeMatch(t.params.p1_type, kArgValues[t.params.arg1_value].matches);
+ t.expectCompileResult(res, code);
+ });
+
+g.test('call_arg_types_match_2_params')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#function-calls')
+ .desc(`Test that the argument types match in order`)
+ .params(u =>
+ u
+ .combine('p1_type', kParamsTypes)
+ .combine('p2_type', kParamsTypes)
+ .beginSubcases()
+ .combine('arg1_value', keysOf(kArgValues))
+ .combine('arg2_value', keysOf(kArgValues))
+ )
+ .fn(t => {
+ const code = `
+fn bar(p1 : ${t.params.p1_type}, p2 : ${t.params.p2_type}) { }
+fn foo() {
+ bar(${kArgValues[t.params.arg1_value].value}, ${kArgValues[t.params.arg2_value].value});
+}`;
+
+ const res =
+ checkArgTypeMatch(t.params.p1_type, kArgValues[t.params.arg1_value].matches) &&
+ checkArgTypeMatch(t.params.p2_type, kArgValues[t.params.arg2_value].matches);
+ t.expectCompileResult(res, code);
+ });
+
+g.test('call_arg_types_match_3_params')
.specURL('https://gpuweb.github.io/gpuweb/wgsl/#function-calls')
.desc(`Test that the argument types match in order`)
.params(u =>
u
- .combine('num_args', [1, 2, 3] as const)
.combine('p1_type', kParamsTypes)
.combine('p2_type', kParamsTypes)
.combine('p3_type', kParamsTypes)
+ .beginSubcases()
.combine('arg1_value', keysOf(kArgValues))
.combine('arg2_value', keysOf(kArgValues))
.combine('arg3_value', keysOf(kArgValues))
)
.fn(t => {
- let code = `
- fn bar(`;
- for (let i = 0; i < t.params.num_args; i++) {
- switch (i) {
- case 0:
- default: {
- code += `p${i} : ${t.params.p1_type},`;
- break;
- }
- case 1: {
- code += `p${i} : ${t.params.p2_type},`;
- break;
- }
- case 2: {
- code += `p${i} : ${t.params.p3_type},`;
- break;
- }
- }
- }
- code += `) { }
- fn foo() {
- bar(`;
- for (let i = 0; i < t.params.num_args; i++) {
- switch (i) {
- case 0:
- default: {
- code += `${kArgValues[t.params.arg1_value].value},`;
- break;
- }
- case 1: {
- code += `${kArgValues[t.params.arg2_value].value},`;
- break;
- }
- case 2: {
- code += `${kArgValues[t.params.arg3_value].value},`;
- break;
- }
- }
- }
- code += `);\n}`;
+ const code = `
+fn bar(p1 : ${t.params.p1_type}, p2 : ${t.params.p2_type}, p3 : ${t.params.p3_type}) { }
+fn foo() {
+ bar(${kArgValues[t.params.arg1_value].value},
+ ${kArgValues[t.params.arg2_value].value},
+ ${kArgValues[t.params.arg3_value].value});
+}`;
- let res = checkArgTypeMatch(t.params.p1_type, kArgValues[t.params.arg1_value].matches);
- if (res && t.params.num_args > 1) {
- res = checkArgTypeMatch(t.params.p2_type, kArgValues[t.params.arg2_value].matches);
- }
- if (res && t.params.num_args > 2) {
- res = checkArgTypeMatch(t.params.p3_type, kArgValues[t.params.arg3_value].matches);
- }
+ const res =
+ checkArgTypeMatch(t.params.p1_type, kArgValues[t.params.arg1_value].matches) &&
+ checkArgTypeMatch(t.params.p2_type, kArgValues[t.params.arg2_value].matches) &&
+ checkArgTypeMatch(t.params.p3_type, kArgValues[t.params.arg3_value].matches);
t.expectCompileResult(res, code);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/break.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/break.spec.ts
index 7c0f067140..46074ba5d0 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/break.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/break.spec.ts
@@ -15,10 +15,6 @@ const kTests = {
src: 'loop { if true { break; } }',
pass: true,
},
- continuing_break_if: {
- src: 'loop { continuing { break if (true); } }',
- pass: true,
- },
while_break: {
src: 'while true { break; }',
pass: true,
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/break_if.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/break_if.spec.ts
new file mode 100644
index 0000000000..97a625f625
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/break_if.spec.ts
@@ -0,0 +1,141 @@
+export const description = `Validation tests for break if`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kTests = {
+ compound_break: {
+ src: '{ break if true; }',
+ pass: false,
+ },
+ loop_break: {
+ src: 'loop { break if true; }',
+ pass: false,
+ },
+ loop_if_break: {
+ src: 'loop { if true { break if false; } }',
+ pass: false,
+ },
+ continuing_break_if: {
+ src: 'loop { continuing { break if true; } }',
+ pass: true,
+ },
+ continuing_break_if_parens: {
+ src: 'loop { continuing { break if (true); } }',
+ pass: true,
+ },
+ continuing_break_if_not_last: {
+ src: 'loop { continuing { break if (true); let a = 4;} }',
+ pass: false,
+ },
+ while_break: {
+ src: 'while true { break if true; }',
+ pass: false,
+ },
+ while_if_break: {
+ src: 'while true { if true { break if true; } }',
+ pass: false,
+ },
+ for_break: {
+ src: 'for (;;) { break if true; }',
+ pass: false,
+ },
+ for_if_break: {
+ src: 'for (;;) { if true { break if true; } }',
+ pass: false,
+ },
+ switch_case_break: {
+ src: 'switch(1) { default: { break if true; } }',
+ pass: false,
+ },
+ switch_case_if_break: {
+ src: 'switch(1) { default: { if true { break if true; } } }',
+ pass: false,
+ },
+ break: {
+ src: 'break if true;',
+ pass: false,
+ },
+ return_break: {
+ src: 'return break if true;',
+ pass: false,
+ },
+ if_break: {
+ src: 'if true { break if true; }',
+ pass: false,
+ },
+ continuing_if_break: {
+ src: 'loop { continuing { if (true) { break if true; } } }',
+ pass: false,
+ },
+ switch_break: {
+ src: 'switch(1) { break if true; }',
+ pass: false,
+ },
+};
+
+g.test('placement')
+ .desc('Test that break if placement is validated correctly')
+ .params(u => u.combine('stmt', keysOf(kTests)))
+ .fn(t => {
+ const code = `
+@vertex
+fn vtx() -> @builtin(position) vec4f {
+ ${kTests[t.params.stmt].src}
+ return vec4f(1);
+}
+ `;
+ t.expectCompileResult(kTests[t.params.stmt].pass, code);
+ });
+
+const vec_types = [2, 3, 4]
+ .map(i => ['i32', 'u32', 'f32', 'f16'].map(j => `vec${i}<${j}>`))
+ .reduce((a, c) => a.concat(c), []);
+const f32_matrix_types = [2, 3, 4]
+ .map(i => [2, 3, 4].map(j => `mat${i}x${j}f`))
+ .reduce((a, c) => a.concat(c), []);
+const f16_matrix_types = [2, 3, 4]
+ .map(i => [2, 3, 4].map(j => `mat${i}x${j}<f16>`))
+ .reduce((a, c) => a.concat(c), []);
+
+g.test('non_bool_param')
+ .desc('Test that break if fails with a non-bool parameter')
+ .params(u =>
+ u.combine('type', [
+ 'f32',
+ 'f16',
+ 'i32',
+ 'u32',
+ 'S',
+ ...vec_types,
+ ...f32_matrix_types,
+ ...f16_matrix_types,
+ ])
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.type.includes('f16')) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const code = `
+struct S {
+ a: i32,
+}
+
+@vertex
+fn vtx() -> @builtin(position) vec4f {
+ var v: ${t.params.type};
+
+ loop {
+ continuing {
+ break if v;
+ }
+ }
+ return vec4f(1);
+}`;
+ t.expectCompileResult(t.params.type === 'bool', code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/compound.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/compound.spec.ts
new file mode 100644
index 0000000000..b3627c2e5b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/compound.spec.ts
@@ -0,0 +1,52 @@
+export const description = `Validation tests for compound statements`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kTests = {
+ missing_start: {
+ src: '}',
+ pass: false,
+ },
+ missing_end: {
+ src: '{',
+ pass: false,
+ },
+ empty: {
+ src: '{}',
+ pass: true,
+ },
+ semicolon: {
+ src: '{;}',
+ pass: true,
+ },
+ semicolons: {
+ src: '{;;}',
+ pass: true,
+ },
+ decl: {
+ src: '{const c = 1;}',
+ pass: true,
+ },
+ nested: {
+ src: '{ {} }',
+ pass: true,
+ },
+};
+
+g.test('parse')
+ .desc('Test that compound statments parse')
+ .params(u => u.combine('stmt', keysOf(kTests)))
+ .fn(t => {
+ const code = `
+@vertex
+fn vtx() -> @builtin(position) vec4f {
+ ${kTests[t.params.stmt].src}
+ return vec4f(1);
+}
+ `;
+ t.expectCompileResult(kTests[t.params.stmt].pass, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/continuing.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/continuing.spec.ts
new file mode 100644
index 0000000000..7b53e4eab8
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/continuing.spec.ts
@@ -0,0 +1,185 @@
+export const description = `Validation tests for continuing`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kTests = {
+ continuing_break_if: {
+ src: 'loop { continuing { break if true; } }',
+ pass: true,
+ },
+ continuing_empty: {
+ src: 'loop { if a == 4 { break; } continuing { } }',
+ pass: true,
+ },
+ continuing_break_if_parens: {
+ src: 'loop { continuing { break if (true); } }',
+ pass: true,
+ },
+ continuing_discard: {
+ src: 'loop { if a == 4 { break; } continuing { discard; } }',
+ pass: true,
+ },
+ continuing_continue_nested: {
+ src: 'loop { if a == 4 { break; } continuing { loop { if a == 4 { break; } continue; } } }',
+ pass: true,
+ },
+ continuing_continue: {
+ src: 'loop { if a == 4 { break; } continuing { continue; } }',
+ pass: false,
+ },
+ continuing_break: {
+ src: 'loop { continuing { break; } }',
+ pass: false,
+ },
+ continuing_for: {
+ src: 'loop { if a == 4 { break; } continuing { for(;a < 4;) { } } }',
+ pass: true,
+ },
+ continuing_for_break: {
+ src: 'loop { if a == 4 { break; } continuing { for(;;) { break; } } }',
+ pass: true,
+ },
+ continuing_while: {
+ src: 'loop { if a == 4 { break; } continuing { while a < 4 { } } }',
+ pass: true,
+ },
+ continuing_while_break: {
+ src: 'loop { if a == 4 { break; } continuing { while true { break; } } }',
+ pass: true,
+ },
+ continuing_semicolon: {
+ src: 'loop { if a == 4 { break; } continuing { ; } }',
+ pass: true,
+ },
+ continuing_functionn_call: {
+ src: 'loop { if a == 4 { break; } continuing { _ = b(); } }',
+ pass: true,
+ },
+ continuing_let: {
+ src: 'loop { if a == 4 { break; } continuing { let c = b(); } }',
+ pass: true,
+ },
+ continuing_var: {
+ src: 'loop { if a == 4 { break; } continuing { var a = b(); } }',
+ pass: true,
+ },
+ continuing_const: {
+ src: 'loop { if a == 4 { break; } continuing { const a = 1; } }',
+ pass: true,
+ },
+ continuing_block: {
+ src: 'loop { if a == 4 { break; } continuing { { } } }',
+ pass: true,
+ },
+ continuing_const_assert: {
+ src: 'loop { if a == 4 { break; } continuing { const_assert(1 != 2); } }',
+ pass: true,
+ },
+ continuing_loop: {
+ src: 'loop { if a == 4 { break; } continuing { loop { break; } } }',
+ pass: true,
+ },
+ continuing_if: {
+ src: 'loop { if a == 4 { break; } continuing { if true { } else if false { } else { } } }',
+ pass: true,
+ },
+ continuing_switch: {
+ src: 'loop { if a == 4 { break; } continuing { switch 2 { default: { } } } }',
+ pass: true,
+ },
+ continuing_switch_break: {
+ src: 'loop { if a == 4 { break; } continuing { switch 2 { default: { break; } } } }',
+ pass: true,
+ },
+ continuing_loop_nested_continuing: {
+ src: 'loop { if a == 4 { break; } continuing { loop { if a == 4 { break; } continuing { } } } }',
+ pass: true,
+ },
+ continuing_inc: {
+ src: 'loop { if a == 4 { break; } continuing { a += 1; } }',
+ pass: true,
+ },
+ continuing_dec: {
+ src: 'loop { if a == 4 { break; } continuing { a -= 1; } }',
+ pass: true,
+ },
+ while: {
+ src: 'while a < 4 { continuing { break if true; } }',
+ pass: false,
+ },
+ for: {
+ src: 'for (;a < 4;) { continuing { break if true; } }',
+ pass: false,
+ },
+ switch_case: {
+ src: 'switch(1) { default: { continuing { break if true; } } }',
+ pass: false,
+ },
+ switch: {
+ src: 'switch(1) { continuing { break if true; } }',
+ pass: false,
+ },
+ continuing: {
+ src: 'continuing { break if true; }',
+ pass: false,
+ },
+ return: {
+ src: 'return continuing { break if true; }',
+ pass: false,
+ },
+ if_body: {
+ src: 'if true { continuing { break if true; } }',
+ pass: false,
+ },
+ if: {
+ src: 'if true { } continuing { break if true; } }',
+ pass: false,
+ },
+ if_else: {
+ src: 'if true { } else { } continuing { break if true; } }',
+ pass: false,
+ },
+ continuing_continuing: {
+ src: 'loop { if a == 4 { break; } continuing { continuing { break if true; } } }',
+ pass: false,
+ },
+ no_body: {
+ src: 'loop { if a == 4 { break; } continuing }',
+ pass: false,
+ },
+ return_in_continue: {
+ src: 'loop { if a == 4 { break; } continuing { return vec4f(2); } }',
+ pass: false,
+ },
+ return_if_nested_in_continue: {
+ src: 'loop { if a == 4 { break; } continuing { if true { return vec4f(2); } } }',
+ pass: false,
+ },
+ return_for_nested_in_continue: {
+ src: 'loop { if a == 4 { break; } continuing { for(;a < 4;) { return vec4f(2); } } }',
+ pass: false,
+ },
+};
+
+g.test('placement')
+ .desc('Test that continuing placement is validated correctly')
+ .params(u => u.combine('stmt', keysOf(kTests)))
+ .fn(t => {
+ const code = `
+fn b() -> i32 {
+ return 1;
+}
+
+@fragment
+fn frag() -> @location(0) vec4f {
+ var a = 0;
+ ${kTests[t.params.stmt].src}
+ return vec4f(1);
+}
+ `;
+ t.expectCompileResult(kTests[t.params.stmt].pass, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/diagnostic.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/diagnostic.spec.ts
index 154a4253ea..701ae46f3a 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/diagnostic.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/diagnostic.spec.ts
@@ -199,3 +199,263 @@ g.test('conflicting_attribute_different_location')
const code = `${kNestedLocations[t.params.loc](d1, d2)}`;
t.expectCompileResult(true, code);
});
+
+g.test('after_other_directives')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#diagnostics')
+ .desc(`Tests other global directives before a diagnostic directive.`)
+ .params(u =>
+ u.combine('directive', ['enable f16', 'requires readonly_and_readwrite_storage_textures'])
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.directive.startsWith('enable')) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ if (t.params.directive.startsWith('requires')) {
+ t.skipIfLanguageFeatureNotSupported('readonly_and_readwrite_storage_textures');
+ }
+
+ let code = `${t.params.directive};`;
+ code += generateDiagnostic('directive', 'info', 'derivative_uniformity') + ';';
+ t.expectCompileResult(true, code);
+ });
+
+interface ScopeCase {
+ code: string;
+ result: boolean | 'warn';
+}
+
+function scopeCode(body: string): string {
+ return `
+@group(0) @binding(0) var t : texture_1d<f32>;
+@group(0) @binding(1) var s : sampler;
+var<private> non_uniform_cond : bool;
+var<private> non_uniform_coord : f32;
+var<private> non_uniform_val : u32;
+@fragment fn main() {
+ ${body}
+}
+`;
+}
+
+const kScopeCases: Record<string, ScopeCase> = {
+ override_global_off: {
+ code: `
+ ${generateDiagnostic('directive', 'error', 'derivative_uniformity')};
+ ${scopeCode(`
+ ${generateDiagnostic('', 'off', 'derivative_uniformity')}
+ if non_uniform_cond {
+ _ = textureSample(t,s,0.0);
+ }`)};
+ `,
+ result: true,
+ },
+ override_global_on: {
+ code: `
+ ${generateDiagnostic('directive', 'off', 'derivative_uniformity')};
+ ${scopeCode(`
+ ${generateDiagnostic('', 'error', 'derivative_uniformity')}
+ if non_uniform_cond {
+ _ = textureSample(t,s,0.0);
+ }`)}
+ `,
+ result: false,
+ },
+ override_global_warn: {
+ code: `
+ ${generateDiagnostic('directive', 'error', 'derivative_uniformity')};
+ ${scopeCode(`
+ ${generateDiagnostic('', 'warning', 'derivative_uniformity')}
+ if non_uniform_cond {
+ _ = textureSample(t,s,0.0);
+ }`)}
+ `,
+ result: 'warn',
+ },
+ global_if_nothing_else_warn: {
+ code: `
+ ${generateDiagnostic('directive', 'warning', 'derivative_uniformity')};
+ ${scopeCode(`
+ if non_uniform_cond {
+ _ = textureSample(t,s,0.0);
+ }`)}
+ `,
+ result: 'warn',
+ },
+ deepest_nesting_warn: {
+ code: scopeCode(`
+ ${generateDiagnostic('', 'error', 'derivative_uniformity')}
+ if non_uniform_cond {
+ ${generateDiagnostic('', 'warning', 'derivative_uniformity')}
+ if non_uniform_cond {
+ _ = textureSample(t,s,0.0);
+ }
+ }`),
+ result: 'warn',
+ },
+ deepest_nesting_off: {
+ code: scopeCode(`
+ ${generateDiagnostic('', 'error', 'derivative_uniformity')}
+ if non_uniform_cond {
+ ${generateDiagnostic('', 'off', 'derivative_uniformity')}
+ if non_uniform_cond {
+ _ = textureSample(t,s,0.0);
+ }
+ }`),
+ result: true,
+ },
+ deepest_nesting_error: {
+ code: scopeCode(`
+ ${generateDiagnostic('', 'off', 'derivative_uniformity')}
+ if non_uniform_cond {
+ ${generateDiagnostic('', 'error', 'derivative_uniformity')}
+ if non_uniform_cond {
+ _ = textureSample(t,s,0.0);
+ }
+ }`),
+ result: false,
+ },
+ other_nest_unaffected: {
+ code: `
+ ${generateDiagnostic('directive', 'warning', 'derivative_uniformity')};
+ ${scopeCode(`
+ ${generateDiagnostic('', 'off', 'derivative_uniformity')}
+ if non_uniform_cond {
+ _ = textureSample(t,s,0.0);
+ }
+ if non_uniform_cond {
+ _ = textureSample(t,s,0.0);
+ }`)}
+ `,
+ result: 'warn',
+ },
+ deeper_nest_no_effect: {
+ code: `
+ ${generateDiagnostic('directive', 'error', 'derivative_uniformity')};
+ ${scopeCode(`
+ if non_uniform_cond {
+ ${generateDiagnostic('', 'off', 'derivative_uniformity')}
+ if non_uniform_cond {
+ }
+ _ = textureSample(t,s,0.0);
+ }`)}
+ `,
+ result: false,
+ },
+ call_unaffected_error: {
+ code: `
+ ${generateDiagnostic('directive', 'error', 'derivative_uniformity')};
+ fn foo() { _ = textureSample(t,s,0.0); }
+ ${scopeCode(`
+ ${generateDiagnostic('', 'off', 'derivative_uniformity')}
+ if non_uniform_cond {
+ foo();
+ }`)}
+ `,
+ result: false,
+ },
+ call_unaffected_warn: {
+ code: `
+ ${generateDiagnostic('directive', 'warning', 'derivative_uniformity')};
+ fn foo() { _ = textureSample(t,s,0.0); }
+ ${scopeCode(`
+ ${generateDiagnostic('', 'off', 'derivative_uniformity')}
+ if non_uniform_cond {
+ foo();
+ }`)}
+ `,
+ result: 'warn',
+ },
+ call_unaffected_off: {
+ code: `
+ ${generateDiagnostic('directive', 'off', 'derivative_uniformity')};
+ fn foo() { _ = textureSample(t,s,0.0); }
+ ${scopeCode(`
+ ${generateDiagnostic('', 'error', 'derivative_uniformity')}
+ if non_uniform_cond {
+ foo();
+ }`)}
+ `,
+ result: true,
+ },
+ if_condition_error: {
+ code: scopeCode(`
+ if (non_uniform_cond) {
+ ${generateDiagnostic('', 'error', 'derivative_uniformity')}
+ if textureSample(t,s,non_uniform_coord).x > 0.0
+ ${generateDiagnostic('', 'off', 'derivative_uniformity')} {
+ }
+ }`),
+ result: false,
+ },
+ if_condition_warn: {
+ code: scopeCode(`
+ if non_uniform_cond {
+ ${generateDiagnostic('', 'warning', 'derivative_uniformity')}
+ if textureSample(t,s,non_uniform_coord).x > 0.0
+ ${generateDiagnostic('', 'error', 'derivative_uniformity')} {
+ }
+ }`),
+ result: 'warn',
+ },
+ if_condition_off: {
+ code: scopeCode(`
+ if non_uniform_cond {
+ ${generateDiagnostic('', 'off', 'derivative_uniformity')}
+ if textureSample(t,s,non_uniform_coord).x > 0.0
+ ${generateDiagnostic('', 'error', 'derivative_uniformity')} {
+ }
+ }`),
+ result: true,
+ },
+ switch_error: {
+ code: scopeCode(`
+ ${generateDiagnostic('', 'error', 'derivative_uniformity')}
+ switch non_uniform_val {
+ case 0 ${generateDiagnostic('', 'off', 'derivative_uniformity')} {
+ }
+ default {
+ _ = textureSample(t,s,0.0);
+ }
+ }`),
+ result: false,
+ },
+ switch_warn: {
+ code: scopeCode(`
+ ${generateDiagnostic('', 'warning', 'derivative_uniformity')}
+ switch non_uniform_val {
+ case 0 ${generateDiagnostic('', 'off', 'derivative_uniformity')} {
+ }
+ default {
+ _ = textureSample(t,s,0.0);
+ }
+ }`),
+ result: 'warn',
+ },
+ switch_off: {
+ code: scopeCode(`
+ ${generateDiagnostic('', 'off', 'derivative_uniformity')}
+ switch non_uniform_val {
+ case 0 ${generateDiagnostic('', 'error', 'derivative_uniformity')}{
+ }
+ default {
+ _ = textureSample(t,s,0.0);
+ }
+ }`),
+ result: true,
+ },
+};
+
+g.test('diagnostic_scoping')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#diagnostics')
+ .desc('Tests that innermost scope controls the diagnostic')
+ .params(u => u.combine('case', keysOf(kScopeCases)))
+ .fn(t => {
+ const testcase = kScopeCases[t.params.case];
+ if (testcase.result === 'warn') {
+ t.expectCompileWarning(true, testcase.code);
+ } else {
+ t.expectCompileResult(testcase.result as boolean, testcase.code);
+ }
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/enable.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/enable.spec.ts
index 230244c6b8..799053b547 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/enable.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/enable.spec.ts
@@ -13,11 +13,21 @@ const kCases = {
enable f16;`,
pass: false,
},
- after_decl: {
+ decl_after: {
code: `enable f16;
alias i = i32;`,
pass: true,
},
+ requires_before: {
+ code: `requires readonly_and_readwrite_storage_textures;
+enable f16;`,
+ pass: true,
+ },
+ diagnostic_before: {
+ code: `diagnostic(info, derivative_uniformity);
+enable f16;`,
+ pass: true,
+ },
const_assert_before: {
code: `const_assert 1 == 1;
enable f16;`,
@@ -48,7 +58,7 @@ f16;`,
enable f16;`,
pass: true,
},
- multipe_entries: {
+ multiple_entries: {
code: `enable f16, f16, f16;`,
pass: true,
},
@@ -65,6 +75,10 @@ g.test('enable')
})
.params(u => u.combine('case', keysOf(kCases)))
.fn(t => {
+ if (t.params.case === 'requires_before') {
+ t.skipIfLanguageFeatureNotSupported('readonly_and_readwrite_storage_textures');
+ }
+
const c = kCases[t.params.case];
t.expectCompileResult(c.pass, c.code);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/must_use.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/must_use.spec.ts
index dd36fabcf6..058a5f8c9b 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/must_use.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/must_use.spec.ts
@@ -57,27 +57,74 @@ g.test('declaration')
});
const kMustUseCalls = {
+ no_call: ``, // Never calling a @must_use function should pass
phony: `_ = bar();`,
let: `let tmp = bar();`,
- var: `var tmp = bar();`,
+ local_var: `var tmp = bar();`,
+ private_var: `private_var = bar();`,
+ storage_var: `storage_var = bar();`,
+ pointer: `
+ var a : f32;
+ let p = &a;
+ (*p) = bar();`,
+ vector_elem: `
+ var a : vec3<f32>;
+ a.x = bar();`,
+ matrix_elem: `
+ var a : mat3x2<f32>;
+ a[0][0] = bar();`,
condition: `if bar() == 0 { }`,
param: `baz(bar());`,
- statement: `bar();`,
+ return: `return bar();`,
+ statement: `bar();`, // should fail if bar is @must_use
};
g.test('call')
.desc(`Validate that a call to must_use function cannot be the whole function call statement`)
- .params(u => u.combine('use', ['@must_use', ''] as const).combine('call', keysOf(kMustUseCalls)))
+ .params(u =>
+ u //
+ .combine('use', ['@must_use', ''] as const)
+ .combine('call', keysOf(kMustUseCalls))
+ )
.fn(t => {
const test = kMustUseCalls[t.params.call];
const code = `
- fn baz(param : u32) { }
- ${t.params.use} fn bar() -> u32 { return 0; }
- fn foo() {
+ @group(0) @binding(0) var<storage, read_write> storage_var : f32;
+ var<private> private_var : f32;
+
+ fn baz(param : f32) { }
+
+ ${t.params.use} fn bar() -> f32 { return 0; }
+
+ fn foo() ${t.params.call === 'return' ? '-> f32' : ''} {
${test}
}`;
- const res = t.params.call !== 'statement' || t.params.use === '';
- t.expectCompileResult(res, code);
+
+ const should_pass = t.params.call !== 'statement' || t.params.use === '';
+ t.expectCompileResult(should_pass, code);
+ });
+
+g.test('ignore_result_of_non_must_use_that_returns_call_of_must_use')
+ .desc(
+ `Test that ignoring the result of a non-@must_use function that returns the result of a @must_use function succeeds`
+ )
+ .fn(t => {
+ const wgsl = `
+ @must_use
+ fn f() -> f32 {
+ return 0;
+ }
+
+ fn g() -> f32 {
+ return f();
+ }
+
+ fn main() {
+ g(); // Ignore result
+ }
+ `;
+
+ t.expectCompileResult(true, wgsl);
});
const kMustUseBuiltinCalls = {
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/pipeline_stage.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/pipeline_stage.spec.ts
index 78dcb95782..f492121f25 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/pipeline_stage.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/pipeline_stage.spec.ts
@@ -73,34 +73,46 @@ g.test('multiple_entry_points')
t.expectCompileResult(true, code);
});
-g.test('duplicate_compute_on_function')
- .desc(`Test that duplcate @compute attributes are not allowed.`)
- .params(u => u.combine('dupe', ['', '@compute']))
+g.test('extra_on_compute_function')
+ .desc(`Test that an extra stage attribute on @compute functions are not allowed.`)
+ .params(u =>
+ u.combine('extra', ['', '@compute', '@fragment', '@vertex']).combine('before', [false, true])
+ )
.fn(t => {
+ const before = t.params.before ? t.params.extra : '';
+ const after = t.params.before ? '' : t.params.extra;
const code = `
-@compute ${t.params.dupe} @workgroup_size(1) fn compute_1() {}
+${before} @compute ${after} @workgroup_size(1) fn main() {}
`;
- t.expectCompileResult(t.params.dupe === '', code);
+ t.expectCompileResult(t.params.extra === '', code);
});
-g.test('duplicate_fragment_on_function')
- .desc(`Test that duplcate @fragment attributes are not allowed.`)
- .params(u => u.combine('dupe', ['', '@fragment']))
+g.test('extra_on_fragment_function')
+ .desc(`Test that an extra stage attribute on @fragment functions are not allowed.`)
+ .params(u =>
+ u.combine('extra', ['', '@compute', '@fragment', '@vertex']).combine('before', [false, true])
+ )
.fn(t => {
+ const before = t.params.before ? t.params.extra : '';
+ const after = t.params.before ? '' : t.params.extra;
const code = `
-@fragment ${t.params.dupe} fn vtx() -> @location(0) vec4f { return vec4f(1); }
+${before} @fragment ${after} fn main() -> @location(0) vec4f { return vec4f(1); }
`;
- t.expectCompileResult(t.params.dupe === '', code);
+ t.expectCompileResult(t.params.extra === '', code);
});
-g.test('duplicate_vertex_on_function')
- .desc(`Test that duplcate @vertex attributes are not allowed.`)
- .params(u => u.combine('dupe', ['', '@vertex']))
+g.test('extra_on_vertex_function')
+ .desc(`Test that an extra stage attribute on @vertex functions are not allowed.`)
+ .params(u =>
+ u.combine('extra', ['', '@compute', '@fragment', '@vertex']).combine('before', [false, true])
+ )
.fn(t => {
+ const before = t.params.before ? t.params.extra : '';
+ const after = t.params.before ? '' : t.params.extra;
const code = `
-@vertex ${t.params.dupe} fn vtx() -> @builtin(position) vec4f { return vec4f(1); }
+${before} @vertex ${after} fn main() -> @builtin(position) vec4f { return vec4f(1); }
`;
- t.expectCompileResult(t.params.dupe === '', code);
+ t.expectCompileResult(t.params.extra === '', code);
});
g.test('placement')
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/requires.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/requires.spec.ts
new file mode 100644
index 0000000000..2b365ae61e
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/requires.spec.ts
@@ -0,0 +1,103 @@
+export const description = `Parser validation tests for requires`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { kKnownWGSLLanguageFeatures } from '../../../capability_info.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kCases = {
+ valid: { code: `requires readonly_and_readwrite_storage_textures;`, pass: true },
+ decl_before: {
+ code: `alias i = i32;
+requires readonly_and_readwrite_storage_textures;`,
+ pass: false,
+ },
+ decl_after: {
+ code: `requires readonly_and_readwrite_storage_textures;
+alias i = i32;`,
+ pass: true,
+ },
+ enable_before: {
+ code: `enable f16;
+requires readonly_and_readwrite_storage_textures;`,
+ pass: true,
+ },
+ diagnostic_before: {
+ code: `diagnostic(info, derivative_uniformity);
+requires readonly_and_readwrite_storage_textures;`,
+ pass: true,
+ },
+ const_assert_before: {
+ code: `const_assert 1 == 1;
+requires readonly_and_readwrite_storage_textures;`,
+ pass: false,
+ },
+ const_assert_after: {
+ code: `requires readonly_and_readwrite_storage_textures;
+const_assert 1 == 1;`,
+ pass: true,
+ },
+ embedded_comment: {
+ code: `/* comment
+
+*/requires readonly_and_readwrite_storage_textures;`,
+ pass: true,
+ },
+ parens: {
+ code: `requires(readonly_and_readwrite_storage_textures);`,
+ pass: false,
+ },
+ multi_line: {
+ code: `requires
+readonly_and_readwrite_storage_textures;`,
+ pass: true,
+ },
+ multiple_requires_duplicate: {
+ code: `requires readonly_and_readwrite_storage_textures;
+requires readonly_and_readwrite_storage_textures;`,
+ pass: true,
+ },
+ multiple_requires_different: {
+ code: `requires readonly_and_readwrite_storage_textures;
+requires packed_4x8_integer_dot_product;`,
+ pass: true,
+ },
+ multiple_entries_duplicate: {
+ code: `requires readonly_and_readwrite_storage_textures, readonly_and_readwrite_storage_textures, readonly_and_readwrite_storage_textures;`,
+ pass: true,
+ },
+ multiple_entries_different: {
+ code: `requires readonly_and_readwrite_storage_textures, packed_4x8_integer_dot_product;`,
+ pass: true,
+ },
+ unknown: {
+ code: `requires unknown;`,
+ pass: false,
+ },
+};
+
+g.test('requires')
+ .desc(`Tests that requires are validated correctly.`)
+ .params(u => u.combine('case', keysOf(kCases)))
+ .beforeAllSubcases(t => {
+ if (t.params.case === 'enable_before') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported('readonly_and_readwrite_storage_textures');
+ t.skipIfLanguageFeatureNotSupported('packed_4x8_integer_dot_product');
+
+ const c = kCases[t.params.case];
+ t.expectCompileResult(c.pass, c.code);
+ });
+
+g.test('wgsl_matches_api')
+ .desc(`Tests that language features are accepted iff the API reports support for them.`)
+ .params(u => u.combine('feature', kKnownWGSLLanguageFeatures))
+ .fn(t => {
+ const code = `requires ${t.params.feature};`;
+ t.expectCompileResult(t.hasLanguageFeature(t.params.feature), code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/semicolon.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/semicolon.spec.ts
index 87cffcfafc..15a225e19f 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/semicolon.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/semicolon.spec.ts
@@ -27,6 +27,21 @@ g.test('after_enable')
t.expectCompileResult(/* pass */ false, `enable f16`);
});
+g.test('after_requires')
+ .desc(`Test that a semicolon must be placed after a requires directive.`)
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported('readonly_and_readwrite_storage_textures');
+ t.expectCompileResult(/* pass */ true, `requires readonly_and_readwrite_storage_textures;`);
+ t.expectCompileResult(/* pass */ false, `requires readonly_and_readwrite_storage_textures`);
+ });
+
+g.test('after_diagnostic')
+ .desc(`Test that a semicolon must be placed after a requires directive.`)
+ .fn(t => {
+ t.expectCompileResult(/* pass */ true, `diagnostic(info, derivative_uniformity);`);
+ t.expectCompileResult(/* pass */ false, `diagnostic(info, derivative_uniformity)`);
+ });
+
g.test('after_struct_decl')
.desc(`Test that a semicolon can be placed after an struct declaration.`)
.fn(t => {
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/shadow_builtins.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/shadow_builtins.spec.ts
new file mode 100644
index 0000000000..3f72a0bf72
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/shadow_builtins.spec.ts
@@ -0,0 +1,995 @@
+export const description = `Validation tests for identifiers`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('function_param')
+ .desc(
+ `Test that a function param can shadow a builtin, but the builtin is available for other params.`
+ )
+ .fn(t => {
+ const code = `
+fn f(f: i32, i32: i32, t: i32) -> i32 { return i32; }
+ `;
+ t.expectCompileResult(true, code);
+ });
+
+const kTests = {
+ abs: {
+ keyword: `abs`,
+ src: `_ = abs(1);`,
+ },
+ acos: {
+ keyword: `acos`,
+ src: `_ = acos(.2);`,
+ },
+ acosh: {
+ keyword: `acosh`,
+ src: `_ = acosh(1.2);`,
+ },
+ all: {
+ keyword: `all`,
+ src: `_ = all(true);`,
+ },
+ any: {
+ keyword: `any`,
+ src: `_ = any(true);`,
+ },
+ array_templated: {
+ keyword: `array`,
+ src: `_ = array<i32, 2>(1, 2);`,
+ },
+ array: {
+ keyword: `array`,
+ src: `_ = array(1, 2);`,
+ },
+ array_length: {
+ keyword: `arrayLength`,
+ src: `_ = arrayLength(&placeholder.rt_arr);`,
+ },
+ asin: {
+ keyword: `asin`,
+ src: `_ = asin(.2);`,
+ },
+ asinh: {
+ keyword: `asinh`,
+ src: `_ = asinh(1.2);`,
+ },
+ atan: {
+ keyword: `atan`,
+ src: `_ = atan(1.2);`,
+ },
+ atanh: {
+ keyword: `atanh`,
+ src: `_ = atanh(.2);`,
+ },
+ atan2: {
+ keyword: `atan2`,
+ src: `_ = atan2(1.2, 2.3);`,
+ },
+ bool: {
+ keyword: `bool`,
+ src: `_ = bool(1);`,
+ },
+ bitcast: {
+ keyword: `bitcast`,
+ src: `_ = bitcast<f32>(1i);`,
+ },
+ ceil: {
+ keyword: `ceil`,
+ src: `_ = ceil(1.23);`,
+ },
+ clamp: {
+ keyword: `clamp`,
+ src: `_ = clamp(1, 2, 3);`,
+ },
+ cos: {
+ keyword: `cos`,
+ src: `_ = cos(2);`,
+ },
+ cosh: {
+ keyword: `cosh`,
+ src: `_ = cosh(2.2);`,
+ },
+ countLeadingZeros: {
+ keyword: `countLeadingZeros`,
+ src: `_ = countLeadingZeros(1);`,
+ },
+ countOneBits: {
+ keyword: `countOneBits`,
+ src: `_ = countOneBits(1);`,
+ },
+ countTrailingZeros: {
+ keyword: `countTrailingZeros`,
+ src: `_ = countTrailingZeros(1);`,
+ },
+ cross: {
+ keyword: `cross`,
+ src: `_ = cross(vec3(1, 2, 3), vec3(4, 5, 6));`,
+ },
+ degrees: {
+ keyword: `degrees`,
+ src: `_ = degrees(1);`,
+ },
+ determinant: {
+ keyword: `determinant`,
+ src: `_ = determinant(mat2x2(1, 2, 3, 4));`,
+ },
+ distance: {
+ keyword: `distance`,
+ src: `_ = distance(1, 2);`,
+ },
+ dot: {
+ keyword: `dot`,
+ src: `_ = dot(vec2(1, 2,), vec2(2, 3));`,
+ },
+ dot4U8Packed: {
+ keyword: `dot4U8Packed`,
+ src: `_ = dot4U8Packed(1, 2);`,
+ },
+ dot4I8Packed: {
+ keyword: `dot4I8Packed`,
+ src: `_ = dot4I8Packed(1, 2);`,
+ },
+ dpdx: {
+ keyword: `dpdx`,
+ src: `_ = dpdx(2);`,
+ },
+ dpdxCoarse: {
+ keyword: `dpdxCoarse`,
+ src: `_ = dpdxCoarse(2);`,
+ },
+ dpdxFine: {
+ keyword: `dpdxFine`,
+ src: `_ = dpdxFine(2);`,
+ },
+ dpdy: {
+ keyword: `dpdy`,
+ src: `_ = dpdy(2);`,
+ },
+ dpdyCoarse: {
+ keyword: `dpdyCoarse`,
+ src: `_ = dpdyCoarse(2);`,
+ },
+ dpdyFine: {
+ keyword: `dpdyFine`,
+ src: `_ = dpdyFine(2);`,
+ },
+ exp: {
+ keyword: `exp`,
+ src: `_ = exp(1);`,
+ },
+ exp2: {
+ keyword: `exp2`,
+ src: `_ = exp2(2);`,
+ },
+ extractBits: {
+ keyword: `extractBits`,
+ src: `_ = extractBits(1, 2, 3);`,
+ },
+ f32: {
+ keyword: `f32`,
+ src: `_ = f32(1i);`,
+ },
+ faceForward: {
+ keyword: `faceForward`,
+ src: `_ = faceForward(vec2(1, 2), vec2(3, 4), vec2(5, 6));`,
+ },
+ firstLeadingBit: {
+ keyword: `firstLeadingBit`,
+ src: `_ = firstLeadingBit(1);`,
+ },
+ firstTrailingBit: {
+ keyword: `firstTrailingBit`,
+ src: `_ = firstTrailingBit(1);`,
+ },
+ floor: {
+ keyword: `floor`,
+ src: `_ = floor(1.2);`,
+ },
+ fma: {
+ keyword: `fma`,
+ src: `_ = fma(1, 2, 3);`,
+ },
+ fract: {
+ keyword: `fract`,
+ src: `_ = fract(1);`,
+ },
+ frexp: {
+ keyword: `frexp`,
+ src: `_ = frexp(1);`,
+ },
+ fwidth: {
+ keyword: `fwidth`,
+ src: `_ = fwidth(2);`,
+ },
+ fwidthCoarse: {
+ keyword: `fwidthCoarse`,
+ src: `_ = fwidthCoarse(2);`,
+ },
+ fwidthFine: {
+ keyword: `fwidthFine`,
+ src: `_ = fwidthFine(2);`,
+ },
+ i32: {
+ keyword: `i32`,
+ src: `_ = i32(2u);`,
+ },
+ insertBits: {
+ keyword: `insertBits`,
+ src: `_ = insertBits(1, 2, 3, 4);`,
+ },
+ inverseSqrt: {
+ keyword: `inverseSqrt`,
+ src: `_ = inverseSqrt(1);`,
+ },
+ ldexp: {
+ keyword: `ldexp`,
+ src: `_ = ldexp(1, 2);`,
+ },
+ length: {
+ keyword: `length`,
+ src: `_ = length(1);`,
+ },
+ log: {
+ keyword: `log`,
+ src: `_ = log(2);`,
+ },
+ log2: {
+ keyword: `log2`,
+ src: `_ = log2(2);`,
+ },
+ mat2x2_templated: {
+ keyword: `mat2x2`,
+ src: `_ = mat2x2<f32>(1, 2, 3, 4);`,
+ },
+ mat2x2: {
+ keyword: `mat2x2`,
+ src: `_ = mat2x2(1, 2, 3, 4);`,
+ },
+ mat2x3_templated: {
+ keyword: `mat2x3`,
+ src: `_ = mat2x3<f32>(1, 2, 3, 4, 5, 6);`,
+ },
+ mat2x3: {
+ keyword: `mat2x3`,
+ src: `_ = mat2x3(1, 2, 3, 4, 5, 6);`,
+ },
+ mat2x4_templated: {
+ keyword: `mat2x4`,
+ src: `_ = mat2x4<f32>(1, 2, 3, 4, 5, 6, 7, 8);`,
+ },
+ mat2x4: {
+ keyword: `mat2x4`,
+ src: `_ = mat2x4(1, 2, 3, 4, 5, 6, 7, 8);`,
+ },
+ mat3x2_templated: {
+ keyword: `mat3x2`,
+ src: `_ = mat3x2<f32>(1, 2, 3, 4, 5, 6);`,
+ },
+ mat3x2: {
+ keyword: `mat3x2`,
+ src: `_ = mat3x2(1, 2, 3, 4, 5, 6);`,
+ },
+ mat3x3_templated: {
+ keyword: `mat3x3`,
+ src: `_ = mat3x3<f32>(1, 2, 3, 4, 5, 6, 7, 8, 9);`,
+ },
+ mat3x3: {
+ keyword: `mat3x3`,
+ src: `_ = mat3x3(1, 2, 3, 4, 5, 6, 7, 8, 9);`,
+ },
+ mat3x4_templated: {
+ keyword: `mat3x4`,
+ src: `_ = mat3x4<f32>(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);`,
+ },
+ mat3x4: {
+ keyword: `mat3x4`,
+ src: `_ = mat3x4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);`,
+ },
+ mat4x2_templated: {
+ keyword: `mat4x2`,
+ src: `_ = mat4x2<f32>(1, 2, 3, 4, 5, 6, 7, 8);`,
+ },
+ mat4x2: {
+ keyword: `mat4x2`,
+ src: `_ = mat4x2(1, 2, 3, 4, 5, 6, 7, 8);`,
+ },
+ mat4x3_templated: {
+ keyword: `mat4x3`,
+ src: `_ = mat4x3<f32>(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);`,
+ },
+ mat4x3: {
+ keyword: `mat4x3`,
+ src: `_ = mat4x3(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);`,
+ },
+ mat4x4_templated: {
+ keyword: `mat4x4`,
+ src: `_ = mat4x4<f32>(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);`,
+ },
+ mat4x4: {
+ keyword: `mat4x4`,
+ src: `_ = mat4x4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);`,
+ },
+ max: {
+ keyword: `max`,
+ src: `_ = max(1, 2);`,
+ },
+ min: {
+ keyword: `min`,
+ src: `_ = min(1, 2);`,
+ },
+ mix: {
+ keyword: `mix`,
+ src: `_ = mix(1, 2, 3);`,
+ },
+ modf: {
+ keyword: `modf`,
+ src: `_ = modf(1.2);`,
+ },
+ normalize: {
+ keyword: `normalize`,
+ src: `_ = normalize(vec2(1, 2));`,
+ },
+ pack2x16snorm: {
+ keyword: `pack2x16snorm`,
+ src: `_ = pack2x16snorm(vec2(1, 2));`,
+ },
+ pack2x16unorm: {
+ keyword: `pack2x16unorm`,
+ src: `_ = pack2x16unorm(vec2(1, 2));`,
+ },
+ pack2x16float: {
+ keyword: `pack2x16float`,
+ src: `_ = pack2x16float(vec2(1, 2));`,
+ },
+ pack4x8snorm: {
+ keyword: `pack4x8snorm`,
+ src: `_ = pack4x8snorm(vec4(1, 2, 3, 4));`,
+ },
+ pack4x8unorm: {
+ keyword: `pack4x8unorm`,
+ src: `_ = pack4x8unorm(vec4(1, 2, 3, 4));`,
+ },
+ pack4xI8: {
+ keyword: `pack4xI8`,
+ src: `_ = pack4xI8(vec4(1, 2, 3, 4));`,
+ },
+ pack4xU8: {
+ keyword: `pack4xU8`,
+ src: `_ = pack4xU8(vec4(1, 2, 3, 4));`,
+ },
+ pack4xI8Clamp: {
+ keyword: `pack4xI8Clamp`,
+ src: `_ = pack4xI8Clamp(vec4(1, 2, 3, 4));`,
+ },
+ pack4xU8Clamp: {
+ keyword: `pack4xU8Clamp`,
+ src: `_ = pack4xU8Clamp(vec4(1, 2, 3, 4));`,
+ },
+ pow: {
+ keyword: `pow`,
+ src: `_ = pow(1, 2);`,
+ },
+ quantizeToF16: {
+ keyword: `quantizeToF16`,
+ src: `_ = quantizeToF16(1.2);`,
+ },
+ radians: {
+ keyword: `radians`,
+ src: `_ = radians(1.2);`,
+ },
+ reflect: {
+ keyword: `reflect`,
+ src: `_ = reflect(vec2(1, 2), vec2(3, 4));`,
+ },
+ refract: {
+ keyword: `refract`,
+ src: `_ = refract(vec2(1, 1), vec2(2, 2), 3);`,
+ },
+ reverseBits: {
+ keyword: `reverseBits`,
+ src: `_ = reverseBits(1);`,
+ },
+ round: {
+ keyword: `round`,
+ src: `_ = round(1.2);`,
+ },
+ saturate: {
+ keyword: `saturate`,
+ src: `_ = saturate(1);`,
+ },
+ select: {
+ keyword: `select`,
+ src: `_ = select(1, 2, false);`,
+ },
+ sign: {
+ keyword: `sign`,
+ src: `_ = sign(1);`,
+ },
+ sin: {
+ keyword: `sin`,
+ src: `_ = sin(2);`,
+ },
+ sinh: {
+ keyword: `sinh`,
+ src: `_ = sinh(3);`,
+ },
+ smoothstep: {
+ keyword: `smoothstep`,
+ src: `_ = smoothstep(1, 2, 3);`,
+ },
+ sqrt: {
+ keyword: `sqrt`,
+ src: `_ = sqrt(24);`,
+ },
+ step: {
+ keyword: `step`,
+ src: `_ = step(4, 5);`,
+ },
+ tan: {
+ keyword: `tan`,
+ src: `_ = tan(2);`,
+ },
+ tanh: {
+ keyword: `tanh`,
+ src: `_ = tanh(2);`,
+ },
+ transpose: {
+ keyword: `transpose`,
+ src: `_ = transpose(mat2x2(1, 2, 3, 4));`,
+ },
+ trunc: {
+ keyword: `trunc`,
+ src: `_ = trunc(2);`,
+ },
+ u32: {
+ keyword: `u32`,
+ src: `_ = u32(1i);`,
+ },
+ unpack2x16snorm: {
+ keyword: `unpack2x16snorm`,
+ src: `_ = unpack2x16snorm(2);`,
+ },
+ unpack2x16unorm: {
+ keyword: `unpack2x16unorm`,
+ src: `_ = unpack2x16unorm(2);`,
+ },
+ unpack2x16float: {
+ keyword: `unpack2x16float`,
+ src: `_ = unpack2x16float(2);`,
+ },
+ unpack4x8snorm: {
+ keyword: `unpack4x8snorm`,
+ src: `_ = unpack4x8snorm(4);`,
+ },
+ unpack4x8unorm: {
+ keyword: `unpack4x8unorm`,
+ src: `_ = unpack4x8unorm(4);`,
+ },
+ unpack4xI8: {
+ keyword: `unpack4xI8`,
+ src: `_ = unpack4xI8(4);`,
+ },
+ unpack4xU8: {
+ keyword: `unpack4xU8`,
+ src: `_ = unpack4xU8(4);`,
+ },
+ vec2_templated: {
+ keyword: `vec2`,
+ src: `_ = vec2<f32>(1, 2);`,
+ },
+ vec2: {
+ keyword: `vec2`,
+ src: `_ = vec2(1, 2);`,
+ },
+ vec3_templated: {
+ keyword: `vec3`,
+ src: `_ = vec3<f32>(1, 2, 3);`,
+ },
+ vec3: {
+ keyword: `vec3`,
+ src: `_ = vec3(1, 2, 3);`,
+ },
+ vec4_templated: {
+ keyword: `vec4`,
+ src: `_ = vec4<f32>(1, 2, 3, 4);`,
+ },
+ vec4: {
+ keyword: `vec4`,
+ src: `_ = vec4(1, 2, 3, 4);`,
+ },
+};
+
+g.test('shadow_hides_builtin')
+ .desc(`Test that shadows hide builtins.`)
+ .params(u =>
+ u
+ .combine('inject', ['none', 'function', 'sibling', 'module'] as const)
+ .beginSubcases()
+ .combine('builtin', keysOf(kTests))
+ )
+ .fn(t => {
+ const data = kTests[t.params.builtin];
+ const local = `let ${data.keyword} = 4;`;
+
+ const module_shadow = t.params.inject === 'module' ? `var<private> ${data.keyword} : i32;` : ``;
+ const sibling_func = t.params.inject === 'sibling' ? local : ``;
+ const func = t.params.inject === 'function' ? local : ``;
+
+ const code = `
+struct Data {
+ rt_arr: array<i32>,
+}
+@group(0) @binding(0) var<storage> placeholder: Data;
+
+${module_shadow}
+
+fn sibling() {
+ ${sibling_func}
+}
+
+@fragment
+fn main() -> @location(0) vec4f {
+ ${func}
+ ${data.src}
+ return vec4f(1);
+}
+ `;
+
+ const pass = t.params.inject === 'none' || t.params.inject === 'sibling';
+ t.expectCompileResult(pass, code);
+ });
+
+const kFloat16Tests = {
+ f16: {
+ keyword: `f16`,
+ src: `_ = f16(2);`,
+ },
+};
+
+g.test('shadow_hides_builtin_f16')
+ .desc(`Test that shadows hide builtins when shader-f16 is enabled.`)
+ .params(u =>
+ u
+ .combine('inject', ['none', 'function', 'sibling', 'module'] as const)
+ .beginSubcases()
+ .combine('builtin', keysOf(kFloat16Tests))
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+ })
+ .fn(t => {
+ const data = kFloat16Tests[t.params.builtin];
+ const local = `let ${data.keyword} = 4;`;
+
+ const module_shadow = t.params.inject === 'module' ? `var<private> ${data.keyword} : f16;` : ``;
+ const sibling_func = t.params.inject === 'sibling' ? local : ``;
+ const func = t.params.inject === 'function' ? local : ``;
+
+ const code = `
+enable f16;
+
+${module_shadow}
+
+fn sibling() {
+ ${sibling_func}
+}
+
+@vertex
+fn vtx() -> @builtin(position) vec4f {
+ ${func}
+ ${data.src}
+ return vec4f(1);
+}
+ `;
+ const pass = t.params.inject === 'none' || t.params.inject === 'sibling';
+ t.expectCompileResult(pass, code);
+ });
+
+const kTextureTypeTests = {
+ texture_1d: {
+ keyword: `texture_1d`,
+ src: `var t: texture_1d<f32>;`,
+ },
+ texture_2d: {
+ keyword: `texture_2d`,
+ src: `var t: texture_2d<f32>;`,
+ },
+ texture_2d_array: {
+ keyword: `texture_2d_array`,
+ src: `var t: texture_2d_array<f32>;`,
+ },
+ texture_3d: {
+ keyword: `texture_3d`,
+ src: `var t: texture_3d<f32>;`,
+ },
+ texture_cube: {
+ keyword: `texture_cube`,
+ src: `var t: texture_cube<f32>;`,
+ },
+ texture_cube_array: {
+ keyword: `texture_cube_array`,
+ src: `var t: texture_cube_array<f32>;`,
+ },
+ texture_multisampled_2d: {
+ keyword: `texture_multisampled_2d`,
+ src: `var t: texture_multisampled_2d<f32>;`,
+ },
+ texture_depth_multisampled_2d: {
+ keyword: `texture_depth_multisampled_2d`,
+ src: `var t: texture_depth_multisampled_2d;`,
+ },
+ texture_external: {
+ keyword: `texture_external`,
+ src: `var t: texture_external;`,
+ },
+ texture_storage_1d: {
+ keyword: `texture_storage_1d`,
+ src: `var t: texture_storage_1d<rgba8unorm, read_write>;`,
+ },
+ texture_storage_2d: {
+ keyword: `texture_storage_2d`,
+ src: `var t: texture_storage_2d<rgba8unorm, read_write>;`,
+ },
+ texture_storage_2d_array: {
+ keyword: `texture_storage_2d_array`,
+ src: `var t: texture_storage_2d_array<rgba8unorm, read_write>;`,
+ },
+ texture_storage_3d: {
+ keyword: `texture_storage_3d`,
+ src: `var t: texture_storage_3d<rgba8unorm, read_write>;`,
+ },
+ texture_depth_2d: {
+ keyword: `texture_depth_2d`,
+ src: `var t: texture_depth_2d;`,
+ },
+ texture_depth_2d_array: {
+ keyword: `texture_depth_2d_array`,
+ src: `var t: texture_depth_2d_array;`,
+ },
+ texture_depth_cube: {
+ keyword: `texture_depth_cube`,
+ src: `var t: texture_depth_cube;`,
+ },
+ texture_depth_cube_array: {
+ keyword: `texture_depth_cube_array`,
+ src: `var t: texture_depth_cube_array;`,
+ },
+ sampler: {
+ keyword: `sampler`,
+ src: `var s: sampler;`,
+ },
+ sampler_comparison: {
+ keyword: `sampler_comparison`,
+ src: `var s: sampler_comparison;`,
+ },
+};
+
+g.test('shadow_hides_builtin_handle_type')
+ .desc(`Test that shadows hide builtins when handle address space types are used.`)
+ .params(u =>
+ u
+ .combine('inject', ['none', 'function', 'module'] as const)
+ .beginSubcases()
+ .combine('builtin', keysOf(kTextureTypeTests))
+ )
+ .fn(t => {
+ const data = kTextureTypeTests[t.params.builtin];
+ const local = `let ${data.keyword} = 4;`;
+
+ const module_shadow = t.params.inject === 'module' ? `var<private> ${data.keyword} : f32;` : ``;
+ const func = t.params.inject === 'function' ? local : ``;
+
+ const code = `
+${module_shadow}
+@group(0) @binding(0) ${data.src}
+
+fn func() {
+ ${func}
+}
+ `;
+ const pass = t.params.inject === 'none' || t.params.inject === 'function';
+ t.expectCompileResult(pass, code);
+ });
+
+const kTextureTests = {
+ textureDimensions: {
+ keyword: `textureDimensions`,
+ src: `_ = textureDimensions(t_2d);`,
+ },
+ textureGather: {
+ keyword: `textureGather`,
+ src: `_ = textureGather(1, t_2d, s, vec2(1, 2));`,
+ },
+ textureGatherCompare: {
+ keyword: `textureGatherCompare`,
+ src: `_ = textureGatherCompare(t_2d_depth, sc, vec2(1, 2), 3);`,
+ },
+ textureLoad: {
+ keyword: `textureLoad`,
+ src: `_ = textureLoad(t_2d, vec2(1, 2), 1);`,
+ },
+ textureNumLayers: {
+ keyword: `textureNumLayers`,
+ src: `_ = textureNumLayers(t_2d_array);`,
+ },
+ textureNumLevels: {
+ keyword: `textureNumLevels`,
+ src: `_ = textureNumLevels(t_2d);`,
+ },
+ textureNumSamples: {
+ keyword: `textureNumSamples`,
+ src: `_ = textureNumSamples(t_2d_ms);`,
+ },
+ textureSample: {
+ keyword: `textureSample`,
+ src: `_ = textureSample(t_2d, s, vec2(1, 2));`,
+ },
+ textureSampleBias: {
+ keyword: `textureSampleBias`,
+ src: `_ = textureSampleBias(t_2d, s, vec2(1, 2), 2);`,
+ },
+ textureSampleCompare: {
+ keyword: `textureSampleCompare`,
+ src: `_ = textureSampleCompare(t_2d_depth, sc, vec2(1, 2), 2);`,
+ },
+ textureSampleCompareLevel: {
+ keyword: `textureSampleCompareLevel`,
+ src: `_ = textureSampleCompareLevel(t_2d_depth, sc, vec2(1, 2), 3, vec2(1, 2));`,
+ },
+ textureSampleGrad: {
+ keyword: `textureSampleGrad`,
+ src: `_ = textureSampleGrad(t_2d, s, vec2(1, 2), vec2(1, 2), vec2(1, 2));`,
+ },
+ textureSampleLevel: {
+ keyword: `textureSampleLevel`,
+ src: `_ = textureSampleLevel(t_2d, s, vec2(1, 2), 3);`,
+ },
+ textureSampleBaseClampToEdge: {
+ keyword: `textureSampleBaseClampToEdge`,
+ src: `_ = textureSampleBaseClampToEdge(t_2d, s, vec2(1, 2));`,
+ },
+};
+
+g.test('shadow_hides_builtin_texture')
+ .desc(`Test that shadows hide texture builtins.`)
+ .params(u =>
+ u
+ .combine('inject', ['none', 'function', 'sibling', 'module'] as const)
+ .beginSubcases()
+ .combine('builtin', keysOf(kTextureTests))
+ )
+ .fn(t => {
+ const data = kTextureTests[t.params.builtin];
+ const local = `let ${data.keyword} = 4;`;
+
+ const module_shadow = t.params.inject === 'module' ? `var<private> ${data.keyword} : i32;` : ``;
+ const sibling_func = t.params.inject === 'sibling' ? local : ``;
+ const func = t.params.inject === 'function' ? local : ``;
+
+ const code = `
+@group(0) @binding(0) var t_2d: texture_2d<f32>;
+@group(0) @binding(1) var t_2d_depth: texture_depth_2d;
+@group(0) @binding(2) var t_2d_array: texture_2d_array<f32>;
+@group(0) @binding(3) var t_2d_ms: texture_multisampled_2d<f32>;
+
+@group(1) @binding(0) var s: sampler;
+@group(1) @binding(1) var sc: sampler_comparison;
+
+${module_shadow}
+
+fn sibling() {
+ ${sibling_func}
+}
+
+@fragment
+fn main() -> @location(0) vec4f {
+ ${func}
+ ${data.src}
+ return vec4f(1);
+}
+ `;
+
+ const pass = t.params.inject === 'none' || t.params.inject === 'sibling';
+ t.expectCompileResult(pass, code);
+ });
+
+g.test('shadow_hides_builtin_atomic_type')
+ .desc(`Test that shadows hide builtins when atomic types are used.`)
+ .params(u => u.combine('inject', ['none', 'function', 'module'] as const).beginSubcases())
+ .fn(t => {
+ const local = `let atomic = 4;`;
+ const module_shadow = t.params.inject === 'module' ? `var<private> atomic: i32;` : ``;
+ const func = t.params.inject === 'function' ? local : ``;
+
+ const code = `
+${module_shadow}
+
+var<workgroup> val: atomic<i32>;
+
+fn func() {
+ ${func}
+}
+ `;
+ const pass = t.params.inject === 'none' || t.params.inject === 'function';
+ t.expectCompileResult(pass, code);
+ });
+
+const kAtomicTests = {
+ atomicLoad: {
+ keyword: `atomicLoad`,
+ src: `_ = atomicLoad(&a);`,
+ },
+ atomicStore: {
+ keyword: `atomicStore`,
+ src: `atomicStore(&a, 1);`,
+ },
+ atomicAdd: {
+ keyword: `atomicAdd`,
+ src: `_ = atomicAdd(&a, 1);`,
+ },
+ atomicSub: {
+ keyword: `atomicSub`,
+ src: `_ = atomicSub(&a, 1);`,
+ },
+ atomicMax: {
+ keyword: `atomicMax`,
+ src: `_ = atomicMax(&a, 1);`,
+ },
+ atomicMin: {
+ keyword: `atomicMin`,
+ src: `_ = atomicMin(&a, 1);`,
+ },
+ atomicAnd: {
+ keyword: `atomicAnd`,
+ src: `_ = atomicAnd(&a, 1);`,
+ },
+ atomicOr: {
+ keyword: `atomicOr`,
+ src: `_ = atomicOr(&a, 1);`,
+ },
+ atomicXor: {
+ keyword: `atomicXor`,
+ src: `_ = atomicXor(&a, 1);`,
+ },
+};
+
+g.test('shadow_hides_builtin_atomic')
+ .desc(`Test that shadows hide builtin atomic methods.`)
+ .params(u =>
+ u
+ .combine('inject', ['none', 'function', 'sibling', 'module'] as const)
+ .beginSubcases()
+ .combine('builtin', keysOf(kAtomicTests))
+ )
+ .fn(t => {
+ const data = kAtomicTests[t.params.builtin];
+ const local = `let ${data.keyword} = 4;`;
+
+ const module_shadow = t.params.inject === 'module' ? `var<private> ${data.keyword} : i32;` : ``;
+ const sibling_func = t.params.inject === 'sibling' ? local : ``;
+ const func = t.params.inject === 'function' ? local : ``;
+
+ const code = `
+var<workgroup> a: atomic<i32>;
+
+${module_shadow}
+
+fn sibling() {
+ ${sibling_func}
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ ${func}
+ ${data.src}
+}
+ `;
+
+ const pass = t.params.inject === 'none' || t.params.inject === 'sibling';
+ t.expectCompileResult(pass, code);
+ });
+
+const kBarrierTests = {
+ storageBarrier: {
+ keyword: `storageBarrier`,
+ src: `storageBarrier();`,
+ },
+ textureBarrier: {
+ keyword: `textureBarrier`,
+ src: `textureBarrier();`,
+ },
+ workgroupBarrier: {
+ keyword: `workgroupBarrier`,
+ src: `workgroupBarrier();`,
+ },
+ workgroupUniformLoad: {
+ keyword: `workgroupUniformLoad`,
+ src: `_ = workgroupUniformLoad(&u);`,
+ },
+};
+
+g.test('shadow_hides_builtin_barriers')
+ .desc(`Test that shadows hide builtin barrier methods.`)
+ .params(u =>
+ u
+ .combine('inject', ['none', 'function', 'sibling', 'module'] as const)
+ .beginSubcases()
+ .combine('builtin', keysOf(kBarrierTests))
+ )
+ .fn(t => {
+ const data = kBarrierTests[t.params.builtin];
+ const local = `let ${data.keyword} = 4;`;
+
+ const module_shadow = t.params.inject === 'module' ? `var<private> ${data.keyword} : i32;` : ``;
+ const sibling_func = t.params.inject === 'sibling' ? local : ``;
+ const func = t.params.inject === 'function' ? local : ``;
+
+ const code = `
+var<workgroup> u: u32;
+
+${module_shadow}
+
+fn sibling() {
+ ${sibling_func}
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ ${func}
+ ${data.src}
+}
+ `;
+
+ const pass = t.params.inject === 'none' || t.params.inject === 'sibling';
+ t.expectCompileResult(pass, code);
+ });
+
+const kAccessModeTests = {
+ read: {
+ keyword: `read`,
+ src: `var<storage, read> a: i32;`,
+ },
+ read_write: {
+ keyword: `read_write`,
+ src: `var<storage, read_write> a: i32;`,
+ },
+ write: {
+ keyword: `write`,
+ src: `var t: texture_storage_1d<rgba8unorm, write>;`,
+ },
+};
+
+g.test('shadow_hides_access_mode')
+ .desc(`Test that shadows hide access modes.`)
+ .params(u =>
+ u
+ .combine('inject', ['none', 'function', 'module'] as const)
+ .beginSubcases()
+ .combine('builtin', keysOf(kAccessModeTests))
+ )
+ .fn(t => {
+ const data = kAccessModeTests[t.params.builtin];
+ const local = `let ${data.keyword} = 4;`;
+
+ const module_shadow = t.params.inject === 'module' ? `var<private> ${data.keyword} : i32;` : ``;
+ const func = t.params.inject === 'function' ? local : ``;
+
+ const code = `
+${module_shadow}
+
+@group(0) @binding(0) ${data.src}
+
+@compute @workgroup_size(1)
+fn main() {
+ ${func}
+}
+ `;
+
+ const pass = t.params.inject === 'none' || t.params.inject === 'function';
+ t.expectCompileResult(pass, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/statement_behavior.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/statement_behavior.spec.ts
new file mode 100644
index 0000000000..1acfd01d8b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/statement_behavior.spec.ts
@@ -0,0 +1,143 @@
+export const description = `
+Test statement behavior analysis.
+
+Functions must have a behavior of {Return}, {Next}, or {Return, Next}.
+Functions with a return type must have a behavior of {Return}.
+
+Each statement in the function must be valid according to the table.
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kInvalidStatements = {
+ break: `break`,
+ break_if: `break if true`,
+ continue: `continue`,
+ loop1: `loop { }`,
+ loop2: `loop { continuing { } }`,
+ loop3: `loop { continue; continuing { } }`,
+ loop4: `loop { continuing { break; } }`,
+ loop5: `loop { continuing { continue; } }`,
+ loop6: `loop { continuing { return; } }`,
+ loop7: `loop { continue; break; }`,
+ loop8: `loop { continuing { break if true; return; } }`,
+ for1: `for (;;) { }`,
+ for2: `for (var i = 0; ; i++) { }`,
+ for3: `for (;; break) { }`,
+ for4: `for (;; continue ) { }`,
+ for5: `for (;; return ) { }`,
+ for6: `for (;;) { continue; break; }`,
+ // while loops always have break in their behaviors.
+ switch1: `switch (1) { case 1 { } }`,
+ sequence1: `return; loop { }`,
+ compound1: `{ loop { } }`,
+};
+
+g.test('invalid_statements')
+ .desc('Test statements with invalid behaviors')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#behaviors-rules')
+ .params(u => u.combine('body', keysOf(kInvalidStatements)))
+ .fn(t => {
+ const body = kInvalidStatements[t.params.body];
+ const code = `fn foo() {
+ ${body};
+ }`;
+ t.expectCompileResult(false, code);
+ });
+
+const kValidStatements = {
+ empty: ``,
+ const_assert: `const_assert true`,
+ let: `let x = 1`,
+ var1: `var x = 1`,
+ var2: `var x : i32`,
+ assign: `v = 1`,
+ phony_assign: `_ = 1`,
+ compound_assign: `v += 1`,
+ return: `return`,
+ discard: `discard`,
+ function_call1: `bar()`,
+ function_call2: `workgroupBarrier()`,
+
+ if1: `if true { } else { }`,
+ if2: `if true { }`,
+
+ break1: `loop { break; }`,
+ break2: `loop { if false { break; } }`,
+ break_if: `loop { continuing { break if false; } }`,
+
+ continue1: `loop { continue; continuing { break if true; } }`,
+
+ loop1: `loop { break; }`,
+ loop2: `loop { break; continuing { } }`,
+ loop3: `loop { continue; continuing { break if true; } }`,
+ loop4: `loop { break; continue; }`,
+
+ for1: `for (; true; ) { }`,
+ for2: `for (;;) { break; }`,
+ for3: `for (;true;) { continue; }`,
+
+ while1: `while true { }`,
+ while2: `while true { continue; }`,
+ while3: `while true { continue; break; }`,
+
+ switch1: `switch 1 { default { } }`,
+ swtich2: `switch 1 { case 1 { } default { } }`,
+ switch3: `switch 1 { default { break; } }`,
+ switch4: `switch 1 { default { } case 1 { break; } }`,
+
+ sequence1: `return; let x = 1`,
+ sequence2: `if true { } let x = 1`,
+ sequence3: `switch 1 { default { break; return; } }`,
+
+ compound1: `{ }`,
+ compound2: `{ loop { break; } if true { return; } }`,
+};
+
+g.test('valid_statements')
+ .desc('Test statements with valid behaviors')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#behaviors-rules')
+ .params(u => u.combine('body', keysOf(kValidStatements)))
+ .fn(t => {
+ const body = kValidStatements[t.params.body];
+ const code = `
+ var<private> v : i32;
+ fn bar() { }
+ fn foo() {
+ ${body};
+ }`;
+ t.expectCompileResult(true, code);
+ });
+
+const kInvalidFunctions = {
+ next_for_type: `fn foo() -> bool { }`,
+ next_return_for_type: `fn foo() -> bool { if true { return true; } }`,
+};
+
+g.test('invalid_functions')
+ .desc('Test functions with invalid behaviors')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#behaviors-rules')
+ .params(u => u.combine('function', keysOf(kInvalidFunctions)))
+ .fn(t => {
+ const func = kInvalidFunctions[t.params.function];
+ t.expectCompileResult(false, func);
+ });
+
+const kValidFunctions = {
+ empty: `fn foo() { }`,
+ next_return: `fn foo() { if true { return; } }`,
+ no_final_return: `fn foo() -> bool { if true { return true; } else { return false; } }`,
+};
+
+g.test('valid_functions')
+ .desc('Test functions with valid behaviors')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#behaviors-rules')
+ .params(u => u.combine('function', keysOf(kValidFunctions)))
+ .fn(t => {
+ const func = kValidFunctions[t.params.function];
+ t.expectCompileResult(true, func);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/binding.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/binding.spec.ts
index 2462025016..ae1b78d931 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/binding.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/binding.spec.ts
@@ -124,17 +124,3 @@ var<storage> a: i32;
}`;
t.expectCompileResult(false, code);
});
-
-g.test('binding_without_group')
- .desc(`Test validation of binding without group`)
- .fn(t => {
- const code = `
-@binding(1)
-var<storage> a: i32;
-
-@workgroup_size(1, 1, 1)
-@compute fn main() {
- _ = a;
-}`;
- t.expectCompileResult(false, code);
- });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/builtins.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/builtins.spec.ts
index 4b32d05539..d99eb82279 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/builtins.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/builtins.spec.ts
@@ -103,9 +103,9 @@ g.test('type')
.params(u =>
u
.combineWithParams(kBuiltins)
- .combine('use_struct', [true, false] as const)
- .combine('target_type', kTestTypes)
.beginSubcases()
+ .combine('target_type', kTestTypes)
+ .combine('use_struct', [true, false] as const)
)
.fn(t => {
let code = '';
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/group.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/group.spec.ts
index 4d37c43a99..cdbf64201a 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/group.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/group.spec.ts
@@ -124,17 +124,3 @@ var<storage> a: i32;
}`;
t.expectCompileResult(false, code);
});
-
-g.test('group_without_binding')
- .desc(`Test validation of group without binding`)
- .fn(t => {
- const code = `
-@group(1)
-var<storage> a: i32;
-
-@workgroup_size(1, 1, 1)
-@compute fn main() {
- _ = a;
-}`;
- t.expectCompileResult(false, code);
- });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/group_and_binding.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/group_and_binding.spec.ts
index 08b4b2738a..5a4168267b 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/group_and_binding.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/group_and_binding.spec.ts
@@ -95,12 +95,12 @@ g.test('single_entry_point')
.combine('stage', ['vertex', 'fragment', 'compute'] as const)
.combine('a_kind', kResourceKindsA)
.combine('b_kind', kResourceKindsB)
+ .combine('usage', ['direct', 'transitive'] as const)
+ .beginSubcases()
.combine('a_group', [0, 3] as const)
.combine('b_group', [0, 3] as const)
.combine('a_binding', [0, 3] as const)
.combine('b_binding', [0, 3] as const)
- .combine('usage', ['direct', 'transitive'] as const)
- .beginSubcases()
)
.fn(t => {
const resourceA = kResourceEmitters.get(t.params.a_kind) as ResourceDeclarationEmitter;
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/layout_constraints.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/layout_constraints.spec.ts
new file mode 100644
index 0000000000..7db1eefbe0
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/layout_constraints.spec.ts
@@ -0,0 +1,543 @@
+export const description = `Validation of address space layout constraints`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+interface LayoutCase {
+ type: string;
+ decls?: string;
+ validity: boolean | 'non-uniform' | 'non-interface' | 'storage' | 'atomic';
+ f16?: boolean;
+}
+
+const kLayoutCases: Record<string, LayoutCase> = {
+ // Scalars
+ u32: {
+ type: 'u32',
+ validity: true,
+ },
+ i32: {
+ type: 'i32',
+ validity: true,
+ },
+ f32: {
+ type: 'f32',
+ validity: true,
+ },
+ f16: {
+ type: 'f16',
+ validity: true,
+ f16: true,
+ },
+ bool: {
+ type: 'bool',
+ validity: 'non-interface',
+ },
+
+ // Vectors
+ vec2u: {
+ type: 'vec2u',
+ validity: true,
+ },
+ vec3u: {
+ type: 'vec3u',
+ validity: true,
+ },
+ vec4u: {
+ type: 'vec4u',
+ validity: true,
+ },
+ vec2i: {
+ type: 'vec2i',
+ validity: true,
+ },
+ vec3i: {
+ type: 'vec3i',
+ validity: true,
+ },
+ vec4i: {
+ type: 'vec4i',
+ validity: true,
+ },
+ vec2f: {
+ type: 'vec2f',
+ validity: true,
+ },
+ vec3f: {
+ type: 'vec3f',
+ validity: true,
+ },
+ vec4f: {
+ type: 'vec4f',
+ validity: true,
+ },
+ vec2h: {
+ type: 'vec2h',
+ validity: true,
+ f16: true,
+ },
+ vec3h: {
+ type: 'vec3h',
+ validity: true,
+ f16: true,
+ },
+ vec4h: {
+ type: 'vec4h',
+ validity: true,
+ f16: true,
+ },
+ vec2b: {
+ type: 'vec2<bool>',
+ validity: 'non-interface',
+ },
+ vec3b: {
+ type: 'vec3<bool>',
+ validity: 'non-interface',
+ },
+ vec4b: {
+ type: 'vec4<bool>',
+ validity: 'non-interface',
+ },
+
+ // Matrices
+ mat2x2f: {
+ type: 'mat2x2f',
+ validity: true,
+ },
+ mat2x3f: {
+ type: 'mat2x3f',
+ validity: true,
+ },
+ mat2x4f: {
+ type: 'mat2x4f',
+ validity: true,
+ },
+ mat3x2f: {
+ type: 'mat3x2f',
+ validity: true,
+ },
+ mat3x3f: {
+ type: 'mat3x3f',
+ validity: true,
+ },
+ mat3x4f: {
+ type: 'mat3x4f',
+ validity: true,
+ },
+ mat4x2f: {
+ type: 'mat4x2f',
+ validity: true,
+ },
+ mat4x3f: {
+ type: 'mat4x3f',
+ validity: true,
+ },
+ mat4x4f: {
+ type: 'mat4x4f',
+ validity: true,
+ },
+ mat2x2h: {
+ type: 'mat2x2h',
+ validity: true,
+ f16: true,
+ },
+ mat2x3h: {
+ type: 'mat2x3h',
+ validity: true,
+ f16: true,
+ },
+ mat2x4h: {
+ type: 'mat2x4h',
+ validity: true,
+ f16: true,
+ },
+ mat3x2h: {
+ type: 'mat3x2h',
+ validity: true,
+ f16: true,
+ },
+ mat3x3h: {
+ type: 'mat3x3h',
+ validity: true,
+ f16: true,
+ },
+ mat3x4h: {
+ type: 'mat3x4h',
+ validity: true,
+ f16: true,
+ },
+ mat4x2h: {
+ type: 'mat4x2h',
+ validity: true,
+ f16: true,
+ },
+ mat4x3h: {
+ type: 'mat4x3h',
+ validity: true,
+ f16: true,
+ },
+ mat4x4h: {
+ type: 'mat4x4h',
+ validity: true,
+ f16: true,
+ },
+
+ // Atomics
+ atomic_u32: {
+ type: 'atomic<u32>',
+ validity: 'atomic',
+ },
+ atomic_i32: {
+ type: 'atomic<i32>',
+ validity: 'atomic',
+ },
+
+ // Sized arrays
+ array_u32: {
+ type: 'array<u32, 16>',
+ validity: 'non-uniform',
+ },
+ array_i32: {
+ type: 'array<i32, 16>',
+ validity: 'non-uniform',
+ },
+ array_f32: {
+ type: 'array<f32, 16>',
+ validity: 'non-uniform',
+ },
+ array_f16: {
+ type: 'array<f16, 16>',
+ validity: 'non-uniform',
+ f16: true,
+ },
+ array_bool: {
+ type: 'array<bool, 16>',
+ validity: 'non-interface',
+ },
+ array_vec2f: {
+ type: 'array<vec2f, 16>',
+ validity: 'non-uniform',
+ },
+ array_vec3f: {
+ type: 'array<vec3f, 16>',
+ validity: true,
+ },
+ array_vec4f: {
+ type: 'array<vec4f, 16>',
+ validity: true,
+ },
+ array_vec2h: {
+ type: 'array<vec2h, 16>',
+ validity: 'non-uniform',
+ f16: true,
+ },
+ array_vec3h: {
+ type: 'array<vec3h, 16>',
+ validity: 'non-uniform',
+ f16: true,
+ },
+ array_vec4h: {
+ type: 'array<vec4h, 16>',
+ validity: 'non-uniform',
+ f16: true,
+ },
+ array_vec2b: {
+ type: 'array<vec2<bool>, 16>',
+ validity: 'non-interface',
+ },
+ array_vec3b: {
+ type: 'array<vec3<bool>, 16>',
+ validity: 'non-interface',
+ },
+ array_vec4b: {
+ type: 'array<vec4<bool>, 16>',
+ validity: 'non-interface',
+ },
+ array_mat2x2f: {
+ type: 'array<mat2x2f, 16>',
+ validity: true,
+ },
+ array_mat2x4f: {
+ type: 'array<mat2x4f, 16>',
+ validity: true,
+ },
+ array_mat4x2f: {
+ type: 'array<mat4x2f, 16>',
+ validity: true,
+ },
+ array_mat4x4f: {
+ type: 'array<mat4x4f, 16>',
+ validity: true,
+ },
+ array_mat2x2h: {
+ type: 'array<mat2x2h, 16>',
+ validity: 'non-uniform',
+ f16: true,
+ },
+ array_mat2x4h: {
+ type: 'array<mat2x4h, 16>',
+ validity: true,
+ f16: true,
+ },
+ array_mat3x2h: {
+ type: 'array<mat3x2h, 16>',
+ validity: 'non-uniform',
+ f16: true,
+ },
+ array_mat4x2h: {
+ type: 'array<mat4x2h, 16>',
+ validity: true,
+ f16: true,
+ },
+ array_mat4x4h: {
+ type: 'array<mat4x4h, 16>',
+ validity: true,
+ f16: true,
+ },
+ array_atomic: {
+ type: 'array<atomic<u32>, 16>',
+ validity: 'atomic',
+ },
+
+ // Runtime arrays
+ runtime_array_u32: {
+ type: 'array<u32>',
+ validity: 'storage',
+ },
+ runtime_array_i32: {
+ type: 'array<i32>',
+ validity: 'storage',
+ },
+ runtime_array_f32: {
+ type: 'array<f32>',
+ validity: 'storage',
+ },
+ runtime_array_f16: {
+ type: 'array<f16>',
+ validity: 'storage',
+ f16: true,
+ },
+ runtime_array_bool: {
+ type: 'array<bool>',
+ validity: false,
+ },
+ runtime_array_vec2f: {
+ type: 'array<vec2f>',
+ validity: 'storage',
+ },
+ runtime_array_vec3f: {
+ type: 'array<vec3f>',
+ validity: 'storage',
+ },
+ runtime_array_vec4f: {
+ type: 'array<vec4f>',
+ validity: 'storage',
+ },
+ runtime_array_vec2h: {
+ type: 'array<vec2h>',
+ validity: 'storage',
+ f16: true,
+ },
+ runtime_array_vec3h: {
+ type: 'array<vec3h>',
+ validity: 'storage',
+ f16: true,
+ },
+ runtime_array_vec4h: {
+ type: 'array<vec4h>',
+ validity: 'storage',
+ f16: true,
+ },
+ runtime_array_vec2b: {
+ type: 'array<vec2<bool>>',
+ validity: false,
+ },
+ runtime_array_vec3b: {
+ type: 'array<vec3<bool>>',
+ validity: false,
+ },
+ runtime_array_vec4b: {
+ type: 'array<vec4<bool>>',
+ validity: false,
+ },
+ runtime_array_mat2x2f: {
+ type: 'array<mat2x2f>',
+ validity: 'storage',
+ },
+ runtime_array_mat2x4f: {
+ type: 'array<mat2x4f>',
+ validity: 'storage',
+ },
+ runtime_array_mat4x2f: {
+ type: 'array<mat4x2f>',
+ validity: 'storage',
+ },
+ runtime_array_mat4x4f: {
+ type: 'array<mat4x4f>',
+ validity: 'storage',
+ },
+ runtime_array_mat2x2h: {
+ type: 'array<mat2x2h>',
+ validity: 'storage',
+ f16: true,
+ },
+ runtime_array_mat2x4h: {
+ type: 'array<mat2x4h>',
+ validity: 'storage',
+ f16: true,
+ },
+ runtime_array_mat3x2h: {
+ type: 'array<mat3x2h>',
+ validity: 'storage',
+ f16: true,
+ },
+ runtime_array_mat4x2h: {
+ type: 'array<mat4x2h>',
+ validity: 'storage',
+ f16: true,
+ },
+ runtime_array_mat4x4h: {
+ type: 'array<mat4x4h>',
+ validity: 'storage',
+ f16: true,
+ },
+ runtime_array_atomic: {
+ type: 'array<atomic<u32>>',
+ validity: 'storage',
+ },
+
+ // Structs (and arrays of structs)
+ array_struct_u32: {
+ type: 'array<S, 16>',
+ decls: 'struct S { x : u32 }',
+ validity: 'non-uniform',
+ },
+ array_struct_u32_size16: {
+ type: 'array<S, 16>',
+ decls: 'struct S { @size(16) x : u32 }',
+ validity: true,
+ },
+ array_struct_vec2f: {
+ type: 'array<S, 16>',
+ decls: 'struct S { x : vec2f }',
+ validity: 'non-uniform',
+ },
+ array_struct_vec2h: {
+ type: 'array<S, 16>',
+ decls: 'struct S { x : vec2h }',
+ validity: 'non-uniform',
+ f16: true,
+ },
+ array_struct_vec2h_align16: {
+ type: 'array<S, 16>',
+ decls: 'struct S { @align(16) x : vec2h }',
+ validity: true,
+ f16: true,
+ },
+ size_too_small: {
+ type: 'S',
+ decls: 'struct S { @size(2) x : u32 }',
+ validity: false,
+ },
+ struct_padding: {
+ type: 'S',
+ decls: `struct T { x : u32 }
+ struct S { t : T, x : u32 }`,
+ validity: 'non-uniform',
+ },
+ struct_array_u32: {
+ type: 'S',
+ decls: 'struct S { x : array<u32, 4> }',
+ validity: 'non-uniform',
+ },
+ struct_runtime_array_u32: {
+ type: 'S',
+ decls: 'struct S { x : array<u32> }',
+ validity: 'storage',
+ },
+ array_struct_size_5: {
+ type: 'array<S, 16>',
+ decls: 'struct S { @size(5) x : u32, y : u32 }',
+ validity: 'non-uniform',
+ },
+ array_struct_size_5x2: {
+ type: 'array<S, 16>',
+ decls: 'struct S { @size(5) x : u32, @size(5) y : u32 }',
+ validity: true,
+ },
+ struct_size_5: {
+ type: 'S',
+ decls: `struct T { @size(5) x : u32 }
+ struct S { x : u32, y : T }`,
+ validity: 'non-uniform',
+ },
+ struct_size_5_align16: {
+ type: 'S',
+ decls: `struct T { @align(16) @size(5) x : u32 }
+ struct S { x : u32, y : T }`,
+ validity: true,
+ },
+};
+
+g.test('layout_constraints')
+ .desc('Test address space layout constraints')
+ .params(u =>
+ u
+ .combine('case', keysOf(kLayoutCases))
+ .beginSubcases()
+ .combine('aspace', ['storage', 'uniform', 'function', 'private', 'workgroup'] as const)
+ )
+ .beforeAllSubcases(t => {
+ const testcase = kLayoutCases[t.params.case];
+ if (testcase.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const testcase = kLayoutCases[t.params.case];
+ const decls = testcase.decls !== undefined ? testcase.decls : '';
+ let code = `
+${testcase.f16 ? 'enable f16;' : ''}
+${decls}
+
+`;
+
+ switch (t.params.aspace) {
+ case 'storage':
+ code += `@group(0) @binding(0) var<storage, read_write> v : ${testcase.type};\n`;
+ break;
+ case 'uniform':
+ code += `@group(0) @binding(0) var<uniform> v : ${testcase.type};\n`;
+ break;
+ case 'workgroup':
+ code += `var<workgroup> v : ${testcase.type};\n`;
+ break;
+ case 'private':
+ code += `var<private> v : ${testcase.type};\n`;
+ break;
+ default:
+ break;
+ }
+
+ code += `@compute @workgroup_size(1,1,1)
+ fn main() {
+ `;
+
+ if (t.params.aspace === 'function') {
+ code += `var v : ${testcase.type};\n`;
+ }
+ code += `}\n`;
+
+ const is_interface = t.params.aspace === 'uniform' || t.params.aspace === 'storage';
+ const supports_atomic = t.params.aspace === 'storage' || t.params.aspace === 'workgroup';
+ const expect =
+ testcase.validity === true ||
+ (testcase.validity === 'non-uniform' && t.params.aspace !== 'uniform') ||
+ (testcase.validity === 'non-interface' && !is_interface) ||
+ (testcase.validity === 'storage' && t.params.aspace === 'storage') ||
+ (testcase.validity === 'atomic' && supports_atomic);
+ t.expectCompileResult(expect, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/locations.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/locations.spec.ts
index 8452679d71..3c41f1a8b5 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/locations.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/locations.spec.ts
@@ -380,3 +380,152 @@ g.test('location_fp16')
}`;
t.expectCompileResult(t.params.ext === '', code);
});
+
+interface OutOfOrderCase {
+ params?: string;
+ returnType?: string;
+ decls?: string;
+ returnValue?: string;
+ valid: boolean;
+}
+
+const kOutOfOrderCases: Record<string, OutOfOrderCase> = {
+ reverse_params: {
+ params: `@location(2) p1 : f32, @location(1) p2 : f32, @location(0) p3 : f32`,
+ valid: true,
+ },
+ no_zero_params: {
+ params: `@location(2) p1 : f32, @location(1) p2 : f32`,
+ valid: true,
+ },
+ reverse_overlap: {
+ params: `@location(2) p1 : f32, @location(1) p2 : f32, @location(1) p3 : f32`,
+ valid: false,
+ },
+ struct: {
+ params: `p1 : S`,
+ decls: `struct S {
+ @location(1) x : f32,
+ @location(0) y : f32,
+ }`,
+ valid: true,
+ },
+ struct_override: {
+ params: `@location(0) p1 : S`,
+ decls: `struct S {
+ @location(1) x : f32,
+ @location(0) y : f32,
+ }`,
+ valid: false,
+ },
+ struct_random: {
+ params: `p1 : S, p2 : T`,
+ decls: `struct S {
+ @location(16) x : f32,
+ @location(4) y : f32,
+ }
+ struct T {
+ @location(13) x : f32,
+ @location(7) y : f32,
+ }`,
+ valid: true,
+ },
+ struct_random_overlap: {
+ params: `p1 : S, p2 : T`,
+ decls: `struct S {
+ @location(16) x : f32,
+ @location(4) y : f32,
+ }
+ struct T {
+ @location(13) x : f32,
+ @location(4) y : f32,
+ }`,
+ valid: false,
+ },
+ mixed_locations1: {
+ params: `@location(12) p1 : f32, p2 : S`,
+ decls: `struct S {
+ @location(2) x : f32,
+ }`,
+ valid: true,
+ },
+ mixed_locations2: {
+ params: `p1 : S, @location(2) p2 : f32`,
+ decls: `struct S {
+ @location(12) x : f32,
+ }`,
+ valid: true,
+ },
+ mixed_overlap: {
+ params: `p1 : S, @location(12) p2 : f32`,
+ decls: `struct S {
+ @location(12) x : f32,
+ }`,
+ valid: false,
+ },
+ with_param_builtin: {
+ params: `p : S`,
+ decls: `struct S {
+ @location(12) x : f32,
+ @builtin(position) pos : vec4f,
+ @location(0) y : f32,
+ }`,
+ valid: true,
+ },
+ non_zero_return: {
+ returnType: `@location(1) vec4f`,
+ returnValue: `vec4f()`,
+ valid: true,
+ },
+ reverse_return: {
+ returnType: `S`,
+ returnValue: `S()`,
+ decls: `struct S {
+ @location(2) x : f32,
+ @location(1) y : f32,
+ @location(0) z : f32,
+ }`,
+ valid: true,
+ },
+ gap_return: {
+ returnType: `S`,
+ returnValue: `S()`,
+ decls: `struct S {
+ @location(13) x : f32,
+ @location(7) y : f32,
+ @location(2) z : f32,
+ }`,
+ valid: true,
+ },
+ with_return_builtin: {
+ returnType: `S`,
+ returnValue: `S()`,
+ decls: `struct S {
+ @location(11) x : f32,
+ @builtin(frag_depth) d : f32,
+ @location(10) y : f32,
+ }`,
+ valid: true,
+ },
+};
+
+g.test('out_of_order')
+ .desc(`Test validation of out of order locations`)
+ .params(u => u.combine('case', keysOf(kOutOfOrderCases)))
+ .fn(t => {
+ const testcase = kOutOfOrderCases[t.params.case];
+ const decls = testcase.decls !== undefined ? testcase.decls : ``;
+ const params = testcase.params !== undefined ? testcase.params : ``;
+ const returnType = testcase.returnType !== undefined ? `-> ${testcase.returnType}` : ``;
+ const returnValue = testcase.returnValue !== undefined ? `return ${testcase.returnValue};` : ``;
+ const code = `
+${decls}
+
+@fragment
+fn main(${params}) ${returnType} {
+ ${returnValue}
+}
+`;
+
+ t.expectCompileResult(testcase.valid, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/size.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/size.spec.ts
index f81dde4a1d..564c3f2b7c 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/size.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/size.spec.ts
@@ -103,7 +103,7 @@ const kSizeTests = {
};
g.test('size')
- .desc(`Test validation of ize`)
+ .desc(`Test validation of size`)
.params(u => u.combine('attr', keysOf(kSizeTests)))
.fn(t => {
const code = `
@@ -210,3 +210,21 @@ g.test('size_non_struct')
t.expectCompileResult(data.pass, code);
});
+
+g.test('size_creation_fixed_footprint')
+ .desc(`Test that @size is only valid on types that have creation-fixed footprint.`)
+ .params(u => u.combine('array_size', [', 4', '']))
+ .fn(t => {
+ const code = `
+struct S {
+ @size(64) a: array<f32${t.params.array_size}>,
+};
+@group(0) @binding(0)
+var<storage> a: S;
+
+@workgroup_size(1)
+@compute fn main() {
+ _ = a.a[0];
+}`;
+ t.expectCompileResult(t.params.array_size !== '', code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/alias.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/alias.spec.ts
index 266b4f9a12..59e8607a5f 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/alias.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/alias.spec.ts
@@ -3,6 +3,7 @@ Validation tests for type aliases
`;
import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
import { ShaderValidationTest } from '../shader_validation_test.js';
export const g = makeTestGroup(ShaderValidationTest);
@@ -121,3 +122,124 @@ alias T = ${t.params.target};
`;
t.expectCompileResult(t.params.target === 'i32', wgsl);
});
+
+const kTypes = [
+ 'bool',
+ 'i32',
+ 'u32',
+ 'f32',
+ 'f16',
+ 'vec2<i32>',
+ 'vec3<u32>',
+ 'vec4<f32>',
+ 'mat2x2<f32>',
+ 'mat2x3<f32>',
+ 'mat2x4<f32>',
+ 'mat3x2<f32>',
+ 'mat3x3<f32>',
+ 'mat3x4<f32>',
+ 'mat4x2<f32>',
+ 'mat4x3<f32>',
+ 'mat4x4<f32>',
+ 'array<u32>',
+ 'array<i32, 4>',
+ 'array<vec2<u32>, 8>',
+ 'S',
+ 'T',
+ 'atomic<u32>',
+ 'atomic<i32>',
+ 'ptr<function, u32>',
+ 'ptr<private, i32>',
+ 'ptr<workgroup, f32>',
+ 'ptr<uniform, vec2f>',
+ 'ptr<storage, vec2u>',
+ 'ptr<storage, vec3i, read>',
+ 'ptr<storage, vec4f, read_write>',
+ 'sampler',
+ 'sampler_comparison',
+ 'texture_1d<f32>',
+ 'texture_2d<u32>',
+ 'texture_2d_array<i32>',
+ 'texture_3d<f32>',
+ 'texture_cube<i32>',
+ 'texture_cube_array<u32>',
+ 'texture_multisampled_2d<f32>',
+ 'texture_depth_multisampled_2d',
+ 'texture_external',
+ 'texture_storage_1d<rgba8snorm, write>',
+ 'texture_storage_1d<r32uint, write>',
+ 'texture_storage_1d<r32sint, read_write>',
+ 'texture_storage_1d<r32float, read>',
+ 'texture_storage_2d<rgba16uint, write>',
+ 'texture_storage_2d_array<rg32float, write>',
+ 'texture_storage_3d<bgra8unorm, write>',
+ 'texture_depth_2d',
+ 'texture_depth_2d_array',
+ 'texture_depth_cube',
+ 'texture_depth_cube_array',
+
+ // Pre-declared aliases (spot check)
+ 'vec2f',
+ 'vec3u',
+ 'vec4i',
+ 'mat2x2f',
+
+ // User-defined aliases
+ 'anotherAlias',
+ 'random_alias',
+];
+
+g.test('any_type')
+ .desc('Test that any type can be aliased')
+ .params(u => u.combine('type', kTypes))
+ .beforeAllSubcases(t => {
+ if (t.params.type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const ty = t.params.type;
+ t.skipIf(
+ ty.includes('texture_storage') &&
+ ty.includes('read') &&
+ !t.hasLanguageFeature('readonly_and_readwrite_storage_textures'),
+ 'Missing language feature'
+ );
+ const enable = ty === 'f16' ? 'enable f16;' : '';
+ const code = `
+ ${enable}
+ struct S { x : u32 }
+ struct T { y : S }
+ alias anotherAlias = u32;
+ alias random_alias = i32;
+ alias myType = ${ty};`;
+ t.expectCompileResult(true, code);
+ });
+
+const kMatchCases = {
+ function_param: `
+ fn foo(x : u32) { }
+ fn bar() {
+ var x : alias_alias_u32;
+ foo(x);
+ }`,
+ constructor: `var<private> v : u32 = alias_u32(1);`,
+ template_param: `var<private> v : vec2<alias_u32> = vec2<u32>();`,
+ predeclared_alias: `var<private> v : vec2<alias_alias_u32> = vec2u();`,
+ struct_element: `
+ struct S { x : alias_u32 }
+ const c_u32 = 0u;
+ const c = S(c_u32);`,
+};
+
+g.test('match_non_alias')
+ .desc('Test that type checking succeeds using aliased and unaliased type')
+ .params(u => u.combine('case', keysOf(kMatchCases)))
+ .fn(t => {
+ const testcase = kMatchCases[t.params.case];
+ const code = `
+ alias alias_u32 = u32;
+ alias alias_alias_u32 = alias_u32;
+ ${testcase}`;
+ t.expectCompileResult(true, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/array.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/array.spec.ts
new file mode 100644
index 0000000000..636906f52e
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/array.spec.ts
@@ -0,0 +1,122 @@
+export const description = `
+Validation tests for array types
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValidCases = {
+ // Basic element types.
+ i32: `alias T = array<i32>;`,
+ u32: `alias T = array<u32>;`,
+ f32: `alias T = array<f32>;`,
+ f16: `enable f16;\nalias T = array<f16>;`,
+ bool: `alias T = array<bool>;`,
+
+ // Composite elements
+ vec2u: `alias T = array<vec2u>;`,
+ vec3i: `alias T = array<vec3i>;`,
+ vec4f: `alias T = array<vec4f>;`,
+ array: `alias T = array<array<u32, 4>>;`,
+ struct: `struct S { x : u32 }\nalias T = array<S>;`,
+ mat2x2f: `alias T = array<mat2x2f>;`,
+ mat4x4h: `enable f16;\nalias T = array<mat4x4h>;`,
+
+ // Atomic elements
+ atomicu: `alias T = array<atomic<u32>>;`,
+ atomici: `alias T = array<atomic<i32>>;`,
+
+ // Count expressions
+ literal_count: `alias T = array<u32, 4>;`,
+ literali_count: `alias T = array<u32, 4i>;`,
+ literalu_count: `alias T = array<u32, 4u>;`,
+ const_count: `const x = 8;\nalias T = array<u32, x>;`,
+ const_expr_count1: `alias T = array<u32, 1 + 3>;`,
+ const_expr_count2: `const x = 4;\nalias T = array<u32, x * 2>;`,
+ override_count: `override x : u32;\nalias T = array<u32, x>;`,
+ override_expr1: `override x = 2;\nalias T = array<u32, vec2(x,x).x>;`,
+ override_expr2: `override x = 1;\nalias T = array<u32, x + 1>;`,
+ override_zero: `override x = 0;\nalias T = array<u32, x>;`,
+ override_neg: `override x = -1;\nalias T = array<u32, x>;`,
+
+ // Same array types
+ same_const_value1: `
+ const x = 8;
+ const y = 8;
+ var<private> v : array<u32, x> = array<u32, y>();`,
+ same_const_value2: `
+ const x = 8;
+ var<private> v : array<u32, x> = array<u32, 8>();`,
+ same_const_value3: `
+ var<private> v : array<u32, 8i> = array<u32, 8u>();`,
+ same_override: `
+ requires unrestricted_pointer_parameters;
+ override x : u32;
+ var<workgroup> v : array<u32, x>;
+ fn bar(p : ptr<workgroup, array<u32, x>>) { }
+ fn foo() { bar(&v); }`,
+
+ // Shadow
+ shadow: `alias array = vec2f;`,
+};
+
+g.test('valid')
+ .desc('Valid array type tests')
+ .params(u => u.combine('case', keysOf(kValidCases)))
+ .beforeAllSubcases(t => {
+ const code = kValidCases[t.params.case];
+ if (code.indexOf('f16') >= 0) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const code = kValidCases[t.params.case];
+ t.skipIf(
+ code.indexOf('unrestricted') >= 0 && !t.hasLanguageFeature('unrestricted_pointer_parameters'),
+ 'Test requires unrestricted_pointer_parameters'
+ );
+ t.expectCompileResult(true, code);
+ });
+
+const kInvalidCases = {
+ f16_without_enable: `alias T = array<f16>;`,
+ runtime_nested: `alias T = array<array<u32>, 4>;`,
+ override_nested: `
+ override x : u32;
+ alias T = array<array<u32, x>, 4>;`,
+ override_nested_struct: `
+ override x : u32;
+ struct T { x : array<u32, x> }`,
+ zero_size: `alias T = array<u32, 0>;`,
+ negative_size: `alias T = array<u32, 1 - 2>;`,
+ const_zero: `const x = 0;\nalias T = array<u32, x>;`,
+ const_neg: `const x = 1;\nconst y = 2;\nalias T = array<u32, x - y>;`,
+ incompatible_overrides: `
+ requires unrestricted_pointer_parameters;
+ override x = 8;
+ override y = 8;
+ var<workgroup> v : array<u32, x>
+ fn bar(p : ptr<workgroup, array<u32 y>>) { }
+ fn foo() { bar(&v); }`,
+};
+
+g.test('invalid')
+ .desc('Invalid array type tests')
+ .params(u => u.combine('case', keysOf(kInvalidCases)))
+ .beforeAllSubcases(t => {
+ const code = kInvalidCases[t.params.case];
+ if (code.indexOf('f16') >= 0) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const code = kInvalidCases[t.params.case];
+ t.skipIf(
+ code.indexOf('unrestricted') >= 0 && !t.hasLanguageFeature('unrestricted_pointer_parameters'),
+ 'Test requires unrestricted_pointer_parameters'
+ );
+ t.expectCompileResult(false, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/atomics.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/atomics.spec.ts
new file mode 100644
index 0000000000..36c37176e8
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/atomics.spec.ts
@@ -0,0 +1,145 @@
+export const description = `
+Validation tests for atomic types
+
+Tests covered:
+* Base type
+* Address spaces
+* Invalid operations (non-exhaustive)
+
+Note: valid operations (e.g. atomic built-in functions) are tested in the builtin tests.
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('type')
+ .desc('Test of the underlying atomic data type')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#atomic-types')
+ .params(u =>
+ u.combine('type', [
+ 'u32',
+ 'i32',
+ 'f32',
+ 'f16',
+ 'bool',
+ 'vec2u',
+ 'vec3i',
+ 'vec4f',
+ 'mat2x2f',
+ 'R',
+ 'S',
+ 'array<u32, 1>',
+ 'array<i32, 4>',
+ 'array<u32>',
+ 'array<i32>',
+ 'atomic<u32>',
+ 'atomic<i32>',
+ ] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const code = `
+struct S {
+ x : u32
+}
+struct T {
+ x : i32
+}
+struct R {
+ x : f32
+}
+
+struct Test {
+ x : atomic<${t.params.type}>
+}
+`;
+
+ const expect = t.params.type === 'u32' || t.params.type === 'i32';
+ t.expectCompileResult(expect, code);
+ });
+
+g.test('address_space')
+ .desc('Test allowed address spaces for atomics')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#atomic-types')
+ .params(u =>
+ u
+ .combine('aspace', [
+ 'storage',
+ 'workgroup',
+ 'storage-ro',
+ 'uniform',
+ 'private',
+ 'function',
+ 'function-let',
+ ] as const)
+ .beginSubcases()
+ .combine('type', ['i32', 'u32'] as const)
+ )
+ .fn(t => {
+ let moduleVar = ``;
+ let functionVar = '';
+ switch (t.params.aspace) {
+ case 'storage-ro':
+ moduleVar = `@group(0) @binding(0) var<storage> x : atomic<${t.params.type}>;\n`;
+ break;
+ case 'storage':
+ moduleVar = `@group(0) @binding(0) var<storage, read_write> x : atomic<${t.params.type}>;\n`;
+ break;
+ case 'uniform':
+ moduleVar = `@group(0) @binding(0) var<uniform> x : atomic<${t.params.type}>;\n`;
+ break;
+ case 'workgroup':
+ case 'private':
+ moduleVar = `var<${t.params.aspace}> x : atomic<${t.params.type}>;\n`;
+ break;
+ case 'function':
+ functionVar = `var x : atomic<${t.params.type}>;\n`;
+ break;
+ case 'function-let':
+ functionVar = `let x : atomic<${t.params.type}>;\n`;
+ break;
+ }
+ const code = `
+${moduleVar}
+
+fn foo() {
+ ${functionVar}
+}
+`;
+
+ const expect = t.params.aspace === 'storage' || t.params.aspace === 'workgroup';
+ t.expectCompileResult(expect, code);
+ });
+
+const kInvalidOperations = {
+ add: `a1 + a2`,
+ load: `a1`,
+ store: `a1 = 1u`,
+ deref: `*a1 = 1u`,
+ equality: `a1 == a2`,
+ abs: `abs(a1)`,
+ address_abs: `abs(&a1)`,
+};
+
+g.test('invalid_operations')
+ .desc('Tests that a selection of invalid operations are invalid')
+ .params(u => u.combine('op', keysOf(kInvalidOperations)))
+ .fn(t => {
+ const code = `
+var<workgroup> a1 : atomic<u32>;
+var<workgroup> a2 : atomic<u32>;
+
+fn foo() {
+ let x : u32 = ${kInvalidOperations[t.params.op]};
+}
+`;
+
+ t.expectCompileResult(false, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/matrix.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/matrix.spec.ts
new file mode 100644
index 0000000000..02458c6c88
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/matrix.spec.ts
@@ -0,0 +1,152 @@
+export const description = `
+Validation tests for matrix types
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValidCases = {
+ // Basic matrices
+ mat2x2_f32: `alias T = mat2x2<f32>;`,
+ mat2x3_f32: `alias T = mat2x3<f32>;`,
+ mat2x4_f32: `alias T = mat2x4<f32>;`,
+ mat3x2_f32: `alias T = mat3x2<f32>;`,
+ mat3x3_f32: `alias T = mat3x3<f32>;`,
+ mat3x4_f32: `alias T = mat3x4<f32>;`,
+ mat4x2_f32: `alias T = mat4x2<f32>;`,
+ mat4x3_f32: `alias T = mat4x3<f32>;`,
+ mat4x4_f32: `alias T = mat4x4<f32>;`,
+ mat2x2_f16: `enable f16;\nalias T = mat2x2<f16>;`,
+ mat2x3_f16: `enable f16;\nalias T = mat2x3<f16>;`,
+ mat2x4_f16: `enable f16;\nalias T = mat2x4<f16>;`,
+ mat3x2_f16: `enable f16;\nalias T = mat3x2<f16>;`,
+ mat3x3_f16: `enable f16;\nalias T = mat3x3<f16>;`,
+ mat3x4_f16: `enable f16;\nalias T = mat3x4<f16>;`,
+ mat4x2_f16: `enable f16;\nalias T = mat4x2<f16>;`,
+ mat4x3_f16: `enable f16;\nalias T = mat4x3<f16>;`,
+ mat4x4_f16: `enable f16;\nalias T = mat4x4<f16>;`,
+
+ // Pre-declared aliases
+ mat2x2f: `alias T = mat2x2f;`,
+ mat2x3f: `alias T = mat2x3f;`,
+ mat2x4f: `alias T = mat2x4f;`,
+ mat3x2f: `alias T = mat3x2f;`,
+ mat3x3f: `alias T = mat3x3f;`,
+ mat3x4f: `alias T = mat3x4f;`,
+ mat4x2f: `alias T = mat4x2f;`,
+ mat4x3f: `alias T = mat4x3f;`,
+ mat4x4f: `alias T = mat4x4f;`,
+ mat2x2h: `enable f16;\nalias T = mat2x2h;`,
+ mat2x3h: `enable f16;\nalias T = mat2x3h;`,
+ mat2x4h: `enable f16;\nalias T = mat2x4h;`,
+ mat3x2h: `enable f16;\nalias T = mat3x2h;`,
+ mat3x3h: `enable f16;\nalias T = mat3x3h;`,
+ mat3x4h: `enable f16;\nalias T = mat3x4h;`,
+ mat4x2h: `enable f16;\nalias T = mat4x2h;`,
+ mat4x3h: `enable f16;\nalias T = mat4x3h;`,
+ mat4x4h: `enable f16;\nalias T = mat4x4h;`,
+
+ trailing_comman: `alias T = mat2x2<f32,>;`,
+
+ // Abstract matrices
+ abstract_2x2: `const m = mat2x2(1,1,1,1);`,
+ abstract_2x3: `const m = mat2x3(1,1,1,1,1,1);`,
+ abstract_2x4: `const m = mat2x4(1,1,1,1,1,1,1,1);`,
+
+ // Base roots shadowable
+ shadow_mat2x2: `alias mat2x2 = array<vec2f, 2>;`,
+ shadow_mat2x3: `alias mat2x3 = array<vec2f, 3>;`,
+ shadow_mat2x4: `alias mat2x4 = array<vec2f, 4>;`,
+ shadow_mat3x2: `alias mat3x2 = array<vec3f, 2>;`,
+ shadow_mat3x3: `alias mat3x3 = array<vec3f, 3>;`,
+ shadow_mat3x4: `alias mat3x4 = array<vec3f, 4>;`,
+ shadow_mat4x2: `alias mat4x2 = array<vec4f, 2>;`,
+ shadow_mat4x3: `alias mat4x3 = array<vec4f, 3>;`,
+ shadow_mat4x4: `alias mat4x4 = array<vec4f, 4>;`,
+
+ // Pre-declared aliases shadowable
+ shadow_mat2x2f: `alias mat2x2f = mat2x2<f32>;`,
+ shadow_mat2x3f: `alias mat2x3f = mat2x3<f32>;`,
+ shadow_mat2x4f: `alias mat2x4f = mat2x4<f32>;`,
+ shadow_mat3x2f: `alias mat3x2f = mat3x2<f32>;`,
+ shadow_mat3x3f: `alias mat3x3f = mat3x3<f32>;`,
+ shadow_mat3x4f: `alias mat3x4f = mat3x4<f32>;`,
+ shadow_mat4x2f: `alias mat4x2f = mat4x2<f32>;`,
+ shadow_mat4x3f: `alias mat4x3f = mat4x3<f32>;`,
+ shadow_mat4x4f: `alias mat4x4f = mat4x4<f32>;`,
+ shadow_mat2x2h: `enable f16;\nalias mat2x2h = mat2x2<f16>;`,
+ shadow_mat2x3h: `enable f16;\nalias mat2x3h = mat2x3<f16>;`,
+ shadow_mat2x4h: `enable f16;\nalias mat2x4h = mat2x4<f16>;`,
+ shadow_mat3x2h: `enable f16;\nalias mat3x2h = mat3x2<f16>;`,
+ shadow_mat3x3h: `enable f16;\nalias mat3x3h = mat3x3<f16>;`,
+ shadow_mat3x4h: `enable f16;\nalias mat3x4h = mat3x4<f16>;`,
+ shadow_mat4x2h: `enable f16;\nalias mat4x2h = mat4x2<f16>;`,
+ shadow_mat4x3h: `enable f16;\nalias mat4x3h = mat4x3<f16>;`,
+ shadow_mat4x4h: `enable f16;\nalias mat4x4h = mat4x4<f16>;`,
+};
+
+g.test('valid')
+ .desc('Valid matrix type tests')
+ .params(u => u.combine('case', keysOf(kValidCases)))
+ .beforeAllSubcases(t => {
+ const code = kValidCases[t.params.case];
+ if (code.indexOf('f16') >= 0) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const code = kValidCases[t.params.case];
+ t.expectCompileResult(true, code);
+ });
+
+const kInvalidCases = {
+ // Invalid component types
+ mat2x2_i32: `alias T = mat2x2<i32>;`,
+ mat3x3_u32: `alias T = mat3x3<u32>;`,
+ mat4x4_bool: `alias T = mat4x4<bool>;`,
+ mat2x2_vec4f: `alias T = mat2x2<vec2f>;`,
+ mat2x2_array: `alias T = mat2x2<array<f32, 2>>;`,
+ mat2x2_struct: `struct S { x : f32 }\nalias T = mat2x2<S>;`,
+
+ // Invalid dimensions
+ mat1x1: `alias T = mat1x1<f32>;`,
+ mat2x1: `alias T = mat2x1<f32>;`,
+ mat2x5: `alias T = mat2x5<f32>;`,
+ mat5x5: `alias T = mat5x5<f32>;`,
+
+ // Half-precision aliases require enable
+ no_enable_mat2x2h: `alias T = mat2x2h;`,
+ no_enable_mat2x3h: `alias T = mat2x3h;`,
+ no_enable_mat2x4h: `alias T = mat2x4h;`,
+ no_enable_mat3x2h: `alias T = mat3x2h;`,
+ no_enable_mat3x3h: `alias T = mat3x3h;`,
+ no_enable_mat3x4h: `alias T = mat3x4h;`,
+ no_enable_mat4x2h: `alias T = mat4x2h;`,
+ no_enable_mat4x3h: `alias T = mat4x3h;`,
+ no_enable_mat4x4h: `alias T = mat4x4h;`,
+
+ missing_template: `alias T = mat2x2;`,
+ missing_left_template: `alias T = mat2x2f32>;`,
+ missing_right_template: `alias T = mat2x2<f32;`,
+ missing_comp: `alias T = mat2x2<>;`,
+ mat2x2i: `alias T = mat2x2i;`,
+ mat2x2u: `alias T = mat2x2u;`,
+ mat2x2b: `alias T = mat2x2b;`,
+};
+
+g.test('invalid')
+ .desc('Invalid matrix type tests')
+ .params(u => u.combine('case', keysOf(kInvalidCases)))
+ .beforeAllSubcases(t => {
+ const code = kInvalidCases[t.params.case];
+ if (code.indexOf('f16') >= 0) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const code = kInvalidCases[t.params.case];
+ t.expectCompileResult(false, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/textures.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/textures.spec.ts
new file mode 100644
index 0000000000..59e5b26db6
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/textures.spec.ts
@@ -0,0 +1,170 @@
+export const description = `
+Validation tests for various texture types in shaders.
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import {
+ isTextureFormatUsableAsStorageFormat,
+ kAllTextureFormats,
+ kColorTextureFormats,
+ kTextureFormatInfo,
+} from '../../../format_info.js';
+import { getPlainTypeInfo } from '../../../util/shader.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('texel_formats')
+ .desc(
+ 'Test channels and channel format of various texel formats when using as the storage texture format'
+ )
+ .params(u =>
+ u
+ .combine('format', kColorTextureFormats)
+ .filter(p => kTextureFormatInfo[p.format].color.storage)
+ .beginSubcases()
+ .combine('shaderScalarType', ['f32', 'u32', 'i32', 'bool', 'f16'] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.shaderScalarType === 'f16') {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+ }
+
+ if (!isTextureFormatUsableAsStorageFormat(t.params.format, t.isCompatibility)) {
+ t.skip('storage usage is unsupported');
+ }
+ })
+ .fn(t => {
+ const { format, shaderScalarType } = t.params;
+ const info = kTextureFormatInfo[format];
+ const validShaderScalarType = getPlainTypeInfo(info.color.type);
+ const shaderValueType = `vec4<${shaderScalarType}>`;
+ const wgsl = `
+ @group(0) @binding(0) var tex: texture_storage_2d<${format}, read>;
+ @compute @workgroup_size(1) fn main() {
+ let v : ${shaderValueType} = textureLoad(tex, vec2u(0));
+ _ = v;
+ }
+`;
+ t.expectCompileResult(validShaderScalarType === shaderScalarType, wgsl);
+ });
+
+g.test('texel_formats,as_value')
+ .desc('Test that texel format cannot be used as value')
+ .fn(t => {
+ const wgsl = `
+ @compute @workgroup_size(1) fn main() {
+ var i = rgba8unorm;
+ }
+`;
+ t.expectCompileResult(false, wgsl);
+ });
+
+const kValidTextureSampledTypes = ['f32', 'i32', 'u32'];
+
+g.test('sampled_texture_types')
+ .desc(
+ `Test that for texture_xx<T>
+- The sampled type T must be f32, i32, or u32
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', ['texture_2d', 'texture_multisampled_2d'])
+ .beginSubcases()
+ .combine('sampledType', [
+ ...kValidTextureSampledTypes,
+ 'bool',
+ 'vec2',
+ 'mat2x2',
+ '1.0',
+ '1',
+ '1u',
+ ] as const)
+ )
+ .fn(t => {
+ const { textureType, sampledType } = t.params;
+ const wgsl = `@group(0) @binding(0) var tex: ${textureType}<${sampledType}>;`;
+ t.expectCompileResult(kValidTextureSampledTypes.includes(sampledType), wgsl);
+ });
+
+g.test('external_sampled_texture_types')
+ .desc(
+ `Test that texture_extenal compiles and cannot specify address space
+`
+ )
+ .fn(t => {
+ t.expectCompileResult(true, `@group(0) @binding(0) var tex: texture_external;`);
+ t.expectCompileResult(false, `@group(0) @binding(0) var<private> tex: texture_external;`);
+ });
+
+const kAccessModes = ['read', 'write', 'read_write'];
+
+g.test('storage_texture_types')
+ .desc(
+ `Test that for texture_storage_xx<format, access>
+- format must be an enumerant for one of the texel formats for storage textures
+- access must be an enumerant for one of the access modes
+
+Besides, the shader compilation should always pass regardless of whether the format supports the usage indicated by the access or not.
+`
+ )
+ .params(u =>
+ u.combine('access', [...kAccessModes, 'storage'] as const).combine('format', kAllTextureFormats)
+ )
+ .fn(t => {
+ const { format, access } = t.params;
+ const info = kTextureFormatInfo[format];
+ // bgra8unorm is considered a valid storage format at shader compilation stage
+ const isFormatValid =
+ info.color?.storage ||
+ info.depth?.storage ||
+ info.stencil?.storage ||
+ format === 'bgra8unorm';
+ const isAccessValid = kAccessModes.includes(access);
+ const wgsl = `@group(0) @binding(0) var tex: texture_storage_2d<${format}, ${access}>;`;
+ t.expectCompileResult(isFormatValid && isAccessValid, wgsl);
+ });
+
+g.test('depth_texture_types')
+ .desc(
+ `Test that for texture_depth_xx
+- must not specify an address space
+`
+ )
+ .params(u =>
+ u.combine('textureType', [
+ 'texture_depth_2d',
+ 'texture_depth_2d_array',
+ 'texture_depth_cube',
+ 'texture_depth_cube_array',
+ ])
+ )
+ .fn(t => {
+ const { textureType } = t.params;
+ t.expectCompileResult(true, `@group(0) @binding(0) var t: ${textureType};`);
+ t.expectCompileResult(false, `@group(0) @binding(0) var<private> t: ${textureType};`);
+ t.expectCompileResult(false, `@group(0) @binding(0) var<storage, read> t: ${textureType};`);
+ });
+
+g.test('sampler_types')
+ .desc(
+ `Test that for sampler and sampler_comparison
+- cannot specify address space
+- cannot be declared in WGSL function scope
+`
+ )
+ .params(u => u.combine('samplerType', ['sampler', 'sampler_comparison']))
+ .fn(t => {
+ const { samplerType } = t.params;
+ t.expectCompileResult(true, `@group(0) @binding(0) var s: ${samplerType};`);
+ t.expectCompileResult(false, `@group(0) @binding(0) var<private> s: ${samplerType};`);
+ t.expectCompileResult(
+ false,
+ `
+ @compute @workgroup_size(1) fn main() {
+ var s: ${samplerType};
+ }
+ `
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/uniformity/uniformity.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/uniformity/uniformity.spec.ts
index 41249e445d..c794bded28 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/uniformity/uniformity.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/uniformity/uniformity.spec.ts
@@ -21,6 +21,7 @@ const kCollectiveOps = [
{ op: 'fwidthCoarse', stage: 'fragment' },
{ op: 'fwidthFine', stage: 'fragment' },
{ op: 'storageBarrier', stage: 'compute' },
+ { op: 'textureBarrier', stage: 'compute' },
{ op: 'workgroupBarrier', stage: 'compute' },
{ op: 'workgroupUniformLoad', stage: 'compute' },
];
@@ -43,6 +44,8 @@ const kConditions = [
{ cond: 'nonuniform_and2', expectation: false },
{ cond: 'uniform_func_var', expectation: true },
{ cond: 'nonuniform_func_var', expectation: false },
+ { cond: 'storage_texture_ro', expectation: true },
+ { cond: 'storage_texture_rw', expectation: false },
];
function generateCondition(condition: string): string {
@@ -98,6 +101,12 @@ function generateCondition(condition: string): string {
case 'nonuniform_func_var': {
return `n_f == 0`;
}
+ case 'storage_texture_ro': {
+ return `textureLoad(ro_storage_texture, vec2()).x == 0`;
+ }
+ case 'storage_texture_rw': {
+ return `textureLoad(rw_storage_texture, vec2()).x == 0`;
+ }
default: {
unreachable(`Unhandled condition`);
}
@@ -116,6 +125,7 @@ function generateOp(op: string): string {
return `let x = ${op}(tex_depth, s_comp, vec2(0,0), 0);\n`;
}
case 'storageBarrier':
+ case 'textureBarrier':
case 'workgroupBarrier': {
return `${op}();\n`;
}
@@ -181,12 +191,16 @@ g.test('basics')
.desc(`Test collective operations in simple uniform or non-uniform control flow.`)
.params(u =>
u
- .combineWithParams(kCollectiveOps)
- .combineWithParams(kConditions)
.combine('statement', ['if', 'for', 'while', 'switch'] as const)
.beginSubcases()
+ .combineWithParams(kConditions)
+ .combineWithParams(kCollectiveOps)
)
.fn(t => {
+ if (t.params.op === 'textureBarrier' || t.params.cond.startsWith('storage_texture')) {
+ t.skipIfLanguageFeatureNotSupported('readonly_and_readwrite_storage_textures');
+ }
+
let code = `
@group(0) @binding(0) var s : sampler;
@group(0) @binding(1) var s_comp : sampler_comparison;
@@ -197,6 +211,9 @@ g.test('basics')
@group(1) @binding(1) var<storage, read_write> rw_buffer : array<f32, 4>;
@group(1) @binding(2) var<uniform> uniform_buffer : vec4<f32>;
+ @group(2) @binding(0) var ro_storage_texture : texture_storage_2d<rgba8unorm, read>;
+ @group(2) @binding(1) var rw_storage_texture : texture_storage_2d<rgba8unorm, read_write>;
+
var<private> priv_var : array<f32, 4> = array(0,0,0,0);
const c = false;
@@ -367,7 +384,14 @@ function generatePointerCheck(check: string): string {
}
}
-const kPointerCases = {
+interface PointerCase {
+ code: string;
+ check: 'address' | 'contents';
+ uniform: boolean | 'never';
+ needs_deref_sugar?: boolean;
+}
+
+const kPointerCases: Record<string, PointerCase> = {
address_uniform_literal: {
code: `let ptr = &wg_array[0];`,
check: `address`,
@@ -585,6 +609,168 @@ const kPointerCases = {
check: `contents`,
uniform: false,
},
+ contents_lhs_ref_pointer_deref1: {
+ code: `*&func_scalar = uniform_value;
+ let test_val = func_scalar;`,
+ check: `contents`,
+ uniform: true,
+ },
+ contents_lhs_ref_pointer_deref1a: {
+ code: `*&func_scalar = nonuniform_value;
+ let test_val = func_scalar;`,
+ check: `contents`,
+ uniform: false,
+ },
+ contents_lhs_ref_pointer_deref2: {
+ code: `*&(func_array[nonuniform_value]) = uniform_value;
+ let test_val = func_array[0];`,
+ check: `contents`,
+ uniform: false,
+ },
+ contents_lhs_ref_pointer_deref2a: {
+ code: `(func_array[nonuniform_value]) = uniform_value;
+ let test_val = func_array[0];`,
+ check: `contents`,
+ uniform: false,
+ },
+ contents_lhs_ref_pointer_deref3: {
+ code: `*&(func_array[needs_uniform(uniform_value)]) = uniform_value;
+ let test_val = func_array[0];`,
+ check: `contents`,
+ uniform: true,
+ },
+ contents_lhs_ref_pointer_deref3a: {
+ code: `*&(func_array[needs_uniform(nonuniform_value)]) = uniform_value;
+ let test_val = func_array[0];`,
+ check: `contents`,
+ uniform: 'never',
+ },
+ contents_lhs_ref_pointer_deref4: {
+ code: `*&((*&(func_struct.x[uniform_value])).x[uniform_value].x[uniform_value]) = uniform_value;
+ let test_val = func_struct.x[0].x[0].x[0];`,
+ check: `contents`,
+ uniform: true,
+ },
+ contents_lhs_ref_pointer_deref4a: {
+ code: `*&((*&(func_struct.x[uniform_value])).x[uniform_value].x[uniform_value]) = nonuniform_value;
+ let test_val = func_struct.x[0].x[0].x[0];`,
+ check: `contents`,
+ uniform: false,
+ },
+ contents_lhs_ref_pointer_deref4b: {
+ code: `*&((*&(func_struct.x[uniform_value])).x[uniform_value].x[nonuniform_value]) = uniform_value;
+ let test_val = func_struct.x[0].x[0].x[0];`,
+ check: `contents`,
+ uniform: false,
+ },
+ contents_lhs_ref_pointer_deref4c: {
+ code: `*&((*&(func_struct.x[uniform_value])).x[nonuniform_value]).x[uniform_value] = uniform_value;
+ let test_val = func_struct.x[0].x[0].x[0];`,
+ check: `contents`,
+ uniform: false,
+ },
+ contents_lhs_ref_pointer_deref4d: {
+ code: `*&((*&(func_struct.x[nonuniform_value])).x[uniform_value].x)[uniform_value] = uniform_value;
+ let test_val = func_struct.x[0].x[0].x[0];`,
+ check: `contents`,
+ uniform: false,
+ },
+ contents_lhs_ref_pointer_deref4e: {
+ code: `*&((*&(func_struct.x[uniform_value])).x[needs_uniform(nonuniform_value)].x[uniform_value]) = uniform_value;
+ let test_val = func_struct.x[0].x[0].x[0];`,
+ check: `contents`,
+ uniform: 'never',
+ },
+
+ // The following cases require the 'pointer_composite_access' language feature.
+ contents_lhs_pointer_deref2: {
+ code: `(&func_array)[uniform_value] = uniform_value;
+ let test_val = func_array[0];`,
+ check: `contents`,
+ uniform: true,
+ needs_deref_sugar: true,
+ },
+ contents_lhs_pointer_deref2a: {
+ code: `(&func_array)[nonuniform_value] = uniform_value;
+ let test_val = func_array[0];`,
+ check: `contents`,
+ uniform: false,
+ needs_deref_sugar: true,
+ },
+ contents_lhs_pointer_deref3: {
+ code: `(&func_array)[needs_uniform(uniform_value)] = uniform_value;
+ let test_val = func_array[0];`,
+ check: `contents`,
+ uniform: true,
+ needs_deref_sugar: true,
+ },
+ contents_lhs_pointer_deref3a: {
+ code: `(&func_array)[needs_uniform(nonuniform_value)] = uniform_value;
+ let test_val = func_array[0];`,
+ check: `contents`,
+ uniform: 'never',
+ needs_deref_sugar: true,
+ },
+ contents_lhs_pointer_deref4: {
+ code: `(&((&(func_struct.x[uniform_value])).x[uniform_value]).x)[uniform_value] = uniform_value;
+ let test_val = func_struct.x[0].x[0].x[0];`,
+ check: `contents`,
+ uniform: true,
+ needs_deref_sugar: true,
+ },
+ contents_lhs_pointer_deref4a: {
+ code: `(&((&(func_struct.x[uniform_value])).x[uniform_value]).x)[uniform_value] = nonuniform_value;
+ let test_val = func_struct.x[0].x[0].x[0];`,
+ check: `contents`,
+ uniform: false,
+ needs_deref_sugar: true,
+ },
+ contents_lhs_pointer_deref4b: {
+ code: `(&((&(func_struct.x[uniform_value])).x)[uniform_value]).x[nonuniform_value] = uniform_value;
+ let test_val = func_struct.x[0].x[0].x[0];`,
+ check: `contents`,
+ uniform: false,
+ needs_deref_sugar: true,
+ },
+ contents_lhs_pointer_deref4c: {
+ code: `(&((&(func_struct.x[uniform_value])).x[nonuniform_value]).x)[uniform_value] = uniform_value;
+ let test_val = func_struct.x[0].x[0].x[0];`,
+ check: `contents`,
+ uniform: false,
+ needs_deref_sugar: true,
+ },
+ contents_lhs_pointer_deref4d: {
+ code: `(&((&(func_struct.x[nonuniform_value])).x[uniform_value]).x)[uniform_value] = uniform_value;
+ let test_val = func_struct.x[0].x[0].x[0];`,
+ check: `contents`,
+ uniform: false,
+ needs_deref_sugar: true,
+ },
+ contents_lhs_pointer_deref4e: {
+ code: `(&((&(func_struct.x[uniform_value])).x)[needs_uniform(nonuniform_value)].x[uniform_value]) = uniform_value;
+ let test_val = func_struct.x[0].x[0].x[0];`,
+ check: `contents`,
+ uniform: 'never',
+ needs_deref_sugar: true,
+ },
+ contents_rhs_pointer_deref1: {
+ code: `let test_val = (&func_array)[uniform_value];`,
+ check: `contents`,
+ uniform: true,
+ needs_deref_sugar: true,
+ },
+ contents_rhs_pointer_deref1a: {
+ code: `let test_val = (&func_array)[nonuniform_value];`,
+ check: `contents`,
+ uniform: false,
+ needs_deref_sugar: true,
+ },
+ contents_rhs_pointer_deref2: {
+ code: `let test_val = (&func_array)[needs_uniform(nonuniform_value)];`,
+ check: `contents`,
+ uniform: `never`,
+ needs_deref_sugar: true,
+ },
};
g.test('pointers')
@@ -612,6 +798,13 @@ var<storage> uniform_value : u32;
@group(0) @binding(1)
var<storage, read_write> nonuniform_value : u32;
+fn needs_uniform(val : u32) -> u32{
+ if val == 0 {
+ workgroupBarrier();
+ }
+ return val;
+}
+
@compute @workgroup_size(16, 1, 1)
fn main(@builtin(local_invocation_id) lid : vec3<u32>,
@builtin(global_invocation_id) gid : vec3<u32>) {
@@ -627,11 +820,16 @@ fn main(@builtin(local_invocation_id) lid : vec3<u32>,
`
${generatePointerCheck(testcase.check)}
}`;
- if (!testcase.uniform) {
+
+ if (testcase.needs_deref_sugar === true) {
+ t.skipIfLanguageFeatureNotSupported('pointer_composite_access');
+ }
+ // Explicitly check false to distinguish from never.
+ if (testcase.uniform === false) {
const without_check = code + `}\n`;
t.expectCompileResult(true, without_check);
}
- t.expectCompileResult(testcase.uniform, with_check);
+ t.expectCompileResult(testcase.uniform === true, with_check);
});
function expectedUniformity(uniform: string, init: string): boolean {
@@ -2019,6 +2217,7 @@ g.test('binary_expressions')
u
.combine('e1', keysOf(kExpressionCases))
.combine('e2', keysOf(kExpressionCases))
+ .beginSubcases()
.combine('op', keysOf(kBinOps))
)
.fn(t => {
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/util/binary_stream.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/util/binary_stream.ts
index a6512020e6..2531521dc4 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/util/binary_stream.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/util/binary_stream.ts
@@ -85,6 +85,16 @@ export default class BinaryStream {
return this.view.getInt16(this.alignedOffset(2), /* littleEndian */ true);
}
+ /** writeI64() writes a bitint to the buffer at the next 64-bit aligned offset */
+ writeI64(value: bigint) {
+ this.view.setBigInt64(this.alignedOffset(8), value, /* littleEndian */ true);
+ }
+
+ /** readI64() reads a bigint from the buffer at the next 64-bit aligned offset */
+ readI64(): bigint {
+ return this.view.getBigInt64(this.alignedOffset(8), /* littleEndian */ true);
+ }
+
/** writeI32() writes a int32 to the buffer at the next 32-bit aligned offset */
writeI32(value: number) {
this.view.setInt32(this.alignedOffset(4), value, /* littleEndian */ true);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/util/check_contents.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/util/check_contents.ts
index 298e7ae4a9..ed71d85d35 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/util/check_contents.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/util/check_contents.ts
@@ -19,7 +19,7 @@ import { generatePrettyTable } from './pretty_diff_tables.js';
/** Generate an expected value at `index`, to test for equality with the actual value. */
export type CheckElementsGenerator = (index: number) => number;
/** Check whether the actual `value` at `index` is as expected. */
-export type CheckElementsPredicate = (index: number, value: number) => boolean;
+export type CheckElementsPredicate = (index: number, value: number | bigint) => boolean;
/**
* Provides a pretty-printing implementation for a particular CheckElementsPredicate.
* This is an array; each element provides info to print an additional row in the error message.
@@ -29,9 +29,9 @@ export type CheckElementsSupplementalTableRows = Array<{
leftHeader: string;
/**
* Get the value for a cell in the table with element index `index`.
- * May be a string or a number; a number will be formatted according to the TypedArray type used.
+ * May be a string or numeric (number | bigint); numerics will be formatted according to the TypedArray type used.
*/
- getValueForCell: (index: number) => number | string;
+ getValueForCell: (index: number) => string | number | bigint;
}>;
/**
@@ -43,7 +43,10 @@ export function checkElementsEqual(
expected: TypedArrayBufferView
): ErrorWithExtra | undefined {
assert(actual.constructor === expected.constructor, 'TypedArray type mismatch');
- assert(actual.length === expected.length, 'size mismatch');
+ assert(
+ actual.length === expected.length,
+ `length mismatch: expected ${expected.length} got ${actual.length}`
+ );
let failedElementsFirstMaybe: number | undefined = undefined;
/** Sparse array with `true` for elements that failed. */
@@ -221,13 +224,17 @@ function failCheckElements({
const printElementsEnd = Math.min(size, failedElementsLast + 2);
const printElementsCount = printElementsEnd - printElementsStart;
- const numberToString = printAsFloat
- ? (n: number) => n.toPrecision(4)
- : (n: number) => intToPaddedHex(n, { byteLength: ctor.BYTES_PER_ELEMENT });
+ const numericToString = (val: number | bigint): string => {
+ if (typeof val === 'number' && printAsFloat) {
+ return val.toPrecision(4);
+ }
+ return intToPaddedHex(val, { byteLength: ctor.BYTES_PER_ELEMENT });
+ };
+
const numberPrefix = printAsFloat ? '' : '0x:';
const printActual = actual.subarray(printElementsStart, printElementsEnd);
- const printExpected: Array<Iterable<string | number>> = [];
+ const printExpected: Array<Iterable<string | number | bigint>> = [];
if (predicatePrinter) {
for (const { leftHeader, getValueForCell: cell } of predicatePrinter) {
printExpected.push(
@@ -246,7 +253,7 @@ function failCheckElements({
const opts = {
fillToWidth: 120,
- numberToString,
+ numericToString,
};
const msg = `Array had unexpected contents at indices ${failedElementsFirst} through ${failedElementsLast}.
Starting at index ${printElementsStart}:
@@ -263,10 +270,11 @@ ${generatePrettyTable(opts, [
// Helper helpers
/** Convert an integral `number` into a hex string, padded to the specified `byteLength`. */
-function intToPaddedHex(number: number, { byteLength }: { byteLength: number }) {
- assert(Number.isInteger(number), 'number must be integer');
- let s = Math.abs(number).toString(16);
- if (byteLength) s = s.padStart(byteLength * 2, '0');
- if (number < 0) s = '-' + s;
- return s;
+function intToPaddedHex(val: number | bigint, { byteLength }: { byteLength: number }) {
+ assert(Number.isInteger(val), 'number must be integer');
+ const is_negative = typeof val === 'number' ? val < 0 : val < 0n;
+ let str = (is_negative ? -val : val).toString(16);
+ if (byteLength) str = str.padStart(byteLength * 2, '0');
+ if (is_negative) str = '-' + str;
+ return str;
}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/util/color_space_conversion.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/util/color_space_conversion.ts
index a1de0e48ba..4ab3679b23 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/util/color_space_conversion.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/util/color_space_conversion.ts
@@ -143,12 +143,7 @@ function XYZ_to_lin_P3(XYZ: Array<Array<number>>) {
* https://drafts.csswg.org/css-color/#predefined-to-predefined
* display-p3 and sRGB share the same white points.
*/
-export function displayP3ToSrgb(pixel: { R: number; G: number; B: number; A: number }): {
- R: number;
- G: number;
- B: number;
- A: number;
-} {
+export function displayP3ToSrgb(pixel: Readonly<RGBA>): RGBA {
assert(
pixel.R !== undefined && pixel.G !== undefined && pixel.B !== undefined,
'color space conversion requires all of R, G and B components'
@@ -161,11 +156,7 @@ export function displayP3ToSrgb(pixel: { R: number; G: number; B: number; A: num
rgbVec = [rgbMatrix[0][0], rgbMatrix[1][0], rgbMatrix[2][0]];
rgbVec = gam_sRGB(rgbVec);
- pixel.R = rgbVec[0];
- pixel.G = rgbVec[1];
- pixel.B = rgbVec[2];
-
- return pixel;
+ return { R: rgbVec[0], G: rgbVec[1], B: rgbVec[2], A: pixel.A };
}
/**
* @returns the converted pixels in `{R: number, G: number, B: number, A: number}`.
@@ -174,12 +165,7 @@ export function displayP3ToSrgb(pixel: { R: number; G: number; B: number; A: num
* https://drafts.csswg.org/css-color/#predefined-to-predefined
* display-p3 and sRGB share the same white points.
*/
-export function srgbToDisplayP3(pixel: { R: number; G: number; B: number; A: number }): {
- R: number;
- G: number;
- B: number;
- A: number;
-} {
+export function srgbToDisplayP3(pixel: Readonly<RGBA>): RGBA {
assert(
pixel.R !== undefined && pixel.G !== undefined && pixel.B !== undefined,
'color space conversion requires all of R, G and B components'
@@ -192,13 +178,10 @@ export function srgbToDisplayP3(pixel: { R: number; G: number; B: number; A: num
rgbVec = [rgbMatrix[0][0], rgbMatrix[1][0], rgbMatrix[2][0]];
rgbVec = gam_P3(rgbVec);
- pixel.R = rgbVec[0];
- pixel.G = rgbVec[1];
- pixel.B = rgbVec[2];
-
- return pixel;
+ return { R: rgbVec[0], G: rgbVec[1], B: rgbVec[2], A: pixel.A };
}
+export type RGBA = { R: number; G: number; B: number; A: number };
type InPlaceColorConversion = (rgba: {
R: number;
G: number;
@@ -247,9 +230,10 @@ export function makeInPlaceColorConversion({
// This technically represents colors outside the src gamut, so no clamping yet.
if (requireColorSpaceConversion) {
- // WebGPU currently only supports dstColorSpace = 'srgb'.
if (srcColorSpace === 'display-p3' && dstColorSpace === 'srgb') {
- rgba = displayP3ToSrgb(rgba);
+ Object.assign(rgba, displayP3ToSrgb(rgba));
+ } else if (srcColorSpace === 'srgb' && dstColorSpace === 'display-p3') {
+ Object.assign(rgba, srgbToDisplayP3(rgba));
} else {
unreachable();
}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/util/compare.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/util/compare.ts
index 45599d25f6..3aa62d6781 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/util/compare.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/util/compare.ts
@@ -5,10 +5,18 @@ import {
deserializeExpectation,
serializeExpectation,
} from '../shader/execution/expression/case_cache.js';
-import { Expectation, toComparator } from '../shader/execution/expression/expression.js';
+import { Expectation, toComparator } from '../shader/execution/expression/expectation.js';
import BinaryStream from './binary_stream.js';
-import { isFloatValue, Matrix, Scalar, Value, Vector } from './conversion.js';
+import {
+ ArrayValue,
+ isFloatValue,
+ isScalarValue,
+ MatrixValue,
+ ScalarValue,
+ Value,
+ VectorValue,
+} from './conversion.js';
import { FPInterval } from './floating_point.js';
/** Comparison describes the result of a Comparator function. */
@@ -98,9 +106,9 @@ function compareValue(got: Value, expected: Value): Comparison {
}
}
- if (got instanceof Scalar) {
+ if (isScalarValue(got)) {
const g = got;
- const e = expected as Scalar;
+ const e = expected as ScalarValue;
const isFloat = g.type.kind === 'f64' || g.type.kind === 'f32' || g.type.kind === 'f16';
const matched =
(isFloat && (g.value as number) === (e.value as number)) || (!isFloat && g.value === e.value);
@@ -111,8 +119,8 @@ function compareValue(got: Value, expected: Value): Comparison {
};
}
- if (got instanceof Vector) {
- const e = expected as Vector;
+ if (got instanceof VectorValue || got instanceof ArrayValue) {
+ const e = expected as VectorValue | ArrayValue;
const gLen = got.elements.length;
const eLen = e.elements.length;
let matched = gLen === eLen;
@@ -130,8 +138,8 @@ function compareValue(got: Value, expected: Value): Comparison {
};
}
- if (got instanceof Matrix) {
- const e = expected as Matrix;
+ if (got instanceof MatrixValue) {
+ const e = expected as MatrixValue;
const gCols = got.type.cols;
const eCols = e.type.cols;
const gRows = got.type.rows;
@@ -153,7 +161,7 @@ function compareValue(got: Value, expected: Value): Comparison {
};
}
- throw new Error(`unhandled type '${typeof got}`);
+ throw new Error(`unhandled type '${typeof got}'`);
}
/**
@@ -175,7 +183,7 @@ function compareInterval(got: Value, expected: FPInterval): Comparison {
}
}
- if (got instanceof Scalar) {
+ if (isScalarValue(got)) {
const g = got.value as number;
const matched = expected.contains(g);
return {
@@ -197,7 +205,7 @@ function compareInterval(got: Value, expected: FPInterval): Comparison {
*/
function compareVector(got: Value, expected: FPInterval[]): Comparison {
// Check got type
- if (!(got instanceof Vector)) {
+ if (!(got instanceof VectorValue)) {
return {
matched: false,
got: `${Colors.red((typeof got).toString())}(${got})`,
@@ -262,7 +270,7 @@ function convertArrayToString<T>(m: T[]): string {
*/
function compareMatrix(got: Value, expected: FPInterval[][]): Comparison {
// Check got type
- if (!(got instanceof Matrix)) {
+ if (!(got instanceof MatrixValue)) {
return {
matched: false,
got: `${Colors.red((typeof got).toString())}(${got})`,
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/util/constants.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/util/constants.ts
index 5ee819c64e..11f0677316 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/util/constants.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/util/constants.ts
@@ -242,6 +242,21 @@ export const kBit = {
} as const;
export const kValue = {
+ // Limits of i64
+ i64: {
+ positive: {
+ min: BigInt(0n),
+ max: BigInt(9223372036854775807n),
+ },
+ negative: {
+ min: BigInt(-9223372036854775808n),
+ max: BigInt(0n),
+ },
+ isOOB: (val: bigint): boolean => {
+ return val > kValue.i64.positive.max || val < kValue.i64.negative.min;
+ },
+ },
+
// Limits of i32
i32: {
positive: {
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/util/conversion.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/util/conversion.ts
index d98367447d..29d892d14b 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/util/conversion.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/util/conversion.ts
@@ -6,6 +6,7 @@ import { Float16Array } from '../../external/petamoriken/float16/float16.js';
import BinaryStream from './binary_stream.js';
import { kBit } from './constants.js';
import {
+ align,
cartesianProduct,
clamp,
correctlyRoundedF16,
@@ -84,6 +85,8 @@ const workingDataI16 = new Int16Array(workingData);
const workingDataI32 = new Int32Array(workingData);
const workingDataI8 = new Int8Array(workingData);
const workingDataF64 = new Float64Array(workingData);
+const workingDataI64 = new BigInt64Array(workingData);
+const workingDataU64 = new BigUint64Array(workingData);
const workingDataView = new DataView(workingData);
/**
@@ -107,7 +110,7 @@ export function float32ToFloatBits(
assert(mantissaBits <= 23);
if (Number.isNaN(n)) {
- // NaN = all exponent bits true, 1 or more mantissia bits true
+ // NaN = all exponent bits true, 1 or more mantissa bits true
return (((1 << exponentBits) - 1) << mantissaBits) | ((1 << mantissaBits) - 1);
}
@@ -584,6 +587,7 @@ export type ScalarKind =
| 'u32'
| 'u16'
| 'u8'
+ | 'abstract-int'
| 'i32'
| 'i16'
| 'i8'
@@ -593,11 +597,18 @@ export type ScalarKind =
export class ScalarType {
readonly kind: ScalarKind; // The named type
readonly _size: number; // In bytes
- readonly read: (buf: Uint8Array, offset: number) => Scalar; // reads a scalar from a buffer
-
- constructor(kind: ScalarKind, size: number, read: (buf: Uint8Array, offset: number) => Scalar) {
+ readonly _signed: boolean;
+ readonly read: (buf: Uint8Array, offset: number) => ScalarValue; // reads a scalar from a buffer
+
+ constructor(
+ kind: ScalarKind,
+ size: number,
+ signed: boolean,
+ read: (buf: Uint8Array, offset: number) => ScalarValue
+ ) {
this.kind = kind;
this._size = size;
+ this._signed = signed;
this.read = read;
}
@@ -609,32 +620,64 @@ export class ScalarType {
return this._size;
}
- /** Constructs a Scalar of this type with `value` */
- public create(value: number): Scalar {
- switch (this.kind) {
- case 'abstract-float':
- return abstractFloat(value);
- case 'f64':
- return f64(value);
- case 'f32':
- return f32(value);
- case 'f16':
- return f16(value);
- case 'u32':
- return u32(value);
- case 'u16':
- return u16(value);
- case 'u8':
- return u8(value);
- case 'i32':
- return i32(value);
- case 'i16':
- return i16(value);
- case 'i8':
- return i8(value);
- case 'bool':
- return bool(value !== 0);
+ public get alignment(): number {
+ return this._size;
+ }
+
+ public get signed(): boolean {
+ return this._signed;
+ }
+
+ // This allows width to be checked in cases where scalar and vector types are mixed.
+ public get width(): number {
+ return 1;
+ }
+
+ public requiresF16(): boolean {
+ return this.kind === 'f16';
+ }
+
+ /** Constructs a ScalarValue of this type with `value` */
+ public create(value: number | bigint): ScalarValue {
+ switch (typeof value) {
+ case 'number':
+ switch (this.kind) {
+ case 'abstract-float':
+ return abstractFloat(value);
+ case 'abstract-int':
+ return abstractInt(BigInt(value));
+ case 'f64':
+ return f64(value);
+ case 'f32':
+ return f32(value);
+ case 'f16':
+ return f16(value);
+ case 'u32':
+ return u32(value);
+ case 'u16':
+ return u16(value);
+ case 'u8':
+ return u8(value);
+ case 'i32':
+ return i32(value);
+ case 'i16':
+ return i16(value);
+ case 'i8':
+ return i8(value);
+ case 'bool':
+ return bool(value !== 0);
+ }
+ break;
+ case 'bigint':
+ switch (this.kind) {
+ case 'abstract-int':
+ return abstractInt(value);
+ case 'bool':
+ return bool(value !== 0n);
+ }
+ break;
}
+ unreachable(`Scalar<${this.kind}>.create() does not support ${typeof value}`);
}
}
@@ -643,6 +686,20 @@ export class VectorType {
readonly width: number; // Number of elements in the vector
readonly elementType: ScalarType; // Element type
+ // Maps a string representation of a vector type to vector type.
+ private static instances = new Map<string, VectorType>();
+
+ static create(width: number, elementType: ScalarType): VectorType {
+ const key = `${elementType.toString()} ${width}}`;
+ let ty = this.instances.get(key);
+ if (ty !== undefined) {
+ return ty;
+ }
+ ty = new VectorType(width, elementType);
+ this.instances.set(key, ty);
+ return ty;
+ }
+
constructor(width: number, elementType: ScalarType) {
this.width = width;
this.elementType = elementType;
@@ -652,13 +709,13 @@ export class VectorType {
* @returns a vector constructed from the values read from the buffer at the
* given byte offset
*/
- public read(buf: Uint8Array, offset: number): Vector {
- const elements: Array<Scalar> = [];
+ public read(buf: Uint8Array, offset: number): VectorValue {
+ const elements: Array<ScalarValue> = [];
for (let i = 0; i < this.width; i++) {
elements[i] = this.elementType.read(buf, offset);
offset += this.elementType.size;
}
- return new Vector(elements);
+ return new VectorValue(elements);
}
public toString(): string {
@@ -669,29 +726,27 @@ export class VectorType {
return this.elementType.size * this.width;
}
+ public get alignment(): number {
+ return VectorType.alignmentOf(this.width, this.elementType);
+ }
+
+ public static alignmentOf(width: number, elementType: ScalarType) {
+ return elementType.size * (width === 3 ? 4 : width);
+ }
+
/** Constructs a Vector of this type with the given values */
- public create(value: number | readonly number[]): Vector {
+ public create(value: (number | bigint) | readonly (number | bigint)[]): VectorValue {
if (value instanceof Array) {
assert(value.length === this.width);
} else {
value = Array(this.width).fill(value);
}
- return new Vector(value.map(v => this.elementType.create(v)));
+ return new VectorValue(value.map(v => this.elementType.create(v)));
}
-}
-// Maps a string representation of a vector type to vector type.
-const vectorTypes = new Map<string, VectorType>();
-
-export function TypeVec(width: number, elementType: ScalarType): VectorType {
- const key = `${elementType.toString()} ${width}}`;
- let ty = vectorTypes.get(key);
- if (ty !== undefined) {
- return ty;
+ public requiresF16(): boolean {
+ return this.elementType.requiresF16();
}
- ty = new VectorType(width, elementType);
- vectorTypes.set(key, ty);
- return ty;
}
/** MatrixType describes the type of WGSL Matrix. */
@@ -700,6 +755,20 @@ export class MatrixType {
readonly rows: number; // Number of elements per column in the Matrix
readonly elementType: ScalarType; // Element type
+ // Maps a string representation of a Matrix type to Matrix type.
+ private static instances = new Map<string, MatrixType>();
+
+ static create(cols: number, rows: number, elementType: ScalarType): MatrixType {
+ const key = `${elementType.toString()} ${cols} ${rows}`;
+ let ty = this.instances.get(key);
+ if (ty !== undefined) {
+ return ty;
+ }
+ ty = new MatrixType(cols, rows, elementType);
+ this.instances.set(key, ty);
+ return ty;
+ }
+
constructor(cols: number, rows: number, elementType: ScalarType) {
this.cols = cols;
this.rows = rows;
@@ -716,8 +785,8 @@ export class MatrixType {
* @returns a Matrix constructed from the values read from the buffer at the
* given byte offset
*/
- public read(buf: Uint8Array, offset: number): Matrix {
- const elements: Scalar[][] = [...Array(this.cols)].map(_ => [...Array(this.rows)]);
+ public read(buf: Uint8Array, offset: number): MatrixValue {
+ const elements: ScalarValue[][] = [...Array(this.cols)].map(_ => [...Array(this.rows)]);
for (let c = 0; c < this.cols; c++) {
for (let r = 0; r < this.rows; r++) {
elements[c][r] = this.elementType.read(buf, offset);
@@ -729,100 +798,265 @@ export class MatrixType {
offset += this.elementType.size;
}
}
- return new Matrix(elements);
+ return new MatrixValue(elements);
}
public toString(): string {
return `mat${this.cols}x${this.rows}<${this.elementType}>`;
}
+
+ public get size(): number {
+ return VectorType.alignmentOf(this.rows, this.elementType) * this.cols;
+ }
+
+ public get alignment(): number {
+ return VectorType.alignmentOf(this.rows, this.elementType);
+ }
+
+ public requiresF16(): boolean {
+ return this.elementType.requiresF16();
+ }
+
+ /** Constructs a Matrix of this type with the given values */
+ public create(value: (number | bigint) | readonly (number | bigint)[]): MatrixValue {
+ if (value instanceof Array) {
+ assert(value.length === this.cols * this.rows);
+ } else {
+ value = Array(this.cols * this.rows).fill(value);
+ }
+ const columns: (number | bigint)[][] = [];
+ for (let i = 0; i < this.cols; i++) {
+ const start = i * this.rows;
+ columns.push(value.slice(start, start + this.rows));
+ }
+ return new MatrixValue(columns.map(c => c.map(v => this.elementType.create(v))));
+ }
}
-// Maps a string representation of a Matrix type to Matrix type.
-const matrixTypes = new Map<string, MatrixType>();
+/** ArrayType describes the type of WGSL Array. */
+export class ArrayType {
+ readonly count: number; // Number of elements in the array. Zero represents a runtime-sized array.
+ readonly elementType: Type; // Element type
-export function TypeMat(cols: number, rows: number, elementType: ScalarType): MatrixType {
- const key = `${elementType.toString()} ${cols} ${rows}`;
- let ty = matrixTypes.get(key);
- if (ty !== undefined) {
+ // Maps a string representation of a array type to array type.
+ private static instances = new Map<string, ArrayType>();
+
+ static create(count: number, elementType: Type): ArrayType {
+ const key = `${elementType.toString()} ${count}`;
+ let ty = this.instances.get(key);
+ if (ty !== undefined) {
+ return ty;
+ }
+ ty = new ArrayType(count, elementType);
+ this.instances.set(key, ty);
return ty;
}
- ty = new MatrixType(cols, rows, elementType);
- matrixTypes.set(key, ty);
- return ty;
+
+ constructor(count: number, elementType: Type) {
+ this.count = count;
+ this.elementType = elementType;
+ }
+
+ /**
+ * @returns a array constructed from the values read from the buffer at the
+ * given byte offset
+ */
+ public read(buf: Uint8Array, offset: number): ArrayValue {
+ const elements: Array<Value> = [];
+
+ for (let i = 0; i < this.count; i++) {
+ elements[i] = this.elementType.read(buf, offset);
+ offset += this.stride;
+ }
+ return new ArrayValue(elements);
+ }
+
+ public toString(): string {
+ return this.count !== 0
+ ? `array<${this.elementType}, ${this.count}>`
+ : `array<${this.elementType}>`;
+ }
+
+ public get stride(): number {
+ return align(this.elementType.size, this.elementType.alignment);
+ }
+
+ public get size(): number {
+ return this.stride * this.count;
+ }
+
+ public get alignment(): number {
+ return this.elementType.alignment;
+ }
+
+ public requiresF16(): boolean {
+ return this.elementType.requiresF16();
+ }
+
+ /** Constructs an Array of this type with the given values */
+ public create(value: (number | bigint) | readonly (number | bigint)[]): ArrayValue {
+ if (value instanceof Array) {
+ assert(value.length === this.count);
+ } else {
+ value = Array(this.count).fill(value);
+ }
+ return new ArrayValue(value.map(v => this.elementType.create(v)));
+ }
}
-/** Type is a ScalarType, VectorType, or MatrixType. */
-export type Type = ScalarType | VectorType | MatrixType;
+/** ArrayElementType infers the element type of the indexable type A */
+type ArrayElementType<A> = A extends { [index: number]: infer T } ? T : never;
/** Copy bytes from `buf` at `offset` into the working data, then read it out using `workingDataOut` */
-function valueFromBytes(workingDataOut: TypedArrayBufferView, buf: Uint8Array, offset: number) {
+function valueFromBytes<A extends TypedArrayBufferView>(
+ workingDataOut: A,
+ buf: Uint8Array,
+ offset: number
+): ArrayElementType<A> {
for (let i = 0; i < workingDataOut.BYTES_PER_ELEMENT; ++i) {
workingDataU8[i] = buf[offset + i];
}
- return workingDataOut[0];
+ return workingDataOut[0] as ArrayElementType<A>;
}
-export const TypeI32 = new ScalarType('i32', 4, (buf: Uint8Array, offset: number) =>
+const abstractIntType = new ScalarType('abstract-int', 8, true, (buf: Uint8Array, offset: number) =>
+ abstractInt(valueFromBytes(workingDataI64, buf, offset))
+);
+const i32Type = new ScalarType('i32', 4, true, (buf: Uint8Array, offset: number) =>
i32(valueFromBytes(workingDataI32, buf, offset))
);
-export const TypeU32 = new ScalarType('u32', 4, (buf: Uint8Array, offset: number) =>
+const u32Type = new ScalarType('u32', 4, false, (buf: Uint8Array, offset: number) =>
u32(valueFromBytes(workingDataU32, buf, offset))
);
-export const TypeAbstractFloat = new ScalarType(
+const i16Type = new ScalarType('i16', 2, true, (buf: Uint8Array, offset: number) =>
+ i16(valueFromBytes(workingDataI16, buf, offset))
+);
+const u16Type = new ScalarType('u16', 2, false, (buf: Uint8Array, offset: number) =>
+ u16(valueFromBytes(workingDataU16, buf, offset))
+);
+const i8Type = new ScalarType('i8', 1, true, (buf: Uint8Array, offset: number) =>
+ i8(valueFromBytes(workingDataI8, buf, offset))
+);
+const u8Type = new ScalarType('u8', 1, false, (buf: Uint8Array, offset: number) =>
+ u8(valueFromBytes(workingDataU8, buf, offset))
+);
+const abstractFloatType = new ScalarType(
'abstract-float',
8,
+ true,
(buf: Uint8Array, offset: number) => abstractFloat(valueFromBytes(workingDataF64, buf, offset))
);
-export const TypeF64 = new ScalarType('f64', 8, (buf: Uint8Array, offset: number) =>
+const f64Type = new ScalarType('f64', 8, true, (buf: Uint8Array, offset: number) =>
f64(valueFromBytes(workingDataF64, buf, offset))
);
-export const TypeF32 = new ScalarType('f32', 4, (buf: Uint8Array, offset: number) =>
+const f32Type = new ScalarType('f32', 4, true, (buf: Uint8Array, offset: number) =>
f32(valueFromBytes(workingDataF32, buf, offset))
);
-export const TypeI16 = new ScalarType('i16', 2, (buf: Uint8Array, offset: number) =>
- i16(valueFromBytes(workingDataI16, buf, offset))
-);
-export const TypeU16 = new ScalarType('u16', 2, (buf: Uint8Array, offset: number) =>
- u16(valueFromBytes(workingDataU16, buf, offset))
-);
-export const TypeF16 = new ScalarType('f16', 2, (buf: Uint8Array, offset: number) =>
+const f16Type = new ScalarType('f16', 2, true, (buf: Uint8Array, offset: number) =>
f16Bits(valueFromBytes(workingDataU16, buf, offset))
);
-export const TypeI8 = new ScalarType('i8', 1, (buf: Uint8Array, offset: number) =>
- i8(valueFromBytes(workingDataI8, buf, offset))
-);
-export const TypeU8 = new ScalarType('u8', 1, (buf: Uint8Array, offset: number) =>
- u8(valueFromBytes(workingDataU8, buf, offset))
-);
-export const TypeBool = new ScalarType('bool', 4, (buf: Uint8Array, offset: number) =>
+const boolType = new ScalarType('bool', 4, false, (buf: Uint8Array, offset: number) =>
bool(valueFromBytes(workingDataU32, buf, offset) !== 0)
);
+/** Type is a ScalarType, VectorType, MatrixType or ArrayType. */
+export type Type = ScalarType | VectorType | MatrixType | ArrayType;
+
+/** Type holds pre-declared Types along with helper constructor functions. */
+export const Type = {
+ abstractInt: abstractIntType,
+ 'abstract-int': abstractIntType,
+ i32: i32Type,
+ u32: u32Type,
+ i16: i16Type,
+ u16: u16Type,
+ i8: i8Type,
+ u8: u8Type,
+
+ abstractFloat: abstractFloatType,
+ 'abstract-float': abstractFloatType,
+ f64: f64Type,
+ f32: f32Type,
+ f16: f16Type,
+
+ bool: boolType,
+
+ vec: (width: number, elementType: ScalarType) => VectorType.create(width, elementType),
+
+ vec2ai: VectorType.create(2, abstractIntType),
+ vec2i: VectorType.create(2, i32Type),
+ vec2u: VectorType.create(2, u32Type),
+ vec2af: VectorType.create(2, abstractFloatType),
+ vec2f: VectorType.create(2, f32Type),
+ vec2h: VectorType.create(2, f16Type),
+ vec2b: VectorType.create(2, boolType),
+ vec3ai: VectorType.create(3, abstractIntType),
+ vec3i: VectorType.create(3, i32Type),
+ vec3u: VectorType.create(3, u32Type),
+ vec3af: VectorType.create(3, abstractFloatType),
+ vec3f: VectorType.create(3, f32Type),
+ vec3h: VectorType.create(3, f16Type),
+ vec3b: VectorType.create(3, boolType),
+ vec4ai: VectorType.create(4, abstractIntType),
+ vec4i: VectorType.create(4, i32Type),
+ vec4u: VectorType.create(4, u32Type),
+ vec4af: VectorType.create(4, abstractFloatType),
+ vec4f: VectorType.create(4, f32Type),
+ vec4h: VectorType.create(4, f16Type),
+ vec4b: VectorType.create(4, boolType),
+
+ mat: (cols: number, rows: number, elementType: ScalarType) =>
+ MatrixType.create(cols, rows, elementType),
+
+ mat2x2f: MatrixType.create(2, 2, f32Type),
+ mat2x2h: MatrixType.create(2, 2, f16Type),
+ mat3x2f: MatrixType.create(3, 2, f32Type),
+ mat3x2h: MatrixType.create(3, 2, f16Type),
+ mat4x2f: MatrixType.create(4, 2, f32Type),
+ mat4x2h: MatrixType.create(4, 2, f16Type),
+ mat2x3f: MatrixType.create(2, 3, f32Type),
+ mat2x3h: MatrixType.create(2, 3, f16Type),
+ mat3x3f: MatrixType.create(3, 3, f32Type),
+ mat3x3h: MatrixType.create(3, 3, f16Type),
+ mat4x3f: MatrixType.create(4, 3, f32Type),
+ mat4x3h: MatrixType.create(4, 3, f16Type),
+ mat2x4f: MatrixType.create(2, 4, f32Type),
+ mat2x4h: MatrixType.create(2, 4, f16Type),
+ mat3x4f: MatrixType.create(3, 4, f32Type),
+ mat3x4h: MatrixType.create(3, 4, f16Type),
+ mat4x4f: MatrixType.create(4, 4, f32Type),
+ mat4x4h: MatrixType.create(4, 4, f16Type),
+
+ array: (count: number, elementType: Type) => ArrayType.create(count, elementType),
+};
+
/** @returns the ScalarType from the ScalarKind */
export function scalarType(kind: ScalarKind): ScalarType {
switch (kind) {
case 'abstract-float':
- return TypeAbstractFloat;
+ return Type.abstractFloat;
case 'f64':
- return TypeF64;
+ return Type.f64;
case 'f32':
- return TypeF32;
+ return Type.f32;
case 'f16':
- return TypeF16;
+ return Type.f16;
case 'u32':
- return TypeU32;
+ return Type.u32;
case 'u16':
- return TypeU16;
+ return Type.u16;
case 'u8':
- return TypeU8;
+ return Type.u8;
+ case 'abstract-int':
+ return Type.abstractInt;
case 'i32':
- return TypeI32;
+ return Type.i32;
case 'i16':
- return TypeI16;
+ return Type.i16;
case 'i8':
- return TypeI8;
+ return Type.i8;
case 'bool':
- return TypeBool;
+ return Type.bool;
}
}
@@ -837,23 +1071,54 @@ export function numElementsOf(ty: Type): number {
if (ty instanceof MatrixType) {
return ty.cols * ty.rows;
}
+ if (ty instanceof ArrayType) {
+ return ty.count;
+ }
throw new Error(`unhandled type ${ty}`);
}
/** @returns the scalar elements of the given Value */
-export function elementsOf(value: Value): Scalar[] {
- if (value instanceof Scalar) {
+export function elementsOf(value: Value): Value[] {
+ if (isScalarValue(value)) {
+ return [value];
+ }
+ if (value instanceof VectorValue) {
+ return value.elements;
+ }
+ if (value instanceof MatrixValue) {
+ return value.elements.flat();
+ }
+ if (value instanceof ArrayValue) {
+ return value.elements;
+ }
+ throw new Error(`unhandled value ${value}`);
+}
+
+/** @returns the scalar elements of the given Value */
+export function scalarElementsOf(value: Value): ScalarValue[] {
+ if (isScalarValue(value)) {
return [value];
}
- if (value instanceof Vector) {
+ if (value instanceof VectorValue) {
return value.elements;
}
- if (value instanceof Matrix) {
+ if (value instanceof MatrixValue) {
return value.elements.flat();
}
+ if (value instanceof ArrayValue) {
+ return value.elements.map(els => scalarElementsOf(els)).flat();
+ }
throw new Error(`unhandled value ${value}`);
}
+/** @returns the inner element type of the given type */
+export function elementTypeOf(t: Type) {
+ if (t instanceof ScalarType) {
+ return t;
+ }
+ return t.elementType;
+}
+
/** @returns the scalar (element) type of the given Type */
export function scalarTypeOf(ty: Type): ScalarType {
if (ty instanceof ScalarType) {
@@ -865,27 +1130,93 @@ export function scalarTypeOf(ty: Type): ScalarType {
if (ty instanceof MatrixType) {
return ty.elementType;
}
+ if (ty instanceof ArrayType) {
+ return scalarTypeOf(ty.elementType);
+ }
throw new Error(`unhandled type ${ty}`);
}
-/** ScalarValue is the JS type that can be held by a Scalar */
-type ScalarValue = boolean | number;
+/**
+ * @returns the implicit concretized type of the given Type.
+ * @param abstractIntToF32 if true, returns f32 for abstractInt else i32
+ * Example: vec3<abstract-float> -> vec3<float>
+ */
+export function concreteTypeOf(ty: Type, allowedScalarTypes?: Type[]): Type {
+ if (allowedScalarTypes && allowedScalarTypes.length > 0) {
+ // https://www.w3.org/TR/WGSL/#conversion-rank
+ switch (ty) {
+ case Type.abstractInt:
+ if (allowedScalarTypes.includes(Type.i32)) {
+ return Type.i32;
+ }
+ if (allowedScalarTypes.includes(Type.u32)) {
+ return Type.u32;
+ }
+ // fallthrough.
+ case Type.abstractFloat:
+ if (allowedScalarTypes.includes(Type.f32)) {
+ return Type.f32;
+ }
+ if (allowedScalarTypes.includes(Type.f16)) {
+ return Type.f32;
+ }
+ throw new Error(`no ${ty}`);
+ }
+ } else {
+ switch (ty) {
+ case Type.abstractInt:
+ return Type.i32;
+ case Type.abstractFloat:
+ return Type.f32;
+ }
+ }
+ if (ty instanceof ScalarType) {
+ return ty;
+ }
+ if (ty instanceof VectorType) {
+ return Type.vec(ty.width, concreteTypeOf(ty.elementType, allowedScalarTypes) as ScalarType);
+ }
+ if (ty instanceof MatrixType) {
+ return Type.mat(
+ ty.cols,
+ ty.rows,
+ concreteTypeOf(ty.elementType, allowedScalarTypes) as ScalarType
+ );
+ }
+ if (ty instanceof ArrayType) {
+ return Type.array(ty.count, concreteTypeOf(ty.elementType, allowedScalarTypes));
+ }
+ throw new Error(`unhandled type ${ty}`);
+}
-/** Class that encapsulates a single scalar value of various types. */
-export class Scalar {
- readonly value: ScalarValue; // The scalar value
- readonly type: ScalarType; // The type of the scalar
+function hex(sizeInBytes: number, bitsLow: number, bitsHigh?: number) {
+ let hex = '';
+ workingDataU32[0] = bitsLow;
+ if (bitsHigh !== undefined) {
+ workingDataU32[1] = bitsHigh;
+ }
+ for (let i = 0; i < sizeInBytes; ++i) {
+ hex = workingDataU8[i].toString(16).padStart(2, '0') + hex;
+ }
+ return `0x${hex}`;
+}
+
+function withPoint(x: number) {
+ const str = `${x}`;
+ return str.indexOf('.') > 0 || str.indexOf('e') > 0 ? str : `${str}.0`;
+}
- // The scalar value, packed in one or two 32-bit unsigned integers.
- // Whether or not the bits1 is used depends on `this.type.size`.
- readonly bits1: number;
- readonly bits0: number;
+/** Class that encapsulates a single abstract-int value. */
+export class AbstractIntValue {
+ readonly value: bigint; // The abstract-integer value
+ readonly bitsLow: number; // The low 32 bits of the abstract-integer value.
+ readonly bitsHigh: number; // The high 32 bits of the abstract-integer value.
+ readonly type = Type.abstractInt; // The type of the value.
- public constructor(type: ScalarType, value: ScalarValue, bits1: number, bits0: number) {
+ public constructor(value: bigint, bitsLow: number, bitsHigh: number) {
this.value = value;
- this.type = type;
- this.bits1 = bits1;
- this.bits0 = bits0;
+ this.bitsLow = bitsLow;
+ this.bitsHigh = bitsHigh;
}
/**
@@ -894,200 +1225,583 @@ export class Scalar {
* @param offset the offset in buffer, in units of `buffer`
*/
public copyTo(buffer: TypedArrayBufferView, offset: number) {
- assert(this.type.kind !== 'f64', `Copying f64 values to/from buffers is not defined`);
- workingDataU32[1] = this.bits1;
- workingDataU32[0] = this.bits0;
- for (let i = 0; i < this.type.size; i++) {
+ workingDataU32[0] = this.bitsLow;
+ workingDataU32[1] = this.bitsHigh;
+ for (let i = 0; i < 8; i++) {
buffer[offset + i] = workingDataU8[i];
}
}
+ /** @returns the WGSL representation of this scalar value */
+ public wgsl(): string {
+ // WGSL parses negative numbers as a negated positive.
+ // This means '-9223372036854775808' parses as `-' & '9223372036854775808', so must be written as
+ // '(-9223372036854775807 - 1)' in WGSL, because '9223372036854775808' is not a valid AbstractInt.
+ if (this.value === -9223372036854775808n) {
+ return `(-9223372036854775807 - 1)`;
+ }
+ return `${this.value}`;
+ }
+
+ public toString(): string {
+ return `${Colors.bold(this.value.toString())} (${hex(8, this.bitsLow, this.bitsHigh)})`;
+ }
+}
+
+/** Class that encapsulates a single abstract-float value. */
+export class AbstractFloatValue {
+ readonly value: number; // The f32 value
+ readonly bitsLow: number; // The low 32 bits of the abstract-float value.
+ readonly bitsHigh: number; // The high 32 bits of the abstract-float value.
+ readonly type = Type.abstractFloat; // The type of the value.
+
+ public constructor(value: number, bitsLow: number, bitsHigh: number) {
+ this.value = value;
+ this.bitsLow = bitsLow;
+ this.bitsHigh = bitsHigh;
+ }
+
/**
- * @returns the WGSL representation of this scalar value
+ * Copies the scalar value to the buffer at the provided byte offset.
+ * @param buffer the destination buffer
+ * @param offset the offset in buffer, in units of `buffer`
*/
+ public copyTo(buffer: TypedArrayBufferView, offset: number) {
+ workingDataU32[0] = this.bitsLow;
+ workingDataU32[1] = this.bitsHigh;
+ for (let i = 0; i < 8; i++) {
+ buffer[offset + i] = workingDataU8[i];
+ }
+ }
+
+ /** @returns the WGSL representation of this scalar value */
public wgsl(): string {
- const withPoint = (x: number) => {
- const str = `${x}`;
- return str.indexOf('.') > 0 || str.indexOf('e') > 0 ? str : `${str}.0`;
- };
- if (isFinite(this.value as number)) {
- switch (this.type.kind) {
- case 'abstract-float':
- return `${withPoint(this.value as number)}`;
- case 'f64':
- return `${withPoint(this.value as number)}`;
- case 'f32':
- return `${withPoint(this.value as number)}f`;
- case 'f16':
- return `${withPoint(this.value as number)}h`;
- case 'u32':
- return `${this.value}u`;
- case 'i32':
- return `i32(${this.value})`;
- case 'bool':
- return `${this.value}`;
+ return `${withPoint(this.value)}`;
+ }
+
+ public toString(): string {
+ switch (this.value) {
+ case Infinity:
+ case -Infinity:
+ return Colors.bold(this.value.toString());
+ default: {
+ let str = this.value.toString();
+ str = str.indexOf('.') > 0 || str.indexOf('e') > 0 ? str : `${str}.0`;
+ return isSubnormalNumberF64(this.value.valueOf())
+ ? `${Colors.bold(str)} (${hex(8, this.bitsLow, this.bitsHigh)} subnormal)`
+ : `${Colors.bold(str)} (${hex(8, this.bitsLow, this.bitsHigh)})`;
}
}
- throw new Error(
- `scalar of value ${this.value} and type ${this.type} has no WGSL representation`
- );
+ }
+}
+
+/** Class that encapsulates a single i32 value. */
+export class I32Value {
+ readonly value: number; // The i32 value
+ readonly bits: number; // The i32 value, bitcast to a 32-bit integer.
+ readonly type = Type.i32; // The type of the value.
+
+ public constructor(value: number, bits: number) {
+ this.value = value;
+ this.bits = bits;
+ }
+
+ /**
+ * Copies the scalar value to the buffer at the provided byte offset.
+ * @param buffer the destination buffer
+ * @param offset the offset in buffer, in units of `buffer`
+ */
+ public copyTo(buffer: TypedArrayBufferView, offset: number) {
+ workingDataU32[0] = this.bits;
+ for (let i = 0; i < 4; i++) {
+ buffer[offset + i] = workingDataU8[i];
+ }
+ }
+
+ /** @returns the WGSL representation of this scalar value */
+ public wgsl(): string {
+ return `i32(${this.value})`;
}
public toString(): string {
- if (this.type.kind === 'bool') {
- return Colors.bold(this.value.toString());
+ return `${Colors.bold(this.value.toString())} (${hex(4, this.bits)})`;
+ }
+}
+
+/** Class that encapsulates a single u32 value. */
+export class U32Value {
+ readonly value: number; // The u32 value
+ readonly type = Type.u32; // The type of the value.
+
+ public constructor(value: number) {
+ this.value = value;
+ }
+
+ /**
+ * Copies the scalar value to the buffer at the provided byte offset.
+ * @param buffer the destination buffer
+ * @param offset the offset in buffer, in units of `buffer`
+ */
+ public copyTo(buffer: TypedArrayBufferView, offset: number) {
+ workingDataU32[0] = this.value;
+ for (let i = 0; i < 4; i++) {
+ buffer[offset + i] = workingDataU8[i];
}
+ }
+
+ /** @returns the WGSL representation of this scalar value */
+ public wgsl(): string {
+ return `${this.value}u`;
+ }
+
+ public toString(): string {
+ return `${Colors.bold(this.value.toString())} (${hex(4, this.value)})`;
+ }
+}
+
+/**
+ * Class that encapsulates a single i16 value.
+ * @note type does not exist in WGSL yet
+ */
+export class I16Value {
+ readonly value: number; // The i16 value
+ readonly bits: number; // The i16 value, bitcast to a 16-bit integer.
+ readonly type = Type.i16; // The type of the value.
+
+ public constructor(value: number, bits: number) {
+ this.value = value;
+ this.bits = bits;
+ }
+
+ /**
+ * Copies the scalar value to the buffer at the provided byte offset.
+ * @param buffer the destination buffer
+ * @param offset the offset in buffer, in units of `buffer`
+ */
+ public copyTo(buffer: TypedArrayBufferView, offset: number) {
+ workingDataU16[0] = this.bits;
+ for (let i = 0; i < 4; i++) {
+ buffer[offset + i] = workingDataU8[i];
+ }
+ }
+
+ /** @returns the WGSL representation of this scalar value */
+ public wgsl(): string {
+ return `i16(${this.value})`;
+ }
+
+ public toString(): string {
+ return `${Colors.bold(this.value.toString())} (${hex(2, this.bits)})`;
+ }
+}
+
+/**
+ * Class that encapsulates a single u16 value.
+ * @note type does not exist in WGSL yet
+ */
+export class U16Value {
+ readonly value: number; // The u16 value
+ readonly type = Type.u16; // The type of the value.
+
+ public constructor(value: number) {
+ this.value = value;
+ }
+
+ /**
+ * Copies the scalar value to the buffer at the provided byte offset.
+ * @param buffer the destination buffer
+ * @param offset the offset in buffer, in units of `buffer`
+ */
+ public copyTo(buffer: TypedArrayBufferView, offset: number) {
+ workingDataU16[0] = this.value;
+ for (let i = 0; i < 2; i++) {
+ buffer[offset + i] = workingDataU8[i];
+ }
+ }
+
+ /** @returns the WGSL representation of this scalar value */
+ public wgsl(): string {
+ assert(false, 'u16 is not a WGSL type');
+ return `u16(${this.value})`;
+ }
+
+ public toString(): string {
+ return `${Colors.bold(this.value.toString())} (${hex(2, this.value)})`;
+ }
+}
+
+/**
+ * Class that encapsulates a single i8 value.
+ * @note type does not exist in WGSL yet
+ */
+export class I8Value {
+ readonly value: number; // The i8 value
+ readonly bits: number; // The i8 value, bitcast to a 8-bit integer.
+ readonly type = Type.i8; // The type of the value.
+
+ public constructor(value: number, bits: number) {
+ this.value = value;
+ this.bits = bits;
+ }
+
+ /**
+ * Copies the scalar value to the buffer at the provided byte offset.
+ * @param buffer the destination buffer
+ * @param offset the offset in buffer, in units of `buffer`
+ */
+ public copyTo(buffer: TypedArrayBufferView, offset: number) {
+ workingDataU8[0] = this.bits;
+ for (let i = 0; i < 4; i++) {
+ buffer[offset + i] = workingDataU8[i];
+ }
+ }
+
+ /** @returns the WGSL representation of this scalar value */
+ public wgsl(): string {
+ return `i8(${this.value})`;
+ }
+
+ public toString(): string {
+ return `${Colors.bold(this.value.toString())} (${hex(2, this.bits)})`;
+ }
+}
+
+/**
+ * Class that encapsulates a single u8 value.
+ * @note type does not exist in WGSL yet
+ */
+export class U8Value {
+ readonly value: number; // The u8 value
+ readonly type = Type.u8; // The type of the value.
+
+ public constructor(value: number) {
+ this.value = value;
+ }
+
+ /**
+ * Copies the scalar value to the buffer at the provided byte offset.
+ * @param buffer the destination buffer
+ * @param offset the offset in buffer, in units of `buffer`
+ */
+ public copyTo(buffer: TypedArrayBufferView, offset: number) {
+ workingDataU8[0] = this.value;
+ for (let i = 0; i < 2; i++) {
+ buffer[offset + i] = workingDataU8[i];
+ }
+ }
+
+ /** @returns the WGSL representation of this scalar value */
+ public wgsl(): string {
+ assert(false, 'u8 is not a WGSL type');
+ return `u8(${this.value})`;
+ }
+
+ public toString(): string {
+ return `${Colors.bold(this.value.toString())} (${hex(2, this.value)})`;
+ }
+}
+
+/**
+ * Class that encapsulates a single f64 value
+ * @note type does not exist in WGSL yet
+ */
+export class F64Value {
+ readonly value: number; // The f32 value
+ readonly bitsLow: number; // The low 32 bits of the abstract-float value.
+ readonly bitsHigh: number; // The high 32 bits of the abstract-float value.
+ readonly type = Type.f64; // The type of the value.
+
+ public constructor(value: number, bitsLow: number, bitsHigh: number) {
+ this.value = value;
+ this.bitsLow = bitsLow;
+ this.bitsHigh = bitsHigh;
+ }
+
+ /**
+ * Copies the scalar value to the buffer at the provided byte offset.
+ * @param buffer the destination buffer
+ * @param offset the offset in buffer, in units of `buffer`
+ */
+ public copyTo(buffer: TypedArrayBufferView, offset: number) {
+ workingDataU32[0] = this.bitsLow;
+ workingDataU32[1] = this.bitsHigh;
+ for (let i = 0; i < 8; i++) {
+ buffer[offset + i] = workingDataU8[i];
+ }
+ }
+
+ /** @returns the WGSL representation of this scalar value */
+ public wgsl(): string {
+ assert(false, 'f64 is not a WGSL type');
+ return `${withPoint(this.value)}`;
+ }
+
+ public toString(): string {
switch (this.value) {
case Infinity:
case -Infinity:
return Colors.bold(this.value.toString());
default: {
- workingDataU32[1] = this.bits1;
- workingDataU32[0] = this.bits0;
- let hex = '';
- for (let i = 0; i < this.type.size; ++i) {
- hex = workingDataU8[i].toString(16).padStart(2, '0') + hex;
- }
- const n = this.value as Number;
- if (n !== null && isFloatValue(this)) {
- let str = this.value.toString();
- str = str.indexOf('.') > 0 || str.indexOf('e') > 0 ? str : `${str}.0`;
- switch (this.type.kind) {
- case 'abstract-float':
- return isSubnormalNumberF64(n.valueOf())
- ? `${Colors.bold(str)} (0x${hex} subnormal)`
- : `${Colors.bold(str)} (0x${hex})`;
- case 'f64':
- return isSubnormalNumberF64(n.valueOf())
- ? `${Colors.bold(str)} (0x${hex} subnormal)`
- : `${Colors.bold(str)} (0x${hex})`;
- case 'f32':
- return isSubnormalNumberF32(n.valueOf())
- ? `${Colors.bold(str)} (0x${hex} subnormal)`
- : `${Colors.bold(str)} (0x${hex})`;
- case 'f16':
- return isSubnormalNumberF16(n.valueOf())
- ? `${Colors.bold(str)} (0x${hex} subnormal)`
- : `${Colors.bold(str)} (0x${hex})`;
- default:
- unreachable(
- `Printing of floating point kind ${this.type.kind} is not implemented...`
- );
- }
- }
- return `${Colors.bold(this.value.toString())} (0x${hex})`;
+ let str = this.value.toString();
+ str = str.indexOf('.') > 0 || str.indexOf('e') > 0 ? str : `${str}.0`;
+ return isSubnormalNumberF64(this.value.valueOf())
+ ? `${Colors.bold(str)} (${hex(8, this.bitsLow, this.bitsHigh)} subnormal)`
+ : `${Colors.bold(str)} (${hex(8, this.bitsLow, this.bitsHigh)})`;
}
}
}
}
-export interface ScalarBuilder {
- (value: number): Scalar;
+/** Class that encapsulates a single f32 value. */
+export class F32Value {
+ readonly value: number; // The f32 value
+ readonly bits: number; // The f32 value, bitcast to a 32-bit integer.
+ readonly type = Type.f32; // The type of the value.
+
+ public constructor(value: number, bits: number) {
+ this.value = value;
+ this.bits = bits;
+ }
+
+ /**
+ * Copies the scalar value to the buffer at the provided byte offset.
+ * @param buffer the destination buffer
+ * @param offset the offset in buffer, in units of `buffer`
+ */
+ public copyTo(buffer: TypedArrayBufferView, offset: number) {
+ workingDataU32[0] = this.bits;
+ for (let i = 0; i < 4; i++) {
+ buffer[offset + i] = workingDataU8[i];
+ }
+ }
+
+ /** @returns the WGSL representation of this scalar value */
+ public wgsl(): string {
+ return `${withPoint(this.value)}f`;
+ }
+
+ public toString(): string {
+ switch (this.value) {
+ case Infinity:
+ case -Infinity:
+ return Colors.bold(this.value.toString());
+ default: {
+ let str = this.value.toString();
+ str = str.indexOf('.') > 0 || str.indexOf('e') > 0 ? str : `${str}.0`;
+ return isSubnormalNumberF32(this.value.valueOf())
+ ? `${Colors.bold(str)} (${hex(4, this.bits)} subnormal)`
+ : `${Colors.bold(str)} (${hex(4, this.bits)})`;
+ }
+ }
+ }
}
-/** Create a Scalar of `type` by storing `value` as an element of `workingDataArray` and retrieving it.
- * The working data array *must* be an alias of `workingData`.
- */
-function scalarFromValue(
- type: ScalarType,
- workingDataArray: TypedArrayBufferView,
- value: number
-): Scalar {
- // Clear all bits of the working data since `value` may be smaller; the upper bits should be 0.
- workingDataU32[1] = 0;
- workingDataU32[0] = 0;
- workingDataArray[0] = value;
- return new Scalar(type, workingDataArray[0], workingDataU32[1], workingDataU32[0]);
-}
-
-/** Create a Scalar of `type` by storing `value` as an element of `workingDataStoreArray` and
- * reinterpreting it as an element of `workingDataLoadArray`.
- * Both working data arrays *must* be aliases of `workingData`.
- */
-function scalarFromBits(
- type: ScalarType,
- workingDataStoreArray: TypedArrayBufferView,
- workingDataLoadArray: TypedArrayBufferView,
- bits: number
-): Scalar {
- // Clear all bits of the working data since `value` may be smaller; the upper bits should be 0.
- workingDataU32[1] = 0;
- workingDataU32[0] = 0;
- workingDataStoreArray[0] = bits;
- return new Scalar(type, workingDataLoadArray[0], workingDataU32[1], workingDataU32[0]);
+/** Class that encapsulates a single f16 value. */
+export class F16Value {
+ readonly value: number; // The f16 value
+ readonly bits: number; // The f16 value, bitcast to a 16-bit integer.
+ readonly type = Type.f16; // The type of the value.
+
+ public constructor(value: number, bits: number) {
+ this.value = value;
+ this.bits = bits;
+ }
+
+ /**
+ * Copies the scalar value to the buffer at the provided byte offset.
+ * @param buffer the destination buffer
+ * @param offset the offset in buffer, in units of `buffer`
+ */
+ public copyTo(buffer: TypedArrayBufferView, offset: number) {
+ workingDataU16[0] = this.bits;
+ for (let i = 0; i < 2; i++) {
+ buffer[offset + i] = workingDataU8[i];
+ }
+ }
+
+ /** @returns the WGSL representation of this scalar value */
+ public wgsl(): string {
+ return `${withPoint(this.value)}h`;
+ }
+
+ public toString(): string {
+ switch (this.value) {
+ case Infinity:
+ case -Infinity:
+ return Colors.bold(this.value.toString());
+ default: {
+ let str = this.value.toString();
+ str = str.indexOf('.') > 0 || str.indexOf('e') > 0 ? str : `${str}.0`;
+ return isSubnormalNumberF16(this.value.valueOf())
+ ? `${Colors.bold(str)} (${hex(2, this.bits)} subnormal)`
+ : `${Colors.bold(str)} (${hex(2, this.bits)})`;
+ }
+ }
+ }
}
+/** Class that encapsulates a single bool value. */
+export class BoolValue {
+ readonly value: boolean; // The bool value
+ readonly type = Type.bool; // The type of the value.
-/** Create an AbstractFloat from a numeric value, a JS `number`. */
-export const abstractFloat = (value: number): Scalar =>
- scalarFromValue(TypeAbstractFloat, workingDataF64, value);
+ public constructor(value: boolean) {
+ this.value = value;
+ }
-/** Create an f64 from a numeric value, a JS `number`. */
-export const f64 = (value: number): Scalar => scalarFromValue(TypeF64, workingDataF64, value);
+ /**
+ * Copies the scalar value to the buffer at the provided byte offset.
+ * @param buffer the destination buffer
+ * @param offset the offset in buffer, in units of `buffer`
+ */
+ public copyTo(buffer: TypedArrayBufferView, offset: number) {
+ buffer[offset] = this.value ? 1 : 0;
+ }
-/** Create an f32 from a numeric value, a JS `number`. */
-export const f32 = (value: number): Scalar => scalarFromValue(TypeF32, workingDataF32, value);
+ /** @returns the WGSL representation of this scalar value */
+ public wgsl(): string {
+ return this.value.toString();
+ }
-/** Create an f16 from a numeric value, a JS `number`. */
-export const f16 = (value: number): Scalar => scalarFromValue(TypeF16, workingDataF16, value);
+ public toString(): string {
+ return Colors.bold(this.value.toString());
+ }
+}
-/** Create an f32 from a bit representation, a uint32 represented as a JS `number`. */
-export const f32Bits = (bits: number): Scalar =>
- scalarFromBits(TypeF32, workingDataU32, workingDataF32, bits);
+/** Scalar represents all the scalar value types */
+export type ScalarValue =
+ | AbstractIntValue
+ | AbstractFloatValue
+ | I32Value
+ | U32Value
+ | I16Value
+ | U16Value
+ | I8Value
+ | U8Value
+ | F64Value
+ | F32Value
+ | F16Value
+ | BoolValue;
+
+export interface ScalarBuilder<T> {
+ (value: T): ScalarValue;
+}
-/** Create an f16 from a bit representation, a uint16 represented as a JS `number`. */
-export const f16Bits = (bits: number): Scalar =>
- scalarFromBits(TypeF16, workingDataU16, workingDataF16, bits);
+export function isScalarValue(value: object): value is ScalarValue {
+ return (
+ value instanceof AbstractIntValue ||
+ value instanceof AbstractFloatValue ||
+ value instanceof I32Value ||
+ value instanceof U32Value ||
+ value instanceof I16Value ||
+ value instanceof U16Value ||
+ value instanceof I8Value ||
+ value instanceof U8Value ||
+ value instanceof F64Value ||
+ value instanceof F32Value ||
+ value instanceof F16Value ||
+ value instanceof BoolValue
+ );
+}
-/** Create an i32 from a numeric value, a JS `number`. */
-export const i32 = (value: number): Scalar => scalarFromValue(TypeI32, workingDataI32, value);
+/** Create an AbstractInt from a numeric value, a JS `bigint`. */
+export function abstractInt(value: bigint) {
+ workingDataI64[0] = value;
+ return new AbstractIntValue(workingDataI64[0], workingDataU32[0], workingDataU32[1]);
+}
-/** Create an i16 from a numeric value, a JS `number`. */
-export const i16 = (value: number): Scalar => scalarFromValue(TypeI16, workingDataI16, value);
+/** Create an AbstractInt from a bit representation, a uint64 represented as a JS `bigint`. */
+export function abstractIntBits(value: bigint) {
+ workingDataU64[0] = value;
+ return new AbstractIntValue(workingDataI64[0], workingDataU32[0], workingDataU32[1]);
+}
-/** Create an i8 from a numeric value, a JS `number`. */
-export const i8 = (value: number): Scalar => scalarFromValue(TypeI8, workingDataI8, value);
+/** Create an AbstractFloat from a numeric value, a JS `number`. */
+export function abstractFloat(value: number) {
+ workingDataF64[0] = value;
+ return new AbstractFloatValue(workingDataF64[0], workingDataU32[0], workingDataU32[1]);
+}
+
+/** Create an i32 from a numeric value, a JS `number`. */
+export function i32(value: number) {
+ workingDataI32[0] = value;
+ return new I32Value(workingDataI32[0], workingDataU32[0]);
+}
/** Create an i32 from a bit representation, a uint32 represented as a JS `number`. */
-export const i32Bits = (bits: number): Scalar =>
- scalarFromBits(TypeI32, workingDataU32, workingDataI32, bits);
+export function i32Bits(bits: number) {
+ workingDataU32[0] = bits;
+ return new I32Value(workingDataI32[0], workingDataU32[0]);
+}
-/** Create an i16 from a bit representation, a uint16 represented as a JS `number`. */
-export const i16Bits = (bits: number): Scalar =>
- scalarFromBits(TypeI16, workingDataU16, workingDataI16, bits);
+/** Create a u32 from a numeric value, a JS `number`. */
+export function u32(value: number) {
+ workingDataU32[0] = value;
+ return new U32Value(workingDataU32[0]);
+}
-/** Create an i8 from a bit representation, a uint8 represented as a JS `number`. */
-export const i8Bits = (bits: number): Scalar =>
- scalarFromBits(TypeI8, workingDataU8, workingDataI8, bits);
+/** Create a u32 from a bit representation, a uint32 represented as a JS `number`. */
+export function u32Bits(bits: number) {
+ workingDataU32[0] = bits;
+ return new U32Value(workingDataU32[0]);
+}
-/** Create a u32 from a numeric value, a JS `number`. */
-export const u32 = (value: number): Scalar => scalarFromValue(TypeU32, workingDataU32, value);
+/** Create an i16 from a numeric value, a JS `number`. */
+export function i16(value: number) {
+ workingDataI16[0] = value;
+ return new I16Value(workingDataI16[0], workingDataU16[0]);
+}
/** Create a u16 from a numeric value, a JS `number`. */
-export const u16 = (value: number): Scalar => scalarFromValue(TypeU16, workingDataU16, value);
+export function u16(value: number) {
+ workingDataU16[0] = value;
+ return new U16Value(workingDataU16[0]);
+}
+
+/** Create an i8 from a numeric value, a JS `number`. */
+export function i8(value: number) {
+ workingDataI8[0] = value;
+ return new I8Value(workingDataI8[0], workingDataU8[0]);
+}
/** Create a u8 from a numeric value, a JS `number`. */
-export const u8 = (value: number): Scalar => scalarFromValue(TypeU8, workingDataU8, value);
+export function u8(value: number) {
+ workingDataU8[0] = value;
+ return new U8Value(workingDataU8[0]);
+}
+
+/** Create an f64 from a numeric value, a JS `number`. */
+export function f64(value: number) {
+ workingDataF64[0] = value;
+ return new F64Value(workingDataF64[0], workingDataU32[0], workingDataU32[1]);
+}
-/** Create an u32 from a bit representation, a uint32 represented as a JS `number`. */
-export const u32Bits = (bits: number): Scalar =>
- scalarFromBits(TypeU32, workingDataU32, workingDataU32, bits);
+/** Create an f32 from a numeric value, a JS `number`. */
+export function f32(value: number) {
+ workingDataF32[0] = value;
+ return new F32Value(workingDataF32[0], workingDataU32[0]);
+}
+
+/** Create an f32 from a bit representation, a uint32 represented as a JS `number`. */
+export function f32Bits(bits: number) {
+ workingDataU32[0] = bits;
+ return new F32Value(workingDataF32[0], workingDataU32[0]);
+}
-/** Create an u16 from a bit representation, a uint16 represented as a JS `number`. */
-export const u16Bits = (bits: number): Scalar =>
- scalarFromBits(TypeU16, workingDataU16, workingDataU16, bits);
+/** Create an f16 from a numeric value, a JS `number`. */
+export function f16(value: number) {
+ workingDataF16[0] = value;
+ return new F16Value(workingDataF16[0], workingDataU16[0]);
+}
-/** Create an u8 from a bit representation, a uint8 represented as a JS `number`. */
-export const u8Bits = (bits: number): Scalar =>
- scalarFromBits(TypeU8, workingDataU8, workingDataU8, bits);
+/** Create an f16 from a bit representation, a uint16 represented as a JS `number`. */
+export function f16Bits(bits: number) {
+ workingDataU16[0] = bits;
+ return new F16Value(workingDataF16[0], workingDataU16[0]);
+}
/** Create a boolean value. */
-export function bool(value: boolean): Scalar {
- // WGSL does not support using 'bool' types directly in storage / uniform
- // buffers, so instead we pack booleans in a u32, where 'false' is zero and
- // 'true' is any non-zero value.
- workingDataU32[0] = value ? 1 : 0;
- workingDataU32[1] = 0;
- return new Scalar(TypeBool, value, workingDataU32[1], workingDataU32[0]);
+export function bool(value: boolean): ScalarValue {
+ return new BoolValue(value);
}
/** A 'true' literal value */
@@ -1099,11 +1813,11 @@ export const False = bool(false);
/**
* Class that encapsulates a vector value.
*/
-export class Vector {
- readonly elements: Array<Scalar>;
+export class VectorValue {
+ readonly elements: Array<ScalarValue>;
readonly type: VectorType;
- public constructor(elements: Array<Scalar>) {
+ public constructor(elements: Array<ScalarValue>) {
if (elements.length < 2 || elements.length > 4) {
throw new Error(`vector element count must be between 2 and 4, got ${elements.length}`);
}
@@ -1117,7 +1831,7 @@ export class Vector {
}
}
this.elements = elements;
- this.type = TypeVec(elements.length, elements[0].type);
+ this.type = VectorType.create(elements.length, elements[0].type);
}
/**
@@ -1165,19 +1879,24 @@ export class Vector {
}
}
+/** Helper for constructing a new vector with the provided values */
+export function vec(...elements: ScalarValue[]) {
+ return new VectorValue(elements);
+}
+
/** Helper for constructing a new two-element vector with the provided values */
-export function vec2(x: Scalar, y: Scalar) {
- return new Vector([x, y]);
+export function vec2(x: ScalarValue, y: ScalarValue) {
+ return new VectorValue([x, y]);
}
/** Helper for constructing a new three-element vector with the provided values */
-export function vec3(x: Scalar, y: Scalar, z: Scalar) {
- return new Vector([x, y, z]);
+export function vec3(x: ScalarValue, y: ScalarValue, z: ScalarValue) {
+ return new VectorValue([x, y, z]);
}
/** Helper for constructing a new four-element vector with the provided values */
-export function vec4(x: Scalar, y: Scalar, z: Scalar, w: Scalar) {
- return new Vector([x, y, z, w]);
+export function vec4(x: ScalarValue, y: ScalarValue, z: ScalarValue, w: ScalarValue) {
+ return new VectorValue([x, y, z, w]);
}
/**
@@ -1186,7 +1905,7 @@ export function vec4(x: Scalar, y: Scalar, z: Scalar, w: Scalar) {
* @param v array of numbers to be converted, must contain 2, 3 or 4 elements
* @param op function to convert from number to Scalar, e.g. 'f32`
*/
-export function toVector(v: readonly number[], op: (n: number) => Scalar): Vector {
+export function toVector(v: readonly number[], op: (n: number) => ScalarValue): VectorValue {
switch (v.length) {
case 2:
return vec2(op(v[0]), op(v[1]));
@@ -1201,11 +1920,11 @@ export function toVector(v: readonly number[], op: (n: number) => Scalar): Vecto
/**
* Class that encapsulates a Matrix value.
*/
-export class Matrix {
- readonly elements: Scalar[][];
+export class MatrixValue {
+ readonly elements: ScalarValue[][];
readonly type: MatrixType;
- public constructor(elements: Array<Array<Scalar>>) {
+ public constructor(elements: Array<Array<ScalarValue>>) {
const num_cols = elements.length;
if (num_cols < 2 || num_cols > 4) {
throw new Error(`matrix cols count must be between 2 and 4, got ${num_cols}`);
@@ -1226,7 +1945,7 @@ export class Matrix {
}
this.elements = elements;
- this.type = TypeMat(num_cols, num_rows, elem_type);
+ this.type = MatrixType.create(num_cols, num_rows, elem_type);
}
/**
@@ -1262,41 +1981,90 @@ export class Matrix {
}
/**
+ * Class that encapsulates an Array value.
+ */
+export class ArrayValue {
+ readonly elements: Value[];
+ readonly type: ArrayType;
+
+ public constructor(elements: Array<Value>) {
+ const elem_type = elements[0].type;
+ if (!elements.every(c => elements.every(r => objectEquals(r.type, elem_type)))) {
+ throw new Error(`cannot mix array element types`);
+ }
+
+ this.elements = elements;
+ this.type = ArrayType.create(elements.length, elem_type);
+ }
+
+ /**
+ * Copies the array value to the Uint8Array buffer at the provided byte offset.
+ * @param buffer the destination buffer
+ * @param offset the byte offset within buffer
+ */
+ public copyTo(buffer: Uint8Array, offset: number) {
+ for (const element of this.elements) {
+ element.copyTo(buffer, offset);
+ offset += this.type.elementType.size;
+ }
+ }
+
+ /**
+ * @returns the WGSL representation of this array value
+ */
+ public wgsl(): string {
+ const els = this.elements.map(r => r.wgsl()).join(', ');
+ return isAbstractType(this.type.elementType) ? `array(${els})` : `${this.type}(${els})`;
+ }
+
+ public toString(): string {
+ return this.wgsl();
+ }
+}
+
+/** Helper for constructing an ArrayValue with the provided values */
+export function array(...elements: Value[]) {
+ return new ArrayValue(elements);
+}
+
+/**
* Helper for constructing Matrices from arrays of numbers
*
* @param m array of array of numbers to be converted, all Array of number must
* be of the same length. All Arrays must have 2, 3, or 4 elements.
* @param op function to convert from number to Scalar, e.g. 'f32`
*/
-export function toMatrix(m: ROArrayArray<number>, op: (n: number) => Scalar): Matrix {
+export function toMatrix(m: ROArrayArray<number>, op: (n: number) => ScalarValue): MatrixValue {
const cols = m.length;
const rows = m[0].length;
- const elements: Scalar[][] = [...Array<Scalar[]>(cols)].map(_ => [...Array<Scalar>(rows)]);
+ const elements: ScalarValue[][] = [...Array<ScalarValue[]>(cols)].map(_ => [
+ ...Array<ScalarValue>(rows),
+ ]);
for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
elements[i][j] = op(m[i][j]);
}
}
- return new Matrix(elements);
+ return new MatrixValue(elements);
}
-/** Value is a Scalar or Vector value. */
-export type Value = Scalar | Vector | Matrix;
+/** Value is a Scalar, Vector, Matrix or Array value. */
+export type Value = ScalarValue | VectorValue | MatrixValue | ArrayValue;
-export type SerializedValueScalar = {
+export type SerializedScalarValue = {
kind: 'scalar';
type: ScalarKind;
value: boolean | number;
};
-export type SerializedValueVector = {
+export type SerializedVectorValue = {
kind: 'vector';
type: ScalarKind;
value: boolean[] | readonly number[];
};
-export type SerializedValueMatrix = {
+export type SerializedMatrixValue = {
kind: 'matrix';
type: ScalarKind;
value: ROArrayArray<number>;
@@ -1314,6 +2082,7 @@ enum SerializedScalarKind {
I16,
I8,
Bool,
+ AbstractInt,
}
/** serializeScalarKind() serializes a ScalarKind to a BinaryStream */
@@ -1340,6 +2109,9 @@ function serializeScalarKind(s: BinaryStream, v: ScalarKind) {
case 'u8':
s.writeU8(SerializedScalarKind.U8);
return;
+ case 'abstract-int':
+ s.writeU8(SerializedScalarKind.AbstractInt);
+ return;
case 'i32':
s.writeU8(SerializedScalarKind.I32);
return;
@@ -1353,6 +2125,7 @@ function serializeScalarKind(s: BinaryStream, v: ScalarKind) {
s.writeU8(SerializedScalarKind.Bool);
return;
}
+ unreachable(`Do not know what to write scalar kind = ${v}`);
}
/** deserializeScalarKind() deserializes a ScalarKind from a BinaryStream */
@@ -1373,6 +2146,8 @@ function deserializeScalarKind(s: BinaryStream): ScalarKind {
return 'u16';
case SerializedScalarKind.U8:
return 'u8';
+ case SerializedScalarKind.AbstractInt:
+ return 'abstract-int';
case SerializedScalarKind.I32:
return 'i32';
case SerializedScalarKind.I16:
@@ -1394,51 +2169,66 @@ enum SerializedValueKind {
/** serializeValue() serializes a Value to a BinaryStream */
export function serializeValue(s: BinaryStream, v: Value) {
- const serializeScalar = (scalar: Scalar, kind: ScalarKind) => {
- switch (kind) {
- case 'abstract-float':
- s.writeF64(scalar.value as number);
- return;
- case 'f64':
- s.writeF64(scalar.value as number);
- return;
- case 'f32':
- s.writeF32(scalar.value as number);
- return;
- case 'f16':
- s.writeF16(scalar.value as number);
- return;
- case 'u32':
- s.writeU32(scalar.value as number);
- return;
- case 'u16':
- s.writeU16(scalar.value as number);
- return;
- case 'u8':
- s.writeU8(scalar.value as number);
- return;
- case 'i32':
- s.writeI32(scalar.value as number);
- return;
- case 'i16':
- s.writeI16(scalar.value as number);
- return;
- case 'i8':
- s.writeI8(scalar.value as number);
- return;
- case 'bool':
- s.writeBool(scalar.value as boolean);
- return;
+ const serializeScalar = (scalar: ScalarValue, kind: ScalarKind) => {
+ switch (typeof scalar.value) {
+ case 'number':
+ switch (kind) {
+ case 'abstract-float':
+ s.writeF64(scalar.value);
+ return;
+ case 'f64':
+ s.writeF64(scalar.value);
+ return;
+ case 'f32':
+ s.writeF32(scalar.value);
+ return;
+ case 'f16':
+ s.writeF16(scalar.value);
+ return;
+ case 'u32':
+ s.writeU32(scalar.value);
+ return;
+ case 'u16':
+ s.writeU16(scalar.value);
+ return;
+ case 'u8':
+ s.writeU8(scalar.value);
+ return;
+ case 'i32':
+ s.writeI32(scalar.value);
+ return;
+ case 'i16':
+ s.writeI16(scalar.value);
+ return;
+ case 'i8':
+ s.writeI8(scalar.value);
+ return;
+ }
+ break;
+ case 'bigint':
+ switch (kind) {
+ case 'abstract-int':
+ s.writeI64(scalar.value);
+ return;
+ }
+ break;
+ case 'boolean':
+ switch (kind) {
+ case 'bool':
+ s.writeBool(scalar.value);
+ return;
+ }
+ break;
}
};
- if (v instanceof Scalar) {
+ if (isScalarValue(v)) {
s.writeU8(SerializedValueKind.Scalar);
serializeScalarKind(s, v.type.kind);
serializeScalar(v, v.type.kind);
return;
}
- if (v instanceof Vector) {
+ if (v instanceof VectorValue) {
s.writeU8(SerializedValueKind.Vector);
serializeScalarKind(s, v.type.elementType.kind);
s.writeU8(v.type.width);
@@ -1447,7 +2237,7 @@ export function serializeValue(s: BinaryStream, v: Value) {
}
return;
}
- if (v instanceof Matrix) {
+ if (v instanceof MatrixValue) {
s.writeU8(SerializedValueKind.Matrix);
serializeScalarKind(s, v.type.elementType.kind);
s.writeU8(v.type.cols);
@@ -1481,6 +2271,8 @@ export function deserializeValue(s: BinaryStream): Value {
return u16(s.readU16());
case 'u8':
return u8(s.readU8());
+ case 'abstract-int':
+ return abstractInt(s.readI64());
case 'i32':
return i32(s.readI32());
case 'i16':
@@ -1498,23 +2290,23 @@ export function deserializeValue(s: BinaryStream): Value {
return deserializeScalar(scalarKind);
case SerializedValueKind.Vector: {
const width = s.readU8();
- const scalars = new Array<Scalar>(width);
+ const scalars = new Array<ScalarValue>(width);
for (let i = 0; i < width; i++) {
scalars[i] = deserializeScalar(scalarKind);
}
- return new Vector(scalars);
+ return new VectorValue(scalars);
}
case SerializedValueKind.Matrix: {
const numCols = s.readU8();
const numRows = s.readU8();
- const columns = new Array<Scalar[]>(numCols);
+ const columns = new Array<ScalarValue[]>(numCols);
for (let c = 0; c < numCols; c++) {
- columns[c] = new Array<Scalar>(numRows);
+ columns[c] = new Array<ScalarValue>(numRows);
for (let i = 0; i < numRows; i++) {
columns[c][i] = deserializeScalar(scalarKind);
}
}
- return new Matrix(columns);
+ return new MatrixValue(columns);
}
default:
unreachable(`invalid serialized value kind: ${valueKind}`);
@@ -1533,7 +2325,7 @@ export function isFloatValue(v: Value): boolean {
*/
export function isAbstractType(ty: Type): boolean {
if (ty instanceof ScalarType) {
- return ty.kind === 'abstract-float';
+ return ty.kind === 'abstract-float' || ty.kind === 'abstract-int';
}
return false;
}
@@ -1552,84 +2344,222 @@ export function isFloatType(ty: Type): boolean {
return false;
}
+/**
+ * @returns if `ty` is a type convertible to floating point type.
+ * @note this does not consider composite types.
+ * Use elementType() if you want to test the element type.
+ */
+export function isConvertibleToFloatType(ty: Type): boolean {
+ if (ty instanceof ScalarType) {
+ return (
+ ty.kind === 'abstract-int' ||
+ ty.kind === 'abstract-float' ||
+ ty.kind === 'f64' ||
+ ty.kind === 'f32' ||
+ ty.kind === 'f16'
+ );
+ }
+ return false;
+}
+
+/**
+ * @returns if `ty` is an unsigned type.
+ */
+export function isUnsignedType(ty: Type): boolean {
+ if (ty instanceof ScalarType) {
+ return ty.kind === 'u8' || ty.kind === 'u16' || ty.kind === 'u32';
+ } else {
+ return isUnsignedType(ty.elementType);
+ }
+}
+
+/** @returns true if an argument of type 'src' can be used for a parameter of type 'dst' */
+export function isConvertible(src: Type, dst: Type) {
+ if (src === dst) {
+ return true;
+ }
+
+ const widthOf = (ty: Type) => {
+ return ty instanceof VectorType ? ty.width : 1;
+ };
+
+ if (widthOf(src) !== widthOf(dst)) {
+ return false;
+ }
+
+ const elSrc = scalarTypeOf(src);
+ const elDst = scalarTypeOf(dst);
+
+ switch (elSrc.kind) {
+ case 'abstract-float':
+ switch (elDst.kind) {
+ case 'abstract-float':
+ case 'f16':
+ case 'f32':
+ case 'f64':
+ return true;
+ default:
+ return false;
+ }
+ case 'abstract-int':
+ switch (elDst.kind) {
+ case 'abstract-int':
+ case 'abstract-float':
+ case 'f16':
+ case 'f32':
+ case 'f64':
+ case 'u16':
+ case 'u32':
+ case 'u8':
+ case 'i16':
+ case 'i32':
+ case 'i8':
+ return true;
+ default:
+ return false;
+ }
+ default:
+ return false;
+ }
+}
+
/// All floating-point scalar types
-export const kAllFloatScalars = [TypeAbstractFloat, TypeF32, TypeF16] as const;
+const kFloatScalars = [Type.abstractFloat, Type.f32, Type.f16] as const;
/// All floating-point vec2 types
-export const kAllFloatVector2 = [
- TypeVec(2, TypeAbstractFloat),
- TypeVec(2, TypeF32),
- TypeVec(2, TypeF16),
-] as const;
+const kFloatVec2 = [Type.vec2af, Type.vec2f, Type.vec2h] as const;
/// All floating-point vec3 types
-export const kAllFloatVector3 = [
- TypeVec(3, TypeAbstractFloat),
- TypeVec(3, TypeF32),
- TypeVec(3, TypeF16),
-] as const;
+const kFloatVec3 = [Type.vec3af, Type.vec3f, Type.vec3h] as const;
/// All floating-point vec4 types
-export const kAllFloatVector4 = [
- TypeVec(4, TypeAbstractFloat),
- TypeVec(4, TypeF32),
- TypeVec(4, TypeF16),
+const kFloatVec4 = [Type.vec4af, Type.vec4f, Type.vec4h] as const;
+
+export const kConcreteF32ScalarsAndVectors = [
+ Type.f32,
+ Type.vec2f,
+ Type.vec3f,
+ Type.vec4f,
] as const;
-/// All floating-point vector types
-export const kAllFloatVectors = [
- ...kAllFloatVector2,
- ...kAllFloatVector3,
- ...kAllFloatVector4,
+/// All f16 floating-point scalar and vector types
+export const kConcreteF16ScalarsAndVectors = [
+ Type.f16,
+ Type.vec2h,
+ Type.vec3h,
+ Type.vec4h,
] as const;
+/// All floating-point vector types
+export const kFloatVectors = [...kFloatVec2, ...kFloatVec3, ...kFloatVec4] as const;
+
/// All floating-point scalar and vector types
-export const kAllFloatScalarsAndVectors = [...kAllFloatScalars, ...kAllFloatVectors] as const;
-
-/// All integer scalar and vector types
-export const kAllIntegerScalarsAndVectors = [
- TypeI32,
- TypeVec(2, TypeI32),
- TypeVec(3, TypeI32),
- TypeVec(4, TypeI32),
- TypeU32,
- TypeVec(2, TypeU32),
- TypeVec(3, TypeU32),
- TypeVec(4, TypeU32),
+export const kFloatScalarsAndVectors = [...kFloatScalars, ...kFloatVectors] as const;
+
+// Abstract and concrete integer types are not grouped into an 'all' type,
+// because for many validation tests there is a valid conversion of
+// AbstractInt -> AbstractFloat, but not one for the concrete integers. Thus, an
+// AbstractInt literal will be a potentially valid input, whereas the concrete
+// integers will not be. For many tests the pattern is to have separate fixtures
+// for the things that might be valid and those that are never valid.
+
+/// All signed integer vector types
+export const kConcreteSignedIntegerVectors = [Type.vec2i, Type.vec3i, Type.vec4i] as const;
+
+/// All unsigned integer vector types
+export const kConcreteUnsignedIntegerVectors = [Type.vec2u, Type.vec3u, Type.vec4u] as const;
+
+/// All concrete integer vector types
+export const kConcreteIntegerVectors = [
+ ...kConcreteSignedIntegerVectors,
+ ...kConcreteUnsignedIntegerVectors,
] as const;
/// All signed integer scalar and vector types
-export const kAllSignedIntegerScalarsAndVectors = [
- TypeI32,
- TypeVec(2, TypeI32),
- TypeVec(3, TypeI32),
- TypeVec(4, TypeI32),
+export const kConcreteSignedIntegerScalarsAndVectors = [
+ Type.i32,
+ ...kConcreteSignedIntegerVectors,
] as const;
/// All unsigned integer scalar and vector types
-export const kAllUnsignedIntegerScalarsAndVectors = [
- TypeU32,
- TypeVec(2, TypeU32),
- TypeVec(3, TypeU32),
- TypeVec(4, TypeU32),
+export const kConcreteUnsignedIntegerScalarsAndVectors = [
+ Type.u32,
+ ...kConcreteUnsignedIntegerVectors,
] as const;
-/// All floating-point and integer scalar and vector types
-export const kAllFloatAndIntegerScalarsAndVectors = [
- ...kAllFloatScalarsAndVectors,
- ...kAllIntegerScalarsAndVectors,
+/// All concrete integer scalar and vector types
+export const kConcreteIntegerScalarsAndVectors = [
+ ...kConcreteSignedIntegerScalarsAndVectors,
+ ...kConcreteUnsignedIntegerScalarsAndVectors,
] as const;
-/// All floating-point and signed integer scalar and vector types
-export const kAllFloatAndSignedIntegerScalarsAndVectors = [
- ...kAllFloatScalarsAndVectors,
- ...kAllSignedIntegerScalarsAndVectors,
+/// All types which are convertable to floating-point scalar types.
+export const kConvertableToFloatScalar = [Type.abstractInt, ...kFloatScalars] as const;
+
+/// All types which are convertable to floating-point vector 2 types.
+export const kConvertableToFloatVec2 = [Type.vec2ai, ...kFloatVec2] as const;
+
+/// All types which are convertable to floating-point vector 3 types.
+export const kConvertableToFloatVec3 = [Type.vec3ai, ...kFloatVec3] as const;
+
+/// All types which are convertable to floating-point vector 4 types.
+export const kConvertableToFloatVec4 = [Type.vec4ai, ...kFloatVec4] as const;
+
+/// All the types which are convertable to floating-point vector types.
+export const kConvertableToFloatVectors = [
+ Type.vec2ai,
+ Type.vec3ai,
+ Type.vec4ai,
+ ...kFloatVectors,
] as const;
-/** @returns the inner element type of the given type */
-export function elementType(t: ScalarType | VectorType | MatrixType) {
- if (t instanceof ScalarType) {
- return t;
- }
- return t.elementType;
-}
+/// All types which are convertable to floating-point scalar or vector types.
+export const kConvertableToFloatScalarsAndVectors = [
+ Type.abstractInt,
+ ...kFloatScalars,
+ ...kConvertableToFloatVectors,
+] as const;
+
+/// All the numeric scalar and vector types.
+export const kAllNumericScalarsAndVectors = [
+ ...kConvertableToFloatScalarsAndVectors,
+ ...kConcreteIntegerScalarsAndVectors,
+] as const;
+
+/// All the concrete integer and floating point scalars and vectors.
+export const kConcreteNumericScalarsAndVectors = [
+ ...kConcreteIntegerScalarsAndVectors,
+ ...kConcreteF16ScalarsAndVectors,
+ ...kConcreteF32ScalarsAndVectors,
+] as const;
+
+/// All boolean types.
+export const kAllBoolScalarsAndVectors = [Type.bool, Type.vec2b, Type.vec3b, Type.vec4b] as const;
+
+/// All the scalar and vector types.
+export const kAllScalarsAndVectors = [
+ ...kAllBoolScalarsAndVectors,
+ ...kAllNumericScalarsAndVectors,
+] as const;
+
+/// All the matrix types
+export const kAllMatrices = [
+ Type.mat2x2f,
+ Type.mat2x2h,
+ Type.mat2x3f,
+ Type.mat2x3h,
+ Type.mat2x4f,
+ Type.mat2x4h,
+ Type.mat3x2f,
+ Type.mat3x2h,
+ Type.mat3x3f,
+ Type.mat3x3h,
+ Type.mat3x4f,
+ Type.mat3x4h,
+ Type.mat4x2f,
+ Type.mat4x2h,
+ Type.mat4x3f,
+ Type.mat4x3h,
+ Type.mat4x4f,
+ Type.mat4x4h,
+] as const;
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/util/device_pool.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/util/device_pool.ts
index 1e6c0402cb..8a90f43248 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/util/device_pool.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/util/device_pool.ts
@@ -9,7 +9,12 @@ import {
} from '../../common/util/util.js';
import { getDefaultLimits, kLimits } from '../capability_info.js';
+// MUST_NOT_BE_IMPORTED_BY_DATA_CACHE
+// This file should not be transitively imported by .cache.ts files
+
export interface DeviceProvider {
+ /** Adapter the device was created from. Cannot be reused; just for adapter info. */
+ readonly adapter: GPUAdapter;
readonly device: GPUDevice;
expectDeviceLost(reason: GPUDeviceLostReason): void;
}
@@ -283,6 +288,8 @@ type DeviceHolderState = 'free' | 'acquired';
* Holds a GPUDevice and tracks its state (free/acquired) and handles device loss.
*/
class DeviceHolder implements DeviceProvider {
+ /** Adapter the device was created from. Cannot be reused; just for adapter info. */
+ readonly adapter: GPUAdapter;
/** The device. Will be cleared during cleanup if there were unexpected errors. */
private _device: GPUDevice | undefined;
/** Whether the device is in use by a test or not. */
@@ -307,10 +314,11 @@ class DeviceHolder implements DeviceProvider {
const device = await adapter.requestDevice(descriptor);
assert(device !== null, 'requestDevice returned null');
- return new DeviceHolder(device);
+ return new DeviceHolder(adapter, device);
}
- private constructor(device: GPUDevice) {
+ private constructor(adapter: GPUAdapter, device: GPUDevice) {
+ this.adapter = adapter;
this._device = device;
void this._device.lost.then(ev => {
this.lostInfo = ev;
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/util/floating_point.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/util/floating_point.ts
index e271e7db7a..b644052ebf 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/util/floating_point.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/util/floating_point.ts
@@ -1,7 +1,8 @@
import { ROArrayArray, ROArrayArrayArray } from '../../common/util/types.js';
import { assert, unreachable } from '../../common/util/util.js';
import { Float16Array } from '../../external/petamoriken/float16/float16.js';
-import { Case, IntervalFilter } from '../shader/execution/expression/expression.js';
+import { Case } from '../shader/execution/expression/case.js';
+import { IntervalFilter } from '../shader/execution/expression/interval_filter.js';
import BinaryStream from './binary_stream.js';
import { anyOf } from './compare.js';
@@ -11,7 +12,7 @@ import {
f16,
f32,
isFloatType,
- Scalar,
+ ScalarValue,
ScalarType,
toMatrix,
toVector,
@@ -23,6 +24,7 @@ import {
correctlyRoundedF16,
correctlyRoundedF32,
correctlyRoundedF64,
+ every2DArray,
flatten2DArray,
FlushMode,
flushSubnormalNumberF16,
@@ -36,10 +38,24 @@ import {
map2DArray,
oneULPF16,
oneULPF32,
- quantizeToF32,
quantizeToF16,
+ quantizeToF32,
+ scalarF16Range,
+ scalarF32Range,
+ scalarF64Range,
+ sparseMatrixF16Range,
+ sparseMatrixF32Range,
+ sparseMatrixF64Range,
+ sparseScalarF16Range,
+ sparseScalarF32Range,
+ sparseScalarF64Range,
+ sparseVectorF16Range,
+ sparseVectorF32Range,
+ sparseVectorF64Range,
unflatten2DArray,
- every2DArray,
+ vectorF16Range,
+ vectorF32Range,
+ vectorF64Range,
} from './math.js';
/** Indicate the kind of WGSL floating point numbers being operated on */
@@ -83,12 +99,12 @@ export function deserializeFPKind(s: BinaryStream): FPKind {
// Containers
/**
- * Representation of bounds for an interval as an array with either one or two
- * elements. Single element indicates that the interval is a single point. For
- * two elements, the first is the lower bound of the interval and the second is
- * the upper bound.
+ * Representation of endpoints for an interval as an array with either one or
+ * two elements. Single element indicates that the interval is a single point.
+ * For two elements, the first is the lower edges of the interval and the
+ * second is the upper edge, i.e. e[0] <= e[1], where e is an IntervalEndpoints
*/
-export type IntervalBounds = readonly [number] | readonly [number, number];
+export type IntervalEndpoints = readonly [number] | readonly [number, number];
/** Represents a closed interval of floating point numbers */
export class FPInterval {
@@ -102,15 +118,18 @@ export class FPInterval {
* `FPTraits.toInterval` is the preferred way to create FPIntervals
*
* @param kind the floating point number type this is an interval for
- * @param bounds beginning and end of the interval
+ * @param endpoints beginning and end of the interval
*/
- public constructor(kind: FPKind, ...bounds: IntervalBounds) {
+ public constructor(kind: FPKind, ...endpoints: IntervalEndpoints) {
this.kind = kind;
- const begin = bounds[0];
- const end = bounds.length === 2 ? bounds[1] : bounds[0];
- assert(!Number.isNaN(begin) && !Number.isNaN(end), `bounds need to be non-NaN`);
- assert(begin <= end, `bounds[0] (${begin}) must be less than or equal to bounds[1] (${end})`);
+ const begin = endpoints[0];
+ const end = endpoints.length === 2 ? endpoints[1] : endpoints[0];
+ assert(!Number.isNaN(begin) && !Number.isNaN(end), `endpoints need to be non-NaN`);
+ assert(
+ begin <= end,
+ `endpoints[0] (${begin}) must be less than or equal to endpoints[1] (${end})`
+ );
this.begin = begin;
this.end = end;
@@ -122,7 +141,7 @@ export class FPInterval {
}
/** @returns begin and end if non-point interval, otherwise just begin */
- public bounds(): IntervalBounds {
+ public endpoints(): IntervalEndpoints {
return this.isPoint() ? [this.begin] : [this.begin, this.end];
}
@@ -163,7 +182,7 @@ export class FPInterval {
/** @returns a string representation for logging purposes */
public toString(): string {
- return `{ '${this.kind}', [${this.bounds().map(this.traits().scalarBuilder)}] }`;
+ return `{ '${this.kind}', [${this.endpoints().map(this.traits().scalarBuilder)}] }`;
}
}
@@ -312,7 +331,7 @@ interface ScalarToIntervalOp {
* occur and returns a span of those points to be used as the domain instead.
*
* Used by this.runScalarToIntervalOp before invoking impl.
- * If not defined, the bounds of the existing domain are assumed to be the
+ * If not defined, the endpoints of the existing domain are assumed to be the
* extrema.
*
* This is only implemented for operations that meet all the following
@@ -323,6 +342,14 @@ interface ScalarToIntervalOp {
* i.e. fooInterval takes in x: number | FPInterval, not x: number
*/
extrema?: (x: FPInterval) => FPInterval;
+
+ /**
+ * Restricts the inputs to operation to the given domain.
+ *
+ * Only defined for operations that have tighter domain requirements than 'must
+ * be finite'.
+ */
+ domain?: () => FPInterval;
}
/**
@@ -334,6 +361,13 @@ export interface ScalarPairToInterval {
(x: number, y: number): FPInterval;
}
+/** Domain for a ScalarPairToInterval implementation */
+interface ScalarPairToIntervalDomain {
+ // Arrays to support discrete valid domain intervals
+ x: readonly FPInterval[];
+ y: readonly FPInterval[];
+}
+
/** Operation used to implement a ScalarPairToInterval */
interface ScalarPairToIntervalOp {
/** @returns acceptance interval for a function at point (x, y) */
@@ -343,23 +377,24 @@ interface ScalarPairToIntervalOp {
* occur and returns spans of those points to be used as the domain instead.
*
* Used by runScalarPairToIntervalOp before invoking impl.
- * If not defined, the bounds of the existing domain are assumed to be the
+ * If not defined, the endpoints of the existing domain are assumed to be the
* extrema.
*
- * This is only implemented for functions that meet all of the following
+ * This is only implemented for functions that meet all the following
* criteria:
* a) non-monotonic
* b) used in inherited accuracy calculations
* c) need to take in an interval for b)
*/
extrema?: (x: FPInterval, y: FPInterval) => [FPInterval, FPInterval];
-}
-/** Domain for a ScalarPairToInterval implementation */
-interface ScalarPairToIntervalDomain {
- // Arrays to support discrete valid domain intervals
- x: readonly FPInterval[];
- y: readonly FPInterval[];
+ /**
+ * Restricts the inputs to operation to the given domain.
+ *
+ * Only defined for operations that have tighter domain requirements than 'must
+ * be finite'.
+ */
+ domain?: () => ScalarPairToIntervalDomain;
}
/**
@@ -556,7 +591,7 @@ export interface VectorMatrixToVector {
// Traits
/**
- * Typed structure containing all the limits/constants defined for each
+ * Typed structure containing all the constants defined for each
* WGSL floating point kind
*/
interface FPConstants {
@@ -599,10 +634,12 @@ interface FPConstants {
sixth: number;
};
};
+ bias: number;
unboundedInterval: FPInterval;
zeroInterval: FPInterval;
negPiToPiInterval: FPInterval;
greaterThanZeroInterval: FPInterval;
+ negOneToOneInterval: FPInterval;
zeroVector: {
2: FPVector;
3: FPVector;
@@ -635,7 +672,7 @@ interface FPConstants {
/** A representation of an FPInterval for a case param */
export type FPIntervalParam = {
kind: FPKind;
- interval: number | IntervalBounds;
+ interval: number | IntervalEndpoints;
};
/** Abstract base class for all floating-point traits */
@@ -650,7 +687,7 @@ export abstract class FPTraits {
// Utilities - Implemented
/** @returns an interval containing the point or the original interval */
- public toInterval(n: number | IntervalBounds | FPInterval): FPInterval {
+ public toInterval(n: number | IntervalEndpoints | FPInterval): FPInterval {
if (n instanceof FPInterval) {
if (n.kind === this.kind) {
return n;
@@ -661,7 +698,7 @@ export abstract class FPTraits {
return this.constants().unboundedInterval;
}
- return new FPInterval(this.kind, ...n.bounds());
+ return new FPInterval(this.kind, ...n.endpoints());
}
if (n instanceof Array) {
@@ -674,7 +711,7 @@ export abstract class FPTraits {
/**
* Makes a param that can be turned into an interval
*/
- public toParam(n: number | IntervalBounds): FPIntervalParam {
+ public toParam(n: number | IntervalEndpoints): FPIntervalParam {
return {
kind: this.kind,
interval: n,
@@ -685,18 +722,18 @@ export abstract class FPTraits {
* Converts p into an FPInterval if it is an FPIntervalPAram
*/
public fromParam(
- p: number | IntervalBounds | FPIntervalParam
- ): number | IntervalBounds | FPInterval {
+ p: number | IntervalEndpoints | FPIntervalParam
+ ): number | IntervalEndpoints | FPInterval {
const param = p as FPIntervalParam;
if (param.interval && param.kind) {
assert(param.kind === this.kind);
return this.toInterval(param.interval);
}
- return p as number | IntervalBounds;
+ return p as number | IntervalEndpoints;
}
/**
- * @returns an interval with the tightest bounds that includes all provided
+ * @returns an interval with the tightest endpoints that includes all provided
* intervals
*/
public spanIntervals(...intervals: readonly FPInterval[]): FPInterval {
@@ -715,7 +752,7 @@ export abstract class FPTraits {
}
/** Narrow an array of values to FPVector if possible */
- public isVector(v: ReadonlyArray<number | IntervalBounds | FPInterval>): v is FPVector {
+ public isVector(v: ReadonlyArray<number | IntervalEndpoints | FPInterval>): v is FPVector {
if (v.every(e => e instanceof FPInterval && e.kind === this.kind)) {
return v.length === 2 || v.length === 3 || v.length === 4;
}
@@ -723,7 +760,7 @@ export abstract class FPTraits {
}
/** @returns an FPVector representation of an array of values if possible */
- public toVector(v: ReadonlyArray<number | IntervalBounds | FPInterval>): FPVector {
+ public toVector(v: ReadonlyArray<number | IntervalEndpoints | FPInterval>): FPVector {
if (this.isVector(v) && v.every(e => e.kind === this.kind)) {
return v;
}
@@ -762,7 +799,7 @@ export abstract class FPTraits {
}
/** Narrow an array of an array of values to FPMatrix if possible */
- public isMatrix(m: Array2D<number | IntervalBounds | FPInterval> | FPVector[]): m is FPMatrix {
+ public isMatrix(m: Array2D<number | IntervalEndpoints | FPInterval> | FPVector[]): m is FPMatrix {
if (!m.every(c => c.every(e => e instanceof FPInterval && e.kind === this.kind))) {
return false;
}
@@ -787,7 +824,7 @@ export abstract class FPTraits {
}
/** @returns an FPMatrix representation of an array of an array of values if possible */
- public toMatrix(m: Array2D<number | IntervalBounds | FPInterval> | FPVector[]): FPMatrix {
+ public toMatrix(m: Array2D<number | IntervalEndpoints | FPInterval> | FPVector[]): FPMatrix {
if (
this.isMatrix(m) &&
every2DArray(m, (e: FPInterval) => {
@@ -840,48 +877,6 @@ export abstract class FPTraits {
return needs_zero ? values.concat(0) : values;
}
- /**
- * Restrict the inputs to an ScalarToInterval operation
- *
- * Only used for operations that have tighter domain requirements than 'must
- * be finite'.
- *
- * @param domain interval to restrict inputs to
- * @param impl operation implementation to run if input is within the required domain
- * @returns a ScalarToInterval that calls impl if domain contains the input,
- * otherwise it returns an unbounded interval */
- protected limitScalarToIntervalDomain(
- domain: FPInterval,
- impl: ScalarToInterval
- ): ScalarToInterval {
- return (n: number): FPInterval => {
- return domain.contains(n) ? impl(n) : this.constants().unboundedInterval;
- };
- }
-
- /**
- * Restrict the inputs to a ScalarPairToInterval
- *
- * Only used for operations that have tighter domain requirements than 'must be
- * finite'.
- *
- * @param domain set of intervals to restrict inputs to
- * @param impl operation implementation to run if input is within the required domain
- * @returns a ScalarPairToInterval that calls impl if domain contains the input,
- * otherwise it returns an unbounded interval */
- protected limitScalarPairToIntervalDomain(
- domain: ScalarPairToIntervalDomain,
- impl: ScalarPairToInterval
- ): ScalarPairToInterval {
- return (x: number, y: number): FPInterval => {
- if (!domain.x.some(d => d.contains(x)) || !domain.y.some(d => d.contains(y))) {
- return this.constants().unboundedInterval;
- }
-
- return impl(x, y);
- };
- }
-
/** Stub for scalar to interval generator */
protected unimplementedScalarToInterval(name: string, _x: number | FPInterval): FPInterval {
unreachable(`'${name}' is not yet implemented for '${this.kind}'`);
@@ -1053,14 +1048,14 @@ export abstract class FPTraits {
unreachable(`'refract' is not yet implemented for '${this.kind}'`);
}
- /** Version of absoluteErrorInterval that always returns the unboundedInterval */
- protected unboundedAbsoluteErrorInterval(_n: number, _error_range: number): FPInterval {
- return this.constants().unboundedInterval;
+ /** Stub for absolute errors */
+ protected unimplementedAbsoluteErrorInterval(_n: number, _error_range: number): FPInterval {
+ unreachable(`Absolute Error is not implement for '${this.kind}'`);
}
- /** Version of ulpInterval that always returns the unboundedInterval */
- protected unboundedUlpInterval(_n: number, _numULP: number): FPInterval {
- return this.constants().unboundedInterval;
+ /** Stub for ULP errors */
+ protected unimplementedUlpInterval(_n: number, _numULP: number): FPInterval {
+ unreachable(`ULP Error is not implement for '${this.kind}'`);
}
// Utilities - Defined by subclass
@@ -1079,8 +1074,22 @@ export abstract class FPTraits {
public abstract readonly flushSubnormal: (n: number) => number;
/** @returns 1 * ULP: (number) */
public abstract readonly oneULP: (target: number, mode?: FlushMode) => number;
- /** @returns a builder for converting numbers to Scalars */
- public abstract readonly scalarBuilder: (n: number) => Scalar;
+ /** @returns a builder for converting numbers to ScalarsValues */
+ public abstract readonly scalarBuilder: (n: number) => ScalarValue;
+ /** @returns a range of scalars for testing */
+ public abstract scalarRange(): readonly number[];
+ /** @returns a reduced range of scalars for testing */
+ public abstract sparseScalarRange(): readonly number[];
+ /** @returns a range of dim element vectors for testing */
+ public abstract vectorRange(dim: number): ROArrayArray<number>;
+ /** @returns a reduced range of dim element vectors for testing */
+ public abstract sparseVectorRange(dim: number): ROArrayArray<number>;
+ /** @returns a reduced range of cols x rows matrices for testing
+ *
+ * A non-sparse version of this generator is intentionally not provided due to
+ * runtime issues with more dense ranges.
+ */
+ public abstract sparseMatrixRange(cols: number, rows: number): ROArrayArrayArray<number>;
// Framework - Cases
@@ -1705,7 +1714,6 @@ export abstract class FPTraits {
): Case | undefined {
param0 = map2DArray(param0, this.quantize);
param1 = map2DArray(param1, this.quantize);
-
const results = ops.map(o => o(param0, param1));
if (filter === 'finite' && results.some(m => m.some(c => c.some(r => !r.isFinite())))) {
return undefined;
@@ -1973,6 +1981,15 @@ export abstract class FPTraits {
assert(!Number.isNaN(n), `flush not defined for NaN`);
const values = this.correctlyRounded(n);
const inputs = this.addFlushedIfNeeded(values);
+
+ if (op.domain !== undefined) {
+ // Cannot invoke op.domain() directly in the .some, because the narrowing doesn't propegate.
+ const domain = op.domain();
+ if (inputs.some(i => !domain.contains(i))) {
+ return this.constants().unboundedInterval;
+ }
+ }
+
const results = new Set<FPInterval>(inputs.map(op.impl));
return this.spanIntervals(...results);
}
@@ -1998,10 +2015,25 @@ export abstract class FPTraits {
): FPInterval {
assert(!Number.isNaN(x), `flush not defined for NaN`);
assert(!Number.isNaN(y), `flush not defined for NaN`);
+
const x_values = this.correctlyRounded(x);
const y_values = this.correctlyRounded(y);
const x_inputs = this.addFlushedIfNeeded(x_values);
const y_inputs = this.addFlushedIfNeeded(y_values);
+
+ if (op.domain !== undefined) {
+ // Cannot invoke op.domain() directly in the .some, because the narrowing doesn't propegate.
+ const domain = op.domain();
+
+ if (x_inputs.some(i => !domain.x.some(e => e.contains(i)))) {
+ return this.constants().unboundedInterval;
+ }
+
+ if (y_inputs.some(j => !domain.y.some(e => e.contains(j)))) {
+ return this.constants().unboundedInterval;
+ }
+ }
+
const intervals = new Set<FPInterval>();
x_inputs.forEach(inner_x => {
y_inputs.forEach(inner_y => {
@@ -2252,7 +2284,7 @@ export abstract class FPTraits {
}
const result = this.spanIntervals(
- ...x.bounds().map(b => this.roundAndFlushScalarToInterval(b, op))
+ ...x.endpoints().map(b => this.roundAndFlushScalarToInterval(b, op))
);
return result.isFinite() ? result : this.constants().unboundedInterval;
}
@@ -2282,8 +2314,8 @@ export abstract class FPTraits {
}
const outputs = new Set<FPInterval>();
- x.bounds().forEach(inner_x => {
- y.bounds().forEach(inner_y => {
+ x.endpoints().forEach(inner_x => {
+ y.endpoints().forEach(inner_y => {
outputs.add(this.roundAndFlushScalarPairToInterval(inner_x, inner_y, op));
});
});
@@ -2312,9 +2344,9 @@ export abstract class FPTraits {
}
const outputs = new Set<FPInterval>();
- x.bounds().forEach(inner_x => {
- y.bounds().forEach(inner_y => {
- z.bounds().forEach(inner_z => {
+ x.endpoints().forEach(inner_x => {
+ y.endpoints().forEach(inner_y => {
+ z.endpoints().forEach(inner_z => {
outputs.add(this.roundAndFlushScalarTripleToInterval(inner_x, inner_y, inner_z, op));
});
});
@@ -2337,7 +2369,7 @@ export abstract class FPTraits {
return this.constants().unboundedInterval;
}
- const x_values = cartesianProduct<number>(...x.map(e => e.bounds()));
+ const x_values = cartesianProduct<number>(...x.map(e => e.endpoints()));
const outputs = new Set<FPInterval>();
x_values.forEach(inner_x => {
@@ -2366,8 +2398,8 @@ export abstract class FPTraits {
return this.constants().unboundedInterval;
}
- const x_values = cartesianProduct<number>(...x.map(e => e.bounds()));
- const y_values = cartesianProduct<number>(...y.map(e => e.bounds()));
+ const x_values = cartesianProduct<number>(...x.map(e => e.endpoints()));
+ const y_values = cartesianProduct<number>(...y.map(e => e.endpoints()));
const outputs = new Set<FPInterval>();
x_values.forEach(inner_x => {
@@ -2393,7 +2425,7 @@ export abstract class FPTraits {
return this.constants().unboundedVector[x.length];
}
- const x_values = cartesianProduct<number>(...x.map(e => e.bounds()));
+ const x_values = cartesianProduct<number>(...x.map(e => e.endpoints()));
const outputs = new Set<FPVector>();
x_values.forEach(inner_x => {
@@ -2437,8 +2469,8 @@ export abstract class FPTraits {
return this.constants().unboundedVector[x.length];
}
- const x_values = cartesianProduct<number>(...x.map(e => e.bounds()));
- const y_values = cartesianProduct<number>(...y.map(e => e.bounds()));
+ const x_values = cartesianProduct<number>(...x.map(e => e.endpoints()));
+ const y_values = cartesianProduct<number>(...y.map(e => e.endpoints()));
const outputs = new Set<FPVector>();
x_values.forEach(inner_x => {
@@ -2495,12 +2527,15 @@ export abstract class FPTraits {
protected runMatrixToMatrixOp(m: FPMatrix, op: MatrixToMatrixOp): FPMatrix {
const num_cols = m.length;
const num_rows = m[0].length;
- if (m.some(c => c.some(r => !r.isFinite()))) {
- return this.constants().unboundedMatrix[num_cols][num_rows];
- }
+
+ // Do not check for OOB inputs and exit early here, because the shape of
+ // the output matrix may be determined by the operation being run,
+ // i.e. transpose.
const m_flat: readonly FPInterval[] = flatten2DArray(m);
- const m_values: ROArrayArray<number> = cartesianProduct<number>(...m_flat.map(e => e.bounds()));
+ const m_values: ROArrayArray<number> = cartesianProduct<number>(
+ ...m_flat.map(e => e.endpoints())
+ );
const outputs = new Set<FPMatrix>();
m_values.forEach(inner_m => {
@@ -2522,6 +2557,33 @@ export abstract class FPTraits {
/**
* Calculate the Matrix of acceptance intervals by running a scalar operation
+ * component-wise over a scalar and a matrix.
+ *
+ * An example of this is performing constant scaling.
+ *
+ * @param i scalar input
+ * @param m matrix input
+ * @param op scalar operation to be run component-wise
+ * @returns a matrix of intervals with the outputs of op.impl
+ */
+ protected runScalarPairToIntervalOpScalarMatrixComponentWise(
+ i: FPInterval,
+ m: FPMatrix,
+ op: ScalarPairToIntervalOp
+ ): FPMatrix {
+ const cols = m.length;
+ const rows = m[0].length;
+ return this.toMatrix(
+ unflatten2DArray(
+ flatten2DArray(m).map(e => this.runScalarPairToIntervalOp(i, e, op)),
+ cols,
+ rows
+ )
+ );
+ }
+
+ /**
+ * Calculate the Matrix of acceptance intervals by running a scalar operation
* component-wise over a pair of matrices.
*
* An example of this is performing matrix addition.
@@ -2531,14 +2593,14 @@ export abstract class FPTraits {
* @param op scalar operation to be run component-wise
* @returns a matrix of intervals with the outputs of op.impl
*/
- protected runScalarPairToIntervalOpMatrixComponentWise(
+ protected runScalarPairToIntervalOpMatrixMatrixComponentWise(
x: FPMatrix,
y: FPMatrix,
op: ScalarPairToIntervalOp
): FPMatrix {
assert(
x.length === y.length && x[0].length === y[0].length,
- `runScalarPairToIntervalOpMatrixComponentWise requires matrices of the same dimensions`
+ `runScalarPairToIntervalOpMatrixMatrixComponentWise requires matrices of the same dimensions`
);
const cols = x.length;
@@ -2673,7 +2735,7 @@ export abstract class FPTraits {
// This op is implemented differently for f32 and f16.
private readonly AcosIntervalOp: ScalarToIntervalOp = {
- impl: this.limitScalarToIntervalDomain(this.toInterval([-1.0, 1.0]), (n: number) => {
+ impl: (n: number) => {
assert(this.kind === 'f32' || this.kind === 'f16');
// acos(n) = atan2(sqrt(1.0 - n * n), n) or a polynomial approximation with absolute error
const y = this.sqrtInterval(this.subtractionInterval(1, this.multiplicationInterval(n, n)));
@@ -2682,7 +2744,10 @@ export abstract class FPTraits {
this.atan2Interval(y, n),
this.absoluteErrorInterval(Math.acos(n), approx_abs_error)
);
- }),
+ },
+ domain: () => {
+ return this.constants().negOneToOneInterval;
+ },
};
protected acosIntervalImpl(n: number): FPInterval {
@@ -2751,7 +2816,7 @@ export abstract class FPTraits {
) => FPInterval;
protected additionMatrixMatrixIntervalImpl(x: Array2D<number>, y: Array2D<number>): FPMatrix {
- return this.runScalarPairToIntervalOpMatrixComponentWise(
+ return this.runScalarPairToIntervalOpMatrixMatrixComponentWise(
this.toMatrix(x),
this.toMatrix(y),
this.AdditionIntervalOp
@@ -2766,16 +2831,19 @@ export abstract class FPTraits {
// This op is implemented differently for f32 and f16.
private readonly AsinIntervalOp: ScalarToIntervalOp = {
- impl: this.limitScalarToIntervalDomain(this.toInterval([-1.0, 1.0]), (n: number) => {
+ impl: (n: number) => {
assert(this.kind === 'f32' || this.kind === 'f16');
// asin(n) = atan2(n, sqrt(1.0 - n * n)) or a polynomial approximation with absolute error
const x = this.sqrtInterval(this.subtractionInterval(1, this.multiplicationInterval(n, n)));
- const approx_abs_error = this.kind === 'f32' ? 6.77e-5 : 3.91e-3;
+ const approx_abs_error = this.kind === 'f32' ? 6.81e-5 : 3.91e-3;
return this.spanIntervals(
this.atan2Interval(n, x),
this.absoluteErrorInterval(Math.asin(n), approx_abs_error)
);
- }),
+ },
+ domain: () => {
+ return this.constants().negOneToOneInterval;
+ },
};
/** Calculate an acceptance interval for asin(n) */
@@ -2836,29 +2904,23 @@ export abstract class FPTraits {
: [this.toInterval([-(2 ** 14), -(2 ** -14)]), this.toInterval([2 ** -14, 2 ** 14])];
const ulp_error = this.kind === 'f32' ? 4096 : 5;
return {
- impl: this.limitScalarPairToIntervalDomain(
- {
- x: domain_x,
- y: domain_y,
- },
- (y: number, x: number): FPInterval => {
- // Accurate result in f64
- let atan_yx = Math.atan(y / x);
- // Offset by +/-pi according to the definition. Use pi value in f64 because we are
- // handling accurate result.
- if (x < 0) {
- // x < 0, y > 0, result is atan(y/x) + π
- if (y > 0) {
- atan_yx = atan_yx + kValue.f64.positive.pi.whole;
- } else {
- // x < 0, y < 0, result is atan(y/x) - π
- atan_yx = atan_yx - kValue.f64.positive.pi.whole;
- }
+ impl: (y: number, x: number): FPInterval => {
+ // Accurate result in f64
+ let atan_yx = Math.atan(y / x);
+ // Offset by +/-pi according to the definition. Use pi value in f64 because we are
+ // handling accurate result.
+ if (x < 0) {
+ // x < 0, y > 0, result is atan(y/x) + π
+ if (y > 0) {
+ atan_yx = atan_yx + kValue.f64.positive.pi.whole;
+ } else {
+ // x < 0, y < 0, result is atan(y/x) - π
+ atan_yx = atan_yx - kValue.f64.positive.pi.whole;
}
-
- return this.ulpInterval(atan_yx, ulp_error);
}
- ),
+
+ return this.ulpInterval(atan_yx, ulp_error);
+ },
extrema: (y: FPInterval, x: FPInterval): [FPInterval, FPInterval] => {
// There is discontinuity, which generates an unbounded result, at y/x = 0 that will dominate the accuracy
if (y.contains(0)) {
@@ -2869,6 +2931,9 @@ export abstract class FPTraits {
}
return [y, x];
},
+ domain: () => {
+ return { x: domain_x, y: domain_y };
+ },
};
}
@@ -2984,14 +3049,14 @@ export abstract class FPTraits {
public abstract readonly clampIntervals: ScalarTripleToInterval[];
private readonly CosIntervalOp: ScalarToIntervalOp = {
- impl: this.limitScalarToIntervalDomain(
- this.constants().negPiToPiInterval,
- (n: number): FPInterval => {
- assert(this.kind === 'f32' || this.kind === 'f16');
- const abs_error = this.kind === 'f32' ? 2 ** -11 : 2 ** -7;
- return this.absoluteErrorInterval(Math.cos(n), abs_error);
- }
- ),
+ impl: (n: number): FPInterval => {
+ assert(this.kind === 'f32' || this.kind === 'f16');
+ const abs_error = this.kind === 'f32' ? 2 ** -11 : 2 ** -7;
+ return this.absoluteErrorInterval(Math.cos(n), abs_error);
+ },
+ domain: () => {
+ return this.constants().negPiToPiInterval;
+ },
};
protected cosIntervalImpl(n: number): FPInterval {
@@ -3041,7 +3106,11 @@ export abstract class FPTraits {
this.multiplicationInterval(x[0], y[1]),
this.multiplicationInterval(x[1], y[0])
);
- return [r0, r1, r2];
+
+ if (r0.isFinite() && r1.isFinite() && r2.isFinite()) {
+ return [r0, r1, r2];
+ }
+ return this.constants().unboundedVector[3];
},
};
@@ -3281,18 +3350,12 @@ export abstract class FPTraits {
? [this.toInterval([-(2 ** 126), -(2 ** -126)]), this.toInterval([2 ** -126, 2 ** 126])]
: [this.toInterval([-(2 ** 14), -(2 ** -14)]), this.toInterval([2 ** -14, 2 ** 14])];
return {
- impl: this.limitScalarPairToIntervalDomain(
- {
- x: domain_x,
- y: domain_y,
- },
- (x: number, y: number): FPInterval => {
- if (y === 0) {
- return constants.unboundedInterval;
- }
- return this.ulpInterval(x / y, 2.5);
+ impl: (x: number, y: number): FPInterval => {
+ if (y === 0) {
+ return constants.unboundedInterval;
}
- ),
+ return this.ulpInterval(x / y, 2.5);
+ },
extrema: (x: FPInterval, y: FPInterval): [FPInterval, FPInterval] => {
// division has a discontinuity at y = 0.
if (y.contains(0)) {
@@ -3300,6 +3363,9 @@ export abstract class FPTraits {
}
return [x, y];
},
+ domain: () => {
+ return { x: domain_x, y: domain_y };
+ },
};
}
@@ -3346,7 +3412,10 @@ export abstract class FPTraits {
x: readonly number[] | readonly FPInterval[],
y: readonly number[] | readonly FPInterval[]
): FPInterval {
- assert(x.length === y.length, `dot not defined for vectors with different lengths`);
+ assert(
+ x.length === y.length,
+ `dot not defined for vectors with different lengths, x = ${x}, y = ${y}`
+ );
return this.runVectorPairToIntervalOp(this.toVector(x), this.toVector(y), this.DotIntervalOp);
}
@@ -3517,12 +3586,12 @@ export abstract class FPTraits {
public abstract readonly fractInterval: (n: number) => FPInterval;
private readonly InverseSqrtIntervalOp: ScalarToIntervalOp = {
- impl: this.limitScalarToIntervalDomain(
- this.constants().greaterThanZeroInterval,
- (n: number): FPInterval => {
- return this.ulpInterval(1 / Math.sqrt(n), 2);
- }
- ),
+ impl: (n: number): FPInterval => {
+ return this.ulpInterval(1 / Math.sqrt(n), 2);
+ },
+ domain: () => {
+ return this.constants().greaterThanZeroInterval;
+ },
};
protected inverseSqrtIntervalImpl(n: number | FPInterval): FPInterval {
@@ -3534,21 +3603,19 @@ export abstract class FPTraits {
private readonly LdexpIntervalOp: ScalarPairToIntervalOp = {
impl: (e1: number, e2: number) => {
- assert(this.kind === 'f32' || this.kind === 'f16');
assert(Number.isInteger(e2), 'the second param of ldexp must be an integer');
- const bias = this.kind === 'f32' ? 127 : 15;
// Spec explicitly calls indeterminate value if e2 > bias + 1
- if (e2 > bias + 1) {
+ if (e2 > this.constants().bias + 1) {
return this.constants().unboundedInterval;
}
- // The spec says the result of ldexp(e1, e2) = e1 * 2 ^ e2, and the accuracy is correctly
- // rounded to the true value, so the inheritance framework does not need to be invoked to
- // determine bounds.
+ // The spec says the result of ldexp(e1, e2) = e1 * 2 ^ e2, and the
+ // accuracy is correctly rounded to the true value, so the inheritance
+ // framework does not need to be invoked to determine endpoints.
// Instead, the value at a higher precision is calculated and passed to
// correctlyRoundedInterval.
const result = e1 * 2 ** e2;
if (!Number.isFinite(result)) {
- // Overflowed TS's number type, so definitely out of bounds for f32/f16
+ // Overflowed TS's number type, so definitely out of bounds
return this.constants().unboundedInterval;
}
// The result may be zero if e2 + bias <= 0, but we can't simply span the interval to 0.0.
@@ -3606,17 +3673,17 @@ export abstract class FPTraits {
) => FPInterval;
private readonly LogIntervalOp: ScalarToIntervalOp = {
- impl: this.limitScalarToIntervalDomain(
- this.constants().greaterThanZeroInterval,
- (n: number): FPInterval => {
- assert(this.kind === 'f32' || this.kind === 'f16');
- const abs_error = this.kind === 'f32' ? 2 ** -21 : 2 ** -7;
- if (n >= 0.5 && n <= 2.0) {
- return this.absoluteErrorInterval(Math.log(n), abs_error);
- }
- return this.ulpInterval(Math.log(n), 3);
+ impl: (n: number): FPInterval => {
+ assert(this.kind === 'f32' || this.kind === 'f16');
+ const abs_error = this.kind === 'f32' ? 2 ** -21 : 2 ** -7;
+ if (n >= 0.5 && n <= 2.0) {
+ return this.absoluteErrorInterval(Math.log(n), abs_error);
}
- ),
+ return this.ulpInterval(Math.log(n), 3);
+ },
+ domain: () => {
+ return this.constants().greaterThanZeroInterval;
+ },
};
protected logIntervalImpl(x: number | FPInterval): FPInterval {
@@ -3627,17 +3694,17 @@ export abstract class FPTraits {
public abstract readonly logInterval: (x: number | FPInterval) => FPInterval;
private readonly Log2IntervalOp: ScalarToIntervalOp = {
- impl: this.limitScalarToIntervalDomain(
- this.constants().greaterThanZeroInterval,
- (n: number): FPInterval => {
- assert(this.kind === 'f32' || this.kind === 'f16');
- const abs_error = this.kind === 'f32' ? 2 ** -21 : 2 ** -7;
- if (n >= 0.5 && n <= 2.0) {
- return this.absoluteErrorInterval(Math.log2(n), abs_error);
- }
- return this.ulpInterval(Math.log2(n), 3);
+ impl: (n: number): FPInterval => {
+ assert(this.kind === 'f32' || this.kind === 'f16');
+ const abs_error = this.kind === 'f32' ? 2 ** -21 : 2 ** -7;
+ if (n >= 0.5 && n <= 2.0) {
+ return this.absoluteErrorInterval(Math.log2(n), abs_error);
}
- ),
+ return this.ulpInterval(Math.log2(n), 3);
+ },
+ domain: () => {
+ return this.constants().greaterThanZeroInterval;
+ },
};
protected log2IntervalImpl(x: number | FPInterval): FPInterval {
@@ -3791,14 +3858,10 @@ export abstract class FPTraits {
}
protected multiplicationMatrixScalarIntervalImpl(mat: Array2D<number>, scalar: number): FPMatrix {
- const cols = mat.length;
- const rows = mat[0].length;
- return this.toMatrix(
- unflatten2DArray(
- flatten2DArray(mat).map(e => this.multiplicationInterval(e, scalar)),
- cols,
- rows
- )
+ return this.runScalarPairToIntervalOpScalarMatrixComponentWise(
+ this.toInterval(scalar),
+ this.toMatrix(mat),
+ this.MultiplicationIntervalOp
);
}
@@ -3809,7 +3872,7 @@ export abstract class FPTraits {
) => FPMatrix;
protected multiplicationScalarMatrixIntervalImpl(scalar: number, mat: Array2D<number>): FPMatrix {
- return this.multiplicationMatrixScalarIntervalImpl(mat, scalar);
+ return this.multiplicationMatrixScalarInterval(mat, scalar);
}
/** Calculate an acceptance interval of x * y, when x is a scalar and y is a matrix */
@@ -3830,13 +3893,22 @@ export abstract class FPTraits {
const x_transposed = this.transposeInterval(mat_x);
+ let oob_result: boolean = false;
const result: FPInterval[][] = [...Array(y_cols)].map(_ => [...Array(x_rows)]);
mat_y.forEach((y, i) => {
x_transposed.forEach((x, j) => {
result[i][j] = this.dotInterval(x, y);
+ if (!oob_result && !result[i][j].isFinite()) {
+ oob_result = true;
+ }
});
});
+ if (oob_result) {
+ return this.constants().unboundedMatrix[result.length as 2 | 3 | 4][
+ result[0].length as 2 | 3 | 4
+ ];
+ }
return result as ROArrayArray<FPInterval> as FPMatrix;
}
@@ -3896,7 +3968,11 @@ export abstract class FPTraits {
private readonly NormalizeIntervalOp: VectorToVectorOp = {
impl: (n: readonly number[]): FPVector => {
const length = this.lengthInterval(n);
- return this.toVector(n.map(e => this.divisionInterval(e, length)));
+ const result = this.toVector(n.map(e => this.divisionInterval(e, length)));
+ if (result.some(r => !r.isFinite())) {
+ return this.constants().unboundedVector[result.length];
+ }
+ return result;
},
};
@@ -3955,11 +4031,16 @@ export abstract class FPTraits {
// y = normal of reflecting surface
const t = this.multiplicationInterval(2.0, this.dotInterval(x, y));
const rhs = this.multiplyVectorByScalar(y, t);
- return this.runScalarPairToIntervalOpVectorComponentWise(
+ const result = this.runScalarPairToIntervalOpVectorComponentWise(
this.toVector(x),
rhs,
this.SubtractionIntervalOp
);
+
+ if (result.some(r => !r.isFinite())) {
+ return this.constants().unboundedVector[result.length];
+ }
+ return result;
},
};
@@ -4015,11 +4096,16 @@ export abstract class FPTraits {
const k_sqrt = this.sqrtInterval(k);
const t = this.additionInterval(dot_times_r, k_sqrt); // t = r * dot(i, s) + sqrt(k)
- return this.runScalarPairToIntervalOpVectorComponentWise(
+ const result = this.runScalarPairToIntervalOpVectorComponentWise(
this.multiplyVectorByScalar(i, r),
this.multiplyVectorByScalar(s, t),
this.SubtractionIntervalOp
); // (i * r) - (s * t)
+
+ if (result.some(r => !r.isFinite())) {
+ return this.constants().unboundedVector[result.length];
+ }
+ return result;
}
/** Calculate acceptance interval vectors of reflect(i, s, r) */
@@ -4116,14 +4202,14 @@ export abstract class FPTraits {
public abstract readonly signInterval: (n: number) => FPInterval;
private readonly SinIntervalOp: ScalarToIntervalOp = {
- impl: this.limitScalarToIntervalDomain(
- this.constants().negPiToPiInterval,
- (n: number): FPInterval => {
- assert(this.kind === 'f32' || this.kind === 'f16');
- const abs_error = this.kind === 'f32' ? 2 ** -11 : 2 ** -7;
- return this.absoluteErrorInterval(Math.sin(n), abs_error);
- }
- ),
+ impl: (n: number): FPInterval => {
+ assert(this.kind === 'f32' || this.kind === 'f16');
+ const abs_error = this.kind === 'f32' ? 2 ** -11 : 2 ** -7;
+ return this.absoluteErrorInterval(Math.sin(n), abs_error);
+ },
+ domain: () => {
+ return this.constants().negPiToPiInterval;
+ },
};
protected sinIntervalImpl(n: number): FPInterval {
@@ -4250,7 +4336,7 @@ export abstract class FPTraits {
) => FPInterval;
protected subtractionMatrixMatrixIntervalImpl(x: Array2D<number>, y: Array2D<number>): FPMatrix {
- return this.runScalarPairToIntervalOpMatrixComponentWise(
+ return this.runScalarPairToIntervalOpMatrixMatrixComponentWise(
this.toMatrix(x),
this.toMatrix(y),
this.SubtractionIntervalOp
@@ -4375,6 +4461,7 @@ class F32Traits extends FPTraits {
sixth: kValue.f32.negative.pi.sixth,
},
},
+ bias: 127,
unboundedInterval: kF32UnboundedInterval,
zeroInterval: kF32ZeroInterval,
// Have to use the constants.ts values here, because values defined in the
@@ -4389,6 +4476,7 @@ class F32Traits extends FPTraits {
kValue.f32.positive.subnormal.min,
kValue.f32.positive.max
),
+ negOneToOneInterval: new FPInterval('f32', -1, 1),
zeroVector: {
2: [kF32ZeroInterval, kF32ZeroInterval],
3: [kF32ZeroInterval, kF32ZeroInterval, kF32ZeroInterval],
@@ -4520,6 +4608,11 @@ class F32Traits extends FPTraits {
public readonly flushSubnormal = flushSubnormalNumberF32;
public readonly oneULP = oneULPF32;
public readonly scalarBuilder = f32;
+ public readonly scalarRange = scalarF32Range;
+ public readonly sparseScalarRange = sparseScalarF32Range;
+ public readonly vectorRange = vectorF32Range;
+ public readonly sparseVectorRange = sparseVectorF32Range;
+ public readonly sparseMatrixRange = sparseMatrixF32Range;
// Framework - Fundamental Error Intervals - Overrides
public readonly absoluteErrorInterval = this.absoluteErrorIntervalImpl.bind(this);
@@ -4686,7 +4779,7 @@ class F32Traits extends FPTraits {
private unpack2x16floatIntervalImpl(n: number): FPVector {
assert(
n >= kValue.u32.min && n <= kValue.u32.max,
- 'unpack2x16floatInterval only accepts values on the bounds of u32'
+ 'unpack2x16floatInterval only accepts valid u32 values'
);
this.unpackDataU32[0] = n;
if (this.unpackDataF16.some(f => !isFiniteF16(f))) {
@@ -4710,7 +4803,7 @@ class F32Traits extends FPTraits {
private unpack2x16snormIntervalImpl(n: number): FPVector {
assert(
n >= kValue.u32.min && n <= kValue.u32.max,
- 'unpack2x16snormInterval only accepts values on the bounds of u32'
+ 'unpack2x16snormInterval only accepts valid u32 values'
);
const op = (n: number): FPInterval => {
return this.ulpInterval(Math.max(n / 32767, -1), 3);
@@ -4726,7 +4819,7 @@ class F32Traits extends FPTraits {
private unpack2x16unormIntervalImpl(n: number): FPVector {
assert(
n >= kValue.u32.min && n <= kValue.u32.max,
- 'unpack2x16unormInterval only accepts values on the bounds of u32'
+ 'unpack2x16unormInterval only accepts valid u32 values'
);
const op = (n: number): FPInterval => {
return this.ulpInterval(n / 65535, 3);
@@ -4742,7 +4835,7 @@ class F32Traits extends FPTraits {
private unpack4x8snormIntervalImpl(n: number): FPVector {
assert(
n >= kValue.u32.min && n <= kValue.u32.max,
- 'unpack4x8snormInterval only accepts values on the bounds of u32'
+ 'unpack4x8snormInterval only accepts valid u32 values'
);
const op = (n: number): FPInterval => {
return this.ulpInterval(Math.max(n / 127, -1), 3);
@@ -4762,7 +4855,7 @@ class F32Traits extends FPTraits {
private unpack4x8unormIntervalImpl(n: number): FPVector {
assert(
n >= kValue.u32.min && n <= kValue.u32.max,
- 'unpack4x8unormInterval only accepts values on the bounds of u32'
+ 'unpack4x8unormInterval only accepts valid u32 values'
);
const op = (n: number): FPInterval => {
return this.ulpInterval(n / 255, 3);
@@ -4836,6 +4929,7 @@ class FPAbstractTraits extends FPTraits {
sixth: kValue.f64.negative.pi.sixth,
},
},
+ bias: 1023,
unboundedInterval: kAbstractUnboundedInterval,
zeroInterval: kAbstractZeroInterval,
// Have to use the constants.ts values here, because values defined in the
@@ -4850,6 +4944,8 @@ class FPAbstractTraits extends FPTraits {
kValue.f64.positive.subnormal.min,
kValue.f64.positive.max
),
+ negOneToOneInterval: new FPInterval('abstract', -1, 1),
+
zeroVector: {
2: [kAbstractZeroInterval, kAbstractZeroInterval],
3: [kAbstractZeroInterval, kAbstractZeroInterval, kAbstractZeroInterval],
@@ -4992,14 +5088,17 @@ class FPAbstractTraits extends FPTraits {
unreachable(`'FPAbstractTraits.oneULP should never be called`);
};
public readonly scalarBuilder = abstractFloat;
+ public readonly scalarRange = scalarF64Range;
+ public readonly sparseScalarRange = sparseScalarF64Range;
+ public readonly vectorRange = vectorF64Range;
+ public readonly sparseVectorRange = sparseVectorF64Range;
+ public readonly sparseMatrixRange = sparseMatrixF64Range;
// Framework - Fundamental Error Intervals - Overrides
- public readonly absoluteErrorInterval = this.unboundedAbsoluteErrorInterval.bind(this);
+ public readonly absoluteErrorInterval = this.unimplementedAbsoluteErrorInterval.bind(this); // Should use FP.f32 instead
public readonly correctlyRoundedInterval = this.correctlyRoundedIntervalImpl.bind(this);
public readonly correctlyRoundedMatrix = this.correctlyRoundedMatrixImpl.bind(this);
- public readonly ulpInterval = (n: number, numULP: number): FPInterval => {
- return this.toInterval(kF32Traits.ulpInterval(n, numULP));
- };
+ public readonly ulpInterval = this.unimplementedUlpInterval.bind(this); // Should use FP.f32 instead
// Framework - API - Overrides
public readonly absInterval = this.absIntervalImpl.bind(this);
@@ -5023,40 +5122,38 @@ class FPAbstractTraits extends FPTraits {
'atan2Interval'
);
public readonly atanhInterval = this.unimplementedScalarToInterval.bind(this, 'atanhInterval');
- public readonly ceilInterval = this.unimplementedScalarToInterval.bind(this, 'ceilInterval');
+ public readonly ceilInterval = this.ceilIntervalImpl.bind(this);
public readonly clampMedianInterval = this.clampMedianIntervalImpl.bind(this);
public readonly clampMinMaxInterval = this.clampMinMaxIntervalImpl.bind(this);
public readonly clampIntervals = [this.clampMedianInterval, this.clampMinMaxInterval];
public readonly cosInterval = this.unimplementedScalarToInterval.bind(this, 'cosInterval');
public readonly coshInterval = this.unimplementedScalarToInterval.bind(this, 'coshInterval');
- public readonly crossInterval = this.crossIntervalImpl.bind(this);
- public readonly degreesInterval = this.degreesIntervalImpl.bind(this);
+ public readonly crossInterval = this.unimplementedVectorPairToVector.bind(this, 'crossInterval');
+ public readonly degreesInterval = this.unimplementedScalarToInterval.bind(
+ this,
+ 'degreesInterval'
+ );
public readonly determinantInterval = this.unimplementedMatrixToInterval.bind(
this,
- 'determinantInterval'
+ 'determinant'
);
public readonly distanceInterval = this.unimplementedDistance.bind(this);
- public readonly divisionInterval = (
- x: number | FPInterval,
- y: number | FPInterval
- ): FPInterval => {
- return this.toInterval(kF32Traits.divisionInterval(x, y));
- };
+ public readonly divisionInterval = this.unimplementedScalarPairToInterval.bind(
+ this,
+ 'divisionInterval'
+ );
public readonly dotInterval = this.unimplementedVectorPairToInterval.bind(this, 'dotInterval');
public readonly expInterval = this.unimplementedScalarToInterval.bind(this, 'expInterval');
public readonly exp2Interval = this.unimplementedScalarToInterval.bind(this, 'exp2Interval');
public readonly faceForwardIntervals = this.unimplementedFaceForward.bind(this);
public readonly floorInterval = this.floorIntervalImpl.bind(this);
- public readonly fmaInterval = this.fmaIntervalImpl.bind(this);
- public readonly fractInterval = this.unimplementedScalarToInterval.bind(this, 'fractInterval');
+ public readonly fmaInterval = this.unimplementedScalarTripleToInterval.bind(this, 'fmaInterval');
+ public readonly fractInterval = this.fractIntervalImpl.bind(this);
public readonly inverseSqrtInterval = this.unimplementedScalarToInterval.bind(
this,
'inverseSqrtInterval'
);
- public readonly ldexpInterval = this.unimplementedScalarPairToInterval.bind(
- this,
- 'ldexpInterval'
- );
+ public readonly ldexpInterval = this.ldexpIntervalImpl.bind(this);
public readonly lengthInterval = this.unimplementedLength.bind(this);
public readonly logInterval = this.unimplementedScalarToInterval.bind(this, 'logInterval');
public readonly log2Interval = this.unimplementedScalarToInterval.bind(this, 'log2Interval');
@@ -5077,14 +5174,10 @@ class FPAbstractTraits extends FPTraits {
this,
'multiplicationMatrixMatrixInterval'
);
- public readonly multiplicationMatrixScalarInterval = this.unimplementedMatrixScalarToMatrix.bind(
- this,
- 'multiplicationMatrixScalarInterval'
- );
- public readonly multiplicationScalarMatrixInterval = this.unimplementedScalarMatrixToMatrix.bind(
- this,
- 'multiplicationScalarMatrixInterval'
- );
+ public readonly multiplicationMatrixScalarInterval =
+ this.multiplicationMatrixScalarIntervalImpl.bind(this);
+ public readonly multiplicationScalarMatrixInterval =
+ this.multiplicationScalarMatrixIntervalImpl.bind(this);
public readonly multiplicationMatrixVectorInterval = this.unimplementedMatrixVectorToVector.bind(
this,
'multiplicationMatrixVectorInterval'
@@ -5099,16 +5192,17 @@ class FPAbstractTraits extends FPTraits {
'normalizeInterval'
);
public readonly powInterval = this.unimplementedScalarPairToInterval.bind(this, 'powInterval');
- public readonly radiansInterval = this.radiansIntervalImpl.bind(this);
+ public readonly radiansInterval = this.unimplementedScalarToInterval.bind(this, 'radiansImpl');
public readonly reflectInterval = this.unimplementedVectorPairToVector.bind(
this,
'reflectInterval'
);
public readonly refractInterval = this.unimplementedRefract.bind(this);
- public readonly remainderInterval = (x: number, y: number): FPInterval => {
- return this.toInterval(kF32Traits.remainderInterval(x, y));
- };
- public readonly roundInterval = this.unimplementedScalarToInterval.bind(this, 'roundInterval');
+ public readonly remainderInterval = this.unimplementedScalarPairToInterval.bind(
+ this,
+ 'remainderInterval'
+ );
+ public readonly roundInterval = this.roundIntervalImpl.bind(this);
public readonly saturateInterval = this.saturateIntervalImpl.bind(this);
public readonly signInterval = this.signIntervalImpl.bind(this);
public readonly sinInterval = this.unimplementedScalarToInterval.bind(this, 'sinInterval');
@@ -5118,7 +5212,7 @@ class FPAbstractTraits extends FPTraits {
'smoothStepInterval'
);
public readonly sqrtInterval = this.unimplementedScalarToInterval.bind(this, 'sqrtInterval');
- public readonly stepInterval = this.unimplementedScalarPairToInterval.bind(this, 'stepInterval');
+ public readonly stepInterval = this.stepIntervalImpl.bind(this);
public readonly subtractionInterval = this.subtractionIntervalImpl.bind(this);
public readonly subtractionMatrixMatrixInterval =
this.subtractionMatrixMatrixIntervalImpl.bind(this);
@@ -5179,6 +5273,7 @@ class F16Traits extends FPTraits {
sixth: kValue.f16.negative.pi.sixth,
},
},
+ bias: 15,
unboundedInterval: kF16UnboundedInterval,
zeroInterval: kF16ZeroInterval,
// Have to use the constants.ts values here, because values defined in the
@@ -5193,6 +5288,8 @@ class F16Traits extends FPTraits {
kValue.f16.positive.subnormal.min,
kValue.f16.positive.max
),
+ negOneToOneInterval: new FPInterval('f16', -1, 1),
+
zeroVector: {
2: [kF16ZeroInterval, kF16ZeroInterval],
3: [kF16ZeroInterval, kF16ZeroInterval, kF16ZeroInterval],
@@ -5324,6 +5421,11 @@ class F16Traits extends FPTraits {
public readonly flushSubnormal = flushSubnormalNumberF16;
public readonly oneULP = oneULPF16;
public readonly scalarBuilder = f16;
+ public readonly scalarRange = scalarF16Range;
+ public readonly sparseScalarRange = sparseScalarF16Range;
+ public readonly vectorRange = vectorF16Range;
+ public readonly sparseVectorRange = sparseVectorF16Range;
+ public readonly sparseMatrixRange = sparseMatrixF16Range;
// Framework - Fundamental Error Intervals - Overrides
public readonly absoluteErrorInterval = this.absoluteErrorIntervalImpl.bind(this);
@@ -5437,5 +5539,6 @@ export function isRepresentable(value: number, type: ScalarType) {
const constants = fpTraitsFor(type).constants();
return value >= constants.negative.min && value <= constants.positive.max;
}
+
assert(false, `isRepresentable() is not yet implemented for type ${type}`);
}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/util/math.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/util/math.ts
index 851db40c71..20d7818df6 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/util/math.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/util/math.ts
@@ -414,6 +414,25 @@ export function oneULPF32(target: number, mode: FlushMode = 'flush'): number {
}
/**
+ * @returns an integer value between 0..0xffffffff using a simple non-cryptographic hash function
+ * @param values integers to generate hash from.
+ */
+export function hashU32(...values: number[]) {
+ let n = 0x3504_f333;
+ for (const v of values) {
+ n = v + (n << 7) + (n >>> 1);
+ n = (n * 0x29493) & 0xffff_ffff;
+ }
+ n ^= n >>> 8;
+ n += n << 15;
+ n = n & 0xffff_ffff;
+ if (n < 0) {
+ n = ~n * 2 + 1;
+ }
+ return n;
+}
+
+/**
* @returns ulp(x), the unit of least precision for a specific number as a 32-bit float
*
* ulp(x) is the distance between the two floating point numbers nearest x.
@@ -881,6 +900,41 @@ export function biasedRange(a: number, b: number, num_steps: number): readonly n
}
/**
+ * Version of biasedRange that operates on bigint values
+ *
+ * biasedRange was not made into a generic or to take in (number|bigint),
+ * because that introduces a bunch of complexity overhead related to type
+ * differentiation.
+ *
+ * Scaling is used internally so that the number of possible indices is
+ * significantly larger than num_steps. This is done to avoid duplicate entries
+ * in the resulting range due to quantizing to integers during the calculation.
+ *
+ * If a and b are close together, such that the number of integers between them
+ * is close to num_steps, then duplicates will occur regardless of scaling.
+ */
+export function biasedRangeBigInt(a: bigint, b: bigint, num_steps: number): readonly bigint[] {
+ if (num_steps <= 0) {
+ return [];
+ }
+
+ // Avoid division by 0
+ if (num_steps === 1) {
+ return [a];
+ }
+
+ const c = 2;
+ const scaling = 1000;
+ const scaled_num_steps = num_steps * scaling;
+
+ return Array.from(Array(num_steps).keys()).map(i => {
+ const biased_i = Math.pow(i / (num_steps - 1), c); // Floating Point on [0, 1]
+ const scaled_i = Math.trunc((scaled_num_steps - 1) * biased_i); // Integer on [0, scaled_num_steps - 1]
+ return lerpBigInt(a, b, scaled_i, scaled_num_steps);
+ });
+}
+
+/**
* @returns an ascending sorted array of numbers spread over the entire range of 32-bit floats
*
* Numbers are divided into 4 regions: negative normals, negative subnormals, positive subnormals & positive normals.
@@ -891,12 +945,12 @@ export function biasedRange(a: number, b: number, num_steps: number): readonly n
* for a wide range of magnitudes to be generated, instead of being extremely biased towards the edges of the f32 range.
*
* This function is intended to provide dense coverage of the f32 range, for a minimal list of values to use to cover
- * f32 behaviour, use sparseF32Range instead.
+ * f32 behaviour, use sparseScalarF32Range instead.
*
* @param counts structure param with 4 entries indicating the number of entries to be generated each region, entries
* must be 0 or greater.
*/
-export function fullF32Range(
+export function scalarF32Range(
counts: {
neg_norm?: number;
neg_sub?: number;
@@ -943,8 +997,8 @@ export function fullF32Range(
* @param low the lowest f32 value to permit when filtered
* @param high the highest f32 value to permit when filtered
*/
-export function sourceFilteredF32Range(source: String, low: number, high: number): Array<number> {
- return fullF32Range().filter(x => source !== 'const' || (x >= low && x <= high));
+export function filteredScalarF32Range(source: String, low: number, high: number): Array<number> {
+ return scalarF32Range().filter(x => source !== 'const' || (x >= low && x <= high));
}
/**
@@ -958,12 +1012,12 @@ export function sourceFilteredF32Range(source: String, low: number, high: number
* for a wide range of magnitudes to be generated, instead of being extremely biased towards the edges of the f16 range.
*
* This function is intended to provide dense coverage of the f16 range, for a minimal list of values to use to cover
- * f16 behaviour, use sparseF16Range instead.
+ * f16 behaviour, use sparseScalarF16Range instead.
*
* @param counts structure param with 4 entries indicating the number of entries to be generated each region, entries
* must be 0 or greater.
*/
-export function fullF16Range(
+export function scalarF16Range(
counts: {
neg_norm?: number;
neg_sub?: number;
@@ -1009,12 +1063,12 @@ export function fullF16Range(
* for a wide range of magnitudes to be generated, instead of being extremely biased towards the edges of the f64 range.
*
* This function is intended to provide dense coverage of the f64 range, for a minimal list of values to use to cover
- * f64 behaviour, use sparseF64Range instead.
+ * f64 behaviour, use sparseScalarF64Range instead.
*
* @param counts structure param with 4 entries indicating the number of entries to be generated each region, entries
* must be 0 or greater.
*/
-export function fullF64Range(
+export function scalarF64Range(
counts: {
neg_norm?: number;
neg_sub?: number;
@@ -1065,7 +1119,7 @@ export function fullF64Range(
* @param counts structure param with 4 entries indicating the number of entries
* to be generated each region, entries must be 0 or greater.
*/
-export function filteredF64Range(
+export function limitedScalarF64Range(
begin: number,
end: number,
counts: { neg_norm?: number; neg_sub?: number; pos_sub: number; pos_norm: number } = {
@@ -1136,27 +1190,18 @@ export function sparseI32Range(): readonly number[] {
const kVectorI32Values = {
2: kInterestingI32Values.flatMap(f => [
[f, 1],
- [1, f],
- [f, -1],
[-1, f],
]),
3: kInterestingI32Values.flatMap(f => [
- [f, 1, 2],
- [1, f, 2],
- [1, 2, f],
- [f, -1, -2],
- [-1, f, -2],
- [-1, -2, f],
+ [f, 1, -2],
+ [-1, f, 2],
+ [1, -2, f],
]),
4: kInterestingI32Values.flatMap(f => [
- [f, 1, 2, 3],
- [1, f, 2, 3],
- [1, 2, f, 3],
- [1, 2, 3, f],
- [f, -1, -2, -3],
- [-1, f, -2, -3],
- [-1, -2, f, -3],
- [-1, -2, -3, f],
+ [f, -1, 2, 3],
+ [1, f, -2, 3],
+ [1, 2, f, -3],
+ [-1, 2, -3, f],
]),
};
@@ -1178,6 +1223,38 @@ export function vectorI32Range(dim: number): ROArrayArray<number> {
return kVectorI32Values[dim];
}
+const kSparseVectorI32Values = {
+ 2: sparseI32Range().map((i, idx) => [idx % 2 === 0 ? i : idx, idx % 2 === 1 ? i : -idx]),
+ 3: sparseI32Range().map((i, idx) => [
+ idx % 3 === 0 ? i : idx,
+ idx % 3 === 1 ? i : -idx,
+ idx % 3 === 2 ? i : idx,
+ ]),
+ 4: sparseI32Range().map((i, idx) => [
+ idx % 4 === 0 ? i : idx,
+ idx % 4 === 1 ? i : -idx,
+ idx % 4 === 2 ? i : idx,
+ idx % 4 === 3 ? i : -idx,
+ ]),
+};
+
+/**
+ * Minimal set of vectors, indexed by dimension, that contain interesting
+ * abstract integer values.
+ *
+ * This is an even more stripped down version of `vectorI32Range` for when
+ * pairs of vectors are being tested.
+ * All interesting integers from sparseI32Range are guaranteed to be
+ * tested, but not in every position.
+ */
+export function sparseVectorI32Range(dim: number): ROArrayArray<number> {
+ assert(
+ dim === 2 || dim === 3 || dim === 4,
+ 'sparseVectorI32Range only accepts dimensions 2, 3, and 4'
+ );
+ return kSparseVectorI32Values[dim];
+}
+
/**
* @returns an ascending sorted array of numbers spread over the entire range of 32-bit signed ints
*
@@ -1256,6 +1333,38 @@ export function vectorU32Range(dim: number): ROArrayArray<number> {
return kVectorU32Values[dim];
}
+const kSparseVectorU32Values = {
+ 2: sparseU32Range().map((i, idx) => [idx % 2 === 0 ? i : idx, idx % 2 === 1 ? i : -idx]),
+ 3: sparseU32Range().map((i, idx) => [
+ idx % 3 === 0 ? i : idx,
+ idx % 3 === 1 ? i : -idx,
+ idx % 3 === 2 ? i : idx,
+ ]),
+ 4: sparseU32Range().map((i, idx) => [
+ idx % 4 === 0 ? i : idx,
+ idx % 4 === 1 ? i : -idx,
+ idx % 4 === 2 ? i : idx,
+ idx % 4 === 3 ? i : -idx,
+ ]),
+};
+
+/**
+ * Minimal set of vectors, indexed by dimension, that contain interesting
+ * abstract integer values.
+ *
+ * This is an even more stripped down version of `vectorU32Range` for when
+ * pairs of vectors are being tested.
+ * All interesting integers from sparseU32Range are guaranteed to be
+ * tested, but not in every position.
+ */
+export function sparseVectorU32Range(dim: number): ROArrayArray<number> {
+ assert(
+ dim === 2 || dim === 3 || dim === 4,
+ 'sparseVectorU32Range only accepts dimensions 2, 3, and 4'
+ );
+ return kSparseVectorU32Values[dim];
+}
+
/**
* @returns an ascending sorted array of numbers spread over the entire range of 32-bit unsigned ints
*
@@ -1267,6 +1376,124 @@ export function fullU32Range(count: number = 50): Array<number> {
return [0, ...biasedRange(1, kValue.u32.max, count)].map(Math.trunc);
}
+/** Short list of i64 values of interest to test against */
+const kInterestingI64Values: readonly bigint[] = [
+ kValue.i64.negative.max,
+ kValue.i64.negative.max / 2n,
+ -256n,
+ -10n,
+ -1n,
+ 0n,
+ 1n,
+ 10n,
+ 256n,
+ kValue.i64.positive.max / 2n,
+ kValue.i64.positive.max,
+];
+
+/** @returns minimal i64 values that cover the entire range of i64 behaviours
+ *
+ * This is used instead of fullI64Range when the number of test cases being
+ * generated is a super linear function of the length of i64 values which is
+ * leading to time outs.
+ */
+export function sparseI64Range(): readonly bigint[] {
+ return kInterestingI64Values;
+}
+
+const kVectorI64Values = {
+ 2: kInterestingI64Values.flatMap(f => [
+ [f, 1n],
+ [-1n, f],
+ ]),
+ 3: kInterestingI64Values.flatMap(f => [
+ [f, 1n, -2n],
+ [-1n, f, 2n],
+ [1n, -2n, f],
+ ]),
+ 4: kInterestingI64Values.flatMap(f => [
+ [f, -1n, 2n, 3n],
+ [1n, f, -2n, 3n],
+ [1n, 2n, f, -3n],
+ [-1n, 2n, -3n, f],
+ ]),
+};
+
+/**
+ * Returns set of vectors, indexed by dimension containing interesting i64
+ * values.
+ *
+ * The tests do not do the simple option for coverage of computing the cartesian
+ * product of all of the interesting i64 values N times for vecN tests,
+ * because that creates a huge number of tests for vec3 and vec4, leading to
+ * time outs.
+ *
+ * Instead they insert the interesting i64 values into each location of the
+ * vector to get a spread of testing over the entire range. This reduces the
+ * number of cases being run substantially, but maintains coverage.
+ */
+export function vectorI64Range(dim: number): ROArrayArray<bigint> {
+ assert(dim === 2 || dim === 3 || dim === 4, 'vectorI64Range only accepts dimensions 2, 3, and 4');
+ return kVectorI64Values[dim];
+}
+
+const kSparseVectorI64Values = {
+ 2: sparseI64Range().map((i, idx) => [
+ idx % 2 === 0 ? i : BigInt(idx),
+ idx % 2 === 1 ? i : -BigInt(idx),
+ ]),
+ 3: sparseI64Range().map((i, idx) => [
+ idx % 3 === 0 ? i : BigInt(idx),
+ idx % 3 === 1 ? i : -BigInt(idx),
+ idx % 3 === 2 ? i : BigInt(idx),
+ ]),
+ 4: sparseI64Range().map((i, idx) => [
+ idx % 4 === 0 ? i : BigInt(idx),
+ idx % 4 === 1 ? i : -BigInt(idx),
+ idx % 4 === 2 ? i : BigInt(idx),
+ idx % 4 === 3 ? i : -BigInt(idx),
+ ]),
+};
+
+/**
+ * Minimal set of vectors, indexed by dimension, that contain interesting
+ * abstract integer values.
+ *
+ * This is an even more stripped down version of `vectorI64Range` for when
+ * pairs of vectors are being tested.
+ * All interesting integers from sparseI64Range are guaranteed to be
+ * tested, but not in every position.
+ */
+export function sparseVectorI64Range(dim: number): ROArrayArray<bigint> {
+ assert(
+ dim === 2 || dim === 3 || dim === 4,
+ 'sparseVectorI64Range only accepts dimensions 2, 3, and 4'
+ );
+ return kSparseVectorI64Values[dim];
+}
+
+/**
+ * @returns an ascending sorted array of numbers spread over the entire range of 64-bit signed ints
+ *
+ * Numbers are divided into 2 regions: negatives, and positives, with their spreads biased towards 0
+ * Zero is included in range.
+ *
+ * @param counts structure param with 2 entries indicating the number of entries to be generated each region, values must be 0 or greater.
+ */
+export function fullI64Range(
+ counts: {
+ negative?: number;
+ positive: number;
+ } = { positive: 50 }
+): Array<bigint> {
+ counts.negative = counts.negative === undefined ? counts.positive : counts.negative;
+ return [
+ ...biasedRangeBigInt(kValue.i64.negative.min, -1n, counts.negative),
+ 0n,
+ ...biasedRangeBigInt(1n, kValue.i64.positive.max, counts.positive),
+ ];
+}
+
/** Short list of f32 values of interest to test against */
const kInterestingF32Values: readonly number[] = [
kValue.f32.negative.min,
@@ -1299,34 +1526,25 @@ const kInterestingF32Values: readonly number[] = [
* specific values of interest. If there are known values of interest they
* should be appended to this list in the test generation code.
*/
-export function sparseF32Range(): readonly number[] {
+export function sparseScalarF32Range(): readonly number[] {
return kInterestingF32Values;
}
const kVectorF32Values = {
- 2: sparseF32Range().flatMap(f => [
+ 2: kInterestingF32Values.flatMap(f => [
[f, 1.0],
- [1.0, f],
- [f, -1.0],
[-1.0, f],
]),
- 3: sparseF32Range().flatMap(f => [
- [f, 1.0, 2.0],
- [1.0, f, 2.0],
- [1.0, 2.0, f],
- [f, -1.0, -2.0],
- [-1.0, f, -2.0],
- [-1.0, -2.0, f],
+ 3: kInterestingF32Values.flatMap(f => [
+ [f, 1.0, -2.0],
+ [-1.0, f, 2.0],
+ [1.0, -2.0, f],
]),
- 4: sparseF32Range().flatMap(f => [
- [f, 1.0, 2.0, 3.0],
- [1.0, f, 2.0, 3.0],
- [1.0, 2.0, f, 3.0],
- [1.0, 2.0, 3.0, f],
- [f, -1.0, -2.0, -3.0],
- [-1.0, f, -2.0, -3.0],
- [-1.0, -2.0, f, -3.0],
- [-1.0, -2.0, -3.0, f],
+ 4: kInterestingF32Values.flatMap(f => [
+ [f, -1.0, 2.0, 3.0],
+ [1.0, f, -2.0, 3.0],
+ [1.0, 2.0, f, -3.0],
+ [-1.0, 2.0, -3.0, f],
]),
};
@@ -1349,13 +1567,13 @@ export function vectorF32Range(dim: number): ROArrayArray<number> {
}
const kSparseVectorF32Values = {
- 2: sparseF32Range().map((f, idx) => [idx % 2 === 0 ? f : idx, idx % 2 === 1 ? f : -idx]),
- 3: sparseF32Range().map((f, idx) => [
+ 2: sparseScalarF32Range().map((f, idx) => [idx % 2 === 0 ? f : idx, idx % 2 === 1 ? f : -idx]),
+ 3: sparseScalarF32Range().map((f, idx) => [
idx % 3 === 0 ? f : idx,
idx % 3 === 1 ? f : -idx,
idx % 3 === 2 ? f : idx,
]),
- 4: sparseF32Range().map((f, idx) => [
+ 4: sparseScalarF32Range().map((f, idx) => [
idx % 4 === 0 ? f : idx,
idx % 4 === 1 ? f : -idx,
idx % 4 === 2 ? f : idx,
@@ -1369,8 +1587,8 @@ const kSparseVectorF32Values = {
*
* This is an even more stripped down version of `vectorF32Range` for when
* pairs of vectors are being tested.
- * All of the interesting floats from sparseF32 are guaranteed to be tested, but
- * not in every position.
+ * All of the interesting floats from sparseScalarF32 are guaranteed to be
+ * tested, but not in every position.
*/
export function sparseVectorF32Range(dim: number): ROArrayArray<number> {
assert(
@@ -1480,16 +1698,16 @@ const kSparseMatrixF32Values = {
};
/**
- * Returns a minimal set of matrices, indexed by dimension containing interesting
- * float values.
+ * Returns a minimal set of matrices, indexed by dimension containing
+ * interesting float values.
*
* This is the matrix analogue of `sparseVectorF32Range`, so it is producing a
* minimal coverage set of matrices that test all of the interesting f32 values.
* There is not a more expansive set of matrices, since matrices are even more
* expensive than vectors for increasing runtime with coverage.
*
- * All of the interesting floats from sparseF32 are guaranteed to be tested, but
- * not in every position.
+ * All of the interesting floats from sparseScalarF32 are guaranteed to be
+ * tested, but not in every position.
*/
export function sparseMatrixF32Range(c: number, r: number): ROArrayArrayArray<number> {
assert(
@@ -1535,34 +1753,25 @@ const kInterestingF16Values: readonly number[] = [
* specific values of interest. If there are known values of interest they
* should be appended to this list in the test generation code.
*/
-export function sparseF16Range(): readonly number[] {
+export function sparseScalarF16Range(): readonly number[] {
return kInterestingF16Values;
}
const kVectorF16Values = {
- 2: sparseF16Range().flatMap(f => [
+ 2: kInterestingF16Values.flatMap(f => [
[f, 1.0],
- [1.0, f],
- [f, -1.0],
[-1.0, f],
]),
- 3: sparseF16Range().flatMap(f => [
- [f, 1.0, 2.0],
- [1.0, f, 2.0],
- [1.0, 2.0, f],
- [f, -1.0, -2.0],
- [-1.0, f, -2.0],
- [-1.0, -2.0, f],
+ 3: kInterestingF16Values.flatMap(f => [
+ [f, 1.0, -2.0],
+ [-1.0, f, 2.0],
+ [1.0, -2.0, f],
]),
- 4: sparseF16Range().flatMap(f => [
- [f, 1.0, 2.0, 3.0],
- [1.0, f, 2.0, 3.0],
- [1.0, 2.0, f, 3.0],
- [1.0, 2.0, 3.0, f],
- [f, -1.0, -2.0, -3.0],
- [-1.0, f, -2.0, -3.0],
- [-1.0, -2.0, f, -3.0],
- [-1.0, -2.0, -3.0, f],
+ 4: kInterestingF16Values.flatMap(f => [
+ [f, -1.0, 2.0, 3.0],
+ [1.0, f, -2.0, 3.0],
+ [1.0, 2.0, f, -3.0],
+ [-1.0, 2.0, -3.0, f],
]),
};
@@ -1585,13 +1794,13 @@ export function vectorF16Range(dim: number): ROArrayArray<number> {
}
const kSparseVectorF16Values = {
- 2: sparseF16Range().map((f, idx) => [idx % 2 === 0 ? f : idx, idx % 2 === 1 ? f : -idx]),
- 3: sparseF16Range().map((f, idx) => [
+ 2: sparseScalarF16Range().map((f, idx) => [idx % 2 === 0 ? f : idx, idx % 2 === 1 ? f : -idx]),
+ 3: sparseScalarF16Range().map((f, idx) => [
idx % 3 === 0 ? f : idx,
idx % 3 === 1 ? f : -idx,
idx % 3 === 2 ? f : idx,
]),
- 4: sparseF16Range().map((f, idx) => [
+ 4: sparseScalarF16Range().map((f, idx) => [
idx % 4 === 0 ? f : idx,
idx % 4 === 1 ? f : -idx,
idx % 4 === 2 ? f : idx,
@@ -1605,8 +1814,8 @@ const kSparseVectorF16Values = {
*
* This is an even more stripped down version of `vectorF16Range` for when
* pairs of vectors are being tested.
- * All of the interesting floats from sparseF16 are guaranteed to be tested, but
- * not in every position.
+ * All of the interesting floats from sparseScalarF16 are guaranteed to be
+ * tested, but not in every position.
*/
export function sparseVectorF16Range(dim: number): ROArrayArray<number> {
assert(
@@ -1724,7 +1933,7 @@ const kSparseMatrixF16Values = {
* There is not a more expansive set of matrices, since matrices are even more
* expensive than vectors for increasing runtime with coverage.
*
- * All of the interesting floats from sparseF16 are guaranteed to be tested, but
+ * All of the interesting floats from sparseScalarF16 are guaranteed to be tested, but
* not in every position.
*/
export function sparseMatrixF16Range(c: number, r: number): ROArrayArray<number>[] {
@@ -1771,34 +1980,25 @@ const kInterestingF64Values: readonly number[] = [
* specific values of interest. If there are known values of interest they
* should be appended to this list in the test generation code.
*/
-export function sparseF64Range(): readonly number[] {
+export function sparseScalarF64Range(): readonly number[] {
return kInterestingF64Values;
}
const kVectorF64Values = {
- 2: sparseF64Range().flatMap(f => [
+ 2: kInterestingF64Values.flatMap(f => [
[f, 1.0],
- [1.0, f],
- [f, -1.0],
[-1.0, f],
]),
- 3: sparseF64Range().flatMap(f => [
- [f, 1.0, 2.0],
- [1.0, f, 2.0],
- [1.0, 2.0, f],
- [f, -1.0, -2.0],
- [-1.0, f, -2.0],
- [-1.0, -2.0, f],
+ 3: kInterestingF64Values.flatMap(f => [
+ [f, 1.0, -2.0],
+ [-1.0, f, 2.0],
+ [1.0, -2.0, f],
]),
- 4: sparseF64Range().flatMap(f => [
- [f, 1.0, 2.0, 3.0],
- [1.0, f, 2.0, 3.0],
- [1.0, 2.0, f, 3.0],
- [1.0, 2.0, 3.0, f],
- [f, -1.0, -2.0, -3.0],
- [-1.0, f, -2.0, -3.0],
- [-1.0, -2.0, f, -3.0],
- [-1.0, -2.0, -3.0, f],
+ 4: kInterestingF64Values.flatMap(f => [
+ [f, -1.0, 2.0, 3.0],
+ [1.0, f, -2.0, 3.0],
+ [1.0, 2.0, f, -3.0],
+ [-1.0, 2.0, -3.0, f],
]),
};
@@ -1821,13 +2021,13 @@ export function vectorF64Range(dim: number): ROArrayArray<number> {
}
const kSparseVectorF64Values = {
- 2: sparseF64Range().map((f, idx) => [idx % 2 === 0 ? f : idx, idx % 2 === 1 ? f : -idx]),
- 3: sparseF64Range().map((f, idx) => [
+ 2: sparseScalarF64Range().map((f, idx) => [idx % 2 === 0 ? f : idx, idx % 2 === 1 ? f : -idx]),
+ 3: sparseScalarF64Range().map((f, idx) => [
idx % 3 === 0 ? f : idx,
idx % 3 === 1 ? f : -idx,
idx % 3 === 2 ? f : idx,
]),
- 4: sparseF64Range().map((f, idx) => [
+ 4: sparseScalarF64Range().map((f, idx) => [
idx % 4 === 0 ? f : idx,
idx % 4 === 1 ? f : -idx,
idx % 4 === 2 ? f : idx,
@@ -1841,7 +2041,7 @@ const kSparseVectorF64Values = {
*
* This is an even more stripped down version of `vectorF64Range` for when
* pairs of vectors are being tested.
- * All the interesting floats from sparseF64 are guaranteed to be tested, but
+ * All the interesting floats from sparseScalarF64 are guaranteed to be tested, but
* not in every position.
*/
export function sparseVectorF64Range(dim: number): ROArrayArray<number> {
@@ -1960,19 +2160,19 @@ const kSparseMatrixF64Values = {
* There is not a more expansive set of matrices, since matrices are even more
* expensive than vectors for increasing runtime with coverage.
*
- * All the interesting floats from sparseF64 are guaranteed to be tested, but
+ * All the interesting floats from sparseScalarF64 are guaranteed to be tested, but
* not in every position.
*/
-export function sparseMatrixF64Range(c: number, r: number): ROArrayArray<number>[] {
+export function sparseMatrixF64Range(cols: number, rows: number): ROArrayArrayArray<number> {
assert(
- c === 2 || c === 3 || c === 4,
+ cols === 2 || cols === 3 || cols === 4,
'sparseMatrixF64Range only accepts column counts of 2, 3, and 4'
);
assert(
- r === 2 || r === 3 || r === 4,
+ rows === 2 || rows === 3 || rows === 4,
'sparseMatrixF64Range only accepts row counts of 2, 3, and 4'
);
- return kSparseMatrixF64Values[c][r];
+ return kSparseMatrixF64Values[cols][rows];
}
/**
@@ -2009,8 +2209,8 @@ export function signExtend(n: number, bits: number): number {
return (n << shift) >> shift;
}
-export interface QuantizeFunc {
- (num: number): number;
+export interface QuantizeFunc<T> {
+ (num: T): T;
}
/** @returns the closest 32-bit floating point value to the input */
@@ -2051,11 +2251,25 @@ export function quantizeToU32(num: number): number {
return Math.trunc(num);
}
+/**
+ * @returns the closest 64-bit signed integer value to the input.
+ */
+export function quantizeToI64(num: bigint): bigint {
+ if (num >= kValue.i64.positive.max) {
+ return kValue.i64.positive.max;
+ }
+ if (num <= kValue.i64.negative.min) {
+ return kValue.i64.negative.min;
+ }
+ return num;
+}
+
/** @returns whether the number is an integer and a power of two */
export function isPowerOfTwo(n: number): boolean {
if (!Number.isInteger(n)) {
return false;
}
+ assert((n | 0) === n, 'isPowerOfTwo only supports 32-bit numbers');
return n !== 0 && (n & (n - 1)) === 0;
}
@@ -2245,3 +2459,32 @@ export function every2DArray<T>(m: ROArrayArray<T>, op: (input: T) => boolean):
);
return m.every(col => col.every(el => op(el)));
}
+
+/**
+ * Subtracts 2 vectors
+ */
+export function subtractVectors(v1: readonly number[], v2: readonly number[]) {
+ return v1.map((v, i) => v - v2[i]);
+}
+
+/**
+ * Computes the dot product of 2 vectors
+ */
+export function dotProduct(v1: readonly number[], v2: readonly number[]) {
+ return v1.reduce((a, v, i) => a + v * v2[i], 0);
+}
+
+/** @returns the absolute value of a bigint */
+export function absBigInt(v: bigint): bigint {
+ return v < 0n ? -v : v;
+}
+
+/** @returns the maximum from a list of bigints */
+export function maxBigInt(...vals: bigint[]): bigint {
+ return vals.reduce((prev, cur) => (cur > prev ? cur : prev));
+}
+
+/** @returns the minimum from a list of bigints */
+export function minBigInt(...vals: bigint[]): bigint {
+ return vals.reduce((prev, cur) => (cur < prev ? cur : prev));
+}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/util/pretty_diff_tables.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/util/pretty_diff_tables.ts
index af98ab7ecf..8dab839c1f 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/util/pretty_diff_tables.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/util/pretty_diff_tables.ts
@@ -1,6 +1,28 @@
import { range } from '../../common/util/util.js';
/**
+ * @returns a function that converts numerics to strings, depending on if they
+ * should be treated as integers or not.
+ */
+export function numericToStringBuilder(is_integer: boolean): (n: number | bigint) => string {
+ if (is_integer) {
+ return (val: number | bigint): string => {
+ if (typeof val === 'number') {
+ return val.toFixed();
+ }
+ return val.toString();
+ };
+ }
+
+ return (val: number | bigint): string => {
+ if (typeof val === 'number') {
+ return val.toPrecision(6);
+ }
+ return val.toString();
+ };
+}
+
+/**
* Pretty-prints a "table" of cell values (each being `number | string`), right-aligned.
* Each row may be any iterator, including lazily-generated (potentially infinite) rows.
*
@@ -12,8 +34,11 @@ import { range } from '../../common/util/util.js';
* Each remaining argument provides one row for the table.
*/
export function generatePrettyTable(
- { fillToWidth, numberToString }: { fillToWidth: number; numberToString: (n: number) => string },
- rows: ReadonlyArray<Iterable<string | number>>
+ {
+ fillToWidth,
+ numericToString,
+ }: { fillToWidth: number; numericToString: (n: number | bigint) => string },
+ rows: ReadonlyArray<Iterable<string | number | bigint>>
): string {
const rowStrings = range(rows.length, () => '');
let totalTableWidth = 0;
@@ -23,7 +48,13 @@ export function generatePrettyTable(
for (;;) {
const cellsForColumn = iters.map(iter => {
const r = iter.next(); // Advance the iterator for each row, in lock-step.
- return r.done ? undefined : typeof r.value === 'number' ? numberToString(r.value) : r.value;
+ if (r.done) {
+ return undefined;
+ }
+ if (typeof r.value === 'number' || typeof r.value === 'bigint') {
+ return numericToString(r.value);
+ }
+ return r.value;
});
if (cellsForColumn.every(cell => cell === undefined)) break;
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/util/shader.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/util/shader.ts
index 2a09061527..721d121aba 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/util/shader.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/util/shader.ts
@@ -11,6 +11,27 @@ export const kDefaultFragmentShaderCode = `
return vec4<f32>(1.0, 1.0, 1.0, 1.0);
}`;
+// MAINTENANCE_TODO(#3344): deduplicate fullscreen quad shader code.
+export const kFullscreenQuadVertexShaderCode = `
+ struct VertexOutput {
+ @builtin(position) Position : vec4<f32>
+ };
+
+ @vertex fn main(@builtin(vertex_index) VertexIndex : u32) -> VertexOutput {
+ var pos = array<vec2<f32>, 6>(
+ vec2<f32>( 1.0, 1.0),
+ vec2<f32>( 1.0, -1.0),
+ vec2<f32>(-1.0, -1.0),
+ vec2<f32>( 1.0, 1.0),
+ vec2<f32>(-1.0, -1.0),
+ vec2<f32>(-1.0, 1.0));
+
+ var output : VertexOutput;
+ output.Position = vec4<f32>(pos[VertexIndex], 0.0, 1.0);
+ return output;
+ }
+`;
+
const kPlainTypeInfo = {
i32: {
suffix: '',
@@ -157,7 +178,9 @@ export function getFragmentShaderCodeWithOutput(
}`;
}
-export type TShaderStage = 'compute' | 'vertex' | 'fragment' | 'empty';
+export const kValidShaderStages = ['compute', 'vertex', 'fragment'] as const;
+export type TValidShaderStage = (typeof kValidShaderStages)[number];
+export type TShaderStage = TValidShaderStage | 'empty';
/**
* Return a foo shader of the given stage with the given entry point
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/base.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/base.ts
index 67b4fc7156..8da318aae6 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/base.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/base.ts
@@ -157,15 +157,23 @@ export function viewDimensionsForTextureDimension(textureDimension: GPUTextureDi
}
}
-/** Returns the default view dimension for a given texture descriptor. */
-export function defaultViewDimensionsForTexture(textureDescriptor: Readonly<GPUTextureDescriptor>) {
- switch (textureDescriptor.dimension) {
+/** Returns the effective view dimension for a given texture dimension and depthOrArrayLayers */
+export function effectiveViewDimensionForDimension(
+ viewDimension: GPUTextureViewDimension | undefined,
+ dimension: GPUTextureDimension | undefined,
+ depthOrArrayLayers: number
+) {
+ if (viewDimension) {
+ return viewDimension;
+ }
+
+ switch (dimension || '2d') {
case '1d':
return '1d';
- case '2d': {
- const sizeDict = reifyExtent3D(textureDescriptor.size);
- return sizeDict.depthOrArrayLayers > 1 ? '2d-array' : '2d';
- }
+ case '2d':
+ case undefined:
+ return depthOrArrayLayers > 1 ? '2d-array' : '2d';
+ break;
case '3d':
return '3d';
default:
@@ -173,6 +181,28 @@ export function defaultViewDimensionsForTexture(textureDescriptor: Readonly<GPUT
}
}
+/** Returns the effective view dimension for a given texture */
+export function effectiveViewDimensionForTexture(
+ texture: GPUTexture,
+ viewDimension: GPUTextureViewDimension | undefined
+) {
+ return effectiveViewDimensionForDimension(
+ viewDimension,
+ texture.dimension,
+ texture.depthOrArrayLayers
+ );
+}
+
+/** Returns the default view dimension for a given texture descriptor. */
+export function defaultViewDimensionsForTexture(textureDescriptor: Readonly<GPUTextureDescriptor>) {
+ const sizeDict = reifyExtent3D(textureDescriptor.size);
+ return effectiveViewDimensionForDimension(
+ undefined,
+ textureDescriptor.dimension,
+ sizeDict.depthOrArrayLayers
+ );
+}
+
/** Reifies the optional fields of `GPUTextureDescriptor`.
* MAINTENANCE_TODO: viewFormats should not be omitted here, but it seems likely that the
* @webgpu/types definition will have to change before we can include it again.
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/color_space_conversions.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/color_space_conversions.spec.ts
new file mode 100644
index 0000000000..e6b281d57a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/color_space_conversions.spec.ts
@@ -0,0 +1,108 @@
+export const description = 'Color space conversion helpers';
+
+import { Fixture } from '../../../common/framework/fixture.js';
+import { makeTestGroup } from '../../../common/framework/test_group.js';
+import { ErrorWithExtra } from '../../../common/util/util.js';
+import { makeInPlaceColorConversion } from '../color_space_conversion.js';
+import { clamp } from '../math.js';
+
+import { TexelView } from './texel_view.js';
+import { findFailedPixels } from './texture_ok.js';
+
+const kTestColors = [
+ [0xff, 0, 0],
+ [0, 0xff, 0],
+ [0, 0, 0xff],
+ [0x80, 0x80, 0],
+ [0, 0x80, 0x80],
+ [0x80, 0, 0x80],
+] as const;
+
+function floatToU8(v: number) {
+ return clamp(Math.round(v * 255), { min: 0, max: 255 });
+}
+
+export const g = makeTestGroup(Fixture);
+
+g.test('util_matches_2d_canvas')
+ .desc(`Test color space conversion helpers matches canvas 2d's color space conversion`)
+ .params(u =>
+ u.combineWithParams([
+ { srcColorSpace: 'srgb', dstColorSpace: 'display-p3' },
+ { srcColorSpace: 'display-p3', dstColorSpace: 'srgb' },
+ ] as { srcColorSpace: PredefinedColorSpace; dstColorSpace: PredefinedColorSpace }[])
+ )
+ .fn(t => {
+ const { srcColorSpace, dstColorSpace } = t.params;
+
+ // putImageData an ImageData(srcColorSpace) in to a canvas2D(dstColorSpace)
+ // then call getImageData. This will convert the colors via the canvas 2D API
+ const width = kTestColors.length;
+ const height = 1;
+ const imgData = new ImageData(
+ new Uint8ClampedArray(kTestColors.map(v => [...v, 255]).flat()),
+ width,
+ height,
+ { colorSpace: srcColorSpace }
+ );
+ const ctx = new OffscreenCanvas(width, height).getContext('2d', {
+ colorSpace: dstColorSpace,
+ })!;
+ ctx.putImageData(imgData, 0, 0);
+ const expectedData = ctx.getImageData(0, 0, width, height).data;
+
+ const conversionFn = makeInPlaceColorConversion({
+ srcPremultiplied: false,
+ dstPremultiplied: false,
+ srcColorSpace,
+ dstColorSpace,
+ });
+
+ // Convert the data via our conversion functions
+ const convertedData = new Uint8ClampedArray(
+ kTestColors
+ .map(color => {
+ const [R, G, B] = color.map(v => v / 255);
+ const floatColor = { R, G, B, A: 1 };
+ conversionFn(floatColor);
+ return [
+ floatToU8(floatColor.R),
+ floatToU8(floatColor.G),
+ floatToU8(floatColor.B),
+ floatToU8(floatColor.A),
+ ];
+ })
+ .flat()
+ );
+
+ const subrectOrigin = [0, 0, 0];
+ const subrectSize = [width, height, 1];
+ const areaDesc = {
+ bytesPerRow: width * 4,
+ rowsPerImage: height,
+ subrectOrigin,
+ subrectSize,
+ };
+
+ const format = 'rgba8unorm';
+ const actTexelView = TexelView.fromTextureDataByReference(format, convertedData, areaDesc);
+ const expTexelView = TexelView.fromTextureDataByReference(format, expectedData, areaDesc);
+
+ const failedPixelsMessage = findFailedPixels(
+ format,
+ { x: 0, y: 0, z: 0 },
+ { width, height, depthOrArrayLayers: 1 },
+ { actTexelView, expTexelView },
+ { maxDiffULPsForNormFormat: 0 }
+ );
+
+ if (failedPixelsMessage !== undefined) {
+ const msg = 'Color space conversion had unexpected results:\n' + failedPixelsMessage;
+ t.expectOK(
+ new ErrorWithExtra(msg, () => ({
+ expTexelView,
+ actTexelView,
+ }))
+ );
+ }
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_data.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_data.spec.ts
index 20f075e6f2..b063c939a9 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_data.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_data.spec.ts
@@ -297,7 +297,9 @@ TODO: Test NaN, Infinity, -Infinity [1]`
g.test('ufloat_texel_data_in_shader')
.desc(
`
-TODO: Test NaN, Infinity [1]`
+Note: this uses values that are representable by both rg11b10ufloat and rgb9e5ufloat.
+
+TODO: Test NaN, Infinity`
)
.params(u =>
u
@@ -312,21 +314,19 @@ TODO: Test NaN, Infinity [1]`
// Test extrema
makeParam(format, () => 0),
- // [2]: Test NaN, Infinity
-
// Test some values
- makeParam(format, () => 0.119140625),
- makeParam(format, () => 1.40625),
- makeParam(format, () => 24896),
+ makeParam(format, () => 128),
+ makeParam(format, () => 1984),
+ makeParam(format, () => 3968),
// Test scattered mixed values
makeParam(format, (bitLength, i) => {
- return [24896, 1.40625, 0.119140625, 0.23095703125][i];
+ return [128, 1984, 3968][i];
}),
// Test mixed values that are close in magnitude.
makeParam(format, (bitLength, i) => {
- return [0.1337890625, 0.17919921875, 0.119140625, 0.125][i];
+ return [0.05859375, 0.03125, 0.03515625][i];
}),
];
})
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_data.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_data.ts
index 42490d800b..0555ac5920 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_data.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_data.ts
@@ -1,5 +1,6 @@
import { assert, unreachable } from '../../../common/util/util.js';
import { UncompressedTextureFormat, EncodableTextureFormat } from '../../format_info.js';
+import { kValue } from '../constants.js';
import {
assertInIntegerRange,
float32ToFloatBits,
@@ -424,6 +425,8 @@ function makeNormalizedInfo(
}
const dataType: ComponentDataType = opt.signed ? 'snorm' : 'unorm';
+ const min = opt.signed ? -1 : 0;
+ const max = 1;
return {
componentOrder,
componentInfo: makePerTexelComponent(componentOrder, {
@@ -438,7 +441,7 @@ function makeNormalizedInfo(
numberToBits,
bitsToNumber,
bitsToULPFromZero,
- numericRange: { min: opt.signed ? -1 : 0, max: 1 },
+ numericRange: { min, max, finiteMin: min, finiteMax: max },
};
}
@@ -454,9 +457,9 @@ function makeIntegerInfo(
opt: { signed: boolean }
): TexelRepresentationInfo {
assert(bitLength <= 32);
- const numericRange = opt.signed
- ? { min: -(2 ** (bitLength - 1)), max: 2 ** (bitLength - 1) - 1 }
- : { min: 0, max: 2 ** bitLength - 1 };
+ const min = opt.signed ? -(2 ** (bitLength - 1)) : 0;
+ const max = opt.signed ? 2 ** (bitLength - 1) - 1 : 2 ** bitLength - 1;
+ const numericRange = { min, max, finiteMin: min, finiteMax: max };
const maxUnsignedValue = 2 ** bitLength;
const encode = applyEach(
(n: number) => (assertInIntegerRange(n, bitLength, opt.signed), n),
@@ -576,8 +579,13 @@ function makeFloatInfo(
bitsToNumber,
bitsToULPFromZero,
numericRange: restrictedDepth
- ? { min: 0, max: 1 }
- : { min: Number.NEGATIVE_INFINITY, max: Number.POSITIVE_INFINITY },
+ ? { min: 0, max: 1, finiteMin: 0, finiteMax: 1 }
+ : {
+ min: Number.NEGATIVE_INFINITY,
+ max: Number.POSITIVE_INFINITY,
+ finiteMin: bitLength === 32 ? kValue.f32.negative.min : kValue.f16.negative.min,
+ finiteMax: bitLength === 32 ? kValue.f32.positive.max : kValue.f16.positive.max,
+ },
};
}
@@ -592,6 +600,7 @@ const identity = (n: number) => n;
const kFloat11Format = { signed: 0, exponentBits: 5, mantissaBits: 6, bias: 15 } as const;
const kFloat10Format = { signed: 0, exponentBits: 5, mantissaBits: 5, bias: 15 } as const;
+export type PerComponentFiniteMax = Record<TexelComponent, number>;
export type TexelRepresentationInfo = {
/** Order of components in the packed representation. */
readonly componentOrder: TexelComponent[];
@@ -619,7 +628,12 @@ export type TexelRepresentationInfo = {
/** Convert integer bit representations into ULPs-from-zero, e.g. unorm8 255 -> 255 ULPs */
readonly bitsToULPFromZero: ComponentMapFn;
/** The valid range of numeric "color" values, e.g. [0, Infinity] for ufloat. */
- readonly numericRange: null | { min: number; max: number };
+ readonly numericRange: null | {
+ min: number;
+ max: number;
+ finiteMin: number;
+ finiteMax: number | PerComponentFiniteMax;
+ };
// Add fields as needed
};
@@ -765,7 +779,7 @@ export const kTexelRepresentationInfo: {
A: normalizedIntegerAsFloat(components.A!, 2, false),
}),
bitsToULPFromZero: components => components,
- numericRange: { min: 0, max: 1 },
+ numericRange: { min: 0, max: 1, finiteMin: 0, finiteMax: 1 },
},
rg11b10ufloat: {
componentOrder: kRGB,
@@ -809,7 +823,16 @@ export const kTexelRepresentationInfo: {
G: floatBitsToNormalULPFromZero(components.G!, kFloat11Format),
B: floatBitsToNormalULPFromZero(components.B!, kFloat10Format),
}),
- numericRange: { min: 0, max: Number.POSITIVE_INFINITY },
+ numericRange: {
+ min: 0,
+ max: Number.POSITIVE_INFINITY,
+ finiteMin: 0,
+ finiteMax: {
+ R: floatBitsToNumber(0b111_1011_1111, kFloat11Format),
+ G: floatBitsToNumber(0b111_1011_1111, kFloat11Format),
+ B: floatBitsToNumber(0b11_1101_1111, kFloat10Format),
+ } as PerComponentFiniteMax,
+ },
},
rgb9e5ufloat: {
componentOrder: kRGB,
@@ -854,7 +877,12 @@ export const kTexelRepresentationInfo: {
G: floatBitsToNormalULPFromZero(components.G!, kUFloat9e5Format),
B: floatBitsToNormalULPFromZero(components.B!, kUFloat9e5Format),
}),
- numericRange: { min: 0, max: Number.POSITIVE_INFINITY },
+ numericRange: {
+ min: 0,
+ max: Number.POSITIVE_INFINITY,
+ finiteMin: 0,
+ finiteMax: ufloatM9E5BitsToNumber(0b11_1111_1111_1111, kUFloat9e5Format),
+ },
},
depth32float: makeFloatInfo([TexelComponent.Depth], 32, { restrictedDepth: true }),
depth16unorm: makeNormalizedInfo([TexelComponent.Depth], 16, { signed: false, sRGB: false }),
@@ -868,7 +896,7 @@ export const kTexelRepresentationInfo: {
numberToBits: () => unreachable('depth24plus has no representation'),
bitsToNumber: () => unreachable('depth24plus has no representation'),
bitsToULPFromZero: () => unreachable('depth24plus has no representation'),
- numericRange: { min: 0, max: 1 },
+ numericRange: { min: 0, max: 1, finiteMin: 0, finiteMax: 1 },
},
stencil8: makeIntegerInfo([TexelComponent.Stencil], 8, { signed: false }),
'depth32float-stencil8': {
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_view.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_view.ts
index fea23b674e..0b920ef699 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_view.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_view.ts
@@ -1,6 +1,6 @@
import { assert, memcpy } from '../../../common/util/util.js';
import { kTextureFormatInfo, EncodableTextureFormat } from '../../format_info.js';
-import { generatePrettyTable } from '../pretty_diff_tables.js';
+import { generatePrettyTable, numericToStringBuilder } from '../pretty_diff_tables.js';
import { reifyExtent3D, reifyOrigin3D } from '../unions.js';
import { fullSubrectCoordinates } from './base.js';
@@ -166,10 +166,13 @@ export class TexelView {
const info = kTextureFormatInfo[this.format];
const repr = kTexelRepresentationInfo[this.format];
- const integerSampleType = info.sampleType === 'uint' || info.sampleType === 'sint';
- const numberToString = integerSampleType
- ? (n: number) => n.toFixed()
- : (n: number) => n.toPrecision(6);
+ // MAINTENANCE_TODO: Print depth-stencil formats as float+int instead of float+float.
+ const printAsInteger = info.color
+ ? // For color, pick the type based on the format type
+ ['uint', 'sint'].includes(info.color.type)
+ : // Print depth as "float", depth-stencil as "float,float", stencil as "int".
+ !info.depth;
+ const numericToString = numericToStringBuilder(printAsInteger);
const componentOrderStr = repr.componentOrder.join(',') + ':';
const subrectCoords = [...fullSubrectCoordinates(subrectOrigin, subrectSize)];
@@ -188,13 +191,13 @@ export class TexelView {
yield* [' act. colors', '==', componentOrderStr];
for (const coords of subrectCoords) {
const pixel = t.color(coords);
- yield `${repr.componentOrder.map(ch => numberToString(pixel[ch]!)).join(',')}`;
+ yield `${repr.componentOrder.map(ch => numericToString(pixel[ch]!)).join(',')}`;
}
})(this);
const opts = {
fillToWidth: 120,
- numberToString,
+ numericToString,
};
return `${generatePrettyTable(opts, [printCoords, printActualBytes, printActualColors])}`;
}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texture_ok.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texture_ok.ts
index 7b85489246..d0267f0627 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texture_ok.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texture_ok.ts
@@ -2,7 +2,7 @@ import { assert, ErrorWithExtra, unreachable } from '../../../common/util/util.j
import { kTextureFormatInfo, EncodableTextureFormat } from '../../format_info.js';
import { GPUTest } from '../../gpu_test.js';
import { numbersApproximatelyEqual } from '../conversion.js';
-import { generatePrettyTable } from '../pretty_diff_tables.js';
+import { generatePrettyTable, numericToStringBuilder } from '../pretty_diff_tables.js';
import { reifyExtent3D, reifyOrigin3D } from '../unions.js';
import { fullSubrectCoordinates } from './base.js';
@@ -223,11 +223,13 @@ export function findFailedPixels(
const info = kTextureFormatInfo[format];
const repr = kTexelRepresentationInfo[format];
-
- const integerSampleType = info.sampleType === 'uint' || info.sampleType === 'sint';
- const numberToString = integerSampleType
- ? (n: number) => n.toFixed()
- : (n: number) => n.toPrecision(6);
+ // MAINTENANCE_TODO: Print depth-stencil formats as float+int instead of float+float.
+ const printAsInteger = info.color
+ ? // For color, pick the type based on the format type
+ ['uint', 'sint'].includes(info.color.type)
+ : // Print depth as "float", depth-stencil as "float,float", stencil as "int".
+ !info.depth;
+ const numericToString = numericToStringBuilder(printAsInteger);
const componentOrderStr = repr.componentOrder.join(',') + ':';
@@ -245,14 +247,14 @@ export function findFailedPixels(
yield* [' act. colors', '==', componentOrderStr];
for (const coords of failedPixels) {
const pixel = actTexelView.color(coords);
- yield `${repr.componentOrder.map(ch => numberToString(pixel[ch]!)).join(',')}`;
+ yield `${repr.componentOrder.map(ch => numericToString(pixel[ch]!)).join(',')}`;
}
})();
const printExpectedColors = (function* () {
yield* [' exp. colors', '==', componentOrderStr];
for (const coords of failedPixels) {
const pixel = expTexelView.color(coords);
- yield `${repr.componentOrder.map(ch => numberToString(pixel[ch]!)).join(',')}`;
+ yield `${repr.componentOrder.map(ch => numericToString(pixel[ch]!)).join(',')}`;
}
})();
const printActualULPs = (function* () {
@@ -272,7 +274,7 @@ export function findFailedPixels(
const opts = {
fillToWidth: 120,
- numberToString,
+ numericToString,
};
return `\
between ${lowerCorner} and ${upperCorner} inclusive:
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/canvas/configure.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/canvas/configure.spec.ts
index 163930e20e..b4da68ba36 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/canvas/configure.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/canvas/configure.spec.ts
@@ -13,7 +13,6 @@ import { GPUConst } from '../../constants.js';
import {
kAllTextureFormats,
kFeaturesForFormats,
- kTextureFormats,
filterFormatsByFeature,
viewCompatible,
} from '../../format_info.js';
@@ -387,7 +386,7 @@ g.test('viewFormats')
.combine('viewFormatFeature', kFeaturesForFormats)
.beginSubcases()
.expand('viewFormat', ({ viewFormatFeature }) =>
- filterFormatsByFeature(viewFormatFeature, kTextureFormats)
+ filterFormatsByFeature(viewFormatFeature, kAllTextureFormats)
)
)
.beforeAllSubcases(t => {
@@ -402,7 +401,7 @@ g.test('viewFormats')
const ctx = canvas.getContext('webgpu');
assert(ctx instanceof GPUCanvasContext, 'Failed to get WebGPU context from canvas');
- const compatible = viewCompatible(format, viewFormat);
+ const compatible = viewCompatible(t.isCompatibility, format, viewFormat);
// Test configure() produces an error if the formats aren't compatible.
t.expectValidationError(() => {
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/canvas/getCurrentTexture.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/canvas/getCurrentTexture.spec.ts
index 609dacb907..633d88d2b8 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/canvas/getCurrentTexture.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/canvas/getCurrentTexture.spec.ts
@@ -40,6 +40,27 @@ class GPUContextTest extends GPUTest {
return ctx;
}
+
+ expectTextureDestroyed(texture: GPUTexture, expectDestroyed = true) {
+ this.expectValidationError(() => {
+ // Try using the texture in a render pass. Because it's a canvas texture
+ // it should have RENDER_ATTACHMENT usage.
+ assert((texture.usage & GPUTextureUsage.RENDER_ATTACHMENT) !== 0);
+ const encoder = this.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: texture.createView(),
+ loadOp: 'clear',
+ storeOp: 'store',
+ },
+ ],
+ });
+ pass.end();
+ // Submitting should generate a validation error if the texture is destroyed.
+ this.queue.submit([encoder.finish()]);
+ }, expectDestroyed);
+ }
}
export const g = makeTestGroup(GPUContextTest);
@@ -170,7 +191,7 @@ g.test('multiple_frames')
// Ensure that each frame a new texture object is returned.
t.expect(currentTexture !== prevTexture);
- // Ensure that texture contents are transparent black.
+ // Ensure that the texture's initial contents are transparent black.
t.expectSingleColor(currentTexture, currentTexture.format, {
size: [currentTexture.width, currentTexture.height, 1],
exp: { R: 0, G: 0, B: 0, A: 0 },
@@ -178,7 +199,8 @@ g.test('multiple_frames')
}
if (clearTexture) {
- // Clear the texture to test that texture contents don't carry over from frame to frame.
+ // Fill the texture with a non-zero color, to test that texture
+ // contents don't carry over from frame to frame.
const encoder = t.device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [
@@ -215,10 +237,8 @@ g.test('multiple_frames')
}
}
- // Call frameCheck for the first time from requestAnimationFrame
- // To make sure two frameChecks are run in different frames for onscreen canvas.
- // offscreen canvas doesn't care.
- requestAnimationFrame(frameCheck);
+ // Render the first frame immediately. The rest will be triggered recursively.
+ frameCheck();
});
});
@@ -235,6 +255,8 @@ g.test('resize')
// Trigger a resize by changing the width.
ctx.canvas.width = 4;
+ t.expectTextureDestroyed(prevTexture);
+
// When the canvas resizes the texture returned by getCurrentTexture should immediately begin
// returning a new texture matching the update dimensions.
let currentTexture = ctx.getCurrentTexture();
@@ -263,7 +285,6 @@ g.test('resize')
t.expect(currentTexture.height === ctx.canvas.height);
t.expect(prevTexture.width === 4);
t.expect(prevTexture.height === 2);
- prevTexture = currentTexture;
// Ensure that texture contents are transparent black.
t.expectSingleColor(currentTexture, currentTexture.format, {
@@ -271,13 +292,31 @@ g.test('resize')
exp: { R: 0, G: 0, B: 0, A: 0 },
});
- // Simply setting the canvas width and height values to their current values should not trigger
- // a change in the texture.
- ctx.canvas.width = 4;
- ctx.canvas.height = 4;
-
- currentTexture = ctx.getCurrentTexture();
- t.expect(prevTexture === currentTexture);
+ // HTMLCanvasElement behaves differently than OffscreenCanvas
+ if (t.params.canvasType === 'onscreen') {
+ // Ensure canvas goes back to defaults when set to negative numbers.
+ ctx.canvas.width = -1;
+ currentTexture = ctx.getCurrentTexture();
+ t.expect(currentTexture.width === 300);
+ t.expect(currentTexture.height === 4);
+
+ ctx.canvas.height = -1;
+ currentTexture = ctx.getCurrentTexture();
+ t.expect(currentTexture.width === 300);
+ t.expect(currentTexture.height === 150);
+
+ // Setting the canvas width and height values to their current values should
+ // still trigger a change in the texture.
+ prevTexture = ctx.getCurrentTexture();
+ const { width, height } = ctx.canvas;
+ ctx.canvas.width = width;
+ ctx.canvas.height = height;
+
+ t.expectTextureDestroyed(prevTexture);
+
+ currentTexture = ctx.getCurrentTexture();
+ t.expect(prevTexture !== currentTexture);
+ }
});
g.test('expiry')
@@ -307,6 +346,14 @@ TODO: test more canvas types, and ways to update the rendering
.combine('prevFrameCallsite', ['runInNewCanvasFrame', 'requestAnimationFrame'] as const)
.combine('getCurrentTextureAgain', [true, false] as const)
)
+ .beforeAllSubcases(t => {
+ if (
+ t.params.prevFrameCallsite === 'requestAnimationFrame' &&
+ typeof requestAnimationFrame === 'undefined'
+ ) {
+ throw new SkipTestCase('requestAnimationFrame not available');
+ }
+ })
.fn(t => {
const { canvasType, prevFrameCallsite, getCurrentTextureAgain } = t.params;
const ctx = t.initCanvasContext(t.params.canvasType);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/canvas/readbackFromWebGPUCanvas.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/canvas/readbackFromWebGPUCanvas.spec.ts
index 7fd7142f00..84d940ed04 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/canvas/readbackFromWebGPUCanvas.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/canvas/readbackFromWebGPUCanvas.spec.ts
@@ -14,7 +14,12 @@ TODO: implement all canvas types, see TODO on kCanvasTypes.
`;
import { makeTestGroup } from '../../../common/framework/test_group.js';
-import { assert, raceWithRejectOnTimeout, unreachable } from '../../../common/util/util.js';
+import {
+ ErrorWithExtra,
+ assert,
+ raceWithRejectOnTimeout,
+ unreachable,
+} from '../../../common/util/util.js';
import {
kCanvasAlphaModes,
kCanvasColorSpaces,
@@ -27,7 +32,10 @@ import {
CanvasType,
createCanvas,
createOnscreenCanvas,
+ createOffscreenCanvas,
} from '../../util/create_elements.js';
+import { TexelView } from '../../util/texture/texel_view.js';
+import { findFailedPixels } from '../../util/texture/texture_ok.js';
export const g = makeTestGroup(GPUTest);
@@ -61,6 +69,26 @@ const expect = {
]),
};
+/**
+ * Given 4 pixels in rgba8unorm format, puts them into an ImageData
+ * of the specified color space and then puts them into an srgb color space
+ * canvas (the default). If the color space is different there will be a
+ * conversion. Returns the resulting 4 pixels in rgba8unorm format.
+ */
+function convertRGBA8UnormBytesToColorSpace(
+ expected: Uint8ClampedArray,
+ srcColorSpace: PredefinedColorSpace,
+ dstColorSpace: PredefinedColorSpace
+) {
+ const srcImgData = new ImageData(2, 2, { colorSpace: srcColorSpace });
+ srcImgData.data.set(expected);
+ const dstCanvas = new OffscreenCanvas(2, 2);
+ const dstCtx = dstCanvas.getContext('2d', { colorSpace: dstColorSpace });
+ assert(dstCtx !== null);
+ dstCtx.putImageData(srcImgData, 0, 0);
+ return dstCtx.getImageData(0, 0, 2, 2).data;
+}
+
function initWebGPUCanvasContent<T extends CanvasType>(
t: GPUTest,
format: GPUTextureFormat,
@@ -119,7 +147,7 @@ function drawImageSourceIntoCanvas(
image: CanvasImageSource,
colorSpace: PredefinedColorSpace
) {
- const canvas: HTMLCanvasElement = createOnscreenCanvas(t, 2, 2);
+ const canvas = createOffscreenCanvas(t, 2, 2);
const ctx = canvas.getContext('2d', { colorSpace });
assert(ctx !== null);
ctx.drawImage(image, 0, 0);
@@ -147,22 +175,13 @@ function checkImageResultWithDifferentColorSpaceCanvas(
// draw the WebGPU derived data into a canvas
const fromWebGPUCtx = drawImageSourceIntoCanvas(t, image, destinationColorSpace);
- // create a 2D canvas with the same source data in the same color space as the WebGPU
- // canvas
- const source2DCanvas: HTMLCanvasElement = createOnscreenCanvas(t, 2, 2);
- const source2DCtx = source2DCanvas.getContext('2d', { colorSpace: sourceColorSpace });
- assert(source2DCtx !== null);
- const imgData = source2DCtx.getImageData(0, 0, 2, 2);
- imgData.data.set(sourceData);
- source2DCtx.putImageData(imgData, 0, 0);
-
- // draw the source 2D canvas into another 2D canvas with the destination color space and
- // then pull out the data. This result should be the same as the WebGPU derived data
- // written to a 2D canvas of the same destination color space.
- const from2DCtx = drawImageSourceIntoCanvas(t, source2DCanvas, destinationColorSpace);
- const expect = from2DCtx.getImageData(0, 0, 2, 2).data;
-
- readPixelsFrom2DCanvasAndCompare(t, fromWebGPUCtx, expect);
+ const expect = convertRGBA8UnormBytesToColorSpace(
+ sourceData,
+ sourceColorSpace,
+ destinationColorSpace
+ );
+
+ readPixelsFrom2DCanvasAndCompare(t, fromWebGPUCtx, expect, 2);
}
function checkImageResult(
@@ -171,18 +190,55 @@ function checkImageResult(
sourceColorSpace: PredefinedColorSpace,
expect: Uint8ClampedArray
) {
+ // canvas(colorSpace)->img(colorSpace)->canvas(colorSpace).drawImage->canvas(colorSpace).getImageData->actual
+ // hard coded data->expected
checkImageResultWithSameColorSpaceCanvas(t, image, sourceColorSpace, expect);
+
+ // canvas(colorSpace)->img(colorSpace)->canvas(diffColorSpace).drawImage->canvas(diffColorSpace).getImageData->actual
+ // hard coded data->ImageData(colorSpace)->canvas(diffColorSpace).putImageData->canvas(diffColorSpace).getImageData->expected
checkImageResultWithDifferentColorSpaceCanvas(t, image, sourceColorSpace, expect);
}
function readPixelsFrom2DCanvasAndCompare(
t: GPUTest,
ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
- expect: Uint8ClampedArray
+ expect: Uint8ClampedArray,
+ maxDiffULPsForNormFormat = 0
) {
- const actual = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height).data;
+ const { width, height } = ctx.canvas;
+ const actual = ctx.getImageData(0, 0, width, height).data;
- t.expectOK(checkElementsEqual(actual, expect));
+ const subrectOrigin = [0, 0, 0];
+ const subrectSize = [width, height, 1];
+
+ const areaDesc = {
+ bytesPerRow: width * 4,
+ rowsPerImage: height,
+ subrectOrigin,
+ subrectSize,
+ };
+
+ const format = 'rgba8unorm';
+ const actTexelView = TexelView.fromTextureDataByReference(format, actual, areaDesc);
+ const expTexelView = TexelView.fromTextureDataByReference(format, expect, areaDesc);
+
+ const failedPixelsMessage = findFailedPixels(
+ format,
+ { x: 0, y: 0, z: 0 },
+ { width, height, depthOrArrayLayers: 1 },
+ { actTexelView, expTexelView },
+ { maxDiffULPsForNormFormat }
+ );
+
+ if (failedPixelsMessage !== undefined) {
+ const msg = 'Canvas had unexpected contents:\n' + failedPixelsMessage;
+ t.expectOK(
+ new ErrorWithExtra(msg, () => ({
+ expTexelView,
+ actTexelView,
+ }))
+ );
+ }
}
g.test('onscreenCanvas,snapshot')
@@ -265,7 +321,7 @@ g.test('offscreenCanvas,snapshot')
.combine('format', kCanvasTextureFormats)
.combine('alphaMode', kCanvasAlphaModes)
.combine('colorSpace', kCanvasColorSpaces)
- .combine('snapshotType', ['convertToBlob', 'transferToImageBitmap', 'imageBitmap'])
+ .combine('snapshotType', ['convertToBlob', 'transferToImageBitmap', 'imageBitmap'] as const)
)
.fn(async t => {
const offscreenCanvas = initWebGPUCanvasContent(
@@ -284,11 +340,7 @@ g.test('offscreenCanvas,snapshot')
return;
}
const blob = await offscreenCanvas.convertToBlob();
- const url = URL.createObjectURL(blob);
- const img = new Image(offscreenCanvas.width, offscreenCanvas.height);
- img.src = url;
- await raceWithRejectOnTimeout(img.decode(), 5000, 'load image timeout');
- snapshot = img;
+ snapshot = await createImageBitmap(blob);
break;
}
case 'transferToImageBitmap': {
@@ -383,6 +435,19 @@ g.test('drawTo2DCanvas')
- colorSpace = {"srgb", "display-p3"}
- WebGPU canvas type = {"onscreen", "offscreen"}
- 2d canvas type = {"onscreen", "offscreen"}
+
+
+ * makes a webgpu canvas with the given colorSpace and puts data in via copy convoluted
+ copy process
+ * makes a 2d canvas with 'srgb' colorSpace (the default)
+ * draws the webgpu canvas into the 2d canvas so if the color spaces do not match
+ there will be a conversion.
+ * gets the pixels from the 2d canvas via getImageData
+ * compares them to hard coded values that are converted to expected values by copying
+ to an ImageData of the given color space, and then using putImageData into an srgb canvas.
+
+ canvas(colorSpace) -> canvas(srgb).drawImage -> canvas(srgb).getImageData -> actual
+ ImageData(colorSpace) -> canvas(srgb).putImageData -> canvas(srgb).getImageData -> expected
`
)
.params(u =>
@@ -396,35 +461,47 @@ g.test('drawTo2DCanvas')
.fn(t => {
const { format, webgpuCanvasType, alphaMode, colorSpace, canvas2DType } = t.params;
- const canvas = initWebGPUCanvasContent(t, format, alphaMode, colorSpace, webgpuCanvasType);
+ const webgpuCanvas = initWebGPUCanvasContent(
+ t,
+ format,
+ alphaMode,
+ colorSpace,
+ webgpuCanvasType
+ );
- const expectCanvas = createCanvas(t, canvas2DType, canvas.width, canvas.height);
- const ctx = expectCanvas.getContext('2d') as CanvasRenderingContext2D;
+ const actualCanvas = createCanvas(t, canvas2DType, webgpuCanvas.width, webgpuCanvas.height);
+ const ctx = actualCanvas.getContext('2d') as CanvasRenderingContext2D;
if (ctx === null) {
t.skip(canvas2DType + ' canvas cannot get 2d context');
return;
}
- ctx.drawImage(canvas, 0, 0);
- readPixelsFrom2DCanvasAndCompare(t, ctx, expect[t.params.alphaMode]);
+ ctx.drawImage(webgpuCanvas, 0, 0);
+
+ readPixelsFrom2DCanvasAndCompare(
+ t,
+ ctx,
+ convertRGBA8UnormBytesToColorSpace(expect[t.params.alphaMode], colorSpace, 'srgb')
+ );
});
g.test('transferToImageBitmap_unconfigured_nonzero_size')
.desc(
`Regression test for a crash when calling transferImageBitmap on an unconfigured. Case where the canvas is not empty`
)
+ .params(u => u.combine('readbackCanvasType', ['onscreen', 'offscreen'] as const))
.fn(t => {
- const canvas = createCanvas(t, 'offscreen', 2, 3);
+ const kWidth = 2;
+ const kHeight = 3;
+ const canvas = createCanvas(t, 'offscreen', kWidth, kHeight);
canvas.getContext('webgpu');
// Transferring gives an ImageBitmap of the correct size filled with transparent black.
const ib = canvas.transferToImageBitmap();
- t.expect(ib.width === canvas.width);
- t.expect(ib.height === canvas.height);
+ t.expect(ib.width === kWidth);
+ t.expect(ib.height === kHeight);
- const readbackCanvas = document.createElement('canvas');
- readbackCanvas.width = canvas.width;
- readbackCanvas.height = canvas.height;
+ const readbackCanvas = createCanvas(t, t.params.readbackCanvasType, kWidth, kHeight);
const readbackContext = readbackCanvas.getContext('2d', {
alpha: true,
});
@@ -434,7 +511,7 @@ g.test('transferToImageBitmap_unconfigured_nonzero_size')
}
// Since there isn't a configuration we expect the ImageBitmap to have the default alphaMode of "opaque".
- const expected = new Uint8ClampedArray(canvas.width * canvas.height * 4);
+ const expected = new Uint8ClampedArray(kWidth * kHeight * 4);
for (let i = 0; i < expected.byteLength; i += 4) {
expected[i + 0] = 0;
expected[i + 1] = 0;
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/copyToTexture/canvas.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/copyToTexture/canvas.spec.ts
index 06c3cd30b2..dc80eff9de 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/copyToTexture/canvas.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/copyToTexture/canvas.spec.ts
@@ -97,9 +97,7 @@ class F extends CopyToTextureUtils {
}
const imageData = new ImageData(imagePixels, width, height, { colorSpace });
- // MAINTENANCE_TODO: Remove as any when tsc support imageData.colorSpace
- /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
- if (typeof (imageData as any).colorSpace === 'undefined') {
+ if (typeof imageData.colorSpace === 'undefined') {
this.skip('color space attr is not supported for ImageData');
}
@@ -762,7 +760,7 @@ g.test('color_space_conversion')
.params(u =>
u
.combine('srcColorSpace', ['srgb', 'display-p3'] as const)
- .combine('dstColorSpace', ['srgb'] as const)
+ .combine('dstColorSpace', ['srgb', 'display-p3'] as const)
.combine('dstColorFormat', kValidTextureFormatsForCopyE2T)
.combine('dstPremultiplied', [true, false])
.combine('srcDoFlipYDuringCopy', [true, false])
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/copyToTexture/video.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/copyToTexture/video.spec.ts
index 1888eb7e58..3f73a41c84 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/copyToTexture/video.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/copyToTexture/video.spec.ts
@@ -1,8 +1,10 @@
export const description = `
copyToTexture with HTMLVideoElement and VideoFrame.
-- videos with various encodings/formats (webm vp8, webm vp9, ogg theora, mp4), color spaces
- (bt.601, bt.709, bt.2020)
+- videos with various encodings/formats (webm vp8, webm vp9, ogg theora, mp4), video color spaces
+ (bt.601, bt.709, bt.2020) and dst color spaces(display-p3, srgb).
+
+ TODO: Test video in BT.2020 color space
`;
import { makeTestGroup } from '../../../common/framework/test_group.js';
@@ -11,7 +13,11 @@ import {
startPlayingAndWaitForVideo,
getVideoElement,
getVideoFrameFromVideoElement,
- kVideoExpectations,
+ convertToUnorm8,
+ kPredefinedColorSpace,
+ kVideoNames,
+ kVideoInfo,
+ kVideoExpectedColors,
} from '../../web_platform/util.js';
const kFormat = 'rgba8unorm';
@@ -36,17 +42,17 @@ It creates HTMLVideoElement with videos under Resource folder.
- Valid 'flipY' config in 'GPUImageCopyExternalImage' (named 'srcDoFlipYDuringCopy' in cases)
- TODO: partial copy tests should be added
- TODO: all valid dstColorFormat tests should be added.
- - TODO: dst color space tests need to be added
`
)
.params(u =>
u //
- .combineWithParams(kVideoExpectations)
+ .combine('videoName', kVideoNames)
.combine('sourceType', ['VideoElement', 'VideoFrame'] as const)
.combine('srcDoFlipYDuringCopy', [true, false])
+ .combine('dstColorSpace', kPredefinedColorSpace)
)
.fn(async t => {
- const { videoName, sourceType, srcDoFlipYDuringCopy } = t.params;
+ const { videoName, sourceType, srcDoFlipYDuringCopy, dstColorSpace } = t.params;
if (sourceType === 'VideoFrame' && typeof VideoFrame === 'undefined') {
t.skip('WebCodec is not supported');
@@ -58,8 +64,8 @@ It creates HTMLVideoElement with videos under Resource folder.
let source, width, height;
if (sourceType === 'VideoFrame') {
source = await getVideoFrameFromVideoElement(t, videoElement);
- width = source.codedWidth;
- height = source.codedHeight;
+ width = source.displayWidth;
+ height = source.displayHeight;
} else {
source = videoElement;
width = source.videoWidth;
@@ -82,33 +88,63 @@ It creates HTMLVideoElement with videos under Resource folder.
{
texture: dstTexture,
origin: { x: 0, y: 0 },
- colorSpace: 'srgb',
+ colorSpace: dstColorSpace,
premultipliedAlpha: true,
},
{ width, height, depthOrArrayLayers: 1 }
);
+ const srcColorSpace = kVideoInfo[videoName].colorSpace;
+ const presentColors = kVideoExpectedColors[srcColorSpace][dstColorSpace];
+
+ // visible rect is whole frame, no clipping.
+ const expect = kVideoInfo[videoName].display;
+
if (srcDoFlipYDuringCopy) {
t.expectSinglePixelComparisonsAreOkInTexture({ texture: dstTexture }, [
- // Top-left should be blue.
- { coord: { x: width * 0.25, y: height * 0.25 }, exp: t.params._blueExpectation },
- // Top-right should be green.
- { coord: { x: width * 0.75, y: height * 0.25 }, exp: t.params._greenExpectation },
- // Bottom-left should be yellow.
- { coord: { x: width * 0.25, y: height * 0.75 }, exp: t.params._yellowExpectation },
- // Bottom-right should be red.
- { coord: { x: width * 0.75, y: height * 0.75 }, exp: t.params._redExpectation },
+ // Flipped top-left.
+ {
+ coord: { x: width * 0.25, y: height * 0.25 },
+ exp: convertToUnorm8(presentColors[expect.bottomLeftColor]),
+ },
+ // Flipped top-right.
+ {
+ coord: { x: width * 0.75, y: height * 0.25 },
+ exp: convertToUnorm8(presentColors[expect.bottomRightColor]),
+ },
+ // Flipped bottom-left.
+ {
+ coord: { x: width * 0.25, y: height * 0.75 },
+ exp: convertToUnorm8(presentColors[expect.topLeftColor]),
+ },
+ // Flipped bottom-right.
+ {
+ coord: { x: width * 0.75, y: height * 0.75 },
+ exp: convertToUnorm8(presentColors[expect.topRightColor]),
+ },
]);
} else {
t.expectSinglePixelComparisonsAreOkInTexture({ texture: dstTexture }, [
- // Top-left should be yellow.
- { coord: { x: width * 0.25, y: height * 0.25 }, exp: t.params._yellowExpectation },
- // Top-right should be red.
- { coord: { x: width * 0.75, y: height * 0.25 }, exp: t.params._redExpectation },
- // Bottom-left should be blue.
- { coord: { x: width * 0.25, y: height * 0.75 }, exp: t.params._blueExpectation },
- // Bottom-right should be green.
- { coord: { x: width * 0.75, y: height * 0.75 }, exp: t.params._greenExpectation },
+ // Top-left.
+ {
+ coord: { x: width * 0.25, y: height * 0.25 },
+ exp: convertToUnorm8(presentColors[expect.topLeftColor]),
+ },
+ // Top-right.
+ {
+ coord: { x: width * 0.75, y: height * 0.25 },
+ exp: convertToUnorm8(presentColors[expect.topRightColor]),
+ },
+ // Bottom-left.
+ {
+ coord: { x: width * 0.25, y: height * 0.75 },
+ exp: convertToUnorm8(presentColors[expect.bottomLeftColor]),
+ },
+ // Bottom-right.
+ {
+ coord: { x: width * 0.75, y: height * 0.75 },
+ exp: convertToUnorm8(presentColors[expect.bottomRightColor]),
+ },
]);
}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/external_texture/video.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/external_texture/video.spec.ts
index baa2a985d2..8e812ccd2a 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/external_texture/video.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/external_texture/video.spec.ts
@@ -1,20 +1,25 @@
export const description = `
Tests for external textures from HTMLVideoElement (and other video-type sources?).
-- videos with various encodings/formats (webm vp8, webm vp9, ogg theora, mp4), color spaces
- (bt.601, bt.709, bt.2020)
+- videos with various encodings/formats (webm vp8, webm vp9, ogg theora, mp4), video color spaces
+ (bt.601, bt.709, bt.2020) and dst color spaces(display-p3, srgb)
TODO: consider whether external_texture and copyToTexture video tests should be in the same file
+TODO(#3193): Test video in BT.2020 color space
`;
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { GPUTest, TextureTestMixin } from '../../gpu_test.js';
+import { createCanvas } from '../../util/create_elements.js';
import {
startPlayingAndWaitForVideo,
getVideoFrameFromVideoElement,
getVideoElement,
- kVideoExpectations,
- kVideoRotationExpectations,
+ convertToUnorm8,
+ kPredefinedColorSpace,
+ kVideoNames,
+ kVideoInfo,
+ kVideoExpectedColors,
} from '../../web_platform/util.js';
const kHeight = 16;
@@ -23,7 +28,10 @@ const kFormat = 'rgba8unorm';
export const g = makeTestGroup(TextureTestMixin(GPUTest));
-function createExternalTextureSamplingTestPipeline(t: GPUTest): GPURenderPipeline {
+function createExternalTextureSamplingTestPipeline(
+ t: GPUTest,
+ colorAttachmentFormat: GPUTextureFormat = kFormat
+): GPURenderPipeline {
const pipeline = t.device.createRenderPipeline({
layout: 'auto',
vertex: {
@@ -59,7 +67,7 @@ function createExternalTextureSamplingTestPipeline(t: GPUTest): GPURenderPipelin
entryPoint: 'main',
targets: [
{
- format: kFormat,
+ format: colorAttachmentFormat,
},
],
},
@@ -73,13 +81,14 @@ function createExternalTextureSamplingTestBindGroup(
t: GPUTest,
checkNonStandardIsZeroCopy: true | undefined,
source: HTMLVideoElement | VideoFrame,
- pipeline: GPURenderPipeline
+ pipeline: GPURenderPipeline,
+ dstColorSpace: PredefinedColorSpace
): GPUBindGroup {
const linearSampler = t.device.createSampler();
const externalTexture = t.device.importExternalTexture({
- /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
- source: source as any,
+ source,
+ colorSpace: dstColorSpace,
});
if (checkNonStandardIsZeroCopy) {
@@ -133,22 +142,24 @@ g.test('importExternalTexture,sample')
.desc(
`
Tests that we can import an HTMLVideoElement/VideoFrame into a GPUExternalTexture, sample from it
-for several combinations of video format and color space.
+for several combinations of video format, video color spaces and dst color spaces.
`
)
.params(u =>
u //
.combineWithParams(checkNonStandardIsZeroCopyIfAvailable())
+ .combine('videoName', kVideoNames)
.combine('sourceType', ['VideoElement', 'VideoFrame'] as const)
- .combineWithParams(kVideoExpectations)
+ .combine('dstColorSpace', kPredefinedColorSpace)
)
.fn(async t => {
- const sourceType = t.params.sourceType;
+ const { videoName, sourceType, dstColorSpace } = t.params;
+
if (sourceType === 'VideoFrame' && typeof VideoFrame === 'undefined') {
t.skip('WebCodec is not supported');
}
- const videoElement = getVideoElement(t, t.params.videoName);
+ const videoElement = getVideoElement(t, videoName);
await startPlayingAndWaitForVideo(videoElement, async () => {
const source =
@@ -167,7 +178,8 @@ for several combinations of video format and color space.
t,
t.params.checkNonStandardIsZeroCopy,
source,
- pipeline
+ pipeline,
+ dstColorSpace
);
const commandEncoder = t.device.createCommandEncoder();
@@ -187,88 +199,162 @@ for several combinations of video format and color space.
passEncoder.end();
t.device.queue.submit([commandEncoder.finish()]);
+ const srcColorSpace = kVideoInfo[videoName].colorSpace;
+ const presentColors = kVideoExpectedColors[srcColorSpace][dstColorSpace];
+
+ // visible rect is whole frame, no clipping.
+ const expect = kVideoInfo[videoName].display;
+
// For validation, we sample a few pixels away from the edges to avoid compression
// artifacts.
t.expectSinglePixelComparisonsAreOkInTexture({ texture: colorAttachment }, [
- // Top-left should be yellow.
- { coord: { x: kWidth * 0.25, y: kHeight * 0.25 }, exp: t.params._yellowExpectation },
- // Top-right should be red.
- { coord: { x: kWidth * 0.75, y: kHeight * 0.25 }, exp: t.params._redExpectation },
- // Bottom-left should be blue.
- { coord: { x: kWidth * 0.25, y: kHeight * 0.75 }, exp: t.params._blueExpectation },
- // Bottom-right should be green.
- { coord: { x: kWidth * 0.75, y: kHeight * 0.75 }, exp: t.params._greenExpectation },
+ // Top-left.
+ {
+ coord: { x: kWidth * 0.25, y: kHeight * 0.25 },
+ exp: convertToUnorm8(presentColors[expect.topLeftColor]),
+ },
+ // Top-right.
+ {
+ coord: { x: kWidth * 0.75, y: kHeight * 0.25 },
+ exp: convertToUnorm8(presentColors[expect.topRightColor]),
+ },
+ // Bottom-left.
+ {
+ coord: { x: kWidth * 0.25, y: kHeight * 0.75 },
+ exp: convertToUnorm8(presentColors[expect.bottomLeftColor]),
+ },
+ // Bottom-right.
+ {
+ coord: { x: kWidth * 0.75, y: kHeight * 0.75 },
+ exp: convertToUnorm8(presentColors[expect.bottomRightColor]),
+ },
]);
-
- if (sourceType === 'VideoFrame') (source as VideoFrame).close();
});
});
-g.test('importExternalTexture,sampleWithRotationMetadata')
+g.test('importExternalTexture,sample_non_YUV_video_frame')
.desc(
`
-Tests that when importing an HTMLVideoElement/VideoFrame into a GPUExternalTexture, sampling from
-it will honor rotation metadata.
+Tests that we can import an VideoFrame with non-YUV pixel format into a GPUExternalTexture and sample it.
`
)
.params(u =>
u //
- .combineWithParams(checkNonStandardIsZeroCopyIfAvailable())
- .combine('sourceType', ['VideoElement', 'VideoFrame'] as const)
- .combineWithParams(kVideoRotationExpectations)
+ .combine('videoFrameFormat', ['RGBA', 'RGBX', 'BGRA', 'BGRX'] as const)
)
- .fn(async t => {
- const sourceType = t.params.sourceType;
- const videoElement = getVideoElement(t, t.params.videoName);
+ .fn(t => {
+ const { videoFrameFormat } = t.params;
- await startPlayingAndWaitForVideo(videoElement, async () => {
- const source =
- sourceType === 'VideoFrame'
- ? await getVideoFrameFromVideoElement(t, videoElement)
- : videoElement;
+ if (typeof VideoFrame === 'undefined') {
+ t.skip('WebCodec is not supported');
+ }
- const colorAttachment = t.device.createTexture({
- format: kFormat,
- size: { width: kWidth, height: kHeight, depthOrArrayLayers: 1 },
- usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
- });
+ const canvas = createCanvas(t, 'onscreen', kWidth, kHeight);
- const pipeline = createExternalTextureSamplingTestPipeline(t);
- const bindGroup = createExternalTextureSamplingTestBindGroup(
- t,
- t.params.checkNonStandardIsZeroCopy,
- source,
- pipeline
- );
+ const canvasContext = canvas.getContext('2d');
- const commandEncoder = t.device.createCommandEncoder();
- const passEncoder = commandEncoder.beginRenderPass({
- colorAttachments: [
- {
- view: colorAttachment.createView(),
- clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },
- loadOp: 'clear',
- storeOp: 'store',
- },
- ],
- });
- passEncoder.setPipeline(pipeline);
- passEncoder.setBindGroup(0, bindGroup);
- passEncoder.draw(6);
- passEncoder.end();
- t.device.queue.submit([commandEncoder.finish()]);
+ if (canvasContext === null) {
+ t.skip(' onscreen canvas 2d context not available');
+ }
- // For validation, we sample a few pixels away from the edges to avoid compression
- // artifacts.
- t.expectSinglePixelComparisonsAreOkInTexture({ texture: colorAttachment }, [
- { coord: { x: kWidth * 0.25, y: kHeight * 0.25 }, exp: t.params._topLeftExpectation },
- { coord: { x: kWidth * 0.75, y: kHeight * 0.25 }, exp: t.params._topRightExpectation },
- { coord: { x: kWidth * 0.25, y: kHeight * 0.75 }, exp: t.params._bottomLeftExpectation },
- { coord: { x: kWidth * 0.75, y: kHeight * 0.75 }, exp: t.params._bottomRightExpectation },
- ]);
+ const ctx = canvasContext as CanvasRenderingContext2D;
+
+ const rectWidth = Math.floor(kWidth / 2);
+ const rectHeight = Math.floor(kHeight / 2);
+
+ // Red
+ ctx.fillStyle = `rgba(255, 0, 0, 1.0)`;
+ ctx.fillRect(0, 0, rectWidth, rectHeight);
+ // Lime
+ ctx.fillStyle = `rgba(0, 255, 0, 1.0)`;
+ ctx.fillRect(rectWidth, 0, kWidth - rectWidth, rectHeight);
+ // Blue
+ ctx.fillStyle = `rgba(0, 0, 255, 1.0)`;
+ ctx.fillRect(0, rectHeight, rectWidth, kHeight - rectHeight);
+ // Fuchsia
+ ctx.fillStyle = `rgba(255, 0, 255, 1.0)`;
+ ctx.fillRect(rectWidth, rectHeight, kWidth - rectWidth, kHeight - rectHeight);
+
+ const imageData = ctx.getImageData(0, 0, kWidth, kHeight);
+
+ // Create video frame with default color space 'srgb'
+ const frameInit: VideoFrameBufferInit = {
+ format: videoFrameFormat,
+ codedWidth: kWidth,
+ codedHeight: kHeight,
+ timestamp: 0,
+ };
+
+ const frame = new VideoFrame(imageData.data.buffer, frameInit);
+ let textureFormat: GPUTextureFormat = 'rgba8unorm';
+
+ if (videoFrameFormat === 'BGRA' || videoFrameFormat === 'BGRX') {
+ textureFormat = 'bgra8unorm';
+ }
- if (sourceType === 'VideoFrame') (source as VideoFrame).close();
+ const colorAttachment = t.device.createTexture({
+ format: textureFormat,
+ size: { width: kWidth, height: kHeight, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
+ });
+
+ const pipeline = createExternalTextureSamplingTestPipeline(t, textureFormat);
+ const bindGroup = createExternalTextureSamplingTestBindGroup(
+ t,
+ undefined /* checkNonStandardIsZeroCopy */,
+ frame,
+ pipeline,
+ 'srgb'
+ );
+
+ const commandEncoder = t.device.createCommandEncoder();
+ const passEncoder = commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: colorAttachment.createView(),
+ clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store',
+ },
+ ],
});
+ passEncoder.setPipeline(pipeline);
+ passEncoder.setBindGroup(0, bindGroup);
+ passEncoder.draw(6);
+ passEncoder.end();
+ t.device.queue.submit([commandEncoder.finish()]);
+
+ const expected = {
+ topLeft: new Uint8Array([255, 0, 0, 255]),
+ topRight: new Uint8Array([0, 255, 0, 255]),
+ bottomLeft: new Uint8Array([0, 0, 255, 255]),
+ bottomRight: new Uint8Array([255, 0, 255, 255]),
+ };
+
+ // For validation, we sample a few pixels away from the edges to avoid compression
+ // artifacts.
+ t.expectSinglePixelComparisonsAreOkInTexture({ texture: colorAttachment }, [
+ // Top-left.
+ {
+ coord: { x: kWidth * 0.25, y: kHeight * 0.25 },
+ exp: expected.topLeft,
+ },
+ // Top-right.
+ {
+ coord: { x: kWidth * 0.75, y: kHeight * 0.25 },
+ exp: expected.topRight,
+ },
+ // Bottom-left.
+ {
+ coord: { x: kWidth * 0.25, y: kHeight * 0.75 },
+ exp: expected.bottomLeft,
+ },
+ // Bottom-right.
+ {
+ coord: { x: kWidth * 0.75, y: kHeight * 0.75 },
+ exp: expected.bottomRight,
+ },
+ ]);
});
g.test('importExternalTexture,sampleWithVideoFrameWithVisibleRectParam')
@@ -281,10 +367,13 @@ parameters are present.
.params(u =>
u //
.combineWithParams(checkNonStandardIsZeroCopyIfAvailable())
- .combineWithParams(kVideoExpectations)
+ .combine('videoName', kVideoNames)
+ .combine('dstColorSpace', kPredefinedColorSpace)
)
.fn(async t => {
- const videoElement = getVideoElement(t, t.params.videoName);
+ const { videoName, dstColorSpace } = t.params;
+
+ const videoElement = getVideoElement(t, videoName);
await startPlayingAndWaitForVideo(videoElement, async () => {
const source = await getVideoFrameFromVideoElement(t, videoElement);
@@ -292,15 +381,24 @@ parameters are present.
// All tested videos are derived from an image showing yellow, red, blue or green in each
// quadrant. In this test we crop the video to each quadrant and check that desired color
// is sampled from each corner of the cropped image.
- const srcVideoHeight = 240;
- const srcVideoWidth = 320;
+ // visible rect clip applies on raw decoded frame, which defines based on video frame coded size.
+ const srcVideoHeight = source.codedHeight;
+ const srcVideoWidth = source.codedWidth;
+
+ const srcColorSpace = kVideoInfo[videoName].colorSpace;
+ const presentColors = kVideoExpectedColors[srcColorSpace][dstColorSpace];
+
+ // The test crops raw decoded videos first and then apply transform. Expectation should
+ // use coded colors as reference.
+ const expect = kVideoInfo[videoName].coded;
+
const cropParams = [
- // Top left (yellow)
+ // Top left
{
subRect: { x: 0, y: 0, width: srcVideoWidth / 2, height: srcVideoHeight / 2 },
- color: t.params._yellowExpectation,
+ color: convertToUnorm8(presentColors[expect.topLeftColor]),
},
- // Top right (red)
+ // Top right
{
subRect: {
x: srcVideoWidth / 2,
@@ -308,9 +406,9 @@ parameters are present.
width: srcVideoWidth / 2,
height: srcVideoHeight / 2,
},
- color: t.params._redExpectation,
+ color: convertToUnorm8(presentColors[expect.topRightColor]),
},
- // Bottom left (blue)
+ // Bottom left
{
subRect: {
x: 0,
@@ -318,9 +416,9 @@ parameters are present.
width: srcVideoWidth / 2,
height: srcVideoHeight / 2,
},
- color: t.params._blueExpectation,
+ color: convertToUnorm8(presentColors[expect.bottomLeftColor]),
},
- // Bottom right (green)
+ // Bottom right
{
subRect: {
x: srcVideoWidth / 2,
@@ -328,14 +426,12 @@ parameters are present.
width: srcVideoWidth / 2,
height: srcVideoHeight / 2,
},
- color: t.params._greenExpectation,
+ color: convertToUnorm8(presentColors[expect.bottomRightColor]),
},
];
for (const cropParam of cropParams) {
- // MAINTENANCE_TODO: remove cast with TypeScript 4.9.6+.
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- const subRect = new VideoFrame(source as any, { visibleRect: cropParam.subRect });
+ const subRect = new VideoFrame(source, { visibleRect: cropParam.subRect });
const colorAttachment = t.device.createTexture({
format: kFormat,
@@ -348,7 +444,8 @@ parameters are present.
t,
t.params.checkNonStandardIsZeroCopy,
subRect,
- pipeline
+ pipeline,
+ dstColorSpace
);
const commandEncoder = t.device.createCommandEncoder();
@@ -387,22 +484,24 @@ g.test('importExternalTexture,compute')
.desc(
`
Tests that we can import an HTMLVideoElement/VideoFrame into a GPUExternalTexture and use it in a
-compute shader, for several combinations of video format and color space.
+compute shader, for several combinations of video format, video color spaces and dst color spaces.
`
)
.params(u =>
u //
.combineWithParams(checkNonStandardIsZeroCopyIfAvailable())
+ .combine('videoName', kVideoNames)
.combine('sourceType', ['VideoElement', 'VideoFrame'] as const)
- .combineWithParams(kVideoExpectations)
+ .combine('dstColorSpace', kPredefinedColorSpace)
)
.fn(async t => {
- const sourceType = t.params.sourceType;
+ const { videoName, sourceType, dstColorSpace } = t.params;
+
if (sourceType === 'VideoFrame' && typeof VideoFrame === 'undefined') {
t.skip('WebCodec is not supported');
}
- const videoElement = getVideoElement(t, t.params.videoName);
+ const videoElement = getVideoElement(t, videoName);
await startPlayingAndWaitForVideo(videoElement, async () => {
const source =
@@ -410,8 +509,8 @@ compute shader, for several combinations of video format and color space.
? await getVideoFrameFromVideoElement(t, videoElement)
: videoElement;
const externalTexture = t.device.importExternalTexture({
- /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
- source: source as any,
+ source,
+ colorSpace: dstColorSpace,
});
if (t.params.checkNonStandardIsZeroCopy) {
expectZeroCopyNonStandard(t, externalTexture);
@@ -422,29 +521,51 @@ compute shader, for several combinations of video format and color space.
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.STORAGE_BINDING,
});
+ // Use display size of VideoFrame and video size of HTMLVideoElement as frame size. These sizes are presenting size which
+ // apply transformation in video metadata if any.
+
const pipeline = t.device.createComputePipeline({
layout: 'auto',
compute: {
- // Shader loads 4 pixels near each corner, and then store them in a storage texture.
+ // Shader loads 4 pixels, and then store them in a storage texture.
module: t.device.createShaderModule({
code: `
+ override frameWidth : i32 = 0;
+ override frameHeight : i32 = 0;
@group(0) @binding(0) var t : texture_external;
@group(0) @binding(1) var outImage : texture_storage_2d<rgba8unorm, write>;
@compute @workgroup_size(1) fn main() {
- var yellow : vec4<f32> = textureLoad(t, vec2<i32>(80, 60));
+ let coordTopLeft = vec2<i32>(frameWidth / 4, frameHeight / 4);
+ let coordTopRight = vec2<i32>(frameWidth / 4 * 3, frameHeight / 4);
+ let coordBottomLeft = vec2<i32>(frameWidth / 4, frameHeight / 4 * 3);
+ let coordBottomRight = vec2<i32>(frameWidth / 4 * 3, frameHeight / 4 * 3);
+ var yellow : vec4<f32> = textureLoad(t, coordTopLeft);
textureStore(outImage, vec2<i32>(0, 0), yellow);
- var red : vec4<f32> = textureLoad(t, vec2<i32>(240, 60));
+ var red : vec4<f32> = textureLoad(t, coordTopRight);
textureStore(outImage, vec2<i32>(0, 1), red);
- var blue : vec4<f32> = textureLoad(t, vec2<i32>(80, 180));
+ var blue : vec4<f32> = textureLoad(t, coordBottomLeft);
textureStore(outImage, vec2<i32>(1, 0), blue);
- var green : vec4<f32> = textureLoad(t, vec2<i32>(240, 180));
+ var green : vec4<f32> = textureLoad(t, coordBottomRight);
textureStore(outImage, vec2<i32>(1, 1), green);
return;
}
`,
}),
entryPoint: 'main',
+
+ // Use display size of VideoFrame and video size of HTMLVideoElement as frame size. These sizes are presenting size which
+ // apply transformation in video metadata if any.
+ constants: {
+ frameWidth:
+ sourceType === 'VideoFrame'
+ ? (source as VideoFrame).displayWidth
+ : (source as HTMLVideoElement).videoWidth,
+ frameHeight:
+ sourceType === 'VideoFrame'
+ ? (source as VideoFrame).displayHeight
+ : (source as HTMLVideoElement).videoHeight,
+ },
},
});
@@ -464,17 +585,21 @@ compute shader, for several combinations of video format and color space.
pass.end();
t.device.queue.submit([encoder.finish()]);
+ const srcColorSpace = kVideoInfo[videoName].colorSpace;
+ const presentColors = kVideoExpectedColors[srcColorSpace][dstColorSpace];
+
+ // visible rect is whole frame, no clipping.
+ const expect = kVideoInfo[videoName].display;
+
t.expectSinglePixelComparisonsAreOkInTexture({ texture: outputTexture }, [
- // Top-left should be yellow.
- { coord: { x: 0, y: 0 }, exp: t.params._yellowExpectation },
- // Top-right should be red.
- { coord: { x: 0, y: 1 }, exp: t.params._redExpectation },
- // Bottom-left should be blue.
- { coord: { x: 1, y: 0 }, exp: t.params._blueExpectation },
- // Bottom-right should be green.
- { coord: { x: 1, y: 1 }, exp: t.params._greenExpectation },
+ // Top-left.
+ { coord: { x: 0, y: 0 }, exp: convertToUnorm8(presentColors[expect.topLeftColor]) },
+ // Top-right.
+ { coord: { x: 0, y: 1 }, exp: convertToUnorm8(presentColors[expect.topRightColor]) },
+ // Bottom-left.
+ { coord: { x: 1, y: 0 }, exp: convertToUnorm8(presentColors[expect.bottomLeftColor]) },
+ // Bottom-right.
+ { coord: { x: 1, y: 1 }, exp: convertToUnorm8(presentColors[expect.bottomRightColor]) },
]);
-
- if (sourceType === 'VideoFrame') (source as VideoFrame).close();
});
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_colorspace_bgra8unorm.https.html b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_colorspace_bgra8unorm.https.html
index c910c97b1d..a0068dbf7a 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_colorspace_bgra8unorm.https.html
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_colorspace_bgra8unorm.https.html
@@ -14,6 +14,7 @@
<link rel="help" href="https://gpuweb.github.io/gpuweb/" />
<meta name="assert" content="WebGPU bgra8norm canvas with colorSpace set should be rendered correctly" />
<link rel="match" href="./ref/canvas_colorspace-ref.html" />
+ <meta name=fuzzy content="maxDifference=0-2;totalPixels=0-9999999">
<script type="module">
import { runColorSpaceTest } from './canvas_colorspace.html.js';
runColorSpaceTest('bgra8unorm');
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_colorspace_rgba16float.https.html b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_colorspace_rgba16float.https.html
index 7f57858e49..b38fef9591 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_colorspace_rgba16float.https.html
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_colorspace_rgba16float.https.html
@@ -14,7 +14,7 @@
<link rel="help" href="https://gpuweb.github.io/gpuweb/" />
<meta name="assert" content="WebGPU rgba16float canvas with colorSpace set should be rendered correctly" />
<link rel="match" href="./ref/canvas_colorspace-ref.html" />
- <meta name=fuzzy content="maxDifference=1;totalPixels=8192">
+ <meta name=fuzzy content="maxDifference=0-2;totalPixels=0-9999999">
<script type="module">
import { runColorSpaceTest } from './canvas_colorspace.html.js';
runColorSpaceTest('rgba16float');
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_colorspace_rgba8unorm.https.html b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_colorspace_rgba8unorm.https.html
index e57e04ef5c..404aed360c 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_colorspace_rgba8unorm.https.html
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_colorspace_rgba8unorm.https.html
@@ -14,6 +14,7 @@
<link rel="help" href="https://gpuweb.github.io/gpuweb/" />
<meta name="assert" content="WebGPU rgba8unorm canvas with colorSpace set should be rendered correctly" />
<link rel="match" href="./ref/canvas_colorspace-ref.html" />
+ <meta name=fuzzy content="maxDifference=0-2;totalPixels=0-9999999">
<script type="module">
import { runColorSpaceTest } from './canvas_colorspace.html.js';
runColorSpaceTest('rgba8unorm');
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied_copy.https.html b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied_copy.https.html
index 70920dc0e6..5c3b888fee 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied_copy.https.html
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied_copy.https.html
@@ -8,7 +8,7 @@
content="WebGPU canvas should have correct orientation, components, scaling, filtering, color space"
/>
<link rel="match" href="./ref/canvas_composite_alpha_premultiplied-ref.html" />
- <meta name=fuzzy content="maxDifference=0-2;totalPixels=0-400">
+ <meta name=fuzzy content="maxDifference=0-2;totalPixels=0-9999999">
<style>
body { background-color: #F0E68C; }
#c-canvas { background-color: #8CF0E6; }
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied_draw.https.html b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied_draw.https.html
index d12751fac2..81335296c0 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied_draw.https.html
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied_draw.https.html
@@ -8,7 +8,7 @@
content="WebGPU canvas should have correct orientation, components, scaling, filtering, color space"
/>
<link rel="match" href="./ref/canvas_composite_alpha_premultiplied-ref.html" />
- <meta name=fuzzy content="maxDifference=0-2;totalPixels=0-400">
+ <meta name=fuzzy content="maxDifference=0-2;totalPixels=0-9999999">
<style>
body { background-color: #F0E68C; }
#c-canvas { background-color: #8CF0E6; }
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba16float_premultiplied_copy.https.html b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba16float_premultiplied_copy.https.html
index ed722013c1..28e2553fed 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba16float_premultiplied_copy.https.html
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba16float_premultiplied_copy.https.html
@@ -8,7 +8,7 @@
content="WebGPU canvas should have correct orientation, components, scaling, filtering, color space"
/>
<link rel="match" href="./ref/canvas_composite_alpha_premultiplied-ref.html" />
- <meta name=fuzzy content="maxDifference=0-2;totalPixels=0-400">
+ <meta name=fuzzy content="maxDifference=0-2;totalPixels=0-9999999">
<style>
body { background-color: #F0E68C; }
#c-canvas { background-color: #8CF0E6; }
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba16float_premultiplied_draw.https.html b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba16float_premultiplied_draw.https.html
index 8a028b168e..ca76fac7cd 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba16float_premultiplied_draw.https.html
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba16float_premultiplied_draw.https.html
@@ -8,7 +8,7 @@
content="WebGPU canvas should have correct orientation, components, scaling, filtering, color space"
/>
<link rel="match" href="./ref/canvas_composite_alpha_premultiplied-ref.html" />
- <meta name=fuzzy content="maxDifference=0-2;totalPixels=0-400">
+ <meta name=fuzzy content="maxDifference=0-2;totalPixels=0-9999999">
<style>
body { background-color: #F0E68C; }
#c-canvas { background-color: #8CF0E6; }
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_premultiplied_copy.https.html b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_premultiplied_copy.https.html
index fa938aba41..7936e0b81c 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_premultiplied_copy.https.html
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_premultiplied_copy.https.html
@@ -8,7 +8,7 @@
content="WebGPU canvas should have correct orientation, components, scaling, filtering, color space"
/>
<link rel="match" href="./ref/canvas_composite_alpha_premultiplied-ref.html" />
- <meta name=fuzzy content="maxDifference=0-2;totalPixels=0-400">
+ <meta name=fuzzy content="maxDifference=0-2;totalPixels=0-9999999">
<style>
body { background-color: #F0E68C; }
#c-canvas { background-color: #8CF0E6; }
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_premultiplied_draw.https.html b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_premultiplied_draw.https.html
index b62e71054c..da48abd2be 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_premultiplied_draw.https.html
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_premultiplied_draw.https.html
@@ -8,7 +8,7 @@
content="WebGPU canvas should have correct orientation, components, scaling, filtering, color space"
/>
<link rel="match" href="./ref/canvas_composite_alpha_premultiplied-ref.html" />
- <meta name=fuzzy content="maxDifference=0-2;totalPixels=0-400">
+ <meta name=fuzzy content="maxDifference=0-2;totalPixels=0-9999999">
<style>
body { background-color: #F0E68C; }
#c-canvas { background-color: #8CF0E6; }
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_image_rendering.https.html b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_image_rendering.https.html
index f51145645b..6a64b3da5d 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_image_rendering.https.html
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_image_rendering.https.html
@@ -5,11 +5,11 @@
<link rel="help" href="https://gpuweb.github.io/gpuweb/" />
<meta name="assert" content="WebGPU canvas with image-rendering set should be rendered correctly" />
<link rel="match" href="./ref/canvas_image_rendering-ref.html" />
- <canvas id="elem1" width="64" height="64" style="width: 99px; height: 99px;"></canvas>
- <canvas id="elem2" width="64" height="64" style="width: 99px; height: 99px; image-rendering: pixelated;"></canvas>
- <canvas id="elem3" width="64" height="64" style="width: 99px; height: 99px; image-rendering: crisp-edges"></canvas>
- <canvas id="elem4" width="64" height="64" style="width: 99px; height: 99px;"></canvas>
- <canvas id="elem5" width="64" height="64" style="width: 99px; height: 99px; image-rendering: pixelated;"></canvas>
- <canvas id="elem6" width="64" height="64" style="width: 99px; height: 99px; image-rendering: crisp-edges"></canvas>
+ <canvas id="elem1" width="64" height="64" style="width: 128px; height: 128px;"></canvas>
+ <canvas id="elem2" width="64" height="64" style="width: 128px; height: 128px; image-rendering: pixelated;"></canvas>
+ <canvas id="elem3" width="64" height="64" style="width: 128px; height: 128px; image-rendering: crisp-edges"></canvas>
+ <canvas id="elem4" width="64" height="64" style="width: 128px; height: 128px;"></canvas>
+ <canvas id="elem5" width="64" height="64" style="width: 128px; height: 128px; image-rendering: pixelated;"></canvas>
+ <canvas id="elem6" width="64" height="64" style="width: 128px; height: 128px; image-rendering: crisp-edges"></canvas>
<script type="module" src="canvas_image_rendering.html.js"></script>
</html>
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/delay_get_texture.html.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/delay_get_texture.html.ts
new file mode 100644
index 0000000000..a73216a34e
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/delay_get_texture.html.ts
@@ -0,0 +1,46 @@
+import { timeout } from '../../../common/util/timeout.js';
+import { takeScreenshotDelayed } from '../../../common/util/wpt_reftest_wait.js';
+
+function assert(condition: boolean, msg?: string | (() => string)): asserts condition {
+ if (!condition) {
+ throw new Error(msg && (typeof msg === 'string' ? msg : msg()));
+ }
+}
+
+void (async () => {
+ assert(
+ typeof navigator !== 'undefined' && navigator.gpu !== undefined,
+ 'No WebGPU implementation found'
+ );
+
+ const adapter = await navigator.gpu.requestAdapter();
+ assert(adapter !== null);
+ const device = await adapter.requestDevice();
+ assert(device !== null);
+
+ const canvas = document.getElementById('cvs0') as HTMLCanvasElement;
+ const ctx = canvas.getContext('webgpu') as unknown as GPUCanvasContext;
+ ctx.configure({
+ device,
+ format: navigator.gpu.getPreferredCanvasFormat(),
+ alphaMode: 'premultiplied',
+ });
+
+ timeout(() => {
+ const encoder = device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: ctx.getCurrentTexture().createView(),
+ clearValue: { r: 0.0, g: 1.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store',
+ },
+ ],
+ });
+ pass.end();
+ device.queue.submit([encoder.finish()]);
+
+ takeScreenshotDelayed(50);
+ }, 100);
+})();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/delay_get_texture.https.html b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/delay_get_texture.https.html
new file mode 100644
index 0000000000..054c352ac2
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/delay_get_texture.https.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <title>WebGPU delay getCurrentTexture</title>
+ <meta charset="utf-8" />
+ <link rel="help" href="https://gpuweb.github.io/gpuweb/" />
+ <meta name="assert" content="WebGPU delay calling getCurrentTexture should be presented correctly" />
+ <link rel="match" href="./ref/delay_get_texture-ref.html" />
+ <canvas id="cvs0" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <script type="module" src="delay_get_texture.html.js"></script>
+</html>
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/ref/canvas_image_rendering-ref.html b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/ref/canvas_image_rendering-ref.html
index f9eca704e8..56e3453c56 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/ref/canvas_image_rendering-ref.html
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/ref/canvas_image_rendering-ref.html
@@ -3,12 +3,12 @@
<title>WebGPU canvas_image_rendering (ref)</title>
<meta charset="utf-8" />
<link rel="help" href="https://gpuweb.github.io/gpuweb/" />
- <img id="elem1" width="64" height="64" style="width: 99px; height: 99px;">
- <img id="elem2" width="64" height="64" style="width: 99px; height: 99px; image-rendering: pixelated;">
- <img id="elem3" width="64" height="64" style="width: 99px; height: 99px; image-rendering: crisp-edges">
- <img id="elem4" width="64" height="64" style="width: 99px; height: 99px;">
- <img id="elem5" width="64" height="64" style="width: 99px; height: 99px; image-rendering: pixelated;">
- <img id="elem6" width="64" height="64" style="width: 99px; height: 99px; image-rendering: crisp-edges">
+ <img id="elem1" width="64" height="64" style="width: 128px; height: 128px;">
+ <img id="elem2" width="64" height="64" style="width: 128px; height: 128px; image-rendering: pixelated;">
+ <img id="elem3" width="64" height="64" style="width: 128px; height: 128px; image-rendering: crisp-edges">
+ <img id="elem4" width="64" height="64" style="width: 128px; height: 128px;">
+ <img id="elem5" width="64" height="64" style="width: 128px; height: 128px; image-rendering: pixelated;">
+ <img id="elem6" width="64" height="64" style="width: 128px; height: 128px; image-rendering: crisp-edges">
<script type="module">
import { takeScreenshotDelayed } from '../../../../common/util/wpt_reftest_wait.js';
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/ref/delay_get_texture-ref.html b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/ref/delay_get_texture-ref.html
new file mode 100644
index 0000000000..fcf485dbe1
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/ref/delay_get_texture-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+ <title>WebGPU delay getCurrentTexture (ref)</title>
+ <meta charset="utf-8" />
+ <link rel="help" href="https://gpuweb.github.io/gpuweb/" />
+ <canvas id="cvs0" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <script>
+ function draw(canvas) {
+ var c = document.getElementById(canvas);
+ var ctx = c.getContext('2d');
+ ctx.fillStyle = '#00FF00';
+ ctx.fillRect(0, 0, c.width, c.height);
+ }
+
+ draw('cvs0');
+ </script>
+</html>
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/util.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/util.ts
index 84ac6b31d1..56514b2203 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/util.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/util.ts
@@ -1,9 +1,10 @@
import { Fixture, SkipTestCase } from '../../common/framework/fixture.js';
import { getResourcePath } from '../../common/framework/resources.js';
-import { makeTable } from '../../common/util/data_tables.js';
+import { keysOf } from '../../common/util/data_tables.js';
import { timeout } from '../../common/util/timeout.js';
import { ErrorWithExtra, raceWithRejectOnTimeout } from '../../common/util/util.js';
import { GPUTest } from '../gpu_test.js';
+import { RGBA, srgbToDisplayP3 } from '../util/color_space_conversion.js';
declare global {
interface HTMLMediaElement {
@@ -13,113 +14,342 @@ declare global {
}
}
-export const kVideoInfo =
- /* prettier-ignore */ makeTable(
- ['mimeType' ] as const,
- [undefined ] as const, {
- // All video names
- 'four-colors-vp8-bt601.webm': ['video/webm; codecs=vp8' ],
- 'four-colors-theora-bt601.ogv': ['video/ogg; codecs=theora' ],
- 'four-colors-h264-bt601.mp4': ['video/mp4; codecs=avc1.4d400c'],
- 'four-colors-vp9-bt601.webm': ['video/webm; codecs=vp9' ],
- 'four-colors-vp9-bt709.webm': ['video/webm; codecs=vp9' ],
- 'four-colors-vp9-bt2020.webm': ['video/webm; codecs=vp9' ],
- 'four-colors-h264-bt601-rotate-90.mp4': ['video/mp4; codecs=avc1.4d400c'],
- 'four-colors-h264-bt601-rotate-180.mp4': ['video/mp4; codecs=avc1.4d400c'],
- 'four-colors-h264-bt601-rotate-270.mp4': ['video/mp4; codecs=avc1.4d400c'],
- } as const);
-export type VideoName = keyof typeof kVideoInfo;
+// MAINTENANCE_TODO: Uses raw floats as expectation in external_texture related cases has some diffs.
+// Remove this conversion utils and uses raw float data as expectation in external_textrue
+// related cases when resolve this.
+export function convertToUnorm8(expectation: Readonly<RGBA>): Uint8Array {
+ const rgba8Unorm = new Uint8ClampedArray(4);
+ rgba8Unorm[0] = Math.round(expectation.R * 255.0);
+ rgba8Unorm[1] = Math.round(expectation.G * 255.0);
+ rgba8Unorm[2] = Math.round(expectation.B * 255.0);
+ rgba8Unorm[3] = Math.round(expectation.A * 255.0);
+ return new Uint8Array(rgba8Unorm.buffer);
+}
+
+// MAINTENANCE_TODO: Add helper function for BT.601 and BT.709 to remove all magic numbers.
// Expectation values about converting video contents to sRGB color space.
// Source video color space affects expected values.
// The process to calculate these expected pixel values can be found:
// https://github.com/gpuweb/cts/pull/2242#issuecomment-1430382811
// and https://github.com/gpuweb/cts/pull/2242#issuecomment-1463273434
const kBt601PixelValue = {
- red: new Float32Array([0.972945567233341, 0.141794376683341, -0.0209589916711088, 1.0]),
- green: new Float32Array([0.248234279433399, 0.984810378661784, -0.0564701319494314, 1.0]),
- blue: new Float32Array([0.10159735826538, 0.135451122863674, 1.00262982899724, 1.0]),
- yellow: new Float32Array([0.995470750775951, 0.992742114518355, -0.0774291236205402, 1.0]),
-};
-
-function convertToUnorm8(expectation: Float32Array): Uint8Array {
- const unorm8 = new Uint8ClampedArray(expectation.length);
+ srgb: {
+ red: { R: 0.972945567233341, G: 0.141794376683341, B: -0.0209589916711088, A: 1.0 },
+ green: { R: 0.248234279433399, G: 0.984810378661784, B: -0.0564701319494314, A: 1.0 },
+ blue: { R: 0.10159735826538, G: 0.135451122863674, B: 1.00262982899724, A: 1.0 },
+ yellow: { R: 0.995470750775951, G: 0.992742114518355, B: -0.0701036235167653, A: 1.0 },
+ },
+} as const;
- for (let i = 0; i < expectation.length; ++i) {
- unorm8[i] = Math.round(expectation[i] * 255.0);
- }
+const kBt709PixelValue = {
+ srgb: {
+ red: { R: 1.0, G: 0.0, B: 0.0, A: 1.0 },
+ green: { R: 0.0, G: 1.0, B: 0.0, A: 1.0 },
+ blue: { R: 0.0, G: 0.0, B: 1.0, A: 1.0 },
+ yellow: { R: 1.0, G: 1.0, B: 0.0, A: 1.0 },
+ },
+} as const;
- return new Uint8Array(unorm8.buffer);
+function makeTable<Table extends { readonly [K: string]: {} }>({
+ table,
+}: {
+ table: Table;
+}): {
+ readonly [F in keyof Table]: {
+ readonly [K in keyof Table[F]]: Table[F][K];
+ };
+} {
+ return Object.fromEntries(
+ Object.entries(table).map(([k, row]) => [k, { ...row }])
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
+ ) as any;
}
-// kVideoExpectations uses unorm8 results
-const kBt601Red = convertToUnorm8(kBt601PixelValue.red);
-const kBt601Green = convertToUnorm8(kBt601PixelValue.green);
-const kBt601Blue = convertToUnorm8(kBt601PixelValue.blue);
-const kBt601Yellow = convertToUnorm8(kBt601PixelValue.yellow);
-
-export const kVideoExpectations = [
- {
- videoName: 'four-colors-vp8-bt601.webm',
- _redExpectation: kBt601Red,
- _greenExpectation: kBt601Green,
- _blueExpectation: kBt601Blue,
- _yellowExpectation: kBt601Yellow,
- },
- {
- videoName: 'four-colors-theora-bt601.ogv',
- _redExpectation: kBt601Red,
- _greenExpectation: kBt601Green,
- _blueExpectation: kBt601Blue,
- _yellowExpectation: kBt601Yellow,
- },
- {
- videoName: 'four-colors-h264-bt601.mp4',
- _redExpectation: kBt601Red,
- _greenExpectation: kBt601Green,
- _blueExpectation: kBt601Blue,
- _yellowExpectation: kBt601Yellow,
+// Video expected pixel value table. Finding expected pixel value
+// with video color space and dst color space.
+export const kVideoExpectedColors = makeTable({
+ table: {
+ bt601: {
+ 'display-p3': {
+ yellow: srgbToDisplayP3(kBt601PixelValue.srgb.yellow),
+ red: srgbToDisplayP3(kBt601PixelValue.srgb.red),
+ blue: srgbToDisplayP3(kBt601PixelValue.srgb.blue),
+ green: srgbToDisplayP3(kBt601PixelValue.srgb.green),
+ },
+ srgb: {
+ yellow: kBt601PixelValue.srgb.yellow,
+ red: kBt601PixelValue.srgb.red,
+ blue: kBt601PixelValue.srgb.blue,
+ green: kBt601PixelValue.srgb.green,
+ },
+ },
+ bt709: {
+ 'display-p3': {
+ yellow: srgbToDisplayP3(kBt709PixelValue.srgb.yellow),
+ red: srgbToDisplayP3(kBt709PixelValue.srgb.red),
+ blue: srgbToDisplayP3(kBt709PixelValue.srgb.blue),
+ green: srgbToDisplayP3(kBt709PixelValue.srgb.green),
+ },
+ srgb: {
+ yellow: kBt709PixelValue.srgb.yellow,
+ red: kBt709PixelValue.srgb.red,
+ blue: kBt709PixelValue.srgb.blue,
+ green: kBt709PixelValue.srgb.green,
+ },
+ },
},
- {
- videoName: 'four-colors-vp9-bt601.webm',
- _redExpectation: kBt601Red,
- _greenExpectation: kBt601Green,
- _blueExpectation: kBt601Blue,
- _yellowExpectation: kBt601Yellow,
- },
- {
- videoName: 'four-colors-vp9-bt709.webm',
- _redExpectation: new Uint8Array([255, 0, 0, 255]),
- _greenExpectation: new Uint8Array([0, 255, 0, 255]),
- _blueExpectation: new Uint8Array([0, 0, 255, 255]),
- _yellowExpectation: new Uint8Array([255, 255, 0, 255]),
- },
-] as const;
+} as const);
-export const kVideoRotationExpectations = [
- {
- videoName: 'four-colors-h264-bt601-rotate-90.mp4',
- _topLeftExpectation: kBt601Red,
- _topRightExpectation: kBt601Green,
- _bottomLeftExpectation: kBt601Yellow,
- _bottomRightExpectation: kBt601Blue,
- },
- {
- videoName: 'four-colors-h264-bt601-rotate-180.mp4',
- _topLeftExpectation: kBt601Green,
- _topRightExpectation: kBt601Blue,
- _bottomLeftExpectation: kBt601Red,
- _bottomRightExpectation: kBt601Yellow,
- },
- {
- videoName: 'four-colors-h264-bt601-rotate-270.mp4',
- _topLeftExpectation: kBt601Blue,
- _topRightExpectation: kBt601Yellow,
- _bottomLeftExpectation: kBt601Green,
- _bottomRightExpectation: kBt601Red,
+// MAINTENANCE_TODO: Add BT.2020 video in table.
+// Video container and codec defines several transform ops to apply to raw decoded frame to display.
+// Our test cases covers 'visible rect' and 'rotation'.
+// 'visible rect' is associated with the
+// video bitstream and should apply to the raw decoded frames before any transformation.
+// 'rotation' is associated with the track or presentation and should transform
+// the whole visible rect (e.g. 90-degree rotate makes visible rect of vertical video to horizontal)
+// The order to apply these transformations is below:
+
+// [raw decoded frame] ----visible rect clipping ---->[visible frame] ---rotation ---> present
+// ^ ^
+// | |
+// coded size display size
+// The table holds test videos meta infos, including mimeType to check browser compatibility
+// video color space, raw frame content layout and the frame displayed layout.
+export const kVideoInfo = makeTable({
+ table: {
+ 'four-colors-vp8-bt601.webm': {
+ mimeType: 'video/webm; codecs=vp8',
+ colorSpace: 'bt601',
+ coded: {
+ topLeftColor: 'yellow',
+ topRightColor: 'red',
+ bottomLeftColor: 'blue',
+ bottomRightColor: 'green',
+ },
+ display: {
+ topLeftColor: 'yellow',
+ topRightColor: 'red',
+ bottomLeftColor: 'blue',
+ bottomRightColor: 'green',
+ },
+ },
+ 'four-colors-h264-bt601.mp4': {
+ mimeType: 'video/mp4; codecs=avc1.4d400c',
+ colorSpace: 'bt601',
+ coded: {
+ topLeftColor: 'yellow',
+ topRightColor: 'red',
+ bottomLeftColor: 'blue',
+ bottomRightColor: 'green',
+ },
+ display: {
+ topLeftColor: 'yellow',
+ topRightColor: 'red',
+ bottomLeftColor: 'blue',
+ bottomRightColor: 'green',
+ },
+ },
+ 'four-colors-vp9-bt601.webm': {
+ mimeType: 'video/webm; codecs=vp9',
+ colorSpace: 'bt601',
+ coded: {
+ topLeftColor: 'yellow',
+ topRightColor: 'red',
+ bottomLeftColor: 'blue',
+ bottomRightColor: 'green',
+ },
+ display: {
+ topLeftColor: 'yellow',
+ topRightColor: 'red',
+ bottomLeftColor: 'blue',
+ bottomRightColor: 'green',
+ },
+ },
+ 'four-colors-vp9-bt709.webm': {
+ mimeType: 'video/webm; codecs=vp9',
+ colorSpace: 'bt709',
+ coded: {
+ topLeftColor: 'yellow',
+ topRightColor: 'red',
+ bottomLeftColor: 'blue',
+ bottomRightColor: 'green',
+ },
+ display: {
+ topLeftColor: 'yellow',
+ topRightColor: 'red',
+ bottomLeftColor: 'blue',
+ bottomRightColor: 'green',
+ },
+ },
+ // video coded content has been rotate
+ 'four-colors-h264-bt601-rotate-90.mp4': {
+ mimeType: 'video/mp4; codecs=avc1.4d400c',
+ colorSpace: 'bt601',
+ coded: {
+ topLeftColor: 'red',
+ topRightColor: 'green',
+ bottomLeftColor: 'yellow',
+ bottomRightColor: 'blue',
+ },
+ display: {
+ topLeftColor: 'yellow',
+ topRightColor: 'red',
+ bottomLeftColor: 'blue',
+ bottomRightColor: 'green',
+ },
+ },
+ 'four-colors-h264-bt601-rotate-180.mp4': {
+ mimeType: 'video/mp4; codecs=avc1.4d400c',
+ colorSpace: 'bt601',
+ coded: {
+ topLeftColor: 'green',
+ topRightColor: 'blue',
+ bottomLeftColor: 'red',
+ bottomRightColor: 'yellow',
+ },
+ display: {
+ topLeftColor: 'yellow',
+ topRightColor: 'red',
+ bottomLeftColor: 'blue',
+ bottomRightColor: 'green',
+ },
+ },
+ 'four-colors-h264-bt601-rotate-270.mp4': {
+ mimeType: 'video/mp4; codecs=avc1.4d400c',
+ colorSpace: 'bt601',
+ coded: {
+ topLeftColor: 'blue',
+ topRightColor: 'yellow',
+ bottomLeftColor: 'green',
+ bottomRightColor: 'red',
+ },
+ display: {
+ topLeftColor: 'yellow',
+ topRightColor: 'red',
+ bottomLeftColor: 'blue',
+ bottomRightColor: 'green',
+ },
+ },
+ 'four-colors-vp9-bt601-rotate-90.mp4': {
+ mimeType: 'video/mp4; codecs=vp09.00.10.08',
+ colorSpace: 'bt601',
+ coded: {
+ topLeftColor: 'red',
+ topRightColor: 'green',
+ bottomLeftColor: 'yellow',
+ bottomRightColor: 'blue',
+ },
+ display: {
+ topLeftColor: 'yellow',
+ topRightColor: 'red',
+ bottomLeftColor: 'blue',
+ bottomRightColor: 'green',
+ },
+ },
+ 'four-colors-vp9-bt601-rotate-180.mp4': {
+ mimeType: 'video/mp4; codecs=vp09.00.10.08',
+ colorSpace: 'bt601',
+ coded: {
+ topLeftColor: 'green',
+ topRightColor: 'blue',
+ bottomLeftColor: 'red',
+ bottomRightColor: 'yellow',
+ },
+ display: {
+ topLeftColor: 'yellow',
+ topRightColor: 'red',
+ bottomLeftColor: 'blue',
+ bottomRightColor: 'green',
+ },
+ },
+ 'four-colors-vp9-bt601-rotate-270.mp4': {
+ mimeType: 'video/mp4; codecs=vp09.00.10.08',
+ colorSpace: 'bt601',
+ coded: {
+ topLeftColor: 'blue',
+ topRightColor: 'yellow',
+ bottomLeftColor: 'green',
+ bottomRightColor: 'red',
+ },
+ display: {
+ topLeftColor: 'yellow',
+ topRightColor: 'red',
+ bottomLeftColor: 'blue',
+ bottomRightColor: 'green',
+ },
+ },
+ 'four-colors-h264-bt601-hflip.mp4': {
+ mimeType: 'video/mp4; codecs=avc1.4d400c',
+ colorSpace: 'bt601',
+ coded: {
+ topLeftColor: 'yellow',
+ topRightColor: 'red',
+ bottomLeftColor: 'blue',
+ bottomRightColor: 'green',
+ },
+ display: {
+ topLeftColor: 'red',
+ topRightColor: 'yellow',
+ bottomLeftColor: 'green',
+ bottomRightColor: 'blue',
+ },
+ },
+ 'four-colors-h264-bt601-vflip.mp4': {
+ mimeType: 'video/mp4; codecs=avc1.4d400c',
+ colorSpace: 'bt601',
+ coded: {
+ topLeftColor: 'yellow',
+ topRightColor: 'red',
+ bottomLeftColor: 'blue',
+ bottomRightColor: 'green',
+ },
+ display: {
+ topLeftColor: 'blue',
+ topRightColor: 'green',
+ bottomLeftColor: 'yellow',
+ bottomRightColor: 'red',
+ },
+ },
+ 'four-colors-vp9-bt601-hflip.mp4': {
+ mimeType: 'video/mp4; codecs=vp09.00.10.08',
+ colorSpace: 'bt601',
+ coded: {
+ topLeftColor: 'yellow',
+ topRightColor: 'red',
+ bottomLeftColor: 'blue',
+ bottomRightColor: 'green',
+ },
+ display: {
+ topLeftColor: 'red',
+ topRightColor: 'yellow',
+ bottomLeftColor: 'green',
+ bottomRightColor: 'blue',
+ },
+ },
+ 'four-colors-vp9-bt601-vflip.mp4': {
+ mimeType: 'video/mp4; codecs=vp09.00.10.08',
+ colorSpace: 'bt601',
+ coded: {
+ topLeftColor: 'yellow',
+ topRightColor: 'red',
+ bottomLeftColor: 'blue',
+ bottomRightColor: 'green',
+ },
+ display: {
+ topLeftColor: 'blue',
+ topRightColor: 'green',
+ bottomLeftColor: 'yellow',
+ bottomRightColor: 'red',
+ },
+ },
},
-] as const;
+} as const);
+type VideoName = keyof typeof kVideoInfo;
+export const kVideoNames: readonly VideoName[] = keysOf(kVideoInfo);
+
+export const kPredefinedColorSpace = ['display-p3', 'srgb'] as const;
/**
* Starts playing a video and waits for it to be consumable.
* Returns a promise which resolves after `callback` (which may be async) completes.
@@ -153,7 +383,12 @@ export function startPlayingAndWaitForVideo(
video.addEventListener(
'error',
- event => reject(new ErrorWithExtra('Video received "error" event', () => ({ event }))),
+ event =>
+ reject(
+ new ErrorWithExtra('Video received "error" event, message: ' + event.message, () => ({
+ event,
+ }))
+ ),
true
);
@@ -238,6 +473,7 @@ export async function getVideoFrameFromVideoElement(
const transformer: TransformStream = new TransformStream({
transform(videoFrame, _controller) {
videoTrack.stop();
+ test.trackForCleanup(videoFrame);
resolve(videoFrame);
},
flush(controller) {
@@ -267,6 +503,10 @@ export async function getVideoFrameFromVideoElement(
*
*/
export function getVideoElement(t: GPUTest, videoName: VideoName): HTMLVideoElement {
+ if (typeof HTMLVideoElement === 'undefined') {
+ t.skip('HTMLVideoElement not available');
+ }
+
const videoElement = document.createElement('video');
const videoInfo = kVideoInfo[videoName];
@@ -277,6 +517,8 @@ export function getVideoElement(t: GPUTest, videoName: VideoName): HTMLVideoElem
const videoUrl = getResourcePath(videoName);
videoElement.src = videoUrl;
+ t.trackForCleanup(videoElement);
+
return videoElement;
}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/worker/worker.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/worker/worker.spec.ts
index 67f9f693be..14fe135633 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/worker/worker.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/worker/worker.spec.ts
@@ -1,9 +1,13 @@
export const description = `
-Tests WebGPU is available in a worker.
+Tests WebGPU is available in a dedicated worker and a shared worker.
-Note: The CTS test can be run in a worker by passing in worker=1 as
-a query parameter. This test is specifically to check that WebGPU
-is available in a worker.
+Note: Any CTS test can be run in a worker by passing ?worker=dedicated, ?worker=shared,
+?worker=service as a query parameter. The tests in this file are specifically to check
+that WebGPU is available in each worker type. When run in combination with a ?worker flag,
+they will test workers created from other workers (where APIs exist to do so).
+
+TODO[2]: Figure out how to make these tests run in service workers (not actually
+important unless service workers gain the ability to launch other workers).
`;
import { Fixture } from '../../../common/framework/fixture.js';
@@ -12,24 +16,52 @@ import { assert } from '../../../common/util/util.js';
export const g = makeTestGroup(Fixture);
-function isNode(): boolean {
- return typeof process !== 'undefined' && process?.versions?.node !== undefined;
-}
+const isNode = typeof process !== 'undefined' && process?.versions?.node !== undefined;
+
+// [1]: we load worker_launcher dynamically because ts-node support
+// is using commonjs which doesn't support import.meta. Further,
+// we need to put the url in a string and pass the string to import
+// otherwise typescript tries to parse the file which again, fails.
+// worker_launcher.js is excluded in node.tsconfig.json.
+
+// [2]: That hack does not work in Service Workers.
+const isServiceWorker = globalThis.constructor.name === 'ServiceWorkerGlobalScope';
-g.test('worker')
- .desc(`test WebGPU is available in DedicatedWorkers and check for basic functionality`)
+g.test('dedicated_worker')
+ .desc(`test WebGPU is available in dedicated workers and check for basic functionality`)
.fn(async t => {
- if (isNode()) {
- t.skip('node does not support 100% compatible workers');
- return;
- }
- // Note: we load worker_launcher dynamically because ts-node support
- // is using commonjs which doesn't support import.meta. Further,
- // we need to put the url in a string add pass the string to import
- // otherwise typescript tries to parse the file which again, fails.
- // worker_launcher.js is excluded in node.tsconfig.json.
+ t.skipIf(isNode, 'node does not support 100% compatible workers');
+
+ t.skipIf(isServiceWorker, 'Service workers do not support this import() hack'); // [2]
const url = './worker_launcher.js';
- const { launchWorker } = await import(url);
- const result = await launchWorker();
+ const { launchDedicatedWorker } = await import(url); // [1]
+
+ const result = await launchDedicatedWorker();
+ assert(result.error === undefined, `should be no error from worker but was: ${result.error}`);
+ });
+
+g.test('shared_worker')
+ .desc(`test WebGPU is available in shared workers and check for basic functionality`)
+ .fn(async t => {
+ t.skipIf(isNode, 'node does not support 100% compatible workers');
+
+ t.skipIf(isServiceWorker, 'Service workers do not support this import() hack'); // [2]
+ const url = './worker_launcher.js';
+ const { launchSharedWorker } = await import(url); // [1]
+
+ const result = await launchSharedWorker();
+ assert(result.error === undefined, `should be no error from worker but was: ${result.error}`);
+ });
+
+g.test('service_worker')
+ .desc(`test WebGPU is available in service workers and check for basic functionality`)
+ .fn(async t => {
+ t.skipIf(isNode, 'node does not support 100% compatible workers');
+
+ t.skipIf(isServiceWorker, 'Service workers do not support this import() hack'); // [2]
+ const url = './worker_launcher.js';
+ const { launchServiceWorker } = await import(url); // [1]
+
+ const result = await launchServiceWorker();
assert(result.error === undefined, `should be no error from worker but was: ${result.error}`);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/worker/worker.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/worker/worker.ts
index a3cf8064e2..033473d63a 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/worker/worker.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/worker/worker.ts
@@ -1,6 +1,10 @@
import { getGPU, setDefaultRequestAdapterOptions } from '../../../common/util/navigator_gpu.js';
import { assert, objectEquals, iterRange } from '../../../common/util/util.js';
+// Should be WorkerGlobalScope, but importing lib "webworker" conflicts with lib "dom".
+/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
+declare const self: any;
+
async function basicTest() {
const adapter = await getGPU(null).requestAdapter();
assert(adapter !== null, 'Failed to get adapter.');
@@ -68,7 +72,7 @@ async function basicTest() {
device.destroy();
}
-self.onmessage = async (ev: MessageEvent) => {
+async function reportTestResults(this: MessagePort | Worker, ev: MessageEvent) {
const defaultRequestAdapterOptions: GPURequestAdapterOptions =
ev.data.defaultRequestAdapterOptions;
setDefaultRequestAdapterOptions(defaultRequestAdapterOptions);
@@ -79,5 +83,17 @@ self.onmessage = async (ev: MessageEvent) => {
} catch (err: unknown) {
error = (err as Error).toString();
}
- self.postMessage({ error });
+ this.postMessage({ error });
+}
+
+self.onmessage = (ev: MessageEvent) => {
+ void reportTestResults.call(ev.source || self, ev);
+};
+
+self.onconnect = (event: MessageEvent) => {
+ const port = event.ports[0];
+
+ port.onmessage = (ev: MessageEvent) => {
+ void reportTestResults.call(port, ev);
+ };
};
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/worker/worker_launcher.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/worker/worker_launcher.ts
index 72059eb99f..4b1d31ae49 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/worker/worker_launcher.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/worker/worker_launcher.ts
@@ -1,10 +1,15 @@
+import { SkipTestCase } from '../../../common/framework/fixture.js';
import { getDefaultRequestAdapterOptions } from '../../../common/util/navigator_gpu.js';
export type TestResult = {
error: String | undefined;
};
-export async function launchWorker() {
+export async function launchDedicatedWorker() {
+ if (typeof Worker === 'undefined') {
+ throw new SkipTestCase(`Worker undefined in context ${globalThis.constructor.name}`);
+ }
+
const selfPath = import.meta.url;
const selfPathDir = selfPath.substring(0, selfPath.lastIndexOf('/'));
const workerPath = selfPathDir + '/worker.js';
@@ -16,3 +21,55 @@ export async function launchWorker() {
worker.postMessage({ defaultRequestAdapterOptions: getDefaultRequestAdapterOptions() });
return await promise;
}
+
+export async function launchSharedWorker() {
+ if (typeof SharedWorker === 'undefined') {
+ throw new SkipTestCase(`SharedWorker undefined in context ${globalThis.constructor.name}`);
+ }
+
+ const selfPath = import.meta.url;
+ const selfPathDir = selfPath.substring(0, selfPath.lastIndexOf('/'));
+ const workerPath = selfPathDir + '/worker.js';
+ const worker = new SharedWorker(workerPath, { type: 'module' });
+
+ const port = worker.port;
+ const promise = new Promise<TestResult>(resolve => {
+ port.addEventListener('message', ev => resolve(ev.data as TestResult), { once: true });
+ });
+ port.start();
+ port.postMessage({
+ defaultRequestAdapterOptions: getDefaultRequestAdapterOptions(),
+ });
+ return await promise;
+}
+
+export async function launchServiceWorker() {
+ if (typeof navigator === 'undefined' || typeof navigator.serviceWorker === 'undefined') {
+ throw new SkipTestCase(
+ `navigator.serviceWorker undefined in context ${globalThis.constructor.name}`
+ );
+ }
+
+ const selfPath = import.meta.url;
+ const selfPathDir = selfPath.substring(0, selfPath.lastIndexOf('/'));
+ const serviceWorkerPath = selfPathDir + '/worker.js';
+ const registration = await navigator.serviceWorker.register(serviceWorkerPath, {
+ type: 'module',
+ });
+ await registration.update();
+
+ const promise = new Promise<TestResult>(resolve => {
+ navigator.serviceWorker.addEventListener(
+ 'message',
+ ev => {
+ resolve(ev.data as TestResult);
+ void registration.unregister();
+ },
+ { once: true }
+ );
+ });
+ registration.active?.postMessage({
+ defaultRequestAdapterOptions: getDefaultRequestAdapterOptions(),
+ });
+ return await promise;
+}
diff --git a/dom/webgpu/tests/cts/checkout/tools/gen_listings b/dom/webgpu/tests/cts/checkout/tools/gen_listings
deleted file mode 100755
index 6c25622423..0000000000
--- a/dom/webgpu/tests/cts/checkout/tools/gen_listings
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/usr/bin/env node
-
-// Crawl a suite directory (e.g. src/webgpu/) to generate a listing.js containing
-// the listing of test files in the suite.
-
-require('../src/common/tools/setup-ts-in-node.js');
-require('../src/common/tools/gen_listings.ts');
diff --git a/dom/webgpu/tests/cts/checkout/tools/gen_listings_and_webworkers b/dom/webgpu/tests/cts/checkout/tools/gen_listings_and_webworkers
new file mode 100644
index 0000000000..285df3657d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/tools/gen_listings_and_webworkers
@@ -0,0 +1,8 @@
+#!/usr/bin/env node
+
+// Crawl a suite directory (e.g. src/webgpu/) to generate a listing.js containing
+// the listing of test files in the suite, and some generated test files
+// (like suite/serviceworker/**/*.spec.js).
+
+require('../src/common/tools/setup-ts-in-node.js');
+require('../src/common/tools/gen_listings_and_webworkers.ts');
diff --git a/dom/webgpu/tests/cts/checkout/tools/gen_version b/dom/webgpu/tests/cts/checkout/tools/gen_version
index 53128ca2a0..2c17db1454 100755
--- a/dom/webgpu/tests/cts/checkout/tools/gen_version
+++ b/dom/webgpu/tests/cts/checkout/tools/gen_version
@@ -1,6 +1,6 @@
#!/usr/bin/env node
-// Get the current git hash, and save (overwrite) it into out/framework/version.js
+// Get the current git hash, and save (overwrite) it into gen/.../version.js
// so it can be read when running inside the browser.
/* eslint-disable no-console */
@@ -16,18 +16,13 @@ if (!fs.existsSync(myself)) {
const { version } = require('../src/common/tools/version.ts');
-fs.mkdirSync('./out/common/internal', { recursive: true });
-// Overwrite the version.js generated by TypeScript compilation.
+fs.mkdirSync('./gen/common/internal', { recursive: true });
+// This will be copied into the various other build directories.
fs.writeFileSync(
- './out/common/internal/version.js',
+ './gen/common/internal/version.js',
`\
// AUTO-GENERATED - DO NOT EDIT. See ${myself}.
export const version = '${version}';
`
);
-
-// Since the generated version.js was overwritten, its source map is no longer relevant.
-try {
- fs.unlinkSync('./out/common/internal/version.js.map');
-} catch (ex) { }
diff --git a/dom/webgpu/tests/cts/checkout/tools/gen_wpt_cfg_chunked2sec.json b/dom/webgpu/tests/cts/checkout/tools/gen_wpt_cfg_chunked2sec.json
index 1d13e85c58..123a06e9cd 100644
--- a/dom/webgpu/tests/cts/checkout/tools/gen_wpt_cfg_chunked2sec.json
+++ b/dom/webgpu/tests/cts/checkout/tools/gen_wpt_cfg_chunked2sec.json
@@ -1,6 +1,7 @@
{
"suite": "webgpu",
"out": "../out-wpt/cts-chunked2sec.https.html",
+ "outVariantList": "../gen/webgpu_variant_list_chunked2sec.json",
"template": "../src/common/templates/cts.https.html",
"maxChunkTimeMS": 2000
}
diff --git a/dom/webgpu/tests/cts/checkout/tools/gen_wpt_cfg_unchunked.json b/dom/webgpu/tests/cts/checkout/tools/gen_wpt_cfg_unchunked.json
index ffe06d5633..9e8dfe229d 100644
--- a/dom/webgpu/tests/cts/checkout/tools/gen_wpt_cfg_unchunked.json
+++ b/dom/webgpu/tests/cts/checkout/tools/gen_wpt_cfg_unchunked.json
@@ -1,5 +1,6 @@
{
"suite": "webgpu",
"out": "../out-wpt/cts.https.html",
+ "outVariantList": "../gen/webgpu_variant_list.json",
"template": "../src/common/templates/cts.https.html"
}
diff --git a/dom/webgpu/tests/cts/checkout/tools/server b/dom/webgpu/tests/cts/checkout/tools/server
new file mode 100644
index 0000000000..01e5a8a0c8
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/tools/server
@@ -0,0 +1,6 @@
+#!/usr/bin/env node
+
+// Launch a server that runs tests server-side on demand.
+
+require('../src/common/tools/setup-ts-in-node.js');
+require('../src/common/runtime/server.ts');
diff --git a/dom/webgpu/tests/cts/checkout_commit.txt b/dom/webgpu/tests/cts/checkout_commit.txt
index 1f42bc3256..723bd3f878 100644
--- a/dom/webgpu/tests/cts/checkout_commit.txt
+++ b/dom/webgpu/tests/cts/checkout_commit.txt
@@ -1 +1 @@
-41f89e77b67e6b66cb017be4e00235a0a9429ca7
+5c8510ec0d47180d1cd4dd92790b5a69335e162c
diff --git a/dom/webgpu/tests/cts/vendor/Cargo.lock b/dom/webgpu/tests/cts/vendor/Cargo.lock
index ea51837ca5..c1f2757393 100644
--- a/dom/webgpu/tests/cts/vendor/Cargo.lock
+++ b/dom/webgpu/tests/cts/vendor/Cargo.lock
@@ -30,9 +30,9 @@ dependencies = [
[[package]]
name = "aho-corasick"
-version = "0.7.20"
+version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
@@ -125,6 +125,26 @@ dependencies = [
]
[[package]]
+name = "const_format"
+version = "0.2.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673"
+dependencies = [
+ "const_format_proc_macros",
+]
+
+[[package]]
+name = "const_format_proc_macros"
+version = "0.2.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
name = "crossbeam"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -366,6 +386,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb"
[[package]]
+name = "itertools"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
+dependencies = [
+ "either",
+]
+
+[[package]]
name = "jwalk"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -449,6 +478,12 @@ dependencies = [
]
[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
name = "miniz_oxide"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -458,6 +493,16 @@ dependencies = [
]
[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
name = "num_cpus"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -495,6 +540,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
[[package]]
+name = "pori"
+version = "0.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4a63d338dec139f56dacc692ca63ad35a6be6a797442479b55acd611d79e906"
+dependencies = [
+ "nom",
+]
+
+[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -560,9 +614,21 @@ dependencies = [
[[package]]
name = "regex"
-version = "1.7.1"
+version = "1.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
+checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629"
dependencies = [
"aho-corasick",
"memchr",
@@ -571,9 +637,9 @@ dependencies = [
[[package]]
name = "regex-syntax"
-version = "0.6.28"
+version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
+checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
[[package]]
name = "rustc-demangle"
@@ -740,6 +806,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
+name = "unicode-xid"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
+
+[[package]]
name = "vendor-webgpu-cts"
version = "0.1.0"
dependencies = [
@@ -748,12 +820,14 @@ dependencies = [
"dunce",
"env_logger",
"format",
+ "itertools",
"lets_find_up",
"log",
"miette",
"regex",
"shell-words",
"thiserror",
+ "wax",
"which",
]
@@ -765,12 +839,11 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "walkdir"
-version = "2.3.2"
+version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
- "winapi",
"winapi-util",
]
@@ -781,6 +854,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
+name = "wax"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d12a78aa0bab22d2f26ed1a96df7ab58e8a93506a3e20adb47c51a93b4e1357"
+dependencies = [
+ "const_format",
+ "itertools",
+ "nom",
+ "pori",
+ "regex",
+ "thiserror",
+ "walkdir",
+]
+
+[[package]]
name = "which"
version = "4.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/dom/webgpu/tests/cts/vendor/Cargo.toml b/dom/webgpu/tests/cts/vendor/Cargo.toml
index d7721d1931..e90a947bad 100644
--- a/dom/webgpu/tests/cts/vendor/Cargo.toml
+++ b/dom/webgpu/tests/cts/vendor/Cargo.toml
@@ -9,12 +9,14 @@ dircpy = "0.3.14"
dunce = "1.0.3"
env_logger = "0.10.0"
format = "0.2.4"
+itertools = "0.11.0"
lets_find_up = "0.0.3"
log = "0.4.17"
miette = { version = "5.5.0", features = ["fancy"] }
regex = "1.7.1"
shell-words = "1.1.0"
thiserror = "1.0.38"
+wax = "0.6.0"
which = "4.4.0"
[workspace]
diff --git a/dom/webgpu/tests/cts/vendor/src/fs.rs b/dom/webgpu/tests/cts/vendor/src/fs.rs
index 31697f9758..3062f27cdb 100644
--- a/dom/webgpu/tests/cts/vendor/src/fs.rs
+++ b/dom/webgpu/tests/cts/vendor/src/fs.rs
@@ -245,15 +245,6 @@ impl Display for Child<'_> {
}
}
-pub(crate) fn existing_file<P>(path: P) -> P
-where
- P: AsRef<Path>,
-{
- let p = path.as_ref();
- assert!(p.is_file(), "{p:?} does not exist as a file");
- path
-}
-
pub(crate) fn copy_dir<P, Q>(source: P, dest: Q) -> miette::Result<()>
where
P: Display + AsRef<Path>,
@@ -297,6 +288,30 @@ where
})
}
+pub(crate) fn rename<P1, P2>(from: P1, to: P2) -> miette::Result<()>
+where
+ P1: AsRef<Path>,
+ P2: AsRef<Path>,
+{
+ fs::rename(&from, &to).into_diagnostic().wrap_err_with(|| {
+ format!(
+ "failed to rename {} to {}",
+ from.as_ref().display(),
+ to.as_ref().display()
+ )
+ })
+}
+
+pub(crate) fn try_exists<P>(path: P) -> miette::Result<bool>
+where
+ P: AsRef<Path>,
+{
+ let path = path.as_ref();
+ path.try_exists()
+ .into_diagnostic()
+ .wrap_err_with(|| format!("failed to check if path exists: {}", path.display()))
+}
+
pub(crate) fn create_dir_all<P>(path: P) -> miette::Result<()>
where
P: AsRef<Path>,
diff --git a/dom/webgpu/tests/cts/vendor/src/main.rs b/dom/webgpu/tests/cts/vendor/src/main.rs
index 750b65c62e..1171c90b3a 100644
--- a/dom/webgpu/tests/cts/vendor/src/main.rs
+++ b/dom/webgpu/tests/cts/vendor/src/main.rs
@@ -6,12 +6,13 @@ use std::{
};
use clap::Parser;
+use itertools::Itertools;
use lets_find_up::{find_up_with, FindUpKind, FindUpOptions};
use miette::{bail, ensure, miette, Context, Diagnostic, IntoDiagnostic, Report, SourceSpan};
use regex::Regex;
use crate::{
- fs::{copy_dir, create_dir_all, existing_file, remove_file, FileRoot},
+ fs::{copy_dir, create_dir_all, remove_file, FileRoot},
path::join_path,
process::{which, EasyCommand},
};
@@ -277,13 +278,6 @@ fn run(args: CliArgs) -> miette::Result<()> {
})?;
let cts_https_html_path = out_wpt_dir.child("cts.https.html");
- log::info!("refining the output of {cts_https_html_path} with `npm run gen_wpt_cts_html …`…");
- EasyCommand::new(&npm_bin, |cmd| {
- cmd.args(["run", "gen_wpt_cts_html"]).arg(existing_file(
- &cts_ckt.child("tools/gen_wpt_cfg_unchunked.json"),
- ))
- })
- .spawn()?;
{
let extra_cts_https_html_path = out_wpt_dir.child("cts-chunked2sec.https.html");
@@ -551,6 +545,45 @@ fn run(args: CliArgs) -> miette::Result<()> {
log::info!(" …removing {cts_https_html_path}, now that it's been divided up…");
remove_file(&cts_https_html_path)?;
+ log::info!("moving ready-to-go WPT test files into `cts`…");
+
+ let webgpu_dir = out_wpt_dir.child("webgpu");
+ let ready_to_go_tests = wax::Glob::new("**/*.{html,{any,sub,worker}.js}")
+ .unwrap()
+ .walk(&webgpu_dir)
+ .map_ok(|entry| webgpu_dir.child(entry.into_path()))
+ .collect::<Result<Vec<_>, _>>()
+ .map_err(Report::msg)
+ .wrap_err_with(|| {
+ format!("failed to walk {webgpu_dir} for ready-to-go WPT test files")
+ })?;
+
+ log::trace!(" …will move the following: {ready_to_go_tests:#?}");
+
+ for file in ready_to_go_tests {
+ let path_relative_to_webgpu_dir = file.strip_prefix(&webgpu_dir).unwrap();
+ let dst_path = cts_tests_dir.child(path_relative_to_webgpu_dir);
+ log::trace!("…moving {file} to {dst_path}…");
+ ensure!(
+ !fs::try_exists(&dst_path)?,
+ "internal error: duplicate path found while moving ready-to-go test {} to {}",
+ file,
+ dst_path,
+ );
+ fs::create_dir_all(dst_path.parent().unwrap()).wrap_err_with(|| {
+ format!(
+ concat!(
+ "failed to create destination parent dirs. ",
+ "while recursively moving from {} to {}",
+ ),
+ file, dst_path,
+ )
+ })?;
+ fs::rename(&file, &dst_path)
+ .wrap_err_with(|| format!("failed to move {file} to {dst_path}"))?;
+ }
+ log::debug!(" …finished moving ready-to-go WPT test files");
+
Ok(())
})?;
diff --git a/dom/webidl/AddonManager.webidl b/dom/webidl/AddonManager.webidl
index fc94228954..0ccf6c9281 100644
--- a/dom/webidl/AddonManager.webidl
+++ b/dom/webidl/AddonManager.webidl
@@ -79,25 +79,6 @@ interface AddonManager : EventTarget {
* @return A promise that resolves to an instance of AddonInstall.
*/
Promise<AddonInstall> createInstall(optional addonInstallOptions options = {});
-
- /**
- * Opens an Abuse Report dialog window for the addon with the given id.
- * The addon may be currently installed (in which case the report will
- * include the details available locally), or not (in which case the report
- * will include the details that can be retrieved from the AMO API endpoint).
- *
- * @param id
- * The ID of the add-on to report.
- * @return A promise that resolves to a boolean (true when the report
- * has been submitted successfully, false if the user cancelled
- * the report). The Promise is rejected is the report fails
- * for a reason other than user cancellation.
- */
- Promise<boolean> reportAbuse(DOMString id);
-
- // Indicator to content whether handing off the reports to the integrated
- // abuse report panel is enabled.
- readonly attribute boolean abuseReportPanelEnabled;
};
[ChromeOnly,Exposed=Window,HeaderFile="mozilla/AddonManagerWebAPI.h"]
diff --git a/dom/webidl/CSPDictionaries.webidl b/dom/webidl/CSPDictionaries.webidl
index a84162c6e5..1f1591129e 100644
--- a/dom/webidl/CSPDictionaries.webidl
+++ b/dom/webidl/CSPDictionaries.webidl
@@ -32,6 +32,8 @@ dictionary CSP {
sequence<DOMString> worker-src;
sequence<DOMString> script-src-elem;
sequence<DOMString> script-src-attr;
+ sequence<DOMString> require-trusted-types-for;
+ sequence<DOMString> trusted-types;
};
[GenerateToJSON]
diff --git a/dom/webidl/CSSMarginRule.webidl b/dom/webidl/CSSMarginRule.webidl
new file mode 100644
index 0000000000..8210bbb79f
--- /dev/null
+++ b/dom/webidl/CSSMarginRule.webidl
@@ -0,0 +1,15 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/.
+ *
+ * The origin of this IDL file is
+ * https://drafts.csswg.org/cssom/#the-cssmarginrule-interface
+ */
+
+// https://drafts.csswg.org/cssom/#the-cssmarginrule-interface
+[Pref="layout.css.margin-rules.enabled", Exposed=Window]
+interface CSSMarginRule : CSSRule {
+ readonly attribute UTF8String name;
+ [SameObject, PutForwards=cssText] readonly attribute CSSStyleDeclaration style;
+};
diff --git a/dom/webidl/CSSStyleRule.webidl b/dom/webidl/CSSStyleRule.webidl
index 652e456f65..c48e7f79fa 100644
--- a/dom/webidl/CSSStyleRule.webidl
+++ b/dom/webidl/CSSStyleRule.webidl
@@ -22,6 +22,10 @@ interface CSSStyleRule : CSSGroupingRule {
optional [LegacyNullToEmptyString] DOMString pseudo = "",
optional boolean includeVisitedStyle = false);
[ChromeOnly] sequence<SelectorWarning> getSelectorWarnings();
+ // Get elements on the page matching the rule's selectors. This is helpful for DevTools
+ // so we can avoid computing a desugared selector, which can be very expensive on deeply
+ // nested rules.
+ [ChromeOnly] NodeList querySelectorAll(Node root);
};
enum SelectorWarningKind {
diff --git a/dom/webidl/Clipboard.webidl b/dom/webidl/Clipboard.webidl
index 5bc5cd51bc..d793916c1a 100644
--- a/dom/webidl/Clipboard.webidl
+++ b/dom/webidl/Clipboard.webidl
@@ -53,6 +53,8 @@ interface ClipboardItem {
[NewObject]
Promise<Blob> getType(DOMString type);
+
+ static boolean supports(DOMString type);
};
enum PresentationStyle { "unspecified", "inline", "attachment" };
diff --git a/dom/webidl/HTMLAnchorElement.webidl b/dom/webidl/HTMLAnchorElement.webidl
index 3ca7455f4c..cfa9ddcc9c 100644
--- a/dom/webidl/HTMLAnchorElement.webidl
+++ b/dom/webidl/HTMLAnchorElement.webidl
@@ -21,7 +21,7 @@ interface HTMLAnchorElement : HTMLElement {
[CEReactions, SetterThrows]
attribute DOMString download;
[CEReactions, SetterThrows]
- attribute DOMString ping;
+ attribute USVString ping;
[CEReactions, SetterThrows]
attribute DOMString rel;
[CEReactions, SetterThrows]
diff --git a/dom/webidl/HTMLAreaElement.webidl b/dom/webidl/HTMLAreaElement.webidl
index 7bef4a316c..ca9bbc8596 100644
--- a/dom/webidl/HTMLAreaElement.webidl
+++ b/dom/webidl/HTMLAreaElement.webidl
@@ -28,7 +28,7 @@ interface HTMLAreaElement : HTMLElement {
[CEReactions, SetterThrows]
attribute DOMString download;
[CEReactions, SetterThrows]
- attribute DOMString ping;
+ attribute USVString ping;
[CEReactions, SetterThrows]
attribute DOMString rel;
[CEReactions, SetterThrows]
diff --git a/dom/webidl/HTMLSourceElement.webidl b/dom/webidl/HTMLSourceElement.webidl
index 4917e57c8e..512f18584f 100644
--- a/dom/webidl/HTMLSourceElement.webidl
+++ b/dom/webidl/HTMLSourceElement.webidl
@@ -16,14 +16,14 @@ interface HTMLSourceElement : HTMLElement {
[HTMLConstructor] constructor();
[CEReactions, SetterNeedsSubjectPrincipal=NonSystem, SetterThrows]
- attribute DOMString src;
+ attribute USVString src;
[CEReactions, SetterThrows]
attribute DOMString type;
};
partial interface HTMLSourceElement {
[CEReactions, SetterNeedsSubjectPrincipal=NonSystem, SetterThrows]
- attribute DOMString srcset;
+ attribute USVString srcset;
[CEReactions, SetterThrows]
attribute DOMString sizes;
[CEReactions, SetterThrows]
diff --git a/dom/webidl/IDBFactory.webidl b/dom/webidl/IDBFactory.webidl
index 9e9153324f..979335716d 100644
--- a/dom/webidl/IDBFactory.webidl
+++ b/dom/webidl/IDBFactory.webidl
@@ -27,17 +27,11 @@ interface IDBFactory {
[NewObject, Throws, NeedsCallerType]
IDBOpenDBRequest
open(DOMString name,
- [EnforceRange] unsigned long long version);
+ optional [EnforceRange] unsigned long long version);
[NewObject, Throws, NeedsCallerType]
IDBOpenDBRequest
- open(DOMString name,
- optional IDBOpenDBOptions options = {});
-
- [NewObject, Throws, NeedsCallerType]
- IDBOpenDBRequest
- deleteDatabase(DOMString name,
- optional IDBOpenDBOptions options = {});
+ deleteDatabase(DOMString name);
[Throws]
Promise<sequence<IDBDatabaseInfo>> databases();
diff --git a/dom/webidl/PushSubscription.webidl b/dom/webidl/PushSubscription.webidl
index 6acb13d10a..25edfd4d55 100644
--- a/dom/webidl/PushSubscription.webidl
+++ b/dom/webidl/PushSubscription.webidl
@@ -35,9 +35,9 @@ dictionary PushSubscriptionInit
{
required USVString endpoint;
required USVString scope;
- ArrayBuffer? p256dhKey;
- ArrayBuffer? authSecret;
- BufferSource? appServerKey;
+ ArrayBuffer? p256dhKey = null;
+ ArrayBuffer? authSecret = null;
+ BufferSource? appServerKey = null;
EpochTimeStamp? expirationTime = null;
};
diff --git a/dom/webidl/RTCDataChannel.webidl b/dom/webidl/RTCDataChannel.webidl
index 956458bd32..7d03ba20ff 100644
--- a/dom/webidl/RTCDataChannel.webidl
+++ b/dom/webidl/RTCDataChannel.webidl
@@ -36,7 +36,7 @@ interface RTCDataChannel : EventTarget
attribute EventHandler onbufferedamountlow;
attribute RTCDataChannelType binaryType;
[Throws]
- undefined send(DOMString data);
+ undefined send(USVString data);
[Throws]
undefined send(Blob data);
[Throws]
diff --git a/dom/webidl/Selection.webidl b/dom/webidl/Selection.webidl
index 263c50181e..24bc244e7f 100644
--- a/dom/webidl/Selection.webidl
+++ b/dom/webidl/Selection.webidl
@@ -21,6 +21,8 @@ interface Selection {
[NeedsCallerType]
readonly attribute unsigned long focusOffset;
readonly attribute boolean isCollapsed;
+ [ChromeOnly]
+ readonly attribute boolean areNormalAndCrossShadowBoundaryRangesCollapsed;
/**
* Returns the number of ranges in the selection.
*/
diff --git a/dom/webidl/StorageEvent.webidl b/dom/webidl/StorageEvent.webidl
index b41d144603..a612afc4e4 100644
--- a/dom/webidl/StorageEvent.webidl
+++ b/dom/webidl/StorageEvent.webidl
@@ -18,17 +18,16 @@ interface StorageEvent : Event
readonly attribute DOMString? key;
readonly attribute DOMString? oldValue;
readonly attribute DOMString? newValue;
- readonly attribute DOMString? url;
+ readonly attribute USVString url;
readonly attribute Storage? storageArea;
- // Bug 1016053 - This is not spec compliant.
undefined initStorageEvent(DOMString type,
optional boolean canBubble = false,
optional boolean cancelable = false,
optional DOMString? key = null,
optional DOMString? oldValue = null,
optional DOMString? newValue = null,
- optional DOMString? url = null,
+ optional USVString url = "",
optional Storage? storageArea = null);
};
@@ -37,6 +36,6 @@ dictionary StorageEventInit : EventInit
DOMString? key = null;
DOMString? oldValue = null;
DOMString? newValue = null;
- DOMString url = "";
+ USVString url = "";
Storage? storageArea = null;
};
diff --git a/dom/webidl/WebGLRenderingContext.webidl b/dom/webidl/WebGLRenderingContext.webidl
index 00585a06bf..15cf4ee7a8 100644
--- a/dom/webidl/WebGLRenderingContext.webidl
+++ b/dom/webidl/WebGLRenderingContext.webidl
@@ -546,6 +546,11 @@ interface mixin WebGLRenderingContextBase {
readonly attribute GLsizei drawingBufferWidth;
readonly attribute GLsizei drawingBufferHeight;
+ /* Upon context creation, drawingBufferColorSpace and unpackColorSpace both
+ default to the value "srgb". */
+ attribute PredefinedColorSpace drawingBufferColorSpace;
+ //attribute PredefinedColorSpace unpackColorSpace;
+
[WebGLHandlesContextLoss] WebGLContextAttributes? getContextAttributes();
[WebGLHandlesContextLoss] boolean isContextLost();
diff --git a/dom/webidl/WebGPU.webidl b/dom/webidl/WebGPU.webidl
index c01d501542..7f15a0edfe 100644
--- a/dom/webidl/WebGPU.webidl
+++ b/dom/webidl/WebGPU.webidl
@@ -602,8 +602,11 @@ interface mixin GPUPipelineBase {
dictionary GPUProgrammableStage {
required GPUShaderModule module;
USVString entryPoint;
+ record<USVString, GPUPipelineConstantValue> constants;
};
+typedef double GPUPipelineConstantValue; // May represent WGSL's bool, f32, i32, u32, and f16 if enabled.
+
//TODO: Serializable
// https://bugzilla.mozilla.org/show_bug.cgi?id=1696219
[Func="mozilla::webgpu::Instance::PrefEnabled",
diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build
index 9f8832d042..58eb0ef8b6 100644
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -491,6 +491,7 @@ WEBIDL_FILES = [
"CSSKeyframesRule.webidl",
"CSSLayerBlockRule.webidl",
"CSSLayerStatementRule.webidl",
+ "CSSMarginRule.webidl",
"CSSMediaRule.webidl",
"CSSMozDocumentRule.webidl",
"CSSNamespaceRule.webidl",
diff --git a/dom/webscheduling/WebTaskSchedulerWorker.cpp b/dom/webscheduling/WebTaskSchedulerWorker.cpp
index fea65c65db..6ab2337b70 100644
--- a/dom/webscheduling/WebTaskSchedulerWorker.cpp
+++ b/dom/webscheduling/WebTaskSchedulerWorker.cpp
@@ -12,7 +12,7 @@ namespace mozilla::dom {
WebTaskWorkerRunnable::WebTaskWorkerRunnable(
WorkerPrivate* aWorkerPrivate, WebTaskSchedulerWorker* aSchedulerWorker)
- : WorkerSameThreadRunnable(aWorkerPrivate, "WebTaskWorkerRunnable"),
+ : WorkerSameThreadRunnable("WebTaskWorkerRunnable"),
mSchedulerWorker(aSchedulerWorker) {
MOZ_ASSERT(mSchedulerWorker);
}
@@ -60,7 +60,7 @@ bool WebTaskSchedulerWorker::DispatchEventLoopRunnable() {
}
RefPtr<WebTaskWorkerRunnable> runnable =
new WebTaskWorkerRunnable(mWorkerPrivate, this);
- return runnable->Dispatch();
+ return runnable->Dispatch(mWorkerPrivate);
}
void WebTaskSchedulerWorker::Disconnect() {
diff --git a/dom/websocket/WebSocket.cpp b/dom/websocket/WebSocket.cpp
index d0be6e1778..04b28798ae 100644
--- a/dom/websocket/WebSocket.cpp
+++ b/dom/websocket/WebSocket.cpp
@@ -1612,6 +1612,19 @@ nsresult WebSocketImpl::Init(JSContext* aCx, bool aIsSecure,
nsresult rv = mWebSocket->CheckCurrentGlobalCorrectness();
NS_ENSURE_SUCCESS(rv, rv);
+ // Assign the sub protocol list and scan it for illegal values
+ for (uint32_t index = 0; index < aProtocolArray.Length(); ++index) {
+ if (!WebSocket::IsValidProtocolString(aProtocolArray[index])) {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ if (!mRequestedProtocolList.IsEmpty()) {
+ mRequestedProtocolList.AppendLiteral(", ");
+ }
+
+ AppendUTF16toUTF8(aProtocolArray[index], mRequestedProtocolList);
+ }
+
// Shut down websocket if window is frozen or destroyed (only needed for
// "ghost" websockets--see bug 696085)
RefPtr<WebSocketImplProxy> proxy;
@@ -1795,19 +1808,6 @@ nsresult WebSocketImpl::Init(JSContext* aCx, bool aIsSecure,
}
}
- // Assign the sub protocol list and scan it for illegal values
- for (uint32_t index = 0; index < aProtocolArray.Length(); ++index) {
- if (!WebSocket::IsValidProtocolString(aProtocolArray[index])) {
- return NS_ERROR_DOM_SYNTAX_ERR;
- }
-
- if (!mRequestedProtocolList.IsEmpty()) {
- mRequestedProtocolList.AppendLiteral(", ");
- }
-
- AppendUTF16toUTF8(aProtocolArray[index], mRequestedProtocolList);
- }
-
if (mIsMainThread) {
mImplProxy = std::move(proxy);
}
@@ -2639,8 +2639,7 @@ namespace {
class CancelRunnable final : public MainThreadWorkerRunnable {
public:
CancelRunnable(ThreadSafeWorkerRef* aWorkerRef, WebSocketImpl* aImpl)
- : MainThreadWorkerRunnable(aWorkerRef->Private(), "CancelRunnable"),
- mImpl(aImpl) {}
+ : MainThreadWorkerRunnable("CancelRunnable"), mImpl(aImpl) {}
bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
aWorkerPrivate->AssertIsOnWorkerThread();
@@ -2675,7 +2674,7 @@ WebSocketImpl::Cancel(nsresult aStatus) {
if (!mIsMainThread) {
MOZ_ASSERT(mWorkerRef);
RefPtr<CancelRunnable> runnable = new CancelRunnable(mWorkerRef, this);
- if (!runnable->Dispatch()) {
+ if (!runnable->Dispatch(mWorkerRef->Private())) {
return NS_ERROR_FAILURE;
}
@@ -2785,15 +2784,14 @@ WebSocketImpl::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
namespace {
-class WorkerRunnableDispatcher final : public WorkerRunnable {
+class WorkerRunnableDispatcher final : public WorkerThreadRunnable {
RefPtr<WebSocketImpl> mWebSocketImpl;
public:
WorkerRunnableDispatcher(WebSocketImpl* aImpl,
ThreadSafeWorkerRef* aWorkerRef,
already_AddRefed<nsIRunnable> aEvent)
- : WorkerRunnable(aWorkerRef->Private(), "WorkerRunnableDispatcher",
- WorkerThread),
+ : WorkerThreadRunnable("WorkerRunnableDispatcher"),
mWebSocketImpl(aImpl),
mEvent(std::move(aEvent)) {}
@@ -2861,7 +2859,7 @@ WebSocketImpl::Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags) {
RefPtr<WorkerRunnableDispatcher> event =
new WorkerRunnableDispatcher(this, mWorkerRef, event_ref.forget());
- if (!event->Dispatch()) {
+ if (!event->Dispatch(mWorkerRef->Private())) {
return NS_ERROR_FAILURE;
}
diff --git a/dom/webtransport/parent/WebTransportParent.cpp b/dom/webtransport/parent/WebTransportParent.cpp
index 236c9a945a..885a7df079 100644
--- a/dom/webtransport/parent/WebTransportParent.cpp
+++ b/dom/webtransport/parent/WebTransportParent.cpp
@@ -625,25 +625,6 @@ void WebTransportParent::NotifyRemoteClosed(bool aCleanly, uint32_t aErrorCode,
}));
}
-// This method is currently not used by WebTransportSessionProxy to inform of
-// any session related events. All notification is recieved via
-// WebTransportSessionProxy::OnSessionReady and
-// WebTransportSessionProxy::OnSessionClosed methods
-NS_IMETHODIMP
-WebTransportParent::OnSessionReadyInternal(
- mozilla::net::Http3WebTransportSession* aSession) {
- Unused << aSession;
- return NS_OK;
-}
-
-NS_IMETHODIMP
-WebTransportParent::OnIncomingStreamAvailableInternal(
- mozilla::net::Http3WebTransportStream* aStream) {
- // XXX implement once DOM WebAPI supports creation of streams
- Unused << aStream;
- return NS_OK;
-}
-
NS_IMETHODIMP
WebTransportParent::OnIncomingUnidirectionalStreamAvailable(
nsIWebTransportReceiveStream* aStream) {
@@ -795,13 +776,6 @@ NS_IMETHODIMP WebTransportParent::OnDatagramReceived(
return NS_OK;
}
-NS_IMETHODIMP WebTransportParent::OnDatagramReceivedInternal(
- nsTArray<uint8_t>&& aData) {
- // this method is used only for internal notificaiton within necko
- // we dont expect to receive any notification with on this interface
- return NS_OK;
-}
-
NS_IMETHODIMP
WebTransportParent::OnOutgoingDatagramOutCome(
uint64_t aId, WebTransportSessionEventListener::DatagramOutcome aOutCome) {
diff --git a/dom/workers/EventWithOptionsRunnable.cpp b/dom/workers/EventWithOptionsRunnable.cpp
index 2ddc56d946..5c4988c9a7 100644
--- a/dom/workers/EventWithOptionsRunnable.cpp
+++ b/dom/workers/EventWithOptionsRunnable.cpp
@@ -33,8 +33,7 @@
namespace mozilla::dom {
EventWithOptionsRunnable::EventWithOptionsRunnable(Worker& aWorker,
const char* aName)
- : WorkerDebuggeeRunnable(aWorker.mWorkerPrivate, aName,
- WorkerRunnable::WorkerThread),
+ : WorkerDebuggeeRunnable(aName),
StructuredCloneHolder(CloningSupported, TransferringSupported,
StructuredCloneScope::SameProcess) {}
@@ -132,19 +131,6 @@ bool EventWithOptionsRunnable::BuildAndFireEvent(
bool EventWithOptionsRunnable::WorkerRun(JSContext* aCx,
WorkerPrivate* aWorkerPrivate) {
- if (mTarget == ParentThread) {
- // Don't fire this event if the JS object has been disconnected from the
- // private object.
- if (!aWorkerPrivate->IsAcceptingEvents()) {
- return true;
- }
-
- aWorkerPrivate->AssertInnerWindowIsCorrect();
-
- return BuildAndFireEvent(aCx, aWorkerPrivate,
- aWorkerPrivate->ParentEventTargetRef());
- }
-
MOZ_ASSERT(aWorkerPrivate == GetWorkerPrivateFromContext(aCx));
MOZ_ASSERT(aWorkerPrivate->GlobalScope());
diff --git a/dom/workers/MessageEventRunnable.cpp b/dom/workers/MessageEventRunnable.cpp
index 8edc7037cd..33efc966b4 100644
--- a/dom/workers/MessageEventRunnable.cpp
+++ b/dom/workers/MessageEventRunnable.cpp
@@ -14,9 +14,8 @@
namespace mozilla::dom {
-MessageEventRunnable::MessageEventRunnable(WorkerPrivate* aWorkerPrivate,
- Target aTarget)
- : WorkerDebuggeeRunnable(aWorkerPrivate, "MessageEventRunnable", aTarget),
+MessageEventRunnable::MessageEventRunnable(WorkerPrivate* aWorkerPrivate)
+ : WorkerDebuggeeRunnable("MessageEventRunnable"),
StructuredCloneHolder(CloningSupported, TransferringSupported,
StructuredCloneScope::SameProcess) {}
@@ -84,20 +83,6 @@ bool MessageEventRunnable::DispatchDOMEvent(JSContext* aCx,
bool MessageEventRunnable::WorkerRun(JSContext* aCx,
WorkerPrivate* aWorkerPrivate) {
- if (mTarget == ParentThread) {
- // Don't fire this event if the JS object has been disconnected from the
- // private object.
- if (!aWorkerPrivate->IsAcceptingEvents()) {
- return true;
- }
-
- aWorkerPrivate->AssertInnerWindowIsCorrect();
-
- return DispatchDOMEvent(aCx, aWorkerPrivate,
- aWorkerPrivate->ParentEventTargetRef(),
- !aWorkerPrivate->GetParent());
- }
-
MOZ_ASSERT(aWorkerPrivate == GetWorkerPrivateFromContext(aCx));
MOZ_ASSERT(aWorkerPrivate->GlobalScope());
@@ -124,4 +109,97 @@ void MessageEventRunnable::DispatchError(JSContext* aCx,
aTarget->DispatchEvent(*event);
}
+MessageEventToParentRunnable::MessageEventToParentRunnable(
+ WorkerPrivate* aWorkerPrivate)
+ : WorkerParentDebuggeeRunnable("MessageEventToParentRunnable"),
+ StructuredCloneHolder(CloningSupported, TransferringSupported,
+ StructuredCloneScope::SameProcess) {}
+
+bool MessageEventToParentRunnable::DispatchDOMEvent(
+ JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+ DOMEventTargetHelper* aTarget, bool aIsMainThread) {
+ nsCOMPtr<nsIGlobalObject> parent = aTarget->GetParentObject();
+
+ // For some workers without window, parent is null and we try to find it
+ // from the JS Context.
+ if (!parent) {
+ JS::Rooted<JSObject*> globalObject(aCx, JS::CurrentGlobalOrNull(aCx));
+ if (NS_WARN_IF(!globalObject)) {
+ return false;
+ }
+
+ parent = xpc::NativeGlobal(globalObject);
+ if (NS_WARN_IF(!parent)) {
+ return false;
+ }
+ }
+
+ MOZ_ASSERT(parent);
+
+ JS::Rooted<JS::Value> messageData(aCx);
+ IgnoredErrorResult rv;
+
+ JS::CloneDataPolicy cloneDataPolicy;
+ if (parent->GetClientInfo().isSome() &&
+ parent->GetClientInfo()->AgentClusterId().isSome() &&
+ parent->GetClientInfo()->AgentClusterId()->Equals(
+ aWorkerPrivate->AgentClusterId())) {
+ cloneDataPolicy.allowIntraClusterClonableSharedObjects();
+ }
+
+ if (aWorkerPrivate->IsSharedMemoryAllowed()) {
+ cloneDataPolicy.allowSharedMemoryObjects();
+ }
+
+ Read(parent, aCx, &messageData, cloneDataPolicy, rv);
+
+ if (NS_WARN_IF(rv.Failed())) {
+ DispatchError(aCx, aTarget);
+ return false;
+ }
+
+ Sequence<OwningNonNull<MessagePort>> ports;
+ if (!TakeTransferredPortsAsSequence(ports)) {
+ DispatchError(aCx, aTarget);
+ return false;
+ }
+
+ RefPtr<MessageEvent> event = new MessageEvent(aTarget, nullptr, nullptr);
+ event->InitMessageEvent(nullptr, u"message"_ns, CanBubble::eNo,
+ Cancelable::eNo, messageData, u""_ns, u""_ns, nullptr,
+ ports);
+
+ event->SetTrusted(true);
+
+ aTarget->DispatchEvent(*event);
+
+ return true;
+}
+
+bool MessageEventToParentRunnable::WorkerRun(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate) {
+ if (!aWorkerPrivate->IsAcceptingEvents()) {
+ return true;
+ }
+
+ aWorkerPrivate->AssertInnerWindowIsCorrect();
+
+ return DispatchDOMEvent(aCx, aWorkerPrivate,
+ aWorkerPrivate->ParentEventTargetRef(),
+ !aWorkerPrivate->GetParent());
+}
+
+void MessageEventToParentRunnable::DispatchError(
+ JSContext* aCx, DOMEventTargetHelper* aTarget) {
+ RootedDictionary<MessageEventInit> init(aCx);
+ init.mBubbles = false;
+ init.mCancelable = false;
+
+ RefPtr<Event> event =
+ MessageEvent::Constructor(aTarget, u"messageerror"_ns, init);
+ event->SetTrusted(true);
+
+ aTarget->DispatchEvent(*event);
+}
+
} // namespace mozilla::dom
diff --git a/dom/workers/MessageEventRunnable.h b/dom/workers/MessageEventRunnable.h
index c20efb4c55..45bce9949f 100644
--- a/dom/workers/MessageEventRunnable.h
+++ b/dom/workers/MessageEventRunnable.h
@@ -20,7 +20,21 @@ namespace dom {
class MessageEventRunnable final : public WorkerDebuggeeRunnable,
public StructuredCloneHolder {
public:
- MessageEventRunnable(WorkerPrivate* aWorkerPrivate, Target aTarget);
+ explicit MessageEventRunnable(WorkerPrivate* aWorkerPrivate);
+
+ bool DispatchDOMEvent(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+ DOMEventTargetHelper* aTarget, bool aIsMainThread);
+
+ private:
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override;
+
+ void DispatchError(JSContext* aCx, DOMEventTargetHelper* aTarget);
+};
+
+class MessageEventToParentRunnable final : public WorkerParentDebuggeeRunnable,
+ public StructuredCloneHolder {
+ public:
+ explicit MessageEventToParentRunnable(WorkerPrivate* aWorkerPrivate);
bool DispatchDOMEvent(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
DOMEventTargetHelper* aTarget, bool aIsMainThread);
diff --git a/dom/workers/RuntimeService.cpp b/dom/workers/RuntimeService.cpp
index 321895e700..047384b420 100644
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -343,7 +343,7 @@ void LoadJSGCMemoryOptions(const char* aPrefName, void* /* aClosure */) {
#define PREF(suffix_, key_) \
{ \
- nsLiteralCString(PREF_MEM_OPTIONS_PREFIX suffix_), \
+ nsLiteralCString(suffix_), \
PREF_JS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX suffix_, key_ \
}
constexpr WorkerGCPref kWorkerPrefs[] = {
@@ -372,6 +372,7 @@ void LoadJSGCMemoryOptions(const char* aPrefName, void* /* aClosure */) {
PREF("gc_parallel_marking", JSGC_PARALLEL_MARKING_ENABLED),
PREF("gc_parallel_marking_threshold_mb",
JSGC_PARALLEL_MARKING_THRESHOLD_MB),
+ PREF("gc_max_parallel_marking_threads", JSGC_MAX_MARKING_THREADS),
#ifdef NIGHTLY_BUILD
PREF("gc_experimental_semispace_nursery", JSGC_SEMISPACE_NURSERY_ENABLED),
#endif
@@ -454,6 +455,7 @@ void LoadJSGCMemoryOptions(const char* aPrefName, void* /* aClosure */) {
case JSGC_MAX_EMPTY_CHUNK_COUNT:
case JSGC_HEAP_GROWTH_FACTOR:
case JSGC_PARALLEL_MARKING_THRESHOLD_MB:
+ case JSGC_MAX_MARKING_THREADS:
UpdateCommonJSGCMemoryOption(rts, pref->fullName, pref->key);
break;
default:
@@ -596,7 +598,7 @@ void CTypesActivityCallback(JSContext* aCx, JS::CTypesActivityType aType) {
// do. Moreover, JS_DestroyContext() does *not* block on JS::Dispatchable::run
// being called, DispatchToEventLoopCallback failure is expected to happen
// during shutdown.
-class JSDispatchableRunnable final : public WorkerRunnable {
+class JSDispatchableRunnable final : public WorkerThreadRunnable {
JS::Dispatchable* mDispatchable;
~JSDispatchableRunnable() { MOZ_ASSERT(!mDispatchable); }
@@ -617,21 +619,19 @@ class JSDispatchableRunnable final : public WorkerRunnable {
public:
JSDispatchableRunnable(WorkerPrivate* aWorkerPrivate,
JS::Dispatchable* aDispatchable)
- : WorkerRunnable(aWorkerPrivate, "JSDispatchableRunnable",
- WorkerRunnable::WorkerThread),
+ : WorkerThreadRunnable("JSDispatchableRunnable"),
mDispatchable(aDispatchable) {
MOZ_ASSERT(mDispatchable);
}
bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
- MOZ_ASSERT(aWorkerPrivate == mWorkerPrivate);
- MOZ_ASSERT(aCx == mWorkerPrivate->GetJSContext());
+ MOZ_ASSERT(aCx == aWorkerPrivate->GetJSContext());
MOZ_ASSERT(mDispatchable);
AutoJSAPI jsapi;
jsapi.Init();
- mDispatchable->run(mWorkerPrivate->GetJSContext(),
+ mDispatchable->run(aWorkerPrivate->GetJSContext(),
JS::Dispatchable::NotShuttingDown);
mDispatchable = nullptr; // mDispatchable may delete itself
@@ -644,7 +644,7 @@ class JSDispatchableRunnable final : public WorkerRunnable {
AutoJSAPI jsapi;
jsapi.Init();
- mDispatchable->run(mWorkerPrivate->GetJSContext(),
+ mDispatchable->run(GetCurrentThreadWorkerPrivate()->GetJSContext(),
JS::Dispatchable::ShuttingDown);
mDispatchable = nullptr; // mDispatchable may delete itself
@@ -665,7 +665,7 @@ static bool DispatchToEventLoop(void* aClosure,
// the JSDispatchableRunnable comment above.
RefPtr<JSDispatchableRunnable> r =
new JSDispatchableRunnable(workerPrivate, aDispatchable);
- return r->Dispatch();
+ return r->Dispatch(workerPrivate);
}
static bool ConsumeStream(JSContext* aCx, JS::Handle<JSObject*> aObj,
@@ -1482,9 +1482,9 @@ namespace {
class DumpCrashInfoRunnable final : public WorkerControlRunnable {
public:
explicit DumpCrashInfoRunnable(WorkerPrivate* aWorkerPrivate)
- : WorkerControlRunnable(aWorkerPrivate, "DumpCrashInfoRunnable",
- WorkerThread),
- mMonitor("DumpCrashInfoRunnable::mMonitor") {}
+ : WorkerControlRunnable("DumpCrashInfoRunnable"),
+ mMonitor("DumpCrashInfoRunnable::mMonitor"),
+ mWorkerPrivate(aWorkerPrivate) {}
bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
MonitorAutoLock lock(mMonitor);
@@ -1510,7 +1510,7 @@ class DumpCrashInfoRunnable final : public WorkerControlRunnable {
bool DispatchAndWait() {
MonitorAutoLock lock(mMonitor);
- if (!Dispatch()) {
+ if (!Dispatch(mWorkerPrivate)) {
// The worker is already dead but the main thread still didn't remove it
// from RuntimeService's registry.
return false;
@@ -1537,6 +1537,7 @@ class DumpCrashInfoRunnable final : public WorkerControlRunnable {
Monitor mMonitor MOZ_UNANNOTATED;
nsCString mMsg;
FlippedOnce<false> mHasMsg;
+ WorkerPrivate* mWorkerPrivate;
};
struct ActiveWorkerStats {
@@ -2066,21 +2067,24 @@ WorkerThreadPrimaryRunnable::Run() {
url.get());
using mozilla::ipc::BackgroundChild;
-
{
+ bool runLoopRan = false;
auto failureCleanup = MakeScopeExit([&]() {
- // The creation of threadHelper above is the point at which a worker is
- // considered to have run, because the `mPreStartRunnables` are all
- // re-dispatched after `mThread` is set. We need to let the WorkerPrivate
- // know so it can clean up the various event loops and delete the worker.
- mWorkerPrivate->RunLoopNeverRan();
+ // If Worker initialization fails, call WorkerPrivate::ScheduleDeletion()
+ // to release the WorkerPrivate in the parent thread.
+ mWorkerPrivate->ScheduleDeletion(WorkerPrivate::WorkerRan);
});
mWorkerPrivate->SetWorkerPrivateInWorkerThread(mThread.unsafeGetRawPtr());
const auto threadCleanup = MakeScopeExit([&] {
- // This must be called before ScheduleDeletion, which is either called
- // from failureCleanup leaving scope, or from the outer scope.
+ // If Worker initialization fails, such as creating a BackgroundChild or
+ // the worker's JSContext initialization failing, call
+ // WorkerPrivate::RunLoopNeverRan() to set the Worker to the correct
+ // status, which means "Dead," to forbid WorkerThreadRunnable dispatching.
+ if (!runLoopRan) {
+ mWorkerPrivate->RunLoopNeverRan();
+ }
mWorkerPrivate->ResetWorkerPrivateInWorkerThread();
});
@@ -2117,6 +2121,7 @@ WorkerThreadPrimaryRunnable::Run() {
}
failureCleanup.release();
+ runLoopRan = true;
{
PROFILER_SET_JS_CONTEXT(cx);
diff --git a/dom/workers/ScriptLoader.cpp b/dom/workers/ScriptLoader.cpp
index 9dcb1d0c9a..b841a6e357 100644
--- a/dom/workers/ScriptLoader.cpp
+++ b/dom/workers/ScriptLoader.cpp
@@ -1641,7 +1641,8 @@ void ScriptLoaderRunnable::DispatchProcessPendingRequests() {
Span<RefPtr<ThreadSafeRequestHandle>>{maybeRangeToExecute->first,
maybeRangeToExecute->second});
- if (!runnable->Dispatch() && mScriptLoader->mSyncLoopTarget) {
+ if (!runnable->Dispatch(mWorkerRef->Private()) &&
+ mScriptLoader->mSyncLoopTarget) {
MOZ_ASSERT(false, "This should never fail!");
}
}
@@ -1651,8 +1652,7 @@ ScriptExecutorRunnable::ScriptExecutorRunnable(
WorkerScriptLoader* aScriptLoader, WorkerPrivate* aWorkerPrivate,
nsISerialEventTarget* aSyncLoopTarget,
Span<RefPtr<ThreadSafeRequestHandle>> aLoadedRequests)
- : MainThreadWorkerSyncRunnable(aWorkerPrivate, aSyncLoopTarget,
- "ScriptExecutorRunnable"),
+ : MainThreadWorkerSyncRunnable(aSyncLoopTarget, "ScriptExecutorRunnable"),
mScriptLoader(aScriptLoader),
mLoadedRequests(aLoadedRequests) {}
diff --git a/dom/workers/Worker.cpp b/dom/workers/Worker.cpp
index 88df53b877..6d85948d96 100644
--- a/dom/workers/Worker.cpp
+++ b/dom/workers/Worker.cpp
@@ -126,7 +126,7 @@ void Worker::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
JS::ProfilingCategoryPair::DOM, flags);
RefPtr<MessageEventRunnable> runnable =
- new MessageEventRunnable(mWorkerPrivate, WorkerRunnable::WorkerThread);
+ new MessageEventRunnable(mWorkerPrivate);
JS::CloneDataPolicy clonePolicy;
// DedicatedWorkers are always part of the same agent cluster.
@@ -157,7 +157,7 @@ void Worker::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
// The worker could have closed between the time we entered this function and
// checked ParentStatusProtected and now, which could cause the dispatch to
// fail.
- Unused << NS_WARN_IF(!runnable->Dispatch());
+ Unused << NS_WARN_IF(!runnable->Dispatch(mWorkerPrivate));
}
void Worker::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
@@ -191,7 +191,7 @@ void Worker::PostEventWithOptions(JSContext* aCx,
return;
}
- Unused << NS_WARN_IF(!aRunnable->Dispatch());
+ Unused << NS_WARN_IF(!aRunnable->Dispatch(mWorkerPrivate));
}
void Worker::Terminate() {
diff --git a/dom/workers/WorkerCSPEventListener.cpp b/dom/workers/WorkerCSPEventListener.cpp
index c693928486..fa17ba0dcb 100644
--- a/dom/workers/WorkerCSPEventListener.cpp
+++ b/dom/workers/WorkerCSPEventListener.cpp
@@ -19,8 +19,7 @@ namespace {
class WorkerCSPEventRunnable final : public MainThreadWorkerRunnable {
public:
WorkerCSPEventRunnable(WorkerPrivate* aWorkerPrivate, const nsAString& aJSON)
- : MainThreadWorkerRunnable(aWorkerPrivate, "WorkerCSPEventRunnable"),
- mJSON(aJSON) {}
+ : MainThreadWorkerRunnable("WorkerCSPEventRunnable"), mJSON(aJSON) {}
private:
bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) {
@@ -83,7 +82,7 @@ WorkerCSPEventListener::OnCSPViolationEvent(const nsAString& aJSON) {
if (NS_IsMainThread()) {
RefPtr<WorkerCSPEventRunnable> runnable =
new WorkerCSPEventRunnable(workerPrivate, aJSON);
- runnable->Dispatch();
+ runnable->Dispatch(workerPrivate);
return NS_OK;
}
diff --git a/dom/workers/WorkerDebugger.cpp b/dom/workers/WorkerDebugger.cpp
index a3e0af7a38..43cccf1102 100644
--- a/dom/workers/WorkerDebugger.cpp
+++ b/dom/workers/WorkerDebugger.cpp
@@ -37,7 +37,7 @@ class DebuggerMessageEventRunnable final : public WorkerDebuggerRunnable {
public:
DebuggerMessageEventRunnable(WorkerPrivate* aWorkerPrivate,
const nsAString& aMessage)
- : WorkerDebuggerRunnable(aWorkerPrivate, "DebuggerMessageEventRunnable"),
+ : WorkerDebuggerRunnable("DebuggerMessageEventRunnable"),
mMessage(aMessage) {}
private:
@@ -74,7 +74,7 @@ class CompileDebuggerScriptRunnable final : public WorkerDebuggerRunnable {
CompileDebuggerScriptRunnable(WorkerPrivate* aWorkerPrivate,
const nsAString& aScriptURL,
const mozilla::Encoding* aDocumentEncoding)
- : WorkerDebuggerRunnable(aWorkerPrivate, "CompileDebuggerScriptRunnable"),
+ : WorkerDebuggerRunnable("CompileDebuggerScriptRunnable"),
mScriptURL(aScriptURL),
mDocumentEncoding(aDocumentEncoding) {}
@@ -376,7 +376,7 @@ WorkerDebugger::Initialize(const nsAString& aURL) {
RefPtr<CompileDebuggerScriptRunnable> runnable =
new CompileDebuggerScriptRunnable(mWorkerPrivate, aURL,
aDocumentEncoding);
- if (!runnable->Dispatch()) {
+ if (!runnable->Dispatch(mWorkerPrivate)) {
return NS_ERROR_FAILURE;
}
@@ -396,7 +396,7 @@ WorkerDebugger::PostMessageMoz(const nsAString& aMessage) {
RefPtr<DebuggerMessageEventRunnable> runnable =
new DebuggerMessageEventRunnable(mWorkerPrivate, aMessage);
- if (!runnable->Dispatch()) {
+ if (!runnable->Dispatch(mWorkerPrivate)) {
return NS_ERROR_FAILURE;
}
diff --git a/dom/workers/WorkerDocumentListener.cpp b/dom/workers/WorkerDocumentListener.cpp
index cdbe30e072..a5285f7ad9 100644
--- a/dom/workers/WorkerDocumentListener.cpp
+++ b/dom/workers/WorkerDocumentListener.cpp
@@ -52,11 +52,10 @@ void WorkerDocumentListener::OnVisible(bool aVisible) {
return;
}
- class VisibleRunnable final : public WorkerRunnable {
+ class VisibleRunnable final : public WorkerThreadRunnable {
public:
VisibleRunnable(WorkerPrivate* aWorkerPrivate, bool aVisible)
- : WorkerRunnable(aWorkerPrivate, "VisibleRunnable"),
- mVisible(aVisible) {}
+ : WorkerThreadRunnable("VisibleRunnable"), mVisible(aVisible) {}
bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) {
WorkerGlobalScope* scope = aWorkerPrivate->GlobalScope();
@@ -70,7 +69,7 @@ void WorkerDocumentListener::OnVisible(bool aVisible) {
};
auto runnable = MakeRefPtr<VisibleRunnable>(mWorkerRef->Private(), aVisible);
- runnable->Dispatch();
+ runnable->Dispatch(mWorkerRef->Private());
}
void WorkerDocumentListener::SetListening(uint64_t aWindowID, bool aListen) {
diff --git a/dom/workers/WorkerError.cpp b/dom/workers/WorkerError.cpp
index 1b75599211..4fe9b237fe 100644
--- a/dom/workers/WorkerError.cpp
+++ b/dom/workers/WorkerError.cpp
@@ -62,13 +62,13 @@ namespace mozilla::dom {
namespace {
-class ReportErrorRunnable final : public WorkerDebuggeeRunnable {
+class ReportErrorRunnable final : public WorkerParentDebuggeeRunnable {
UniquePtr<WorkerErrorReport> mReport;
public:
ReportErrorRunnable(WorkerPrivate* aWorkerPrivate,
UniquePtr<WorkerErrorReport> aReport)
- : WorkerDebuggeeRunnable(aWorkerPrivate, "ReportErrorRunnable"),
+ : WorkerParentDebuggeeRunnable("ReportErrorRunnable"),
mReport(std::move(aReport)) {}
private:
@@ -138,7 +138,7 @@ class ReportErrorRunnable final : public WorkerDebuggeeRunnable {
}
};
-class ReportGenericErrorRunnable final : public WorkerDebuggeeRunnable {
+class ReportGenericErrorRunnable final : public WorkerParentDebuggeeRunnable {
public:
static void CreateAndDispatch(WorkerPrivate* aWorkerPrivate) {
MOZ_ASSERT(aWorkerPrivate);
@@ -146,12 +146,12 @@ class ReportGenericErrorRunnable final : public WorkerDebuggeeRunnable {
RefPtr<ReportGenericErrorRunnable> runnable =
new ReportGenericErrorRunnable(aWorkerPrivate);
- runnable->Dispatch();
+ runnable->Dispatch(aWorkerPrivate);
}
private:
explicit ReportGenericErrorRunnable(WorkerPrivate* aWorkerPrivate)
- : WorkerDebuggeeRunnable(aWorkerPrivate, "ReportGenericErrorRunnable") {
+ : WorkerParentDebuggeeRunnable("ReportGenericErrorRunnable") {
aWorkerPrivate->AssertIsOnWorkerThread();
}
@@ -354,7 +354,7 @@ void WorkerErrorReport::ReportError(
if (aWorkerPrivate) {
RefPtr<ReportErrorRunnable> runnable =
new ReportErrorRunnable(aWorkerPrivate, std::move(aReport));
- runnable->Dispatch();
+ runnable->Dispatch(aWorkerPrivate);
return;
}
diff --git a/dom/workers/WorkerEventTarget.cpp b/dom/workers/WorkerEventTarget.cpp
index cb58b4f8ed..94ba7a8097 100644
--- a/dom/workers/WorkerEventTarget.cpp
+++ b/dom/workers/WorkerEventTarget.cpp
@@ -34,8 +34,7 @@ class WrappedControlRunnable final : public WorkerControlRunnable {
public:
WrappedControlRunnable(WorkerPrivate* aWorkerPrivate,
nsCOMPtr<nsIRunnable>&& aInner)
- : WorkerControlRunnable(aWorkerPrivate, "WrappedControlRunnable",
- WorkerThread),
+ : WorkerControlRunnable("WrappedControlRunnable"),
mInner(std::move(aInner)) {}
virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
@@ -119,7 +118,7 @@ WorkerEventTarget::Dispatch(already_AddRefed<nsIRunnable> aRunnable,
RefPtr<WorkerRunnable> r =
mWorkerPrivate->MaybeWrapAsWorkerRunnable(runnable.forget());
- if (r->Dispatch()) {
+ if (r->Dispatch(mWorkerPrivate)) {
return NS_OK;
}
runnable = std::move(r);
@@ -134,7 +133,7 @@ WorkerEventTarget::Dispatch(already_AddRefed<nsIRunnable> aRunnable,
("WorkerEventTarget::Dispatch [%p] Wrapped runnable as control "
"runnable(%p)",
this, r.get()));
- if (!r->Dispatch()) {
+ if (!r->Dispatch(mWorkerPrivate)) {
LOGV(
("WorkerEventTarget::Dispatch [%p] Dispatch as control runnable(%p) "
"fail",
diff --git a/dom/workers/WorkerNavigator.cpp b/dom/workers/WorkerNavigator.cpp
index 1be36bc7d4..0f9dada965 100644
--- a/dom/workers/WorkerNavigator.cpp
+++ b/dom/workers/WorkerNavigator.cpp
@@ -227,6 +227,8 @@ StorageManager* WorkerNavigator::Storage() {
MOZ_ASSERT(global);
mStorageManager = new StorageManager(global);
+
+ workerPrivate->NotifyStorageKeyUsed();
}
return mStorageManager;
diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp
index df248acda4..d9bf764312 100644
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -180,19 +180,20 @@ inline UniquePtrComparator<T> GetUniquePtrComparator(
// This class is used to wrap any runnables that the worker receives via the
// nsIEventTarget::Dispatch() method (either from NS_DispatchToCurrentThread or
// from the worker's EventTarget).
-class ExternalRunnableWrapper final : public WorkerRunnable {
+class ExternalRunnableWrapper final : public WorkerThreadRunnable {
nsCOMPtr<nsIRunnable> mWrappedRunnable;
public:
ExternalRunnableWrapper(WorkerPrivate* aWorkerPrivate,
nsIRunnable* aWrappedRunnable)
- : WorkerRunnable(aWorkerPrivate, "ExternalRunnableWrapper", WorkerThread),
+ : WorkerThreadRunnable("ExternalRunnableWrapper"),
mWrappedRunnable(aWrappedRunnable) {
MOZ_ASSERT(aWorkerPrivate);
MOZ_ASSERT(aWrappedRunnable);
}
- NS_INLINE_DECL_REFCOUNTING_INHERITED(ExternalRunnableWrapper, WorkerRunnable)
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(ExternalRunnableWrapper,
+ WorkerThreadRunnable)
private:
~ExternalRunnableWrapper() = default;
@@ -249,8 +250,7 @@ class WorkerFinishedRunnable final : public WorkerControlRunnable {
public:
WorkerFinishedRunnable(WorkerPrivate* aWorkerPrivate,
WorkerPrivate* aFinishedWorker)
- : WorkerControlRunnable(aWorkerPrivate, "WorkerFinishedRunnable",
- WorkerThread),
+ : WorkerControlRunnable("WorkerFinishedRunnable"),
mFinishedWorker(aFinishedWorker) {
aFinishedWorker->IncreaseWorkerFinishedRunnableCount();
}
@@ -337,8 +337,7 @@ class CompileScriptRunnable final : public WorkerDebuggeeRunnable {
UniquePtr<SerializedStackHolder> aOriginStack,
const nsAString& aScriptURL,
const mozilla::Encoding* aDocumentEncoding)
- : WorkerDebuggeeRunnable(aWorkerPrivate, "CompileScriptRunnable",
- WorkerThread),
+ : WorkerDebuggeeRunnable("CompileScriptRunnable"),
mScriptURL(aScriptURL),
mDocumentEncoding(aDocumentEncoding),
mOriginStack(aOriginStack.release()) {}
@@ -436,7 +435,7 @@ class CompileScriptRunnable final : public WorkerDebuggeeRunnable {
if (!aRunResult) {
aWorkerPrivate->CloseInternal();
}
- WorkerRunnable::PostRun(aCx, aWorkerPrivate, aRunResult);
+ WorkerThreadRunnable::PostRun(aCx, aWorkerPrivate, aRunResult);
}
};
@@ -445,8 +444,7 @@ class NotifyRunnable final : public WorkerControlRunnable {
public:
NotifyRunnable(WorkerPrivate* aWorkerPrivate, WorkerStatus aStatus)
- : WorkerControlRunnable(aWorkerPrivate, "NotifyRunnable", WorkerThread),
- mStatus(aStatus) {
+ : WorkerControlRunnable("NotifyRunnable"), mStatus(aStatus) {
MOZ_ASSERT(aStatus == Closing || aStatus == Canceling ||
aStatus == Killing);
}
@@ -474,7 +472,7 @@ class NotifyRunnable final : public WorkerControlRunnable {
class FreezeRunnable final : public WorkerControlRunnable {
public:
explicit FreezeRunnable(WorkerPrivate* aWorkerPrivate)
- : WorkerControlRunnable(aWorkerPrivate, "FreezeRunnable", WorkerThread) {}
+ : WorkerControlRunnable("FreezeRunnable") {}
private:
virtual bool WorkerRun(JSContext* aCx,
@@ -486,7 +484,7 @@ class FreezeRunnable final : public WorkerControlRunnable {
class ThawRunnable final : public WorkerControlRunnable {
public:
explicit ThawRunnable(WorkerPrivate* aWorkerPrivate)
- : WorkerControlRunnable(aWorkerPrivate, "ThawRunnable", WorkerThread) {}
+ : WorkerControlRunnable("ThawRunnable") {}
private:
virtual bool WorkerRun(JSContext* aCx,
@@ -500,9 +498,8 @@ class PropagateStorageAccessPermissionGrantedRunnable final
public:
explicit PropagateStorageAccessPermissionGrantedRunnable(
WorkerPrivate* aWorkerPrivate)
- : WorkerControlRunnable(aWorkerPrivate,
- "PropagateStorageAccessPermissionGrantedRunnable",
- WorkerThread) {}
+ : WorkerControlRunnable(
+ "PropagateStorageAccessPermissionGrantedRunnable") {}
private:
bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
@@ -511,7 +508,7 @@ class PropagateStorageAccessPermissionGrantedRunnable final
}
};
-class ReportErrorToConsoleRunnable final : public WorkerRunnable {
+class ReportErrorToConsoleRunnable final : public WorkerParentThreadRunnable {
const char* mMessage;
const nsTArray<nsString> mParams;
@@ -529,7 +526,7 @@ class ReportErrorToConsoleRunnable final : public WorkerRunnable {
if (aWorkerPrivate) {
RefPtr<ReportErrorToConsoleRunnable> runnable =
new ReportErrorToConsoleRunnable(aWorkerPrivate, aMessage, aParams);
- runnable->Dispatch();
+ runnable->Dispatch(aWorkerPrivate);
return;
}
@@ -543,8 +540,7 @@ class ReportErrorToConsoleRunnable final : public WorkerRunnable {
ReportErrorToConsoleRunnable(WorkerPrivate* aWorkerPrivate,
const char* aMessage,
const nsTArray<nsString>& aParams)
- : WorkerRunnable(aWorkerPrivate, "ReportErrorToConsoleRunnable",
- ParentThread),
+ : WorkerParentThreadRunnable("ReportErrorToConsoleRunnable"),
mMessage(aMessage),
mParams(aParams.Clone()) {}
@@ -565,14 +561,13 @@ class ReportErrorToConsoleRunnable final : public WorkerRunnable {
}
};
-class RunExpiredTimoutsRunnable final : public WorkerRunnable,
+class RunExpiredTimoutsRunnable final : public WorkerThreadRunnable,
public nsITimerCallback {
public:
NS_DECL_ISUPPORTS_INHERITED
explicit RunExpiredTimoutsRunnable(WorkerPrivate* aWorkerPrivate)
- : WorkerRunnable(aWorkerPrivate, "RunExpiredTimoutsRunnable",
- WorkerThread) {}
+ : WorkerThreadRunnable("RunExpiredTimoutsRunnable") {}
private:
~RunExpiredTimoutsRunnable() = default;
@@ -599,17 +594,16 @@ class RunExpiredTimoutsRunnable final : public WorkerRunnable,
Notify(nsITimer* aTimer) override { return Run(); }
};
-NS_IMPL_ISUPPORTS_INHERITED(RunExpiredTimoutsRunnable, WorkerRunnable,
+NS_IMPL_ISUPPORTS_INHERITED(RunExpiredTimoutsRunnable, WorkerThreadRunnable,
nsITimerCallback)
-class DebuggerImmediateRunnable final : public WorkerRunnable {
+class DebuggerImmediateRunnable final : public WorkerThreadRunnable {
RefPtr<dom::Function> mHandler;
public:
explicit DebuggerImmediateRunnable(WorkerPrivate* aWorkerPrivate,
dom::Function& aHandler)
- : WorkerRunnable(aWorkerPrivate, "DebuggerImmediateRunnable",
- WorkerThread),
+ : WorkerThreadRunnable("DebuggerImmediateRunnable"),
mHandler(&aHandler) {}
private:
@@ -670,8 +664,7 @@ class UpdateContextOptionsRunnable final : public WorkerControlRunnable {
public:
UpdateContextOptionsRunnable(WorkerPrivate* aWorkerPrivate,
const JS::ContextOptions& aContextOptions)
- : WorkerControlRunnable(aWorkerPrivate, "UpdateContextOptionsRunnable",
- WorkerThread),
+ : WorkerControlRunnable("UpdateContextOptionsRunnable"),
mContextOptions(aContextOptions) {}
private:
@@ -682,13 +675,13 @@ class UpdateContextOptionsRunnable final : public WorkerControlRunnable {
}
};
-class UpdateLanguagesRunnable final : public WorkerRunnable {
+class UpdateLanguagesRunnable final : public WorkerThreadRunnable {
nsTArray<nsString> mLanguages;
public:
UpdateLanguagesRunnable(WorkerPrivate* aWorkerPrivate,
const nsTArray<nsString>& aLanguages)
- : WorkerRunnable(aWorkerPrivate, "UpdateLanguagesRunnable"),
+ : WorkerThreadRunnable("UpdateLanguagesRunnable"),
mLanguages(aLanguages.Clone()) {}
virtual bool WorkerRun(JSContext* aCx,
@@ -707,9 +700,7 @@ class UpdateJSWorkerMemoryParameterRunnable final
UpdateJSWorkerMemoryParameterRunnable(WorkerPrivate* aWorkerPrivate,
JSGCParamKey aKey,
Maybe<uint32_t> aValue)
- : WorkerControlRunnable(aWorkerPrivate,
- "UpdateJSWorkerMemoryParameterRunnable",
- WorkerThread),
+ : WorkerControlRunnable("UpdateJSWorkerMemoryParameterRunnable"),
mValue(aValue),
mKey(aKey) {}
@@ -729,8 +720,7 @@ class UpdateGCZealRunnable final : public WorkerControlRunnable {
public:
UpdateGCZealRunnable(WorkerPrivate* aWorkerPrivate, uint8_t aGCZeal,
uint32_t aFrequency)
- : WorkerControlRunnable(aWorkerPrivate, "UpdateGCZealRunnable",
- WorkerThread),
+ : WorkerControlRunnable("UpdateGCZealRunnable"),
mGCZeal(aGCZeal),
mFrequency(aFrequency) {}
@@ -748,9 +738,7 @@ class SetLowMemoryStateRunnable final : public WorkerControlRunnable {
public:
SetLowMemoryStateRunnable(WorkerPrivate* aWorkerPrivate, bool aState)
- : WorkerControlRunnable(aWorkerPrivate, "SetLowMemoryStateRunnable",
- WorkerThread),
- mState(aState) {}
+ : WorkerControlRunnable("SetLowMemoryStateRunnable"), mState(aState) {}
private:
virtual bool WorkerRun(JSContext* aCx,
@@ -767,8 +755,7 @@ class GarbageCollectRunnable final : public WorkerControlRunnable {
public:
GarbageCollectRunnable(WorkerPrivate* aWorkerPrivate, bool aShrinking,
bool aCollectChildren)
- : WorkerControlRunnable(aWorkerPrivate, "GarbageCollectRunnable",
- WorkerThread),
+ : WorkerControlRunnable("GarbageCollectRunnable"),
mShrinking(aShrinking),
mCollectChildren(aCollectChildren) {}
@@ -802,8 +789,7 @@ class CycleCollectRunnable final : public WorkerControlRunnable {
public:
CycleCollectRunnable(WorkerPrivate* aWorkerPrivate, bool aCollectChildren)
- : WorkerControlRunnable(aWorkerPrivate, "CycleCollectRunnable",
- WorkerThread),
+ : WorkerControlRunnable("CycleCollectRunnable"),
mCollectChildren(aCollectChildren) {}
bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
@@ -812,10 +798,10 @@ class CycleCollectRunnable final : public WorkerControlRunnable {
}
};
-class OfflineStatusChangeRunnable final : public WorkerRunnable {
+class OfflineStatusChangeRunnable final : public WorkerThreadRunnable {
public:
OfflineStatusChangeRunnable(WorkerPrivate* aWorkerPrivate, bool aIsOffline)
- : WorkerRunnable(aWorkerPrivate, "OfflineStatusChangeRunnable"),
+ : WorkerThreadRunnable("OfflineStatusChangeRunnable"),
mIsOffline(aIsOffline) {}
bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
@@ -830,8 +816,7 @@ class OfflineStatusChangeRunnable final : public WorkerRunnable {
class MemoryPressureRunnable final : public WorkerControlRunnable {
public:
explicit MemoryPressureRunnable(WorkerPrivate* aWorkerPrivate)
- : WorkerControlRunnable(aWorkerPrivate, "MemoryPressureRunnable",
- WorkerThread) {}
+ : WorkerControlRunnable("MemoryPressureRunnable") {}
bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
aWorkerPrivate->MemoryPressureInternal();
@@ -859,11 +844,10 @@ PRThread* PRThreadFromThread(nsIThread* aThread) {
// called. This runnable is executed on the parent process in order to cancel
// the current runnable. It uses a normal WorkerDebuggeeRunnable in order to be
// sure that all the pending WorkerDebuggeeRunnables are executed before this.
-class CancelingOnParentRunnable final : public WorkerDebuggeeRunnable {
+class CancelingOnParentRunnable final : public WorkerParentDebuggeeRunnable {
public:
explicit CancelingOnParentRunnable(WorkerPrivate* aWorkerPrivate)
- : WorkerDebuggeeRunnable(aWorkerPrivate, "CancelingOnParentRunnable",
- ParentThread) {}
+ : WorkerParentDebuggeeRunnable("CancelingOnParentRunnable") {}
bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
aWorkerPrivate->Cancel();
@@ -873,12 +857,10 @@ class CancelingOnParentRunnable final : public WorkerDebuggeeRunnable {
// A runnable to cancel the worker from the parent process.
class CancelingWithTimeoutOnParentRunnable final
- : public WorkerControlRunnable {
+ : public WorkerParentControlRunnable {
public:
explicit CancelingWithTimeoutOnParentRunnable(WorkerPrivate* aWorkerPrivate)
- : WorkerControlRunnable(aWorkerPrivate,
- "CancelingWithTimeoutOnParentRunnable",
- ParentThread) {}
+ : WorkerParentControlRunnable("CancelingWithTimeoutOnParentRunnable") {}
bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
aWorkerPrivate->AssertIsOnParentThread();
@@ -926,7 +908,7 @@ class CancelingRunnable final : public Runnable {
// Now we can cancel the this worker from the parent process.
RefPtr<CancelingOnParentRunnable> r =
new CancelingOnParentRunnable(workerPrivate);
- r->Dispatch();
+ r->Dispatch(workerPrivate);
return NS_OK;
}
@@ -1252,7 +1234,7 @@ WorkerPrivate::MemoryReporter::CollectReports(
aAnonymize, path);
}
- if (!runnable->Dispatch()) {
+ if (!runnable->Dispatch(mWorkerPrivate)) {
return NS_ERROR_UNEXPECTED;
}
@@ -1262,7 +1244,7 @@ WorkerPrivate::MemoryReporter::CollectReports(
WorkerPrivate::MemoryReporter::CollectReportsRunnable::CollectReportsRunnable(
WorkerPrivate* aWorkerPrivate, nsIHandleReportCallback* aHandleReport,
nsISupports* aHandlerData, bool aAnonymize, const nsACString& aPath)
- : MainThreadWorkerControlRunnable(aWorkerPrivate),
+ : MainThreadWorkerControlRunnable("CollectReportsRunnable"),
mFinishCollectRunnable(new FinishCollectRunnable(
aHandleReport, aHandlerData, aAnonymize, aPath)),
mAnonymize(aAnonymize) {}
@@ -1559,8 +1541,48 @@ void WorkerPrivate::Traverse(nsCycleCollectionTraversalCallback& aCb) {
nsresult WorkerPrivate::Dispatch(already_AddRefed<WorkerRunnable> aRunnable,
nsIEventTarget* aSyncLoopTarget) {
// May be called on any thread!
+ RefPtr<WorkerRunnable> runnable(aRunnable);
+
+ LOGV(("WorkerPrivate::Dispatch [%p] runnable %p", this, runnable.get()));
+ if (!aSyncLoopTarget) {
+ // Dispatch control runnable
+ if (runnable->IsControlRunnable()) {
+ return DispatchControlRunnable(runnable.forget());
+ }
+
+ // Dispatch debugger runnable
+ if (runnable->IsDebuggerRunnable()) {
+ return DispatchDebuggerRunnable(runnable.forget());
+ }
+ }
MutexAutoLock lock(mMutex);
- return DispatchLockHeld(std::move(aRunnable), aSyncLoopTarget, lock);
+ return DispatchLockHeld(runnable.forget(), aSyncLoopTarget, lock);
+}
+
+nsresult WorkerPrivate::DispatchToParent(
+ already_AddRefed<WorkerRunnable> aRunnable) {
+ RefPtr<WorkerRunnable> runnable(aRunnable);
+
+ LOGV(("WorkerPrivate::DispatchToParent [%p] runnable %p", this,
+ runnable.get()));
+
+ WorkerPrivate* parent = GetParent();
+ // Dispatch to parent worker
+ if (parent) {
+ if (runnable->IsControlRunnable()) {
+ return parent->DispatchControlRunnable(runnable.forget());
+ }
+ return parent->Dispatch(runnable.forget());
+ }
+
+ // Dispatch to main thread
+ if (runnable->IsDebuggeeRunnable()) {
+ RefPtr<WorkerParentDebuggeeRunnable> debuggeeRunnable =
+ runnable.forget().downcast<WorkerParentDebuggeeRunnable>();
+ return DispatchDebuggeeToMainThread(debuggeeRunnable.forget(),
+ NS_DISPATCH_NORMAL);
+ }
+ return DispatchToMainThread(runnable.forget());
}
nsresult WorkerPrivate::DispatchLockHeld(
@@ -1573,6 +1595,7 @@ nsresult WorkerPrivate::DispatchLockHeld(
MOZ_ASSERT_IF(aSyncLoopTarget, mThread);
+ // Dispatch normal worker runnable
if (mStatus == Dead || (!aSyncLoopTarget && ParentStatus() > Canceling)) {
NS_WARNING(
"A runnable was posted to a worker that is already shutting "
@@ -1592,7 +1615,10 @@ nsresult WorkerPrivate::DispatchLockHeld(
("WorkerPrivate::DispatchLockHeld [%p] runnable %p is queued in "
"mPreStartRunnables",
this, runnable.get()));
- mPreStartRunnables.AppendElement(runnable);
+ RefPtr<WorkerThreadRunnable> workerThreadRunnable =
+ static_cast<WorkerThreadRunnable*>(runnable.get());
+ workerThreadRunnable->mWorkerPrivateForPreStartCleaning = this;
+ mPreStartRunnables.AppendElement(workerThreadRunnable);
return NS_OK;
}
@@ -1654,10 +1680,10 @@ void WorkerPrivate::DisableDebugger() {
}
nsresult WorkerPrivate::DispatchControlRunnable(
- already_AddRefed<WorkerControlRunnable> aWorkerControlRunnable) {
+ already_AddRefed<WorkerRunnable> aWorkerRunnable) {
// May be called on any thread!
- RefPtr<WorkerControlRunnable> runnable(aWorkerControlRunnable);
- MOZ_ASSERT(runnable);
+ RefPtr<WorkerRunnable> runnable(aWorkerRunnable);
+ MOZ_ASSERT_DEBUG_OR_FUZZING(runnable && runnable->IsControlRunnable());
LOG(WorkerLog(), ("WorkerPrivate::DispatchControlRunnable [%p] runnable %p",
this, runnable.get()));
@@ -1769,6 +1795,8 @@ bool WorkerPrivate::Notify(WorkerStatus aStatus) {
mCancellationCallback = nullptr;
}
+ mParentRef->DropWorkerPrivate();
+
if (pending) {
#ifdef DEBUG
{
@@ -1803,7 +1831,7 @@ bool WorkerPrivate::Notify(WorkerStatus aStatus) {
}
RefPtr<NotifyRunnable> runnable = new NotifyRunnable(this, aStatus);
- return runnable->Dispatch();
+ return runnable->Dispatch(this);
}
bool WorkerPrivate::Freeze(const nsPIDOMWindowInner* aWindow) {
@@ -1841,7 +1869,7 @@ bool WorkerPrivate::Freeze(const nsPIDOMWindowInner* aWindow) {
DisableDebugger();
RefPtr<FreezeRunnable> runnable = new FreezeRunnable(this);
- return runnable->Dispatch();
+ return runnable->Dispatch(this);
}
bool WorkerPrivate::Thaw(const nsPIDOMWindowInner* aWindow) {
@@ -1883,7 +1911,7 @@ bool WorkerPrivate::Thaw(const nsPIDOMWindowInner* aWindow) {
EnableDebugger();
RefPtr<ThawRunnable> runnable = new ThawRunnable(this);
- return runnable->Dispatch();
+ return runnable->Dispatch(this);
}
void WorkerPrivate::ParentWindowPaused() {
@@ -1945,7 +1973,33 @@ void WorkerPrivate::PropagateStorageAccessPermissionGranted() {
RefPtr<PropagateStorageAccessPermissionGrantedRunnable> runnable =
new PropagateStorageAccessPermissionGrantedRunnable(this);
- Unused << NS_WARN_IF(!runnable->Dispatch());
+ Unused << NS_WARN_IF(!runnable->Dispatch(this));
+}
+
+void WorkerPrivate::NotifyStorageKeyUsed() {
+ AssertIsOnWorkerThread();
+
+ // Only notify once per global.
+ if (hasNotifiedStorageKeyUsed) {
+ return;
+ }
+ hasNotifiedStorageKeyUsed = true;
+
+ // Notify about storage access on the main thread.
+ RefPtr<StrongWorkerRef> strongRef =
+ StrongWorkerRef::Create(this, "WorkerPrivate::NotifyStorageKeyUsed");
+ if (!strongRef) {
+ return;
+ }
+ RefPtr<ThreadSafeWorkerRef> ref = new ThreadSafeWorkerRef(strongRef);
+ DispatchToMainThread(NS_NewRunnableFunction(
+ "WorkerPrivate::NotifyStorageKeyUsed", [ref = std::move(ref)] {
+ nsGlobalWindowInner* window =
+ nsGlobalWindowInner::Cast(ref->Private()->GetAncestorWindow());
+ if (window) {
+ window->MaybeNotifyStorageKeyUsed();
+ }
+ }));
}
bool WorkerPrivate::Close() {
@@ -1987,7 +2041,7 @@ void WorkerPrivate::UpdateContextOptions(
RefPtr<UpdateContextOptionsRunnable> runnable =
new UpdateContextOptionsRunnable(this, aContextOptions);
- if (!runnable->Dispatch()) {
+ if (!runnable->Dispatch(this)) {
NS_WARNING("Failed to update worker context options!");
}
}
@@ -1997,7 +2051,7 @@ void WorkerPrivate::UpdateLanguages(const nsTArray<nsString>& aLanguages) {
RefPtr<UpdateLanguagesRunnable> runnable =
new UpdateLanguagesRunnable(this, aLanguages);
- if (!runnable->Dispatch()) {
+ if (!runnable->Dispatch(this)) {
NS_WARNING("Failed to update worker languages!");
}
}
@@ -2016,7 +2070,7 @@ void WorkerPrivate::UpdateJSWorkerMemoryParameter(JSGCParamKey aKey,
if (changed) {
RefPtr<UpdateJSWorkerMemoryParameterRunnable> runnable =
new UpdateJSWorkerMemoryParameterRunnable(this, aKey, aValue);
- if (!runnable->Dispatch()) {
+ if (!runnable->Dispatch(this)) {
NS_WARNING("Failed to update memory parameter!");
}
}
@@ -2034,7 +2088,7 @@ void WorkerPrivate::UpdateGCZeal(uint8_t aGCZeal, uint32_t aFrequency) {
RefPtr<UpdateGCZealRunnable> runnable =
new UpdateGCZealRunnable(this, aGCZeal, aFrequency);
- if (!runnable->Dispatch()) {
+ if (!runnable->Dispatch(this)) {
NS_WARNING("Failed to update worker gczeal!");
}
}
@@ -2045,7 +2099,7 @@ void WorkerPrivate::SetLowMemoryState(bool aState) {
RefPtr<SetLowMemoryStateRunnable> runnable =
new SetLowMemoryStateRunnable(this, aState);
- if (!runnable->Dispatch()) {
+ if (!runnable->Dispatch(this)) {
NS_WARNING("Failed to set low memory state!");
}
}
@@ -2055,7 +2109,7 @@ void WorkerPrivate::GarbageCollect(bool aShrinking) {
RefPtr<GarbageCollectRunnable> runnable = new GarbageCollectRunnable(
this, aShrinking, /* aCollectChildren = */ true);
- if (!runnable->Dispatch()) {
+ if (!runnable->Dispatch(this)) {
NS_WARNING("Failed to GC worker!");
}
}
@@ -2065,7 +2119,7 @@ void WorkerPrivate::CycleCollect() {
RefPtr<CycleCollectRunnable> runnable =
new CycleCollectRunnable(this, /* aCollectChildren = */ true);
- if (!runnable->Dispatch()) {
+ if (!runnable->Dispatch(this)) {
NS_WARNING("Failed to CC worker!");
}
}
@@ -2075,7 +2129,7 @@ void WorkerPrivate::OfflineStatusChangeEvent(bool aIsOffline) {
RefPtr<OfflineStatusChangeRunnable> runnable =
new OfflineStatusChangeRunnable(this, aIsOffline);
- if (!runnable->Dispatch()) {
+ if (!runnable->Dispatch(this)) {
NS_WARNING("Failed to dispatch offline status change event!");
}
}
@@ -2118,7 +2172,7 @@ void WorkerPrivate::MemoryPressure() {
AssertIsOnParentThread();
RefPtr<MemoryPressureRunnable> runnable = new MemoryPressureRunnable(this);
- Unused << NS_WARN_IF(!runnable->Dispatch());
+ Unused << NS_WARN_IF(!runnable->Dispatch(this));
}
RefPtr<WorkerPrivate::JSMemoryUsagePromise> WorkerPrivate::GetJSMemoryUsage() {
@@ -2661,6 +2715,7 @@ already_AddRefed<WorkerPrivate> WorkerPrivate::Constructor(
// must keep ourself alive. We can now only be cleared by
// ClearSelfAndParentEventTargetRef().
worker->mSelfRef = worker;
+ worker->mParentRef = MakeRefPtr<WorkerParentRef>(worker);
worker->EnableDebugger();
@@ -2681,7 +2736,7 @@ already_AddRefed<WorkerPrivate> WorkerPrivate::Constructor(
RefPtr<CompileScriptRunnable> compiler = new CompileScriptRunnable(
worker, std::move(stack), aScriptURL, aDocumentEncoding);
- if (!compiler->Dispatch()) {
+ if (!compiler->Dispatch(worker)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
@@ -3134,7 +3189,7 @@ void WorkerPrivate::RunLoopNeverRan() {
// After mStatus is set to Dead there can be no more
// WorkerControlRunnables so no need to lock here.
if (!mControlQueue.IsEmpty()) {
- WorkerControlRunnable* runnable = nullptr;
+ WorkerRunnable* runnable = nullptr;
while (mControlQueue.Pop(runnable)) {
runnable->Cancel();
runnable->Release();
@@ -3146,8 +3201,6 @@ void WorkerPrivate::RunLoopNeverRan() {
// PerformanceStorageWorker which holds a WeakWorkerRef.
// Notify WeakWorkerRefs with Dead status.
NotifyWorkerRefs(Dead);
-
- ScheduleDeletion(WorkerPrivate::WorkerRan);
}
void WorkerPrivate::UnrootGlobalScopes() {
@@ -3355,7 +3408,7 @@ void WorkerPrivate::DoRunLoop(JSContext* aCx) {
("WorkerPrivate::DoRunLoop [%p] dropping control runnables in "
"Dead status",
this));
- WorkerControlRunnable* runnable = nullptr;
+ WorkerRunnable* runnable = nullptr;
while (mControlQueue.Pop(runnable)) {
runnable->Cancel();
runnable->Release();
@@ -3530,8 +3583,11 @@ nsresult WorkerPrivate::DispatchToMainThread(
}
nsresult WorkerPrivate::DispatchDebuggeeToMainThread(
- already_AddRefed<WorkerDebuggeeRunnable> aRunnable, uint32_t aFlags) {
- return mMainThreadDebuggeeEventTarget->Dispatch(std::move(aRunnable), aFlags);
+ already_AddRefed<WorkerRunnable> aRunnable, uint32_t aFlags) {
+ RefPtr<WorkerRunnable> debuggeeRunnable = std::move(aRunnable);
+ MOZ_ASSERT_DEBUG_OR_FUZZING(debuggeeRunnable->IsDebuggeeRunnable());
+ return mMainThreadDebuggeeEventTarget->Dispatch(debuggeeRunnable.forget(),
+ aFlags);
}
nsISerialEventTarget* WorkerPrivate::ControlEventTarget() {
@@ -3915,7 +3971,7 @@ void WorkerPrivate::ScheduleDeletion(WorkerRanOrNot aRanOrNot) {
if (WorkerPrivate* parent = GetParent()) {
RefPtr<WorkerFinishedRunnable> runnable =
new WorkerFinishedRunnable(parent, this);
- if (!runnable->Dispatch()) {
+ if (!runnable->Dispatch(parent)) {
NS_WARNING("Failed to dispatch runnable!");
}
} else {
@@ -4028,7 +4084,7 @@ WorkerPrivate::ProcessAllControlRunnablesLocked() {
auto result = ProcessAllControlRunnablesResult::Nothing;
for (;;) {
- WorkerControlRunnable* event;
+ WorkerRunnable* event;
if (!mControlQueue.Pop(event)) {
break;
}
@@ -4327,6 +4383,11 @@ void WorkerPrivate::RunShutdownTasks() {
mWorkerHybridEventTarget->ForgetWorkerPrivate(this);
}
+RefPtr<WorkerParentRef> WorkerPrivate::GetWorkerParentRef() const {
+ RefPtr<WorkerParentRef> ref(mParentRef);
+ return ref;
+}
+
void WorkerPrivate::AdjustNonblockingCCBackgroundActorCount(int32_t aCount) {
AssertIsOnWorkerThread();
auto data = mWorkerThreadAccessible.Access();
@@ -4704,7 +4765,7 @@ void WorkerPrivate::DispatchCancelingRunnable() {
this));
RefPtr<CancelingWithTimeoutOnParentRunnable> rr =
new CancelingWithTimeoutOnParentRunnable(this);
- rr->Dispatch();
+ rr->Dispatch(this);
}
void WorkerPrivate::ReportUseCounters() {
@@ -4848,8 +4909,8 @@ void WorkerPrivate::PostMessageToParent(
return;
}
- RefPtr<MessageEventRunnable> runnable =
- new MessageEventRunnable(this, WorkerRunnable::ParentThread);
+ RefPtr<MessageEventToParentRunnable> runnable =
+ new MessageEventToParentRunnable(this);
JS::CloneDataPolicy clonePolicy;
@@ -4866,7 +4927,7 @@ void WorkerPrivate::PostMessageToParent(
return;
}
- if (!runnable->Dispatch()) {
+ if (!runnable->Dispatch(this)) {
aRv = NS_ERROR_FAILURE;
}
}
@@ -4966,7 +5027,7 @@ void WorkerPrivate::SetDebuggerImmediate(dom::Function& aHandler,
RefPtr<DebuggerImmediateRunnable> runnable =
new DebuggerImmediateRunnable(this, aHandler);
- if (!runnable->Dispatch()) {
+ if (!runnable->Dispatch(this)) {
aRv.Throw(NS_ERROR_FAILURE);
}
}
@@ -5711,10 +5772,11 @@ void WorkerPrivate::ResetWorkerPrivateInWorkerThread() {
// Release the mutex before doomedThread.
MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mStatus == Dead);
MOZ_ASSERT(mThread);
- mThread->SetWorker(WorkerThreadFriendKey{}, nullptr);
+ mThread->ClearEventQueueAndWorker(WorkerThreadFriendKey{});
mThread.swap(doomedThread);
}
@@ -6229,5 +6291,32 @@ WorkerPrivate::AutoPushEventLoopGlobal::~AutoPushEventLoopGlobal() {
data->mCurrentEventLoopGlobal = std::move(mOldEventLoopGlobal);
}
+// -----------------------------------------------------------------------------
+// WorkerParentRef
+WorkerParentRef::WorkerParentRef(RefPtr<WorkerPrivate>& aWorkerPrivate)
+ : mWorkerPrivate(aWorkerPrivate) {
+ LOGV(("WorkerParentRef::WorkerParentRef [%p] aWorkerPrivate %p", this,
+ aWorkerPrivate.get()));
+ MOZ_ASSERT(mWorkerPrivate);
+ mWorkerPrivate->AssertIsOnParentThread();
+}
+
+const RefPtr<WorkerPrivate>& WorkerParentRef::Private() const {
+ if (mWorkerPrivate) {
+ mWorkerPrivate->AssertIsOnParentThread();
+ }
+ return mWorkerPrivate;
+}
+
+void WorkerParentRef::DropWorkerPrivate() {
+ LOGV(("WorkerParentRef::DropWorkerPrivate [%p]", this));
+ if (mWorkerPrivate) {
+ mWorkerPrivate->AssertIsOnParentThread();
+ mWorkerPrivate = nullptr;
+ }
+}
+
+WorkerParentRef::~WorkerParentRef() = default;
+
} // namespace dom
} // namespace mozilla
diff --git a/dom/workers/WorkerPrivate.h b/dom/workers/WorkerPrivate.h
index ce754ba9f6..e9aa976bb3 100644
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -85,6 +85,7 @@ class WorkerDebuggerGlobalScope;
class WorkerErrorReport;
class WorkerEventTarget;
class WorkerGlobalScope;
+class WorkerParentRef;
class WorkerRef;
class WorkerRunnable;
class WorkerDebuggeeRunnable;
@@ -590,7 +591,7 @@ class WorkerPrivate final
uint32_t aFlags = NS_DISPATCH_NORMAL);
nsresult DispatchDebuggeeToMainThread(
- already_AddRefed<WorkerDebuggeeRunnable> aRunnable,
+ already_AddRefed<WorkerRunnable> aRunnable,
uint32_t aFlags = NS_DISPATCH_NORMAL);
// Get an event target that will dispatch runnables as control runnables on
@@ -996,6 +997,8 @@ class WorkerPrivate final
void PropagateStorageAccessPermissionGranted();
+ void NotifyStorageKeyUsed();
+
void EnableDebugger();
void DisableDebugger();
@@ -1048,11 +1051,13 @@ class WorkerPrivate final
nsIEventTarget* aSyncLoopTarget = nullptr);
nsresult DispatchControlRunnable(
- already_AddRefed<WorkerControlRunnable> aWorkerControlRunnable);
+ already_AddRefed<WorkerRunnable> aWorkerRunnable);
nsresult DispatchDebuggerRunnable(
already_AddRefed<WorkerRunnable> aDebuggerRunnable);
+ nsresult DispatchToParent(already_AddRefed<WorkerRunnable> aRunnable);
+
bool IsOnParentThread() const;
#ifdef DEBUG
@@ -1169,6 +1174,8 @@ class WorkerPrivate final
// Worker thread only.
void AdjustNonblockingCCBackgroundActorCount(int32_t aCount);
+ RefPtr<WorkerParentRef> GetWorkerParentRef() const;
+
private:
WorkerPrivate(
WorkerPrivate* aParent, const nsAString& aScriptURL, bool aIsChromeWorker,
@@ -1366,7 +1373,7 @@ class WorkerPrivate final
WorkerDebugger* mDebugger;
- workerinternals::Queue<WorkerControlRunnable*, 4> mControlQueue;
+ workerinternals::Queue<WorkerRunnable*, 4> mControlQueue;
workerinternals::Queue<WorkerRunnable*, 4> mDebuggerQueue;
// Touched on multiple threads, protected with mMutex. Only modified on the
@@ -1618,6 +1625,12 @@ class WorkerPrivate final
// The flag indicates if the worke is idle for events in the main event loop.
bool mWorkerLoopIsIdle MOZ_GUARDED_BY(mMutex){false};
+
+ // This flag is used to ensure we only call NotifyStorageKeyUsed once per
+ // global.
+ bool hasNotifiedStorageKeyUsed{false};
+
+ RefPtr<WorkerParentRef> mParentRef;
};
class AutoSyncLoopHolder {
@@ -1658,6 +1671,42 @@ class AutoSyncLoopHolder {
}
};
+/**
+ * WorkerParentRef is a RefPtr<WorkerPrivate> wrapper for cross-thread access.
+ * WorkerPrivate needs to be accessed in multiple threads; for example,
+ * in WorkerParentThreadRunnable, the associated WorkerPrivate must be accessed
+ * in the worker thread when creating/dispatching and in the parent thread when
+ * executing. Unfortunately, RefPtr can not be used on this WorkerPrivate since
+ * it is not a thread-safe ref-counted object.
+ *
+ * Instead of using a raw pointer and a complicated mechanism to ensure the
+ * WorkerPrivate's accessibility. WorkerParentRef is used to resolve the
+ * problem. WorkerParentRef has a RefPtr<WorkerPrivate> mWorkerPrivate
+ * initialized on the parent thread when WorkerPrivate::Constructor().
+ * WorkerParentRef is a thread-safe ref-counted object that can be copied at
+ * any thread by WorkerPrivate::GetWorkerParentRef() and propagated to other
+ * threads. In the target thread, call WorkerParentRef::Private() to get the
+ * reference for WorkerPrivate or get a nullptr if the Worker has shut down.
+ *
+ * Since currently usage cases, WorkerParentRef::Private() will assert to be on
+ * the parent thread.
+ */
+class WorkerParentRef final {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WorkerParentRef);
+
+ explicit WorkerParentRef(RefPtr<WorkerPrivate>& aWorkerPrivate);
+
+ const RefPtr<WorkerPrivate>& Private() const;
+
+ void DropWorkerPrivate();
+
+ private:
+ ~WorkerParentRef();
+
+ RefPtr<WorkerPrivate> mWorkerPrivate;
+};
+
} // namespace dom
} // namespace mozilla
diff --git a/dom/workers/WorkerRef.cpp b/dom/workers/WorkerRef.cpp
index 9ba9841041..7935a7c67f 100644
--- a/dom/workers/WorkerRef.cpp
+++ b/dom/workers/WorkerRef.cpp
@@ -20,8 +20,7 @@ class ReleaseRefControlRunnable final : public WorkerControlRunnable {
public:
ReleaseRefControlRunnable(WorkerPrivate* aWorkerPrivate,
already_AddRefed<StrongWorkerRef> aRef)
- : WorkerControlRunnable(aWorkerPrivate, "ReleaseRefControlRunnable",
- WorkerThread),
+ : WorkerControlRunnable("ReleaseRefControlRunnable"),
mRef(std::move(aRef)) {
MOZ_ASSERT(mRef);
}
@@ -211,7 +210,7 @@ ThreadSafeWorkerRef::~ThreadSafeWorkerRef() {
WorkerPrivate* workerPrivate = mRef->mWorkerPrivate;
RefPtr<ReleaseRefControlRunnable> r =
new ReleaseRefControlRunnable(workerPrivate, mRef.forget());
- r->Dispatch();
+ r->Dispatch(workerPrivate);
return;
}
}
diff --git a/dom/workers/WorkerRef.h b/dom/workers/WorkerRef.h
index e41ef07bfd..f1dc4deb1b 100644
--- a/dom/workers/WorkerRef.h
+++ b/dom/workers/WorkerRef.h
@@ -174,6 +174,8 @@ class WeakWorkerRef final : public WorkerRef {
WorkerPrivate* GetUnsafePrivate() const;
private:
+ friend class ThreadSafeWeakWorkerRef;
+
explicit WeakWorkerRef(WorkerPrivate* aWorkerPrivate);
~WeakWorkerRef();
diff --git a/dom/workers/WorkerRunnable.cpp b/dom/workers/WorkerRunnable.cpp
index 14a3e5e3f9..05831c7cef 100644
--- a/dom/workers/WorkerRunnable.cpp
+++ b/dom/workers/WorkerRunnable.cpp
@@ -54,129 +54,17 @@ const nsIID kWorkerRunnableIID = {
} // namespace
#ifdef DEBUG
-WorkerRunnable::WorkerRunnable(WorkerPrivate* aWorkerPrivate, const char* aName,
- Target aTarget)
- : mWorkerPrivate(aWorkerPrivate),
- mTarget(aTarget),
+WorkerRunnable::WorkerRunnable(const char* aName)
# ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
- mName(aName),
-# endif
- mCallingCancelWithinRun(false) {
- LOG(("WorkerRunnable::WorkerRunnable [%p]", this));
- MOZ_ASSERT(aWorkerPrivate);
-}
-#endif
-
-bool WorkerRunnable::IsDebuggerRunnable() const { return false; }
-
-nsIGlobalObject* WorkerRunnable::DefaultGlobalObject() const {
- if (IsDebuggerRunnable()) {
- return mWorkerPrivate->DebuggerGlobalScope();
- } else {
- return mWorkerPrivate->GlobalScope();
- }
-}
-
-bool WorkerRunnable::PreDispatch(WorkerPrivate* aWorkerPrivate) {
-#ifdef DEBUG
- MOZ_ASSERT(aWorkerPrivate);
-
- switch (mTarget) {
- case ParentThread:
- aWorkerPrivate->AssertIsOnWorkerThread();
- break;
-
- case WorkerThread:
- aWorkerPrivate->AssertIsOnParentThread();
- break;
-
- default:
- MOZ_ASSERT_UNREACHABLE("Unknown behavior!");
- }
-#endif
- return true;
-}
-
-bool WorkerRunnable::Dispatch() {
- bool ok = PreDispatch(mWorkerPrivate);
- if (ok) {
- ok = DispatchInternal();
- }
- PostDispatch(mWorkerPrivate, ok);
- return ok;
+ : mName(aName) {
+ LOG(("WorkerRunnable::WorkerRunnable [%p] (%s)", this, mName));
}
-
-bool WorkerRunnable::DispatchInternal() {
- LOG(("WorkerRunnable::DispatchInternal [%p]", this));
- RefPtr<WorkerRunnable> runnable(this);
-
- if (mTarget == WorkerThread) {
- if (IsDebuggerRunnable()) {
- return NS_SUCCEEDED(
- mWorkerPrivate->DispatchDebuggerRunnable(runnable.forget()));
- } else {
- return NS_SUCCEEDED(mWorkerPrivate->Dispatch(runnable.forget()));
- }
- }
-
- MOZ_ASSERT(mTarget == ParentThread);
-
- if (WorkerPrivate* parent = mWorkerPrivate->GetParent()) {
- return NS_SUCCEEDED(parent->Dispatch(runnable.forget()));
- }
-
- if (IsDebuggeeRunnable()) {
- RefPtr<WorkerDebuggeeRunnable> debuggeeRunnable =
- runnable.forget().downcast<WorkerDebuggeeRunnable>();
- return NS_SUCCEEDED(mWorkerPrivate->DispatchDebuggeeToMainThread(
- debuggeeRunnable.forget(), NS_DISPATCH_NORMAL));
- }
-
- return NS_SUCCEEDED(mWorkerPrivate->DispatchToMainThread(runnable.forget()));
-}
-
-void WorkerRunnable::PostDispatch(WorkerPrivate* aWorkerPrivate,
- bool aDispatchResult) {
- MOZ_ASSERT(aWorkerPrivate);
-
-#ifdef DEBUG
- switch (mTarget) {
- case ParentThread:
- aWorkerPrivate->AssertIsOnWorkerThread();
- break;
-
- case WorkerThread:
- aWorkerPrivate->AssertIsOnParentThread();
- break;
-
- default:
- MOZ_ASSERT_UNREACHABLE("Unknown behavior!");
- }
-#endif
+# else
+{
+ LOG(("WorkerRunnable::WorkerRunnable [%p]", this));
}
-
-bool WorkerRunnable::PreRun(WorkerPrivate* aWorkerPrivate) { return true; }
-
-void WorkerRunnable::PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
- bool aRunResult) {
- MOZ_ASSERT(aCx);
- MOZ_ASSERT(aWorkerPrivate);
-
-#ifdef DEBUG
- switch (mTarget) {
- case ParentThread:
- aWorkerPrivate->AssertIsOnParentThread();
- break;
-
- case WorkerThread:
- aWorkerPrivate->AssertIsOnWorkerThread();
- break;
-
- default:
- MOZ_ASSERT_UNREACHABLE("Unknown behavior!");
- }
+# endif
#endif
-}
// static
WorkerRunnable* WorkerRunnable::FromRunnable(nsIRunnable* aRunnable) {
@@ -193,6 +81,20 @@ WorkerRunnable* WorkerRunnable::FromRunnable(nsIRunnable* aRunnable) {
return runnable;
}
+bool WorkerRunnable::Dispatch(WorkerPrivate* aWorkerPrivate) {
+ LOG(("WorkerRunnable::Dispatch [%p] aWorkerPrivate: %p", this,
+ aWorkerPrivate));
+ MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate);
+ bool ok = PreDispatch(aWorkerPrivate);
+ if (ok) {
+ ok = DispatchInternal(aWorkerPrivate);
+ }
+ PostDispatch(aWorkerPrivate, ok);
+ return ok;
+}
+
+NS_IMETHODIMP WorkerRunnable::Run() { return NS_OK; }
+
NS_IMPL_ADDREF(WorkerRunnable)
NS_IMPL_RELEASE(WorkerRunnable)
@@ -221,79 +123,83 @@ NS_INTERFACE_MAP_BEGIN(WorkerRunnable)
} else
NS_INTERFACE_MAP_END
-NS_IMETHODIMP
-WorkerRunnable::Run() {
- LOG(("WorkerRunnable::Run [%p]", this));
- bool targetIsWorkerThread = mTarget == WorkerThread;
+WorkerParentThreadRunnable::WorkerParentThreadRunnable(const char* aName)
+ : WorkerRunnable(aName) {
+ LOG(("WorkerParentThreadRunnable::WorkerParentThreadRunnable [%p]", this));
+}
+WorkerParentThreadRunnable::~WorkerParentThreadRunnable() = default;
+
+bool WorkerParentThreadRunnable::PreDispatch(WorkerPrivate* aWorkerPrivate) {
#ifdef DEBUG
- if (targetIsWorkerThread) {
- mWorkerPrivate->AssertIsOnWorkerThread();
- } else {
- MOZ_ASSERT(mTarget == ParentThread);
- mWorkerPrivate->AssertIsOnParentThread();
- }
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
#endif
+ return true;
+}
- if (targetIsWorkerThread && !mCallingCancelWithinRun &&
- mWorkerPrivate->CancelBeforeWorkerScopeConstructed()) {
- mCallingCancelWithinRun = true;
- Cancel();
- mCallingCancelWithinRun = false;
+bool WorkerParentThreadRunnable::DispatchInternal(
+ WorkerPrivate* aWorkerPrivate) {
+ LOG(("WorkerParentThreadRunnable::DispatchInternal [%p]", this));
+ mWorkerParentRef = aWorkerPrivate->GetWorkerParentRef();
+ RefPtr<WorkerParentThreadRunnable> runnable(this);
+ return NS_SUCCEEDED(aWorkerPrivate->DispatchToParent(runnable.forget()));
+}
+
+void WorkerParentThreadRunnable::PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult) {
+#ifdef DEBUG
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+#endif
+}
+
+bool WorkerParentThreadRunnable::PreRun(WorkerPrivate* aWorkerPrivate) {
+ return true;
+}
+
+void WorkerParentThreadRunnable::PostRun(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate,
+ bool aRunResult) {
+ MOZ_ASSERT(aCx);
+#ifdef DEBUG
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnParentThread();
+#endif
+}
+
+NS_IMETHODIMP
+WorkerParentThreadRunnable::Run() {
+ LOG(("WorkerParentThreadRunnable::Run [%p]", this));
+ RefPtr<WorkerPrivate> workerPrivate;
+ MOZ_ASSERT(mWorkerParentRef);
+ workerPrivate = mWorkerParentRef->Private();
+ if (!workerPrivate) {
+ NS_WARNING("Worker has already shut down!!!");
return NS_OK;
}
+#ifdef DEBUG
+ workerPrivate->AssertIsOnParentThread();
+#endif
- bool result = PreRun(mWorkerPrivate);
- if (!result) {
- MOZ_ASSERT(targetIsWorkerThread,
- "The only PreRun implementation that can fail is "
- "ScriptExecutorRunnable");
- mWorkerPrivate->AssertIsOnWorkerThread();
- MOZ_ASSERT(!JS_IsExceptionPending(mWorkerPrivate->GetJSContext()));
- // We can't enter a useful realm on the JSContext here; just pass it
- // in as-is.
- PostRun(mWorkerPrivate->GetJSContext(), mWorkerPrivate, false);
- return NS_ERROR_FAILURE;
- }
+ WorkerPrivate* parent = workerPrivate->GetParent();
+ bool isOnMainThread = !parent;
+ bool result = PreRun(workerPrivate);
+ MOZ_ASSERT(result);
+
+ LOG(("WorkerParentThreadRunnable::Run [%p] WorkerPrivate: %p, parent: %p",
+ this, workerPrivate.get(), parent));
// Track down the appropriate global, if any, to use for the AutoEntryScript.
nsCOMPtr<nsIGlobalObject> globalObject;
- bool isMainThread = !targetIsWorkerThread && !mWorkerPrivate->GetParent();
- MOZ_ASSERT(isMainThread == NS_IsMainThread());
- RefPtr<WorkerPrivate> kungFuDeathGrip;
- if (targetIsWorkerThread) {
- globalObject = mWorkerPrivate->GetCurrentEventLoopGlobal();
- if (!globalObject) {
- globalObject = DefaultGlobalObject();
- // Our worker thread may not be in a good state here if there is no
- // JSContext avaliable. The way this manifests itself is that
- // globalObject ends up null (though it's not clear to me how we can be
- // running runnables at all when DefaultGlobalObject() is returning
- // false!) and then when we try to init the AutoJSAPI either
- // CycleCollectedJSContext::Get() returns null or it has a null JSContext.
- // In any case, we used to have a check for
- // GetCurrentWorkerThreadJSContext() being non-null here and that seems to
- // avoid the problem, so let's keep doing that check even if we don't need
- // the JSContext here at all.
- if (NS_WARN_IF(!globalObject && !GetCurrentWorkerThreadJSContext())) {
- return NS_ERROR_FAILURE;
- }
- }
-
- // We may still not have a globalObject here: in the case of
- // CompileScriptRunnable, we don't actually create the global object until
- // we have the script data, which happens in a syncloop under
- // CompileScriptRunnable::WorkerRun, so we can't assert that it got created
- // in the PreRun call above.
+ if (isOnMainThread) {
+ MOZ_ASSERT(isOnMainThread == NS_IsMainThread());
+ globalObject = nsGlobalWindowInner::Cast(workerPrivate->GetWindow());
} else {
- kungFuDeathGrip = mWorkerPrivate;
- if (isMainThread) {
- globalObject = nsGlobalWindowInner::Cast(mWorkerPrivate->GetWindow());
- } else {
- globalObject = mWorkerPrivate->GetParent()->GlobalScope();
- }
+ MOZ_ASSERT(parent == GetCurrentThreadWorkerPrivate());
+ globalObject = parent->GlobalScope();
+ MOZ_DIAGNOSTIC_ASSERT(globalObject);
}
-
// We might run script as part of WorkerRun, so we need an AutoEntryScript.
// This is part of the HTML spec for workers at:
// http://www.whatwg.org/specs/web-apps/current-work/#run-a-worker
@@ -303,8 +209,9 @@ WorkerRunnable::Run() {
Maybe<mozilla::dom::AutoEntryScript> aes;
JSContext* cx;
AutoJSAPI* jsapi;
+
if (globalObject) {
- aes.emplace(globalObject, "Worker runnable", isMainThread);
+ aes.emplace(globalObject, "Worker parent thread runnable", isOnMainThread);
jsapi = aes.ptr();
cx = aes->cx();
} else {
@@ -315,7 +222,7 @@ WorkerRunnable::Run() {
}
// Note that we can't assert anything about
- // mWorkerPrivate->ParentEventTargetRef()->GetWrapper()
+ // workerPrivate->ParentEventTargetRef()->GetWrapper()
// existing, since it may in fact have been GCed (and we may be one of the
// runnables cleaning up the worker as a result).
@@ -325,17 +232,18 @@ WorkerRunnable::Run() {
// definitely have a globalObject. If it _is_ the main thread, globalObject
// can be null for workers started from JSMs or other non-window contexts,
// sadly.
- MOZ_ASSERT_IF(!targetIsWorkerThread && !isMainThread,
- mWorkerPrivate->IsDedicatedWorker() && globalObject);
+ MOZ_ASSERT_IF(!isOnMainThread,
+ workerPrivate->IsDedicatedWorker() && globalObject);
// If we're on the parent thread we might be in a null realm in the
// situation described above when globalObject is null. Make sure to enter
// the realm of the worker's reflector if there is one. There might
// not be one if we're just starting to compile the script for this worker.
Maybe<JSAutoRealm> ar;
- if (!targetIsWorkerThread && mWorkerPrivate->IsDedicatedWorker() &&
- mWorkerPrivate->ParentEventTargetRef()->GetWrapper()) {
- JSObject* wrapper = mWorkerPrivate->ParentEventTargetRef()->GetWrapper();
+ if (workerPrivate->IsDedicatedWorker() &&
+ workerPrivate->ParentEventTargetRef() &&
+ workerPrivate->ParentEventTargetRef()->GetWrapper()) {
+ JSObject* wrapper = workerPrivate->ParentEventTargetRef()->GetWrapper();
// If we're on the parent thread and have a reflector and a globalObject,
// then the realms of cx, globalObject, and the worker's reflector
@@ -359,12 +267,9 @@ WorkerRunnable::Run() {
}
MOZ_ASSERT(!jsapi->HasException());
- result = WorkerRun(cx, mWorkerPrivate);
+ result = WorkerRun(cx, workerPrivate);
jsapi->ReportException();
- // We can't even assert that this didn't create our global, since in the case
- // of CompileScriptRunnable it _does_.
-
// It would be nice to avoid passing a JSContext to PostRun, but in the case
// of ScriptExecutorRunnable we need to know the current compartment on the
// JSContext (the one we set up based on the global returned from PreRun) so
@@ -383,71 +288,226 @@ WorkerRunnable::Run() {
// this point; in the one case in which we could do that
// (CompileScriptRunnable) it actually doesn't matter which compartment we're
// in for PostRun.
- PostRun(cx, mWorkerPrivate, result);
+ PostRun(cx, workerPrivate, result);
MOZ_ASSERT(!jsapi->HasException());
return result ? NS_OK : NS_ERROR_FAILURE;
}
-nsresult WorkerRunnable::Cancel() {
- LOG(("WorkerRunnable::Cancel [%p]", this));
+nsresult WorkerParentThreadRunnable::Cancel() {
+ LOG(("WorkerParentThreadRunnable::Cancel [%p]", this));
return NS_OK;
}
-void WorkerDebuggerRunnable::PostDispatch(WorkerPrivate* aWorkerPrivate,
- bool aDispatchResult) {}
+WorkerParentControlRunnable::WorkerParentControlRunnable(const char* aName)
+ : WorkerParentThreadRunnable(aName) {}
-WorkerSyncRunnable::WorkerSyncRunnable(WorkerPrivate* aWorkerPrivate,
- nsIEventTarget* aSyncLoopTarget,
- const char* aName)
- : WorkerRunnable(aWorkerPrivate, aName, WorkerThread),
- mSyncLoopTarget(aSyncLoopTarget) {
-#ifdef DEBUG
- if (mSyncLoopTarget) {
- mWorkerPrivate->AssertValidSyncLoop(mSyncLoopTarget);
+WorkerParentControlRunnable::~WorkerParentControlRunnable() = default;
+
+nsresult WorkerParentControlRunnable::Cancel() {
+ LOG(("WorkerParentControlRunnable::Cancel [%p]", this));
+ if (NS_FAILED(Run())) {
+ NS_WARNING("WorkerParentControlRunnable::Run() failed.");
+ }
+ return NS_OK;
+}
+
+WorkerThreadRunnable::WorkerThreadRunnable(const char* aName)
+ : WorkerRunnable(aName), mCallingCancelWithinRun(false) {
+ LOG(("WorkerThreadRunnable::WorkerThreadRunnable [%p]", this));
+}
+
+nsIGlobalObject* WorkerThreadRunnable::DefaultGlobalObject(
+ WorkerPrivate* aWorkerPrivate) const {
+ MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate);
+ if (IsDebuggerRunnable()) {
+ return aWorkerPrivate->DebuggerGlobalScope();
}
+ return aWorkerPrivate->GlobalScope();
+}
+
+bool WorkerThreadRunnable::PreDispatch(WorkerPrivate* aWorkerPrivate) {
+ MOZ_ASSERT(aWorkerPrivate);
+#ifdef DEBUG
+ aWorkerPrivate->AssertIsOnParentThread();
#endif
+ return true;
}
-WorkerSyncRunnable::WorkerSyncRunnable(
- WorkerPrivate* aWorkerPrivate, nsCOMPtr<nsIEventTarget>&& aSyncLoopTarget,
- const char* aName)
- : WorkerRunnable(aWorkerPrivate, aName, WorkerThread),
- mSyncLoopTarget(std::move(aSyncLoopTarget)) {
+bool WorkerThreadRunnable::DispatchInternal(WorkerPrivate* aWorkerPrivate) {
+ LOG(("WorkerThreadRunnable::DispatchInternal [%p]", this));
+ RefPtr<WorkerThreadRunnable> runnable(this);
+ return NS_SUCCEEDED(aWorkerPrivate->Dispatch(runnable.forget()));
+}
+
+void WorkerThreadRunnable::PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult) {
+ MOZ_ASSERT(aWorkerPrivate);
#ifdef DEBUG
- if (mSyncLoopTarget) {
- mWorkerPrivate->AssertValidSyncLoop(mSyncLoopTarget);
+ aWorkerPrivate->AssertIsOnParentThread();
+#endif
+}
+
+bool WorkerThreadRunnable::PreRun(WorkerPrivate* aWorkerPrivate) {
+ return true;
+}
+
+void WorkerThreadRunnable::PostRun(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate,
+ bool aRunResult) {
+ MOZ_ASSERT(aCx);
+ MOZ_ASSERT(aWorkerPrivate);
+
+#ifdef DEBUG
+ aWorkerPrivate->AssertIsOnWorkerThread();
+#endif
+}
+
+NS_IMETHODIMP
+WorkerThreadRunnable::Run() {
+ LOG(("WorkerThreadRunnable::Run [%p]", this));
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ if (!workerPrivate && mWorkerPrivateForPreStartCleaning) {
+ workerPrivate = mWorkerPrivateForPreStartCleaning;
+ mWorkerPrivateForPreStartCleaning = nullptr;
}
+ MOZ_ASSERT_DEBUG_OR_FUZZING(workerPrivate);
+#ifdef DEBUG
+ workerPrivate->AssertIsOnWorkerThread();
#endif
+
+ if (!mCallingCancelWithinRun &&
+ workerPrivate->CancelBeforeWorkerScopeConstructed()) {
+ mCallingCancelWithinRun = true;
+ Cancel();
+ mCallingCancelWithinRun = false;
+ return NS_OK;
+ }
+
+ bool result = PreRun(workerPrivate);
+ if (!result) {
+ workerPrivate->AssertIsOnWorkerThread();
+ MOZ_ASSERT(!JS_IsExceptionPending(workerPrivate->GetJSContext()));
+ // We can't enter a useful realm on the JSContext here; just pass it
+ // in as-is.
+ PostRun(workerPrivate->GetJSContext(), workerPrivate, false);
+ return NS_ERROR_FAILURE;
+ }
+
+ // Track down the appropriate global, if any, to use for the AutoEntryScript.
+ nsCOMPtr<nsIGlobalObject> globalObject =
+ workerPrivate->GetCurrentEventLoopGlobal();
+ if (!globalObject) {
+ globalObject = DefaultGlobalObject(workerPrivate);
+ // Our worker thread may not be in a good state here if there is no
+ // JSContext avaliable. The way this manifests itself is that
+ // globalObject ends up null (though it's not clear to me how we can be
+ // running runnables at all when default globalObject(DebuggerGlobalScope
+ // for debugger runnable, and GlobalScope for normal runnables) is returning
+ // false!) and then when we try to init the AutoJSAPI either
+ // CycleCollectedJSContext::Get() returns null or it has a null JSContext.
+ // In any case, we used to have a check for
+ // GetCurrentWorkerThreadJSContext() being non-null here and that seems to
+ // avoid the problem, so let's keep doing that check even if we don't need
+ // the JSContext here at all.
+ if (NS_WARN_IF(!globalObject && !GetCurrentWorkerThreadJSContext())) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ // We might run script as part of WorkerRun, so we need an AutoEntryScript.
+ // This is part of the HTML spec for workers at:
+ // http://www.whatwg.org/specs/web-apps/current-work/#run-a-worker
+ // If we don't have a globalObject we have to use an AutoJSAPI instead, but
+ // this is OK as we won't be running script in these circumstances.
+ Maybe<mozilla::dom::AutoJSAPI> maybeJSAPI;
+ Maybe<mozilla::dom::AutoEntryScript> aes;
+ JSContext* cx;
+ AutoJSAPI* jsapi;
+ if (globalObject) {
+ aes.emplace(globalObject, "Worker runnable", false);
+ jsapi = aes.ptr();
+ cx = aes->cx();
+ } else {
+ maybeJSAPI.emplace();
+ maybeJSAPI->Init();
+ jsapi = maybeJSAPI.ptr();
+ cx = jsapi->cx();
+ }
+
+ MOZ_ASSERT(!jsapi->HasException());
+ result = WorkerRun(cx, workerPrivate);
+ jsapi->ReportException();
+
+ // We can't even assert that this didn't create our global, since in the case
+ // of CompileScriptRunnable it _does_.
+
+ // It would be nice to avoid passing a JSContext to PostRun, but in the case
+ // of ScriptExecutorRunnable we need to know the current compartment on the
+ // JSContext (the one we set up based on the global returned from PreRun) so
+ // that we can sanely do exception reporting. In particular, we want to make
+ // sure that we do our JS_SetPendingException while still in that compartment,
+ // because otherwise we might end up trying to create a cross-compartment
+ // wrapper when we try to move the JS exception from our runnable's
+ // ErrorResult to the JSContext, and that's not desirable in this case.
+ //
+ // We _could_ skip passing a JSContext here and then in
+ // ScriptExecutorRunnable::PostRun end up grabbing it from the WorkerPrivate
+ // and looking at its current compartment. But that seems like slightly weird
+ // action-at-a-distance...
+ //
+ // In any case, we do NOT try to change the compartment on the JSContext at
+ // this point; in the one case in which we could do that
+ // (CompileScriptRunnable) it actually doesn't matter which compartment we're
+ // in for PostRun.
+ PostRun(cx, workerPrivate, result);
+ MOZ_ASSERT(!jsapi->HasException());
+
+ return result ? NS_OK : NS_ERROR_FAILURE;
}
+nsresult WorkerThreadRunnable::Cancel() {
+ LOG(("WorkerThreadRunnable::Cancel [%p]", this));
+ return NS_OK;
+}
+
+void WorkerDebuggerRunnable::PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult) {}
+
+WorkerSyncRunnable::WorkerSyncRunnable(nsIEventTarget* aSyncLoopTarget,
+ const char* aName)
+ : WorkerThreadRunnable(aName), mSyncLoopTarget(aSyncLoopTarget) {}
+
+WorkerSyncRunnable::WorkerSyncRunnable(
+ nsCOMPtr<nsIEventTarget>&& aSyncLoopTarget, const char* aName)
+ : WorkerThreadRunnable(aName),
+ mSyncLoopTarget(std::move(aSyncLoopTarget)) {}
+
WorkerSyncRunnable::~WorkerSyncRunnable() = default;
-bool WorkerSyncRunnable::DispatchInternal() {
+bool WorkerSyncRunnable::DispatchInternal(WorkerPrivate* aWorkerPrivate) {
if (mSyncLoopTarget) {
+#ifdef DEBUG
+ aWorkerPrivate->AssertValidSyncLoop(mSyncLoopTarget);
+#endif
RefPtr<WorkerSyncRunnable> runnable(this);
return NS_SUCCEEDED(
mSyncLoopTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL));
}
- return WorkerRunnable::DispatchInternal();
+ return WorkerThreadRunnable::DispatchInternal(aWorkerPrivate);
}
void MainThreadWorkerSyncRunnable::PostDispatch(WorkerPrivate* aWorkerPrivate,
bool aDispatchResult) {}
MainThreadStopSyncLoopRunnable::MainThreadStopSyncLoopRunnable(
- WorkerPrivate* aWorkerPrivate, nsCOMPtr<nsIEventTarget>&& aSyncLoopTarget,
- nsresult aResult)
- : WorkerSyncRunnable(aWorkerPrivate, std::move(aSyncLoopTarget)),
- mResult(aResult) {
+ nsCOMPtr<nsIEventTarget>&& aSyncLoopTarget, nsresult aResult)
+ : WorkerSyncRunnable(std::move(aSyncLoopTarget)), mResult(aResult) {
LOG(("MainThreadStopSyncLoopRunnable::MainThreadStopSyncLoopRunnable [%p]",
this));
AssertIsOnMainThread();
-#ifdef DEBUG
- mWorkerPrivate->AssertValidSyncLoop(mSyncLoopTarget);
-#endif
}
nsresult MainThreadStopSyncLoopRunnable::Cancel() {
@@ -470,9 +530,12 @@ bool MainThreadStopSyncLoopRunnable::WorkerRun(JSContext* aCx,
return true;
}
-bool MainThreadStopSyncLoopRunnable::DispatchInternal() {
+bool MainThreadStopSyncLoopRunnable::DispatchInternal(
+ WorkerPrivate* aWorkerPrivate) {
MOZ_ASSERT(mSyncLoopTarget);
-
+#ifdef DEBUG
+ aWorkerPrivate->AssertValidSyncLoop(mSyncLoopTarget);
+#endif
RefPtr<MainThreadStopSyncLoopRunnable> runnable(this);
return NS_SUCCEEDED(
mSyncLoopTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL));
@@ -481,13 +544,8 @@ bool MainThreadStopSyncLoopRunnable::DispatchInternal() {
void MainThreadStopSyncLoopRunnable::PostDispatch(WorkerPrivate* aWorkerPrivate,
bool aDispatchResult) {}
-#ifdef DEBUG
-WorkerControlRunnable::WorkerControlRunnable(WorkerPrivate* aWorkerPrivate,
- const char* aName, Target aTarget)
- : WorkerRunnable(aWorkerPrivate, aName, aTarget) {
- MOZ_ASSERT(aWorkerPrivate);
-}
-#endif
+WorkerControlRunnable::WorkerControlRunnable(const char* aName)
+ : WorkerThreadRunnable(aName) {}
nsresult WorkerControlRunnable::Cancel() {
LOG(("WorkerControlRunnable::Cancel [%p]", this));
@@ -498,21 +556,6 @@ nsresult WorkerControlRunnable::Cancel() {
return NS_OK;
}
-bool WorkerControlRunnable::DispatchInternal() {
- RefPtr<WorkerControlRunnable> runnable(this);
-
- if (mTarget == WorkerThread) {
- return NS_SUCCEEDED(
- mWorkerPrivate->DispatchControlRunnable(runnable.forget()));
- }
-
- if (WorkerPrivate* parent = mWorkerPrivate->GetParent()) {
- return NS_SUCCEEDED(parent->DispatchControlRunnable(runnable.forget()));
- }
-
- return NS_SUCCEEDED(mWorkerPrivate->DispatchToMainThread(runnable.forget()));
-}
-
WorkerMainThreadRunnable::WorkerMainThreadRunnable(
WorkerPrivate* aWorkerPrivate, const nsACString& aTelemetryKey)
: mozilla::Runnable("dom::WorkerMainThreadRunnable"),
@@ -570,11 +613,10 @@ WorkerMainThreadRunnable::Run() {
bool runResult = MainThreadRun();
RefPtr<MainThreadStopSyncLoopRunnable> response =
- new MainThreadStopSyncLoopRunnable(mWorkerPrivate,
- std::move(mSyncLoopTarget),
+ new MainThreadStopSyncLoopRunnable(std::move(mSyncLoopTarget),
runResult ? NS_OK : NS_ERROR_FAILURE);
- MOZ_ALWAYS_TRUE(response->Dispatch());
+ MOZ_ALWAYS_TRUE(response->Dispatch(mWorkerPrivate));
return NS_OK;
}
@@ -633,15 +675,15 @@ void WorkerProxyToMainThreadRunnable::PostDispatchOnMainThread() {
RefPtr<WorkerProxyToMainThreadRunnable> mRunnable;
public:
- ReleaseRunnable(WorkerPrivate* aWorkerPrivate,
- WorkerProxyToMainThreadRunnable* aRunnable)
- : MainThreadWorkerControlRunnable(aWorkerPrivate),
+ explicit ReleaseRunnable(WorkerProxyToMainThreadRunnable* aRunnable)
+ : MainThreadWorkerControlRunnable("ReleaseRunnable"),
mRunnable(aRunnable) {
MOZ_ASSERT(aRunnable);
}
virtual nsresult Cancel() override {
- Unused << WorkerRun(nullptr, mWorkerPrivate);
+ MOZ_ASSERT(GetCurrentThreadWorkerPrivate());
+ Unused << WorkerRun(nullptr, GetCurrentThreadWorkerPrivate());
return NS_OK;
}
@@ -665,25 +707,10 @@ void WorkerProxyToMainThreadRunnable::PostDispatchOnMainThread() {
~ReleaseRunnable() = default;
};
- RefPtr<WorkerControlRunnable> runnable =
- new ReleaseRunnable(mWorkerRef->Private(), this);
- Unused << NS_WARN_IF(!runnable->Dispatch());
+ RefPtr<WorkerControlRunnable> runnable = new ReleaseRunnable(this);
+ Unused << NS_WARN_IF(!runnable->Dispatch(mWorkerRef->Private()));
}
void WorkerProxyToMainThreadRunnable::ReleaseWorker() { mWorkerRef = nullptr; }
-bool WorkerDebuggeeRunnable::PreDispatch(WorkerPrivate* aWorkerPrivate) {
- if (mTarget == ParentThread) {
- RefPtr<StrongWorkerRef> strongRef = StrongWorkerRef::Create(
- aWorkerPrivate, "WorkerDebuggeeRunnable::mSender");
- if (!strongRef) {
- return false;
- }
-
- mSender = new ThreadSafeWorkerRef(strongRef);
- }
-
- return WorkerRunnable::PreDispatch(aWorkerPrivate);
-}
-
} // namespace mozilla::dom
diff --git a/dom/workers/WorkerRunnable.h b/dom/workers/WorkerRunnable.h
index d133f11ea2..d404e12d8d 100644
--- a/dom/workers/WorkerRunnable.h
+++ b/dom/workers/WorkerRunnable.h
@@ -12,8 +12,11 @@
#include "MainThreadUtils.h"
#include "mozilla/Atomics.h"
#include "mozilla/RefPtr.h"
+#include "mozilla/ThreadSafeWeakPtr.h"
+#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/WorkerRef.h"
#include "mozilla/dom/WorkerStatus.h"
+#include "mozilla/dom/quota/CheckedUnsafePtr.h"
#include "nsCOMPtr.h"
#include "nsIRunnable.h"
#include "nsISupports.h"
@@ -31,53 +34,30 @@ class ErrorResult;
namespace dom {
-class WorkerPrivate;
+class Worker;
-// Use this runnable to communicate from the worker to its parent or vice-versa.
class WorkerRunnable : public nsIRunnable
#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
,
public nsINamed
#endif
{
- public:
- enum Target {
- // Target the main thread for top-level workers, otherwise target the
- // WorkerThread of the worker's parent.
- ParentThread,
-
- // Target the thread where the worker event loop runs.
- WorkerThread,
- };
-
protected:
- // The WorkerPrivate that this runnable is associated with.
- WorkerPrivate* mWorkerPrivate;
-
- // See above.
- Target mTarget;
-
#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
const char* mName = nullptr;
#endif
- private:
- // Whether or not Cancel() is currently being called from inside the Run()
- // method. Avoids infinite recursion when a subclass calls Run() from inside
- // Cancel(). Only checked and modified on the target thread.
- bool mCallingCancelWithinRun;
-
public:
NS_DECL_THREADSAFE_ISUPPORTS
#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
NS_DECL_NSINAMED
#endif
- virtual nsresult Cancel();
+ virtual nsresult Cancel() = 0;
// The return value is true if and only if both PreDispatch and
// DispatchInternal return true.
- bool Dispatch();
+ virtual bool Dispatch(WorkerPrivate* aWorkerPrivate);
// True if this runnable is handled by running JavaScript in some global that
// could possibly be a debuggee, and thus needs to be deferred when the target
@@ -90,41 +70,43 @@ class WorkerRunnable : public nsIRunnable
// support debugging the debugger server at the moment.
virtual bool IsDebuggeeRunnable() const { return false; }
+ // True if this runnable needs to be dispatched to
+ // WorkerPrivate::mControlEventTareget.
+ virtual bool IsControlRunnable() const { return false; }
+
+ // True if this runnable should be dispatched to the debugger queue,
+ // and false otherwise.
+ virtual bool IsDebuggerRunnable() const { return false; }
+
static WorkerRunnable* FromRunnable(nsIRunnable* aRunnable);
protected:
- WorkerRunnable(WorkerPrivate* aWorkerPrivate,
- const char* aName = "WorkerRunnable",
- Target aTarget = WorkerThread)
+ explicit WorkerRunnable(const char* aName = "WorkerRunnable")
#ifdef DEBUG
;
#else
- : mWorkerPrivate(aWorkerPrivate),
- mTarget(aTarget),
# ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
- mName(aName),
+ : mName(aName)
# endif
- mCallingCancelWithinRun(false) {
+ {
}
#endif
// This class is reference counted.
virtual ~WorkerRunnable() = default;
- // Returns true if this runnable should be dispatched to the debugger queue,
- // and false otherwise.
- virtual bool IsDebuggerRunnable() const;
-
- nsIGlobalObject* DefaultGlobalObject() const;
+ // Calling Run() directly is not supported. Just call Dispatch() and
+ // WorkerRun() will be called on the correct thread automatically.
+ NS_DECL_NSIRUNNABLE
// By default asserts that Dispatch() is being called on the right thread
- // (ParentThread if |mTarget| is WorkerThread).
- virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate);
+ virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) = 0;
// By default asserts that Dispatch() is being called on the right thread
- // (ParentThread if |mTarget| is WorkerThread).
virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
- bool aDispatchResult);
+ bool aDispatchResult) = 0;
+
+ virtual bool DispatchInternal(WorkerPrivate* aWorkerPrivate) = 0;
// May be implemented by subclasses if desired if they need to do some sort of
// setup before we try to set up our JSContext and compartment for real.
@@ -133,17 +115,19 @@ class WorkerRunnable : public nsIRunnable
//
// If false is returned, WorkerRun will not be called at all. PostRun will
// still be called, with false passed for aRunResult.
- virtual bool PreRun(WorkerPrivate* aWorkerPrivate);
+ virtual bool PreRun(WorkerPrivate* aWorkerPrivate) = 0;
// Must be implemented by subclasses. Called on the target thread. The return
// value will be passed to PostRun(). The JSContext passed in here comes from
- // an AutoJSAPI (or AutoEntryScript) that we set up on the stack. If
- // mTarget is ParentThread, it is in the compartment of
+ // an AutoJSAPI (or AutoEntryScript) that we set up on the stack.
+ //
+ // If the runnable is for parent thread, aCx is in the compartment of
// mWorkerPrivate's reflector (i.e. the worker object in the parent thread),
// unless that reflector is null, in which case it's in the compartment of the
// parent global (which is the compartment reflector would have been in), or
- // in the null compartment if there is no parent global. For other mTarget
- // values, we're running on the worker thread and aCx is in whatever
+ // in the null compartment if there is no parent global.
+ //
+ // For runnables on the worker thread, aCx is in whatever
// compartment GetCurrentWorkerThreadJSContext() was in when
// nsIRunnable::Run() got called. This is actually important for cases when a
// runnable spins a syncloop and wants everything that happens during the
@@ -166,21 +150,138 @@ class WorkerRunnable : public nsIRunnable
// exception on the JSContext and must not run script, because the incoming
// JSContext may be in the null compartment.
virtual void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
- bool aRunResult);
+ bool aRunResult) = 0;
+};
+
+class WorkerParentThreadRunnable : public WorkerRunnable {
+ public:
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(WorkerParentThreadRunnable,
+ WorkerRunnable)
+
+ virtual nsresult Cancel() override;
+
+ protected:
+ explicit WorkerParentThreadRunnable(
+ const char* aName = "WorkerParentThreadRunnable");
+
+ // This class is reference counted.
+ virtual ~WorkerParentThreadRunnable();
+
+ virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override;
+
+ virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult) override;
+
+ virtual bool PreRun(WorkerPrivate* aWorkerPrivate) override;
+
+ virtual bool WorkerRun(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate) override = 0;
+
+ virtual void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+ bool aRunResult) override;
+
+ virtual bool DispatchInternal(WorkerPrivate* aWorkerPrivate) final;
+
+ // Calling Run() directly is not supported. Just call Dispatch() and
+ // WorkerRun() will be called on the correct thread automatically.
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ RefPtr<WorkerParentRef> mWorkerParentRef;
+};
+
+class WorkerParentControlRunnable : public WorkerParentThreadRunnable {
+ friend class WorkerPrivate;
+
+ protected:
+ explicit WorkerParentControlRunnable(
+ const char* aName = "WorkerParentControlRunnable");
+
+ virtual ~WorkerParentControlRunnable();
+
+ nsresult Cancel() override;
+
+ public:
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(WorkerParentControlRunnable,
+ WorkerParentThreadRunnable)
+
+ private:
+ bool IsControlRunnable() const override { return true; }
+
+ // Should only be called by WorkerPrivate::DoRunLoop.
+ using WorkerParentThreadRunnable::Cancel;
+};
+
+class WorkerParentDebuggeeRunnable : public WorkerParentThreadRunnable {
+ protected:
+ explicit WorkerParentDebuggeeRunnable(
+ const char* aName = "WorkerParentDebuggeeRunnable")
+ : WorkerParentThreadRunnable(aName) {}
+
+ private:
+ // This override is deliberately private: it doesn't make sense to call it if
+ // we know statically that we are a WorkerDebuggeeRunnable.
+ bool IsDebuggeeRunnable() const override { return true; }
+};
+
+class WorkerThreadRunnable : public WorkerRunnable {
+ friend class WorkerPrivate;
+
+ public:
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(WorkerThreadRunnable, WorkerRunnable)
+
+ virtual nsresult Cancel() override;
+
+ protected:
+ explicit WorkerThreadRunnable(const char* aName = "WorkerThreadRunnable");
+
+ // This class is reference counted.
+ virtual ~WorkerThreadRunnable() = default;
+
+ nsIGlobalObject* DefaultGlobalObject(WorkerPrivate* aWorkerPrivate) const;
+
+ virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override;
+
+ virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult) override;
+
+ virtual bool PreRun(WorkerPrivate* aWorkerPrivate) override;
+
+ virtual bool WorkerRun(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate) override = 0;
+
+ virtual void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+ bool aRunResult) override;
- virtual bool DispatchInternal();
+ virtual bool DispatchInternal(WorkerPrivate* aWorkerPrivate) override;
// Calling Run() directly is not supported. Just call Dispatch() and
// WorkerRun() will be called on the correct thread automatically.
NS_DECL_NSIRUNNABLE
+
+ // Whether or not Cancel() is currently being called from inside the Run()
+ // method. Avoids infinite recursion when a subclass calls Run() from inside
+ // Cancel(). Only checked and modified on the target thread.
+ bool mCallingCancelWithinRun;
+
+ // If dispatching a WorkerThreadRunnable before Worker initialization complete
+ // in worker thread, which are in WorkerPrivate::mPreStartRunnables, when
+ // GetCurrentThreadWorkerPrivate() might get an invalid WorkerPrivate for
+ // WorkerThreadRunnable::Run() because it is in Worker's shutdown.
+ //
+ // This is specific for cleanup these pre-start runnables if the shutdown
+ // starts before Worker executes its event loop.
+ // This member is only set when the runnable is dispatched to
+ // WorkerPrivate::mPreStartRunnables. Any other cases to use this
+ // WorkerPrivate is always wrong.
+ CheckedUnsafePtr<WorkerPrivate> mWorkerPrivateForPreStartCleaning;
};
// This runnable is used to send a message to a worker debugger.
-class WorkerDebuggerRunnable : public WorkerRunnable {
+class WorkerDebuggerRunnable : public WorkerThreadRunnable {
protected:
- explicit WorkerDebuggerRunnable(WorkerPrivate* aWorkerPrivate,
- const char* aName = "WorkerDebuggerRunnable")
- : WorkerRunnable(aWorkerPrivate, aName, WorkerThread) {}
+ explicit WorkerDebuggerRunnable(const char* aName = "WorkerDebuggerRunnable")
+ : WorkerThreadRunnable(aName) {}
virtual ~WorkerDebuggerRunnable() = default;
@@ -198,23 +299,21 @@ class WorkerDebuggerRunnable : public WorkerRunnable {
};
// This runnable is used to send a message directly to a worker's sync loop.
-class WorkerSyncRunnable : public WorkerRunnable {
+class WorkerSyncRunnable : public WorkerThreadRunnable {
protected:
nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
// Passing null for aSyncLoopTarget is allowed and will result in the behavior
- // of a normal WorkerRunnable.
- WorkerSyncRunnable(WorkerPrivate* aWorkerPrivate,
- nsIEventTarget* aSyncLoopTarget,
- const char* aName = "WorkerSyncRunnable");
+ // of a normal WorkerThreadRunnable.
+ explicit WorkerSyncRunnable(nsIEventTarget* aSyncLoopTarget,
+ const char* aName = "WorkerSyncRunnable");
- WorkerSyncRunnable(WorkerPrivate* aWorkerPrivate,
- nsCOMPtr<nsIEventTarget>&& aSyncLoopTarget,
- const char* aName = "WorkerSyncRunnable");
+ explicit WorkerSyncRunnable(nsCOMPtr<nsIEventTarget>&& aSyncLoopTarget,
+ const char* aName = "WorkerSyncRunnable");
virtual ~WorkerSyncRunnable();
- virtual bool DispatchInternal() override;
+ virtual bool DispatchInternal(WorkerPrivate* aWorkerPrivate) override;
};
// This runnable is identical to WorkerSyncRunnable except it is meant to be
@@ -223,18 +322,18 @@ class WorkerSyncRunnable : public WorkerRunnable {
class MainThreadWorkerSyncRunnable : public WorkerSyncRunnable {
protected:
// Passing null for aSyncLoopTarget is allowed and will result in the behavior
- // of a normal WorkerRunnable.
- MainThreadWorkerSyncRunnable(
- WorkerPrivate* aWorkerPrivate, nsIEventTarget* aSyncLoopTarget,
+ // of a normal WorkerThreadRunnable.
+ explicit MainThreadWorkerSyncRunnable(
+ nsIEventTarget* aSyncLoopTarget,
const char* aName = "MainThreadWorkerSyncRunnable")
- : WorkerSyncRunnable(aWorkerPrivate, aSyncLoopTarget, aName) {
+ : WorkerSyncRunnable(aSyncLoopTarget, aName) {
AssertIsOnMainThread();
}
- MainThreadWorkerSyncRunnable(
- WorkerPrivate* aWorkerPrivate, nsCOMPtr<nsIEventTarget>&& aSyncLoopTarget,
+ explicit MainThreadWorkerSyncRunnable(
+ nsCOMPtr<nsIEventTarget>&& aSyncLoopTarget,
const char* aName = "MainThreadWorkerSyncRunnable")
- : WorkerSyncRunnable(aWorkerPrivate, std::move(aSyncLoopTarget), aName) {
+ : WorkerSyncRunnable(std::move(aSyncLoopTarget), aName) {
AssertIsOnMainThread();
}
@@ -254,42 +353,34 @@ class MainThreadWorkerSyncRunnable : public WorkerSyncRunnable {
// potentially running before previously queued runnables and perhaps even with
// other JS code executing on the stack. These runnables must not alter the
// state of the JS runtime and should only twiddle state values.
-class WorkerControlRunnable : public WorkerRunnable {
+class WorkerControlRunnable : public WorkerThreadRunnable {
friend class WorkerPrivate;
protected:
- WorkerControlRunnable(WorkerPrivate* aWorkerPrivate,
- const char* aName = "WorkerControlRunnable",
- Target aTarget = WorkerThread)
-#ifdef DEBUG
- ;
-#else
- : WorkerRunnable(aWorkerPrivate, aName, aTarget) {
- }
-#endif
+ explicit WorkerControlRunnable(const char* aName = "WorkerControlRunnable");
virtual ~WorkerControlRunnable() = default;
nsresult Cancel() override;
public:
- NS_INLINE_DECL_REFCOUNTING_INHERITED(WorkerControlRunnable, WorkerRunnable)
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(WorkerControlRunnable,
+ WorkerThreadRunnable)
private:
- virtual bool DispatchInternal() override;
+ bool IsControlRunnable() const override { return true; }
// Should only be called by WorkerPrivate::DoRunLoop.
- using WorkerRunnable::Cancel;
+ using WorkerThreadRunnable::Cancel;
};
-// A convenience class for WorkerRunnables that are originated on the main
+// A convenience class for WorkerThreadRunnables that are originated on the main
// thread.
-class MainThreadWorkerRunnable : public WorkerRunnable {
+class MainThreadWorkerRunnable : public WorkerThreadRunnable {
protected:
explicit MainThreadWorkerRunnable(
- WorkerPrivate* aWorkerPrivate,
const char* aName = "MainThreadWorkerRunnable")
- : WorkerRunnable(aWorkerPrivate, aName, WorkerThread) {
+ : WorkerThreadRunnable(aName) {
AssertIsOnMainThread();
}
@@ -311,9 +402,8 @@ class MainThreadWorkerRunnable : public WorkerRunnable {
class MainThreadWorkerControlRunnable : public WorkerControlRunnable {
protected:
explicit MainThreadWorkerControlRunnable(
- WorkerPrivate* aWorkerPrivate,
const char* aName = "MainThreadWorkerControlRunnable")
- : WorkerControlRunnable(aWorkerPrivate, aName, WorkerThread) {}
+ : WorkerControlRunnable(aName) {}
virtual ~MainThreadWorkerControlRunnable() = default;
@@ -328,17 +418,16 @@ class MainThreadWorkerControlRunnable : public WorkerControlRunnable {
}
};
-// A WorkerRunnable that should be dispatched from the worker to itself for
-// async tasks.
+// A WorkerThreadRunnable that should be dispatched from the worker to itself
+// for async tasks.
//
// Async tasks will almost always want to use this since
// a WorkerSameThreadRunnable keeps the Worker from being GCed.
-class WorkerSameThreadRunnable : public WorkerRunnable {
+class WorkerSameThreadRunnable : public WorkerThreadRunnable {
protected:
explicit WorkerSameThreadRunnable(
- WorkerPrivate* aWorkerPrivate,
const char* aName = "WorkerSameThreadRunnable")
- : WorkerRunnable(aWorkerPrivate, aName, WorkerThread) {}
+ : WorkerThreadRunnable(aName) {}
virtual ~WorkerSameThreadRunnable() = default;
@@ -347,7 +436,7 @@ class WorkerSameThreadRunnable : public WorkerRunnable {
virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
bool aDispatchResult) override;
- // We just delegate PostRun to WorkerRunnable, since it does exactly
+ // We just delegate PostRun to WorkerThreadRunnable, since it does exactly
// what we want.
};
@@ -424,8 +513,7 @@ class MainThreadStopSyncLoopRunnable : public WorkerSyncRunnable {
public:
// Passing null for aSyncLoopTarget is not allowed.
- MainThreadStopSyncLoopRunnable(WorkerPrivate* aWorkerPrivate,
- nsCOMPtr<nsIEventTarget>&& aSyncLoopTarget,
+ MainThreadStopSyncLoopRunnable(nsCOMPtr<nsIEventTarget>&& aSyncLoopTarget,
nsresult aResult);
// By default StopSyncLoopRunnables cannot be canceled since they could leave
@@ -447,7 +535,7 @@ class MainThreadStopSyncLoopRunnable : public WorkerSyncRunnable {
virtual bool WorkerRun(JSContext* aCx,
WorkerPrivate* aWorkerPrivate) override;
- bool DispatchInternal() final;
+ bool DispatchInternal(WorkerPrivate* aWorkerPrivate) final;
};
// Runnables handled by content JavaScript (MessageEventRunnable, JavaScript
@@ -470,31 +558,15 @@ class MainThreadStopSyncLoopRunnable : public WorkerSyncRunnable {
// from a top-level frozen worker to its parent window must not be delivered
// either, even as the main thread event loop continues to spin. Thus, freezing
// a top-level worker also pauses mMainThreadDebuggeeEventTarget.
-class WorkerDebuggeeRunnable : public WorkerRunnable {
+class WorkerDebuggeeRunnable : public WorkerThreadRunnable {
protected:
- WorkerDebuggeeRunnable(WorkerPrivate* aWorkerPrivate,
- const char* aName = "WorkerDebuggeeRunnable",
- Target aTarget = ParentThread)
- : WorkerRunnable(aWorkerPrivate, aName, aTarget) {}
-
- bool PreDispatch(WorkerPrivate* aWorkerPrivate) override;
+ explicit WorkerDebuggeeRunnable(const char* aName = "WorkerDebuggeeRunnable")
+ : WorkerThreadRunnable(aName) {}
private:
// This override is deliberately private: it doesn't make sense to call it if
// we know statically that we are a WorkerDebuggeeRunnable.
bool IsDebuggeeRunnable() const override { return true; }
-
- // Runnables sent upwards, to the content window or parent worker, must keep
- // their sender alive until they are delivered: they check back with the
- // sender in case it has been terminated after having dispatched the runnable
- // (in which case it should not be acted upon); and runnables sent to content
- // wait until delivery to determine the target window, since
- // WorkerPrivate::GetWindow may only be used on the main thread.
- //
- // Runnables sent downwards, from content to a worker or from a worker to a
- // child, keep the sender alive because they are WorkerThread
- // runnables, and so leave this null.
- RefPtr<ThreadSafeWorkerRef> mSender;
};
} // namespace dom
diff --git a/dom/workers/WorkerScope.cpp b/dom/workers/WorkerScope.cpp
index 2121a99cb3..cbb61c7055 100644
--- a/dom/workers/WorkerScope.cpp
+++ b/dom/workers/WorkerScope.cpp
@@ -468,6 +468,7 @@ already_AddRefed<CacheStorage> WorkerGlobalScope::GetCaches(ErrorResult& aRv) {
if (!mCacheStorage) {
mCacheStorage = CacheStorage::CreateOnWorker(cache::DEFAULT_NAMESPACE, this,
mWorkerPrivate, aRv);
+ mWorkerPrivate->NotifyStorageKeyUsed();
}
RefPtr<CacheStorage> ref = mCacheStorage;
@@ -735,25 +736,28 @@ already_AddRefed<IDBFactory> WorkerGlobalScope::GetIndexedDB(
if (!indexedDB) {
StorageAccess access = mWorkerPrivate->StorageAccess();
+ bool allowed = true;
if (access == StorageAccess::eDeny) {
NS_WARNING("IndexedDB is not allowed in this worker!");
- aErrorResult = NS_ERROR_DOM_SECURITY_ERR;
- return nullptr;
+ allowed = false;
}
if (ShouldPartitionStorage(access) &&
!StoragePartitioningEnabled(access,
mWorkerPrivate->CookieJarSettings())) {
NS_WARNING("IndexedDB is not allowed in this worker!");
- aErrorResult = NS_ERROR_DOM_SECURITY_ERR;
- return nullptr;
+ allowed = false;
}
- const PrincipalInfo& principalInfo =
- mWorkerPrivate->GetEffectiveStoragePrincipalInfo();
+ auto windowID = mWorkerPrivate->WindowID();
+
+ auto principalInfoPtr =
+ allowed ? MakeUnique<PrincipalInfo>(
+ mWorkerPrivate->GetEffectiveStoragePrincipalInfo())
+ : nullptr;
+ auto res = IDBFactory::CreateForWorker(this, std::move(principalInfoPtr),
+ windowID);
- auto res = IDBFactory::CreateForWorker(this, principalInfo,
- mWorkerPrivate->WindowID());
if (NS_WARN_IF(res.isErr())) {
aErrorResult = res.unwrapErr();
return nullptr;
@@ -763,6 +767,8 @@ already_AddRefed<IDBFactory> WorkerGlobalScope::GetIndexedDB(
mIndexedDB = indexedDB;
}
+ mWorkerPrivate->NotifyStorageKeyUsed();
+
return indexedDB.forget();
}
diff --git a/dom/workers/WorkerThread.cpp b/dom/workers/WorkerThread.cpp
index 14d944e4d3..2b051c0440 100644
--- a/dom/workers/WorkerThread.cpp
+++ b/dom/workers/WorkerThread.cpp
@@ -117,50 +117,54 @@ SafeRefPtr<WorkerThread> WorkerThread::Create(
void WorkerThread::SetWorker(const WorkerThreadFriendKey& /* aKey */,
WorkerPrivate* aWorkerPrivate) {
MOZ_ASSERT(PR_GetCurrentThread() == mThread);
+ MOZ_ASSERT(aWorkerPrivate);
- if (aWorkerPrivate) {
- {
- MutexAutoLock lock(mLock);
+ {
+ MutexAutoLock lock(mLock);
- MOZ_ASSERT(!mWorkerPrivate);
- MOZ_ASSERT(mAcceptingNonWorkerRunnables);
+ MOZ_ASSERT(!mWorkerPrivate);
+ MOZ_ASSERT(mAcceptingNonWorkerRunnables);
- mWorkerPrivate = aWorkerPrivate;
+ mWorkerPrivate = aWorkerPrivate;
#ifdef DEBUG
- mAcceptingNonWorkerRunnables = false;
+ mAcceptingNonWorkerRunnables = false;
#endif
- }
+ }
- mObserver = new Observer(aWorkerPrivate);
- MOZ_ALWAYS_SUCCEEDS(AddObserver(mObserver));
- } else {
- MOZ_ALWAYS_SUCCEEDS(RemoveObserver(mObserver));
- mObserver = nullptr;
+ mObserver = new Observer(aWorkerPrivate);
+ MOZ_ALWAYS_SUCCEEDS(AddObserver(mObserver));
+}
- {
- MutexAutoLock lock(mLock);
+void WorkerThread::ClearEventQueueAndWorker(
+ const WorkerThreadFriendKey& /* aKey */) {
+ MOZ_ASSERT(PR_GetCurrentThread() == mThread);
- MOZ_ASSERT(mWorkerPrivate);
- MOZ_ASSERT(!mAcceptingNonWorkerRunnables);
- // mOtherThreadsDispatchingViaEventTarget can still be non-zero here
- // because WorkerThread::Dispatch isn't atomic so a thread initiating
- // dispatch can have dispatched a runnable at this thread allowing us to
- // begin shutdown before that thread gets a chance to decrement
- // mOtherThreadsDispatchingViaEventTarget back to 0. So we need to wait
- // for that.
- while (mOtherThreadsDispatchingViaEventTarget) {
- mWorkerPrivateCondVar.Wait();
- }
- // Need to clean up the dispatched runnables if
- // mOtherThreadsDispatchingViaEventTarget was non-zero.
- if (NS_HasPendingEvents(nullptr)) {
- NS_ProcessPendingEvents(nullptr);
- }
+ MOZ_ALWAYS_SUCCEEDS(RemoveObserver(mObserver));
+ mObserver = nullptr;
+
+ {
+ MutexAutoLock lock(mLock);
+
+ MOZ_ASSERT(mWorkerPrivate);
+ MOZ_ASSERT(!mAcceptingNonWorkerRunnables);
+ // mOtherThreadsDispatchingViaEventTarget can still be non-zero here
+ // because WorkerThread::Dispatch isn't atomic so a thread initiating
+ // dispatch can have dispatched a runnable at this thread allowing us to
+ // begin shutdown before that thread gets a chance to decrement
+ // mOtherThreadsDispatchingViaEventTarget back to 0. So we need to wait
+ // for that.
+ while (mOtherThreadsDispatchingViaEventTarget) {
+ mWorkerPrivateCondVar.Wait();
+ }
+ // Need to clean up the dispatched runnables if
+ // mOtherThreadsDispatchingViaEventTarget was non-zero.
+ if (NS_HasPendingEvents(nullptr)) {
+ NS_ProcessPendingEvents(nullptr);
+ }
#ifdef DEBUG
- mAcceptingNonWorkerRunnables = true;
+ mAcceptingNonWorkerRunnables = true;
#endif
- mWorkerPrivate = nullptr;
- }
+ mWorkerPrivate = nullptr;
}
}
@@ -246,8 +250,13 @@ WorkerThread::Dispatch(already_AddRefed<nsIRunnable> aRunnable,
WorkerPrivate* workerPrivate = nullptr;
if (onWorkerThread) {
+ // If the mWorkerPrivate has already disconnected by
+ // WorkerPrivate::ResetWorkerPrivateInWorkerThread(), there is no chance
+ // that to execute this runnable. Return NS_ERROR_UNEXPECTED here.
+ if (!mWorkerPrivate) {
+ return NS_ERROR_UNEXPECTED;
+ }
// No need to lock here because it is only modified on this thread.
- MOZ_ASSERT(mWorkerPrivate);
mWorkerPrivate->AssertIsOnWorkerThread();
workerPrivate = mWorkerPrivate;
@@ -266,13 +275,7 @@ WorkerThread::Dispatch(already_AddRefed<nsIRunnable> aRunnable,
}
nsresult rv;
- if (runnable && onWorkerThread) {
- RefPtr<WorkerRunnable> workerRunnable =
- workerPrivate->MaybeWrapAsWorkerRunnable(runnable.forget());
- rv = nsThread::Dispatch(workerRunnable.forget(), NS_DISPATCH_NORMAL);
- } else {
- rv = nsThread::Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
- }
+ rv = nsThread::Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
if (!onWorkerThread && workerPrivate) {
// We need to wake the worker thread if we're not already on the right
diff --git a/dom/workers/WorkerThread.h b/dom/workers/WorkerThread.h
index 06624f60c1..8da085b51b 100644
--- a/dom/workers/WorkerThread.h
+++ b/dom/workers/WorkerThread.h
@@ -25,9 +25,6 @@ namespace dom {
class WorkerRunnable;
class WorkerPrivate;
-template <class>
-class WorkerPrivateParent;
-
namespace workerinternals {
class RuntimeService;
}
@@ -38,7 +35,6 @@ class RuntimeService;
class WorkerThreadFriendKey {
friend class workerinternals::RuntimeService;
friend class WorkerPrivate;
- friend class WorkerPrivateParent<WorkerPrivate>;
WorkerThreadFriendKey();
~WorkerThreadFriendKey();
@@ -76,6 +72,13 @@ class WorkerThread final : public nsThread {
void SetWorker(const WorkerThreadFriendKey& aKey,
WorkerPrivate* aWorkerPrivate);
+ // This method is used to decouple the connection with the WorkerPrivate which
+ // is set in SetWorker(). And it also clears all pending runnables on this
+ // WorkerThread.
+ // After decoupling, WorkerThreadRunnable can not run on this WorkerThread
+ // anymore, since WorkerPrivate is invalid.
+ void ClearEventQueueAndWorker(const WorkerThreadFriendKey& aKey);
+
nsresult DispatchPrimaryRunnable(const WorkerThreadFriendKey& aKey,
already_AddRefed<nsIRunnable> aRunnable);
diff --git a/dom/workers/remoteworkers/RemoteWorkerChild.cpp b/dom/workers/remoteworkers/RemoteWorkerChild.cpp
index bf63c1729c..9a4ca60287 100644
--- a/dom/workers/remoteworkers/RemoteWorkerChild.cpp
+++ b/dom/workers/remoteworkers/RemoteWorkerChild.cpp
@@ -106,12 +106,12 @@ NS_IMPL_RELEASE(SharedWorkerInterfaceRequestor)
NS_IMPL_QUERY_INTERFACE(SharedWorkerInterfaceRequestor, nsIInterfaceRequestor)
// Normal runnable because AddPortIdentifier() is going to exec JS code.
-class MessagePortIdentifierRunnable final : public WorkerRunnable {
+class MessagePortIdentifierRunnable final : public WorkerThreadRunnable {
public:
MessagePortIdentifierRunnable(WorkerPrivate* aWorkerPrivate,
RemoteWorkerChild* aActor,
const MessagePortIdentifier& aPortIdentifier)
- : WorkerRunnable(aWorkerPrivate, "MessagePortIdentifierRunnable"),
+ : WorkerThreadRunnable("MessagePortIdentifierRunnable"),
mActor(aActor),
mPortIdentifier(aPortIdentifier) {}
@@ -933,7 +933,7 @@ class RemoteWorkerChild::SharedWorkerOp : public RemoteWorkerChild::Op {
new MessagePortIdentifierRunnable(
workerPrivate, aOwner,
mOp.get_RemoteWorkerPortIdentifierOp().portIdentifier());
- if (NS_WARN_IF(!r->Dispatch())) {
+ if (NS_WARN_IF(!r->Dispatch(workerPrivate))) {
aOwner->ErrorPropagationDispatch(NS_ERROR_FAILURE);
}
} else if (mOp.type() == RemoteWorkerOp::TRemoteWorkerAddWindowIDOp) {
diff --git a/dom/workers/test/test_worker_interfaces.js b/dom/workers/test/test_worker_interfaces.js
index c53c0b2b0f..c3535fadba 100644
--- a/dom/workers/test/test_worker_interfaces.js
+++ b/dom/workers/test/test_worker_interfaces.js
@@ -46,6 +46,7 @@ let wasmGlobalInterfaces = [
{ name: "Function", insecureContext: true, nightly: true },
{ name: "Exception", insecureContext: true },
{ name: "Tag", insecureContext: true },
+ { name: "JSTag", insecureContext: true, earlyBetaOrEarlier: true },
{ name: "compile", insecureContext: true },
{ name: "compileStreaming", insecureContext: true },
{ name: "instantiate", insecureContext: true },
diff --git a/dom/xhr/XMLHttpRequestString.cpp b/dom/xhr/XMLHttpRequestString.cpp
index 8f9092243a..e0e68f3d2d 100644
--- a/dom/xhr/XMLHttpRequestString.cpp
+++ b/dom/xhr/XMLHttpRequestString.cpp
@@ -58,8 +58,7 @@ class XMLHttpRequestStringBuffer final {
// XXX: Bug 1408793 suggests encapsulating the following sequence within
// DOMString.
- nsStringBuffer* buf = nsStringBuffer::FromString(mData);
- if (buf) {
+ if (nsStringBuffer* buf = mData.GetStringBuffer()) {
// We have to use SetStringBuffer, because once we release our mutex mData
// can get mutated from some other thread while the DOMString is still
// alive.
diff --git a/dom/xhr/XMLHttpRequestWorker.cpp b/dom/xhr/XMLHttpRequestWorker.cpp
index 67cba876db..974b465aa6 100644
--- a/dom/xhr/XMLHttpRequestWorker.cpp
+++ b/dom/xhr/XMLHttpRequestWorker.cpp
@@ -290,8 +290,7 @@ class MainThreadProxyRunnable : public MainThreadWorkerSyncRunnable {
MainThreadProxyRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
const char* aName = "MainThreadProxyRunnable")
- : MainThreadWorkerSyncRunnable(aWorkerPrivate, aProxy->GetEventTarget(),
- aName),
+ : MainThreadWorkerSyncRunnable(aProxy->GetEventTarget(), aName),
mProxy(aProxy) {
MOZ_ASSERT(aProxy);
}
@@ -777,9 +776,9 @@ void Proxy::Teardown() {
if (mSyncLoopTarget) {
// We have an unclosed sync loop. Fix that now.
RefPtr<MainThreadStopSyncLoopRunnable> runnable =
- new MainThreadStopSyncLoopRunnable(
- mWorkerPrivate, std::move(mSyncLoopTarget), NS_ERROR_FAILURE);
- MOZ_ALWAYS_TRUE(runnable->Dispatch());
+ new MainThreadStopSyncLoopRunnable(std::move(mSyncLoopTarget),
+ NS_ERROR_FAILURE);
+ MOZ_ALWAYS_TRUE(runnable->Dispatch(mWorkerPrivate));
}
mOutstandingSendCount = 0;
@@ -886,7 +885,7 @@ Proxy::HandleEvent(Event* aEvent) {
}
if (runnable) {
- runnable->Dispatch();
+ runnable->Dispatch(mWorkerPrivate);
}
}
@@ -924,7 +923,7 @@ LoadStartDetectionRunnable::Run() {
RefPtr<ProxyCompleteRunnable> runnable =
new ProxyCompleteRunnable(mWorkerPrivate, mProxy, mChannelId);
- if (runnable->Dispatch()) {
+ if (runnable->Dispatch(mWorkerPrivate)) {
mProxy->mWorkerPrivate = nullptr;
mProxy->mSyncLoopTarget = nullptr;
mProxy->mOutstandingSendCount--;
diff --git a/dom/xul/nsXULElement.cpp b/dom/xul/nsXULElement.cpp
index 5bccc15f69..be798e309f 100644
--- a/dom/xul/nsXULElement.cpp
+++ b/dom/xul/nsXULElement.cpp
@@ -37,6 +37,7 @@
#include "mozilla/EventQueue.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/FlushType.h"
+#include "mozilla/FocusModel.h"
#include "mozilla/GlobalKeyListener.h"
#include "mozilla/HoldDropJSObjects.h"
#include "mozilla/MacroForEach.h"
@@ -50,6 +51,7 @@
#include "mozilla/StaticAnalysisFunctions.h"
#include "mozilla/StaticPrefs_javascript.h"
#include "mozilla/StaticPtr.h"
+#include "mozilla/FocusModel.h"
#include "mozilla/TaskController.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/URLExtraData.h"
@@ -373,12 +375,12 @@ static bool IsNonList(mozilla::dom::NodeInfo* aNodeInfo) {
}
nsXULElement::XULFocusability nsXULElement::GetXULFocusability(
- bool aWithMouse) {
+ IsFocusableFlags aFlags) {
#ifdef XP_MACOSX
// On Mac, mouse interactions only focus the element if it's a list,
// or if it's a remote target, since the remote target must handle
// the focus.
- if (aWithMouse && IsNonList(mNodeInfo) &&
+ if ((aFlags & IsFocusableFlags::WithMouse) && IsNonList(mNodeInfo) &&
!EventStateManager::IsTopLevelRemoteTarget(this)) {
return XULFocusability::NeverFocusable();
}
@@ -402,15 +404,12 @@ nsXULElement::XULFocusability nsXULElement::GetXULFocusability(
result.mForcedFocusable.emplace(true);
result.mForcedTabIndexIfFocusable.emplace(attrVal.value());
}
- if (xulControl && sTabFocusModelAppliesToXUL &&
- !(sTabFocusModel & eTabFocus_formElementsMask) && IsNonList(mNodeInfo)) {
+ if (xulControl && FocusModel::AppliesToXUL() &&
+ !FocusModel::IsTabFocusable(TabFocusableType::FormElements) &&
+ IsNonList(mNodeInfo)) {
// By default, the tab focus model doesn't apply to xul element on any
- // system but OS X. on OS X we're following it for UI elements (XUL) as
- // sTabFocusModel is based on "Full Keyboard Access" system setting (see
- // mac/nsILookAndFeel). both textboxes and list elements (i.e. trees and
- // list) should always be focusable (textboxes are handled as html:input)
- // For compatibility, we only do this for controls, otherwise elements
- // like <browser> cannot take this focus.
+ // system but OS X. For compatibility, we only do this for controls,
+ // otherwise elements like <browser> cannot take this focus.
result.mForcedTabIndexIfFocusable = Some(-1);
}
return result;
@@ -418,8 +417,8 @@ nsXULElement::XULFocusability nsXULElement::GetXULFocusability(
// XUL elements are not focusable unless explicitly opted-into it with
// -moz-user-focus: normal, or the tabindex attribute.
-Focusable nsXULElement::IsFocusableWithoutStyle(bool aWithMouse) {
- const auto focusability = GetXULFocusability(aWithMouse);
+Focusable nsXULElement::IsFocusableWithoutStyle(IsFocusableFlags aFlags) {
+ const auto focusability = GetXULFocusability(aFlags);
const bool focusable = focusability.mDefaultFocusable;
return {focusable,
focusable ? focusability.mForcedTabIndexIfFocusable.valueOr(-1) : -1};
diff --git a/dom/xul/nsXULElement.h b/dom/xul/nsXULElement.h
index 2681a14a9a..f14ec31505 100644
--- a/dom/xul/nsXULElement.h
+++ b/dom/xul/nsXULElement.h
@@ -401,8 +401,8 @@ class nsXULElement : public nsStyledElement {
return {false, mozilla::Some(false), mozilla::Some(-1)};
}
};
- XULFocusability GetXULFocusability(bool aWithMouse);
- Focusable IsFocusableWithoutStyle(bool aWithMouse) override;
+ XULFocusability GetXULFocusability(mozilla::IsFocusableFlags);
+ Focusable IsFocusableWithoutStyle(mozilla::IsFocusableFlags) override;
NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override;