diff --git a/Cargo.lock b/Cargo.lock index f824c507915e84..b30c86e989f346 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,6 +16,12 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" +[[package]] +name = "ahash" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e" + [[package]] name = "aho-corasick" version = "0.7.15" @@ -73,6 +79,15 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +[[package]] +name = "ash" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c69a8137596e84c22d57f3da1b5de1d4230b1742a710091c85f4d7ce50f00f38" +dependencies = [ + "libloading", +] + [[package]] name = "ast_node" version = "0.7.1" @@ -180,6 +195,21 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "bit-set" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitflags" version = "1.2.1" @@ -198,6 +228,12 @@ dependencies = [ "wyz", ] +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + [[package]] name = "block-buffer" version = "0.9.0" @@ -257,6 +293,9 @@ name = "cc" version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48" +dependencies = [ + "jobserver", +] [[package]] name = "cfg-if" @@ -270,6 +309,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + [[package]] name = "chrono" version = "0.4.19" @@ -298,6 +343,55 @@ dependencies = [ "vec_map", ] +[[package]] +name = "cocoa-foundation" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318" +dependencies = [ + "bitflags", + "block", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "copyless" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536" + +[[package]] +name = "core-foundation" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" + +[[package]] +name = "core-graphics-types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" +dependencies = [ + "bitflags", + "core-foundation", + "foreign-types", + "libc", +] + [[package]] name = "cpuid-bool" version = "0.1.2" @@ -343,6 +437,17 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "d3d12" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a60cceb22c7c53035f8980524fdc7f17cf49681a3c154e6757d30afbec6ec4" +dependencies = [ + "bitflags", + "libloading", + "winapi 0.3.9", +] + [[package]] name = "darling" version = "0.10.2" @@ -538,6 +643,7 @@ dependencies = [ "deno_crypto", "deno_fetch", "deno_web", + "deno_webgpu", "deno_websocket", "dlopen", "encoding_rs", @@ -579,6 +685,17 @@ dependencies = [ "serde", ] +[[package]] +name = "deno_webgpu" +version = "0.1.0" +dependencies = [ + "deno_core", + "serde", + "tokio", + "wgpu-core", + "wgpu-types", +] + [[package]] name = "deno_websocket" version = "0.5.0" @@ -840,6 +957,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.0.0" @@ -1059,6 +1191,205 @@ dependencies = [ "wasi 0.10.0+wasi-snapshot-preview1", ] +[[package]] +name = "gfx-auxil" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7b33ecf067f2117668d91c9b0f2e5f223ebd1ffec314caa2f3de27bb580186d" +dependencies = [ + "fxhash", + "gfx-hal", + "spirv_cross", +] + +[[package]] +name = "gfx-backend-dx11" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f851d03c2e8f117e3702bf41201a4fafa447d5cb1276d5375870ae7573d069dd" +dependencies = [ + "arrayvec", + "bitflags", + "gfx-auxil", + "gfx-hal", + "libloading", + "log", + "parking_lot", + "range-alloc", + "raw-window-handle", + "smallvec", + "spirv_cross", + "thunderdome", + "winapi 0.3.9", + "wio", +] + +[[package]] +name = "gfx-backend-dx12" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36dc6ba2b7647e2c2b27b8f74ff5ccdd53c703776588eee5b1de515fdcbd6bc9" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags", + "d3d12", + "gfx-auxil", + "gfx-hal", + "log", + "parking_lot", + "range-alloc", + "raw-window-handle", + "smallvec", + "spirv_cross", + "winapi 0.3.9", +] + +[[package]] +name = "gfx-backend-empty" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f07ef26a65954cfdd7b4c587f485100d1bb3b0bd6a51b02d817d6c87cca7a91" +dependencies = [ + "gfx-hal", + "log", + "raw-window-handle", +] + +[[package]] +name = "gfx-backend-gl" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17fd85420547bceb851fadb90f196f168abfc252d57528bd2d749db0d18b75f" +dependencies = [ + "arrayvec", + "bitflags", + "gfx-auxil", + "gfx-hal", + "glow", + "js-sys", + "khronos-egl", + "libloading", + "log", + "naga", + "parking_lot", + "raw-window-handle", + "spirv_cross", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gfx-backend-metal" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dc54b456ece69ef49f8893269ebf24ac70969ed34ba2719c3f3abcc8fbff14e" +dependencies = [ + "arrayvec", + "bitflags", + "block", + "cocoa-foundation", + "copyless", + "foreign-types", + "gfx-auxil", + "gfx-hal", + "log", + "metal", + "naga", + "objc", + "parking_lot", + "range-alloc", + "raw-window-handle", + "spirv_cross", + "storage-map", +] + +[[package]] +name = "gfx-backend-vulkan" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dabe88b1a5c91e0f969b441cc57e70364858066e4ba937deeb62065654ef9bd9" +dependencies = [ + "arrayvec", + "ash", + "byteorder", + "core-graphics-types", + "gfx-hal", + "inplace_it", + "log", + "naga", + "objc", + "parking_lot", + "raw-window-handle", + "smallvec", + "winapi 0.3.9", +] + +[[package]] +name = "gfx-hal" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1d9cc8d3b573dda62d0baca4f02e0209786e22c562caff001d77c389008781d" +dependencies = [ + "bitflags", + "naga", + "raw-window-handle", + "thiserror", +] + +[[package]] +name = "glow" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "072136d2c3783f3a92f131acb227bc806d3886278e2a4dc1e9990ec89ef9e70b" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gpu-alloc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7724b9aef57ea36d70faf54e0ee6265f86e41de16bed8333efdeab5b00e16b" +dependencies = [ + "bitflags", + "gpu-alloc-types", + "tracing", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54804d0d6bc9d7f26db4eaec1ad10def69b599315f487d32c334a80d1efe67a5" +dependencies = [ + "bitflags", +] + +[[package]] +name = "gpu-descriptor" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a70f1e87a3840ed6a3e99e02c2b861e4dbdf26f0d07e38f42ea5aff46cfce2" +dependencies = [ + "bitflags", + "gpu-descriptor-types", + "hashbrown", + "tracing", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "363e3677e55ad168fef68cf9de3a4a310b53124c5e784c53a1d70e92d23f2126" +dependencies = [ + "bitflags", +] + [[package]] name = "h2" version = "0.3.0" @@ -1084,6 +1415,9 @@ name = "hashbrown" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" +dependencies = [ + "ahash", +] [[package]] name = "heck" @@ -1246,6 +1580,12 @@ dependencies = [ "libc", ] +[[package]] +name = "inplace_it" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90953f308a79fe6d62a4643e51f848fbfddcd05975a38e69fdf4ab86a7baf7ca" + [[package]] name = "input_buffer" version = "0.4.0" @@ -1301,6 +1641,15 @@ version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" +[[package]] +name = "jobserver" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.47" @@ -1326,6 +1675,16 @@ dependencies = [ "winapi-build", ] +[[package]] +name = "khronos-egl" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8020ff3b84f9ac87461216ad0501bc09b33c1cbe17404d8ea405160fd164bab" +dependencies = [ + "libc", + "libloading", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -1351,6 +1710,16 @@ version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c" +[[package]] +name = "libloading" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "351a32417a12d5f7e82c368a66781e307834dae04c6ce0cd4456d52989229883" +dependencies = [ + "cfg-if 1.0.0", + "winapi 0.3.9", +] + [[package]] name = "linked-hash-map" version = "0.5.4" @@ -1435,6 +1804,15 @@ dependencies = [ "syn 1.0.60", ] +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + [[package]] name = "match_cfg" version = "0.1.0" @@ -1453,6 +1831,20 @@ version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" +[[package]] +name = "metal" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4598d719460ade24c7d91f335daf055bf2a7eec030728ce751814c50cdd6a26c" +dependencies = [ + "bitflags", + "block", + "cocoa-foundation", + "foreign-types", + "log", + "objc", +] + [[package]] name = "mime" version = "0.3.16" @@ -1492,6 +1884,23 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "naga" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8f30d7036f137a2f64fd7d53b70a91545d3f09e030b77b3816ff7bd4cf3f789" +dependencies = [ + "bit-set", + "bitflags", + "fxhash", + "log", + "num-traits", + "petgraph", + "serde", + "spirv_headers", + "thiserror", +] + [[package]] name = "new_debug_unreachable" version = "1.0.4" @@ -1600,6 +2009,25 @@ dependencies = [ "libc", ] +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", + "objc_exception", +] + +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + [[package]] name = "once_cell" version = "1.5.2" @@ -2012,6 +2440,21 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "range-alloc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e935c45e09cc6dcf00d2f0b2d630a58f4095320223d47fc68918722f0538b6" + +[[package]] +name = "raw-window-handle" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a441a7a6c80ad6473bd4b74ec1c9a4c951794285bf941c2126f607c72e48211" +dependencies = [ + "libc", +] + [[package]] name = "redox_syscall" version = "0.1.57" @@ -2128,6 +2571,17 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "ron" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "064ea8613fb712a19faf920022ec8ddf134984f100090764a4e1d768f3827f1f" +dependencies = [ + "base64 0.13.0", + "bitflags", + "serde", +] + [[package]] name = "rustc_version" version = "0.2.3" @@ -2349,6 +2803,12 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" +[[package]] +name = "slotmap" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c46a3482db8f247956e464d783693ece164ca056e6e67563ee5505bdb86452cd" + [[package]] name = "smallvec" version = "1.6.1" @@ -2388,6 +2848,27 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spirv_cross" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06db6bd7b6518f761593783e2896eefe55e90455efc5f44511078ce0426ed418" +dependencies = [ + "cc", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "spirv_headers" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f5b132530b1ac069df335577e3581765995cba5a13995cdbbdbc8fb057c532c" +dependencies = [ + "bitflags", + "num-traits", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -2400,6 +2881,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "storage-map" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418bb14643aa55a7841d5303f72cf512cfb323b8cc221d51580500a1ca75206c" +dependencies = [ + "lock_api", +] + [[package]] name = "string_cache" version = "0.8.1" @@ -2925,6 +3415,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "thunderdome" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7572415bd688d401c52f6e36f4c8e805b9ae1622619303b9fa835d531db0acae" + [[package]] name = "time" version = "0.1.44" @@ -3087,9 +3583,21 @@ checksum = "f7d40a22fd029e33300d8d89a5cc8ffce18bb7c587662f54629e94c9de5487f3" dependencies = [ "cfg-if 1.0.0", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f080ea7e4107844ef4766459426fa2d5c1ada2e47edba05dc7fa99d9629f47" +dependencies = [ + "proc-macro2 1.0.24", + "quote 1.0.8", + "syn 1.0.60", +] + [[package]] name = "tracing-core" version = "0.1.17" @@ -3468,6 +3976,46 @@ dependencies = [ "webpki", ] +[[package]] +name = "wgpu-core" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89fa2cc5d72236461ac09c5be967012663e29cb62f1a972654cbf35e49dffa8" +dependencies = [ + "arrayvec", + "bitflags", + "cfg_aliases", + "copyless", + "fxhash", + "gfx-backend-dx11", + "gfx-backend-dx12", + "gfx-backend-empty", + "gfx-backend-gl", + "gfx-backend-metal", + "gfx-backend-vulkan", + "gfx-hal", + "gpu-alloc", + "gpu-descriptor", + "naga", + "parking_lot", + "ron", + "serde", + "smallvec", + "thiserror", + "tracing", + "wgpu-types", +] + +[[package]] +name = "wgpu-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72fa9ba80626278fd87351555c363378d08122d7601e58319be3d6fa85a87747" +dependencies = [ + "bitflags", + "serde", +] + [[package]] name = "which" version = "4.0.2" @@ -3554,6 +4102,15 @@ dependencies = [ "toml", ] +[[package]] +name = "wio" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "wyz" version = "0.2.0" diff --git a/cli/build.rs b/cli/build.rs index a54bf962c701c5..236097cbf88c99 100644 --- a/cli/build.rs +++ b/cli/build.rs @@ -11,6 +11,7 @@ use deno_core::RuntimeOptions; use deno_runtime::deno_crypto; use deno_runtime::deno_fetch; use deno_runtime::deno_web; +use deno_runtime::deno_webgpu; use deno_runtime::deno_websocket; use regex::Regex; use std::collections::HashMap; @@ -62,6 +63,7 @@ fn create_compiler_snapshot( let mut op_crate_libs = HashMap::new(); op_crate_libs.insert("deno.web", deno_web::get_declaration()); op_crate_libs.insert("deno.fetch", deno_fetch::get_declaration()); + op_crate_libs.insert("deno.webgpu", deno_webgpu::get_declaration()); op_crate_libs.insert("deno.websocket", deno_websocket::get_declaration()); op_crate_libs.insert("deno.crypto", deno_crypto::get_declaration()); @@ -260,6 +262,10 @@ fn main() { "cargo:rustc-env=DENO_FETCH_LIB_PATH={}", deno_fetch::get_declaration().display() ); + println!( + "cargo:rustc-env=DENO_WEBGPU_LIB_PATH={}", + deno_webgpu::get_declaration().display() + ); println!( "cargo:rustc-env=DENO_WEBSOCKET_LIB_PATH={}", deno_websocket::get_declaration().display() diff --git a/cli/dts/lib.deno.window.d.ts b/cli/dts/lib.deno.window.d.ts index 721c89af229e30..2d46e5fe05ed5f 100644 --- a/cli/dts/lib.deno.window.d.ts +++ b/cli/dts/lib.deno.window.d.ts @@ -3,6 +3,7 @@ /// /// /// +/// /// declare class Window extends EventTarget { @@ -17,12 +18,18 @@ declare class Window extends EventTarget { confirm: (message?: string) => boolean; prompt: (message?: string, defaultValue?: string) => string | null; Deno: typeof Deno; + navigator: Navigator; } declare var window: Window & typeof globalThis; declare var self: Window & typeof globalThis; declare var onload: ((this: Window, ev: Event) => any) | null; declare var onunload: ((this: Window, ev: Event) => any) | null; +declare var navigator: Navigator; + +declare interface Navigator { + readonly gpu: GPU; +} /** * Shows the given message and waits for the enter key pressed. diff --git a/cli/dts/lib.deno.worker.d.ts b/cli/dts/lib.deno.worker.d.ts index a8f42f3362e9bb..93549ecd5cd36f 100644 --- a/cli/dts/lib.deno.worker.d.ts +++ b/cli/dts/lib.deno.worker.d.ts @@ -3,6 +3,7 @@ /// /// /// +/// /// declare class WorkerGlobalScope { @@ -29,6 +30,13 @@ declare class WorkerGlobalScope { close: () => void; postMessage: (message: any) => void; Deno: typeof Deno; + navigator: WorkerNavigator; +} + +declare var navigator: WorkerNavigator; + +declare interface WorkerNavigator { + readonly gpu: GPU; } declare class DedicatedWorkerGlobalScope extends WorkerGlobalScope { diff --git a/cli/flags.rs b/cli/flags.rs index a803ff63ff2b10..6add2df8789671 100644 --- a/cli/flags.rs +++ b/cli/flags.rs @@ -234,6 +234,7 @@ static ENV_VARIABLES_HELP: &str = r#"ENVIRONMENT VARIABLES: DENO_DIR Set the cache directory DENO_INSTALL_ROOT Set deno install's output directory (defaults to $HOME/.deno/bin) + DENO_WEBGPU_TRACE Directory to use for wgpu traces HTTP_PROXY Proxy address for HTTP requests (module downloads, fetch) HTTPS_PROXY Proxy address for HTTPS requests diff --git a/cli/main.rs b/cli/main.rs index 7871fe882fb045..0fa63a138edccd 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -278,10 +278,11 @@ fn print_cache_info( pub fn get_types(unstable: bool) -> String { let mut types = format!( - "{}\n{}\n{}\n{}\n{}\n{}", + "{}\n{}\n{}\n{}\n{}\n{}\n{}", crate::tsc::DENO_NS_LIB, crate::tsc::DENO_WEB_LIB, crate::tsc::DENO_FETCH_LIB, + crate::tsc::DENO_WEBGPU_LIB, crate::tsc::DENO_WEBSOCKET_LIB, crate::tsc::SHARED_GLOBALS_LIB, crate::tsc::WINDOW_LIB, @@ -1022,6 +1023,8 @@ fn init_logger(maybe_level: Option) { ) // https://github.com/denoland/deno/issues/6641 .filter_module("rustyline", LevelFilter::Off) + // wgpu backend crates (gfx_backend), have a lot of useless INFO and WARN logs + .filter_module("gfx", LevelFilter::Error) .format(|buf, record| { let mut target = record.target().to_string(); if let Some(line_no) = record.line() { diff --git a/cli/tests/unit/unit_tests.ts b/cli/tests/unit/unit_tests.ts index 8af28c1bb76bfb..2664a9ab0e3d03 100644 --- a/cli/tests/unit/unit_tests.ts +++ b/cli/tests/unit/unit_tests.ts @@ -76,3 +76,4 @@ import "./write_text_file_test.ts"; import "./performance_test.ts"; import "./version_test.ts"; import "./websocket_test.ts"; +import "./webgpu_test.ts"; diff --git a/cli/tests/unit/webgpu_test.ts b/cli/tests/unit/webgpu_test.ts new file mode 100644 index 00000000000000..628a3c8db92667 --- /dev/null +++ b/cli/tests/unit/webgpu_test.ts @@ -0,0 +1,225 @@ +// TODO(lucacasonato): remove when GPUBufferUsage and friends are added to dlint +// deno-lint-ignore-file no-undef + +import { assert, assertEquals, unitTest } from "./test_util.ts"; + +let isCI: boolean; +try { + isCI = (Deno.env.get("CI")?.length ?? 0) > 0; +} catch { + isCI = true; +} + +// Skip this test on linux CI, because the vulkan emulator is not good enough +// yet, and skip on macOS because these do not have virtual GPUs. +unitTest({ + perms: { read: true, env: true }, + ignore: (Deno.build.os === "linux" || Deno.build.os === "darwin") && isCI, +}, async function webgpuComputePass() { + const adapter = await navigator.gpu.requestAdapter(); + assert(adapter); + + const numbers = [1, 4, 3, 295]; + + const device = await adapter.requestDevice(); + assert(device); + + const shaderCode = await Deno.readTextFile( + "cli/tests/webgpu_computepass_shader.wgsl", + ); + + const shaderModule = device.createShaderModule({ + code: shaderCode, + }); + + const size = new Uint32Array(numbers).byteLength; + + const stagingBuffer = device.createBuffer({ + size: size, + usage: 1 | 8, + }); + + const storageBuffer = device.createBuffer({ + label: "Storage Buffer", + size: size, + usage: 0x80 | 8 | 4, + mappedAtCreation: true, + }); + + const buf = new Uint32Array(storageBuffer.getMappedRange()); + + buf.set(numbers); + + storageBuffer.unmap(); + + const bindGroupLayout = device.createBindGroupLayout({ + entries: [ + { + binding: 0, + visibility: 4, + buffer: { + type: "storage", + minBindingSize: 4, + }, + }, + ], + }); + + const bindGroup = device.createBindGroup({ + layout: bindGroupLayout, + entries: [ + { + binding: 0, + resource: { + buffer: storageBuffer, + }, + }, + ], + }); + + const pipelineLayout = device.createPipelineLayout({ + bindGroupLayouts: [bindGroupLayout], + }); + + const computePipeline = device.createComputePipeline({ + layout: pipelineLayout, + compute: { + module: shaderModule, + entryPoint: "main", + }, + }); + + const encoder = device.createCommandEncoder(); + + const computePass = encoder.beginComputePass(); + computePass.setPipeline(computePipeline); + computePass.setBindGroup(0, bindGroup); + computePass.insertDebugMarker("compute collatz iterations"); + computePass.dispatch(numbers.length); + computePass.endPass(); + + encoder.copyBufferToBuffer(storageBuffer, 0, stagingBuffer, 0, size); + + device.queue.submit([encoder.finish()]); + + await stagingBuffer.mapAsync(1); + + const data = stagingBuffer.getMappedRange(); + + assertEquals(new Uint32Array(data), new Uint32Array([0, 2, 7, 55])); + + stagingBuffer.unmap(); + + device.destroy(); + + // TODO(lucacasonato): webgpu spec should add a explicit destroy method for + // adapters. + const resources = Object.keys(Deno.resources()); + Deno.close(Number(resources[resources.length - 1])); +}); + +// Skip this test on linux CI, because the vulkan emulator is not good enough +// yet, and skip on macOS because these do not have virtual GPUs. +unitTest({ + perms: { read: true, env: true }, + ignore: (Deno.build.os === "linux" || Deno.build.os === "darwin") && isCI, +}, async function webgpuHelloTriangle() { + const adapter = await navigator.gpu.requestAdapter(); + assert(adapter); + + const device = await adapter.requestDevice(); + assert(device); + + const shaderCode = await Deno.readTextFile( + "cli/tests/webgpu_hellotriangle_shader.wgsl", + ); + + const shaderModule = device.createShaderModule({ + code: shaderCode, + }); + + const pipelineLayout = device.createPipelineLayout({ + bindGroupLayouts: [], + }); + + const renderPipeline = device.createRenderPipeline({ + layout: pipelineLayout, + vertex: { + module: shaderModule, + entryPoint: "vs_main", + }, + fragment: { + module: shaderModule, + entryPoint: "fs_main", + targets: [ + { + format: "rgba8unorm-srgb", + }, + ], + }, + }); + + const dimensions = { + width: 200, + height: 200, + }; + const unpaddedBytesPerRow = dimensions.width * 4; + const align = 256; + const paddedBytesPerRowPadding = (align - unpaddedBytesPerRow % align) % + align; + const paddedBytesPerRow = unpaddedBytesPerRow + paddedBytesPerRowPadding; + + const outputBuffer = device.createBuffer({ + label: "Capture", + size: paddedBytesPerRow * dimensions.height, + usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST, + }); + const texture = device.createTexture({ + label: "Capture", + size: dimensions, + format: "rgba8unorm-srgb", + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, + }); + + const encoder = device.createCommandEncoder(); + const renderPass = encoder.beginRenderPass({ + colorAttachments: [ + { + view: texture.createView(), + storeOp: "store", + loadValue: [0, 1, 0, 1], + }, + ], + }); + renderPass.setPipeline(renderPipeline); + renderPass.draw(3, 1); + renderPass.endPass(); + + encoder.copyTextureToBuffer( + { + texture, + }, + { + buffer: outputBuffer, + bytesPerRow: paddedBytesPerRow, + rowsPerImage: 0, + }, + dimensions, + ); + + device.queue.submit([encoder.finish()]); + + await outputBuffer.mapAsync(1); + const data = new Uint8Array(outputBuffer.getMappedRange()); + + assertEquals(data, await Deno.readFile("cli/tests/webgpu_hellotriangle.out")); + + outputBuffer.unmap(); + + device.destroy(); + + // TODO(lucacasonato): webgpu spec should add a explicit destroy method for + // adapters. + const resources = Object.keys(Deno.resources()); + Deno.close(Number(resources[resources.length - 1])); +}); diff --git a/cli/tests/webgpu_computepass_shader.wgsl b/cli/tests/webgpu_computepass_shader.wgsl new file mode 100644 index 00000000000000..2433f324335203 --- /dev/null +++ b/cli/tests/webgpu_computepass_shader.wgsl @@ -0,0 +1,39 @@ +[[builtin(global_invocation_id)]] +var global_id: vec3; + +[[block]] +struct PrimeIndices { + data: [[stride(4)]] array; +}; // this is used as both input and output for convenience + +[[group(0), binding(0)]] +var v_indices: [[access(read_write)]] PrimeIndices; + +// The Collatz Conjecture states that for any integer n: +// If n is even, n = n/2 +// If n is odd, n = 3n+1 +// And repeat this process for each new n, you will always eventually reach 1. +// Though the conjecture has not been proven, no counterexample has ever been found. +// This function returns how many times this recurrence needs to be applied to reach 1. +fn collatz_iterations(n_base: u32) -> u32{ + var n: u32 = n_base; + var i: u32 = 0u; + loop { + if (n <= 1u) { + break; + } + if (n % 2u == 0u) { + n = n / 2u; + } + else { + n = 3u * n + 1u; + } + i = i + 1u; + } + return i; +} + +[[stage(compute), workgroup_size(1)]] +fn main() { + v_indices.data[global_id.x] = collatz_iterations(v_indices.data[global_id.x]); +} diff --git a/cli/tests/webgpu_hellotriangle.out b/cli/tests/webgpu_hellotriangle.out new file mode 100644 index 00000000000000..91454dbfc665ef Binary files /dev/null and b/cli/tests/webgpu_hellotriangle.out differ diff --git a/cli/tests/webgpu_hellotriangle_shader.wgsl b/cli/tests/webgpu_hellotriangle_shader.wgsl new file mode 100644 index 00000000000000..71934415be220e --- /dev/null +++ b/cli/tests/webgpu_hellotriangle_shader.wgsl @@ -0,0 +1,19 @@ +[[builtin(vertex_index)]] +var in_vertex_index: u32; +[[builtin(position)]] +var out_pos: vec4; + +[[stage(vertex)]] +fn vs_main() { + var x: f32 = f32(i32(in_vertex_index) - 1); + var y: f32 = f32(i32(in_vertex_index & 1) * 2 - 1); + out_pos = vec4(x, y, 0.0, 1.0); +} + +[[location(0)]] +var out_color: vec4; + +[[stage(fragment)]] +fn fs_main() { + out_color = vec4(1.0, 0.0, 0.0, 1.0); +} diff --git a/cli/tsc.rs b/cli/tsc.rs index 6328175b9c16aa..8d28b959d37d41 100644 --- a/cli/tsc.rs +++ b/cli/tsc.rs @@ -31,6 +31,7 @@ use std::sync::Mutex; pub static DENO_NS_LIB: &str = include_str!("dts/lib.deno.ns.d.ts"); pub static DENO_WEB_LIB: &str = include_str!(env!("DENO_WEB_LIB_PATH")); pub static DENO_FETCH_LIB: &str = include_str!(env!("DENO_FETCH_LIB_PATH")); +pub static DENO_WEBGPU_LIB: &str = include_str!(env!("DENO_WEBGPU_LIB_PATH")); pub static DENO_WEBSOCKET_LIB: &str = include_str!(env!("DENO_WEBSOCKET_LIB_PATH")); pub static SHARED_GLOBALS_LIB: &str = diff --git a/op_crates/web/02_event.js b/op_crates/web/02_event.js index d253599e5ec32d..ac813d0911c600 100644 --- a/op_crates/web/02_event.js +++ b/op_crates/web/02_event.js @@ -1204,6 +1204,7 @@ window.removeEventListener = EventTarget.prototype.removeEventListener; window.__bootstrap = (window.__bootstrap || {}); window.__bootstrap.eventTarget = { + EventTarget, setEventTargetData, }; window.__bootstrap.event = { diff --git a/op_crates/web/internal.d.ts b/op_crates/web/internal.d.ts index d43332e4b05907..8be80c8a520752 100644 --- a/op_crates/web/internal.d.ts +++ b/op_crates/web/internal.d.ts @@ -270,6 +270,10 @@ declare namespace globalThis { ): (v: any, opts: ValueConverterOpts) => any; } + declare var eventTarget: { + EventTarget: typeof EventTarget; + }; + declare var url: { URLSearchParams: typeof URLSearchParams; }; diff --git a/op_crates/webgpu/01_webgpu.js b/op_crates/webgpu/01_webgpu.js new file mode 100644 index 00000000000000..7a078619e019b7 --- /dev/null +++ b/op_crates/webgpu/01_webgpu.js @@ -0,0 +1,5048 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +// @ts-check +/// +/// +/// +/// + +"use strict"; + +((window) => { + const core = window.Deno.core; + const webidl = window.__bootstrap.webidl; + const eventTarget = window.__bootstrap.eventTarget; + + /** + * @param {any} self + * @param {{prefix: string, context: string}} opts + * @returns {InnerGPUDevice & {rid: number}} + */ + function assertDevice(self, { prefix, context }) { + const device = self[_device]; + const deviceRid = device?.rid; + if (deviceRid === undefined) { + throw new DOMException( + `${prefix}: ${context} references an invalid or destroyed device.`, + "OperationError", + ); + } + return device; + } + + /** + * @param {InnerGPUDevice} self + * @param {any} resource + * @param {{prefix: string, resourceContext: string, selfContext: string}} opts + * @returns {InnerGPUDevice & {rid: number}} + */ + function assertDeviceMatch( + self, + resource, + { prefix, resourceContext, selfContext }, + ) { + const resourceDevice = assertDevice(resource, { + prefix, + context: resourceContext, + }); + if (resourceDevice.rid !== self.rid) { + throw new DOMException( + `${prefix}: ${resourceContext} belongs to a diffent device than ${selfContext}.`, + "OperationError", + ); + } + return { ...resourceDevice, rid: resourceDevice.rid }; + } + + /** + * @param {any} self + * @param {{prefix: string, context: string}} opts + * @returns {number} + */ + function assertResource(self, { prefix, context }) { + const rid = self[_rid]; + if (rid === undefined) { + throw new DOMException( + `${prefix}: ${context} an invalid or destroyed resource.`, + "OperationError", + ); + } + return rid; + } + + /** + * @param {number[] | GPUExtent3DDict} data + * @returns {GPUExtent3DDict} + */ + function normalizeGPUExtent3D(data) { + if (Array.isArray(data)) { + return { + width: data[0], + height: data[1], + depth: data[2], + }; + } else { + return data; + } + } + + /** + * @param {number[] | GPUOrigin3DDict} data + * @returns {GPUOrigin3DDict} + */ + function normalizeGPUOrigin3D(data) { + if (Array.isArray(data)) { + return { + x: data[0], + y: data[1], + z: data[2], + }; + } else { + return data; + } + } + + /** + * @param {number[] | GPUColor} data + * @returns {GPUColor} + */ + function normalizeGPUColor(data) { + if (Array.isArray(data)) { + return { + r: data[0], + g: data[1], + b: data[2], + a: data[3], + }; + } else { + return data; + } + } + + class GPUOutOfMemoryError extends Error { + constructor() { + super(); + } + } + + class GPUValidationError extends Error { + /** @param {string} message */ + constructor(message) { + const prefix = "Failed to construct 'GPUValidationError'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + message = webidl.converters.DOMString(message, { + prefix, + context: "Argument 1", + }); + super(message); + } + } + + class GPU { + [webidl.brand] = webidl.brand; + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {GPURequestAdapterOptions} options + */ + async requestAdapter(options = {}) { + webidl.assertBranded(this, GPU); + options = webidl.converters.GPURequestAdapterOptions(options, { + prefix: "Failed to execute 'requestAdapter' on 'GPU'", + context: "Argument 1", + }); + + const { err, ...data } = await core.jsonOpAsync( + "op_webgpu_request_adapter", + { ...options }, + ); + + if (err) { + return null; + } else { + return createGPUAdapter(data.name, data); + } + } + + [Symbol.for("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${inspect({})}`; + } + } + + const _name = Symbol("[[name]]"); + const _adapter = Symbol("[[adapter]]"); + const _cleanup = Symbol("[[cleanup]]"); + + /** + * @typedef InnerGPUAdapter + * @property {number} rid + * @property {GPUAdapterFeatures} features + * @property {GPUAdapterLimits} limits + */ + + /** + * @param {string} name + * @param {InnerGPUAdapter} inner + * @returns {GPUAdapter} + */ + function createGPUAdapter(name, inner) { + /** @type {GPUAdapter} */ + const adapter = webidl.createBranded(GPUAdapter); + adapter[_name] = name; + adapter[_adapter] = { + ...inner, + features: createGPUAdapterFeatures(inner.features), + limits: createGPUAdapterLimits(inner.limits), + }; + return adapter; + } + + class GPUAdapter { + /** @type {string} */ + [_name]; + /** @type {InnerGPUAdapter} */ + [_adapter]; + + /** @returns {string} */ + get name() { + webidl.assertBranded(this, GPUAdapter); + return this[_name]; + } + /** @returns {GPUAdapterFeatures} */ + get features() { + webidl.assertBranded(this, GPUAdapter); + return this[_adapter].features; + } + /** @returns {GPUAdapterLimits} */ + get limits() { + webidl.assertBranded(this, GPUAdapter); + return this[_adapter].limits; + } + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {GPUDeviceDescriptor} descriptor + * @returns {Promise} + */ + async requestDevice(descriptor = {}) { + webidl.assertBranded(this, GPUAdapter); + const prefix = "Failed to execute 'requestDevice' on 'GPUAdapter'"; + descriptor = webidl.converters.GPUDeviceDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const nonGuaranteedFeatures = descriptor.nonGuaranteedFeatures ?? []; + for (const feature of nonGuaranteedFeatures) { + if (!this[_adapter].features.has(feature)) { + throw new TypeError( + `${prefix}: nonGuaranteedFeatures must be a subset of the adapter features.`, + ); + } + } + const nonGuaranteedLimits = descriptor.nonGuaranteedLimits ?? []; + // TODO(lucacasonato): validate nonGuaranteedLimits + + const { rid, features, limits } = await core.jsonOpAsync( + "op_webgpu_request_device", + { + adapterRid: this[_adapter].rid, + labe: descriptor.label, + nonGuaranteedFeatures, + nonGuaranteedLimits, + }, + ); + + const inner = new InnerGPUDevice({ + rid, + adapter: this, + features: Object.freeze(features), + limits: Object.freeze(limits), + }); + return createGPUDevice( + descriptor.label ?? null, + inner, + createGPUQueue(descriptor.label ?? null, inner), + ); + } + + [Symbol.for("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + name: this.name, + features: this.features, + limits: this.limits, + }) + }`; + } + } + + const _limits = Symbol("[[limits]]"); + + function createGPUAdapterLimits(features) { + /** @type {GPUAdapterLimits} */ + const adapterFeatures = webidl.createBranded(GPUAdapterLimits); + adapterFeatures[_limits] = features; + return adapterFeatures; + } + + /** + * @typedef InnerAdapterLimits + * @property {number} maxTextureDimension1D + * @property {number} maxTextureDimension2D + * @property {number} maxTextureDimension3D + * @property {number} maxTextureArrayLayers + * @property {number} maxBindGroups + * @property {number} maxDynamicUniformBuffersPerPipelineLayout + * @property {number} maxDynamicStorageBuffersPerPipelineLayout + * @property {number} maxSampledTexturesPerShaderStage + * @property {number} maxSamplersPerShaderStage + * @property {number} maxStorageBuffersPerShaderStage + * @property {number} maxStorageTexturesPerShaderStage + * @property {number} maxUniformBuffersPerShaderStage + * @property {number} maxUniformBufferBindingSize + * @property {number} maxStorageBufferBindingSize + * @property {number} maxVertexBuffers + * @property {number} maxVertexAttributes + * @property {number} maxVertexBufferArrayStride + */ + + class GPUAdapterLimits { + /** @type {InnerAdapterLimits} */ + [_limits]; + constructor() { + webidl.illegalConstructor(); + } + + get maxTextureDimension1D() { + throw new TypeError("Not yet implemented"); + } + get maxTextureDimension2D() { + throw new TypeError("Not yet implemented"); + } + get maxTextureDimension3D() { + throw new TypeError("Not yet implemented"); + } + get maxTextureArrayLayers() { + throw new TypeError("Not yet implemented"); + } + get maxBindGroups() { + webidl.assertBranded(this, GPUAdapterLimits); + return this[_limits].maxBindGroups; + } + get maxDynamicUniformBuffersPerPipelineLayout() { + webidl.assertBranded(this, GPUAdapterLimits); + return this[_limits].maxDynamicUniformBuffersPerPipelineLayout; + } + get maxDynamicStorageBuffersPerPipelineLayout() { + webidl.assertBranded(this, GPUAdapterLimits); + return this[_limits].maxDynamicStorageBuffersPerPipelineLayout; + } + get maxSampledTexturesPerShaderStage() { + webidl.assertBranded(this, GPUAdapterLimits); + return this[_limits].maxSampledTexturesPerShaderStage; + } + get maxSamplersPerShaderStage() { + webidl.assertBranded(this, GPUAdapterLimits); + return this[_limits].maxSamplersPerShaderStage; + } + get maxStorageBuffersPerShaderStage() { + webidl.assertBranded(this, GPUAdapterLimits); + return this[_limits].maxStorageBuffersPerShaderStage; + } + get maxStorageTexturesPerShaderStage() { + webidl.assertBranded(this, GPUAdapterLimits); + return this[_limits].maxStorageTexturesPerShaderStage; + } + get maxUniformBuffersPerShaderStage() { + webidl.assertBranded(this, GPUAdapterLimits); + return this[_limits].maxUniformBuffersPerShaderStage; + } + get maxUniformBufferBindingSize() { + webidl.assertBranded(this, GPUAdapterLimits); + return this[_limits].maxUniformBufferBindingSize; + } + get maxStorageBufferBindingSize() { + throw new TypeError("Not yet implemented"); + } + get maxVertexBuffers() { + throw new TypeError("Not yet implemented"); + } + get maxVertexAttributes() { + throw new TypeError("Not yet implemented"); + } + get maxVertexBufferArrayStride() { + throw new TypeError("Not yet implemented"); + } + + [Symbol.for("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${inspect(this[_limits])}`; + } + } + + const _features = Symbol("[[features]]"); + + function createGPUAdapterFeatures(features) { + /** @type {GPUAdapterFeatures} */ + const adapterFeatures = webidl.createBranded(GPUAdapterFeatures); + adapterFeatures[_features] = new Set(features); + return adapterFeatures; + } + + class GPUAdapterFeatures { + /** @type {Set} */ + [_features]; + + constructor() { + webidl.illegalConstructor(); + } + + /** @return {IterableIterator<[string, string]>} */ + entries() { + webidl.assertBranded(this, GPUAdapterFeatures); + return this[_features].entries(); + } + + /** @return {void} */ + forEach(callbackfn, thisArg) { + webidl.assertBranded(this, GPUAdapterFeatures); + this[_features].forEach(callbackfn, thisArg); + } + + /** @return {boolean} */ + has(value) { + webidl.assertBranded(this, GPUAdapterFeatures); + return this[_features].has(value); + } + + /** @return {IterableIterator} */ + keys() { + webidl.assertBranded(this, GPUAdapterFeatures); + return this[_features].keys(); + } + + /** @return {IterableIterator} */ + values() { + webidl.assertBranded(this, GPUAdapterFeatures); + return this[_features].values(); + } + + /** @return {number} */ + get size() { + webidl.assertBranded(this, GPUAdapterFeatures); + return this[_features].size; + } + + [Symbol.iterator]() { + webidl.assertBranded(this, GPUAdapterFeatures); + return this[_features][Symbol.iterator](); + } + + [Symbol.for("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${inspect([...this.values()])}`; + } + } + + const _reason = Symbol("[[reason]]"); + const _message = Symbol("[[message]]"); + + /** + * + * @param {string | undefined} reason + * @param {string} message + * @returns {GPUDeviceLostInfo} + */ + function createGPUDeviceLostInfo(reason, message) { + /** @type {GPUDeviceLostInfo} */ + const deviceLostInfo = webidl.createBranded(GPUDeviceLostInfo); + deviceLostInfo[_reason] = reason; + deviceLostInfo[_message] = message; + return deviceLostInfo; + } + + class GPUDeviceLostInfo { + /** @type {string | undefined} */ + [_reason]; + /** @type {string} */ + [_message]; + + constructor() { + webidl.illegalConstructor(); + } + + get reason() { + webidl.assertBranded(this, GPUDeviceLostInfo); + return this[_reason]; + } + get message() { + webidl.assertBranded(this, GPUDeviceLostInfo); + return this[_message]; + } + + [Symbol.for("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ reason: this[_reason], message: this[_message] }) + }`; + } + } + + const _label = Symbol("[[label]]"); + + /** + * @param {string} name + * @param {any} type + */ + function GPUObjectBaseMixin(name, type) { + type.prototype[_label] = null; + Object.defineProperty(type.prototype, "label", { + /** + * @return {string | null} + */ + get() { + webidl.assertBranded(this, type); + return this[_label]; + }, + /** + * @param {string | null} label + */ + set(label) { + webidl.assertBranded(this, type); + label = webidl.converters["UVString?"](label, { + prefix: `Failed to set 'label' on '${name}'`, + context: "Argument 1", + }); + this[_label] = label; + }, + }); + } + + const _device = Symbol("[[device]]"); + const _queue = Symbol("[[queue]]"); + + /** + * @typedef ErrorScope + * @property {string} filter + * @property {GPUError | undefined} error + */ + + /** + * @typedef InnerGPUDeviceOptions + * @property {GPUAdapter} adapter + * @property {number | undefined} rid + * @property {GPUFeatureName[]} features + * @property {object} limits + */ + + class InnerGPUDevice { + /** @type {GPUAdapter} */ + adapter; + /** @type {number | undefined} */ + rid; + /** @type {GPUFeatureName[]} */ + features; + /** @type {object} */ + limits; + /** @type {WeakRef[]} */ + resources; + /** @type {boolean} */ + isLost; + /** @type {Promise} */ + lost; + /** @type {(info: GPUDeviceLostInfo) => void} */ + resolveLost; + /** @type {ErrorScope[]} */ + errorScopeStack; + + /** + * @param {InnerGPUDeviceOptions} options + */ + constructor(options) { + this.adapter = options.adapter; + this.rid = options.rid; + this.features = options.features; + this.limits = options.limits; + this.resources = []; + this.isLost = false; + this.resolveLost = () => {}; + this.lost = new Promise((resolve) => { + this.resolveLost = resolve; + }); + this.errorScopeStack = []; + } + + /** @param {any} resource */ + trackResource(resource) { + this.resources.push(new WeakRef(resource)); + } + + /** @param {{ type: string, value: string | null } | undefined} err */ + pushError(err) { + if (err) { + switch (err.type) { + case "lost": + this.isLost = true; + this.resolveLost( + createGPUDeviceLostInfo(undefined, "device was lost"), + ); + break; + case "validation": + case "out-of-memory": + for ( + let i = this.errorScopeStack.length - 1; + i >= 0; + i-- + ) { + const scope = this.errorScopeStack[i]; + if (scope.filter == err.type) { + if (!scope.error) { + switch (err.type) { + case "validation": + scope.error = new GPUValidationError( + err.value ?? "validation error", + ); + break; + case "out-of-memory": + scope.error = new GPUOutOfMemoryError(); + break; + } + } + return; + } + } + // TODO(lucacasonato): emit a UncapturedErrorEvent + break; + } + } + } + } + + /** + * @param {string | null} label + * @param {InnerGPUDevice} inner + * @param {GPUQueue} queue + * @returns {GPUDevice} + */ + function createGPUDevice(label, inner, queue) { + /** @type {GPUDevice} */ + const device = webidl.createBranded(GPUDevice); + device[_label] = label; + device[_device] = inner; + device[_queue] = queue; + return device; + } + + // TODO(@crowlKats): https://gpuweb.github.io/gpuweb/#errors-and-debugging + class GPUDevice extends eventTarget.EventTarget { + /** @type {InnerGPUDevice} */ + [_device]; + + /** @type {GPUQueue} */ + [_queue]; + + [_cleanup]() { + const device = this[_device]; + const resources = device.resources; + while (resources.length > 0) { + const resource = resources.pop()?.deref(); + if (resource) { + resource[_cleanup](); + } + } + const rid = device.rid; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + device.rid = undefined; + } + } + + get adapter() { + webidl.assertBranded(this, GPUDevice); + return this[_device].adapter; + } + get features() { + webidl.assertBranded(this, GPUDevice); + return this[_device].features; + } + get limits() { + webidl.assertBranded(this, GPUDevice); + return this[_device].limits; + } + get queue() { + webidl.assertBranded(this, GPUDevice); + return this[_queue]; + } + + constructor() { + webidl.illegalConstructor(); + super(); + } + + destroy() { + webidl.assertBranded(this, GPUDevice); + this[_cleanup](); + } + + /** + * @param {GPUBufferDescriptor} descriptor + * @returns {GPUBuffer} + */ + createBuffer(descriptor) { + webidl.assertBranded(this, GPUDevice); + const prefix = "Failed to execute 'createBuffer' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPUBufferDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const { rid, err } = core.jsonOpSync("op_webgpu_create_buffer", { + deviceRid: device.rid, + ...descriptor, + }); + device.pushError(err); + /** @type {CreateGPUBufferOptions} */ + let options; + if (descriptor.mappedAtCreation) { + options = { + mapping: new ArrayBuffer(descriptor.size), + mappingRange: [0, descriptor.size], + mappedRanges: [], + state: "mapped at creation", + }; + } else { + options = { + mapping: null, + mappedRanges: null, + mappingRange: null, + state: "unmapped", + }; + } + const buffer = createGPUBuffer( + descriptor.label ?? null, + device, + rid, + descriptor.size, + descriptor.usage, + options, + ); + device.trackResource((buffer)); + return buffer; + } + + /** + * @param {GPUTextureDescriptor} descriptor + * @returns {GPUTexture} + */ + createTexture(descriptor) { + webidl.assertBranded(this, GPUDevice); + const prefix = "Failed to execute 'createTexture' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPUTextureDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const { rid, err } = core.jsonOpSync("op_webgpu_create_texture", { + deviceRid: device.rid, + ...descriptor, + size: normalizeGPUExtent3D(descriptor.size), + }); + device.pushError(err); + + const texture = createGPUTexture( + descriptor.label ?? null, + device, + rid, + ); + device.trackResource((texture)); + return texture; + } + + /** + * @param {GPUSamplerDescriptor} descriptor + * @returns {GPUSampler} + */ + createSampler(descriptor = {}) { + webidl.assertBranded(this, GPUDevice); + const prefix = "Failed to execute 'createSampler' on 'GPUDevice'"; + descriptor = webidl.converters.GPUSamplerDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const { rid, err } = core.jsonOpSync("op_webgpu_create_sampler", { + deviceRid: device.rid, + ...descriptor, + }); + device.pushError(err); + + const sampler = createGPUSampler( + descriptor.label ?? null, + device, + rid, + ); + device.trackResource((sampler)); + return sampler; + } + + /** + * @param {GPUBindGroupLayoutDescriptor} descriptor + * @returns {GPUBindGroupLayout} + */ + createBindGroupLayout(descriptor) { + webidl.assertBranded(this, GPUDevice); + const prefix = "Failed to execute 'createBindGroupLayout' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPUBindGroupLayoutDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + for (const entry of descriptor.entries) { + let i = 0; + if (entry.buffer) i++; + if (entry.sampler) i++; + if (entry.texture) i++; + if (entry.storageTexture) i++; + + if (i !== 1) { + throw new Error(); // TODO(@crowlKats): correct error + } + } + + const { rid, err } = core.jsonOpSync( + "op_webgpu_create_bind_group_layout", + { + deviceRid: device.rid, + ...descriptor, + }, + ); + device.pushError(err); + + const bindGroupLayout = createGPUBindGroupLayout( + descriptor.label ?? null, + device, + rid, + ); + device.trackResource((bindGroupLayout)); + return bindGroupLayout; + } + + /** + * @param {GPUPipelineLayoutDescriptor} descriptor + * @returns {GPUPipelineLayout} + */ + createPipelineLayout(descriptor) { + webidl.assertBranded(this, GPUDevice); + const prefix = "Failed to execute 'createPipelineLayout' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPUPipelineLayoutDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const bindGroupLayouts = descriptor.bindGroupLayouts.map( + (layout, i) => { + const context = `bind group layout ${i + 1}`; + const rid = assertResource(layout, { prefix, context }); + assertDeviceMatch(device, layout, { + prefix, + selfContext: "this", + resourceContext: context, + }); + return rid; + }, + ); + const { rid, err } = core.jsonOpSync("op_webgpu_create_pipeline_layout", { + deviceRid: device.rid, + label: descriptor.label, + bindGroupLayouts, + }); + device.pushError(err); + + const pipelineLayout = createGPUPipelineLayout( + descriptor.label ?? null, + device, + rid, + ); + device.trackResource((pipelineLayout)); + return pipelineLayout; + } + + /** + * @param {GPUBindGroupDescriptor} descriptor + * @returns {GPUBindGroup} + */ + createBindGroup(descriptor) { + webidl.assertBranded(this, GPUDevice); + const prefix = "Failed to execute 'createBindGroup' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPUBindGroupDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const layout = assertResource(descriptor.layout, { + prefix, + context: "layout", + }); + assertDeviceMatch(device, descriptor.layout, { + prefix, + resourceContext: "layout", + selfContext: "this", + }); + const entries = descriptor.entries.map((entry, i) => { + const context = `entry ${i + 1}`; + const resource = entry.resource; + if (resource instanceof GPUSampler) { + const rid = assertResource(resource, { + prefix, + context, + }); + assertDeviceMatch(device, resource, { + prefix, + resourceContext: context, + selfContext: "this", + }); + return { + binding: entry.binding, + kind: "GPUSampler", + resource: rid, + }; + } else if (resource instanceof GPUTextureView) { + const rid = assertResource(resource, { + prefix, + context, + }); + assertResource(resource[_texture], { + prefix, + context, + }); + assertDeviceMatch(device, resource[_texture], { + prefix, + resourceContext: context, + selfContext: "this", + }); + return { + binding: entry.binding, + kind: "GPUTextureView", + resource: rid, + }; + } else { + const rid = assertResource(resource.buffer, { prefix, context }); + assertDeviceMatch(device, resource.buffer, { + prefix, + resourceContext: context, + selfContext: "this", + }); + return { + binding: entry.binding, + kind: "GPUBufferBinding", + resource: rid, + offset: entry.resource.offset, + size: entry.resource.size, + }; + } + }); + + const { rid, err } = core.jsonOpSync("op_webgpu_create_bind_group", { + deviceRid: device.rid, + label: descriptor.label, + layout, + entries, + }); + device.pushError(err); + + const bindGroup = createGPUBindGroup( + descriptor.label ?? null, + device, + rid, + ); + device.trackResource((bindGroup)); + return bindGroup; + } + + /** + * @param {GPUShaderModuleDescriptor} descriptor + */ + createShaderModule(descriptor) { + webidl.assertBranded(this, GPUDevice); + const prefix = "Failed to execute 'createShaderModule' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPUShaderModuleDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const { rid, err } = core.jsonOpSync( + "op_webgpu_create_shader_module", + { + deviceRid: device.rid, + label: descriptor.label, + code: (typeof descriptor.code === "string") + ? descriptor.code + : undefined, + sourceMap: descriptor.sourceMap, + }, + ...(descriptor.code instanceof Uint32Array + ? [new Uint8Array(descriptor.code.buffer)] + : []), + ); + device.pushError(err); + + const shaderModule = createGPUShaderModule( + descriptor.label ?? null, + device, + rid, + ); + device.trackResource((shaderModule)); + return shaderModule; + } + + /** + * @param {GPUComputePipelineDescriptor} descriptor + * @returns {GPUComputePipeline} + */ + createComputePipeline(descriptor) { + webidl.assertBranded(this, GPUDevice); + const prefix = "Failed to execute 'createComputePipeline' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPUComputePipelineDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + let layout = undefined; + if (descriptor.layout) { + const context = "layout"; + layout = assertResource(descriptor.layout, { prefix, context }); + assertDeviceMatch(device, descriptor.layout, { + prefix, + resourceContext: context, + selfContext: "this", + }); + } + const module = assertResource(descriptor.compute.module, { + prefix, + context: "compute shader module", + }); + assertDeviceMatch(device, descriptor.compute.module, { + prefix, + resourceContext: "compute shader module", + selfContext: "this", + }); + + const { rid, err } = core.jsonOpSync( + "op_webgpu_create_compute_pipeline", + { + deviceRid: device.rid, + label: descriptor.label, + layout, + compute: { + module, + entryPoint: descriptor.compute.entryPoint, + }, + }, + ); + device.pushError(err); + + const computePipeline = createGPUComputePipeline( + descriptor.label ?? null, + device, + rid, + ); + device.trackResource((computePipeline)); + return computePipeline; + } + + /** + * @param {GPURenderPipelineDescriptor} descriptor + * @returns {GPURenderPipeline} + */ + createRenderPipeline(descriptor) { + webidl.assertBranded(this, GPUDevice); + const prefix = "Failed to execute 'createRenderPipeline' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPURenderPipelineDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + let layout = undefined; + if (descriptor.layout) { + const context = "layout"; + layout = assertResource(descriptor.layout, { prefix, context }); + assertDeviceMatch(device, descriptor.layout, { + prefix, + resourceContext: context, + selfContext: "this", + }); + } + const module = assertResource(descriptor.vertex.module, { + prefix, + context: "vertex shader module", + }); + assertDeviceMatch(device, descriptor.vertex.module, { + prefix, + resourceContext: "vertex shader module", + selfContext: "this", + }); + let fragment = undefined; + if (descriptor.fragment) { + const module = assertResource(descriptor.fragment.module, { + prefix, + context: "fragment shader module", + }); + assertDeviceMatch(device, descriptor.fragment.module, { + prefix, + resourceContext: "fragment shader module", + selfContext: "this", + }); + fragment = { + module, + entryPoint: descriptor.fragment.entryPoint, + targets: descriptor.fragment.targets, + }; + } + + const { rid, err } = core.jsonOpSync("op_webgpu_create_render_pipeline", { + deviceRid: device.rid, + label: descriptor.label, + layout, + vertex: { + module, + entryPoint: descriptor.vertex.entryPoint, + buffers: descriptor.vertex.buffers, + }, + primitive: descriptor.primitive, + depthStencil: descriptor.depthStencil, + multisample: descriptor.multisample, + fragment, + }); + device.pushError(err); + + const renderPipeline = createGPURenderPipeline( + descriptor.label ?? null, + device, + rid, + ); + device.trackResource((renderPipeline)); + return renderPipeline; + } + + createComputePipelineAsync(descriptor) { + // TODO(lucacasonato): this should be real async + return Promise.resolve(this.createComputePipeline(descriptor)); + } + + createRenderPipelineAsync(descriptor) { + // TODO(lucacasonato): this should be real async + return Promise.resolve(this.createRenderPipeline(descriptor)); + } + + /** + * @param {GPUCommandEncoderDescriptor} descriptor + * @returns {GPUCommandEncoder} + */ + createCommandEncoder(descriptor = {}) { + webidl.assertBranded(this, GPUDevice); + const prefix = "Failed to execute 'createCommandEncoder' on 'GPUDevice'"; + descriptor = webidl.converters.GPUCommandEncoderDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const { rid, err } = core.jsonOpSync("op_webgpu_create_command_encoder", { + deviceRid: device.rid, + ...descriptor, + }); + device.pushError(err); + + const commandEncoder = createGPUCommandEncoder( + descriptor.label ?? null, + device, + rid, + ); + device.trackResource((commandEncoder)); + return commandEncoder; + } + + /** + * @param {GPURenderBundleEncoderDescriptor} descriptor + * @returns {GPURenderBundleEncoder} + */ + createRenderBundleEncoder(descriptor) { + webidl.assertBranded(this, GPUDevice); + const prefix = + "Failed to execute 'createRenderBundleEncoder' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPURenderBundleEncoderDescriptor( + descriptor, + { + prefix, + context: "Argument 1", + }, + ); + const device = assertDevice(this, { prefix, context: "this" }); + const { rid, err } = core.jsonOpSync( + "op_webgpu_create_render_bundle_encoder", + { + deviceRid: device.rid, + ...descriptor, + }, + ); + device.pushError(err); + + const renderBundleEncoder = createGPURenderBundleEncoder( + descriptor.label ?? null, + device, + rid, + ); + device.trackResource((renderBundleEncoder)); + return renderBundleEncoder; + } + + /** + * @param {GPUQuerySetDescriptor} descriptor + * @returns {GPUQuerySet} + */ + createQuerySet(descriptor) { + webidl.assertBranded(this, GPUDevice); + const prefix = "Failed to execute 'createQuerySet' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPUQuerySetDescriptor( + descriptor, + { + prefix, + context: "Argument 1", + }, + ); + const device = assertDevice(this, { prefix, context: "this" }); + const { rid, err } = core.jsonOpSync("op_webgpu_create_query_set", { + deviceRid: device.rid, + ...descriptor, + }); + device.pushError(err); + + const querySet = createGPUQuerySet( + descriptor.label ?? null, + device, + rid, + descriptor, + ); + device.trackResource((querySet)); + return querySet; + } + + get lost() { + webidl.assertBranded(this, GPUDevice); + const device = this[_device]; + if (!device) { + return Promise.resolve(true); + } + if (device.rid === undefined) { + return Promise.resolve(true); + } + return device.lost; + } + + /** + * @param {GPUErrorFilter} filter + */ + pushErrorScope(filter) { + webidl.assertBranded(this, GPUDevice); + const prefix = "Failed to execute 'pushErrorScope' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + filter = webidl.converters.GPUErrorFilter(filter, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + device.errorScopeStack.push({ filter, error: undefined }); + } + + /** + * @returns {Promise} + */ + // deno-lint-ignore require-await + async popErrorScope() { + webidl.assertBranded(this, GPUDevice); + const prefix = "Failed to execute 'pushErrorScope' on 'GPUDevice'"; + const device = assertDevice(this, { prefix, context: "this" }); + if (device.isLost) { + throw new DOMException("Device has been lost.", "OperationError"); + } + const scope = device.errorScopeStack.pop(); + if (!scope) { + throw new DOMException( + "There are no error scopes on that stack.", + "OperationError", + ); + } + return scope.error ?? null; + } + + [Symbol.for("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + adapter: this.adapter, + features: this.features, + label: this.label, + limits: this.limits, + queue: this.queue, + }) + }`; + } + } + GPUObjectBaseMixin("GPUDevice", GPUDevice); + + /** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @returns {GPUQueue} + */ + function createGPUQueue(label, device) { + /** @type {GPUQueue} */ + const queue = webidl.createBranded(GPUQueue); + queue[_label] = label; + queue[_device] = device; + return queue; + } + + class GPUQueue { + /** @type {InnerGPUDevice} */ + [_device]; + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {GPUCommandBuffer[]} commandBuffers + */ + submit(commandBuffers) { + webidl.assertBranded(this, GPUQueue); + const prefix = "Failed to execute 'submit' on 'GPUQueue'"; + webidl.requiredArguments(arguments.length, 1, { + prefix, + }); + commandBuffers = webidl.converters["sequence"]( + commandBuffers, + { prefix, context: "Argument 1" }, + ); + const device = assertDevice(this, { prefix, context: "this" }); + const commandBufferRids = commandBuffers.map((buffer, i) => { + const context = `command buffer ${i + 1}`; + const rid = assertResource(buffer, { prefix, context }); + assertDeviceMatch(device, buffer, { + prefix, + selfContext: "this", + resourceContext: context, + }); + return rid; + }); + const { err } = core.jsonOpSync("op_webgpu_queue_submit", { + queueRid: device.rid, + commandBuffers: commandBufferRids, + }); + device.pushError(err); + } + + onSubmittedWorkDone() { + webidl.assertBranded(this, GPUQueue); + return Promise.resolve(); + } + + /** + * @param {GPUBuffer} buffer + * @param {number} bufferOffset + * @param {BufferSource} data + * @param {number} [dataOffset] + * @param {number} [size] + */ + writeBuffer(buffer, bufferOffset, data, dataOffset = 0, size) { + webidl.assertBranded(this, GPUQueue); + const prefix = "Failed to execute 'writeBuffer' on 'GPUQueue'"; + webidl.requiredArguments(arguments.length, 3, { prefix }); + buffer = webidl.converters["GPUBuffer"](buffer, { + prefix, + context: "Argument 1", + }); + bufferOffset = webidl.converters["GPUSize64"](bufferOffset, { + prefix, + context: "Argument 2", + }); + data = webidl.converters.BufferSource(data, { + prefix, + context: "Argument 3", + }); + dataOffset = webidl.converters["GPUSize64"](dataOffset, { + prefix, + context: "Argument 4", + }); + size = size === undefined + ? undefined + : webidl.converters["GPUSize64"](size, { + prefix, + context: "Argument 5", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const bufferRid = assertResource(buffer, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, buffer, { + prefix, + selfContext: "this", + resourceContext: "Argument 1", + }); + const { err } = core.jsonOpSync( + "op_webgpu_write_buffer", + { + queueRid: device.rid, + buffer: bufferRid, + bufferOffset, + dataOffset, + size, + }, + new Uint8Array(ArrayBuffer.isView(data) ? data.buffer : data), + ); + device.pushError(err); + } + + /** + * @param {GPUImageCopyTexture} destination + * @param {BufferSource} data + * @param {GPUImageDataLayout} dataLayout + * @param {GPUExtent3D} size + */ + writeTexture(destination, data, dataLayout, size) { + webidl.assertBranded(this, GPUQueue); + const prefix = "Failed to execute 'writeTexture' on 'GPUQueue'"; + webidl.requiredArguments(arguments.length, 4, { prefix }); + destination = webidl.converters.GPUImageCopyTexture(destination, { + prefix, + context: "Argument 1", + }); + data = webidl.converters.BufferSource(data, { + prefix, + context: "Argument 2", + }); + dataLayout = webidl.converters.GPUImageDataLayout(dataLayout, { + prefix, + context: "Argument 3", + }); + size = webidl.converters.GPUExtent3D(size, { + prefix, + context: "Argument 4", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const textureRid = assertResource(destination.texture, { + prefix, + context: "texture", + }); + assertDeviceMatch(device, destination.texture, { + prefix, + selfContext: "this", + resourceContext: "texture", + }); + const { err } = core.jsonOpSync( + "op_webgpu_write_texture", + { + queueRid: device.rid, + destination: { + texture: textureRid, + mipLevel: destination.mipLevel, + origin: destination.origin + ? normalizeGPUOrigin3D(destination.origin) + : undefined, + }, + dataLayout, + size: normalizeGPUExtent3D(size), + }, + new Uint8Array(ArrayBuffer.isView(data) ? data.buffer : data), + ); + device.pushError(err); + } + + copyImageBitmapToTexture(_source, _destination, _copySize) { + throw new Error("Not yet implemented"); + } + + [Symbol.for("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } + } + GPUObjectBaseMixin("GPUQueue", GPUQueue); + + const _rid = Symbol("[[rid]]"); + + const _size = Symbol("[[size]]"); + const _usage = Symbol("[[usage]]"); + const _state = Symbol("[[state]]"); + const _mappingRange = Symbol("[[mapping_range]]"); + const _mappedRanges = Symbol("[[mapped_ranges]]"); + const _mapMode = Symbol("[[map_mode]]"); + + /** + * @typedef CreateGPUBufferOptions + * @property {ArrayBuffer | null} mapping + * @property {number[] | null} mappingRange + * @property {[ArrayBuffer, number, number][] | null} mappedRanges + * @property {"mapped" | "mapped at creation" | "mapped pending" | "unmapped" | "destroy" } state + */ + + /** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @param {number} size + * @param {number} usage + * @param {CreateGPUBufferOptions} options + * @returns {GPUBuffer} + */ + function createGPUBuffer(label, device, rid, size, usage, options) { + /** @type {GPUBuffer} */ + const buffer = webidl.createBranded(GPUBuffer); + buffer[_label] = label; + buffer[_device] = device; + buffer[_rid] = rid; + buffer[_size] = size; + buffer[_usage] = usage; + buffer[_mappingRange] = options.mappingRange; + buffer[_mappedRanges] = options.mappedRanges; + buffer[_state] = options.state; + return buffer; + } + + class GPUBuffer { + /** @type {InnerGPUDevice} */ + [_device]; + + /** @type {number} */ + [_rid]; + + /** @type {number} */ + [_size]; + + /** @type {number} */ + [_usage]; + + /** @type {"mapped" | "mapped at creation" | "mapped pending" | "unmapped" | "destroy"} */ + [_state]; + + /** @type {[number, number] | null} */ + [_mappingRange]; + + /** @type {[ArrayBuffer, number, number][] | null} */ + [_mappedRanges]; + + /** @type {number} */ + [_mapMode]; + + [_cleanup]() { + const mappedRanges = this[_mappedRanges]; + if (mappedRanges) { + while (mappedRanges.length > 0) { + const mappedRange = mappedRanges.pop(); + if (mappedRange !== undefined) { + core.close(mappedRange[1]); + } + } + } + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + this[_state] = "destroy"; + } + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {number} mode + * @param {number} offset + * @param {number} [size] + */ + async mapAsync(mode, offset = 0, size) { + webidl.assertBranded(this, GPUBuffer); + const prefix = "Failed to execute 'mapAsync' on 'GPUBuffer'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + mode = webidl.converters.GPUMapModeFlags(mode, { + prefix, + context: "Argument 1", + }); + offset = webidl.converters.GPUSize64(offset, { + prefix, + context: "Argument 2", + }); + size = size === undefined + ? undefined + : webidl.converters.GPUSize64(size, { + prefix, + context: "Argument 3", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const bufferRid = assertResource(this, { prefix, context: "this" }); + /** @type {number} */ + let rangeSize; + if (size === undefined) { + rangeSize = Math.max(0, this[_size] - offset); + } else { + rangeSize = this[_size]; + } + if ((offset % 8) !== 0) { + throw new DOMException( + `${prefix}: offset must be a multiple of 8.`, + "OperationError", + ); + } + if ((rangeSize % 4) !== 0) { + throw new DOMException( + `${prefix}: rangeSize must be a multiple of 4.`, + "OperationError", + ); + } + if ((offset + rangeSize) > this[_size]) { + throw new DOMException( + `${prefix}: offset + rangeSize must be less than or equal to buffer size.`, + "OperationError", + ); + } + if (this[_state] !== "unmapped") { + throw new DOMException( + `${prefix}: GPUBuffer is not currently unmapped.`, + "OperationError", + ); + } + const readMode = (mode & 0x0001) === 0x0001; + const writeMode = (mode & 0x0002) === 0x0002; + if ((readMode && writeMode) || (!readMode && !writeMode)) { + throw new DOMException( + `${prefix}: exactly one of READ or WRITE map mode must be set.`, + "OperationError", + ); + } + if (readMode && !((this[_usage] && 0x0001) === 0x0001)) { + throw new DOMException( + `${prefix}: READ map mode not valid because buffer does not have MAP_READ usage.`, + "OperationError", + ); + } + if (writeMode && !((this[_usage] && 0x0002) === 0x0002)) { + throw new DOMException( + `${prefix}: WRITE map mode not valid because buffer does not have MAP_WRITE usage.`, + "OperationError", + ); + } + + this[_mapMode] = mode; + this[_state] = "mapping pending"; + const { err } = await core.jsonOpAsync( + "op_webgpu_buffer_get_map_async", + { + bufferRid, + deviceRid: device.rid, + mode, + offset, + size: rangeSize, + }, + ); + device.pushError(err); + this[_state] = "mapped"; + this[_mappingRange] = [offset, offset + rangeSize]; + /** @type {[ArrayBuffer, number, number][] | null} */ + this[_mappedRanges] = []; + } + + /** + * @param {number} offset + * @param {number} size + */ + getMappedRange(offset = 0, size) { + webidl.assertBranded(this, GPUBuffer); + const prefix = "Failed to execute 'getMappedRange' on 'GPUBuffer'"; + offset = webidl.converters.GPUSize64(offset, { + prefix, + context: "Argument 1", + }); + size = size === undefined + ? undefined + : webidl.converters.GPUSize64(size, { + prefix, + context: "Argument 2", + }); + assertDevice(this, { prefix, context: "this" }); + const bufferRid = assertResource(this, { prefix, context: "this" }); + /** @type {number} */ + let rangeSize; + if (size === undefined) { + rangeSize = Math.max(0, this[_size] - offset); + } else { + rangeSize = this[_size]; + } + if (this[_state] !== "mapped" && this[_state] !== "mapped at creation") { + throw new DOMException( + `${prefix}: buffer is not mapped.`, + "OperationError", + ); + } + if ((offset % 8) !== 0) { + throw new DOMException( + `${prefix}: offset must be a multiple of 8.`, + "OperationError", + ); + } + if ((rangeSize % 4) !== 0) { + throw new DOMException( + `${prefix}: rangeSize must be a multiple of 4.`, + "OperationError", + ); + } + const mappingRange = this[_mappingRange]; + if (!mappingRange) { + throw new DOMException(`${prefix}: invalid state.`, "OperationError"); + } + if (offset < mappingRange[0]) { + throw new DOMException( + `${prefix}: offset is out of bounds.`, + "OperationError", + ); + } + if ((offset + rangeSize) > mappingRange[1]) { + throw new DOMException( + `${prefix}: offset is out of bounds.`, + "OperationError", + ); + } + const mappedRanges = this[_mappedRanges]; + if (!mappedRanges) { + throw new DOMException(`${prefix}: invalid state.`, "OperationError"); + } + for (const [buffer, _rid, start] of mappedRanges) { + // TODO(lucacasonato): is this logic correct? + const end = start + buffer.byteLength; + if ( + (start >= offset && start < (offset + rangeSize)) || + (end >= offset && end < (offset + rangeSize)) + ) { + throw new DOMException( + `${prefix}: requested buffer overlaps with another mapped range.`, + "OperationError", + ); + } + } + + const buffer = new ArrayBuffer(rangeSize); + const { rid } = core.jsonOpSync( + "op_webgpu_buffer_get_mapped_range", + { + bufferRid, + offset: offset - mappingRange[0], + size: rangeSize, + }, + new Uint8Array(buffer), + ); + + mappedRanges.push([buffer, rid, offset]); + + return buffer; + } + + unmap() { + webidl.assertBranded(this, GPUBuffer); + const prefix = "Failed to execute 'unmap' on 'GPUBuffer'"; + const device = assertDevice(this, { prefix, context: "this" }); + const bufferRid = assertResource(this, { prefix, context: "this" }); + if (this[_state] === "unmapped" || this[_state] === "destroyed") { + throw new DOMException( + `${prefix}: buffer is not ready to be unmapped.`, + "OperationError", + ); + } + if (this[_state] === "mapping pending") { + // TODO(lucacasonato): this is not spec compliant. + throw new DOMException( + `${prefix}: can not unmap while mapping. This is a Deno limitation.`, + "OperationError", + ); + } else if ( + this[_state] === "mapped" || this[_state] === "mapped at creation" + ) { + /** @type {boolean} */ + let write = false; + if (this[_state] === "mapped at creation") { + write = true; + } else if (this[_state] === "mapped") { + const mapMode = this[_mapMode]; + if (mapMode === undefined) { + throw new DOMException( + `${prefix}: invalid state.`, + "OperationError", + ); + } + if ((mapMode & 0x0002) === 0x0002) { + write = true; + } + } + + const mappedRanges = this[_mappedRanges]; + if (!mappedRanges) { + throw new DOMException(`${prefix}: invalid state.`, "OperationError"); + } + for (const [buffer, mappedRid] of mappedRanges) { + const { err } = core.jsonOpSync("op_webgpu_buffer_unmap", { + bufferRid, + mappedRid, + }, ...(write ? [new Uint8Array(buffer)] : [])); + device.pushError(err); + if (err) return; + } + this[_mappingRange] = null; + this[_mappedRanges] = null; + } + + this[_state] = "unmapped"; + } + + destroy() { + webidl.assertBranded(this, GPUBuffer); + this[_cleanup](); + } + + [Symbol.for("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } + } + GPUObjectBaseMixin("GPUBuffer", GPUBuffer); + + class GPUBufferUsage { + constructor() { + webidl.illegalConstructor(); + } + + static get MAP_READ() { + return 0x0001; + } + static get MAP_WRITE() { + return 0x0002; + } + static get COPY_SRC() { + return 0x0004; + } + static get COPY_DST() { + return 0x0008; + } + static get INDEX() { + return 0x0010; + } + static get VERTEX() { + return 0x0020; + } + static get UNIFORM() { + return 0x0040; + } + static get STORAGE() { + return 0x0080; + } + static get INDIRECT() { + return 0x0100; + } + static get QUERY_RESOLVE() { + return 0x0200; + } + } + + class GPUMapMode { + constructor() { + webidl.illegalConstructor(); + } + + static get READ() { + return 0x0001; + } + static get WRITE() { + return 0x0002; + } + } + + const _views = Symbol("[[views]]"); + + /** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUTexture} + */ + function createGPUTexture(label, device, rid) { + /** @type {GPUTexture} */ + const texture = webidl.createBranded(GPUTexture); + texture[_label] = label; + texture[_device] = device; + texture[_rid] = rid; + texture[_views] = []; + return texture; + } + + class GPUTexture { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + /** @type {WeakRef[]} */ + [_views]; + + [_cleanup]() { + const views = this[_views]; + while (views.length > 0) { + const view = views.pop()?.deref(); + if (view) { + view[_cleanup](); + } + } + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {GPUTextureViewDescriptor} descriptor + */ + createView(descriptor = {}) { + webidl.assertBranded(this, GPUTexture); + const prefix = "Failed to execute 'createView' on 'GPUTexture'"; + webidl.requiredArguments(arguments.length, 0, { prefix }); + descriptor = webidl.converters.GPUTextureViewDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const textureRid = assertResource(this, { prefix, context: "this" }); + const { rid, err } = core.jsonOpSync("op_webgpu_create_texture_view", { + textureRid, + ...descriptor, + }); + device.pushError(err); + + const textureView = createGPUTextureView( + descriptor.label ?? null, + this, + rid, + ); + this[_views].push(new WeakRef(textureView)); + return textureView; + } + + destroy() { + webidl.assertBranded(this, GPUTexture); + this[_cleanup](); + } + + [Symbol.for("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } + } + GPUObjectBaseMixin("GPUTexture", GPUTexture); + + class GPUTextureUsage { + constructor() { + webidl.illegalConstructor(); + } + + static get COPY_SRC() { + return 0x01; + } + static get COPY_DST() { + return 0x02; + } + static get SAMPLED() { + return 0x04; + } + static get STORAGE() { + return 0x08; + } + static get RENDER_ATTACHMENT() { + return 0x10; + } + } + + const _texture = Symbol("[[texture]]"); + + /** + * @param {string | null} label + * @param {GPUTexture} texture + * @param {number} rid + * @returns {GPUTextureView} + */ + function createGPUTextureView(label, texture, rid) { + /** @type {GPUTextureView} */ + const textureView = webidl.createBranded(GPUTextureView); + textureView[_label] = label; + textureView[_texture] = texture; + textureView[_rid] = rid; + return textureView; + } + class GPUTextureView { + /** @type {GPUTexture} */ + [_texture]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + [Symbol.for("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } + } + GPUObjectBaseMixin("GPUTextureView", GPUTextureView); + + /** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUSampler} + */ + function createGPUSampler(label, device, rid) { + /** @type {GPUSampler} */ + const sampler = webidl.createBranded(GPUSampler); + sampler[_label] = label; + sampler[_device] = device; + sampler[_rid] = rid; + return sampler; + } + class GPUSampler { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + [Symbol.for("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } + } + GPUObjectBaseMixin("GPUSampler", GPUSampler); + + /** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUBindGroupLayout} + */ + function createGPUBindGroupLayout(label, device, rid) { + /** @type {GPUBindGroupLayout} */ + const bindGroupLayout = webidl.createBranded(GPUBindGroupLayout); + bindGroupLayout[_label] = label; + bindGroupLayout[_device] = device; + bindGroupLayout[_rid] = rid; + return bindGroupLayout; + } + class GPUBindGroupLayout { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + [Symbol.for("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } + } + GPUObjectBaseMixin("GPUBindGroupLayout", GPUBindGroupLayout); + + /** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUPipelineLayout} + */ + function createGPUPipelineLayout(label, device, rid) { + /** @type {GPUPipelineLayout} */ + const pipelineLayout = webidl.createBranded(GPUPipelineLayout); + pipelineLayout[_label] = label; + pipelineLayout[_device] = device; + pipelineLayout[_rid] = rid; + return pipelineLayout; + } + class GPUPipelineLayout { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + [Symbol.for("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } + } + GPUObjectBaseMixin("GPUPipelineLayout", GPUPipelineLayout); + + /** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUBindGroup} + */ + function createGPUBindGroup(label, device, rid) { + /** @type {GPUBindGroup} */ + const bindGroup = webidl.createBranded(GPUBindGroup); + bindGroup[_label] = label; + bindGroup[_device] = device; + bindGroup[_rid] = rid; + return bindGroup; + } + class GPUBindGroup { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + [Symbol.for("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } + } + GPUObjectBaseMixin("GPUBindGroup", GPUBindGroup); + + /** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUShaderModule} + */ + function createGPUShaderModule(label, device, rid) { + /** @type {GPUShaderModule} */ + const bindGroup = webidl.createBranded(GPUShaderModule); + bindGroup[_label] = label; + bindGroup[_device] = device; + bindGroup[_rid] = rid; + return bindGroup; + } + class GPUShaderModule { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + compilationInfo() { + throw new Error("Not yet implemented"); + } + + [Symbol.for("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } + } + GPUObjectBaseMixin("GPUShaderModule", GPUShaderModule); + + class GPUShaderStage { + constructor() { + webidl.illegalConstructor(); + } + + static get VERTEX() { + return 0x1; + } + + static get FRAGMENT() { + return 0x2; + } + + static get COMPUTE() { + return 0x4; + } + } + + /** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUComputePipeline} + */ + function createGPUComputePipeline(label, device, rid) { + /** @type {GPUComputePipeline} */ + const pipeline = webidl.createBranded(GPUComputePipeline); + pipeline[_label] = label; + pipeline[_device] = device; + pipeline[_rid] = rid; + return pipeline; + } + class GPUComputePipeline { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {number} index + * @returns {GPUBindGroupLayout} + */ + getBindGroupLayout(index) { + webidl.assertBranded(this, GPURenderPipeline); + const prefix = + "Failed to execute 'getBindGroupLayout' on 'GPUComputePipeline'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + index = webidl.converters["unsigned long"](index, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const computePipelineRid = assertResource(this, { + prefix, + context: "this", + }); + const { rid, label, err } = core.jsonOpSync( + "op_webgpu_compute_pipeline_get_bind_group_layout", + { computePipelineRid, index }, + ); + device.pushError(err); + + const bindGroupLayout = createGPUBindGroupLayout( + label, + device, + rid, + ); + device.trackResource((bindGroupLayout)); + return bindGroupLayout; + } + + [Symbol.for("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } + } + GPUObjectBaseMixin("GPUComputePipeline", GPUComputePipeline); + + /** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPURenderPipeline} + */ + function createGPURenderPipeline(label, device, rid) { + /** @type {GPURenderPipeline} */ + const pipeline = webidl.createBranded(GPURenderPipeline); + pipeline[_label] = label; + pipeline[_device] = device; + pipeline[_rid] = rid; + return pipeline; + } + class GPURenderPipeline { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {number} index + */ + getBindGroupLayout(index) { + webidl.assertBranded(this, GPURenderPipeline); + const prefix = + "Failed to execute 'getBindGroupLayout' on 'GPURenderPipeline'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + index = webidl.converters["unsigned long"](index, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const renderPipelineRid = assertResource(this, { + prefix, + context: "this", + }); + const { rid, label, err } = core.jsonOpSync( + "op_webgpu_render_pipeline_get_bind_group_layout", + { renderPipelineRid, index }, + ); + device.pushError(err); + + const bindGroupLayout = createGPUBindGroupLayout( + label, + device, + rid, + ); + device.trackResource((bindGroupLayout)); + return bindGroupLayout; + } + + [Symbol.for("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } + } + GPUObjectBaseMixin("GPURenderPipeline", GPURenderPipeline); + + class GPUColorWrite { + constructor() { + webidl.illegalConstructor(); + } + + static get RED() { + return 0x1; + } + static get GREEN() { + return 0x2; + } + static get BLUE() { + return 0x4; + } + static get ALPHA() { + return 0x8; + } + static get ALL() { + return 0xF; + } + } + + const _encoders = Symbol("[[encoders]]"); + + /** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUCommandEncoder} + */ + function createGPUCommandEncoder(label, device, rid) { + /** @type {GPUCommandEncoder} */ + const encoder = webidl.createBranded(GPUCommandEncoder); + encoder[_label] = label; + encoder[_device] = device; + encoder[_rid] = rid; + encoder[_encoders] = []; + return encoder; + } + class GPUCommandEncoder { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + /** @type {WeakRef[]} */ + [_encoders]; + + [_cleanup]() { + const encoders = this[_encoders]; + while (encoders.length > 0) { + const encoder = encoders.pop()?.deref(); + if (encoder) { + encoder[_cleanup](); + } + } + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {GPURenderPassDescriptor} descriptor + * @return {GPURenderPassEncoder} + */ + beginRenderPass(descriptor) { + webidl.assertBranded(this, GPUCommandEncoder); + const prefix = + "Failed to execute 'beginRenderPass' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPURenderPassDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + + if (this[_rid] === undefined) { + throw new DOMException( + "Failed to execute 'beginRenderPass' on 'GPUCommandEncoder': already consumed", + "OperationError", + ); + } + + let depthStencilAttachment; + if (descriptor.depthStencilAttachment) { + const view = assertResource(descriptor.depthStencilAttachment.view, { + prefix, + context: "texture view for depth stencil attachment", + }); + assertDeviceMatch( + device, + descriptor.depthStencilAttachment.view[_texture], + { + prefix, + resourceContext: "texture view for depth stencil attachment", + selfContext: "this", + }, + ); + + depthStencilAttachment = { + ...descriptor.depthStencilAttachment, + view, + }; + + if ( + typeof descriptor.depthStencilAttachment.depthLoadValue === "string" + ) { + depthStencilAttachment.depthLoadOp = + descriptor.depthStencilAttachment.depthLoadValue; + } else { + depthStencilAttachment.depthLoadOp = "clear"; + depthStencilAttachment.depthLoadValue = + descriptor.depthStencilAttachment.depthLoadValue; + } + + if ( + typeof descriptor.depthStencilAttachment.stencilLoadValue === "string" + ) { + depthStencilAttachment.stencilLoadOp = + descriptor.depthStencilAttachment.stencilLoadValue; + depthStencilAttachment.stencilLoadValue = undefined; + } else { + depthStencilAttachment.stencilLoadOp = "clear"; + depthStencilAttachment.stencilLoadValue = + descriptor.depthStencilAttachment.stencilLoadValue; + } + } + const colorAttachments = descriptor.colorAttachments.map( + (colorAttachment, i) => { + const context = `color attachment ${i + 1}`; + const view = assertResource(colorAttachment.view, { + prefix, + context: `texture view for ${context}`, + }); + assertResource(colorAttachment.view[_texture], { + prefix, + context: `texture backing texture view for ${context}`, + }); + assertDeviceMatch( + device, + colorAttachment.view[_texture], + { + prefix, + resourceContext: `texture view for ${context}`, + selfContext: "this", + }, + ); + let resolveTarget; + if (colorAttachment.resolveTarget) { + resolveTarget = assertResource( + colorAttachment.resolveTarget, + { + prefix, + context: `resolve target texture view for ${context}`, + }, + ); + assertResource(colorAttachment.resolveTarget[_texture], { + prefix, + context: + `texture backing resolve target texture view for ${context}`, + }); + assertDeviceMatch( + device, + colorAttachment.resolveTarget[_texture], + { + prefix, + resourceContext: `resolve target texture view for ${context}`, + selfContext: "this", + }, + ); + } + const attachment = { + view: view, + resolveTarget, + storeOp: colorAttachment.storeOp, + }; + + if (typeof colorAttachment.loadValue === "string") { + attachment.loadOp = colorAttachment.loadValue; + } else { + attachment.loadOp = "clear"; + attachment.loadValue = normalizeGPUColor( + colorAttachment.loadValue, + ); + } + + return attachment; + }, + ); + + const { rid } = core.jsonOpSync( + "op_webgpu_command_encoder_begin_render_pass", + { + commandEncoderRid, + ...descriptor, + colorAttachments, + depthStencilAttachment, + }, + ); + + const renderPassEncoder = createGPURenderPassEncoder( + descriptor.label ?? null, + this, + rid, + ); + this[_encoders].push(new WeakRef(renderPassEncoder)); + return renderPassEncoder; + } + + /** + * @param {GPUComputePassDescriptor} descriptor + */ + beginComputePass(descriptor = {}) { + webidl.assertBranded(this, GPUCommandEncoder); + const prefix = + "Failed to execute 'beginComputePass' on 'GPUCommandEncoder'"; + descriptor = webidl.converters.GPUComputePassDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + + assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + + const { rid } = core.jsonOpSync( + "op_webgpu_command_encoder_begin_compute_pass", + { + commandEncoderRid, + ...descriptor, + }, + ); + + const computePassEncoder = createGPUComputePassEncoder( + descriptor.label ?? null, + this, + rid, + ); + this[_encoders].push(new WeakRef(computePassEncoder)); + return computePassEncoder; + } + + /** + * @param {GPUBuffer} source + * @param {number} sourceOffset + * @param {GPUBuffer} destination + * @param {number} destinationOffset + * @param {number} size + */ + copyBufferToBuffer( + source, + sourceOffset, + destination, + destinationOffset, + size, + ) { + webidl.assertBranded(this, GPUCommandEncoder); + const prefix = + "Failed to execute 'copyBufferToBuffer' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 5, { prefix }); + source = webidl.converters.GPUBuffer(source, { + prefix, + context: "Argument 1", + }); + sourceOffset = webidl.converters.GPUSize64(sourceOffset, { + prefix, + context: "Argument 2", + }); + destination = webidl.converters.GPUBuffer(destination, { + prefix, + context: "Argument 3", + }); + destinationOffset = webidl.converters.GPUSize64(destinationOffset, { + prefix, + context: "Argument 4", + }); + size = webidl.converters.GPUSize64(size, { + prefix, + context: "Argument 5", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const sourceRid = assertResource(source, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, source, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + const destinationRid = assertResource(destination, { + prefix, + context: "Argument 3", + }); + assertDeviceMatch(device, destination, { + prefix, + resourceContext: "Argument 3", + selfContext: "this", + }); + + const { err } = core.jsonOpSync( + "op_webgpu_command_encoder_copy_buffer_to_buffer", + { + commandEncoderRid, + source: sourceRid, + sourceOffset, + destination: destinationRid, + destinationOffset, + size, + }, + ); + device.pushError(err); + } + + /** + * @param {GPUImageCopyBuffer} source + * @param {GPUImageCopyTexture} destination + * @param {GPUExtent3D} copySize + */ + copyBufferToTexture(source, destination, copySize) { + webidl.assertBranded(this, GPUCommandEncoder); + const prefix = + "Failed to execute 'copyBufferToTexture' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 3, { prefix }); + source = webidl.converters.GPUImageCopyBuffer(source, { + prefix, + context: "Argument 1", + }); + destination = webidl.converters.GPUImageCopyTexture(destination, { + prefix, + context: "Argument 2", + }); + copySize = webidl.converters.GPUExtent3D(copySize, { + prefix, + context: "Argument 3", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const sourceBufferRid = assertResource(source.buffer, { + prefix, + context: "source in Argument 1", + }); + assertDeviceMatch(device, source.buffer, { + prefix, + resourceContext: "source in Argument 1", + selfContext: "this", + }); + const destinationTextureRid = assertResource(destination.texture, { + prefix, + context: "texture in Argument 2", + }); + assertDeviceMatch(device, destination.texture, { + prefix, + resourceContext: "texture in Argument 2", + selfContext: "this", + }); + + const { err } = core.jsonOpSync( + "op_webgpu_command_encoder_copy_buffer_to_texture", + { + commandEncoderRid, + source: { + ...source, + buffer: sourceBufferRid, + }, + destination: { + texture: destinationTextureRid, + mipLevel: destination.mipLevel, + origin: destination.origin + ? normalizeGPUOrigin3D(destination.origin) + : undefined, + }, + copySize: normalizeGPUExtent3D(copySize), + }, + ); + device.pushError(err); + } + + /** + * @param {GPUImageCopyTexture} source + * @param {GPUImageCopyBuffer} destination + * @param {GPUExtent3D} copySize + */ + copyTextureToBuffer(source, destination, copySize) { + webidl.assertBranded(this, GPUCommandEncoder); + const prefix = + "Failed to execute 'copyTextureToBuffer' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 3, { prefix }); + source = webidl.converters.GPUImageCopyTexture(source, { + prefix, + context: "Argument 1", + }); + destination = webidl.converters.GPUImageCopyBuffer(destination, { + prefix, + context: "Argument 2", + }); + copySize = webidl.converters.GPUExtent3D(copySize, { + prefix, + context: "Argument 3", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const sourceTextureRid = assertResource(source.texture, { + prefix, + context: "texture in Argument 1", + }); + assertDeviceMatch(device, source.texture, { + prefix, + resourceContext: "texture in Argument 1", + selfContext: "this", + }); + const destinationBufferRid = assertResource(destination.buffer, { + prefix, + context: "buffer in Argument 2", + }); + assertDeviceMatch(device, destination.buffer, { + prefix, + resourceContext: "buffer in Argument 2", + selfContext: "this", + }); + const { err } = core.jsonOpSync( + "op_webgpu_command_encoder_copy_texture_to_buffer", + { + commandEncoderRid, + source: { + texture: sourceTextureRid, + mipLevel: source.mipLevel, + origin: source.origin + ? normalizeGPUOrigin3D(source.origin) + : undefined, + }, + destination: { + ...destination, + buffer: destinationBufferRid, + }, + copySize: normalizeGPUExtent3D(copySize), + }, + ); + device.pushError(err); + } + + /** + * @param {GPUImageCopyTexture} source + * @param {GPUImageCopyTexture} destination + * @param {GPUExtent3D} copySize + */ + copyTextureToTexture(source, destination, copySize) { + webidl.assertBranded(this, GPUCommandEncoder); + const prefix = + "Failed to execute 'copyTextureToTexture' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 3, { prefix }); + source = webidl.converters.GPUImageCopyTexture(source, { + prefix, + context: "Argument 1", + }); + destination = webidl.converters.GPUImageCopyTexture(destination, { + prefix, + context: "Argument 2", + }); + copySize = webidl.converters.GPUExtent3D(copySize, { + prefix, + context: "Argument 3", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const sourceTextureRid = assertResource(source.texture, { + prefix, + context: "texture in Argument 1", + }); + assertDeviceMatch(device, source.texture, { + prefix, + resourceContext: "texture in Argument 1", + selfContext: "this", + }); + const destinationTextureRid = assertResource(destination.texture, { + prefix, + context: "texture in Argument 2", + }); + assertDeviceMatch(device, destination.texture, { + prefix, + resourceContext: "texture in Argument 2", + selfContext: "this", + }); + const { err } = core.jsonOpSync( + "op_webgpu_command_encoder_copy_texture_to_texture", + { + commandEncoderRid, + source: { + texture: sourceTextureRid, + mipLevel: source.mipLevel, + origin: source.origin + ? normalizeGPUOrigin3D(source.origin) + : undefined, + }, + destination: { + texture: destinationTextureRid, + mipLevel: destination.mipLevel, + origin: destination.origin + ? normalizeGPUOrigin3D(destination.origin) + : undefined, + }, + copySize: normalizeGPUExtent3D(copySize), + }, + ); + device.pushError(err); + } + + /** + * @param {string} groupLabel + */ + pushDebugGroup(groupLabel) { + webidl.assertBranded(this, GPUCommandEncoder); + const prefix = + "Failed to execute 'pushDebugGroup' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + groupLabel = webidl.converters.USVString(groupLabel, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const { err } = core.jsonOpSync( + "op_webgpu_command_encoder_push_debug_group", + { + commandEncoderRid, + groupLabel, + }, + ); + device.pushError(err); + } + + popDebugGroup() { + webidl.assertBranded(this, GPUCommandEncoder); + const prefix = "Failed to execute 'popDebugGroup' on 'GPUCommandEncoder'"; + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const { err } = core.jsonOpSync( + "op_webgpu_command_encoder_pop_debug_group", + { + commandEncoderRid, + }, + ); + device.pushError(err); + } + + /** + * @param {string} markerLabel + */ + insertDebugMarker(markerLabel) { + webidl.assertBranded(this, GPUCommandEncoder); + const prefix = + "Failed to execute 'insertDebugMarker' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + markerLabel = webidl.converters.USVString(markerLabel, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const { err } = core.jsonOpSync( + "op_webgpu_command_encoder_insert_debug_marker", + { + commandEncoderRid, + markerLabel, + }, + ); + device.pushError(err); + } + + /** + * @param {GPUQuerySet} querySet + * @param {number} queryIndex + */ + writeTimestamp(querySet, queryIndex) { + webidl.assertBranded(this, GPUCommandEncoder); + const prefix = + "Failed to execute 'writeTimestamp' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + querySet = webidl.converters.GPUQuerySet(querySet, { + prefix, + context: "Argument 1", + }); + queryIndex = webidl.converters.GPUSize32(queryIndex, { + prefix, + context: "Argument 2", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const querySetRid = assertResource(querySet, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, querySet, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + const { err } = core.jsonOpSync( + "op_webgpu_command_encoder_write_timestamp", + { + commandEncoderRid, + querySet: querySetRid, + queryIndex, + }, + ); + device.pushError(err); + } + + /** + * @param {GPUQuerySet} querySet + * @param {number} firstQuery + * @param {number} queryCount + * @param {GPUBuffer} destination + * @param {number} destinationOffset + */ + resolveQuerySet( + querySet, + firstQuery, + queryCount, + destination, + destinationOffset, + ) { + webidl.assertBranded(this, GPUCommandEncoder); + const prefix = + "Failed to execute 'resolveQuerySet' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 5, { prefix }); + querySet = webidl.converters.GPUQuerySet(querySet, { + prefix, + context: "Argument 1", + }); + firstQuery = webidl.converters.GPUSize32(firstQuery, { + prefix, + context: "Argument 2", + }); + queryCount = webidl.converters.GPUSize32(queryCount, { + prefix, + context: "Argument 3", + }); + destination = webidl.converters.GPUQuerySet(destination, { + prefix, + context: "Argument 4", + }); + destinationOffset = webidl.converters.GPUSize64(destinationOffset, { + prefix, + context: "Argument 5", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const querySetRid = assertResource(querySet, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, querySet, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + const destinationRid = assertResource(destination, { + prefix, + context: "Argument 3", + }); + assertDeviceMatch(device, destination, { + prefix, + resourceContext: "Argument 3", + selfContext: "this", + }); + const { err } = core.jsonOpSync( + "op_webgpu_command_encoder_resolve_query_set", + { + commandEncoderRid, + querySet: querySetRid, + firstQuery, + queryCount, + destination: destinationRid, + destinationOffset, + }, + ); + device.pushError(err); + } + + /** + * @param {GPUCommandBufferDescriptor} descriptor + * @returns {GPUCommandBuffer} + */ + finish(descriptor = {}) { + webidl.assertBranded(this, GPUCommandEncoder); + const prefix = "Failed to execute 'finish' on 'GPUCommandEncoder'"; + descriptor = webidl.converters.GPUCommandBufferDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const { rid, err } = core.jsonOpSync("op_webgpu_command_encoder_finish", { + commandEncoderRid, + ...descriptor, + }); + device.pushError(err); + /** @type {number | undefined} */ + this[_rid] = undefined; + + const commandBuffer = createGPUCommandBuffer( + descriptor.label ?? null, + device, + rid, + ); + device.trackResource((commandBuffer)); + return commandBuffer; + } + + [Symbol.for("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } + } + GPUObjectBaseMixin("GPUCommandEncoder", GPUCommandEncoder); + + const _encoder = Symbol("[[encoder]]"); + + /** + * @param {string | null} label + * @param {GPUCommandEncoder} encoder + * @param {number} rid + * @returns {GPURenderPassEncoder} + */ + function createGPURenderPassEncoder(label, encoder, rid) { + /** @type {GPURenderPassEncoder} */ + const passEncoder = webidl.createBranded(GPURenderPassEncoder); + passEncoder[_label] = label; + passEncoder[_encoder] = encoder; + passEncoder[_rid] = rid; + return passEncoder; + } + + class GPURenderPassEncoder { + /** @type {GPUCommandEncoder} */ + [_encoder]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {number} x + * @param {number} y + * @param {number} width + * @param {number} height + * @param {number} minDepth + * @param {number} maxDepth + */ + setViewport(x, y, width, height, minDepth, maxDepth) { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'setViewport' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 6, { prefix }); + x = webidl.converters.float(x, { prefix, context: "Argument 1" }); + y = webidl.converters.float(y, { prefix, context: "Argument 2" }); + width = webidl.converters.float(width, { prefix, context: "Argument 3" }); + height = webidl.converters.float(height, { + prefix, + context: "Argument 4", + }); + minDepth = webidl.converters.float(minDepth, { + prefix, + context: "Argument 5", + }); + maxDepth = webidl.converters.float(maxDepth, { + prefix, + context: "Argument 6", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + core.jsonOpSync("op_webgpu_render_pass_set_viewport", { + renderPassRid, + x, + y, + width, + height, + minDepth, + maxDepth, + }); + } + + /** + * + * @param {number} x + * @param {number} y + * @param {number} width + * @param {number} height + */ + setScissorRect(x, y, width, height) { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'setScissorRect' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 4, { prefix }); + x = webidl.converters.GPUIntegerCoordinate(x, { + prefix, + context: "Argument 1", + }); + y = webidl.converters.GPUIntegerCoordinate(y, { + prefix, + context: "Argument 2", + }); + width = webidl.converters.GPUIntegerCoordinate(width, { + prefix, + context: "Argument 3", + }); + height = webidl.converters.GPUIntegerCoordinate(height, { + prefix, + context: "Argument 4", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + core.jsonOpSync("op_webgpu_render_pass_set_scissor_rect", { + renderPassRid, + x, + y, + width, + height, + }); + } + + /** + * @param {GPUColor} color + */ + setBlendColor(color) { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'setBlendColor' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + color = webidl.converters.GPUColor(color, { + prefix, + context: "Argument 1", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + core.jsonOpSync("op_webgpu_render_pass_set_blend_color", { + renderPassRid, + color: normalizeGPUColor(color), + }); + } + + /** + * @param {number} reference + */ + setStencilReference(reference) { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'setStencilReference' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + reference = webidl.converters.GPUStencilValue(reference, { + prefix, + context: "Argument 1", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + core.jsonOpSync("op_webgpu_render_pass_set_stencil_reference", { + renderPassRid, + reference, + }); + } + + beginOcclusionQuery(_queryIndex) { + throw new Error("Not yet implemented"); + } + + endOcclusionQuery() { + throw new Error("Not yet implemented"); + } + + /** + * @param {GPUQuerySet} querySet + * @param {number} queryIndex + */ + beginPipelineStatisticsQuery(querySet, queryIndex) { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'beginPipelineStatisticsQuery' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + querySet = webidl.converters.GPUQuerySet(querySet, { + prefix, + context: "Argument 1", + }); + queryIndex = webidl.converters.GPUSize32(queryIndex, { + prefix, + context: "Argument 2", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const querySetRid = assertResource(querySet, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, querySet, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + core.jsonOpSync("op_webgpu_render_pass_begin_pipeline_statistics_query", { + renderPassRid, + querySet: querySetRid, + queryIndex, + }); + } + + endPipelineStatisticsQuery() { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'endPipelineStatisticsQuery' on 'GPURenderPassEncoder'"; + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + core.jsonOpSync("op_webgpu_render_pass_end_pipeline_statistics_query", { + renderPassRid, + }); + } + + /** + * @param {GPUQuerySet} querySet + * @param {number} queryIndex + */ + writeTimestamp(querySet, queryIndex) { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'writeTimestamp' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + querySet = webidl.converters.GPUQuerySet(querySet, { + prefix, + context: "Argument 1", + }); + queryIndex = webidl.converters.GPUSize32(queryIndex, { + prefix, + context: "Argument 2", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const querySetRid = assertResource(querySet, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, querySet, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + core.jsonOpSync("op_webgpu_render_pass_write_timestamp", { + renderPassRid, + querySet: querySetRid, + queryIndex, + }); + } + + /** + * @param {GPURenderBundle[]} bundles + */ + executeBundles(bundles) { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'executeBundles' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + bundles = webidl.converters["sequence"](bundles, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const bundleRids = bundles.map((bundle, i) => { + const context = `bundle ${i + 1}`; + const rid = assertResource(bundle, { prefix, context }); + assertDeviceMatch(device, bundle, { + prefix, + resourceContext: context, + selfContext: "this", + }); + return rid; + }); + core.jsonOpSync("op_webgpu_render_pass_execute_bundles", { + renderPassRid, + bundles: bundleRids, + }); + } + + endPass() { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = "Failed to execute 'endPass' on 'GPURenderPassEncoder'"; + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const commandEncoderRid = assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const { err } = core.jsonOpSync("op_webgpu_render_pass_end_pass", { + commandEncoderRid, + renderPassRid, + }); + device.pushError(err); + this[_rid] = undefined; + } + + // TODO(lucacasonato): has an overload + setBindGroup( + index, + bindGroup, + dynamicOffsetsData, + dynamicOffsetsDataStart, + dynamicOffsetsDataLength, + ) { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'setBindGroup' on 'GPURenderPassEncoder'"; + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const bindGroupRid = assertResource(bindGroup, { + prefix, + context: "Argument 2", + }); + assertDeviceMatch(device, bindGroup, { + prefix, + resourceContext: "Argument 2", + selfContext: "this", + }); + if (dynamicOffsetsData instanceof Uint32Array) { + core.jsonOpSync( + "op_webgpu_render_pass_set_bind_group", + { + renderPassRid, + index, + bindGroup: bindGroupRid, + dynamicOffsetsDataStart, + dynamicOffsetsDataLength, + }, + dynamicOffsetsData, + ); + } else { + dynamicOffsetsData ??= []; + core.jsonOpSync("op_webgpu_render_pass_set_bind_group", { + renderPassRid, + index, + bindGroup: bindGroupRid, + dynamicOffsetsData, + dynamicOffsetsDataStart: 0, + dynamicOffsetsDataLength: dynamicOffsetsData.length, + }); + } + } + + /** + * @param {string} groupLabel + */ + pushDebugGroup(groupLabel) { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'pushDebugGroup' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + groupLabel = webidl.converters.USVString(groupLabel, { + prefix, + context: "Argument 1", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + core.jsonOpSync("op_webgpu_render_pass_push_debug_group", { + renderPassRid, + groupLabel, + }); + } + + popDebugGroup() { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'popDebugGroup' on 'GPURenderPassEncoder'"; + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + core.jsonOpSync("op_webgpu_render_pass_pop_debug_group", { + renderPassRid, + }); + } + + /** + * @param {string} markerLabel + */ + insertDebugMarker(markerLabel) { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'insertDebugMarker' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + markerLabel = webidl.converters.USVString(markerLabel, { + prefix, + context: "Argument 1", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + core.jsonOpSync("op_webgpu_render_pass_insert_debug_marker", { + renderPassRid, + markerLabel, + }); + } + + /** + * @param {GPURenderPipeline} pipeline + */ + setPipeline(pipeline) { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'setPipeline' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + pipeline = webidl.converters.GPURenderPipeline(pipeline, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const pipelineRid = assertResource(pipeline, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, pipeline, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + core.jsonOpSync("op_webgpu_render_pass_set_pipeline", { + renderPassRid, + pipeline: pipelineRid, + }); + } + + /** + * @param {GPUBuffer} buffer + * @param {GPUIndexFormat} indexFormat + * @param {number} offset + * @param {number} size + */ + setIndexBuffer(buffer, indexFormat, offset = 0, size = 0) { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'setIndexBuffer' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + buffer = webidl.converters.GPUBuffer(buffer, { + prefix, + context: "Argument 1", + }); + indexFormat = webidl.converters.GPUIndexFormat(indexFormat, { + prefix, + context: "Argument 2", + }); + offset = webidl.converters.GPUSize64(offset, { + prefix, + context: "Argument 3", + }); + size = webidl.converters.GPUSize64(size, { + prefix, + context: "Argument 4", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const bufferRid = assertResource(buffer, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, buffer, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + core.jsonOpSync("op_webgpu_render_pass_set_index_buffer", { + renderPassRid, + buffer: bufferRid, + indexFormat, + offset, + size, + }); + } + + /** + * @param {number} slot + * @param {GPUBuffer} buffer + * @param {number} offset + * @param {number} size + */ + setVertexBuffer(slot, buffer, offset = 0, size = 0) { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'setVertexBuffer' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + slot = webidl.converters.GPUSize32(slot, { + prefix, + context: "Argument 2", + }); + buffer = webidl.converters.GPUBuffer(buffer, { + prefix, + context: "Argument 2", + }); + offset = webidl.converters.GPUSize64(offset, { + prefix, + context: "Argument 3", + }); + size = webidl.converters.GPUSize64(size, { + prefix, + context: "Argument 4", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const bufferRid = assertResource(buffer, { + prefix, + context: "Argument 2", + }); + assertDeviceMatch(device, buffer, { + prefix, + resourceContext: "Argument 2", + selfContext: "this", + }); + core.jsonOpSync("op_webgpu_render_pass_set_vertex_buffer", { + renderPassRid, + slot, + buffer: bufferRid, + offset, + size, + }); + } + + /** + * @param {number} vertexCount + * @param {number} instanceCount + * @param {number} firstVertex + * @param {number} firstInstance + */ + draw(vertexCount, instanceCount = 1, firstVertex = 0, firstInstance = 0) { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = "Failed to execute 'draw' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + vertexCount = webidl.converters.GPUSize32(vertexCount, { + prefix, + context: "Argument 1", + }); + instanceCount = webidl.converters.GPUSize32(instanceCount, { + prefix, + context: "Argument 2", + }); + firstVertex = webidl.converters.GPUSize32(firstVertex, { + prefix, + context: "Argument 3", + }); + firstInstance = webidl.converters.GPUSize32(firstInstance, { + prefix, + context: "Argument 4", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + core.jsonOpSync("op_webgpu_render_pass_draw", { + renderPassRid, + vertexCount, + instanceCount, + firstVertex, + firstInstance, + }); + } + + /** + * @param {number} indexCount + * @param {number} instanceCount + * @param {number} firstIndex + * @param {number} baseVertex + * @param {number} firstInstance + */ + drawIndexed( + indexCount, + instanceCount = 1, + firstIndex = 0, + baseVertex = 0, + firstInstance = 0, + ) { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'drawIndexed' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + indexCount = webidl.converters.GPUSize32(indexCount, { + prefix, + context: "Argument 1", + }); + instanceCount = webidl.converters.GPUSize32(instanceCount, { + prefix, + context: "Argument 2", + }); + firstIndex = webidl.converters.GPUSize32(firstIndex, { + prefix, + context: "Argument 3", + }); + baseVertex = webidl.converters.GPUSignedOffset32(baseVertex, { + prefix, + context: "Argument 4", + }); + firstInstance = webidl.converters.GPUSize32(firstInstance, { + prefix, + context: "Argument 5", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + core.jsonOpSync("op_webgpu_render_pass_draw_indexed", { + renderPassRid, + indexCount, + instanceCount, + firstIndex, + baseVertex, + firstInstance, + }); + } + + /** + * @param {GPUBuffer} indirectBuffer + * @param {number} indirectOffset + */ + drawIndirect(indirectBuffer, indirectOffset) { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'drawIndirect' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + indirectBuffer = webidl.converters.GPUBuffer(indirectBuffer, { + prefix, + context: "Argument 1", + }); + indirectOffset = webidl.converters.GPUSize64(indirectOffset, { + prefix, + context: "Argument 2", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const indirectBufferRid = assertResource(indirectBuffer, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, indirectBuffer, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + core.jsonOpSync("op_webgpu_render_pass_draw_indirect", { + renderPassRid, + indirectBuffer: indirectBufferRid, + indirectOffset, + }); + } + + /** + * @param {GPUBuffer} indirectBuffer + * @param {number} indirectOffset + */ + drawIndexedIndirect(indirectBuffer, indirectOffset) { + webidl.assertBranded(this, GPURenderPassEncoder); + const prefix = + "Failed to execute 'drawIndirect' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + indirectBuffer = webidl.converters.GPUBuffer(indirectBuffer, { + prefix, + context: "Argument 1", + }); + indirectOffset = webidl.converters.GPUSize64(indirectOffset, { + prefix, + context: "Argument 2", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const indirectBufferRid = assertResource(indirectBuffer, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, indirectBuffer, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + core.jsonOpSync("op_webgpu_render_pass_draw_indexed_indirect", { + renderPassRid, + indirectBuffer: indirectBufferRid, + indirectOffset, + }); + } + + [Symbol.for("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } + } + GPUObjectBaseMixin("GPURenderPassEncoder", GPURenderPassEncoder); + + /** + * @param {string | null} label + * @param {GPUCommandEncoder} encoder + * @param {number} rid + * @returns {GPUComputePassEncoder} + */ + function createGPUComputePassEncoder(label, encoder, rid) { + /** @type {GPUComputePassEncoder} */ + const computePassEncoder = webidl.createBranded(GPUComputePassEncoder); + computePassEncoder[_label] = label; + computePassEncoder[_encoder] = encoder; + computePassEncoder[_rid] = rid; + return computePassEncoder; + } + + class GPUComputePassEncoder { + /** @type {GPUCommandEncoder} */ + [_encoder]; + + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {GPUComputePipeline} pipeline + */ + setPipeline(pipeline) { + webidl.assertBranded(this, GPUComputePassEncoder); + const prefix = + "Failed to execute 'setPipeline' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + pipeline = webidl.converters.GPUComputePipeline(pipeline, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + const pipelineRid = assertResource(pipeline, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, pipeline, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + core.jsonOpSync("op_webgpu_compute_pass_set_pipeline", { + computePassRid, + pipeline: pipelineRid, + }); + } + + /** + * @param {number} x + * @param {number} y + * @param {number} z + */ + dispatch(x, y = 1, z = 1) { + webidl.assertBranded(this, GPUComputePassEncoder); + const prefix = "Failed to execute 'dispatch' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + x = webidl.converters.GPUSize32(x, { prefix, context: "Argument 1" }); + y = webidl.converters.GPUSize32(y, { prefix, context: "Argument 2" }); + z = webidl.converters.GPUSize32(z, { prefix, context: "Argument 3" }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + core.jsonOpSync("op_webgpu_compute_pass_dispatch", { + computePassRid, + x, + y, + z, + }); + } + + /** + * @param {GPUBuffer} indirectBuffer + * @param {number} indirectOffset + */ + dispatchIndirect(indirectBuffer, indirectOffset) { + webidl.assertBranded(this, GPUComputePassEncoder); + const prefix = + "Failed to execute 'dispatchIndirect' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + indirectBuffer = webidl.converters.GPUBuffer(indirectBuffer, { + prefix, + context: "Argument 1", + }); + indirectOffset = webidl.converters.GPUSize64(indirectOffset, { + prefix, + context: "Argument 2", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + const indirectBufferRid = assertResource(indirectBuffer, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, indirectBuffer, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + core.jsonOpSync("op_webgpu_compute_pass_dispatch_indirect", { + computePassRid: computePassRid, + indirectBuffer: indirectBufferRid, + indirectOffset, + }); + } + + /** + * @param {GPUQuerySet} querySet + * @param {number} queryIndex + */ + beginPipelineStatisticsQuery(querySet, queryIndex) { + webidl.assertBranded(this, GPUComputePassEncoder); + const prefix = + "Failed to execute 'beginPipelineStatisticsQuery' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + querySet = webidl.converters.GPUQuerySet(querySet, { + prefix, + context: "Argument 1", + }); + queryIndex = webidl.converters.GPUSize32(queryIndex, { + prefix, + context: "Argument 2", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + const querySetRid = assertResource(querySet, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, querySet, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + core.jsonOpSync( + "op_webgpu_compute_pass_begin_pipeline_statistics_query", + { + computePassRid, + querySet: querySetRid, + queryIndex, + }, + ); + } + + endPipelineStatisticsQuery() { + webidl.assertBranded(this, GPUComputePassEncoder); + const prefix = + "Failed to execute 'endPipelineStatisticsQuery' on 'GPUComputePassEncoder'"; + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + core.jsonOpSync("op_webgpu_compute_pass_end_pipeline_statistics_query", { + computePassRid, + }); + } + + /** + * @param {GPUQuerySet} querySet + * @param {number} queryIndex + */ + writeTimestamp(querySet, queryIndex) { + webidl.assertBranded(this, GPUComputePassEncoder); + const prefix = + "Failed to execute 'writeTimestamp' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + querySet = webidl.converters.GPUQuerySet(querySet, { + prefix, + context: "Argument 1", + }); + queryIndex = webidl.converters.GPUSize32(queryIndex, { + prefix, + context: "Argument 2", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + const querySetRid = assertResource(querySet, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, querySet, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + core.jsonOpSync("op_webgpu_compute_pass_write_timestamp", { + computePassRid, + querySet: querySetRid, + queryIndex, + }); + } + + endPass() { + webidl.assertBranded(this, GPUComputePassEncoder); + const prefix = "Failed to execute 'endPass' on 'GPUComputePassEncoder'"; + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const commandEncoderRid = assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + const { err } = core.jsonOpSync("op_webgpu_compute_pass_end_pass", { + commandEncoderRid, + computePassRid, + }); + device.pushError(err); + this[_rid] = undefined; + } + + // TODO(lucacasonato): has an overload + setBindGroup( + index, + bindGroup, + dynamicOffsetsData, + dynamicOffsetsDataStart, + dynamicOffsetsDataLength, + ) { + webidl.assertBranded(this, GPUComputePassEncoder); + const prefix = + "Failed to execute 'setBindGroup' on 'GPUComputePassEncoder'"; + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + const bindGroupRid = assertResource(bindGroup, { + prefix, + context: "Argument 2", + }); + assertDeviceMatch(device, bindGroup, { + prefix, + resourceContext: "Argument 2", + selfContext: "this", + }); + if (dynamicOffsetsData instanceof Uint32Array) { + core.jsonOpSync( + "op_webgpu_compute_pass_set_bind_group", + { + computePassRid, + index, + bindGroup: bindGroupRid, + dynamicOffsetsDataStart, + dynamicOffsetsDataLength, + }, + dynamicOffsetsData, + ); + } else { + dynamicOffsetsData ??= []; + core.jsonOpSync("op_webgpu_compute_pass_set_bind_group", { + computePassRid, + index, + bindGroup: bindGroupRid, + dynamicOffsetsData, + dynamicOffsetsDataStart: 0, + dynamicOffsetsDataLength: dynamicOffsetsData.length, + }); + } + } + + /** + * @param {string} groupLabel + */ + pushDebugGroup(groupLabel) { + webidl.assertBranded(this, GPUComputePassEncoder); + const prefix = + "Failed to execute 'pushDebugGroup' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + groupLabel = webidl.converters.USVString(groupLabel, { + prefix, + context: "Argument 1", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + core.jsonOpSync("op_webgpu_compute_pass_push_debug_group", { + computePassRid, + groupLabel, + }); + } + + popDebugGroup() { + webidl.assertBranded(this, GPUComputePassEncoder); + const prefix = + "Failed to execute 'popDebugGroup' on 'GPUComputePassEncoder'"; + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + core.jsonOpSync("op_webgpu_compute_pass_pop_debug_group", { + computePassRid, + }); + } + + /** + * @param {string} markerLabel + */ + insertDebugMarker(markerLabel) { + webidl.assertBranded(this, GPUComputePassEncoder); + const prefix = + "Failed to execute 'insertDebugMarker' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + markerLabel = webidl.converters.USVString(markerLabel, { + prefix, + context: "Argument 1", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + core.jsonOpSync("op_webgpu_compute_pass_insert_debug_marker", { + computePassRid, + markerLabel, + }); + } + + [Symbol.for("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } + } + GPUObjectBaseMixin("GPUComputePassEncoder", GPUComputePassEncoder); + + /** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUCommandBuffer} + */ + function createGPUCommandBuffer(label, device, rid) { + /** @type {GPUCommandBuffer} */ + const commandBuffer = webidl.createBranded(GPUCommandBuffer); + commandBuffer[_label] = label; + commandBuffer[_device] = device; + commandBuffer[_rid] = rid; + return commandBuffer; + } + + class GPUCommandBuffer { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + get executionTime() { + throw new Error("Not yet implemented"); + } + + [Symbol.for("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + // TODO(crowlKats): executionTime + }) + }`; + } + } + GPUObjectBaseMixin("GPUCommandBuffer", GPUCommandBuffer); + + /** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPURenderBundleEncoder} + */ + function createGPURenderBundleEncoder(label, device, rid) { + /** @type {GPURenderBundleEncoder} */ + const bundleEncoder = webidl.createBranded(GPURenderBundleEncoder); + bundleEncoder[_label] = label; + bundleEncoder[_device] = device; + bundleEncoder[_rid] = rid; + return bundleEncoder; + } + + class GPURenderBundleEncoder { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {GPURenderBundleDescriptor} descriptor + */ + finish(descriptor = {}) { + webidl.assertBranded(this, GPURenderBundleEncoder); + const prefix = "Failed to execute 'finish' on 'GPURenderBundleEncoder'"; + descriptor = webidl.converters.GPURenderBundleDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const { rid, err } = core.jsonOpSync( + "op_webgpu_render_bundle_encoder_finish", + { + renderBundleEncoderRid, + ...descriptor, + }, + ); + device.pushError(err); + this[_rid] = undefined; + + const renderBundle = createGPURenderBundle( + descriptor.label ?? null, + device, + rid, + ); + device.trackResource((renderBundle)); + return renderBundle; + } + + // TODO(lucacasonato): has an overload + setBindGroup( + index, + bindGroup, + dynamicOffsetsData, + dynamicOffsetsDataStart, + dynamicOffsetsDataLength, + ) { + webidl.assertBranded(this, GPURenderBundleEncoder); + const prefix = + "Failed to execute 'setBindGroup' on 'GPURenderBundleEncoder'"; + const device = assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const bindGroupRid = assertResource(bindGroup, { + prefix, + context: "Argument 2", + }); + assertDeviceMatch(device, bindGroup, { + prefix, + resourceContext: "Argument 2", + selfContext: "this", + }); + if (dynamicOffsetsData instanceof Uint32Array) { + core.jsonOpSync( + "op_webgpu_render_bundle_encoder_set_bind_group", + { + renderBundleEncoderRid, + index, + bindGroup: bindGroupRid, + dynamicOffsetsDataStart, + dynamicOffsetsDataLength, + }, + dynamicOffsetsData, + ); + } else { + dynamicOffsetsData ??= []; + core.jsonOpSync("op_webgpu_render_bundle_encoder_set_bind_group", { + renderBundleEncoderRid, + index, + bindGroup: bindGroupRid, + dynamicOffsetsData, + dynamicOffsetsDataStart: 0, + dynamicOffsetsDataLength: dynamicOffsetsData.length, + }); + } + } + + /** + * @param {string} groupLabel + */ + pushDebugGroup(groupLabel) { + webidl.assertBranded(this, GPURenderBundleEncoder); + const prefix = + "Failed to execute 'pushDebugGroup' on 'GPURenderBundleEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + groupLabel = webidl.converters.USVString(groupLabel, { + prefix, + context: "Argument 1", + }); + assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + core.jsonOpSync("op_webgpu_render_bundle_encoder_push_debug_group", { + renderBundleEncoderRid, + groupLabel, + }); + } + + popDebugGroup() { + webidl.assertBranded(this, GPURenderBundleEncoder); + const prefix = + "Failed to execute 'popDebugGroup' on 'GPURenderBundleEncoder'"; + assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + core.jsonOpSync("op_webgpu_render_bundle_encoder_pop_debug_group", { + renderBundleEncoderRid, + }); + } + + /** + * @param {string} markerLabel + */ + insertDebugMarker(markerLabel) { + webidl.assertBranded(this, GPURenderBundleEncoder); + const prefix = + "Failed to execute 'insertDebugMarker' on 'GPURenderBundleEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + markerLabel = webidl.converters.USVString(markerLabel, { + prefix, + context: "Argument 1", + }); + assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + core.jsonOpSync("op_webgpu_render_bundle_encoder_push_debug_group", { + renderBundleEncoderRid, + markerLabel, + }); + } + + /** + * @param {GPURenderPipeline} pipeline + */ + setPipeline(pipeline) { + webidl.assertBranded(this, GPURenderBundleEncoder); + const prefix = + "Failed to execute 'setPipeline' on 'GPURenderBundleEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + pipeline = webidl.converters.GPURenderPipeline(pipeline, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const pipelineRid = assertResource(pipeline, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, pipeline, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + core.jsonOpSync("op_webgpu_render_bundle_encoder_set_pipeline", { + renderBundleEncoderRid, + pipeline: pipelineRid, + }); + } + + /** + * @param {GPUBuffer} buffer + * @param {GPUIndexFormat} indexFormat + * @param {number} offset + * @param {number} size + */ + setIndexBuffer(buffer, indexFormat, offset = 0, size = 0) { + webidl.assertBranded(this, GPURenderBundleEncoder); + const prefix = + "Failed to execute 'setIndexBuffer' on 'GPURenderBundleEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + buffer = webidl.converters.GPUBuffer(buffer, { + prefix, + context: "Argument 1", + }); + indexFormat = webidl.converters.GPUIndexFormat(indexFormat, { + prefix, + context: "Argument 2", + }); + offset = webidl.converters.GPUSize64(offset, { + prefix, + context: "Argument 3", + }); + size = webidl.converters.GPUSize64(size, { + prefix, + context: "Argument 4", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const bufferRid = assertResource(buffer, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, buffer, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + core.jsonOpSync("op_webgpu_render_bundle_encoder_set_index_buffer", { + renderBundleEncoderRid, + buffer: bufferRid, + indexFormat, + offset, + size, + }); + } + + /** + * @param {number} slot + * @param {GPUBuffer} buffer + * @param {number} offset + * @param {number} size + */ + setVertexBuffer(slot, buffer, offset = 0, size = 0) { + webidl.assertBranded(this, GPURenderBundleEncoder); + const prefix = + "Failed to execute 'setVertexBuffer' on 'GPURenderBundleEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + slot = webidl.converters.GPUSize32(slot, { + prefix, + context: "Argument 2", + }); + buffer = webidl.converters.GPUBuffer(buffer, { + prefix, + context: "Argument 2", + }); + offset = webidl.converters.GPUSize64(offset, { + prefix, + context: "Argument 3", + }); + size = webidl.converters.GPUSize64(size, { + prefix, + context: "Argument 4", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const bufferRid = assertResource(buffer, { + prefix, + context: "Argument 2", + }); + assertDeviceMatch(device, buffer, { + prefix, + resourceContext: "Argument 2", + selfContext: "this", + }); + core.jsonOpSync("op_webgpu_render_bundle_encoder_set_vertex_buffer", { + renderBundleEncoderRid, + slot, + buffer: bufferRid, + offset, + size, + }); + } + + /** + * @param {number} vertexCount + * @param {number} instanceCount + * @param {number} firstVertex + * @param {number} firstInstance + */ + draw(vertexCount, instanceCount = 1, firstVertex = 0, firstInstance = 0) { + webidl.assertBranded(this, GPURenderBundleEncoder); + const prefix = "Failed to execute 'draw' on 'GPURenderBundleEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + vertexCount = webidl.converters.GPUSize32(vertexCount, { + prefix, + context: "Argument 1", + }); + instanceCount = webidl.converters.GPUSize32(instanceCount, { + prefix, + context: "Argument 2", + }); + firstVertex = webidl.converters.GPUSize32(firstVertex, { + prefix, + context: "Argument 3", + }); + firstInstance = webidl.converters.GPUSize32(firstInstance, { + prefix, + context: "Argument 4", + }); + assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + core.jsonOpSync("op_webgpu_render_bundle_encoder_draw", { + renderBundleEncoderRid, + vertexCount, + instanceCount, + firstVertex, + firstInstance, + }); + } + + /** + * @param {number} indexCount + * @param {number} instanceCount + * @param {number} firstIndex + * @param {number} baseVertex + * @param {number} firstInstance + */ + drawIndexed( + indexCount, + instanceCount = 1, + firstIndex = 0, + baseVertex = 0, + firstInstance = 0, + ) { + webidl.assertBranded(this, GPURenderBundleEncoder); + const prefix = + "Failed to execute 'drawIndexed' on 'GPURenderBundleEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + indexCount = webidl.converters.GPUSize32(indexCount, { + prefix, + context: "Argument 1", + }); + instanceCount = webidl.converters.GPUSize32(instanceCount, { + prefix, + context: "Argument 2", + }); + firstIndex = webidl.converters.GPUSize32(firstIndex, { + prefix, + context: "Argument 3", + }); + baseVertex = webidl.converters.GPUSignedOffset32(baseVertex, { + prefix, + context: "Argument 4", + }); + firstInstance = webidl.converters.GPUSize32(firstInstance, { + prefix, + context: "Argument 5", + }); + assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + core.jsonOpSync("op_webgpu_render_bundle_encoder_draw_indexed", { + renderBundleEncoderRid, + indexCount, + instanceCount, + firstIndex, + baseVertex, + firstInstance, + }); + } + + /** + * @param {GPUBuffer} indirectBuffer + * @param {number} indirectOffset + */ + drawIndirect(indirectBuffer, indirectOffset) { + webidl.assertBranded(this, GPURenderBundleEncoder); + const prefix = + "Failed to execute 'drawIndirect' on 'GPURenderBundleEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + indirectBuffer = webidl.converters.GPUBuffer(indirectBuffer, { + prefix, + context: "Argument 1", + }); + indirectOffset = webidl.converters.GPUSize64(indirectOffset, { + prefix, + context: "Argument 2", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const indirectBufferRid = assertResource(indirectBuffer, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, indirectBuffer, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + core.jsonOpSync("op_webgpu_render_bundle_encoder_draw_indirect", { + renderBundleEncoderRid, + indirectBuffer: indirectBufferRid, + indirectOffset, + }); + } + + drawIndexedIndirect(_indirectBuffer, _indirectOffset) { + throw new Error("Not yet implemented"); + } + + [Symbol.for("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } + } + GPUObjectBaseMixin("GPURenderBundleEncoder", GPURenderBundleEncoder); + + /** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPURenderBundle} + */ + function createGPURenderBundle(label, device, rid) { + /** @type {GPURenderBundle} */ + const bundle = webidl.createBranded(GPURenderBundle); + bundle[_label] = label; + bundle[_device] = device; + bundle[_rid] = rid; + return bundle; + } + + class GPURenderBundle { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + [Symbol.for("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } + } + GPUObjectBaseMixin("GPURenderBundle", GPURenderBundle); + + const _descriptor = Symbol("[[descriptor]]"); + + /** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUQuerySet} + */ + function createGPUQuerySet(label, device, rid, descriptor) { + /** @type {GPUQuerySet} */ + const queue = webidl.createBranded(GPUQuerySet); + queue[_label] = label; + queue[_device] = device; + queue[_rid] = rid; + queue[_descriptor] = descriptor; + return queue; + } + + class GPUQuerySet { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + /** @type {GPUQuerySetDescriptor} */ + [_descriptor]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + destroy() { + webidl.assertBranded(this, GPUQuerySet); + this[_cleanup](); + } + + [Symbol.for("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } + } + GPUObjectBaseMixin("GPUQuerySet", GPUQuerySet); + + window.__bootstrap.webgpu = { + gpu: webidl.createBranded(GPU), + GPU, + GPUAdapter, + GPUAdapterLimits, + GPUAdapterFeatures, + GPUDevice, + GPUQueue, + GPUBuffer, + GPUBufferUsage, + GPUMapMode, + GPUTextureUsage, + GPUTexture, + GPUTextureView, + GPUSampler, + GPUBindGroupLayout, + GPUPipelineLayout, + GPUBindGroup, + GPUShaderModule, + GPUShaderStage, + GPUComputePipeline, + GPURenderPipeline, + GPUColorWrite, + GPUCommandEncoder, + GPURenderPassEncoder, + GPUComputePassEncoder, + GPUCommandBuffer, + GPURenderBundleEncoder, + GPURenderBundle, + GPUQuerySet, + GPUOutOfMemoryError, + GPUValidationError, + }; +})(this); diff --git a/op_crates/webgpu/02_idl_types.js b/op_crates/webgpu/02_idl_types.js new file mode 100644 index 00000000000000..68c3e324751dd8 --- /dev/null +++ b/op_crates/webgpu/02_idl_types.js @@ -0,0 +1,1800 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +// @ts-check +/// + +"use strict"; + +((window) => { + const webidl = window.__bootstrap.webidl; + const { + GPU, + GPUAdapter, + GPUAdapterLimits, + GPUAdapterFeatures, + GPUDevice, + GPUQueue, + GPUBuffer, + GPUBufferUsage, + GPUMapMode, + GPUTextureUsage, + GPUTexture, + GPUTextureView, + GPUSampler, + GPUBindGroupLayout, + GPUPipelineLayout, + GPUBindGroup, + GPUShaderModule, + GPUShaderStage, + GPUComputePipeline, + GPURenderPipeline, + GPUColorWrite, + GPUCommandEncoder, + GPURenderPassEncoder, + GPUComputePassEncoder, + GPUCommandBuffer, + GPURenderBundleEncoder, + GPURenderBundle, + GPUQuerySet, + GPUOutOfMemoryError, + GPUValidationError, + } = window.__bootstrap.webgpu; + + // This needs to be initalized after all of the base classes are implmented, + // otherwise their converters might not be available yet. + + // DICTIONARY: GPUObjectDescriptorBase + const dictMembersGPUObjectDescriptorBase = [ + { key: "label", converter: webidl.converters["USVString"] }, + ]; + webidl.converters["GPUObjectDescriptorBase"] = webidl + .createDictionaryConverter( + "GPUObjectDescriptorBase", + dictMembersGPUObjectDescriptorBase, + ); + + // INTERFACE: GPUAdapterLimits + webidl.converters.GPUAdapterLimits = webidl.createInterfaceConverter( + "GPUAdapterLimits", + GPUAdapterLimits, + ); + + // INTERFACE: GPUAdapterFeatures + webidl.converters.GPUAdapterFeatures = webidl.createInterfaceConverter( + "GPUAdapterFeatures", + GPUAdapterFeatures, + ); + + // INTERFACE: GPU + webidl.converters.GPU = webidl.createInterfaceConverter("GPU", GPU); + + // ENUM: GPUPowerPreference + webidl.converters["GPUPowerPreference"] = webidl.createEnumConverter( + "GPUPowerPreference", + [ + "low-power", + "high-performance", + ], + ); + + // DICTIONARY: GPURequestAdapterOptions + const dictMembersGPURequestAdapterOptions = [ + { + key: "powerPreference", + converter: webidl.converters["GPUPowerPreference"], + }, + ]; + webidl.converters["GPURequestAdapterOptions"] = webidl + .createDictionaryConverter( + "GPURequestAdapterOptions", + dictMembersGPURequestAdapterOptions, + ); + + // INTERFACE: GPUAdapter + webidl.converters.GPUAdapter = webidl.createInterfaceConverter( + "GPUAdapter", + GPUAdapter, + ); + + // ENUM: GPUFeatureName + webidl.converters["GPUFeatureName"] = webidl.createEnumConverter( + "GPUFeatureName", + [ + "depth-clamping", + "depth24unorm-stencil8", + "depth32float-stencil8", + "pipeline-statistics-query", + "texture-compression-bc", + "timestamp-query", + ], + ); + + // DICTIONARY: GPUDeviceDescriptor + const dictMembersGPUDeviceDescriptor = [ + { + key: "nonGuaranteedFeatures", + converter: webidl.createSequenceConverter( + webidl.converters["GPUFeatureName"], + ), + defaultValue: [], + }, + { + key: "nonGuaranteedLimits", + converter: webidl.converters.any, + defaultValue: {}, + }, + ]; + webidl.converters["GPUDeviceDescriptor"] = webidl.createDictionaryConverter( + "GPUDeviceDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUDeviceDescriptor, + ); + + // INTERFACE: GPUDevice + webidl.converters.GPUDevice = webidl.createInterfaceConverter( + "GPUDevice", + GPUDevice, + ); + + // INTERFACE: GPUBuffer + webidl.converters.GPUBuffer = webidl.createInterfaceConverter( + "GPUBuffer", + GPUBuffer, + ); + + // TYPEDEF: GPUSize64 + webidl.converters["GPUSize64"] = (V, opts) => + webidl.converters["unsigned long long"](V, { ...opts, enforceRange: true }); + + // TYPEDEF: GPUBufferUsageFlags + webidl.converters["GPUBufferUsageFlags"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + + // DICTIONARY: GPUBufferDescriptor + const dictMembersGPUBufferDescriptor = [ + { key: "size", converter: webidl.converters["GPUSize64"], required: true }, + { + key: "usage", + converter: webidl.converters["GPUBufferUsageFlags"], + required: true, + }, + { + key: "mappedAtCreation", + converter: webidl.converters["boolean"], + defaultValue: false, + }, + ]; + webidl.converters["GPUBufferDescriptor"] = webidl.createDictionaryConverter( + "GPUBufferDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUBufferDescriptor, + ); + + // INTERFACE: GPUBufferUsage + webidl.converters.GPUBufferUsage = webidl.createInterfaceConverter( + "GPUBufferUsage", + GPUBufferUsage, + ); + + // TYPEDEF: GPUMapModeFlags + webidl.converters["GPUMapModeFlags"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + + // INTERFACE: GPUMapMode + webidl.converters.GPUMapMode = webidl.createInterfaceConverter( + "GPUMapMode", + GPUMapMode, + ); + + // INTERFACE: GPUTexture + webidl.converters.GPUTexture = webidl.createInterfaceConverter( + "GPUTexture", + GPUTexture, + ); + + // TYPEDEF: GPUExtent3D + webidl.converters["GPUExtent3D"] = webidl.converters.any; + + // TYPEDEF: GPUIntegerCoordinate + webidl.converters["GPUIntegerCoordinate"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + + // TYPEDEF: GPUSize32 + webidl.converters["GPUSize32"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + + // ENUM: GPUTextureDimension + webidl.converters["GPUTextureDimension"] = webidl.createEnumConverter( + "GPUTextureDimension", + [ + "1d", + "2d", + "3d", + ], + ); + + // ENUM: GPUTextureFormat + webidl.converters["GPUTextureFormat"] = webidl.createEnumConverter( + "GPUTextureFormat", + [ + "r8unorm", + "r8snorm", + "r8uint", + "r8sint", + "r16uint", + "r16sint", + "r16float", + "rg8unorm", + "rg8snorm", + "rg8uint", + "rg8sint", + "r32uint", + "r32sint", + "r32float", + "rg16uint", + "rg16sint", + "rg16float", + "rgba8unorm", + "rgba8unorm-srgb", + "rgba8snorm", + "rgba8uint", + "rgba8sint", + "bgra8unorm", + "bgra8unorm-srgb", + "rgb9e5ufloat", + "rgb10a2unorm", + "rg11b10ufloat", + "rg32uint", + "rg32sint", + "rg32float", + "rgba16uint", + "rgba16sint", + "rgba16float", + "rgba32uint", + "rgba32sint", + "rgba32float", + "stencil8", + "depth16unorm", + "depth24plus", + "depth24plus-stencil8", + "depth32float", + "bc1-rgba-unorm", + "bc1-rgba-unorm-srgb", + "bc2-rgba-unorm", + "bc2-rgba-unorm-srgb", + "bc3-rgba-unorm", + "bc3-rgba-unorm-srgb", + "bc4-r-unorm", + "bc4-r-snorm", + "bc5-rg-unorm", + "bc5-rg-snorm", + "bc6h-rgb-ufloat", + "bc6h-rgb-float", + "bc7-rgba-unorm", + "bc7-rgba-unorm-srgb", + "depth24unorm-stencil8", + "depth32float-stencil8", + ], + ); + + // TYPEDEF: GPUTextureUsageFlags + webidl.converters["GPUTextureUsageFlags"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + + // DICTIONARY: GPUTextureDescriptor + const dictMembersGPUTextureDescriptor = [ + { + key: "size", + converter: webidl.converters["GPUExtent3D"], + required: true, + }, + { + key: "mipLevelCount", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 1, + }, + { + key: "sampleCount", + converter: webidl.converters["GPUSize32"], + defaultValue: 1, + }, + { + key: "dimension", + converter: webidl.converters["GPUTextureDimension"], + defaultValue: "2d", + }, + { + key: "format", + converter: webidl.converters["GPUTextureFormat"], + required: true, + }, + { + key: "usage", + converter: webidl.converters["GPUTextureUsageFlags"], + required: true, + }, + ]; + webidl.converters["GPUTextureDescriptor"] = webidl.createDictionaryConverter( + "GPUTextureDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUTextureDescriptor, + ); + + // INTERFACE: GPUTextureUsage + webidl.converters.GPUTextureUsage = webidl.createInterfaceConverter( + "GPUTextureUsage", + GPUTextureUsage, + ); + + // INTERFACE: GPUTextureView + webidl.converters.GPUTextureView = webidl.createInterfaceConverter( + "GPUTextureView", + GPUTextureView, + ); + + // ENUM: GPUTextureViewDimension + webidl.converters["GPUTextureViewDimension"] = webidl.createEnumConverter( + "GPUTextureViewDimension", + [ + "1d", + "2d", + "2d-array", + "cube", + "cube-array", + "3d", + ], + ); + + // ENUM: GPUTextureAspect + webidl.converters["GPUTextureAspect"] = webidl.createEnumConverter( + "GPUTextureAspect", + [ + "all", + "stencil-only", + "depth-only", + ], + ); + + // DICTIONARY: GPUTextureViewDescriptor + const dictMembersGPUTextureViewDescriptor = [ + { key: "format", converter: webidl.converters["GPUTextureFormat"] }, + { + key: "dimension", + converter: webidl.converters["GPUTextureViewDimension"], + }, + { + key: "aspect", + converter: webidl.converters["GPUTextureAspect"], + defaultValue: "all", + }, + { + key: "baseMipLevel", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 0, + }, + { + key: "mipLevelCount", + converter: webidl.converters["GPUIntegerCoordinate"], + }, + { + key: "baseArrayLayer", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 0, + }, + { + key: "arrayLayerCount", + converter: webidl.converters["GPUIntegerCoordinate"], + }, + ]; + webidl.converters["GPUTextureViewDescriptor"] = webidl + .createDictionaryConverter( + "GPUTextureViewDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUTextureViewDescriptor, + ); + + // INTERFACE: GPUSampler + webidl.converters.GPUSampler = webidl.createInterfaceConverter( + "GPUSampler", + GPUSampler, + ); + + // ENUM: GPUAddressMode + webidl.converters["GPUAddressMode"] = webidl.createEnumConverter( + "GPUAddressMode", + [ + "clamp-to-edge", + "repeat", + "mirror-repeat", + ], + ); + + // ENUM: GPUFilterMode + webidl.converters["GPUFilterMode"] = webidl.createEnumConverter( + "GPUFilterMode", + [ + "nearest", + "linear", + ], + ); + + // ENUM: GPUCompareFunction + webidl.converters["GPUCompareFunction"] = webidl.createEnumConverter( + "GPUCompareFunction", + [ + "never", + "less", + "equal", + "less-equal", + "greater", + "not-equal", + "greater-equal", + "always", + ], + ); + + // DICTIONARY: GPUSamplerDescriptor + const dictMembersGPUSamplerDescriptor = [ + { + key: "addressModeU", + converter: webidl.converters["GPUAddressMode"], + defaultValue: "clamp-to-edge", + }, + { + key: "addressModeV", + converter: webidl.converters["GPUAddressMode"], + defaultValue: "clamp-to-edge", + }, + { + key: "addressModeW", + converter: webidl.converters["GPUAddressMode"], + defaultValue: "clamp-to-edge", + }, + { + key: "magFilter", + converter: webidl.converters["GPUFilterMode"], + defaultValue: "nearest", + }, + { + key: "minFilter", + converter: webidl.converters["GPUFilterMode"], + defaultValue: "nearest", + }, + { + key: "mipmapFilter", + converter: webidl.converters["GPUFilterMode"], + defaultValue: "nearest", + }, + { + key: "lodMinClamp", + converter: webidl.converters["float"], + defaultValue: 0, + }, + { + key: "lodMaxClamp", + converter: webidl.converters["float"], + defaultValue: 0xffffffff, + }, + { key: "compare", converter: webidl.converters["GPUCompareFunction"] }, + { + key: "maxAnisotropy", + converter: (V, opts) => + webidl.converters["unsigned short"](V, { ...opts, clamp: true }), + defaultValue: 1, + }, + ]; + webidl.converters["GPUSamplerDescriptor"] = webidl.createDictionaryConverter( + "GPUSamplerDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUSamplerDescriptor, + ); + + // INTERFACE: GPUBindGroupLayout + webidl.converters.GPUBindGroupLayout = webidl.createInterfaceConverter( + "GPUBindGroupLayout", + GPUBindGroupLayout, + ); + + // TYPEDEF: GPUIndex32 + webidl.converters["GPUIndex32"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + + // TYPEDEF: GPUShaderStageFlags + webidl.converters["GPUShaderStageFlags"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + + // ENUM: GPUBufferBindingType + webidl.converters["GPUBufferBindingType"] = webidl.createEnumConverter( + "GPUBufferBindingType", + [ + "uniform", + "storage", + "read-only-storage", + ], + ); + + // DICTIONARY: GPUBufferBindingLayout + const dictMembersGPUBufferBindingLayout = [ + { + key: "type", + converter: webidl.converters["GPUBufferBindingType"], + defaultValue: "uniform", + }, + { + key: "hasDynamicOffset", + converter: webidl.converters["boolean"], + defaultValue: false, + }, + { + key: "minBindingSize", + converter: webidl.converters["GPUSize64"], + defaultValue: 0, + }, + ]; + webidl.converters["GPUBufferBindingLayout"] = webidl + .createDictionaryConverter( + "GPUBufferBindingLayout", + dictMembersGPUBufferBindingLayout, + ); + + // ENUM: GPUSamplerBindingType + webidl.converters["GPUSamplerBindingType"] = webidl.createEnumConverter( + "GPUSamplerBindingType", + [ + "filtering", + "non-filtering", + "comparison", + ], + ); + + // DICTIONARY: GPUSamplerBindingLayout + const dictMembersGPUSamplerBindingLayout = [ + { + key: "type", + converter: webidl.converters["GPUSamplerBindingType"], + defaultValue: "filtering", + }, + ]; + webidl.converters["GPUSamplerBindingLayout"] = webidl + .createDictionaryConverter( + "GPUSamplerBindingLayout", + dictMembersGPUSamplerBindingLayout, + ); + + // ENUM: GPUTextureSampleType + webidl.converters["GPUTextureSampleType"] = webidl.createEnumConverter( + "GPUTextureSampleType", + [ + "float", + "unfilterable-float", + "depth", + "sint", + "uint", + ], + ); + + // DICTIONARY: GPUTextureBindingLayout + const dictMembersGPUTextureBindingLayout = [ + { + key: "sampleType", + converter: webidl.converters["GPUTextureSampleType"], + defaultValue: "float", + }, + { + key: "viewDimension", + converter: webidl.converters["GPUTextureViewDimension"], + defaultValue: "2d", + }, + { + key: "multisampled", + converter: webidl.converters["boolean"], + defaultValue: false, + }, + ]; + webidl.converters["GPUTextureBindingLayout"] = webidl + .createDictionaryConverter( + "GPUTextureBindingLayout", + dictMembersGPUTextureBindingLayout, + ); + + // ENUM: GPUStorageTextureAccess + webidl.converters["GPUStorageTextureAccess"] = webidl.createEnumConverter( + "GPUStorageTextureAccess", + [ + "read-only", + "write-only", + ], + ); + + // DICTIONARY: GPUStorageTextureBindingLayout + const dictMembersGPUStorageTextureBindingLayout = [ + { + key: "access", + converter: webidl.converters["GPUStorageTextureAccess"], + required: true, + }, + { + key: "format", + converter: webidl.converters["GPUTextureFormat"], + required: true, + }, + { + key: "viewDimension", + converter: webidl.converters["GPUTextureViewDimension"], + defaultValue: "2d", + }, + ]; + webidl.converters["GPUStorageTextureBindingLayout"] = webidl + .createDictionaryConverter( + "GPUStorageTextureBindingLayout", + dictMembersGPUStorageTextureBindingLayout, + ); + + // DICTIONARY: GPUBindGroupLayoutEntry + const dictMembersGPUBindGroupLayoutEntry = [ + { + key: "binding", + converter: webidl.converters["GPUIndex32"], + required: true, + }, + { + key: "visibility", + converter: webidl.converters["GPUShaderStageFlags"], + required: true, + }, + { key: "buffer", converter: webidl.converters["GPUBufferBindingLayout"] }, + { key: "sampler", converter: webidl.converters["GPUSamplerBindingLayout"] }, + { key: "texture", converter: webidl.converters["GPUTextureBindingLayout"] }, + { + key: "storageTexture", + converter: webidl.converters["GPUStorageTextureBindingLayout"], + }, + ]; + webidl.converters["GPUBindGroupLayoutEntry"] = webidl + .createDictionaryConverter( + "GPUBindGroupLayoutEntry", + dictMembersGPUBindGroupLayoutEntry, + ); + + // DICTIONARY: GPUBindGroupLayoutDescriptor + const dictMembersGPUBindGroupLayoutDescriptor = [ + { + key: "entries", + converter: webidl.createSequenceConverter( + webidl.converters["GPUBindGroupLayoutEntry"], + ), + required: true, + }, + ]; + webidl.converters["GPUBindGroupLayoutDescriptor"] = webidl + .createDictionaryConverter( + "GPUBindGroupLayoutDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUBindGroupLayoutDescriptor, + ); + + // INTERFACE: GPUShaderStage + webidl.converters.GPUShaderStage = webidl.createInterfaceConverter( + "GPUShaderStage", + GPUShaderStage, + ); + + // INTERFACE: GPUBindGroup + webidl.converters.GPUBindGroup = webidl.createInterfaceConverter( + "GPUBindGroup", + GPUBindGroup, + ); + + // TYPEDEF: GPUBindingResource + webidl.converters["GPUBindingResource"] = webidl.converters.any; + + // DICTIONARY: GPUBindGroupEntry + const dictMembersGPUBindGroupEntry = [ + { + key: "binding", + converter: webidl.converters["GPUIndex32"], + required: true, + }, + { + key: "resource", + converter: webidl.converters["GPUBindingResource"], + required: true, + }, + ]; + webidl.converters["GPUBindGroupEntry"] = webidl.createDictionaryConverter( + "GPUBindGroupEntry", + dictMembersGPUBindGroupEntry, + ); + + // DICTIONARY: GPUBindGroupDescriptor + const dictMembersGPUBindGroupDescriptor = [ + { + key: "layout", + converter: webidl.converters["GPUBindGroupLayout"], + required: true, + }, + { + key: "entries", + converter: webidl.createSequenceConverter( + webidl.converters["GPUBindGroupEntry"], + ), + required: true, + }, + ]; + webidl.converters["GPUBindGroupDescriptor"] = webidl + .createDictionaryConverter( + "GPUBindGroupDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUBindGroupDescriptor, + ); + + // DICTIONARY: GPUBufferBinding + const dictMembersGPUBufferBinding = [ + { + key: "buffer", + converter: webidl.converters["GPUBuffer"], + required: true, + }, + { + key: "offset", + converter: webidl.converters["GPUSize64"], + defaultValue: 0, + }, + { key: "size", converter: webidl.converters["GPUSize64"] }, + ]; + webidl.converters["GPUBufferBinding"] = webidl.createDictionaryConverter( + "GPUBufferBinding", + dictMembersGPUBufferBinding, + ); + + // INTERFACE: GPUPipelineLayout + webidl.converters.GPUPipelineLayout = webidl.createInterfaceConverter( + "GPUPipelineLayout", + GPUPipelineLayout, + ); + + // DICTIONARY: GPUPipelineLayoutDescriptor + const dictMembersGPUPipelineLayoutDescriptor = [ + { + key: "bindGroupLayouts", + converter: webidl.createSequenceConverter( + webidl.converters["GPUBindGroupLayout"], + ), + required: true, + }, + ]; + webidl.converters["GPUPipelineLayoutDescriptor"] = webidl + .createDictionaryConverter( + "GPUPipelineLayoutDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUPipelineLayoutDescriptor, + ); + + // ENUM: GPUCompilationMessageType + webidl.converters["GPUCompilationMessageType"] = webidl.createEnumConverter( + "GPUCompilationMessageType", + [ + "error", + "warning", + "info", + ], + ); + + // // INTERFACE: GPUCompilationMessage + // webidl.converters.GPUCompilationMessage = webidl.createInterfaceConverter( + // "GPUCompilationMessage", + // GPUCompilationMessage, + // ); + + // // INTERFACE: GPUCompilationInfo + // webidl.converters.GPUCompilationInfo = webidl.createInterfaceConverter( + // "GPUCompilationInfo", + // GPUCompilationInfo, + // ); + + // INTERFACE: GPUShaderModule + webidl.converters.GPUShaderModule = webidl.createInterfaceConverter( + "GPUShaderModule", + GPUShaderModule, + ); + + // DICTIONARY: GPUShaderModuleDescriptor + const dictMembersGPUShaderModuleDescriptor = [ + { key: "code", converter: webidl.converters["USVString"], required: true }, + { key: "sourceMap", converter: webidl.converters["object"] }, + ]; + webidl.converters["GPUShaderModuleDescriptor"] = webidl + .createDictionaryConverter( + "GPUShaderModuleDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUShaderModuleDescriptor, + ); + + // DICTIONARY: GPUPipelineDescriptorBase + const dictMembersGPUPipelineDescriptorBase = [ + { key: "layout", converter: webidl.converters["GPUPipelineLayout"] }, + ]; + webidl.converters["GPUPipelineDescriptorBase"] = webidl + .createDictionaryConverter( + "GPUPipelineDescriptorBase", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUPipelineDescriptorBase, + ); + + // DICTIONARY: GPUProgrammableStage + const dictMembersGPUProgrammableStage = [ + { + key: "module", + converter: webidl.converters["GPUShaderModule"], + required: true, + }, + { + key: "entryPoint", + converter: webidl.converters["USVString"], + required: true, + }, + ]; + webidl.converters["GPUProgrammableStage"] = webidl.createDictionaryConverter( + "GPUProgrammableStage", + dictMembersGPUProgrammableStage, + ); + + // INTERFACE: GPUComputePipeline + webidl.converters.GPUComputePipeline = webidl.createInterfaceConverter( + "GPUComputePipeline", + GPUComputePipeline, + ); + + // DICTIONARY: GPUComputePipelineDescriptor + const dictMembersGPUComputePipelineDescriptor = [ + { + key: "compute", + converter: webidl.converters["GPUProgrammableStage"], + required: true, + }, + ]; + webidl.converters["GPUComputePipelineDescriptor"] = webidl + .createDictionaryConverter( + "GPUComputePipelineDescriptor", + dictMembersGPUPipelineDescriptorBase, + dictMembersGPUComputePipelineDescriptor, + ); + + // INTERFACE: GPURenderPipeline + webidl.converters.GPURenderPipeline = webidl.createInterfaceConverter( + "GPURenderPipeline", + GPURenderPipeline, + ); + + // ENUM: GPUInputStepMode + webidl.converters["GPUInputStepMode"] = webidl.createEnumConverter( + "GPUInputStepMode", + [ + "vertex", + "instance", + ], + ); + + // ENUM: GPUVertexFormat + webidl.converters["GPUVertexFormat"] = webidl.createEnumConverter( + "GPUVertexFormat", + [ + "uchar2", + "uchar4", + "char2", + "char4", + "uchar2norm", + "uchar4norm", + "char2norm", + "char4norm", + "ushort2", + "ushort4", + "short2", + "short4", + "ushort2norm", + "ushort4norm", + "short2norm", + "short4norm", + "half2", + "half4", + "float", + "float2", + "float3", + "float4", + "uint", + "uint2", + "uint3", + "uint4", + "int", + "int2", + "int3", + "int4", + ], + ); + + // DICTIONARY: GPUVertexAttribute + const dictMembersGPUVertexAttribute = [ + { + key: "format", + converter: webidl.converters["GPUVertexFormat"], + required: true, + }, + { + key: "offset", + converter: webidl.converters["GPUSize64"], + required: true, + }, + { + key: "shaderLocation", + converter: webidl.converters["GPUIndex32"], + required: true, + }, + ]; + webidl.converters["GPUVertexAttribute"] = webidl.createDictionaryConverter( + "GPUVertexAttribute", + dictMembersGPUVertexAttribute, + ); + + // DICTIONARY: GPUVertexBufferLayout + const dictMembersGPUVertexBufferLayout = [ + { + key: "arrayStride", + converter: webidl.converters["GPUSize64"], + required: true, + }, + { + key: "stepMode", + converter: webidl.converters["GPUInputStepMode"], + defaultValue: "vertex", + }, + { + key: "attributes", + converter: webidl.createSequenceConverter( + webidl.converters["GPUVertexAttribute"], + ), + required: true, + }, + ]; + webidl.converters["GPUVertexBufferLayout"] = webidl.createDictionaryConverter( + "GPUVertexBufferLayout", + dictMembersGPUVertexBufferLayout, + ); + + // DICTIONARY: GPUVertexState + const dictMembersGPUVertexState = [ + { + key: "buffers", + converter: webidl.createSequenceConverter( + webidl.createNullableConverter( + webidl.converters["GPUVertexBufferLayout"], + ), + ), + defaultValue: [], + }, + ]; + webidl.converters["GPUVertexState"] = webidl.createDictionaryConverter( + "GPUVertexState", + dictMembersGPUProgrammableStage, + dictMembersGPUVertexState, + ); + + // ENUM: GPUPrimitiveTopology + webidl.converters["GPUPrimitiveTopology"] = webidl.createEnumConverter( + "GPUPrimitiveTopology", + [ + "point-list", + "line-list", + "line-strip", + "triangle-list", + "triangle-strip", + ], + ); + + // ENUM: GPUIndexFormat + webidl.converters["GPUIndexFormat"] = webidl.createEnumConverter( + "GPUIndexFormat", + [ + "uint16", + "uint32", + ], + ); + + // ENUM: GPUFrontFace + webidl.converters["GPUFrontFace"] = webidl.createEnumConverter( + "GPUFrontFace", + [ + "ccw", + "cw", + ], + ); + + // ENUM: GPUCullMode + webidl.converters["GPUCullMode"] = webidl.createEnumConverter("GPUCullMode", [ + "none", + "front", + "back", + ]); + + // DICTIONARY: GPUPrimitiveState + const dictMembersGPUPrimitiveState = [ + { + key: "topology", + converter: webidl.converters["GPUPrimitiveTopology"], + defaultValue: "triangle-list", + }, + { key: "stripIndexFormat", converter: webidl.converters["GPUIndexFormat"] }, + { + key: "frontFace", + converter: webidl.converters["GPUFrontFace"], + defaultValue: "ccw", + }, + { + key: "cullMode", + converter: webidl.converters["GPUCullMode"], + defaultValue: "none", + }, + ]; + webidl.converters["GPUPrimitiveState"] = webidl.createDictionaryConverter( + "GPUPrimitiveState", + dictMembersGPUPrimitiveState, + ); + + // ENUM: GPUStencilOperation + webidl.converters["GPUStencilOperation"] = webidl.createEnumConverter( + "GPUStencilOperation", + [ + "keep", + "zero", + "replace", + "invert", + "increment-clamp", + "decrement-clamp", + "increment-wrap", + "decrement-wrap", + ], + ); + + // DICTIONARY: GPUStencilFaceState + const dictMembersGPUStencilFaceState = [ + { + key: "compare", + converter: webidl.converters["GPUCompareFunction"], + defaultValue: "always", + }, + { + key: "failOp", + converter: webidl.converters["GPUStencilOperation"], + defaultValue: "keep", + }, + { + key: "depthFailOp", + converter: webidl.converters["GPUStencilOperation"], + defaultValue: "keep", + }, + { + key: "passOp", + converter: webidl.converters["GPUStencilOperation"], + defaultValue: "keep", + }, + ]; + webidl.converters["GPUStencilFaceState"] = webidl.createDictionaryConverter( + "GPUStencilFaceState", + dictMembersGPUStencilFaceState, + ); + + // TYPEDEF: GPUStencilValue + webidl.converters["GPUStencilValue"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + + // TYPEDEF: GPUDepthBias + webidl.converters["GPUDepthBias"] = (V, opts) => + webidl.converters["long"](V, { ...opts, enforceRange: true }); + + // DICTIONARY: GPUDepthStencilState + const dictMembersGPUDepthStencilState = [ + { + key: "format", + converter: webidl.converters["GPUTextureFormat"], + required: true, + }, + { + key: "depthWriteEnabled", + converter: webidl.converters["boolean"], + defaultValue: false, + }, + { + key: "depthCompare", + converter: webidl.converters["GPUCompareFunction"], + defaultValue: "always", + }, + { + key: "stencilFront", + converter: webidl.converters["GPUStencilFaceState"], + defaultValue: {}, + }, + { + key: "stencilBack", + converter: webidl.converters["GPUStencilFaceState"], + defaultValue: {}, + }, + { + key: "stencilReadMask", + converter: webidl.converters["GPUStencilValue"], + defaultValue: 0xFFFFFFFF, + }, + { + key: "stencilWriteMask", + converter: webidl.converters["GPUStencilValue"], + defaultValue: 0xFFFFFFFF, + }, + { + key: "depthBias", + converter: webidl.converters["GPUDepthBias"], + defaultValue: 0, + }, + { + key: "depthBiasSlopeScale", + converter: webidl.converters["float"], + defaultValue: 0, + }, + { + key: "depthBiasClamp", + converter: webidl.converters["float"], + defaultValue: 0, + }, + { + key: "clampDepth", + converter: webidl.converters["boolean"], + defaultValue: false, + }, + ]; + webidl.converters["GPUDepthStencilState"] = webidl.createDictionaryConverter( + "GPUDepthStencilState", + dictMembersGPUDepthStencilState, + ); + + // TYPEDEF: GPUSampleMask + webidl.converters["GPUSampleMask"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + + // DICTIONARY: GPUMultisampleState + const dictMembersGPUMultisampleState = [ + { + key: "count", + converter: webidl.converters["GPUSize32"], + defaultValue: 1, + }, + { + key: "mask", + converter: webidl.converters["GPUSampleMask"], + defaultValue: 0xFFFFFFFF, + }, + { + key: "alphaToCoverageEnabled", + converter: webidl.converters["boolean"], + defaultValue: false, + }, + ]; + webidl.converters["GPUMultisampleState"] = webidl.createDictionaryConverter( + "GPUMultisampleState", + dictMembersGPUMultisampleState, + ); + + // ENUM: GPUBlendFactor + webidl.converters["GPUBlendFactor"] = webidl.createEnumConverter( + "GPUBlendFactor", + [ + "zero", + "one", + "src-color", + "one-minus-src-color", + "src-alpha", + "one-minus-src-alpha", + "dst-color", + "one-minus-dst-color", + "dst-alpha", + "one-minus-dst-alpha", + "src-alpha-saturated", + "blend-color", + "one-minus-blend-color", + ], + ); + + // ENUM: GPUBlendOperation + webidl.converters["GPUBlendOperation"] = webidl.createEnumConverter( + "GPUBlendOperation", + [ + "add", + "subtract", + "reverse-subtract", + "min", + "max", + ], + ); + + // DICTIONARY: GPUBlendComponent + const dictMembersGPUBlendComponent = [ + { + key: "srcFactor", + converter: webidl.converters["GPUBlendFactor"], + defaultValue: "one", + }, + { + key: "dstFactor", + converter: webidl.converters["GPUBlendFactor"], + defaultValue: "zero", + }, + { + key: "operation", + converter: webidl.converters["GPUBlendOperation"], + defaultValue: "add", + }, + ]; + webidl.converters["GPUBlendComponent"] = webidl.createDictionaryConverter( + "GPUBlendComponent", + dictMembersGPUBlendComponent, + ); + + // DICTIONARY: GPUBlendState + const dictMembersGPUBlendState = [ + { + key: "color", + converter: webidl.converters["GPUBlendComponent"], + required: true, + }, + { + key: "alpha", + converter: webidl.converters["GPUBlendComponent"], + required: true, + }, + ]; + webidl.converters["GPUBlendState"] = webidl.createDictionaryConverter( + "GPUBlendState", + dictMembersGPUBlendState, + ); + + // TYPEDEF: GPUColorWriteFlags + webidl.converters["GPUColorWriteFlags"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + + // DICTIONARY: GPUColorTargetState + const dictMembersGPUColorTargetState = [ + { + key: "format", + converter: webidl.converters["GPUTextureFormat"], + required: true, + }, + { key: "blend", converter: webidl.converters["GPUBlendState"] }, + { + key: "writeMask", + converter: webidl.converters["GPUColorWriteFlags"], + defaultValue: 0xF, + }, + ]; + webidl.converters["GPUColorTargetState"] = webidl.createDictionaryConverter( + "GPUColorTargetState", + dictMembersGPUColorTargetState, + ); + + // DICTIONARY: GPUFragmentState + const dictMembersGPUFragmentState = [ + { + key: "targets", + converter: webidl.createSequenceConverter( + webidl.converters["GPUColorTargetState"], + ), + required: true, + }, + ]; + webidl.converters["GPUFragmentState"] = webidl.createDictionaryConverter( + "GPUFragmentState", + dictMembersGPUProgrammableStage, + dictMembersGPUFragmentState, + ); + + // DICTIONARY: GPURenderPipelineDescriptor + const dictMembersGPURenderPipelineDescriptor = [ + { + key: "vertex", + converter: webidl.converters["GPUVertexState"], + required: true, + }, + { + key: "primitive", + converter: webidl.converters["GPUPrimitiveState"], + defaultValue: {}, + }, + { + key: "depthStencil", + converter: webidl.converters["GPUDepthStencilState"], + }, + { + key: "multisample", + converter: webidl.converters["GPUMultisampleState"], + defaultValue: {}, + }, + { key: "fragment", converter: webidl.converters["GPUFragmentState"] }, + ]; + webidl.converters["GPURenderPipelineDescriptor"] = webidl + .createDictionaryConverter( + "GPURenderPipelineDescriptor", + dictMembersGPUPipelineDescriptorBase, + dictMembersGPURenderPipelineDescriptor, + ); + + // INTERFACE: GPUColorWrite + webidl.converters.GPUColorWrite = webidl.createInterfaceConverter( + "GPUColorWrite", + GPUColorWrite, + ); + + // INTERFACE: GPUCommandBuffer + webidl.converters.GPUCommandBuffer = webidl.createInterfaceConverter( + "GPUCommandBuffer", + GPUCommandBuffer, + ); + + // DICTIONARY: GPUCommandBufferDescriptor + const dictMembersGPUCommandBufferDescriptor = []; + webidl.converters["GPUCommandBufferDescriptor"] = webidl + .createDictionaryConverter( + "GPUCommandBufferDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUCommandBufferDescriptor, + ); + + // INTERFACE: GPUCommandEncoder + webidl.converters.GPUCommandEncoder = webidl.createInterfaceConverter( + "GPUCommandEncoder", + GPUCommandEncoder, + ); + + // DICTIONARY: GPUCommandEncoderDescriptor + const dictMembersGPUCommandEncoderDescriptor = [ + { + key: "measureExecutionTime", + converter: webidl.converters["boolean"], + defaultValue: false, + }, + ]; + webidl.converters["GPUCommandEncoderDescriptor"] = webidl + .createDictionaryConverter( + "GPUCommandEncoderDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUCommandEncoderDescriptor, + ); + + // DICTIONARY: GPUImageDataLayout + const dictMembersGPUImageDataLayout = [ + { + key: "offset", + converter: webidl.converters["GPUSize64"], + defaultValue: 0, + }, + { key: "bytesPerRow", converter: webidl.converters["GPUSize32"] }, + { key: "rowsPerImage", converter: webidl.converters["GPUSize32"] }, + ]; + webidl.converters["GPUImageDataLayout"] = webidl.createDictionaryConverter( + "GPUImageDataLayout", + dictMembersGPUImageDataLayout, + ); + + // DICTIONARY: GPUImageCopyBuffer + const dictMembersGPUImageCopyBuffer = [ + { + key: "buffer", + converter: webidl.converters["GPUBuffer"], + required: true, + }, + ]; + webidl.converters["GPUImageCopyBuffer"] = webidl.createDictionaryConverter( + "GPUImageCopyBuffer", + dictMembersGPUImageDataLayout, + dictMembersGPUImageCopyBuffer, + ); + + // TYPEDEF: GPUOrigin3D + webidl.converters["GPUOrigin3D"] = webidl.converters.any; + + // DICTIONARY: GPUImageCopyTexture + const dictMembersGPUImageCopyTexture = [ + { + key: "texture", + converter: webidl.converters["GPUTexture"], + required: true, + }, + { + key: "mipLevel", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 0, + }, + { + key: "origin", + converter: webidl.converters["GPUOrigin3D"], + defaultValue: {}, + }, + { + key: "aspect", + converter: webidl.converters["GPUTextureAspect"], + defaultValue: "all", + }, + ]; + webidl.converters["GPUImageCopyTexture"] = webidl.createDictionaryConverter( + "GPUImageCopyTexture", + dictMembersGPUImageCopyTexture, + ); + + // INTERFACE: GPUComputePassEncoder + webidl.converters.GPUComputePassEncoder = webidl.createInterfaceConverter( + "GPUComputePassEncoder", + GPUComputePassEncoder, + ); + + // DICTIONARY: GPUComputePassDescriptor + const dictMembersGPUComputePassDescriptor = []; + webidl.converters["GPUComputePassDescriptor"] = webidl + .createDictionaryConverter( + "GPUComputePassDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUComputePassDescriptor, + ); + + // INTERFACE: GPURenderPassEncoder + webidl.converters.GPURenderPassEncoder = webidl.createInterfaceConverter( + "GPURenderPassEncoder", + GPURenderPassEncoder, + ); + + // ENUM: GPUStoreOp + webidl.converters["GPUStoreOp"] = webidl.createEnumConverter("GPUStoreOp", [ + "store", + "clear", + ]); + + // DICTIONARY: GPURenderPassColorAttachment + const dictMembersGPURenderPassColorAttachment = [ + { + key: "view", + converter: webidl.converters["GPUTextureView"], + required: true, + }, + { key: "resolveTarget", converter: webidl.converters["GPUTextureView"] }, + { key: "loadValue", converter: webidl.converters.any, required: true }, + { + key: "storeOp", + converter: webidl.converters["GPUStoreOp"], + defaultValue: "store", + }, + ]; + webidl.converters["GPURenderPassColorAttachment"] = webidl + .createDictionaryConverter( + "GPURenderPassColorAttachment", + dictMembersGPURenderPassColorAttachment, + ); + + // DICTIONARY: GPURenderPassDepthStencilAttachment + const dictMembersGPURenderPassDepthStencilAttachment = [ + { + key: "view", + converter: webidl.converters["GPUTextureView"], + required: true, + }, + { key: "depthLoadValue", converter: webidl.converters.any, required: true }, + { + key: "depthStoreOp", + converter: webidl.converters["GPUStoreOp"], + required: true, + }, + { + key: "depthReadOnly", + converter: webidl.converters["boolean"], + defaultValue: false, + }, + { + key: "stencilLoadValue", + converter: webidl.converters.any, + required: true, + }, + { + key: "stencilStoreOp", + converter: webidl.converters["GPUStoreOp"], + required: true, + }, + { + key: "stencilReadOnly", + converter: webidl.converters["boolean"], + defaultValue: false, + }, + ]; + webidl.converters["GPURenderPassDepthStencilAttachment"] = webidl + .createDictionaryConverter( + "GPURenderPassDepthStencilAttachment", + dictMembersGPURenderPassDepthStencilAttachment, + ); + + // INTERFACE: GPUQuerySet + webidl.converters.GPUQuerySet = webidl.createInterfaceConverter( + "GPUQuerySet", + GPUQuerySet, + ); + + // DICTIONARY: GPURenderPassDescriptor + const dictMembersGPURenderPassDescriptor = [ + { + key: "colorAttachments", + converter: webidl.createSequenceConverter( + webidl.converters["GPURenderPassColorAttachment"], + ), + required: true, + }, + { + key: "depthStencilAttachment", + converter: webidl.converters["GPURenderPassDepthStencilAttachment"], + }, + { key: "occlusionQuerySet", converter: webidl.converters["GPUQuerySet"] }, + ]; + webidl.converters["GPURenderPassDescriptor"] = webidl + .createDictionaryConverter( + "GPURenderPassDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPURenderPassDescriptor, + ); + + // ENUM: GPULoadOp + webidl.converters["GPULoadOp"] = webidl.createEnumConverter("GPULoadOp", [ + "load", + ]); + + // INTERFACE: GPURenderBundle + webidl.converters.GPURenderBundle = webidl.createInterfaceConverter( + "GPURenderBundle", + GPURenderBundle, + ); + + // DICTIONARY: GPURenderBundleDescriptor + const dictMembersGPURenderBundleDescriptor = []; + webidl.converters["GPURenderBundleDescriptor"] = webidl + .createDictionaryConverter( + "GPURenderBundleDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPURenderBundleDescriptor, + ); + + // INTERFACE: GPURenderBundleEncoder + webidl.converters.GPURenderBundleEncoder = webidl.createInterfaceConverter( + "GPURenderBundleEncoder", + GPURenderBundleEncoder, + ); + + // DICTIONARY: GPURenderBundleEncoderDescriptor + const dictMembersGPURenderBundleEncoderDescriptor = [ + { + key: "colorFormats", + converter: webidl.createSequenceConverter( + webidl.converters["GPUTextureFormat"], + ), + required: true, + }, + { + key: "depthStencilFormat", + converter: webidl.converters["GPUTextureFormat"], + }, + { + key: "sampleCount", + converter: webidl.converters["GPUSize32"], + defaultValue: 1, + }, + ]; + webidl.converters["GPURenderBundleEncoderDescriptor"] = webidl + .createDictionaryConverter( + "GPURenderBundleEncoderDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPURenderBundleEncoderDescriptor, + ); + + // INTERFACE: GPUQueue + webidl.converters.GPUQueue = webidl.createInterfaceConverter( + "GPUQueue", + GPUQueue, + ); + + // ENUM: GPUQueryType + webidl.converters["GPUQueryType"] = webidl.createEnumConverter( + "GPUQueryType", + [ + "occlusion", + "pipeline-statistics", + "timestamp", + ], + ); + + // ENUM: GPUPipelineStatisticName + webidl.converters["GPUPipelineStatisticName"] = webidl.createEnumConverter( + "GPUPipelineStatisticName", + [ + "vertex-shader-invocations", + "clipper-invocations", + "clipper-primitives-out", + "fragment-shader-invocations", + "compute-shader-invocations", + ], + ); + + // DICTIONARY: GPUQuerySetDescriptor + const dictMembersGPUQuerySetDescriptor = [ + { + key: "type", + converter: webidl.converters["GPUQueryType"], + required: true, + }, + { key: "count", converter: webidl.converters["GPUSize32"], required: true }, + { + key: "pipelineStatistics", + converter: webidl.createSequenceConverter( + webidl.converters["GPUPipelineStatisticName"], + ), + defaultValue: [], + }, + ]; + webidl.converters["GPUQuerySetDescriptor"] = webidl.createDictionaryConverter( + "GPUQuerySetDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUQuerySetDescriptor, + ); + + // ENUM: GPUDeviceLostReason + webidl.converters["GPUDeviceLostReason"] = webidl.createEnumConverter( + "GPUDeviceLostReason", + [ + "destroyed", + ], + ); + + // // INTERFACE: GPUDeviceLostInfo + // webidl.converters.GPUDeviceLostInfo = webidl.createInterfaceConverter( + // "GPUDeviceLostInfo", + // GPUDeviceLostInfo, + // ); + + // ENUM: GPUErrorFilter + webidl.converters["GPUErrorFilter"] = webidl.createEnumConverter( + "GPUErrorFilter", + [ + "out-of-memory", + "validation", + ], + ); + + // INTERFACE: GPUOutOfMemoryError + webidl.converters.GPUOutOfMemoryError = webidl.createInterfaceConverter( + "GPUOutOfMemoryError", + GPUOutOfMemoryError, + ); + + // INTERFACE: GPUValidationError + webidl.converters.GPUValidationError = webidl.createInterfaceConverter( + "GPUValidationError", + GPUValidationError, + ); + + // TYPEDEF: GPUError + webidl.converters["GPUError"] = webidl.converters.any; + + // // INTERFACE: GPUUncapturedErrorEvent + // webidl.converters.GPUUncapturedErrorEvent = webidl.createInterfaceConverter( + // "GPUUncapturedErrorEvent", + // GPUUncapturedErrorEvent, + // ); + + // DICTIONARY: GPUUncapturedErrorEventInit + const dictMembersGPUUncapturedErrorEventInit = [ + { key: "error", converter: webidl.converters["GPUError"], required: true }, + ]; + webidl.converters["GPUUncapturedErrorEventInit"] = webidl + .createDictionaryConverter( + "GPUUncapturedErrorEventInit", + // dictMembersEventInit, + dictMembersGPUUncapturedErrorEventInit, + ); + + // TYPEDEF: GPUBufferDynamicOffset + webidl.converters["GPUBufferDynamicOffset"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + + // TYPEDEF: GPUSignedOffset32 + webidl.converters["GPUSignedOffset32"] = (V, opts) => + webidl.converters["long"](V, { ...opts, enforceRange: true }); + + // TYPEDEF: GPUFlagsConstant + webidl.converters["GPUFlagsConstant"] = webidl.converters["unsigned long"]; + + // DICTIONARY: GPUColorDict + const dictMembersGPUColorDict = [ + { key: "r", converter: webidl.converters["double"], required: true }, + { key: "g", converter: webidl.converters["double"], required: true }, + { key: "b", converter: webidl.converters["double"], required: true }, + { key: "a", converter: webidl.converters["double"], required: true }, + ]; + webidl.converters["GPUColorDict"] = webidl.createDictionaryConverter( + "GPUColorDict", + dictMembersGPUColorDict, + ); + + // TYPEDEF: GPUColor + webidl.converters["GPUColor"] = webidl.converters.any; + + // DICTIONARY: GPUOrigin2DDict + const dictMembersGPUOrigin2DDict = [ + { + key: "x", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 0, + }, + { + key: "y", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 0, + }, + ]; + webidl.converters["GPUOrigin2DDict"] = webidl.createDictionaryConverter( + "GPUOrigin2DDict", + dictMembersGPUOrigin2DDict, + ); + + // TYPEDEF: GPUOrigin2D + webidl.converters["GPUOrigin2D"] = webidl.converters.any; + + // DICTIONARY: GPUOrigin3DDict + const dictMembersGPUOrigin3DDict = [ + { + key: "x", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 0, + }, + { + key: "y", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 0, + }, + { + key: "z", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 0, + }, + ]; + webidl.converters["GPUOrigin3DDict"] = webidl.createDictionaryConverter( + "GPUOrigin3DDict", + dictMembersGPUOrigin3DDict, + ); + + // DICTIONARY: GPUExtent3DDict + const dictMembersGPUExtent3DDict = [ + { + key: "width", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 1, + }, + { + key: "height", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 1, + }, + { + key: "depthOrArrayLayers", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 1, + }, + ]; + webidl.converters["GPUExtent3DDict"] = webidl.createDictionaryConverter( + "GPUExtent3DDict", + dictMembersGPUExtent3DDict, + ); + + webidl.converters["sequence"] = webidl + .createSequenceConverter(webidl.converters["GPURenderBundle"]); + webidl.converters["sequence"] = webidl + .createSequenceConverter(webidl.converters["GPUCommandBuffer"]); + webidl.converters["UVString?"] = webidl.createNullableConverter( + webidl.converters.USVString, + ); +})(this); diff --git a/op_crates/webgpu/Cargo.toml b/op_crates/webgpu/Cargo.toml new file mode 100644 index 00000000000000..92d9e01f342bcb --- /dev/null +++ b/op_crates/webgpu/Cargo.toml @@ -0,0 +1,21 @@ +# Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +[package] +name = "deno_webgpu" +version = "0.1.0" +edition = "2018" +description = "provides webgpu Web API to deno_core" +authors = ["the Deno authors"] +license = "MIT" +readme = "README.md" +repository = "https://github.com/denoland/deno" + +[lib] +path = "lib.rs" + +[dependencies] +deno_core = { version = "0.79.0", path = "../../core" } +tokio = { version = "1.1.1", features = ["full"] } +serde = { version = "1.0.123", features = ["derive"] } +wgpu-core = { version = "0.7.0", features = ["trace"] } +wgpu-types = "0.7.0" diff --git a/op_crates/webgpu/README.md b/op_crates/webgpu/README.md new file mode 100644 index 00000000000000..2f915dcbbee656 --- /dev/null +++ b/op_crates/webgpu/README.md @@ -0,0 +1,35 @@ +# deno_webgpu + +This op crate implements the WebGPU API as defined in +https://gpuweb.github.io/gpuweb/ in Deno. The implementation targets the spec +draft as of February 22, 2021. The spec is still very much in flux. This op +crate tries to stay up to date with the spec, but is constrained by the features +implemented in our GPU backend library [wgpu](https://github.com/gfx-rs/wgpu). + +The spec is still very bare bones, and is still missing many details. As the +spec becomes more concrete, we will implement to follow the spec more closely. + +In addition, setting the `DENO_WEBGPU_TRACE` environmental variable will output +a +[wgpu trace](https://github.com/gfx-rs/wgpu/wiki/Debugging-wgpu-Applications#tracing-infrastructure) +to the specified directory. + +For testing this op crate will make use of the WebGPU conformance tests suite, +running through our WPT runner. This will be used to validate implementation +conformance. + +GitHub CI doesn't run with GPUs, so testing relies on software like DX WARP & +Vulkan lavapipe. Currently only using DX WARP works, so tests are only run on +Windows. + +## Links + +Specification: https://gpuweb.github.io/gpuweb/ + +Design documents: https://github.com/gpuweb/gpuweb/tree/main/design + +Conformance tests suite: https://github.com/gpuweb/cts + +WebGPU examples for Deno: https://github.com/crowlKats/webgpu-examples + +wgpu-users matrix channel: https://matrix.to/#/#wgpu-users:matrix.org diff --git a/op_crates/webgpu/binding.rs b/op_crates/webgpu/binding.rs new file mode 100644 index 00000000000000..b93b223cf54d64 --- /dev/null +++ b/op_crates/webgpu/binding.rs @@ -0,0 +1,362 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::bad_resource_id; +use deno_core::error::AnyError; +use deno_core::serde_json::json; +use deno_core::serde_json::Value; +use deno_core::ZeroCopyBuf; +use deno_core::{OpState, Resource}; +use serde::Deserialize; +use std::borrow::Cow; + +use super::error::WebGPUError; + +pub(crate) struct WebGPUBindGroupLayout( + pub(crate) wgpu_core::id::BindGroupLayoutId, +); +impl Resource for WebGPUBindGroupLayout { + fn name(&self) -> Cow { + "webGPUBindGroupLayout".into() + } +} + +pub(crate) struct WebGPUBindGroup(pub(crate) wgpu_core::id::BindGroupId); +impl Resource for WebGPUBindGroup { + fn name(&self) -> Cow { + "webGPUBindGroup".into() + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GPUBufferBindingLayout { + #[serde(rename = "type")] + kind: Option, + has_dynamic_offset: Option, + min_binding_size: Option, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GPUSamplerBindingLayout { + #[serde(rename = "type")] + kind: Option, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GPUTextureBindingLayout { + sample_type: Option, + view_dimension: Option, + multisampled: Option, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GPUStorageTextureBindingLayout { + access: String, + format: String, + view_dimension: Option, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GPUBindGroupLayoutEntry { + binding: u32, + visibility: u32, + buffer: Option, + sampler: Option, + texture: Option, + storage_texture: Option, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateBindGroupLayoutArgs { + device_rid: u32, + label: Option, + entries: Vec, +} + +pub fn op_webgpu_create_bind_group_layout( + state: &mut OpState, + args: CreateBindGroupLayoutArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let instance = state.borrow::(); + let device_resource = state + .resource_table + .get::(args.device_rid) + .ok_or_else(bad_resource_id)?; + let device = device_resource.0; + + let mut entries = vec![]; + + for entry in &args.entries { + entries.push(wgpu_types::BindGroupLayoutEntry { + binding: entry.binding, + visibility: wgpu_types::ShaderStage::from_bits(entry.visibility).unwrap(), + ty: if let Some(buffer) = &entry.buffer { + wgpu_types::BindingType::Buffer { + ty: match &buffer.kind { + Some(kind) => match kind.as_str() { + "uniform" => wgpu_types::BufferBindingType::Uniform, + "storage" => { + wgpu_types::BufferBindingType::Storage { read_only: false } + } + "read-only-storage" => { + wgpu_types::BufferBindingType::Storage { read_only: true } + } + _ => unreachable!(), + }, + None => wgpu_types::BufferBindingType::Uniform, + }, + has_dynamic_offset: buffer.has_dynamic_offset.unwrap_or(false), + min_binding_size: if let Some(min_binding_size) = + buffer.min_binding_size + { + std::num::NonZeroU64::new(min_binding_size) + } else { + None + }, + } + } else if let Some(sampler) = &entry.sampler { + match &sampler.kind { + Some(kind) => match kind.as_str() { + "filtering" => wgpu_types::BindingType::Sampler { + filtering: true, + comparison: false, + }, + "non-filtering" => wgpu_types::BindingType::Sampler { + filtering: false, + comparison: false, + }, + "comparison" => wgpu_types::BindingType::Sampler { + filtering: false, + comparison: true, + }, + _ => unreachable!(), + }, + None => wgpu_types::BindingType::Sampler { + filtering: true, + comparison: false, + }, + } + } else if let Some(texture) = &entry.texture { + wgpu_types::BindingType::Texture { + sample_type: match &texture.sample_type { + Some(sample_type) => match sample_type.as_str() { + "float" => { + wgpu_types::TextureSampleType::Float { filterable: true } + } + "unfilterable-float" => { + wgpu_types::TextureSampleType::Float { filterable: false } + } + "depth" => wgpu_types::TextureSampleType::Depth, + "sint" => wgpu_types::TextureSampleType::Sint, + "uint" => wgpu_types::TextureSampleType::Uint, + _ => unreachable!(), + }, + None => wgpu_types::TextureSampleType::Float { filterable: true }, + }, + view_dimension: match &texture.view_dimension { + Some(view_dimension) => { + super::texture::serialize_dimension(view_dimension) + } + None => wgpu_types::TextureViewDimension::D2, + }, + multisampled: texture.multisampled.unwrap_or(false), + } + } else if let Some(storage_texture) = &entry.storage_texture { + wgpu_types::BindingType::StorageTexture { + access: match storage_texture.access.as_str() { + "read-only" => wgpu_types::StorageTextureAccess::ReadOnly, + "write-only" => wgpu_types::StorageTextureAccess::WriteOnly, + _ => unreachable!(), + }, + format: super::texture::serialize_texture_format( + &storage_texture.format, + )?, + view_dimension: match &storage_texture.view_dimension { + Some(view_dimension) => { + super::texture::serialize_dimension(view_dimension) + } + None => wgpu_types::TextureViewDimension::D2, + }, + } + } else { + unreachable!() + }, + count: None, // native-only + }); + } + + let descriptor = wgpu_core::binding_model::BindGroupLayoutDescriptor { + label: args.label.map(Cow::from), + entries: Cow::from(entries), + }; + + let (bind_group_layout, maybe_err) = gfx_select!(device => instance.device_create_bind_group_layout( + device, + &descriptor, + std::marker::PhantomData + )); + + let rid = state + .resource_table + .add(WebGPUBindGroupLayout(bind_group_layout)); + + Ok(json!({ + "rid": rid, + "err": maybe_err.map(WebGPUError::from) + })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreatePipelineLayoutArgs { + device_rid: u32, + label: Option, + bind_group_layouts: Vec, +} + +pub fn op_webgpu_create_pipeline_layout( + state: &mut OpState, + args: CreatePipelineLayoutArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let instance = state.borrow::(); + let device_resource = state + .resource_table + .get::(args.device_rid) + .ok_or_else(bad_resource_id)?; + let device = device_resource.0; + + let mut bind_group_layouts = vec![]; + + for rid in &args.bind_group_layouts { + let bind_group_layout = state + .resource_table + .get::(*rid) + .ok_or_else(bad_resource_id)?; + bind_group_layouts.push(bind_group_layout.0); + } + + let descriptor = wgpu_core::binding_model::PipelineLayoutDescriptor { + label: args.label.map(Cow::from), + bind_group_layouts: Cow::from(bind_group_layouts), + push_constant_ranges: Default::default(), + }; + + let (pipeline_layout, maybe_err) = gfx_select!(device => instance.device_create_pipeline_layout( + device, + &descriptor, + std::marker::PhantomData + )); + + let rid = state + .resource_table + .add(super::pipeline::WebGPUPipelineLayout(pipeline_layout)); + + Ok(json!({ + "rid": rid, + "err": maybe_err.map(WebGPUError::from) + })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GPUBindGroupEntry { + binding: u32, + kind: String, + resource: u32, + offset: Option, + size: Option, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateBindGroupArgs { + device_rid: u32, + label: Option, + layout: u32, + entries: Vec, +} + +pub fn op_webgpu_create_bind_group( + state: &mut OpState, + args: CreateBindGroupArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let instance = state.borrow::(); + let device_resource = state + .resource_table + .get::(args.device_rid) + .ok_or_else(bad_resource_id)?; + let device = device_resource.0; + + let mut entries = vec![]; + + for entry in &args.entries { + let e = wgpu_core::binding_model::BindGroupEntry { + binding: entry.binding, + resource: match entry.kind.as_str() { + "GPUSampler" => { + let sampler_resource = state + .resource_table + .get::(entry.resource) + .ok_or_else(bad_resource_id)?; + wgpu_core::binding_model::BindingResource::Sampler(sampler_resource.0) + } + "GPUTextureView" => { + let texture_view_resource = state + .resource_table + .get::(entry.resource) + .ok_or_else(bad_resource_id)?; + wgpu_core::binding_model::BindingResource::TextureView( + texture_view_resource.0, + ) + } + "GPUBufferBinding" => { + let buffer_resource = state + .resource_table + .get::(entry.resource) + .ok_or_else(bad_resource_id)?; + wgpu_core::binding_model::BindingResource::Buffer( + wgpu_core::binding_model::BufferBinding { + buffer_id: buffer_resource.0, + offset: entry.offset.unwrap_or(0), + size: std::num::NonZeroU64::new(entry.size.unwrap_or(0)), + }, + ) + } + _ => unreachable!(), + }, + }; + entries.push(e); + } + + let bind_group_layout = state + .resource_table + .get::(args.layout) + .ok_or_else(bad_resource_id)?; + + let descriptor = wgpu_core::binding_model::BindGroupDescriptor { + label: args.label.map(Cow::from), + layout: bind_group_layout.0, + entries: Cow::from(entries), + }; + + let (bind_group, maybe_err) = gfx_select!(device => instance.device_create_bind_group( + device, + &descriptor, + std::marker::PhantomData + )); + + let rid = state.resource_table.add(WebGPUBindGroup(bind_group)); + + Ok(json!({ + "rid": rid, + "err": maybe_err.map(WebGPUError::from) + })) +} diff --git a/op_crates/webgpu/buffer.rs b/op_crates/webgpu/buffer.rs new file mode 100644 index 00000000000000..8ff9f311530c6d --- /dev/null +++ b/op_crates/webgpu/buffer.rs @@ -0,0 +1,243 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::bad_resource_id; +use deno_core::error::AnyError; +use deno_core::futures::channel::oneshot; +use deno_core::serde_json::json; +use deno_core::serde_json::Value; +use deno_core::OpState; +use deno_core::ZeroCopyBuf; +use deno_core::{BufVec, Resource}; +use serde::Deserialize; +use std::borrow::Cow; +use std::cell::RefCell; +use std::rc::Rc; +use std::time::Duration; + +use super::error::DOMExceptionOperationError; +use super::error::WebGPUError; + +pub(crate) struct WebGPUBuffer(pub(crate) wgpu_core::id::BufferId); +impl Resource for WebGPUBuffer { + fn name(&self) -> Cow { + "webGPUBuffer".into() + } +} + +struct WebGPUBufferMapped(*mut u8, usize); +impl Resource for WebGPUBufferMapped { + fn name(&self) -> Cow { + "webGPUBufferMapped".into() + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateBufferArgs { + device_rid: u32, + label: Option, + size: u64, + usage: u32, + mapped_at_creation: Option, +} + +pub fn op_webgpu_create_buffer( + state: &mut OpState, + args: CreateBufferArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let instance = state.borrow::(); + let device_resource = state + .resource_table + .get::(args.device_rid) + .ok_or_else(bad_resource_id)?; + let device = device_resource.0; + + let descriptor = wgpu_core::resource::BufferDescriptor { + label: args.label.map(Cow::from), + size: args.size, + usage: wgpu_types::BufferUsage::from_bits(args.usage).unwrap(), + mapped_at_creation: args.mapped_at_creation.unwrap_or(false), + }; + + let (buffer, maybe_err) = gfx_select!(device => instance.device_create_buffer( + device, + &descriptor, + std::marker::PhantomData + )); + + let rid = state.resource_table.add(WebGPUBuffer(buffer)); + + Ok(json!({ + "rid": rid, + "err": maybe_err.map(WebGPUError::from) + })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BufferGetMapAsyncArgs { + buffer_rid: u32, + device_rid: u32, + mode: u32, + offset: u64, + size: u64, +} + +pub async fn op_webgpu_buffer_get_map_async( + state: Rc>, + args: BufferGetMapAsyncArgs, + _bufs: BufVec, +) -> Result { + let (sender, receiver) = oneshot::channel::>(); + + let device; + { + let state_ = state.borrow(); + let instance = state_.borrow::(); + let buffer_resource = state_ + .resource_table + .get::(args.buffer_rid) + .ok_or_else(bad_resource_id)?; + let buffer = buffer_resource.0; + let device_resource = state_ + .resource_table + .get::(args.device_rid) + .ok_or_else(bad_resource_id)?; + device = device_resource.0; + + let boxed_sender = Box::new(sender); + let sender_ptr = Box::into_raw(boxed_sender) as *mut u8; + + extern "C" fn buffer_map_future_wrapper( + status: wgpu_core::resource::BufferMapAsyncStatus, + user_data: *mut u8, + ) { + let sender_ptr = user_data as *mut oneshot::Sender>; + let boxed_sender = unsafe { Box::from_raw(sender_ptr) }; + boxed_sender + .send(match status { + wgpu_core::resource::BufferMapAsyncStatus::Success => Ok(()), + _ => unreachable!(), // TODO + }) + .unwrap(); + } + + // TODO(lucacasonato): error handling + gfx_select!(buffer => instance.buffer_map_async( + buffer, + args.offset..(args.offset + args.size), + wgpu_core::resource::BufferMapOperation { + host: match args.mode { + 1 => wgpu_core::device::HostMap::Read, + 2 => wgpu_core::device::HostMap::Write, + _ => unreachable!(), + }, + callback: buffer_map_future_wrapper, + user_data: sender_ptr, + } + ))?; + } + + let done = Rc::new(RefCell::new(false)); + let done_ = done.clone(); + let device_poll_fut = async move { + while !*done.borrow() { + { + let state = state.borrow(); + let instance = state.borrow::(); + gfx_select!(device => instance.device_poll(device, false)).unwrap() + } + tokio::time::sleep(Duration::from_millis(10)).await; + } + Ok::<(), AnyError>(()) + }; + + let receiver_fut = async move { + receiver.await??; + let mut done = done_.borrow_mut(); + *done = true; + Ok::<(), AnyError>(()) + }; + + tokio::try_join!(device_poll_fut, receiver_fut)?; + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BufferGetMappedRangeArgs { + buffer_rid: u32, + offset: u64, + size: u64, +} + +pub fn op_webgpu_buffer_get_mapped_range( + state: &mut OpState, + args: BufferGetMappedRangeArgs, + zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let instance = state.borrow::(); + let buffer_resource = state + .resource_table + .get::(args.buffer_rid) + .ok_or_else(bad_resource_id)?; + let buffer = buffer_resource.0; + + let slice_pointer = gfx_select!(buffer => instance.buffer_get_mapped_range( + buffer, + args.offset, + std::num::NonZeroU64::new(args.size) + )) + .map_err(|e| DOMExceptionOperationError::new(&e.to_string()))?; + + let slice = unsafe { + std::slice::from_raw_parts_mut(slice_pointer, args.size as usize) + }; + zero_copy[0].copy_from_slice(slice); + + let rid = state + .resource_table + .add(WebGPUBufferMapped(slice_pointer, args.size as usize)); + + Ok(json!({ + "rid": rid, + })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BufferUnmapArgs { + buffer_rid: u32, + mapped_rid: u32, +} + +pub fn op_webgpu_buffer_unmap( + state: &mut OpState, + args: BufferUnmapArgs, + zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let mapped_resource = state + .resource_table + .take::(args.mapped_rid) + .ok_or_else(bad_resource_id)?; + let instance = state.borrow::(); + let buffer_resource = state + .resource_table + .get::(args.buffer_rid) + .ok_or_else(bad_resource_id)?; + let buffer = buffer_resource.0; + + let slice_pointer = mapped_resource.0; + let size = mapped_resource.1; + + if let Some(buffer) = zero_copy.get(0) { + let slice = unsafe { std::slice::from_raw_parts_mut(slice_pointer, size) }; + slice.copy_from_slice(&buffer); + } + + let maybe_err = gfx_select!(buffer => instance.buffer_unmap(buffer)).err(); + + Ok(json!({ "err": maybe_err.map(WebGPUError::from) })) +} diff --git a/op_crates/webgpu/bundle.rs b/op_crates/webgpu/bundle.rs new file mode 100644 index 00000000000000..7043b0a9e47327 --- /dev/null +++ b/op_crates/webgpu/bundle.rs @@ -0,0 +1,465 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::bad_resource_id; +use deno_core::error::AnyError; +use deno_core::serde_json::json; +use deno_core::serde_json::Value; +use deno_core::ZeroCopyBuf; +use deno_core::{OpState, Resource}; +use serde::Deserialize; +use std::borrow::Cow; +use std::cell::RefCell; +use std::rc::Rc; + +use super::error::WebGPUError; +use super::texture::serialize_texture_format; + +struct WebGPURenderBundleEncoder( + RefCell, +); +impl Resource for WebGPURenderBundleEncoder { + fn name(&self) -> Cow { + "webGPURenderBundleEncoder".into() + } +} + +pub(crate) struct WebGPURenderBundle(pub(crate) wgpu_core::id::RenderBundleId); +impl Resource for WebGPURenderBundle { + fn name(&self) -> Cow { + "webGPURenderBundle".into() + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateRenderBundleEncoderArgs { + device_rid: u32, + label: Option, + color_formats: Vec, + depth_stencil_format: Option, + sample_count: Option, +} + +pub fn op_webgpu_create_render_bundle_encoder( + state: &mut OpState, + args: CreateRenderBundleEncoderArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let device_resource = state + .resource_table + .get::(args.device_rid) + .ok_or_else(bad_resource_id)?; + let device = device_resource.0; + + let mut color_formats = vec![]; + + for format in &args.color_formats { + color_formats.push(serialize_texture_format(format)?); + } + + let descriptor = wgpu_core::command::RenderBundleEncoderDescriptor { + label: args.label.map(Cow::from), + color_formats: Cow::from(color_formats), + depth_stencil_format: args + .depth_stencil_format + .map(|s| serialize_texture_format(&s)) + .transpose()?, + sample_count: args.sample_count.unwrap_or(1), + }; + + let res = + wgpu_core::command::RenderBundleEncoder::new(&descriptor, device, None); + let (render_bundle_encoder, maybe_err) = match res { + Ok(encoder) => (encoder, None), + Err(e) => ( + wgpu_core::command::RenderBundleEncoder::dummy(device), + Some(e), + ), + }; + + let rid = state + .resource_table + .add(WebGPURenderBundleEncoder(RefCell::new( + render_bundle_encoder, + ))); + + Ok(json!({ + "rid": rid, + "err": maybe_err.map(WebGPUError::from), + })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderFinishArgs { + render_bundle_encoder_rid: u32, + label: Option, +} + +pub fn op_webgpu_render_bundle_encoder_finish( + state: &mut OpState, + args: RenderBundleEncoderFinishArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let render_bundle_encoder_resource = state + .resource_table + .take::(args.render_bundle_encoder_rid) + .ok_or_else(bad_resource_id)?; + let render_bundle_encoder = Rc::try_unwrap(render_bundle_encoder_resource) + .ok() + .expect("unwrapping render_bundle_encoder_resource should succeed") + .0 + .into_inner(); + let instance = state.borrow::(); + + let (render_bundle, maybe_err) = gfx_select!(render_bundle_encoder.parent() => instance.render_bundle_encoder_finish( + render_bundle_encoder, + &wgpu_core::command::RenderBundleDescriptor { + label: args.label.map(Cow::from), + }, + std::marker::PhantomData + )); + + let rid = state.resource_table.add(WebGPURenderBundle(render_bundle)); + + Ok(json!({ + "rid": rid, + "err": maybe_err.map(WebGPUError::from) + })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderSetBindGroupArgs { + render_bundle_encoder_rid: u32, + index: u32, + bind_group: u32, + dynamic_offsets_data: Option>, + dynamic_offsets_data_start: usize, + dynamic_offsets_data_length: usize, +} + +pub fn op_webgpu_render_bundle_encoder_set_bind_group( + state: &mut OpState, + args: RenderBundleEncoderSetBindGroupArgs, + zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let bind_group_resource = state + .resource_table + .get::(args.bind_group) + .ok_or_else(bad_resource_id)?; + let render_bundle_encoder_resource = state + .resource_table + .get::(args.render_bundle_encoder_rid) + .ok_or_else(bad_resource_id)?; + + // I know this might look like it can be easily deduplicated, but it can not + // be due to the lifetime of the args.dynamic_offsets_data slice. Because we + // need to use a raw pointer here the slice can be freed before the pointer + // is used in wgpu_render_pass_set_bind_group. See + // https://matrix.to/#/!XFRnMvAfptAHthwBCx:matrix.org/$HgrlhD-Me1DwsGb8UdMu2Hqubgks8s7ILwWRwigOUAg + match args.dynamic_offsets_data { + Some(data) => unsafe { + wgpu_core::command::bundle_ffi::wgpu_render_bundle_set_bind_group( + &mut render_bundle_encoder_resource.0.borrow_mut(), + args.index, + bind_group_resource.0, + data.as_slice().as_ptr(), + args.dynamic_offsets_data_length, + ); + }, + None => { + let (prefix, data, suffix) = unsafe { zero_copy[0].align_to::() }; + assert!(prefix.is_empty()); + assert!(suffix.is_empty()); + unsafe { + wgpu_core::command::bundle_ffi::wgpu_render_bundle_set_bind_group( + &mut render_bundle_encoder_resource.0.borrow_mut(), + args.index, + bind_group_resource.0, + data[args.dynamic_offsets_data_start..].as_ptr(), + args.dynamic_offsets_data_length, + ); + } + } + }; + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderPushDebugGroupArgs { + render_bundle_encoder_rid: u32, + group_label: String, +} + +pub fn op_webgpu_render_bundle_encoder_push_debug_group( + state: &mut OpState, + args: RenderBundleEncoderPushDebugGroupArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let render_bundle_encoder_resource = state + .resource_table + .get::(args.render_bundle_encoder_rid) + .ok_or_else(bad_resource_id)?; + + unsafe { + let label = std::ffi::CString::new(args.group_label).unwrap(); + wgpu_core::command::bundle_ffi::wgpu_render_bundle_push_debug_group( + &mut render_bundle_encoder_resource.0.borrow_mut(), + label.as_ptr(), + ); + } + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderPopDebugGroupArgs { + render_bundle_encoder_rid: u32, +} + +pub fn op_webgpu_render_bundle_encoder_pop_debug_group( + state: &mut OpState, + args: RenderBundleEncoderPopDebugGroupArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let render_bundle_encoder_resource = state + .resource_table + .get::(args.render_bundle_encoder_rid) + .ok_or_else(bad_resource_id)?; + + unsafe { + wgpu_core::command::bundle_ffi::wgpu_render_bundle_pop_debug_group( + &mut render_bundle_encoder_resource.0.borrow_mut(), + ); + } + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderInsertDebugMarkerArgs { + render_bundle_encoder_rid: u32, + marker_label: String, +} + +pub fn op_webgpu_render_bundle_encoder_insert_debug_marker( + state: &mut OpState, + args: RenderBundleEncoderInsertDebugMarkerArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let render_bundle_encoder_resource = state + .resource_table + .get::(args.render_bundle_encoder_rid) + .ok_or_else(bad_resource_id)?; + + unsafe { + let label = std::ffi::CString::new(args.marker_label).unwrap(); + wgpu_core::command::bundle_ffi::wgpu_render_bundle_insert_debug_marker( + &mut render_bundle_encoder_resource.0.borrow_mut(), + label.as_ptr(), + ); + } + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderSetPipelineArgs { + render_bundle_encoder_rid: u32, + pipeline: u32, +} + +pub fn op_webgpu_render_bundle_encoder_set_pipeline( + state: &mut OpState, + args: RenderBundleEncoderSetPipelineArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let render_pipeline_resource = state + .resource_table + .get::(args.pipeline) + .ok_or_else(bad_resource_id)?; + let render_bundle_encoder_resource = state + .resource_table + .get::(args.render_bundle_encoder_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::bundle_ffi::wgpu_render_bundle_set_pipeline( + &mut render_bundle_encoder_resource.0.borrow_mut(), + render_pipeline_resource.0, + ); + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderSetIndexBufferArgs { + render_bundle_encoder_rid: u32, + buffer: u32, + index_format: String, + offset: u64, + size: u64, +} + +pub fn op_webgpu_render_bundle_encoder_set_index_buffer( + state: &mut OpState, + args: RenderBundleEncoderSetIndexBufferArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let buffer_resource = state + .resource_table + .get::(args.buffer) + .ok_or_else(bad_resource_id)?; + let render_bundle_encoder_resource = state + .resource_table + .get::(args.render_bundle_encoder_rid) + .ok_or_else(bad_resource_id)?; + + render_bundle_encoder_resource + .0 + .borrow_mut() + .set_index_buffer( + buffer_resource.0, + super::pipeline::serialize_index_format(args.index_format), + args.offset, + std::num::NonZeroU64::new(args.size), + ); + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderSetVertexBufferArgs { + render_bundle_encoder_rid: u32, + slot: u32, + buffer: u32, + offset: u64, + size: u64, +} + +pub fn op_webgpu_render_bundle_encoder_set_vertex_buffer( + state: &mut OpState, + args: RenderBundleEncoderSetVertexBufferArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let buffer_resource = state + .resource_table + .get::(args.buffer) + .ok_or_else(bad_resource_id)?; + let render_bundle_encoder_resource = state + .resource_table + .get::(args.render_bundle_encoder_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::bundle_ffi::wgpu_render_bundle_set_vertex_buffer( + &mut render_bundle_encoder_resource.0.borrow_mut(), + args.slot, + buffer_resource.0, + args.offset, + std::num::NonZeroU64::new(args.size), + ); + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderDrawArgs { + render_bundle_encoder_rid: u32, + vertex_count: u32, + instance_count: u32, + first_vertex: u32, + first_instance: u32, +} + +pub fn op_webgpu_render_bundle_encoder_draw( + state: &mut OpState, + args: RenderBundleEncoderDrawArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let render_bundle_encoder_resource = state + .resource_table + .get::(args.render_bundle_encoder_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::bundle_ffi::wgpu_render_bundle_draw( + &mut render_bundle_encoder_resource.0.borrow_mut(), + args.vertex_count, + args.instance_count, + args.first_vertex, + args.first_instance, + ); + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderDrawIndexedArgs { + render_bundle_encoder_rid: u32, + index_count: u32, + instance_count: u32, + first_index: u32, + base_vertex: i32, + first_instance: u32, +} + +pub fn op_webgpu_render_bundle_encoder_draw_indexed( + state: &mut OpState, + args: RenderBundleEncoderDrawIndexedArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let render_bundle_encoder_resource = state + .resource_table + .get::(args.render_bundle_encoder_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::bundle_ffi::wgpu_render_bundle_draw_indexed( + &mut render_bundle_encoder_resource.0.borrow_mut(), + args.index_count, + args.instance_count, + args.first_index, + args.base_vertex, + args.first_instance, + ); + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderDrawIndirectArgs { + render_bundle_encoder_rid: u32, + indirect_buffer: u32, + indirect_offset: u64, +} + +pub fn op_webgpu_render_bundle_encoder_draw_indirect( + state: &mut OpState, + args: RenderBundleEncoderDrawIndirectArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let buffer_resource = state + .resource_table + .get::(args.indirect_buffer) + .ok_or_else(bad_resource_id)?; + let render_bundle_encoder_resource = state + .resource_table + .get::(args.render_bundle_encoder_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::bundle_ffi::wgpu_render_bundle_draw_indirect( + &mut render_bundle_encoder_resource.0.borrow_mut(), + buffer_resource.0, + args.indirect_offset, + ); + + Ok(json!({})) +} diff --git a/op_crates/webgpu/command_encoder.rs b/op_crates/webgpu/command_encoder.rs new file mode 100644 index 00000000000000..b91f677ee8f185 --- /dev/null +++ b/op_crates/webgpu/command_encoder.rs @@ -0,0 +1,734 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::bad_resource_id; +use deno_core::error::AnyError; +use deno_core::serde_json::json; +use deno_core::serde_json::Value; +use deno_core::ZeroCopyBuf; +use deno_core::{OpState, Resource}; +use serde::Deserialize; +use std::borrow::Cow; +use std::cell::RefCell; + +use super::error::WebGPUError; + +pub(crate) struct WebGPUCommandEncoder( + pub(crate) wgpu_core::id::CommandEncoderId, +); +impl Resource for WebGPUCommandEncoder { + fn name(&self) -> Cow { + "webGPUCommandEncoder".into() + } +} + +pub(crate) struct WebGPUCommandBuffer( + pub(crate) wgpu_core::id::CommandBufferId, +); +impl Resource for WebGPUCommandBuffer { + fn name(&self) -> Cow { + "webGPUCommandBuffer".into() + } +} + +fn serialize_store_op(store_op: String) -> wgpu_core::command::StoreOp { + match store_op.as_str() { + "store" => wgpu_core::command::StoreOp::Store, + "clear" => wgpu_core::command::StoreOp::Clear, + _ => unreachable!(), + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateCommandEncoderArgs { + device_rid: u32, + label: Option, + _measure_execution_time: Option, // not yet implemented +} + +pub fn op_webgpu_create_command_encoder( + state: &mut OpState, + args: CreateCommandEncoderArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let instance = state.borrow::(); + let device_resource = state + .resource_table + .get::(args.device_rid) + .ok_or_else(bad_resource_id)?; + let device = device_resource.0; + + let descriptor = wgpu_types::CommandEncoderDescriptor { + label: args.label.map(Cow::from), + }; + + let (command_encoder, maybe_err) = gfx_select!(device => instance.device_create_command_encoder( + device, + &descriptor, + std::marker::PhantomData + )); + + let rid = state + .resource_table + .add(WebGPUCommandEncoder(command_encoder)); + + Ok(json!({ + "rid": rid, + "err": maybe_err.map(WebGPUError::from), + })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GPURenderPassColorAttachment { + view: u32, + resolve_target: Option, + load_op: String, + load_value: Option, + store_op: Option, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GPURenderPassDepthStencilAttachment { + view: u32, + depth_load_op: String, + depth_load_value: Option, + depth_store_op: String, + depth_read_only: Option, + stencil_load_op: String, + stencil_load_value: Option, + stencil_store_op: String, + stencil_read_only: Option, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderBeginRenderPassArgs { + command_encoder_rid: u32, + label: Option, + color_attachments: Vec, + depth_stencil_attachment: Option, + _occlusion_query_set: Option, // not yet implemented +} + +pub fn op_webgpu_command_encoder_begin_render_pass( + state: &mut OpState, + args: CommandEncoderBeginRenderPassArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let command_encoder_resource = state + .resource_table + .get::(args.command_encoder_rid) + .ok_or_else(bad_resource_id)?; + + let mut color_attachments = vec![]; + + for color_attachment in args.color_attachments { + let texture_view_resource = state + .resource_table + .get::(color_attachment.view) + .ok_or_else(bad_resource_id)?; + + let attachment = wgpu_core::command::ColorAttachmentDescriptor { + attachment: texture_view_resource.0, + resolve_target: color_attachment + .resolve_target + .map(|rid| { + state + .resource_table + .get::(rid) + .ok_or_else(bad_resource_id) + }) + .transpose()? + .map(|texture| texture.0), + channel: match color_attachment.load_op.as_str() { + "load" => wgpu_core::command::PassChannel { + load_op: wgpu_core::command::LoadOp::Load, + store_op: color_attachment + .store_op + .map_or(wgpu_core::command::StoreOp::Store, serialize_store_op), + clear_value: Default::default(), + read_only: false, + }, + "clear" => { + let color = color_attachment.load_value.unwrap(); + wgpu_core::command::PassChannel { + load_op: wgpu_core::command::LoadOp::Clear, + store_op: color_attachment + .store_op + .map_or(wgpu_core::command::StoreOp::Store, serialize_store_op), + clear_value: wgpu_types::Color { + r: color.r, + g: color.g, + b: color.b, + a: color.a, + }, + read_only: false, + } + } + _ => unreachable!(), + }, + }; + + color_attachments.push(attachment) + } + + let mut depth_stencil_attachment = None; + + if let Some(attachment) = args.depth_stencil_attachment { + let texture_view_resource = state + .resource_table + .get::(attachment.view) + .ok_or_else(bad_resource_id)?; + + depth_stencil_attachment = + Some(wgpu_core::command::DepthStencilAttachmentDescriptor { + attachment: texture_view_resource.0, + depth: match attachment.depth_load_op.as_str() { + "load" => wgpu_core::command::PassChannel { + load_op: wgpu_core::command::LoadOp::Load, + store_op: serialize_store_op(attachment.depth_store_op), + clear_value: 0.0, + read_only: attachment.depth_read_only.unwrap_or(false), + }, + "clear" => wgpu_core::command::PassChannel { + load_op: wgpu_core::command::LoadOp::Clear, + store_op: serialize_store_op(attachment.depth_store_op), + clear_value: attachment.depth_load_value.unwrap(), + read_only: attachment.depth_read_only.unwrap_or(false), + }, + _ => unreachable!(), + }, + stencil: match attachment.stencil_load_op.as_str() { + "load" => wgpu_core::command::PassChannel { + load_op: wgpu_core::command::LoadOp::Load, + store_op: serialize_store_op(attachment.stencil_store_op), + clear_value: 0, + read_only: attachment.stencil_read_only.unwrap_or(false), + }, + "clear" => wgpu_core::command::PassChannel { + load_op: wgpu_core::command::LoadOp::Clear, + store_op: serialize_store_op(attachment.stencil_store_op), + clear_value: attachment.stencil_load_value.unwrap(), + read_only: attachment.stencil_read_only.unwrap_or(false), + }, + _ => unreachable!(), + }, + }); + } + + let descriptor = wgpu_core::command::RenderPassDescriptor { + label: args.label.map(Cow::from), + color_attachments: Cow::from(color_attachments), + depth_stencil_attachment: depth_stencil_attachment.as_ref(), + }; + + let render_pass = wgpu_core::command::RenderPass::new( + command_encoder_resource.0, + &descriptor, + ); + + let rid = state + .resource_table + .add(super::render_pass::WebGPURenderPass(RefCell::new( + render_pass, + ))); + + Ok(json!({ + "rid": rid, + })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderBeginComputePassArgs { + command_encoder_rid: u32, + label: Option, +} + +pub fn op_webgpu_command_encoder_begin_compute_pass( + state: &mut OpState, + args: CommandEncoderBeginComputePassArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let command_encoder_resource = state + .resource_table + .get::(args.command_encoder_rid) + .ok_or_else(bad_resource_id)?; + + let descriptor = wgpu_core::command::ComputePassDescriptor { + label: args.label.map(Cow::from), + }; + + let compute_pass = wgpu_core::command::ComputePass::new( + command_encoder_resource.0, + &descriptor, + ); + + let rid = state + .resource_table + .add(super::compute_pass::WebGPUComputePass(RefCell::new( + compute_pass, + ))); + + Ok(json!({ + "rid": rid, + })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderCopyBufferToBufferArgs { + command_encoder_rid: u32, + source: u32, + source_offset: u64, + destination: u32, + destination_offset: u64, + size: u64, +} + +pub fn op_webgpu_command_encoder_copy_buffer_to_buffer( + state: &mut OpState, + args: CommandEncoderCopyBufferToBufferArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let instance = state.borrow::(); + let command_encoder_resource = state + .resource_table + .get::(args.command_encoder_rid) + .ok_or_else(bad_resource_id)?; + let command_encoder = command_encoder_resource.0; + let source_buffer_resource = state + .resource_table + .get::(args.source) + .ok_or_else(bad_resource_id)?; + let source_buffer = source_buffer_resource.0; + let destination_buffer_resource = state + .resource_table + .get::(args.destination) + .ok_or_else(bad_resource_id)?; + let destination_buffer = destination_buffer_resource.0; + + let maybe_err = gfx_select!(command_encoder => instance.command_encoder_copy_buffer_to_buffer( + command_encoder, + source_buffer, + args.source_offset, + destination_buffer, + args.destination_offset, + args.size + )).err(); + + Ok(json!({ "err": maybe_err.map(WebGPUError::from) })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GPUImageCopyBuffer { + buffer: u32, + offset: Option, + bytes_per_row: Option, + rows_per_image: Option, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GPUOrigin3D { + pub x: Option, + pub y: Option, + pub z: Option, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GPUImageCopyTexture { + pub texture: u32, + pub mip_level: Option, + pub origin: Option, + pub _aspect: Option, // not yet implemented +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderCopyBufferToTextureArgs { + command_encoder_rid: u32, + source: GPUImageCopyBuffer, + destination: GPUImageCopyTexture, + copy_size: super::texture::GPUExtent3D, +} + +pub fn op_webgpu_command_encoder_copy_buffer_to_texture( + state: &mut OpState, + args: CommandEncoderCopyBufferToTextureArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let instance = state.borrow::(); + let command_encoder_resource = state + .resource_table + .get::(args.command_encoder_rid) + .ok_or_else(bad_resource_id)?; + let command_encoder = command_encoder_resource.0; + let source_buffer_resource = state + .resource_table + .get::(args.source.buffer) + .ok_or_else(bad_resource_id)?; + let destination_texture_resource = state + .resource_table + .get::(args.destination.texture) + .ok_or_else(bad_resource_id)?; + + let source = wgpu_core::command::BufferCopyView { + buffer: source_buffer_resource.0, + layout: wgpu_types::TextureDataLayout { + offset: args.source.offset.unwrap_or(0), + bytes_per_row: args.source.bytes_per_row.unwrap_or(0), + rows_per_image: args.source.rows_per_image.unwrap_or(0), + }, + }; + let destination = wgpu_core::command::TextureCopyView { + texture: destination_texture_resource.0, + mip_level: args.destination.mip_level.unwrap_or(0), + origin: args + .destination + .origin + .map_or(Default::default(), |origin| wgpu_types::Origin3d { + x: origin.x.unwrap_or(0), + y: origin.y.unwrap_or(0), + z: origin.z.unwrap_or(0), + }), + }; + let maybe_err = gfx_select!(command_encoder => instance.command_encoder_copy_buffer_to_texture( + command_encoder, + &source, + &destination, + &wgpu_types::Extent3d { + width: args.copy_size.width.unwrap_or(1), + height: args.copy_size.height.unwrap_or(1), + depth: args.copy_size.depth.unwrap_or(1), + } + )).err(); + + Ok(json!({ "err": maybe_err.map(WebGPUError::from) })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderCopyTextureToBufferArgs { + command_encoder_rid: u32, + source: GPUImageCopyTexture, + destination: GPUImageCopyBuffer, + copy_size: super::texture::GPUExtent3D, +} + +pub fn op_webgpu_command_encoder_copy_texture_to_buffer( + state: &mut OpState, + args: CommandEncoderCopyTextureToBufferArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let instance = state.borrow::(); + let command_encoder_resource = state + .resource_table + .get::(args.command_encoder_rid) + .ok_or_else(bad_resource_id)?; + let command_encoder = command_encoder_resource.0; + let source_texture_resource = state + .resource_table + .get::(args.source.texture) + .ok_or_else(bad_resource_id)?; + let destination_buffer_resource = state + .resource_table + .get::(args.destination.buffer) + .ok_or_else(bad_resource_id)?; + + let source = wgpu_core::command::TextureCopyView { + texture: source_texture_resource.0, + mip_level: args.source.mip_level.unwrap_or(0), + origin: args.source.origin.map_or(Default::default(), |origin| { + wgpu_types::Origin3d { + x: origin.x.unwrap_or(0), + y: origin.y.unwrap_or(0), + z: origin.z.unwrap_or(0), + } + }), + }; + let destination = wgpu_core::command::BufferCopyView { + buffer: destination_buffer_resource.0, + layout: wgpu_types::TextureDataLayout { + offset: args.destination.offset.unwrap_or(0), + bytes_per_row: args.destination.bytes_per_row.unwrap_or(0), + rows_per_image: args.destination.rows_per_image.unwrap_or(0), + }, + }; + let maybe_err = gfx_select!(command_encoder => instance.command_encoder_copy_texture_to_buffer( + command_encoder, + &source, + &destination, + &wgpu_types::Extent3d { + width: args.copy_size.width.unwrap_or(1), + height: args.copy_size.height.unwrap_or(1), + depth: args.copy_size.depth.unwrap_or(1), + } + )).err(); + + Ok(json!({ "err": maybe_err.map(WebGPUError::from) })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderCopyTextureToTextureArgs { + command_encoder_rid: u32, + source: GPUImageCopyTexture, + destination: GPUImageCopyTexture, + copy_size: super::texture::GPUExtent3D, +} + +pub fn op_webgpu_command_encoder_copy_texture_to_texture( + state: &mut OpState, + args: CommandEncoderCopyTextureToTextureArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let instance = state.borrow::(); + let command_encoder_resource = state + .resource_table + .get::(args.command_encoder_rid) + .ok_or_else(bad_resource_id)?; + let command_encoder = command_encoder_resource.0; + let source_texture_resource = state + .resource_table + .get::(args.source.texture) + .ok_or_else(bad_resource_id)?; + let destination_texture_resource = state + .resource_table + .get::(args.destination.texture) + .ok_or_else(bad_resource_id)?; + + let source = wgpu_core::command::TextureCopyView { + texture: source_texture_resource.0, + mip_level: args.source.mip_level.unwrap_or(0), + origin: args.source.origin.map_or(Default::default(), |origin| { + wgpu_types::Origin3d { + x: origin.x.unwrap_or(0), + y: origin.y.unwrap_or(0), + z: origin.z.unwrap_or(0), + } + }), + }; + let destination = wgpu_core::command::TextureCopyView { + texture: destination_texture_resource.0, + mip_level: args.destination.mip_level.unwrap_or(0), + origin: args + .destination + .origin + .map_or(Default::default(), |origin| wgpu_types::Origin3d { + x: origin.x.unwrap_or(0), + y: origin.y.unwrap_or(0), + z: origin.z.unwrap_or(0), + }), + }; + let maybe_err = gfx_select!(command_encoder => instance.command_encoder_copy_texture_to_texture( + command_encoder, + &source, + &destination, + &wgpu_types::Extent3d { + width: args.copy_size.width.unwrap_or(1), + height: args.copy_size.height.unwrap_or(1), + depth: args.copy_size.depth.unwrap_or(1), + } + )).err(); + + Ok(json!({ "err": maybe_err.map(WebGPUError::from) })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderPushDebugGroupArgs { + command_encoder_rid: u32, + group_label: String, +} + +pub fn op_webgpu_command_encoder_push_debug_group( + state: &mut OpState, + args: CommandEncoderPushDebugGroupArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let instance = state.borrow::(); + let command_encoder_resource = state + .resource_table + .get::(args.command_encoder_rid) + .ok_or_else(bad_resource_id)?; + let command_encoder = command_encoder_resource.0; + + let maybe_err = gfx_select!(command_encoder => instance + .command_encoder_push_debug_group(command_encoder, &args.group_label)) + .err(); + + Ok(json!({ "err": maybe_err.map(WebGPUError::from) })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderPopDebugGroupArgs { + command_encoder_rid: u32, +} + +pub fn op_webgpu_command_encoder_pop_debug_group( + state: &mut OpState, + args: CommandEncoderPopDebugGroupArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let instance = state.borrow::(); + let command_encoder_resource = state + .resource_table + .get::(args.command_encoder_rid) + .ok_or_else(bad_resource_id)?; + let command_encoder = command_encoder_resource.0; + + let maybe_err = gfx_select!(command_encoder => instance.command_encoder_pop_debug_group(command_encoder)).err(); + + Ok(json!({ "err": maybe_err.map(WebGPUError::from) })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderInsertDebugMarkerArgs { + command_encoder_rid: u32, + marker_label: String, +} + +pub fn op_webgpu_command_encoder_insert_debug_marker( + state: &mut OpState, + args: CommandEncoderInsertDebugMarkerArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let instance = state.borrow::(); + let command_encoder_resource = state + .resource_table + .get::(args.command_encoder_rid) + .ok_or_else(bad_resource_id)?; + let command_encoder = command_encoder_resource.0; + + let maybe_err = gfx_select!(command_encoder => instance.command_encoder_insert_debug_marker( + command_encoder, + &args.marker_label + )).err(); + + Ok(json!({ "err": maybe_err.map(WebGPUError::from) })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderWriteTimestampArgs { + command_encoder_rid: u32, + query_set: u32, + query_index: u32, +} + +pub fn op_webgpu_command_encoder_write_timestamp( + state: &mut OpState, + args: CommandEncoderWriteTimestampArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let instance = state.borrow::(); + let command_encoder_resource = state + .resource_table + .get::(args.command_encoder_rid) + .ok_or_else(bad_resource_id)?; + let command_encoder = command_encoder_resource.0; + let query_set_resource = state + .resource_table + .get::(args.query_set) + .ok_or_else(bad_resource_id)?; + + let maybe_err = + gfx_select!(command_encoder => instance.command_encoder_write_timestamp( + command_encoder, + query_set_resource.0, + args.query_index + )) + .err(); + + Ok(json!({ "err": maybe_err.map(WebGPUError::from) })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderResolveQuerySetArgs { + command_encoder_rid: u32, + query_set: u32, + first_query: u32, + query_count: u32, + destination: u32, + destination_offset: u64, +} + +pub fn op_webgpu_command_encoder_resolve_query_set( + state: &mut OpState, + args: CommandEncoderResolveQuerySetArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let instance = state.borrow::(); + let command_encoder_resource = state + .resource_table + .get::(args.command_encoder_rid) + .ok_or_else(bad_resource_id)?; + let command_encoder = command_encoder_resource.0; + let query_set_resource = state + .resource_table + .get::(args.query_set) + .ok_or_else(bad_resource_id)?; + let destination_resource = state + .resource_table + .get::(args.destination) + .ok_or_else(bad_resource_id)?; + + let maybe_err = + gfx_select!(command_encoder => instance.command_encoder_resolve_query_set( + command_encoder, + query_set_resource.0, + args.first_query, + args.query_count, + destination_resource.0, + args.destination_offset + )) + .err(); + + Ok(json!({ "err": maybe_err.map(WebGPUError::from) })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderFinishArgs { + command_encoder_rid: u32, + label: Option, +} + +pub fn op_webgpu_command_encoder_finish( + state: &mut OpState, + args: CommandEncoderFinishArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let command_encoder_resource = state + .resource_table + .take::(args.command_encoder_rid) + .ok_or_else(bad_resource_id)?; + let command_encoder = command_encoder_resource.0; + let instance = state.borrow::(); + + let descriptor = wgpu_types::CommandBufferDescriptor { + label: args.label.map(Cow::from), + }; + + let (command_buffer, maybe_err) = gfx_select!(command_encoder => instance.command_encoder_finish( + command_encoder, + &descriptor + )); + + let rid = state + .resource_table + .add(WebGPUCommandBuffer(command_buffer)); + + Ok(json!({ + "rid": rid, + "err": maybe_err.map(WebGPUError::from) + })) +} diff --git a/op_crates/webgpu/compute_pass.rs b/op_crates/webgpu/compute_pass.rs new file mode 100644 index 00000000000000..711bdea97ad8d0 --- /dev/null +++ b/op_crates/webgpu/compute_pass.rs @@ -0,0 +1,365 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::bad_resource_id; +use deno_core::error::AnyError; +use deno_core::serde_json::json; +use deno_core::serde_json::Value; +use deno_core::ZeroCopyBuf; +use deno_core::{OpState, Resource}; +use serde::Deserialize; +use std::borrow::Cow; +use std::cell::RefCell; + +use super::error::WebGPUError; + +pub(crate) struct WebGPUComputePass( + pub(crate) RefCell, +); +impl Resource for WebGPUComputePass { + fn name(&self) -> Cow { + "webGPUComputePass".into() + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassSetPipelineArgs { + compute_pass_rid: u32, + pipeline: u32, +} + +pub fn op_webgpu_compute_pass_set_pipeline( + state: &mut OpState, + args: ComputePassSetPipelineArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let compute_pipeline_resource = state + .resource_table + .get::(args.pipeline) + .ok_or_else(bad_resource_id)?; + let compute_pass_resource = state + .resource_table + .get::(args.compute_pass_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::compute_ffi::wgpu_compute_pass_set_pipeline( + &mut compute_pass_resource.0.borrow_mut(), + compute_pipeline_resource.0, + ); + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassDispatchArgs { + compute_pass_rid: u32, + x: u32, + y: u32, + z: u32, +} + +pub fn op_webgpu_compute_pass_dispatch( + state: &mut OpState, + args: ComputePassDispatchArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let compute_pass_resource = state + .resource_table + .get::(args.compute_pass_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::compute_ffi::wgpu_compute_pass_dispatch( + &mut compute_pass_resource.0.borrow_mut(), + args.x, + args.y, + args.z, + ); + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassDispatchIndirectArgs { + compute_pass_rid: u32, + indirect_buffer: u32, + indirect_offset: u64, +} + +pub fn op_webgpu_compute_pass_dispatch_indirect( + state: &mut OpState, + args: ComputePassDispatchIndirectArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let buffer_resource = state + .resource_table + .get::(args.indirect_buffer) + .ok_or_else(bad_resource_id)?; + let compute_pass_resource = state + .resource_table + .get::(args.compute_pass_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::compute_ffi::wgpu_compute_pass_dispatch_indirect( + &mut compute_pass_resource.0.borrow_mut(), + buffer_resource.0, + args.indirect_offset, + ); + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassBeginPipelineStatisticsQueryArgs { + compute_pass_rid: u32, + query_set: u32, + query_index: u32, +} + +pub fn op_webgpu_compute_pass_begin_pipeline_statistics_query( + state: &mut OpState, + args: ComputePassBeginPipelineStatisticsQueryArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let compute_pass_resource = state + .resource_table + .get::(args.compute_pass_rid) + .ok_or_else(bad_resource_id)?; + let query_set_resource = state + .resource_table + .get::(args.query_set) + .ok_or_else(bad_resource_id)?; + + unsafe { + wgpu_core::command::compute_ffi::wgpu_compute_pass_begin_pipeline_statistics_query( + &mut compute_pass_resource.0.borrow_mut(), + query_set_resource.0, + args.query_index, + ); + } + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassEndPipelineStatisticsQueryArgs { + compute_pass_rid: u32, +} + +pub fn op_webgpu_compute_pass_end_pipeline_statistics_query( + state: &mut OpState, + args: ComputePassEndPipelineStatisticsQueryArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let compute_pass_resource = state + .resource_table + .get::(args.compute_pass_rid) + .ok_or_else(bad_resource_id)?; + + unsafe { + wgpu_core::command::compute_ffi::wgpu_compute_pass_end_pipeline_statistics_query( + &mut compute_pass_resource.0.borrow_mut(), + ); + } + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassWriteTimestampArgs { + compute_pass_rid: u32, + query_set: u32, + query_index: u32, +} + +pub fn op_webgpu_compute_pass_write_timestamp( + state: &mut OpState, + args: ComputePassWriteTimestampArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let compute_pass_resource = state + .resource_table + .get::(args.compute_pass_rid) + .ok_or_else(bad_resource_id)?; + let query_set_resource = state + .resource_table + .get::(args.query_set) + .ok_or_else(bad_resource_id)?; + + unsafe { + wgpu_core::command::compute_ffi::wgpu_compute_pass_write_timestamp( + &mut compute_pass_resource.0.borrow_mut(), + query_set_resource.0, + args.query_index, + ); + } + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassEndPassArgs { + command_encoder_rid: u32, + compute_pass_rid: u32, +} + +pub fn op_webgpu_compute_pass_end_pass( + state: &mut OpState, + args: ComputePassEndPassArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let command_encoder_resource = state + .resource_table + .get::( + args.command_encoder_rid, + ) + .ok_or_else(bad_resource_id)?; + let command_encoder = command_encoder_resource.0; + let compute_pass_resource = state + .resource_table + .take::(args.compute_pass_rid) + .ok_or_else(bad_resource_id)?; + let compute_pass = &compute_pass_resource.0.borrow(); + let instance = state.borrow::(); + + let maybe_err = + gfx_select!(command_encoder => instance.command_encoder_run_compute_pass( + command_encoder, + compute_pass + )) + .err(); + + Ok(json!({ "err": maybe_err.map(WebGPUError::from) })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassSetBindGroupArgs { + compute_pass_rid: u32, + index: u32, + bind_group: u32, + dynamic_offsets_data: Option>, + dynamic_offsets_data_start: usize, + dynamic_offsets_data_length: usize, +} + +pub fn op_webgpu_compute_pass_set_bind_group( + state: &mut OpState, + args: ComputePassSetBindGroupArgs, + zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let bind_group_resource = state + .resource_table + .get::(args.bind_group) + .ok_or_else(bad_resource_id)?; + let compute_pass_resource = state + .resource_table + .get::(args.compute_pass_rid) + .ok_or_else(bad_resource_id)?; + + unsafe { + wgpu_core::command::compute_ffi::wgpu_compute_pass_set_bind_group( + &mut compute_pass_resource.0.borrow_mut(), + args.index, + bind_group_resource.0, + match args.dynamic_offsets_data { + Some(data) => data.as_ptr(), + None => { + let (prefix, data, suffix) = zero_copy[0].align_to::(); + assert!(prefix.is_empty()); + assert!(suffix.is_empty()); + data[args.dynamic_offsets_data_start..].as_ptr() + } + }, + args.dynamic_offsets_data_length, + ); + } + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassPushDebugGroupArgs { + compute_pass_rid: u32, + group_label: String, +} + +pub fn op_webgpu_compute_pass_push_debug_group( + state: &mut OpState, + args: ComputePassPushDebugGroupArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let compute_pass_resource = state + .resource_table + .get::(args.compute_pass_rid) + .ok_or_else(bad_resource_id)?; + + unsafe { + let label = std::ffi::CString::new(args.group_label).unwrap(); + wgpu_core::command::compute_ffi::wgpu_compute_pass_push_debug_group( + &mut compute_pass_resource.0.borrow_mut(), + label.as_ptr(), + 0, // wgpu#975 + ); + } + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassPopDebugGroupArgs { + compute_pass_rid: u32, +} + +pub fn op_webgpu_compute_pass_pop_debug_group( + state: &mut OpState, + args: ComputePassPopDebugGroupArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let compute_pass_resource = state + .resource_table + .get::(args.compute_pass_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::compute_ffi::wgpu_compute_pass_pop_debug_group( + &mut compute_pass_resource.0.borrow_mut(), + ); + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassInsertDebugMarkerArgs { + compute_pass_rid: u32, + marker_label: String, +} + +pub fn op_webgpu_compute_pass_insert_debug_marker( + state: &mut OpState, + args: ComputePassInsertDebugMarkerArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let compute_pass_resource = state + .resource_table + .get::(args.compute_pass_rid) + .ok_or_else(bad_resource_id)?; + + unsafe { + let label = std::ffi::CString::new(args.marker_label).unwrap(); + wgpu_core::command::compute_ffi::wgpu_compute_pass_insert_debug_marker( + &mut compute_pass_resource.0.borrow_mut(), + label.as_ptr(), + 0, // wgpu#975 + ); + } + + Ok(json!({})) +} diff --git a/op_crates/webgpu/error.rs b/op_crates/webgpu/error.rs new file mode 100644 index 00000000000000..e793dabfa3e7c7 --- /dev/null +++ b/op_crates/webgpu/error.rs @@ -0,0 +1,252 @@ +use deno_core::error::AnyError; +use serde::Serialize; +use std::fmt; +use wgpu_core::binding_model::CreateBindGroupError; +use wgpu_core::binding_model::CreateBindGroupLayoutError; +use wgpu_core::binding_model::CreatePipelineLayoutError; +use wgpu_core::binding_model::GetBindGroupLayoutError; +use wgpu_core::command::CommandAllocatorError; +use wgpu_core::command::CommandEncoderError; +use wgpu_core::command::ComputePassError; +use wgpu_core::command::CopyError; +use wgpu_core::command::CreateRenderBundleError; +use wgpu_core::command::QueryError; +use wgpu_core::command::RenderBundleError; +use wgpu_core::command::RenderPassError; +use wgpu_core::device::queue::QueueSubmitError; +use wgpu_core::device::queue::QueueWriteError; +use wgpu_core::device::DeviceError; +use wgpu_core::pipeline::CreateComputePipelineError; +use wgpu_core::pipeline::CreateRenderPipelineError; +use wgpu_core::pipeline::CreateShaderModuleError; +use wgpu_core::resource::BufferAccessError; +use wgpu_core::resource::CreateBufferError; +use wgpu_core::resource::CreateQuerySetError; +use wgpu_core::resource::CreateSamplerError; +use wgpu_core::resource::CreateTextureError; +use wgpu_core::resource::CreateTextureViewError; + +#[derive(Serialize)] +#[serde(tag = "type", content = "value")] +#[serde(rename_all = "kebab-case")] +pub enum WebGPUError { + Lost, + OutOfMemory, + Validation(String), +} + +impl From for WebGPUError { + fn from(err: CreateBufferError) -> Self { + match err { + CreateBufferError::Device(err) => err.into(), + CreateBufferError::AccessError(err) => err.into(), + err => WebGPUError::Validation(err.to_string()), + } + } +} + +impl From for WebGPUError { + fn from(err: DeviceError) -> Self { + match err { + DeviceError::Lost => WebGPUError::Lost, + DeviceError::OutOfMemory => WebGPUError::OutOfMemory, + DeviceError::Invalid => WebGPUError::Validation(err.to_string()), + } + } +} + +impl From for WebGPUError { + fn from(err: BufferAccessError) -> Self { + match err { + BufferAccessError::Device(err) => err.into(), + err => WebGPUError::Validation(err.to_string()), + } + } +} + +impl From for WebGPUError { + fn from(err: CreateBindGroupLayoutError) -> Self { + match err { + CreateBindGroupLayoutError::Device(err) => err.into(), + err => WebGPUError::Validation(err.to_string()), + } + } +} + +impl From for WebGPUError { + fn from(err: CreatePipelineLayoutError) -> Self { + match err { + CreatePipelineLayoutError::Device(err) => err.into(), + err => WebGPUError::Validation(err.to_string()), + } + } +} + +impl From for WebGPUError { + fn from(err: CreateBindGroupError) -> Self { + match err { + CreateBindGroupError::Device(err) => err.into(), + err => WebGPUError::Validation(err.to_string()), + } + } +} + +impl From for WebGPUError { + fn from(err: RenderBundleError) -> Self { + WebGPUError::Validation(err.to_string()) + } +} + +impl From for WebGPUError { + fn from(err: CreateRenderBundleError) -> Self { + WebGPUError::Validation(err.to_string()) + } +} + +impl From for WebGPUError { + fn from(err: CommandAllocatorError) -> Self { + match err { + CommandAllocatorError::Device(err) => err.into(), + } + } +} + +impl From for WebGPUError { + fn from(err: CopyError) -> Self { + WebGPUError::Validation(err.to_string()) + } +} + +impl From for WebGPUError { + fn from(err: CommandEncoderError) -> Self { + WebGPUError::Validation(err.to_string()) + } +} + +impl From for WebGPUError { + fn from(err: QueryError) -> Self { + WebGPUError::Validation(err.to_string()) + } +} + +impl From for WebGPUError { + fn from(err: ComputePassError) -> Self { + WebGPUError::Validation(err.to_string()) + } +} + +impl From for WebGPUError { + fn from(err: CreateComputePipelineError) -> Self { + match err { + CreateComputePipelineError::Device(err) => err.into(), + err => WebGPUError::Validation(err.to_string()), + } + } +} + +impl From for WebGPUError { + fn from(err: GetBindGroupLayoutError) -> Self { + WebGPUError::Validation(err.to_string()) + } +} + +impl From for WebGPUError { + fn from(err: CreateRenderPipelineError) -> Self { + match err { + CreateRenderPipelineError::Device(err) => err.into(), + err => WebGPUError::Validation(err.to_string()), + } + } +} + +impl From for WebGPUError { + fn from(err: RenderPassError) -> Self { + WebGPUError::Validation(err.to_string()) + } +} + +impl From for WebGPUError { + fn from(err: CreateSamplerError) -> Self { + match err { + CreateSamplerError::Device(err) => err.into(), + err => WebGPUError::Validation(err.to_string()), + } + } +} + +impl From for WebGPUError { + fn from(err: CreateShaderModuleError) -> Self { + match err { + CreateShaderModuleError::Device(err) => err.into(), + err => WebGPUError::Validation(err.to_string()), + } + } +} + +impl From for WebGPUError { + fn from(err: CreateTextureError) -> Self { + match err { + CreateTextureError::Device(err) => err.into(), + err => WebGPUError::Validation(err.to_string()), + } + } +} + +impl From for WebGPUError { + fn from(err: CreateTextureViewError) -> Self { + WebGPUError::Validation(err.to_string()) + } +} + +impl From for WebGPUError { + fn from(err: CreateQuerySetError) -> Self { + match err { + CreateQuerySetError::Device(err) => err.into(), + err => WebGPUError::Validation(err.to_string()), + } + } +} + +impl From for WebGPUError { + fn from(err: QueueSubmitError) -> Self { + match err { + QueueSubmitError::Queue(err) => err.into(), + err => WebGPUError::Validation(err.to_string()), + } + } +} + +impl From for WebGPUError { + fn from(err: QueueWriteError) -> Self { + match err { + QueueWriteError::Queue(err) => err.into(), + err => WebGPUError::Validation(err.to_string()), + } + } +} + +#[derive(Debug)] +pub struct DOMExceptionOperationError { + pub msg: String, +} + +impl DOMExceptionOperationError { + pub fn new(msg: &str) -> Self { + DOMExceptionOperationError { + msg: msg.to_string(), + } + } +} + +impl fmt::Display for DOMExceptionOperationError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.pad(&self.msg) + } +} + +impl std::error::Error for DOMExceptionOperationError {} + +pub fn get_error_class_name(e: &AnyError) -> Option<&'static str> { + e.downcast_ref::() + .map(|_| "DOMExceptionOperationError") +} diff --git a/op_crates/webgpu/lib.deno_webgpu.d.ts b/op_crates/webgpu/lib.deno_webgpu.d.ts new file mode 100644 index 00000000000000..cd2680b3d7e790 --- /dev/null +++ b/op_crates/webgpu/lib.deno_webgpu.d.ts @@ -0,0 +1,1126 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +// deno-lint-ignore-file no-explicit-any no-empty-interface + +/// +/// + +// 8cc98b6f10b7f354473a08c3773bb1de839845b9 + +interface GPUObjectBase { + label: string | null; +} + +declare interface GPUObjectDescriptorBase { + label?: string; +} + +declare class GPUAdapterLimits { + maxTextureDimension1D?: number; + maxTextureDimension2D?: number; + maxTextureDimension3D?: number; + maxTextureArrayLayers?: number; + maxBindGroups?: number; + maxDynamicUniformBuffersPerPipelineLayout?: number; + maxDynamicStorageBuffersPerPipelineLayout?: number; + maxSampledTexturesPerShaderStage?: number; + maxSamplersPerShaderStage?: number; + maxStorageBuffersPerShaderStage?: number; + maxStorageTexturesPerShaderStage?: number; + maxUniformBuffersPerShaderStage?: number; + maxUniformBufferBindingSize?: number; + maxStorageBufferBindingSize?: number; + maxVertexBuffers?: number; + maxVertexAttributes?: number; + maxVertexBufferArrayStride?: number; +} + +declare class GPUAdapterFeatures { + forEach( + callbackfn: ( + value: GPUFeatureName, + value2: GPUFeatureName, + set: Set, + ) => void, + thisArg?: any, + ): void; + has(value: GPUFeatureName): boolean; + size: number; + [ + Symbol + .iterator + ](): IterableIterator; + entries(): IterableIterator<[GPUFeatureName, GPUFeatureName]>; + keys(): IterableIterator; + values(): IterableIterator; +} + +declare class GPU { + requestAdapter( + options?: GPURequestAdapterOptions, + ): Promise; +} + +declare interface GPURequestAdapterOptions { + powerPreference?: GPUPowerPreference; +} + +declare type GPUPowerPreference = "low-power" | "high-performance"; + +declare class GPUAdapter { + readonly name: string; + readonly features: GPUAdapterFeatures; + readonly limits: GPUAdapterLimits; + + requestDevice(descriptor?: GPUDeviceDescriptor): Promise; +} + +declare interface GPUDeviceDescriptor extends GPUObjectDescriptorBase { + nonGuaranteedFeatures?: GPUFeatureName[]; + nonGuaranteedLimits?: Record; +} + +declare type GPUFeatureName = + | "depth-clamping" + | "depth24unorm-stencil8" + | "depth32float-stencil8" + | "pipeline-statistics-query" + | "texture-compression-bc" + | "timestamp-query" + // extended from spec + | "mappable-primary-buffers" + | "sampled-texture-binding-array" + | "sampled-texture-array-dynamic-indexing" + | "sampled-texture-array-non-uniform-indexing" + | "unsized-binding-array" + | "multi-draw-indirect" + | "multi-draw-indirect-count" + | "push-constants" + | "address-mode-clamp-to-border" + | "non-fill-polygon-mode" + | "texture-compression-etc2" + | "texture-compression-astc-ldr" + | "texture-adapter-specific-format-features" + | "shader-float64" + | "vertex-attribute-64bit"; + +declare class GPUDevice extends EventTarget implements GPUObjectBase { + label: string | null; + + readonly lost: Promise; + pushErrorScope(filter: GPUErrorFilter): undefined; + popErrorScope(): Promise; + onuncapturederror: + | ((this: GPUDevice, ev: GPUUncapturedErrorEvent) => any) + | null; + + readonly adapter: GPUAdapter; + readonly features: ReadonlyArray; + readonly limits: Record; + readonly queue: GPUQueue; + + destroy(): undefined; + + createBuffer(descriptor: GPUBufferDescriptor): GPUBuffer; + createTexture(descriptor: GPUTextureDescriptor): GPUTexture; + createSampler(descriptor?: GPUSamplerDescriptor): GPUSampler; + + createBindGroupLayout( + descriptor: GPUBindGroupLayoutDescriptor, + ): GPUBindGroupLayout; + createPipelineLayout( + descriptor: GPUPipelineLayoutDescriptor, + ): GPUPipelineLayout; + createBindGroup(descriptor: GPUBindGroupDescriptor): GPUBindGroup; + + createShaderModule(descriptor: GPUShaderModuleDescriptor): GPUShaderModule; + createComputePipeline( + descriptor: GPUComputePipelineDescriptor, + ): GPUComputePipeline; + createRenderPipeline( + descriptor: GPURenderPipelineDescriptor, + ): GPURenderPipeline; + createComputePipelineAsync( + descriptor: GPUComputePipelineDescriptor, + ): Promise; + createRenderPipelineAsync( + descriptor: GPURenderPipelineDescriptor, + ): Promise; + + createCommandEncoder( + descriptor?: GPUCommandEncoderDescriptor, + ): GPUCommandEncoder; + createRenderBundleEncoder( + descriptor: GPURenderBundleEncoderDescriptor, + ): GPURenderBundleEncoder; + + createQuerySet(descriptor: GPUQuerySetDescriptor): GPUQuerySet; +} + +declare class GPUBuffer implements GPUObjectBase { + label: string | null; + + mapAsync( + mode: GPUMapModeFlags, + offset?: number, + size?: number, + ): Promise; + getMappedRange(offset?: number, size?: number): ArrayBuffer; + unmap(): undefined; + + destroy(): undefined; +} + +declare interface GPUBufferDescriptor extends GPUObjectDescriptorBase { + size: number; + usage: GPUBufferUsageFlags; + mappedAtCreation?: boolean; +} + +declare type GPUBufferUsageFlags = number; +declare class GPUBufferUsage { + static MAP_READ: 0x0001; + static MAP_WRITE: 0x0002; + static COPY_SRC: 0x0004; + static COPY_DST: 0x0008; + static INDEX: 0x0010; + static VERTEX: 0x0020; + static UNIFORM: 0x0040; + static STORAGE: 0x0080; + static INDIRECT: 0x0100; + static QUERY_RESOLVE: 0x0200; +} + +declare type GPUMapModeFlags = number; +declare class GPUMapMode { + static READ: 0x0001; + static WRITE: 0x0002; +} + +declare class GPUTexture implements GPUObjectBase { + label: string | null; + + createView(descriptor?: GPUTextureViewDescriptor): GPUTextureView; + destroy(): undefined; +} + +declare interface GPUTextureDescriptor extends GPUObjectDescriptorBase { + size: GPUExtent3D; + mipLevelCount?: number; + sampleCount?: number; + dimension?: GPUTextureDimension; + format: GPUTextureFormat; + usage: GPUTextureUsageFlags; +} + +declare type GPUTextureDimension = "1d" | "2d" | "3d"; + +declare type GPUTextureUsageFlags = number; +declare class GPUTextureUsage { + static COPY_SRC: 0x01; + static COPY_DST: 0x02; + static SAMPLED: 0x04; + static STORAGE: 0x08; + static RENDER_ATTACHMENT: 0x10; +} + +declare class GPUTextureView implements GPUObjectBase { + label: string | null; +} + +declare interface GPUTextureViewDescriptor extends GPUObjectDescriptorBase { + format?: GPUTextureFormat; + dimension?: GPUTextureViewDimension; + aspect?: GPUTextureAspect; + baseMipLevel?: number; + mipLevelCount?: number; + baseArrayLayer?: number; + arrayLayerCount?: number; +} + +declare type GPUTextureViewDimension = + | "1d" + | "2d" + | "2d-array" + | "cube" + | "cube-array" + | "3d"; + +declare type GPUTextureAspect = "all" | "stencil-only" | "depth-only"; + +declare type GPUTextureFormat = + | "r8unorm" + | "r8snorm" + | "r8uint" + | "r8sint" + | "r16uint" + | "r16sint" + | "r16float" + | "rg8unorm" + | "rg8snorm" + | "rg8uint" + | "rg8sint" + | "r32uint" + | "r32sint" + | "r32float" + | "rg16uint" + | "rg16sint" + | "rg16float" + | "rgba8unorm" + | "rgba8unorm-srgb" + | "rgba8snorm" + | "rgba8uint" + | "rgba8sint" + | "bgra8unorm" + | "bgra8unorm-srgb" + | "rgb9e5ufloat" + | "rgb10a2unorm" + | "rg11b10ufloat" + | "rg32uint" + | "rg32sint" + | "rg32float" + | "rgba16uint" + | "rgba16sint" + | "rgba16float" + | "rgba32uint" + | "rgba32sint" + | "rgba32float" + | "stencil8" + | "depth16unorm" + | "depth24plus" + | "depth24plus-stencil8" + | "depth32float" + | "bc1-rgba-unorm" + | "bc1-rgba-unorm-srgb" + | "bc2-rgba-unorm" + | "bc2-rgba-unorm-srgb" + | "bc3-rgba-unorm" + | "bc3-rgba-unorm-srgb" + | "bc4-r-unorm" + | "bc4-r-snorm" + | "bc5-rg-unorm" + | "bc5-rg-snorm" + | "bc6h-rgb-ufloat" + | "bc6h-rgb-float" + | "bc7-rgba-unorm" + | "bc7-rgba-unorm-srgb" + | "depth24unorm-stencil8" + | "depth32float-stencil8"; + +declare class GPUSampler implements GPUObjectBase { + label: string | null; +} + +declare interface GPUSamplerDescriptor extends GPUObjectDescriptorBase { + addressModeU?: GPUAddressMode; + addressModeV?: GPUAddressMode; + addressModeW?: GPUAddressMode; + magFilter?: GPUFilterMode; + minFilter?: GPUFilterMode; + mipmapFilter?: GPUFilterMode; + lodMinClamp?: number; + lodMaxClamp?: number; + compare?: GPUCompareFunction; + maxAnisotropy?: number; +} + +declare type GPUAddressMode = "clamp-to-edge" | "repeat" | "mirror-repeat"; + +declare type GPUFilterMode = "nearest" | "linear"; + +declare type GPUCompareFunction = + | "never" + | "less" + | "equal" + | "less-equal" + | "greater" + | "not-equal" + | "greater-equal" + | "always"; + +declare class GPUBindGroupLayout implements GPUObjectBase { + label: string | null; +} + +declare interface GPUBindGroupLayoutDescriptor extends GPUObjectDescriptorBase { + entries: GPUBindGroupLayoutEntry[]; +} + +declare interface GPUBindGroupLayoutEntry { + binding: number; + visibility: GPUShaderStageFlags; + + buffer?: GPUBufferBindingLayout; + sampler?: GPUSamplerBindingLayout; + texture?: GPUTextureBindingLayout; + storageTexture?: GPUStorageTextureBindingLayout; +} + +declare type GPUShaderStageFlags = number; +declare class GPUShaderStage { + static VERTEX: 0x1; + static FRAGMENT: 0x2; + static COMPUTE: 0x4; +} + +declare interface GPUBufferBindingLayout { + type?: GPUBufferBindingType; + hasDynamicOffset?: boolean; + minBindingSize?: number; +} + +declare type GPUBufferBindingType = "uniform" | "storage" | "read-only-storage"; + +declare interface GPUSamplerBindingLayout { + type?: GPUSamplerBindingType; +} + +declare type GPUSamplerBindingType = + | "filtering" + | "non-filtering" + | "comparison"; + +declare interface GPUTextureBindingLayout { + sampleType?: GPUTextureSampleType; + viewDimension?: GPUTextureViewDimension; + multisampled?: boolean; +} + +declare type GPUTextureSampleType = + | "float" + | "unfilterable-float" + | "depth" + | "sint" + | "uint"; + +declare interface GPUTextureBindingLayout { + sampleType?: GPUTextureSampleType; + viewDimension?: GPUTextureViewDimension; + multisampled?: boolean; +} + +declare type GPUStorageTextureAccess = "read-only" | "write-only"; + +declare interface GPUStorageTextureBindingLayout { + access: GPUStorageTextureAccess; + format: GPUTextureFormat; + viewDimension?: GPUTextureViewDimension; +} + +declare class GPUBindGroup implements GPUObjectBase { + label: string | null; +} + +declare interface GPUBindGroupDescriptor extends GPUObjectDescriptorBase { + layout: GPUBindGroupLayout; + entries: GPUBindGroupEntry[]; +} + +declare type GPUBindingResource = + | GPUSampler + | GPUTextureView + | GPUBufferBinding; + +declare interface GPUBindGroupEntry { + binding: number; + resource: GPUBindingResource; +} + +declare interface GPUBufferBinding { + buffer: GPUBuffer; + offset?: number; + size?: number; +} + +declare class GPUPipelineLayout implements GPUObjectBase { + label: string | null; +} + +declare interface GPUPipelineLayoutDescriptor extends GPUObjectDescriptorBase { + bindGroupLayouts: GPUBindGroupLayout[]; +} + +declare type GPUCompilationMessageType = "error" | "warning" | "info"; + +declare interface GPUCompilationMessage { + readonly message: string; + readonly type: GPUCompilationMessageType; + readonly lineNum: number; + readonly linePos: number; +} + +declare interface GPUCompilationInfo { + readonly messages: ReadonlyArray; +} + +declare class GPUShaderModule implements GPUObjectBase { + label: string | null; + + compilationInfo(): Promise; +} + +declare interface GPUShaderModuleDescriptor extends GPUObjectDescriptorBase { + code: string; + sourceMap?: any; +} + +declare interface GPUPipelineDescriptorBase extends GPUObjectDescriptorBase { + layout?: GPUPipelineLayout; +} + +declare interface GPUPipelineBase { + getBindGroupLayout(index: number): GPUBindGroupLayout; +} + +declare interface GPUProgrammableStage { + module: GPUShaderModule; + entryPoint: string; +} + +declare class GPUComputePipeline implements GPUObjectBase, GPUPipelineBase { + label: string | null; + + getBindGroupLayout(index: number): GPUBindGroupLayout; +} + +declare interface GPUComputePipelineDescriptor + extends GPUPipelineDescriptorBase { + compute: GPUProgrammableStage; +} + +declare class GPURenderPipeline implements GPUObjectBase, GPUPipelineBase { + label: string | null; + + getBindGroupLayout(index: number): GPUBindGroupLayout; +} + +declare interface GPURenderPipelineDescriptor + extends GPUPipelineDescriptorBase { + vertex: GPUVertexState; + primitive?: GPUPrimitiveState; + depthStencil?: GPUDepthStencilState; + multisample?: GPUMultisampleState; + fragment?: GPUFragmentState; +} + +declare type GPUPrimitiveTopology = + | "point-list" + | "line-list" + | "line-strip" + | "triangle-list" + | "triangle-strip"; + +declare interface GPUPrimitiveState { + topology?: GPUPrimitiveTopology; + stripIndexFormat?: GPUIndexFormat; + frontFace?: GPUFrontFace; + cullMode?: GPUCullMode; +} + +declare type GPUFrontFace = "ccw" | "cw"; + +declare type GPUCullMode = "none" | "front" | "back"; + +declare interface GPUMultisampleState { + count?: number; + mask?: number; + alphaToCoverageEnabled?: boolean; +} + +declare interface GPUFragmentState extends GPUProgrammableStage { + targets: GPUColorTargetState[]; +} + +declare interface GPUColorTargetState { + format: GPUTextureFormat; + + blend?: GPUBlendState; + writeMask?: GPUColorWriteFlags; +} + +declare interface GPUBlendState { + color: GPUBlendComponent; + alpha: GPUBlendComponent; +} + +declare type GPUColorWriteFlags = number; +declare class GPUColorWrite { + static RED: 0x1; + static GREEN: 0x2; + static BLUE: 0x4; + static ALPHA: 0x8; + static ALL: 0xF; +} + +declare interface GPUBlendComponent { + srcFactor: GPUBlendFactor; + dstFactor: GPUBlendFactor; + operation: GPUBlendOperation; +} + +declare type GPUBlendFactor = + | "zero" + | "one" + | "src-color" + | "one-minus-src-color" + | "src-alpha" + | "one-minus-src-alpha" + | "dst-color" + | "one-minus-dst-color" + | "dst-alpha" + | "one-minus-dst-alpha" + | "src-alpha-saturated" + | "blend-color" + | "one-minus-blend-color"; + +declare type GPUBlendOperation = + | "add" + | "subtract" + | "reverse-subtract" + | "min" + | "max"; + +declare interface GPUDepthStencilState { + format: GPUTextureFormat; + + depthWriteEnabled?: boolean; + depthCompare?: GPUCompareFunction; + + stencilFront?: GPUStencilFaceState; + stencilBack?: GPUStencilFaceState; + + stencilReadMask?: number; + stencilWriteMask?: number; + + depthBias?: number; + depthBiasSlopeScale?: number; + depthBiasClamp?: number; + + clampDepth?: boolean; +} + +declare interface GPUStencilFaceState { + compare?: GPUCompareFunction; + failOp?: GPUStencilOperation; + depthFailOp?: GPUStencilOperation; + passOp?: GPUStencilOperation; +} + +declare type GPUStencilOperation = + | "keep" + | "zero" + | "replace" + | "invert" + | "increment-clamp" + | "decrement-clamp" + | "increment-wrap" + | "decrement-wrap"; + +declare type GPUIndexFormat = "uint16" | "uint32"; + +declare type GPUVertexFormat = + | "uchar2" + | "uchar4" + | "char2" + | "char4" + | "uchar2norm" + | "uchar4norm" + | "char2norm" + | "char4norm" + | "ushort2" + | "ushort4" + | "short2" + | "short4" + | "ushort2norm" + | "ushort4norm" + | "short2norm" + | "short4norm" + | "half2" + | "half4" + | "float" + | "float2" + | "float3" + | "float4" + | "uint" + | "uint2" + | "uint3" + | "uint4" + | "int" + | "int2" + | "int3" + | "int4"; + +declare type GPUInputStepMode = "vertex" | "instance"; + +declare interface GPUVertexState extends GPUProgrammableStage { + buffers?: (GPUVertexBufferLayout | null)[]; +} + +declare interface GPUVertexBufferLayout { + arrayStride: number; + stepMode?: GPUInputStepMode; + attributes: GPUVertexAttribute[]; +} + +declare interface GPUVertexAttribute { + format: GPUVertexFormat; + offset: number; + + shaderLocation: number; +} + +declare class GPUCommandBuffer implements GPUObjectBase { + label: string | null; + + readonly executionTime: Promise; +} + +declare interface GPUCommandBufferDescriptor extends GPUObjectDescriptorBase {} + +declare class GPUCommandEncoder implements GPUObjectBase { + label: string | null; + + beginRenderPass(descriptor: GPURenderPassDescriptor): GPURenderPassEncoder; + beginComputePass( + descriptor?: GPUComputePassDescriptor, + ): GPUComputePassEncoder; + + copyBufferToBuffer( + source: GPUBuffer, + sourceOffset: number, + destination: GPUBuffer, + destinationOffset: number, + size: number, + ): undefined; + + copyBufferToTexture( + source: GPUImageCopyBuffer, + destination: GPUImageCopyTexture, + copySize: GPUExtent3D, + ): undefined; + + copyTextureToBuffer( + source: GPUImageCopyTexture, + destination: GPUImageCopyBuffer, + copySize: GPUExtent3D, + ): undefined; + + copyTextureToTexture( + source: GPUImageCopyTexture, + destination: GPUImageCopyTexture, + copySize: GPUExtent3D, + ): undefined; + + pushDebugGroup(groupLabel: string): undefined; + popDebugGroup(): undefined; + insertDebugMarker(markerLabel: string): undefined; + + writeTimestamp(querySet: GPUQuerySet, queryIndex: number): undefined; + + resolveQuerySet( + querySet: GPUQuerySet, + firstQuery: number, + queryCount: number, + destination: GPUBuffer, + destinationOffset: number, + ): undefined; + + finish(descriptor?: GPUCommandBufferDescriptor): GPUCommandBuffer; +} + +declare interface GPUCommandEncoderDescriptor extends GPUObjectDescriptorBase { + measureExecutionTime?: boolean; +} + +declare interface GPUImageDataLayout { + offset?: number; + bytesPerRow?: number; + rowsPerImage?: number; +} + +declare interface GPUImageCopyBuffer extends GPUImageDataLayout { + buffer: GPUBuffer; +} + +declare interface GPUImageCopyTexture { + texture: GPUTexture; + mipLevel?: number; + origin?: GPUOrigin3D; + aspect?: GPUTextureAspect; +} + +interface GPUProgrammablePassEncoder { + setBindGroup( + index: number, + bindGroup: GPUBindGroup, + dynamicOffsets?: number[], + ): undefined; + + setBindGroup( + index: number, + bindGroup: GPUBindGroup, + dynamicOffsetsData: Uint32Array, + dynamicOffsetsDataStart: number, + dynamicOffsetsDataLength: number, + ): undefined; + + pushDebugGroup(groupLabel: string): undefined; + popDebugGroup(): undefined; + insertDebugMarker(markerLabel: string): undefined; +} + +declare class GPUComputePassEncoder + implements GPUObjectBase, GPUProgrammablePassEncoder { + label: string | null; + setBindGroup( + index: number, + bindGroup: GPUBindGroup, + dynamicOffsets?: number[], + ): undefined; + setBindGroup( + index: number, + bindGroup: GPUBindGroup, + dynamicOffsetsData: Uint32Array, + dynamicOffsetsDataStart: number, + dynamicOffsetsDataLength: number, + ): undefined; + pushDebugGroup(groupLabel: string): undefined; + popDebugGroup(): undefined; + insertDebugMarker(markerLabel: string): undefined; + setPipeline(pipeline: GPUComputePipeline): undefined; + dispatch(x: number, y?: number, z?: number): undefined; + dispatchIndirect( + indirectBuffer: GPUBuffer, + indirectOffset: number, + ): undefined; + + beginPipelineStatisticsQuery( + querySet: GPUQuerySet, + queryIndex: number, + ): undefined; + endPipelineStatisticsQuery(): undefined; + + writeTimestamp(querySet: GPUQuerySet, queryIndex: number): undefined; + + endPass(): undefined; +} + +declare interface GPUComputePassDescriptor extends GPUObjectDescriptorBase {} + +interface GPURenderEncoderBase { + setPipeline(pipeline: GPURenderPipeline): undefined; + + setIndexBuffer( + buffer: GPUBuffer, + indexFormat: GPUIndexFormat, + offset?: number, + size?: number, + ): undefined; + setVertexBuffer( + slot: number, + buffer: GPUBuffer, + offset?: number, + size?: number, + ): undefined; + + draw( + vertexCount: number, + instanceCount?: number, + firstVertex?: number, + firstInstance?: number, + ): undefined; + drawIndexed( + indexCount: number, + instanceCount?: number, + firstIndex?: number, + baseVertex?: number, + firstInstance?: number, + ): undefined; + + drawIndirect(indirectBuffer: GPUBuffer, indirectOffset: number): undefined; + drawIndexedIndirect( + indirectBuffer: GPUBuffer, + indirectOffset: number, + ): undefined; +} + +declare class GPURenderPassEncoder + implements GPUObjectBase, GPUProgrammablePassEncoder, GPURenderEncoderBase { + label: string | null; + setBindGroup( + index: number, + bindGroup: GPUBindGroup, + dynamicOffsets?: number[], + ): undefined; + setBindGroup( + index: number, + bindGroup: GPUBindGroup, + dynamicOffsetsData: Uint32Array, + dynamicOffsetsDataStart: number, + dynamicOffsetsDataLength: number, + ): undefined; + pushDebugGroup(groupLabel: string): undefined; + popDebugGroup(): undefined; + insertDebugMarker(markerLabel: string): undefined; + setPipeline(pipeline: GPURenderPipeline): undefined; + setIndexBuffer( + buffer: GPUBuffer, + indexFormat: GPUIndexFormat, + offset?: number, + size?: number, + ): undefined; + setVertexBuffer( + slot: number, + buffer: GPUBuffer, + offset?: number, + size?: number, + ): undefined; + draw( + vertexCount: number, + instanceCount?: number, + firstVertex?: number, + firstInstance?: number, + ): undefined; + drawIndexed( + indexCount: number, + instanceCount?: number, + firstIndex?: number, + baseVertex?: number, + firstInstance?: number, + ): undefined; + drawIndirect(indirectBuffer: GPUBuffer, indirectOffset: number): undefined; + drawIndexedIndirect( + indirectBuffer: GPUBuffer, + indirectOffset: number, + ): undefined; + + setViewport( + x: number, + y: number, + width: number, + height: number, + minDepth: number, + maxDepth: number, + ): undefined; + + setScissorRect( + x: number, + y: number, + width: number, + height: number, + ): undefined; + + setBlendColor(color: GPUColor): undefined; + setStencilReference(reference: number): undefined; + + beginOcclusionQuery(queryIndex: number): undefined; + endOcclusionQuery(): undefined; + + beginPipelineStatisticsQuery( + querySet: GPUQuerySet, + queryIndex: number, + ): undefined; + endPipelineStatisticsQuery(): undefined; + + writeTimestamp(querySet: GPUQuerySet, queryIndex: number): undefined; + + executeBundles(bundles: GPURenderBundle[]): undefined; + endPass(): undefined; +} + +declare interface GPURenderPassDescriptor extends GPUObjectDescriptorBase { + colorAttachments: GPURenderPassColorAttachment[]; + depthStencilAttachment?: GPURenderPassDepthStencilAttachment; + occlusionQuerySet?: GPUQuerySet; +} + +declare interface GPURenderPassColorAttachment { + view: GPUTextureView; + resolveTarget?: GPUTextureView; + + loadValue: GPULoadOp | GPUColor; + storeOp?: GPUStoreOp; +} + +declare interface GPURenderPassDepthStencilAttachment { + view: GPUTextureView; + + depthLoadValue: GPULoadOp | number; + depthStoreOp: GPUStoreOp; + depthReadOnly?: boolean; + + stencilLoadValue: GPULoadOp | number; + stencilStoreOp: GPUStoreOp; + stencilReadOnly?: boolean; +} + +declare type GPULoadOp = "load"; + +declare type GPUStoreOp = "store" | "clear"; + +declare class GPURenderBundle implements GPUObjectBase { + label: string | null; +} + +declare interface GPURenderBundleDescriptor extends GPUObjectDescriptorBase {} + +declare class GPURenderBundleEncoder + implements GPUObjectBase, GPUProgrammablePassEncoder, GPURenderEncoderBase { + label: string | null; + draw( + vertexCount: number, + instanceCount?: number, + firstVertex?: number, + firstInstance?: number, + ): undefined; + drawIndexed( + indexCount: number, + instanceCount?: number, + firstIndex?: number, + baseVertex?: number, + firstInstance?: number, + ): undefined; + drawIndexedIndirect( + indirectBuffer: GPUBuffer, + indirectOffset: number, + ): undefined; + drawIndirect(indirectBuffer: GPUBuffer, indirectOffset: number): undefined; + insertDebugMarker(markerLabel: string): undefined; + popDebugGroup(): undefined; + pushDebugGroup(groupLabel: string): undefined; + setBindGroup( + index: number, + bindGroup: GPUBindGroup, + dynamicOffsets?: number[], + ): undefined; + setBindGroup( + index: number, + bindGroup: GPUBindGroup, + dynamicOffsetsData: Uint32Array, + dynamicOffsetsDataStart: number, + dynamicOffsetsDataLength: number, + ): undefined; + setIndexBuffer( + buffer: GPUBuffer, + indexFormat: GPUIndexFormat, + offset?: number, + size?: number, + ): undefined; + setPipeline(pipeline: GPURenderPipeline): undefined; + setVertexBuffer( + slot: number, + buffer: GPUBuffer, + offset?: number, + size?: number, + ): undefined; + + finish(descriptor?: GPURenderBundleDescriptor): GPURenderBundle; +} + +declare interface GPURenderBundleEncoderDescriptor + extends GPUObjectDescriptorBase { + colorFormats: GPUTextureFormat[]; + depthStencilFormat?: GPUTextureFormat; + sampleCount?: number; +} + +declare class GPUQueue implements GPUObjectBase { + label: string | null; + + submit(commandBuffers: GPUCommandBuffer[]): undefined; + + onSubmittedWorkDone(): Promise; + + writeBuffer( + buffer: GPUBuffer, + bufferOffset: number, + data: BufferSource, + dataOffset?: number, + size?: number, + ): undefined; + + writeTexture( + destination: GPUImageCopyTexture, + data: BufferSource, + dataLayout: GPUImageDataLayout, + size: GPUExtent3D, + ): undefined; +} + +declare class GPUQuerySet implements GPUObjectBase { + label: string | null; + + destroy(): undefined; +} + +declare interface GPUQuerySetDescriptor extends GPUObjectDescriptorBase { + type: GPUQueryType; + count: number; + pipelineStatistics?: GPUPipelineStatisticName[]; +} + +declare type GPUQueryType = "occlusion" | "pipeline-statistics" | "timestamp"; + +declare type GPUPipelineStatisticName = + | "vertex-shader-invocations" + | "clipper-invocations" + | "clipper-primitives-out" + | "fragment-shader-invocations" + | "compute-shader-invocations"; + +declare type GPUDeviceLostReason = "destroyed"; + +declare interface GPUDeviceLostInfo { + readonly reason: GPUDeviceLostReason | undefined; + readonly message: string; +} + +declare type GPUErrorFilter = "out-of-memory" | "validation"; + +declare class GPUOutOfMemoryError { + constructor(); +} + +declare class GPUValidationError { + constructor(message: string); + readonly message: string; +} + +declare type GPUError = GPUOutOfMemoryError | GPUValidationError; + +declare class GPUUncapturedErrorEvent extends Event { + constructor( + type: string, + gpuUncapturedErrorEventInitDict: GPUUncapturedErrorEventInit, + ); + readonly error: GPUError; +} + +declare interface GPUUncapturedErrorEventInit extends EventInit { + error?: GPUError; +} + +declare interface GPUColorDict { + r: number; + g: number; + b: number; + a: number; +} + +declare type GPUColor = number[] | GPUColorDict; + +declare interface GPUOrigin3DDict { + x?: number; + y?: number; + z?: number; +} + +declare type GPUOrigin3D = number[] | GPUOrigin3DDict; + +declare interface GPUExtent3DDict { + width?: number; + height?: number; + depth?: number; +} + +declare type GPUExtent3D = number[] | GPUExtent3DDict; diff --git a/op_crates/webgpu/lib.rs b/op_crates/webgpu/lib.rs new file mode 100644 index 00000000000000..2f1769dafe88a9 --- /dev/null +++ b/op_crates/webgpu/lib.rs @@ -0,0 +1,541 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +#![deny(warnings)] + +use deno_core::error::AnyError; +use deno_core::error::{bad_resource_id, not_supported}; +use deno_core::serde_json::json; +use deno_core::serde_json::Value; +use deno_core::OpState; +use deno_core::ZeroCopyBuf; +use deno_core::{BufVec, Resource}; +use serde::Deserialize; +use std::borrow::Cow; +use std::cell::RefCell; +use std::path::PathBuf; +use std::rc::Rc; +pub use wgpu_core; +pub use wgpu_types; + +use error::DOMExceptionOperationError; +use error::WebGPUError; + +#[macro_use] +mod macros { + macro_rules! gfx_select { + ($id:expr => $global:ident.$method:ident( $($param:expr),* )) => { + match $id.backend() { + #[cfg(all(not(target_arch = "wasm32"), not(any(target_os = "ios", target_os = "macos"))))] + wgpu_types::Backend::Vulkan => $global.$method::( $($param),* ), + #[cfg(all(not(target_arch = "wasm32"), any(target_os = "ios", target_os = "macos")))] + wgpu_types::Backend::Metal => $global.$method::( $($param),* ), + #[cfg(all(not(target_arch = "wasm32"), windows))] + wgpu_types::Backend::Dx12 => $global.$method::( $($param),* ), + #[cfg(all(not(target_arch = "wasm32"), windows))] + wgpu_types::Backend::Dx11 => $global.$method::( $($param),* ), + #[cfg(any(target_arch = "wasm32", all(unix, not(any(target_os = "ios", target_os = "macos")))))] + wgpu_types::Backend::Gl => $global.$method::( $($param),+ ), + other => panic!("Unexpected backend {:?}", other), + } + }; + } +} + +pub mod binding; +pub mod buffer; +pub mod bundle; +pub mod command_encoder; +pub mod compute_pass; +pub mod error; +pub mod pipeline; +pub mod queue; +pub mod render_pass; +pub mod sampler; +pub mod shader; +pub mod texture; + +pub struct Unstable(pub bool); + +fn check_unstable(state: &OpState, api_name: &str) { + let unstable = state.borrow::(); + + if !unstable.0 { + eprintln!( + "Unstable API '{}'. The --unstable flag must be provided.", + api_name + ); + std::process::exit(70); + } +} + +type Instance = wgpu_core::hub::Global; + +struct WebGPUAdapter(wgpu_core::id::AdapterId); +impl Resource for WebGPUAdapter { + fn name(&self) -> Cow { + "webGPUAdapter".into() + } +} + +struct WebGPUDevice(wgpu_core::id::DeviceId); +impl Resource for WebGPUDevice { + fn name(&self) -> Cow { + "webGPUDevice".into() + } +} + +struct WebGPUQuerySet(wgpu_core::id::QuerySetId); +impl Resource for WebGPUQuerySet { + fn name(&self) -> Cow { + "webGPUQuerySet".into() + } +} + +/// Execute this crates' JS source files. +pub fn init(isolate: &mut deno_core::JsRuntime) { + let files = vec![ + ( + "deno:op_crates/webgpu/01_webgpu.js", + include_str!("01_webgpu.js"), + ), + ( + "deno:op_crates/webgpu/02_idl_types.js", + include_str!("02_idl_types.js"), + ), + ]; + for (url, source_code) in files { + isolate.execute(url, source_code).unwrap(); + } +} + +pub fn get_declaration() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("lib.deno_webgpu.d.ts") +} + +fn deserialize_features(features: &wgpu_types::Features) -> Vec<&str> { + let mut return_features: Vec<&str> = vec![]; + + if features.contains(wgpu_types::Features::DEPTH_CLAMPING) { + return_features.push("depth-clamping"); + } + if features.contains(wgpu_types::Features::PIPELINE_STATISTICS_QUERY) { + return_features.push("pipeline-statistics-query"); + } + if features.contains(wgpu_types::Features::TEXTURE_COMPRESSION_BC) { + return_features.push("texture-compression-bc"); + } + if features.contains(wgpu_types::Features::TIMESTAMP_QUERY) { + return_features.push("timestamp-query"); + } + + // extended from spec + if features.contains(wgpu_types::Features::MAPPABLE_PRIMARY_BUFFERS) { + return_features.push("mappable-primary-buffers"); + } + if features.contains(wgpu_types::Features::SAMPLED_TEXTURE_BINDING_ARRAY) { + return_features.push("sampled-texture-binding-array"); + } + if features + .contains(wgpu_types::Features::SAMPLED_TEXTURE_ARRAY_DYNAMIC_INDEXING) + { + return_features.push("sampled-texture-array-dynamic-indexing"); + } + if features + .contains(wgpu_types::Features::SAMPLED_TEXTURE_ARRAY_NON_UNIFORM_INDEXING) + { + return_features.push("sampled-texture-array-non-uniform-indexing"); + } + if features.contains(wgpu_types::Features::UNSIZED_BINDING_ARRAY) { + return_features.push("unsized-binding-array"); + } + if features.contains(wgpu_types::Features::MULTI_DRAW_INDIRECT) { + return_features.push("multi-draw-indirect"); + } + if features.contains(wgpu_types::Features::MULTI_DRAW_INDIRECT_COUNT) { + return_features.push("multi-draw-indirect-count"); + } + if features.contains(wgpu_types::Features::PUSH_CONSTANTS) { + return_features.push("push-constants"); + } + if features.contains(wgpu_types::Features::ADDRESS_MODE_CLAMP_TO_BORDER) { + return_features.push("address-mode-clamp-to-border"); + } + if features.contains(wgpu_types::Features::NON_FILL_POLYGON_MODE) { + return_features.push("non-fill-polygon-mode"); + } + if features.contains(wgpu_types::Features::TEXTURE_COMPRESSION_ETC2) { + return_features.push("texture-compression-etc2"); + } + if features.contains(wgpu_types::Features::TEXTURE_COMPRESSION_ASTC_LDR) { + return_features.push("texture-compression-astc-ldr"); + } + if features + .contains(wgpu_types::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES) + { + return_features.push("texture-adapter-specific-format-features"); + } + if features.contains(wgpu_types::Features::SHADER_FLOAT64) { + return_features.push("shader-float64"); + } + if features.contains(wgpu_types::Features::VERTEX_ATTRIBUTE_64BIT) { + return_features.push("vertex-attribute-64bit"); + } + + return_features +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RequestAdapterArgs { + power_preference: Option, +} + +pub async fn op_webgpu_request_adapter( + state: Rc>, + args: RequestAdapterArgs, + _bufs: BufVec, +) -> Result { + let mut state = state.borrow_mut(); + check_unstable(&state, "navigator.gpu.requestAdapter"); + let instance = state.borrow::(); + + let descriptor = wgpu_core::instance::RequestAdapterOptions { + power_preference: match args.power_preference { + Some(power_preference) => match power_preference.as_str() { + "low-power" => wgpu_types::PowerPreference::LowPower, + "high-performance" => wgpu_types::PowerPreference::HighPerformance, + _ => unreachable!(), + }, + None => Default::default(), + }, + compatible_surface: None, // windowless + }; + let res = instance.request_adapter( + &descriptor, + wgpu_core::instance::AdapterInputs::Mask( + wgpu_types::BackendBit::PRIMARY, + |_| std::marker::PhantomData, + ), + ); + + let adapter = match res { + Ok(adapter) => adapter, + Err(err) => { + return Ok(json!({ + "err": err.to_string() + })) + } + }; + let name = gfx_select!(adapter => instance.adapter_get_info(adapter))?.name; + let adapter_features = + gfx_select!(adapter => instance.adapter_features(adapter))?; + let features = deserialize_features(&adapter_features); + let adapter_limits = + gfx_select!(adapter => instance.adapter_limits(adapter))?; + + let limits = json!({ + "maxBindGroups": adapter_limits.max_bind_groups, + "maxDynamicUniformBuffersPerPipelineLayout": adapter_limits.max_dynamic_uniform_buffers_per_pipeline_layout, + "maxDynamicStorageBuffersPerPipelineLayout": adapter_limits.max_dynamic_storage_buffers_per_pipeline_layout, + "maxSampledTexturesPerShaderStage": adapter_limits.max_sampled_textures_per_shader_stage, + "maxSamplersPerShaderStage": adapter_limits.max_samplers_per_shader_stage, + "maxStorageBuffersPerShaderStage": adapter_limits.max_storage_buffers_per_shader_stage, + "maxStorageTexturesPerShaderStage": adapter_limits.max_storage_textures_per_shader_stage, + "maxUniformBuffersPerShaderStage": adapter_limits.max_uniform_buffers_per_shader_stage, + "maxUniformBufferBindingSize": adapter_limits.max_uniform_buffer_binding_size + }); + + let rid = state.resource_table.add(WebGPUAdapter(adapter)); + + Ok(json!({ + "rid": rid, + "name": name, + "features": features, + "limits": limits + })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GPULimits { + _max_texture_dimension1d: Option, + _max_texture_dimension2d: Option, + _max_texture_dimension3d: Option, + _max_texture_array_layers: Option, + max_bind_groups: Option, + max_dynamic_uniform_buffers_per_pipeline_layout: Option, + max_dynamic_storage_buffers_per_pipeline_layout: Option, + max_sampled_textures_per_shader_stage: Option, + max_samplers_per_shader_stage: Option, + max_storage_buffers_per_shader_stage: Option, + max_storage_textures_per_shader_stage: Option, + max_uniform_buffers_per_shader_stage: Option, + max_uniform_buffer_binding_size: Option, + _max_storage_buffer_binding_size: Option, + _max_vertex_buffers: Option, + _max_vertex_attributes: Option, + _max_vertex_buffer_array_stride: Option, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RequestDeviceArgs { + adapter_rid: u32, + label: Option, + non_guaranteed_features: Option>, + non_guaranteed_limits: Option, +} + +pub async fn op_webgpu_request_device( + state: Rc>, + args: RequestDeviceArgs, + _bufs: BufVec, +) -> Result { + let mut state = state.borrow_mut(); + let adapter_resource = state + .resource_table + .get::(args.adapter_rid) + .ok_or_else(bad_resource_id)?; + let adapter = adapter_resource.0; + let instance = state.borrow::(); + + let mut features: wgpu_types::Features = wgpu_types::Features::empty(); + + if let Some(passed_features) = args.non_guaranteed_features { + if passed_features.contains(&"depth-clamping".to_string()) { + features.set(wgpu_types::Features::DEPTH_CLAMPING, true); + } + if passed_features.contains(&"pipeline-statistics-query".to_string()) { + features.set(wgpu_types::Features::PIPELINE_STATISTICS_QUERY, true); + } + if passed_features.contains(&"texture-compression-bc".to_string()) { + features.set(wgpu_types::Features::TEXTURE_COMPRESSION_BC, true); + } + if passed_features.contains(&"timestamp-query".to_string()) { + features.set(wgpu_types::Features::TIMESTAMP_QUERY, true); + } + + // extended from spec + if passed_features.contains(&"mappable-primary-buffers".to_string()) { + features.set(wgpu_types::Features::MAPPABLE_PRIMARY_BUFFERS, true); + } + if passed_features.contains(&"sampled-texture-binding-array".to_string()) { + features.set(wgpu_types::Features::SAMPLED_TEXTURE_BINDING_ARRAY, true); + } + if passed_features + .contains(&"sampled-texture-array-dynamic-indexing".to_string()) + { + features.set( + wgpu_types::Features::SAMPLED_TEXTURE_ARRAY_DYNAMIC_INDEXING, + true, + ); + } + if passed_features + .contains(&"sampled-texture-array-non-uniform-indexing".to_string()) + { + features.set( + wgpu_types::Features::SAMPLED_TEXTURE_ARRAY_NON_UNIFORM_INDEXING, + true, + ); + } + if passed_features.contains(&"unsized-binding-array".to_string()) { + features.set(wgpu_types::Features::UNSIZED_BINDING_ARRAY, true); + } + if passed_features.contains(&"multi-draw-indirect".to_string()) { + features.set(wgpu_types::Features::MULTI_DRAW_INDIRECT, true); + } + if passed_features.contains(&"multi-draw-indirect-count".to_string()) { + features.set(wgpu_types::Features::MULTI_DRAW_INDIRECT_COUNT, true); + } + if passed_features.contains(&"push-constants".to_string()) { + features.set(wgpu_types::Features::PUSH_CONSTANTS, true); + } + if passed_features.contains(&"address-mode-clamp-to-border".to_string()) { + features.set(wgpu_types::Features::ADDRESS_MODE_CLAMP_TO_BORDER, true); + } + if passed_features.contains(&"non-fill-polygon-mode".to_string()) { + features.set(wgpu_types::Features::NON_FILL_POLYGON_MODE, true); + } + if passed_features.contains(&"texture-compression-etc2".to_string()) { + features.set(wgpu_types::Features::TEXTURE_COMPRESSION_ETC2, true); + } + if passed_features.contains(&"texture-compression-astc-ldr".to_string()) { + features.set(wgpu_types::Features::TEXTURE_COMPRESSION_ASTC_LDR, true); + } + if passed_features + .contains(&"texture-adapter-specific-format-features".to_string()) + { + features.set( + wgpu_types::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES, + true, + ); + } + if passed_features.contains(&"shader-float64".to_string()) { + features.set(wgpu_types::Features::SHADER_FLOAT64, true); + } + if passed_features.contains(&"vertex-attribute-64bit".to_string()) { + features.set(wgpu_types::Features::VERTEX_ATTRIBUTE_64BIT, true); + } + } + + let descriptor = wgpu_types::DeviceDescriptor { + label: args.label.map(Cow::from), + features, + limits: args + .non_guaranteed_limits + .map_or(Default::default(), |limits| wgpu_types::Limits { + max_bind_groups: limits.max_bind_groups.unwrap_or(4), + max_dynamic_uniform_buffers_per_pipeline_layout: limits + .max_dynamic_uniform_buffers_per_pipeline_layout + .unwrap_or(8), + max_dynamic_storage_buffers_per_pipeline_layout: limits + .max_dynamic_storage_buffers_per_pipeline_layout + .unwrap_or(4), + max_sampled_textures_per_shader_stage: limits + .max_sampled_textures_per_shader_stage + .unwrap_or(16), + max_samplers_per_shader_stage: limits + .max_samplers_per_shader_stage + .unwrap_or(16), + max_storage_buffers_per_shader_stage: limits + .max_storage_buffers_per_shader_stage + .unwrap_or(4), + max_storage_textures_per_shader_stage: limits + .max_storage_textures_per_shader_stage + .unwrap_or(4), + max_uniform_buffers_per_shader_stage: limits + .max_uniform_buffers_per_shader_stage + .unwrap_or(12), + max_uniform_buffer_binding_size: limits + .max_uniform_buffer_binding_size + .unwrap_or(16384), + max_push_constant_size: 0, + }), + }; + + let (device, maybe_err) = gfx_select!(adapter => instance.adapter_request_device( + adapter, + &descriptor, + std::env::var("DENO_WEBGPU_TRACE").ok().as_ref().map(std::path::Path::new), + std::marker::PhantomData + )); + if let Some(err) = maybe_err { + return Err(DOMExceptionOperationError::new(&err.to_string()).into()); + } + + let device_features = + gfx_select!(device => instance.device_features(device))?; + let features = deserialize_features(&device_features); + let limits = gfx_select!(device => instance.device_limits(device))?; + let json_limits = json!({ + "maxBindGroups": limits.max_bind_groups, + "maxDynamicUniformBuffersPerPipelineLayout": limits.max_dynamic_uniform_buffers_per_pipeline_layout, + "maxDynamicStorageBuffersPerPipelineLayout": limits.max_dynamic_storage_buffers_per_pipeline_layout, + "maxSampledTexturesPerShaderStage": limits.max_sampled_textures_per_shader_stage, + "maxSamplersPerShaderStage": limits.max_samplers_per_shader_stage, + "maxStorageBuffersPerShaderStage": limits.max_storage_buffers_per_shader_stage, + "maxStorageTexturesPerShaderStage": limits.max_storage_textures_per_shader_stage, + "maxUniformBuffersPerShaderStage": limits.max_uniform_buffers_per_shader_stage, + "maxUniformBufferBindingSize": limits.max_uniform_buffer_binding_size, + }); + + let rid = state.resource_table.add(WebGPUDevice(device)); + + Ok(json!({ + "rid": rid, + "features": features, + "limits": json_limits, + })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateQuerySetArgs { + device_rid: u32, + _label: Option, // not yet implemented + #[serde(rename = "type")] + kind: String, + count: u32, + pipeline_statistics: Option>, +} + +pub fn op_webgpu_create_query_set( + state: &mut OpState, + args: CreateQuerySetArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let device_resource = state + .resource_table + .get::(args.device_rid) + .ok_or_else(bad_resource_id)?; + let device = device_resource.0; + let instance = &state.borrow::(); + + let descriptor = wgpu_types::QuerySetDescriptor { + ty: match args.kind.as_str() { + "pipeline-statistics" => { + let mut pipeline_statistics_names = + wgpu_types::PipelineStatisticsTypes::empty(); + + if let Some(pipeline_statistics) = args.pipeline_statistics { + if pipeline_statistics + .contains(&"vertex-shader-invocations".to_string()) + { + pipeline_statistics_names.set( + wgpu_types::PipelineStatisticsTypes::VERTEX_SHADER_INVOCATIONS, + true, + ); + } + if pipeline_statistics.contains(&"clipper-invocations".to_string()) { + pipeline_statistics_names.set( + wgpu_types::PipelineStatisticsTypes::CLIPPER_INVOCATIONS, + true, + ); + } + if pipeline_statistics.contains(&"clipper-primitives-out".to_string()) + { + pipeline_statistics_names.set( + wgpu_types::PipelineStatisticsTypes::CLIPPER_PRIMITIVES_OUT, + true, + ); + } + if pipeline_statistics + .contains(&"fragment-shader-invocations".to_string()) + { + pipeline_statistics_names.set( + wgpu_types::PipelineStatisticsTypes::FRAGMENT_SHADER_INVOCATIONS, + true, + ); + } + if pipeline_statistics + .contains(&"compute-shader-invocations".to_string()) + { + pipeline_statistics_names.set( + wgpu_types::PipelineStatisticsTypes::COMPUTE_SHADER_INVOCATIONS, + true, + ); + } + }; + + wgpu_types::QueryType::PipelineStatistics(pipeline_statistics_names) + } + "occlusion" => return Err(not_supported()), + "timestamp" => wgpu_types::QueryType::Timestamp, + _ => unreachable!(), + }, + count: args.count, + }; + + let (query_set, maybe_err) = gfx_select!(device => instance.device_create_query_set( + device, + &descriptor, + std::marker::PhantomData + )); + + let rid = state.resource_table.add(WebGPUQuerySet(query_set)); + + Ok(json!({ + "rid": rid, + "err": maybe_err.map(WebGPUError::from), + })) +} diff --git a/op_crates/webgpu/pipeline.rs b/op_crates/webgpu/pipeline.rs new file mode 100644 index 00000000000000..04493b18aac4f2 --- /dev/null +++ b/op_crates/webgpu/pipeline.rs @@ -0,0 +1,643 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::bad_resource_id; +use deno_core::error::AnyError; +use deno_core::serde_json::json; +use deno_core::serde_json::Value; +use deno_core::ZeroCopyBuf; +use deno_core::{OpState, Resource}; +use serde::Deserialize; +use std::borrow::Cow; + +use super::error::WebGPUError; + +pub(crate) struct WebGPUPipelineLayout( + pub(crate) wgpu_core::id::PipelineLayoutId, +); +impl Resource for WebGPUPipelineLayout { + fn name(&self) -> Cow { + "webGPUPipelineLayout".into() + } +} + +pub(crate) struct WebGPUComputePipeline( + pub(crate) wgpu_core::id::ComputePipelineId, +); +impl Resource for WebGPUComputePipeline { + fn name(&self) -> Cow { + "webGPUComputePipeline".into() + } +} + +pub(crate) struct WebGPURenderPipeline( + pub(crate) wgpu_core::id::RenderPipelineId, +); +impl Resource for WebGPURenderPipeline { + fn name(&self) -> Cow { + "webGPURenderPipeline".into() + } +} + +pub fn serialize_index_format(format: String) -> wgpu_types::IndexFormat { + match format.as_str() { + "uint16" => wgpu_types::IndexFormat::Uint16, + "uint32" => wgpu_types::IndexFormat::Uint32, + _ => unreachable!(), + } +} + +fn serialize_stencil_operation( + operation: &str, +) -> wgpu_types::StencilOperation { + match operation { + "keep" => wgpu_types::StencilOperation::Keep, + "zero" => wgpu_types::StencilOperation::Zero, + "replace" => wgpu_types::StencilOperation::Replace, + "invert" => wgpu_types::StencilOperation::Invert, + "increment-clamp" => wgpu_types::StencilOperation::IncrementClamp, + "decrement-clamp" => wgpu_types::StencilOperation::DecrementClamp, + "increment-wrap" => wgpu_types::StencilOperation::IncrementWrap, + "decrement-wrap" => wgpu_types::StencilOperation::DecrementWrap, + _ => unreachable!(), + } +} + +fn serialize_stencil_face_state( + state: GPUStencilFaceState, +) -> wgpu_types::StencilFaceState { + wgpu_types::StencilFaceState { + compare: state + .compare + .as_ref() + .map_or(wgpu_types::CompareFunction::Always, |op| { + super::sampler::serialize_compare_function(op) + }), + fail_op: state + .fail_op + .as_ref() + .map_or(wgpu_types::StencilOperation::Keep, |op| { + serialize_stencil_operation(op) + }), + depth_fail_op: state + .depth_fail_op + .as_ref() + .map_or(wgpu_types::StencilOperation::Keep, |op| { + serialize_stencil_operation(op) + }), + pass_op: state + .pass_op + .as_ref() + .map_or(wgpu_types::StencilOperation::Keep, |op| { + serialize_stencil_operation(op) + }), + } +} + +fn serialize_blend_factor(blend_factor: &str) -> wgpu_types::BlendFactor { + match blend_factor { + "zero" => wgpu_types::BlendFactor::Zero, + "one" => wgpu_types::BlendFactor::One, + "src-color" => wgpu_types::BlendFactor::SrcColor, + "one-minus-src-color" => wgpu_types::BlendFactor::OneMinusSrcColor, + "src-alpha" => wgpu_types::BlendFactor::SrcAlpha, + "one-minus-src-alpha" => wgpu_types::BlendFactor::OneMinusSrcAlpha, + "dst-color" => wgpu_types::BlendFactor::DstColor, + "one-minus-dst-color" => wgpu_types::BlendFactor::OneMinusDstColor, + "dst-alpha" => wgpu_types::BlendFactor::DstAlpha, + "one-minus-dst-alpha" => wgpu_types::BlendFactor::OneMinusDstAlpha, + "src-alpha-saturated" => wgpu_types::BlendFactor::SrcAlphaSaturated, + "blend-color" => wgpu_types::BlendFactor::BlendColor, + "one-minus-blend-color" => wgpu_types::BlendFactor::OneMinusBlendColor, + _ => unreachable!(), + } +} + +fn serialize_blend_component( + blend: GPUBlendComponent, +) -> wgpu_types::BlendState { + wgpu_types::BlendState { + src_factor: blend + .src_factor + .as_ref() + .map_or(wgpu_types::BlendFactor::One, |factor| { + serialize_blend_factor(factor) + }), + dst_factor: blend + .dst_factor + .as_ref() + .map_or(wgpu_types::BlendFactor::Zero, |factor| { + serialize_blend_factor(factor) + }), + operation: match &blend.operation { + Some(operation) => match operation.as_str() { + "add" => wgpu_types::BlendOperation::Add, + "subtract" => wgpu_types::BlendOperation::Subtract, + "reverse-subtract" => wgpu_types::BlendOperation::ReverseSubtract, + "min" => wgpu_types::BlendOperation::Min, + "max" => wgpu_types::BlendOperation::Max, + _ => unreachable!(), + }, + None => wgpu_types::BlendOperation::Add, + }, + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GPUProgrammableStage { + module: u32, + entry_point: String, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateComputePipelineArgs { + device_rid: u32, + label: Option, + layout: Option, + compute: GPUProgrammableStage, +} + +pub fn op_webgpu_create_compute_pipeline( + state: &mut OpState, + args: CreateComputePipelineArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let instance = state.borrow::(); + let device_resource = state + .resource_table + .get::(args.device_rid) + .ok_or_else(bad_resource_id)?; + let device = device_resource.0; + + let pipeline_layout = if let Some(rid) = args.layout { + let id = state + .resource_table + .get::(rid) + .ok_or_else(bad_resource_id)?; + Some(id.0) + } else { + None + }; + + let compute_shader_module_resource = state + .resource_table + .get::(args.compute.module) + .ok_or_else(bad_resource_id)?; + + let descriptor = wgpu_core::pipeline::ComputePipelineDescriptor { + label: args.label.map(Cow::from), + layout: pipeline_layout, + stage: wgpu_core::pipeline::ProgrammableStageDescriptor { + module: compute_shader_module_resource.0, + entry_point: Cow::from(args.compute.entry_point), + }, + }; + let implicit_pipelines = match args.layout { + Some(_) => None, + None => Some(wgpu_core::device::ImplicitPipelineIds { + root_id: std::marker::PhantomData, + group_ids: &[std::marker::PhantomData; wgpu_core::MAX_BIND_GROUPS], + }), + }; + + let (compute_pipeline, _, maybe_err) = gfx_select!(device => instance.device_create_compute_pipeline( + device, + &descriptor, + std::marker::PhantomData, + implicit_pipelines + )); + + let rid = state + .resource_table + .add(WebGPUComputePipeline(compute_pipeline)); + + Ok(json!({ + "rid": rid, + "err": maybe_err.map(WebGPUError::from), + })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePipelineGetBindGroupLayoutArgs { + compute_pipeline_rid: u32, + index: u32, +} + +pub fn op_webgpu_compute_pipeline_get_bind_group_layout( + state: &mut OpState, + args: ComputePipelineGetBindGroupLayoutArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let instance = state.borrow::(); + let compute_pipeline_resource = state + .resource_table + .get::(args.compute_pipeline_rid) + .ok_or_else(bad_resource_id)?; + let compute_pipeline = compute_pipeline_resource.0; + + let (bind_group_layout, maybe_err) = gfx_select!(compute_pipeline => instance.compute_pipeline_get_bind_group_layout(compute_pipeline, args.index, std::marker::PhantomData)); + + let label = gfx_select!(bind_group_layout => instance.bind_group_layout_label(bind_group_layout)); + + let rid = state + .resource_table + .add(super::binding::WebGPUBindGroupLayout(bind_group_layout)); + + Ok(json!({ + "rid": rid, + "label": label, + "err": maybe_err.map(WebGPUError::from) + })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GPUPrimitiveState { + topology: Option, + strip_index_format: Option, + front_face: Option, + cull_mode: Option, +} + +#[derive(Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +struct GPUBlendComponent { + src_factor: Option, + dst_factor: Option, + operation: Option, +} + +#[derive(Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +struct GPUBlendState { + color: GPUBlendComponent, + alpha: GPUBlendComponent, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GPUColorTargetState { + format: String, + blend: Option, + write_mask: Option, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GPUStencilFaceState { + compare: Option, + fail_op: Option, + depth_fail_op: Option, + pass_op: Option, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GPUDepthStencilState { + format: String, + depth_write_enabled: Option, + depth_compare: Option, + stencil_front: Option, + stencil_back: Option, + stencil_read_mask: Option, + stencil_write_mask: Option, + depth_bias: Option, + depth_bias_slope_scale: Option, + depth_bias_clamp: Option, + clamp_depth: Option, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GPUVertexAttribute { + format: String, + offset: u64, + shader_location: u32, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GPUVertexBufferLayout { + array_stride: u64, + step_mode: Option, + attributes: Vec, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GPUVertexState { + module: u32, + entry_point: String, + buffers: Option>>, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GPUMultisampleState { + count: Option, + mask: Option, // against spec, but future proof + alpha_to_coverage_enabled: Option, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GPUFragmentState { + targets: Vec, + module: u32, + entry_point: String, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateRenderPipelineArgs { + device_rid: u32, + label: Option, + layout: Option, + vertex: GPUVertexState, + primitive: Option, + depth_stencil: Option, + multisample: Option, + fragment: Option, +} + +pub fn op_webgpu_create_render_pipeline( + state: &mut OpState, + args: CreateRenderPipelineArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let instance = state.borrow::(); + let device_resource = state + .resource_table + .get::(args.device_rid) + .ok_or_else(bad_resource_id)?; + let device = device_resource.0; + + let layout = if let Some(rid) = args.layout { + let pipeline_layout_resource = state + .resource_table + .get::(rid) + .ok_or_else(bad_resource_id)?; + Some(pipeline_layout_resource.0) + } else { + None + }; + + let vertex_shader_module_resource = state + .resource_table + .get::(args.vertex.module) + .ok_or_else(bad_resource_id)?; + + let descriptor = wgpu_core::pipeline::RenderPipelineDescriptor { + label: args.label.map(Cow::from), + layout, + vertex: wgpu_core::pipeline::VertexState { + stage: wgpu_core::pipeline::ProgrammableStageDescriptor { + module: vertex_shader_module_resource.0, + entry_point: Cow::from(args.vertex.entry_point), + }, + buffers: Cow::from(if let Some(buffers) = args.vertex.buffers { + let mut return_buffers = vec![]; + for buffer in buffers { + if let Some(buffer) = buffer { + return_buffers.push(wgpu_core::pipeline::VertexBufferLayout { + array_stride: buffer.array_stride, + step_mode: match buffer.step_mode { + Some(step_mode) => match step_mode.as_str() { + "vertex" => wgpu_types::InputStepMode::Vertex, + "instance" => wgpu_types::InputStepMode::Instance, + _ => unreachable!(), + }, + None => wgpu_types::InputStepMode::Vertex, + }, + attributes: Cow::from( + buffer + .attributes + .iter() + .map(|attribute| wgpu_types::VertexAttribute { + format: match attribute.format.as_str() { + "uchar2" => wgpu_types::VertexFormat::Uchar2, + "uchar4" => wgpu_types::VertexFormat::Uchar4, + "char2" => wgpu_types::VertexFormat::Char2, + "char4" => wgpu_types::VertexFormat::Char4, + "uchar2norm" => wgpu_types::VertexFormat::Uchar2Norm, + "uchar4norm" => wgpu_types::VertexFormat::Uchar4, + "char2norm" => wgpu_types::VertexFormat::Char2Norm, + "char4norm" => wgpu_types::VertexFormat::Char4Norm, + "ushort2" => wgpu_types::VertexFormat::Ushort2, + "ushort4" => wgpu_types::VertexFormat::Ushort4, + "short2" => wgpu_types::VertexFormat::Short2, + "short4" => wgpu_types::VertexFormat::Short4, + "ushort2norm" => wgpu_types::VertexFormat::Ushort2Norm, + "ushort4norm" => wgpu_types::VertexFormat::Ushort4Norm, + "short2norm" => wgpu_types::VertexFormat::Short2Norm, + "short4norm" => wgpu_types::VertexFormat::Short4Norm, + "half2" => wgpu_types::VertexFormat::Half2, + "half4" => wgpu_types::VertexFormat::Half4, + "float" => wgpu_types::VertexFormat::Float, + "float2" => wgpu_types::VertexFormat::Float2, + "float3" => wgpu_types::VertexFormat::Float3, + "float4" => wgpu_types::VertexFormat::Float4, + "uint" => wgpu_types::VertexFormat::Uint, + "uint2" => wgpu_types::VertexFormat::Uint2, + "uint3" => wgpu_types::VertexFormat::Uint3, + "uint4" => wgpu_types::VertexFormat::Uint4, + "int" => wgpu_types::VertexFormat::Int, + "int2" => wgpu_types::VertexFormat::Int2, + "int3" => wgpu_types::VertexFormat::Int3, + "int4" => wgpu_types::VertexFormat::Int4, + _ => unreachable!(), + }, + offset: attribute.offset, + shader_location: attribute.shader_location, + }) + .collect::>(), + ), + }) + } + } + return_buffers + } else { + vec![] + }), + }, + primitive: args.primitive.map_or(Default::default(), |primitive| { + wgpu_types::PrimitiveState { + topology: match primitive.topology { + Some(topology) => match topology.as_str() { + "point-list" => wgpu_types::PrimitiveTopology::PointList, + "line-list" => wgpu_types::PrimitiveTopology::LineList, + "line-strip" => wgpu_types::PrimitiveTopology::LineStrip, + "triangle-list" => wgpu_types::PrimitiveTopology::TriangleList, + "triangle-strip" => wgpu_types::PrimitiveTopology::TriangleStrip, + _ => unreachable!(), + }, + None => wgpu_types::PrimitiveTopology::TriangleList, + }, + strip_index_format: primitive + .strip_index_format + .map(serialize_index_format), + front_face: match primitive.front_face { + Some(front_face) => match front_face.as_str() { + "ccw" => wgpu_types::FrontFace::Ccw, + "cw" => wgpu_types::FrontFace::Cw, + _ => unreachable!(), + }, + None => wgpu_types::FrontFace::Ccw, + }, + cull_mode: match primitive.cull_mode { + Some(cull_mode) => match cull_mode.as_str() { + "none" => wgpu_types::CullMode::None, + "front" => wgpu_types::CullMode::Front, + "back" => wgpu_types::CullMode::Back, + _ => unreachable!(), + }, + None => wgpu_types::CullMode::None, + }, + polygon_mode: Default::default(), // native-only + } + }), + depth_stencil: args.depth_stencil.map(|depth_stencil| { + wgpu_types::DepthStencilState { + format: super::texture::serialize_texture_format(&depth_stencil.format) + .unwrap(), + depth_write_enabled: depth_stencil.depth_write_enabled.unwrap_or(false), + depth_compare: match depth_stencil.depth_compare { + Some(depth_compare) => { + super::sampler::serialize_compare_function(&depth_compare) + } + None => wgpu_types::CompareFunction::Always, + }, + stencil: wgpu_types::StencilState { + front: depth_stencil + .stencil_front + .map_or(Default::default(), serialize_stencil_face_state), + back: depth_stencil + .stencil_back + .map_or(Default::default(), serialize_stencil_face_state), + read_mask: depth_stencil.stencil_read_mask.unwrap_or(0xFFFFFFFF), + write_mask: depth_stencil.stencil_write_mask.unwrap_or(0xFFFFFFFF), + }, + bias: wgpu_types::DepthBiasState { + constant: depth_stencil.depth_bias.unwrap_or(0), + slope_scale: depth_stencil.depth_bias_slope_scale.unwrap_or(0.0), + clamp: depth_stencil.depth_bias_clamp.unwrap_or(0.0), + }, + clamp_depth: depth_stencil.clamp_depth.unwrap_or(false), + } + }), + multisample: args.multisample.map_or(Default::default(), |multisample| { + wgpu_types::MultisampleState { + count: multisample.count.unwrap_or(1), + mask: multisample.mask.unwrap_or(0xFFFFFFFF), + alpha_to_coverage_enabled: multisample + .alpha_to_coverage_enabled + .unwrap_or(false), + } + }), + fragment: args.fragment.map(|fragment| { + let fragment_shader_module_resource = state + .resource_table + .get::(fragment.module) + .ok_or_else(bad_resource_id) + .unwrap(); + + wgpu_core::pipeline::FragmentState { + stage: wgpu_core::pipeline::ProgrammableStageDescriptor { + module: fragment_shader_module_resource.0, + entry_point: Cow::from(fragment.entry_point), + }, + targets: Cow::from( + fragment + .targets + .iter() + .map(|target| { + let blends = target.blend.clone().map(|blend| { + ( + serialize_blend_component(blend.alpha), + serialize_blend_component(blend.color), + ) + }); + + wgpu_types::ColorTargetState { + format: super::texture::serialize_texture_format( + &target.format, + ) + .unwrap(), + alpha_blend: blends + .clone() + .map_or(Default::default(), |states| states.0), + color_blend: blends + .map_or(Default::default(), |states| states.1), + write_mask: target + .write_mask + .map_or(Default::default(), |mask| { + wgpu_types::ColorWrite::from_bits(mask).unwrap() + }), + } + }) + .collect::>(), + ), + } + }), + }; + + let implicit_pipelines = match args.layout { + Some(_) => None, + None => Some(wgpu_core::device::ImplicitPipelineIds { + root_id: std::marker::PhantomData, + group_ids: &[std::marker::PhantomData; wgpu_core::MAX_BIND_GROUPS], + }), + }; + + let (render_pipeline, _, maybe_err) = gfx_select!(device => instance.device_create_render_pipeline( + device, + &descriptor, + std::marker::PhantomData, + implicit_pipelines + )); + + let rid = state + .resource_table + .add(WebGPURenderPipeline(render_pipeline)); + + Ok(json!({ + "rid": rid, + "err": maybe_err.map(WebGPUError::from) + })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPipelineGetBindGroupLayoutArgs { + render_pipeline_rid: u32, + index: u32, +} + +pub fn op_webgpu_render_pipeline_get_bind_group_layout( + state: &mut OpState, + args: RenderPipelineGetBindGroupLayoutArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let instance = state.borrow::(); + let render_pipeline_resource = state + .resource_table + .get::(args.render_pipeline_rid) + .ok_or_else(bad_resource_id)?; + let render_pipeline = render_pipeline_resource.0; + + let (bind_group_layout, maybe_err) = gfx_select!(render_pipeline => instance.render_pipeline_get_bind_group_layout(render_pipeline, args.index, std::marker::PhantomData)); + + let label = gfx_select!(bind_group_layout => instance.bind_group_layout_label(bind_group_layout)); + + let rid = state + .resource_table + .add(super::binding::WebGPUBindGroupLayout(bind_group_layout)); + + Ok(json!({ + "rid": rid, + "label": label, + "err": maybe_err.map(WebGPUError::from), + })) +} diff --git a/op_crates/webgpu/queue.rs b/op_crates/webgpu/queue.rs new file mode 100644 index 00000000000000..c141a3c455cd81 --- /dev/null +++ b/op_crates/webgpu/queue.rs @@ -0,0 +1,157 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::bad_resource_id; +use deno_core::error::AnyError; +use deno_core::serde_json::json; +use deno_core::serde_json::Value; +use deno_core::OpState; +use deno_core::ZeroCopyBuf; +use serde::Deserialize; + +use super::error::WebGPUError; + +type WebGPUQueue = super::WebGPUDevice; + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct QueueSubmitArgs { + queue_rid: u32, + command_buffers: Vec, +} + +pub fn op_webgpu_queue_submit( + state: &mut OpState, + args: QueueSubmitArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let instance = state.borrow::(); + let queue_resource = state + .resource_table + .get::(args.queue_rid) + .ok_or_else(bad_resource_id)?; + let queue = queue_resource.0; + + let mut ids = vec![]; + + for rid in args.command_buffers { + let buffer_resource = state + .resource_table + .get::(rid) + .ok_or_else(bad_resource_id)?; + ids.push(buffer_resource.0); + } + + let maybe_err = + gfx_select!(queue => instance.queue_submit(queue, &ids)).err(); + + Ok(json!({ "err": maybe_err.map(WebGPUError::from) })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GPUImageDataLayout { + offset: Option, + bytes_per_row: Option, + rows_per_image: Option, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct QueueWriteBufferArgs { + queue_rid: u32, + buffer: u32, + buffer_offset: u64, + data_offset: usize, + size: Option, +} + +pub fn op_webgpu_write_buffer( + state: &mut OpState, + args: QueueWriteBufferArgs, + zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let instance = state.borrow::(); + let buffer_resource = state + .resource_table + .get::(args.buffer) + .ok_or_else(bad_resource_id)?; + let buffer = buffer_resource.0; + let queue_resource = state + .resource_table + .get::(args.queue_rid) + .ok_or_else(bad_resource_id)?; + let queue = queue_resource.0; + + let data = match args.size { + Some(size) => &zero_copy[0][args.data_offset..(args.data_offset + size)], + None => &zero_copy[0][args.data_offset..], + }; + let maybe_err = gfx_select!(queue => instance.queue_write_buffer( + queue, + buffer, + args.buffer_offset, + data + )) + .err(); + + Ok(json!({ "err": maybe_err.map(WebGPUError::from) })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct QueueWriteTextureArgs { + queue_rid: u32, + destination: super::command_encoder::GPUImageCopyTexture, + data_layout: GPUImageDataLayout, + size: super::texture::GPUExtent3D, +} + +pub fn op_webgpu_write_texture( + state: &mut OpState, + args: QueueWriteTextureArgs, + zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let instance = state.borrow::(); + let texture_resource = state + .resource_table + .get::(args.destination.texture) + .ok_or_else(bad_resource_id)?; + let queue_resource = state + .resource_table + .get::(args.queue_rid) + .ok_or_else(bad_resource_id)?; + let queue = queue_resource.0; + + let destination = wgpu_core::command::TextureCopyView { + texture: texture_resource.0, + mip_level: args.destination.mip_level.unwrap_or(0), + origin: args + .destination + .origin + .map_or(Default::default(), |origin| wgpu_types::Origin3d { + x: origin.x.unwrap_or(0), + y: origin.y.unwrap_or(0), + z: origin.z.unwrap_or(0), + }), + }; + let data_layout = wgpu_types::TextureDataLayout { + offset: args.data_layout.offset.unwrap_or(0), + bytes_per_row: args.data_layout.bytes_per_row.unwrap_or(0), + rows_per_image: args.data_layout.rows_per_image.unwrap_or(0), + }; + + let maybe_err = gfx_select!(queue => instance.queue_write_texture( + queue, + &destination, + &*zero_copy[0], + &data_layout, + &wgpu_types::Extent3d { + width: args.size.width.unwrap_or(1), + height: args.size.height.unwrap_or(1), + depth: args.size.depth.unwrap_or(1), + } + )) + .err(); + + Ok(json!({ "err": maybe_err.map(WebGPUError::from) })) +} diff --git a/op_crates/webgpu/render_pass.rs b/op_crates/webgpu/render_pass.rs new file mode 100644 index 00000000000000..c9018726b79dc0 --- /dev/null +++ b/op_crates/webgpu/render_pass.rs @@ -0,0 +1,676 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::bad_resource_id; +use deno_core::error::AnyError; +use deno_core::serde_json::json; +use deno_core::serde_json::Value; +use deno_core::ZeroCopyBuf; +use deno_core::{OpState, Resource}; +use serde::Deserialize; +use std::borrow::Cow; +use std::cell::RefCell; + +use super::error::WebGPUError; + +pub(crate) struct WebGPURenderPass( + pub(crate) RefCell, +); +impl Resource for WebGPURenderPass { + fn name(&self) -> Cow { + "webGPURenderPass".into() + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassSetViewportArgs { + render_pass_rid: u32, + x: f32, + y: f32, + width: f32, + height: f32, + min_depth: f32, + max_depth: f32, +} + +pub fn op_webgpu_render_pass_set_viewport( + state: &mut OpState, + args: RenderPassSetViewportArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let render_pass_resource = state + .resource_table + .get::(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_set_viewport( + &mut render_pass_resource.0.borrow_mut(), + args.x, + args.y, + args.width, + args.height, + args.min_depth, + args.max_depth, + ); + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassSetScissorRectArgs { + render_pass_rid: u32, + x: u32, + y: u32, + width: u32, + height: u32, +} + +pub fn op_webgpu_render_pass_set_scissor_rect( + state: &mut OpState, + args: RenderPassSetScissorRectArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let render_pass_resource = state + .resource_table + .get::(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_set_scissor_rect( + &mut render_pass_resource.0.borrow_mut(), + args.x, + args.y, + args.width, + args.height, + ); + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GPUColor { + pub r: f64, + pub g: f64, + pub b: f64, + pub a: f64, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassSetBlendColorArgs { + render_pass_rid: u32, + color: GPUColor, +} + +pub fn op_webgpu_render_pass_set_blend_color( + state: &mut OpState, + args: RenderPassSetBlendColorArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let render_pass_resource = state + .resource_table + .get::(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_set_blend_color( + &mut render_pass_resource.0.borrow_mut(), + &wgpu_types::Color { + r: args.color.r, + g: args.color.g, + b: args.color.b, + a: args.color.a, + }, + ); + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassSetStencilReferenceArgs { + render_pass_rid: u32, + reference: u32, +} + +pub fn op_webgpu_render_pass_set_stencil_reference( + state: &mut OpState, + args: RenderPassSetStencilReferenceArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let render_pass_resource = state + .resource_table + .get::(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_set_stencil_reference( + &mut render_pass_resource.0.borrow_mut(), + args.reference, + ); + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassBeginPipelineStatisticsQueryArgs { + render_pass_rid: u32, + query_set: u32, + query_index: u32, +} + +pub fn op_webgpu_render_pass_begin_pipeline_statistics_query( + state: &mut OpState, + args: RenderPassBeginPipelineStatisticsQueryArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let render_pass_resource = state + .resource_table + .get::(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + let query_set_resource = state + .resource_table + .get::(args.query_set) + .ok_or_else(bad_resource_id)?; + + unsafe { + wgpu_core::command::render_ffi::wgpu_render_pass_begin_pipeline_statistics_query( + &mut render_pass_resource.0.borrow_mut(), + query_set_resource.0, + args.query_index, + ); + } + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassEndPipelineStatisticsQueryArgs { + render_pass_rid: u32, +} + +pub fn op_webgpu_render_pass_end_pipeline_statistics_query( + state: &mut OpState, + args: RenderPassEndPipelineStatisticsQueryArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let render_pass_resource = state + .resource_table + .get::(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + + unsafe { + wgpu_core::command::render_ffi::wgpu_render_pass_end_pipeline_statistics_query( + &mut render_pass_resource.0.borrow_mut(), + ); + } + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassWriteTimestampArgs { + render_pass_rid: u32, + query_set: u32, + query_index: u32, +} + +pub fn op_webgpu_render_pass_write_timestamp( + state: &mut OpState, + args: RenderPassWriteTimestampArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let render_pass_resource = state + .resource_table + .get::(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + let query_set_resource = state + .resource_table + .get::(args.query_set) + .ok_or_else(bad_resource_id)?; + + unsafe { + wgpu_core::command::render_ffi::wgpu_render_pass_write_timestamp( + &mut render_pass_resource.0.borrow_mut(), + query_set_resource.0, + args.query_index, + ); + } + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassExecuteBundlesArgs { + render_pass_rid: u32, + bundles: Vec, +} + +pub fn op_webgpu_render_pass_execute_bundles( + state: &mut OpState, + args: RenderPassExecuteBundlesArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let mut render_bundle_ids = vec![]; + + for rid in &args.bundles { + let render_bundle_resource = state + .resource_table + .get::(*rid) + .ok_or_else(bad_resource_id)?; + render_bundle_ids.push(render_bundle_resource.0); + } + + let render_pass_resource = state + .resource_table + .get::(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + + unsafe { + wgpu_core::command::render_ffi::wgpu_render_pass_execute_bundles( + &mut render_pass_resource.0.borrow_mut(), + render_bundle_ids.as_ptr(), + args.bundles.len(), + ); + } + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassEndPassArgs { + command_encoder_rid: u32, + render_pass_rid: u32, +} + +pub fn op_webgpu_render_pass_end_pass( + state: &mut OpState, + args: RenderPassEndPassArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let command_encoder_resource = state + .resource_table + .get::( + args.command_encoder_rid, + ) + .ok_or_else(bad_resource_id)?; + let command_encoder = command_encoder_resource.0; + let render_pass_resource = state + .resource_table + .take::(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + let render_pass = &render_pass_resource.0.borrow(); + let instance = state.borrow::(); + + let maybe_err = gfx_select!(command_encoder => instance.command_encoder_run_render_pass(command_encoder, render_pass)).err(); + + Ok(json!({ "err": maybe_err.map(WebGPUError::from) })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassSetBindGroupArgs { + render_pass_rid: u32, + index: u32, + bind_group: u32, + dynamic_offsets_data: Option>, + dynamic_offsets_data_start: usize, + dynamic_offsets_data_length: usize, +} + +pub fn op_webgpu_render_pass_set_bind_group( + state: &mut OpState, + args: RenderPassSetBindGroupArgs, + zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let bind_group_resource = state + .resource_table + .get::(args.bind_group) + .ok_or_else(bad_resource_id)?; + let render_pass_resource = state + .resource_table + .get::(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + + // I know this might look like it can be easily deduplicated, but it can not + // be due to the lifetime of the args.dynamic_offsets_data slice. Because we + // need to use a raw pointer here the slice can be freed before the pointer + // is used in wgpu_render_pass_set_bind_group. See + // https://matrix.to/#/!XFRnMvAfptAHthwBCx:matrix.org/$HgrlhD-Me1DwsGb8UdMu2Hqubgks8s7ILwWRwigOUAg + match args.dynamic_offsets_data { + Some(data) => unsafe { + wgpu_core::command::render_ffi::wgpu_render_pass_set_bind_group( + &mut render_pass_resource.0.borrow_mut(), + args.index, + bind_group_resource.0, + data.as_slice().as_ptr(), + args.dynamic_offsets_data_length, + ); + }, + None => { + let (prefix, data, suffix) = unsafe { zero_copy[0].align_to::() }; + assert!(prefix.is_empty()); + assert!(suffix.is_empty()); + unsafe { + wgpu_core::command::render_ffi::wgpu_render_pass_set_bind_group( + &mut render_pass_resource.0.borrow_mut(), + args.index, + bind_group_resource.0, + data[args.dynamic_offsets_data_start..].as_ptr(), + args.dynamic_offsets_data_length, + ); + } + } + }; + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassPushDebugGroupArgs { + render_pass_rid: u32, + group_label: String, +} + +pub fn op_webgpu_render_pass_push_debug_group( + state: &mut OpState, + args: RenderPassPushDebugGroupArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let render_pass_resource = state + .resource_table + .get::(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + + unsafe { + let label = std::ffi::CString::new(args.group_label).unwrap(); + wgpu_core::command::render_ffi::wgpu_render_pass_push_debug_group( + &mut render_pass_resource.0.borrow_mut(), + label.as_ptr(), + 0, // wgpu#975 + ); + } + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassPopDebugGroupArgs { + render_pass_rid: u32, +} + +pub fn op_webgpu_render_pass_pop_debug_group( + state: &mut OpState, + args: RenderPassPopDebugGroupArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let render_pass_resource = state + .resource_table + .get::(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_pop_debug_group( + &mut render_pass_resource.0.borrow_mut(), + ); + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassInsertDebugMarkerArgs { + render_pass_rid: u32, + marker_label: String, +} + +pub fn op_webgpu_render_pass_insert_debug_marker( + state: &mut OpState, + args: RenderPassInsertDebugMarkerArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let render_pass_resource = state + .resource_table + .get::(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + + unsafe { + let label = std::ffi::CString::new(args.marker_label).unwrap(); + wgpu_core::command::render_ffi::wgpu_render_pass_insert_debug_marker( + &mut render_pass_resource.0.borrow_mut(), + label.as_ptr(), + 0, // wgpu#975 + ); + } + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassSetPipelineArgs { + render_pass_rid: u32, + pipeline: u32, +} + +pub fn op_webgpu_render_pass_set_pipeline( + state: &mut OpState, + args: RenderPassSetPipelineArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let render_pipeline_resource = state + .resource_table + .get::(args.pipeline) + .ok_or_else(bad_resource_id)?; + let render_pass_resource = state + .resource_table + .get::(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_set_pipeline( + &mut render_pass_resource.0.borrow_mut(), + render_pipeline_resource.0, + ); + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassSetIndexBufferArgs { + render_pass_rid: u32, + buffer: u32, + index_format: String, + offset: u64, + size: u64, +} + +pub fn op_webgpu_render_pass_set_index_buffer( + state: &mut OpState, + args: RenderPassSetIndexBufferArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let buffer_resource = state + .resource_table + .get::(args.buffer) + .ok_or_else(bad_resource_id)?; + let render_pass_resource = state + .resource_table + .get::(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + + render_pass_resource.0.borrow_mut().set_index_buffer( + buffer_resource.0, + super::pipeline::serialize_index_format(args.index_format), + args.offset, + std::num::NonZeroU64::new(args.size), + ); + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassSetVertexBufferArgs { + render_pass_rid: u32, + slot: u32, + buffer: u32, + offset: u64, + size: u64, +} + +pub fn op_webgpu_render_pass_set_vertex_buffer( + state: &mut OpState, + args: RenderPassSetVertexBufferArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let buffer_resource = state + .resource_table + .get::(args.buffer) + .ok_or_else(bad_resource_id)?; + let render_pass_resource = state + .resource_table + .get::(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_set_vertex_buffer( + &mut render_pass_resource.0.borrow_mut(), + args.slot, + buffer_resource.0, + args.offset, + std::num::NonZeroU64::new(args.size), + ); + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassDrawArgs { + render_pass_rid: u32, + vertex_count: u32, + instance_count: u32, + first_vertex: u32, + first_instance: u32, +} + +pub fn op_webgpu_render_pass_draw( + state: &mut OpState, + args: RenderPassDrawArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let render_pass_resource = state + .resource_table + .get::(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_draw( + &mut render_pass_resource.0.borrow_mut(), + args.vertex_count, + args.instance_count, + args.first_vertex, + args.first_instance, + ); + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassDrawIndexedArgs { + render_pass_rid: u32, + index_count: u32, + instance_count: u32, + first_index: u32, + base_vertex: i32, + first_instance: u32, +} + +pub fn op_webgpu_render_pass_draw_indexed( + state: &mut OpState, + args: RenderPassDrawIndexedArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let render_pass_resource = state + .resource_table + .get::(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_draw_indexed( + &mut render_pass_resource.0.borrow_mut(), + args.index_count, + args.instance_count, + args.first_index, + args.base_vertex, + args.first_instance, + ); + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassDrawIndirectArgs { + render_pass_rid: u32, + indirect_buffer: u32, + indirect_offset: u64, +} + +pub fn op_webgpu_render_pass_draw_indirect( + state: &mut OpState, + args: RenderPassDrawIndirectArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let buffer_resource = state + .resource_table + .get::(args.indirect_buffer) + .ok_or_else(bad_resource_id)?; + let render_pass_resource = state + .resource_table + .get::(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_draw_indirect( + &mut render_pass_resource.0.borrow_mut(), + buffer_resource.0, + args.indirect_offset, + ); + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassDrawIndexedIndirectArgs { + render_pass_rid: u32, + indirect_buffer: u32, + indirect_offset: u64, +} + +pub fn op_webgpu_render_pass_draw_indexed_indirect( + state: &mut OpState, + args: RenderPassDrawIndexedIndirectArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let buffer_resource = state + .resource_table + .get::(args.indirect_buffer) + .ok_or_else(bad_resource_id)?; + let render_pass_resource = state + .resource_table + .get::(args.render_pass_rid) + .ok_or_else(bad_resource_id)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_draw_indexed_indirect( + &mut render_pass_resource.0.borrow_mut(), + buffer_resource.0, + args.indirect_offset, + ); + + Ok(json!({})) +} diff --git a/op_crates/webgpu/sampler.rs b/op_crates/webgpu/sampler.rs new file mode 100644 index 00000000000000..48974a3280179b --- /dev/null +++ b/op_crates/webgpu/sampler.rs @@ -0,0 +1,129 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::bad_resource_id; +use deno_core::error::AnyError; +use deno_core::serde_json::json; +use deno_core::serde_json::Value; +use deno_core::ZeroCopyBuf; +use deno_core::{OpState, Resource}; +use serde::Deserialize; +use std::borrow::Cow; + +use super::error::WebGPUError; + +pub(crate) struct WebGPUSampler(pub(crate) wgpu_core::id::SamplerId); +impl Resource for WebGPUSampler { + fn name(&self) -> Cow { + "webGPUSampler".into() + } +} + +fn serialize_address_mode( + address_mode: Option, +) -> wgpu_types::AddressMode { + match address_mode { + Some(address_mode) => match address_mode.as_str() { + "clamp-to-edge" => wgpu_types::AddressMode::ClampToEdge, + "repeat" => wgpu_types::AddressMode::Repeat, + "mirror-repeat" => wgpu_types::AddressMode::MirrorRepeat, + _ => unreachable!(), + }, + None => wgpu_types::AddressMode::ClampToEdge, + } +} + +fn serialize_filter_mode( + filter_mode: Option, +) -> wgpu_types::FilterMode { + match filter_mode { + Some(filter_mode) => match filter_mode.as_str() { + "nearest" => wgpu_types::FilterMode::Nearest, + "linear" => wgpu_types::FilterMode::Linear, + _ => unreachable!(), + }, + None => wgpu_types::FilterMode::Nearest, + } +} + +pub fn serialize_compare_function( + compare: &str, +) -> wgpu_types::CompareFunction { + match compare { + "never" => wgpu_types::CompareFunction::Never, + "less" => wgpu_types::CompareFunction::Less, + "equal" => wgpu_types::CompareFunction::Equal, + "less-equal" => wgpu_types::CompareFunction::LessEqual, + "greater" => wgpu_types::CompareFunction::Greater, + "not-equal" => wgpu_types::CompareFunction::NotEqual, + "greater-equal" => wgpu_types::CompareFunction::GreaterEqual, + "always" => wgpu_types::CompareFunction::Always, + _ => unreachable!(), + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateSamplerArgs { + device_rid: u32, + label: Option, + address_mode_u: Option, + address_mode_v: Option, + address_mode_w: Option, + mag_filter: Option, + min_filter: Option, + mipmap_filter: Option, + lod_min_clamp: Option, + lod_max_clamp: Option, + compare: Option, + max_anisotropy: Option, +} + +pub fn op_webgpu_create_sampler( + state: &mut OpState, + args: CreateSamplerArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let instance = state.borrow::(); + let device_resource = state + .resource_table + .get::(args.device_rid) + .ok_or_else(bad_resource_id)?; + let device = device_resource.0; + + let descriptor = wgpu_core::resource::SamplerDescriptor { + label: args.label.map(Cow::from), + address_modes: [ + serialize_address_mode(args.address_mode_u), + serialize_address_mode(args.address_mode_v), + serialize_address_mode(args.address_mode_w), + ], + mag_filter: serialize_filter_mode(args.mag_filter), + min_filter: serialize_filter_mode(args.min_filter), + mipmap_filter: serialize_filter_mode(args.mipmap_filter), + lod_min_clamp: args.lod_min_clamp.unwrap_or(0.0), + lod_max_clamp: args.lod_max_clamp.unwrap_or( + wgpu_core::resource::SamplerDescriptor::default().lod_max_clamp, + ), + compare: args + .compare + .as_ref() + .map(|compare| serialize_compare_function(compare)), + anisotropy_clamp: std::num::NonZeroU8::new( + args.max_anisotropy.unwrap_or(0), + ), + border_color: None, // native-only + }; + + let (sampler, maybe_err) = gfx_select!(device => instance.device_create_sampler( + device, + &descriptor, + std::marker::PhantomData + )); + + let rid = state.resource_table.add(WebGPUSampler(sampler)); + + Ok(json!({ + "rid": rid, + "err": maybe_err.map(WebGPUError::from) + })) +} diff --git a/op_crates/webgpu/shader.rs b/op_crates/webgpu/shader.rs new file mode 100644 index 00000000000000..25adf9b9596151 --- /dev/null +++ b/op_crates/webgpu/shader.rs @@ -0,0 +1,77 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::bad_resource_id; +use deno_core::error::AnyError; +use deno_core::serde_json::json; +use deno_core::serde_json::Value; +use deno_core::ZeroCopyBuf; +use deno_core::{OpState, Resource}; +use serde::Deserialize; +use std::borrow::Cow; + +use super::error::WebGPUError; + +pub(crate) struct WebGPUShaderModule(pub(crate) wgpu_core::id::ShaderModuleId); +impl Resource for WebGPUShaderModule { + fn name(&self) -> Cow { + "webGPUShaderModule".into() + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateShaderModuleArgs { + device_rid: u32, + label: Option, + code: Option, + _source_map: Option<()>, // not yet implemented +} + +pub fn op_webgpu_create_shader_module( + state: &mut OpState, + args: CreateShaderModuleArgs, + zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let instance = state.borrow::(); + let device_resource = state + .resource_table + .get::(args.device_rid) + .ok_or_else(bad_resource_id)?; + let device = device_resource.0; + + let source = match args.code { + Some(code) => { + wgpu_core::pipeline::ShaderModuleSource::Wgsl(Cow::from(code)) + } + None => wgpu_core::pipeline::ShaderModuleSource::SpirV(Cow::from(unsafe { + let (prefix, data, suffix) = zero_copy[0].align_to::(); + assert!(prefix.is_empty()); + assert!(suffix.is_empty()); + data + })), + }; + + let mut flags = wgpu_types::ShaderFlags::default(); + flags.set(wgpu_types::ShaderFlags::VALIDATION, true); + #[cfg(all(target_os = "macos", target_arch = "x86_64"))] + flags.set(wgpu_types::ShaderFlags::EXPERIMENTAL_TRANSLATION, true); + + let descriptor = wgpu_core::pipeline::ShaderModuleDescriptor { + label: args.label.map(Cow::from), + flags, + }; + + let (shader_module, maybe_err) = gfx_select!(device => instance.device_create_shader_module( + device, + &descriptor, + source, + std::marker::PhantomData + )); + + let rid = state.resource_table.add(WebGPUShaderModule(shader_module)); + + Ok(json!({ + "rid": rid, + "err": maybe_err.map(WebGPUError::from) + })) +} diff --git a/op_crates/webgpu/texture.rs b/op_crates/webgpu/texture.rs new file mode 100644 index 00000000000000..94de28fedd795a --- /dev/null +++ b/op_crates/webgpu/texture.rs @@ -0,0 +1,256 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::AnyError; +use deno_core::error::{bad_resource_id, not_supported}; +use deno_core::serde_json::json; +use deno_core::serde_json::Value; +use deno_core::ZeroCopyBuf; +use deno_core::{OpState, Resource}; +use serde::Deserialize; +use std::borrow::Cow; + +use super::error::WebGPUError; +pub(crate) struct WebGPUTexture(pub(crate) wgpu_core::id::TextureId); +impl Resource for WebGPUTexture { + fn name(&self) -> Cow { + "webGPUTexture".into() + } +} + +pub(crate) struct WebGPUTextureView(pub(crate) wgpu_core::id::TextureViewId); +impl Resource for WebGPUTextureView { + fn name(&self) -> Cow { + "webGPUTextureView".into() + } +} + +pub fn serialize_texture_format( + format: &str, +) -> Result { + Ok(match format { + // 8-bit formats + "r8unorm" => wgpu_types::TextureFormat::R8Unorm, + "r8snorm" => wgpu_types::TextureFormat::R8Snorm, + "r8uint" => wgpu_types::TextureFormat::R8Uint, + "r8sint" => wgpu_types::TextureFormat::R8Sint, + + // 16-bit formats + "r16uint" => wgpu_types::TextureFormat::R16Uint, + "r16sint" => wgpu_types::TextureFormat::R16Sint, + "r16float" => wgpu_types::TextureFormat::R16Float, + "rg8unorm" => wgpu_types::TextureFormat::Rg8Unorm, + "rg8snorm" => wgpu_types::TextureFormat::Rg8Snorm, + "rg8uint" => wgpu_types::TextureFormat::Rg8Uint, + "rg8sint" => wgpu_types::TextureFormat::Rg8Sint, + + // 32-bit formats + "r32uint" => wgpu_types::TextureFormat::R32Uint, + "r32sint" => wgpu_types::TextureFormat::R32Sint, + "r32float" => wgpu_types::TextureFormat::R32Float, + "rg16uint" => wgpu_types::TextureFormat::Rg16Uint, + "rg16sint" => wgpu_types::TextureFormat::Rg16Sint, + "rg16float" => wgpu_types::TextureFormat::Rg16Float, + "rgba8unorm" => wgpu_types::TextureFormat::Rgba8Unorm, + "rgba8unorm-srgb" => wgpu_types::TextureFormat::Rgba8UnormSrgb, + "rgba8snorm" => wgpu_types::TextureFormat::Rgba8Snorm, + "rgba8uint" => wgpu_types::TextureFormat::Rgba8Uint, + "rgba8sint" => wgpu_types::TextureFormat::Rgba8Sint, + "bgra8unorm" => wgpu_types::TextureFormat::Bgra8Unorm, + "bgra8unorm-srgb" => wgpu_types::TextureFormat::Bgra8UnormSrgb, + // Packed 32-bit formats + "rgb9e5ufloat" => return Err(not_supported()), // wgpu#967 + "rgb10a2unorm" => wgpu_types::TextureFormat::Rgb10a2Unorm, + "rg11b10ufloat" => wgpu_types::TextureFormat::Rg11b10Float, + + // 64-bit formats + "rg32uint" => wgpu_types::TextureFormat::Rg32Uint, + "rg32sint" => wgpu_types::TextureFormat::Rg32Sint, + "rg32float" => wgpu_types::TextureFormat::Rg32Float, + "rgba16uint" => wgpu_types::TextureFormat::Rgba16Uint, + "rgba16sint" => wgpu_types::TextureFormat::Rgba16Sint, + "rgba16float" => wgpu_types::TextureFormat::Rgba16Float, + + // 128-bit formats + "rgba32uint" => wgpu_types::TextureFormat::Rgba32Uint, + "rgba32sint" => wgpu_types::TextureFormat::Rgba32Sint, + "rgba32float" => wgpu_types::TextureFormat::Rgba32Float, + + // Depth and stencil formats + "stencil8" => return Err(not_supported()), // wgpu#967 + "depth16unorm" => return Err(not_supported()), // wgpu#967 + "depth24plus" => wgpu_types::TextureFormat::Depth24Plus, + "depth24plus-stencil8" => wgpu_types::TextureFormat::Depth24PlusStencil8, + "depth32float" => wgpu_types::TextureFormat::Depth32Float, + + // BC compressed formats usable if "texture-compression-bc" is both + // supported by the device/user agent and enabled in requestDevice. + "bc1-rgba-unorm" => wgpu_types::TextureFormat::Bc1RgbaUnorm, + "bc1-rgba-unorm-srgb" => wgpu_types::TextureFormat::Bc1RgbaUnormSrgb, + "bc2-rgba-unorm" => wgpu_types::TextureFormat::Bc2RgbaUnorm, + "bc2-rgba-unorm-srgb" => wgpu_types::TextureFormat::Bc2RgbaUnormSrgb, + "bc3-rgba-unorm" => wgpu_types::TextureFormat::Bc3RgbaUnorm, + "bc3-rgba-unorm-srgb" => wgpu_types::TextureFormat::Bc3RgbaUnormSrgb, + "bc4-r-unorm" => wgpu_types::TextureFormat::Bc4RUnorm, + "bc4-r-snorm" => wgpu_types::TextureFormat::Bc4RSnorm, + "bc5-rg-unorm" => wgpu_types::TextureFormat::Bc5RgUnorm, + "bc5-rg-snorm" => wgpu_types::TextureFormat::Bc5RgSnorm, + "bc6h-rgb-ufloat" => wgpu_types::TextureFormat::Bc6hRgbUfloat, + "bc6h-rgb-float" => wgpu_types::TextureFormat::Bc6hRgbSfloat, // wgpu#967 + "bc7-rgba-unorm" => wgpu_types::TextureFormat::Bc7RgbaUnorm, + "bc7-rgba-unorm-srgb" => wgpu_types::TextureFormat::Bc7RgbaUnormSrgb, + + // "depth24unorm-stencil8" extension + "depth24unorm-stencil8" => return Err(not_supported()), // wgpu#967 + + // "depth32float-stencil8" extension + "depth32float-stencil8" => return Err(not_supported()), // wgpu#967 + _ => unreachable!(), + }) +} + +pub fn serialize_dimension( + dimension: &str, +) -> wgpu_types::TextureViewDimension { + match dimension { + "1d" => wgpu_types::TextureViewDimension::D1, + "2d" => wgpu_types::TextureViewDimension::D2, + "2d-array" => wgpu_types::TextureViewDimension::D2Array, + "cube" => wgpu_types::TextureViewDimension::Cube, + "cube-array" => wgpu_types::TextureViewDimension::CubeArray, + "3d" => wgpu_types::TextureViewDimension::D3, + _ => unreachable!(), + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GPUExtent3D { + pub width: Option, + pub height: Option, + pub depth: Option, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateTextureArgs { + device_rid: u32, + label: Option, + size: GPUExtent3D, + mip_level_count: Option, + sample_count: Option, + dimension: Option, + format: String, + usage: u32, +} + +pub fn op_webgpu_create_texture( + state: &mut OpState, + args: CreateTextureArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let instance = state.borrow::(); + let device_resource = state + .resource_table + .get::(args.device_rid) + .ok_or_else(bad_resource_id)?; + let device = device_resource.0; + + let descriptor = wgpu_core::resource::TextureDescriptor { + label: args.label.map(Cow::from), + size: wgpu_types::Extent3d { + width: args.size.width.unwrap_or(1), + height: args.size.height.unwrap_or(1), + depth: args.size.depth.unwrap_or(1), + }, + mip_level_count: args.mip_level_count.unwrap_or(1), + sample_count: args.sample_count.unwrap_or(1), + dimension: match args.dimension { + Some(dimension) => match dimension.as_str() { + "1d" => wgpu_types::TextureDimension::D1, + "2d" => wgpu_types::TextureDimension::D2, + "3d" => wgpu_types::TextureDimension::D3, + _ => unreachable!(), + }, + None => wgpu_types::TextureDimension::D2, + }, + format: serialize_texture_format(&args.format)?, + usage: wgpu_types::TextureUsage::from_bits(args.usage).unwrap(), + }; + + let (texture, maybe_err) = gfx_select!(device => instance.device_create_texture( + device, + &descriptor, + std::marker::PhantomData + )); + + let rid = state.resource_table.add(WebGPUTexture(texture)); + + Ok(json!({ + "rid": rid, + "err": maybe_err.map(WebGPUError::from) + })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateTextureViewArgs { + texture_rid: u32, + label: Option, + format: Option, + dimension: Option, + aspect: Option, + base_mip_level: Option, + mip_level_count: Option, + base_array_layer: Option, + array_layer_count: Option, +} + +pub fn op_webgpu_create_texture_view( + state: &mut OpState, + args: CreateTextureViewArgs, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + let instance = state.borrow::(); + let texture_resource = state + .resource_table + .get::(args.texture_rid) + .ok_or_else(bad_resource_id)?; + let texture = texture_resource.0; + + let descriptor = wgpu_core::resource::TextureViewDescriptor { + label: args.label.map(Cow::from), + format: args + .format + .map(|s| serialize_texture_format(&s)) + .transpose()?, + dimension: args.dimension.map(|s| serialize_dimension(&s)), + aspect: match args.aspect { + Some(aspect) => match aspect.as_str() { + "all" => wgpu_types::TextureAspect::All, + "stencil-only" => wgpu_types::TextureAspect::StencilOnly, + "depth-only" => wgpu_types::TextureAspect::DepthOnly, + _ => unreachable!(), + }, + None => wgpu_types::TextureAspect::All, + }, + base_mip_level: args.base_mip_level.unwrap_or(0), + level_count: std::num::NonZeroU32::new(args.mip_level_count.unwrap_or(0)), + base_array_layer: args.base_array_layer.unwrap_or(0), + array_layer_count: std::num::NonZeroU32::new( + args.array_layer_count.unwrap_or(0), + ), + }; + + let (texture_view, maybe_err) = gfx_select!(texture => instance.texture_create_view( + texture, + &descriptor, + std::marker::PhantomData + )); + + let rid = state.resource_table.add(WebGPUTextureView(texture_view)); + + Ok(json!({ + "rid": rid, + "err": maybe_err.map(WebGPUError::from) + })) +} diff --git a/op_crates/webgpu/webgpu.idl b/op_crates/webgpu/webgpu.idl new file mode 100644 index 00000000000000..598df7efbe9e49 --- /dev/null +++ b/op_crates/webgpu/webgpu.idl @@ -0,0 +1,1023 @@ +interface mixin GPUObjectBase { + attribute USVString? label; +}; + +dictionary GPUObjectDescriptorBase { + USVString label; +}; + +interface GPUAdapterLimits { + readonly attribute GPUSize32 maxTextureDimension1D; + readonly attribute GPUSize32 maxTextureDimension2D; + readonly attribute GPUSize32 maxTextureDimension3D; + readonly attribute GPUSize32 maxTextureArrayLayers; + readonly attribute GPUSize32 maxBindGroups; + readonly attribute GPUSize32 maxDynamicUniformBuffersPerPipelineLayout; + readonly attribute GPUSize32 maxDynamicStorageBuffersPerPipelineLayout; + readonly attribute GPUSize32 maxSampledTexturesPerShaderStage; + readonly attribute GPUSize32 maxSamplersPerShaderStage; + readonly attribute GPUSize32 maxStorageBuffersPerShaderStage; + readonly attribute GPUSize32 maxStorageTexturesPerShaderStage; + readonly attribute GPUSize32 maxUniformBuffersPerShaderStage; + readonly attribute GPUSize32 maxUniformBufferBindingSize; + readonly attribute GPUSize32 maxStorageBufferBindingSize; + readonly attribute GPUSize32 maxVertexBuffers; + readonly attribute GPUSize32 maxVertexAttributes; + readonly attribute GPUSize32 maxVertexBufferArrayStride; +}; + +interface GPUAdapterFeatures { + readonly setlike; +}; + +[Exposed=Window] +partial interface Navigator { + [SameObject] readonly attribute GPU gpu; +}; + +[Exposed=DedicatedWorker] +partial interface WorkerNavigator { + [SameObject] readonly attribute GPU gpu; +}; + +[Exposed=(Window, DedicatedWorker)] +interface GPU { + Promise requestAdapter(optional GPURequestAdapterOptions options = {}); +}; + +dictionary GPURequestAdapterOptions { + GPUPowerPreference powerPreference; +}; + +enum GPUPowerPreference { + "low-power", + "high-performance" +}; + +interface GPUAdapter { + readonly attribute DOMString name; + [SameObject] readonly attribute GPUAdapterFeatures features; + [SameObject] readonly attribute GPUAdapterLimits limits; + + Promise requestDevice(optional GPUDeviceDescriptor descriptor = {}); +}; + +dictionary GPUDeviceDescriptor : GPUObjectDescriptorBase { + sequence nonGuaranteedFeatures = []; + record nonGuaranteedLimits = {}; +}; + +enum GPUFeatureName { + "depth-clamping", + "depth24unorm-stencil8", + "depth32float-stencil8", + "pipeline-statistics-query", + "texture-compression-bc", + "timestamp-query", +}; + +[Exposed=(Window, DedicatedWorker), Serializable] +interface GPUDevice : EventTarget { + [SameObject] readonly attribute GPUAdapter adapter; + readonly attribute FrozenArray features; + readonly attribute object limits; + + [SameObject] readonly attribute GPUQueue queue; + + undefined destroy(); + + GPUBuffer createBuffer(GPUBufferDescriptor descriptor); + GPUTexture createTexture(GPUTextureDescriptor descriptor); + GPUSampler createSampler(optional GPUSamplerDescriptor descriptor = {}); + + GPUBindGroupLayout createBindGroupLayout(GPUBindGroupLayoutDescriptor descriptor); + GPUPipelineLayout createPipelineLayout(GPUPipelineLayoutDescriptor descriptor); + GPUBindGroup createBindGroup(GPUBindGroupDescriptor descriptor); + + GPUShaderModule createShaderModule(GPUShaderModuleDescriptor descriptor); + GPUComputePipeline createComputePipeline(GPUComputePipelineDescriptor descriptor); + GPURenderPipeline createRenderPipeline(GPURenderPipelineDescriptor descriptor); + Promise createComputePipelineAsync(GPUComputePipelineDescriptor descriptor); + Promise createRenderPipelineAsync(GPURenderPipelineDescriptor descriptor); + + GPUCommandEncoder createCommandEncoder(optional GPUCommandEncoderDescriptor descriptor = {}); + GPURenderBundleEncoder createRenderBundleEncoder(GPURenderBundleEncoderDescriptor descriptor); + + GPUQuerySet createQuerySet(GPUQuerySetDescriptor descriptor); +}; +GPUDevice includes GPUObjectBase; + +[Serializable] +interface GPUBuffer { + Promise mapAsync(GPUMapModeFlags mode, optional GPUSize64 offset = 0, optional GPUSize64 size); + ArrayBuffer getMappedRange(optional GPUSize64 offset = 0, optional GPUSize64 size); + undefined unmap(); + + undefined destroy(); +}; +GPUBuffer includes GPUObjectBase; + +dictionary GPUBufferDescriptor : GPUObjectDescriptorBase { + required GPUSize64 size; + required GPUBufferUsageFlags usage; + boolean mappedAtCreation = false; +}; + +typedef [EnforceRange] unsigned long GPUBufferUsageFlags; +interface GPUBufferUsage { + const GPUFlagsConstant MAP_READ = 0x0001; + const GPUFlagsConstant MAP_WRITE = 0x0002; + const GPUFlagsConstant COPY_SRC = 0x0004; + const GPUFlagsConstant COPY_DST = 0x0008; + const GPUFlagsConstant INDEX = 0x0010; + const GPUFlagsConstant VERTEX = 0x0020; + const GPUFlagsConstant UNIFORM = 0x0040; + const GPUFlagsConstant STORAGE = 0x0080; + const GPUFlagsConstant INDIRECT = 0x0100; + const GPUFlagsConstant QUERY_RESOLVE = 0x0200; +}; + +typedef [EnforceRange] unsigned long GPUMapModeFlags; +interface GPUMapMode { + const GPUFlagsConstant READ = 0x0001; + const GPUFlagsConstant WRITE = 0x0002; +}; + +[Serializable] +interface GPUTexture { + GPUTextureView createView(optional GPUTextureViewDescriptor descriptor = {}); + + undefined destroy(); +}; +GPUTexture includes GPUObjectBase; + +dictionary GPUTextureDescriptor : GPUObjectDescriptorBase { + required GPUExtent3D size; + GPUIntegerCoordinate mipLevelCount = 1; + GPUSize32 sampleCount = 1; + GPUTextureDimension dimension = "2d"; + required GPUTextureFormat format; + required GPUTextureUsageFlags usage; +}; + +enum GPUTextureDimension { + "1d", + "2d", + "3d", +}; + +typedef [EnforceRange] unsigned long GPUTextureUsageFlags; +interface GPUTextureUsage { + const GPUFlagsConstant COPY_SRC = 0x01; + const GPUFlagsConstant COPY_DST = 0x02; + const GPUFlagsConstant SAMPLED = 0x04; + const GPUFlagsConstant STORAGE = 0x08; + const GPUFlagsConstant RENDER_ATTACHMENT = 0x10; +}; + +interface GPUTextureView { +}; +GPUTextureView includes GPUObjectBase; + +dictionary GPUTextureViewDescriptor : GPUObjectDescriptorBase { + GPUTextureFormat format; + GPUTextureViewDimension dimension; + GPUTextureAspect aspect = "all"; + GPUIntegerCoordinate baseMipLevel = 0; + GPUIntegerCoordinate mipLevelCount; + GPUIntegerCoordinate baseArrayLayer = 0; + GPUIntegerCoordinate arrayLayerCount; +}; + +enum GPUTextureViewDimension { + "1d", + "2d", + "2d-array", + "cube", + "cube-array", + "3d" +}; + +enum GPUTextureAspect { + "all", + "stencil-only", + "depth-only" +}; + +enum GPUTextureFormat { + // 8-bit formats + "r8unorm", + "r8snorm", + "r8uint", + "r8sint", + + // 16-bit formats + "r16uint", + "r16sint", + "r16float", + "rg8unorm", + "rg8snorm", + "rg8uint", + "rg8sint", + + // 32-bit formats + "r32uint", + "r32sint", + "r32float", + "rg16uint", + "rg16sint", + "rg16float", + "rgba8unorm", + "rgba8unorm-srgb", + "rgba8snorm", + "rgba8uint", + "rgba8sint", + "bgra8unorm", + "bgra8unorm-srgb", + // Packed 32-bit formats + "rgb9e5ufloat", + "rgb10a2unorm", + "rg11b10ufloat", + + // 64-bit formats + "rg32uint", + "rg32sint", + "rg32float", + "rgba16uint", + "rgba16sint", + "rgba16float", + + // 128-bit formats + "rgba32uint", + "rgba32sint", + "rgba32float", + + // Depth and stencil formats + "stencil8", + "depth16unorm", + "depth24plus", + "depth24plus-stencil8", + "depth32float", + + // BC compressed formats usable if "texture-compression-bc" is both + // supported by the device/user agent and enabled in requestDevice. + "bc1-rgba-unorm", + "bc1-rgba-unorm-srgb", + "bc2-rgba-unorm", + "bc2-rgba-unorm-srgb", + "bc3-rgba-unorm", + "bc3-rgba-unorm-srgb", + "bc4-r-unorm", + "bc4-r-snorm", + "bc5-rg-unorm", + "bc5-rg-snorm", + "bc6h-rgb-ufloat", + "bc6h-rgb-float", + "bc7-rgba-unorm", + "bc7-rgba-unorm-srgb", + + // "depth24unorm-stencil8" feature + "depth24unorm-stencil8", + + // "depth32float-stencil8" feature + "depth32float-stencil8", +}; + +interface GPUSampler { +}; +GPUSampler includes GPUObjectBase; + +dictionary GPUSamplerDescriptor : GPUObjectDescriptorBase { + GPUAddressMode addressModeU = "clamp-to-edge"; + GPUAddressMode addressModeV = "clamp-to-edge"; + GPUAddressMode addressModeW = "clamp-to-edge"; + GPUFilterMode magFilter = "nearest"; + GPUFilterMode minFilter = "nearest"; + GPUFilterMode mipmapFilter = "nearest"; + float lodMinClamp = 0; + float lodMaxClamp = 0xffffffff; // TODO: What should this be? Was Number.MAX_VALUE. + GPUCompareFunction compare; + [Clamp] unsigned short maxAnisotropy = 1; +}; + +enum GPUAddressMode { + "clamp-to-edge", + "repeat", + "mirror-repeat" +}; + +enum GPUFilterMode { + "nearest", + "linear" +}; + +enum GPUCompareFunction { + "never", + "less", + "equal", + "less-equal", + "greater", + "not-equal", + "greater-equal", + "always" +}; + +[Serializable] +interface GPUBindGroupLayout { +}; +GPUBindGroupLayout includes GPUObjectBase; + +dictionary GPUBindGroupLayoutDescriptor : GPUObjectDescriptorBase { + required sequence entries; +}; + +typedef [EnforceRange] unsigned long GPUShaderStageFlags; +interface GPUShaderStage { + const GPUFlagsConstant VERTEX = 0x1; + const GPUFlagsConstant FRAGMENT = 0x2; + const GPUFlagsConstant COMPUTE = 0x4; +}; + +dictionary GPUBindGroupLayoutEntry { + required GPUIndex32 binding; + required GPUShaderStageFlags visibility; + + GPUBufferBindingLayout buffer; + GPUSamplerBindingLayout sampler; + GPUTextureBindingLayout texture; + GPUStorageTextureBindingLayout storageTexture; +}; + +enum GPUBufferBindingType { + "uniform", + "storage", + "read-only-storage", +}; + +dictionary GPUBufferBindingLayout { + GPUBufferBindingType type = "uniform"; + boolean hasDynamicOffset = false; + GPUSize64 minBindingSize = 0; +}; + +enum GPUSamplerBindingType { + "filtering", + "non-filtering", + "comparison", +}; + +dictionary GPUSamplerBindingLayout { + GPUSamplerBindingType type = "filtering"; +}; + +enum GPUTextureSampleType { + "float", + "unfilterable-float", + "depth", + "sint", + "uint", +}; + +dictionary GPUTextureBindingLayout { + GPUTextureSampleType sampleType = "float"; + GPUTextureViewDimension viewDimension = "2d"; + boolean multisampled = false; +}; + +enum GPUStorageTextureAccess { + "read-only", + "write-only", +}; + +dictionary GPUStorageTextureBindingLayout { + required GPUStorageTextureAccess access; + required GPUTextureFormat format; + GPUTextureViewDimension viewDimension = "2d"; +}; + +interface GPUBindGroup { +}; +GPUBindGroup includes GPUObjectBase; + +dictionary GPUBindGroupDescriptor : GPUObjectDescriptorBase { + required GPUBindGroupLayout layout; + required sequence entries; +}; + +typedef (GPUSampler or GPUTextureView or GPUBufferBinding) GPUBindingResource; + +dictionary GPUBindGroupEntry { + required GPUIndex32 binding; + required GPUBindingResource resource; +}; + +dictionary GPUBufferBinding { + required GPUBuffer buffer; + GPUSize64 offset = 0; + GPUSize64 size; +}; + +[Serializable] +interface GPUPipelineLayout { +}; +GPUPipelineLayout includes GPUObjectBase; + +dictionary GPUPipelineLayoutDescriptor : GPUObjectDescriptorBase { + required sequence bindGroupLayouts; +}; + +enum GPUCompilationMessageType { + "error", + "warning", + "info" +}; + +[Serializable] +interface GPUCompilationMessage { + readonly attribute DOMString message; + readonly attribute GPUCompilationMessageType type; + readonly attribute unsigned long long lineNum; + readonly attribute unsigned long long linePos; +}; + +[Serializable] +interface GPUCompilationInfo { + readonly attribute FrozenArray messages; +}; + +[Serializable] +interface GPUShaderModule { + Promise compilationInfo(); +}; +GPUShaderModule includes GPUObjectBase; + +dictionary GPUShaderModuleDescriptor : GPUObjectDescriptorBase { + required USVString code; + object sourceMap; +}; + +dictionary GPUPipelineDescriptorBase : GPUObjectDescriptorBase { + GPUPipelineLayout layout; +}; + +interface mixin GPUPipelineBase { + GPUBindGroupLayout getBindGroupLayout(unsigned long index); +}; + +dictionary GPUProgrammableStage { + required GPUShaderModule module; + required USVString entryPoint; +}; + +[Serializable] +interface GPUComputePipeline { +}; +GPUComputePipeline includes GPUObjectBase; +GPUComputePipeline includes GPUPipelineBase; + +dictionary GPUComputePipelineDescriptor : GPUPipelineDescriptorBase { + required GPUProgrammableStage compute; +}; + +[Serializable] +interface GPURenderPipeline { +}; +GPURenderPipeline includes GPUObjectBase; +GPURenderPipeline includes GPUPipelineBase; + +dictionary GPURenderPipelineDescriptor : GPUPipelineDescriptorBase { + required GPUVertexState vertex; + GPUPrimitiveState primitive = {}; + GPUDepthStencilState depthStencil; + GPUMultisampleState multisample = {}; + GPUFragmentState fragment; +}; + +enum GPUPrimitiveTopology { + "point-list", + "line-list", + "line-strip", + "triangle-list", + "triangle-strip" +}; + +dictionary GPUPrimitiveState { + GPUPrimitiveTopology topology = "triangle-list"; + GPUIndexFormat stripIndexFormat; + GPUFrontFace frontFace = "ccw"; + GPUCullMode cullMode = "none"; +}; + +enum GPUFrontFace { + "ccw", + "cw" +}; + +enum GPUCullMode { + "none", + "front", + "back" +}; + +dictionary GPUMultisampleState { + GPUSize32 count = 1; + GPUSampleMask mask = 0xFFFFFFFF; + boolean alphaToCoverageEnabled = false; +}; + +dictionary GPUFragmentState: GPUProgrammableStage { + required sequence targets; +}; + +dictionary GPUColorTargetState { + required GPUTextureFormat format; + + GPUBlendState blend; + GPUColorWriteFlags writeMask = 0xF; // GPUColorWrite.ALL +}; + +dictionary GPUBlendState { + required GPUBlendComponent color; + required GPUBlendComponent alpha; +}; + +typedef [EnforceRange] unsigned long GPUColorWriteFlags; +interface GPUColorWrite { + const GPUFlagsConstant RED = 0x1; + const GPUFlagsConstant GREEN = 0x2; + const GPUFlagsConstant BLUE = 0x4; + const GPUFlagsConstant ALPHA = 0x8; + const GPUFlagsConstant ALL = 0xF; +}; + +dictionary GPUBlendComponent { + GPUBlendFactor srcFactor = "one"; + GPUBlendFactor dstFactor = "zero"; + GPUBlendOperation operation = "add"; +}; + +enum GPUBlendFactor { + "zero", + "one", + "src-color", + "one-minus-src-color", + "src-alpha", + "one-minus-src-alpha", + "dst-color", + "one-minus-dst-color", + "dst-alpha", + "one-minus-dst-alpha", + "src-alpha-saturated", + "blend-color", + "one-minus-blend-color" +}; + +enum GPUBlendOperation { + "add", + "subtract", + "reverse-subtract", + "min", + "max" +}; + +dictionary GPUDepthStencilState { + required GPUTextureFormat format; + + boolean depthWriteEnabled = false; + GPUCompareFunction depthCompare = "always"; + + GPUStencilFaceState stencilFront = {}; + GPUStencilFaceState stencilBack = {}; + + GPUStencilValue stencilReadMask = 0xFFFFFFFF; + GPUStencilValue stencilWriteMask = 0xFFFFFFFF; + + GPUDepthBias depthBias = 0; + float depthBiasSlopeScale = 0; + float depthBiasClamp = 0; + + // Enable depth clamping (requires "depth-clamping" feature) + boolean clampDepth = false; +}; + +dictionary GPUStencilFaceState { + GPUCompareFunction compare = "always"; + GPUStencilOperation failOp = "keep"; + GPUStencilOperation depthFailOp = "keep"; + GPUStencilOperation passOp = "keep"; +}; + +enum GPUStencilOperation { + "keep", + "zero", + "replace", + "invert", + "increment-clamp", + "decrement-clamp", + "increment-wrap", + "decrement-wrap" +}; + +enum GPUIndexFormat { + "uint16", + "uint32" +}; + +enum GPUVertexFormat { + "uchar2", + "uchar4", + "char2", + "char4", + "uchar2norm", + "uchar4norm", + "char2norm", + "char4norm", + "ushort2", + "ushort4", + "short2", + "short4", + "ushort2norm", + "ushort4norm", + "short2norm", + "short4norm", + "half2", + "half4", + "float", + "float2", + "float3", + "float4", + "uint", + "uint2", + "uint3", + "uint4", + "int", + "int2", + "int3", + "int4" +}; + +enum GPUInputStepMode { + "vertex", + "instance" +}; + +dictionary GPUVertexState: GPUProgrammableStage { + sequence buffers = []; +}; + +dictionary GPUVertexBufferLayout { + required GPUSize64 arrayStride; + GPUInputStepMode stepMode = "vertex"; + required sequence attributes; +}; + +dictionary GPUVertexAttribute { + required GPUVertexFormat format; + required GPUSize64 offset; + + required GPUIndex32 shaderLocation; +}; + +interface GPUCommandBuffer { + readonly attribute Promise executionTime; +}; +GPUCommandBuffer includes GPUObjectBase; + +dictionary GPUCommandBufferDescriptor : GPUObjectDescriptorBase { +}; + +interface GPUCommandEncoder { + GPURenderPassEncoder beginRenderPass(GPURenderPassDescriptor descriptor); + GPUComputePassEncoder beginComputePass(optional GPUComputePassDescriptor descriptor = {}); + + undefined copyBufferToBuffer( + GPUBuffer source, + GPUSize64 sourceOffset, + GPUBuffer destination, + GPUSize64 destinationOffset, + GPUSize64 size); + + undefined copyBufferToTexture( + GPUImageCopyBuffer source, + GPUImageCopyTexture destination, + GPUExtent3D copySize); + + undefined copyTextureToBuffer( + GPUImageCopyTexture source, + GPUImageCopyBuffer destination, + GPUExtent3D copySize); + + undefined copyTextureToTexture( + GPUImageCopyTexture source, + GPUImageCopyTexture destination, + GPUExtent3D copySize); + + undefined pushDebugGroup(USVString groupLabel); + undefined popDebugGroup(); + undefined insertDebugMarker(USVString markerLabel); + + undefined writeTimestamp(GPUQuerySet querySet, GPUSize32 queryIndex); + + undefined resolveQuerySet( + GPUQuerySet querySet, + GPUSize32 firstQuery, + GPUSize32 queryCount, + GPUBuffer destination, + GPUSize64 destinationOffset); + + GPUCommandBuffer finish(optional GPUCommandBufferDescriptor descriptor = {}); +}; +GPUCommandEncoder includes GPUObjectBase; + +dictionary GPUCommandEncoderDescriptor : GPUObjectDescriptorBase { + boolean measureExecutionTime = false; + + // TODO: reusability flag? +}; + +dictionary GPUImageDataLayout { + GPUSize64 offset = 0; + GPUSize32 bytesPerRow; + GPUSize32 rowsPerImage; +}; + +dictionary GPUImageCopyBuffer : GPUImageDataLayout { + required GPUBuffer buffer; +}; + +dictionary GPUImageCopyTexture { + required GPUTexture texture; + GPUIntegerCoordinate mipLevel = 0; + GPUOrigin3D origin = {}; + GPUTextureAspect aspect = "all"; +}; + +interface mixin GPUProgrammablePassEncoder { + undefined setBindGroup(GPUIndex32 index, GPUBindGroup bindGroup, + optional sequence dynamicOffsets = []); + + undefined setBindGroup(GPUIndex32 index, GPUBindGroup bindGroup, + Uint32Array dynamicOffsetsData, + GPUSize64 dynamicOffsetsDataStart, + GPUSize32 dynamicOffsetsDataLength); + + undefined pushDebugGroup(USVString groupLabel); + undefined popDebugGroup(); + undefined insertDebugMarker(USVString markerLabel); +}; + +interface GPUComputePassEncoder { + undefined setPipeline(GPUComputePipeline pipeline); + undefined dispatch(GPUSize32 x, optional GPUSize32 y = 1, optional GPUSize32 z = 1); + undefined dispatchIndirect(GPUBuffer indirectBuffer, GPUSize64 indirectOffset); + + undefined beginPipelineStatisticsQuery(GPUQuerySet querySet, GPUSize32 queryIndex); + undefined endPipelineStatisticsQuery(); + + undefined writeTimestamp(GPUQuerySet querySet, GPUSize32 queryIndex); + + undefined endPass(); +}; +GPUComputePassEncoder includes GPUObjectBase; +GPUComputePassEncoder includes GPUProgrammablePassEncoder; + +dictionary GPUComputePassDescriptor : GPUObjectDescriptorBase { +}; + +interface mixin GPURenderEncoderBase { + undefined setPipeline(GPURenderPipeline pipeline); + + undefined setIndexBuffer(GPUBuffer buffer, GPUIndexFormat indexFormat, optional GPUSize64 offset = 0, optional GPUSize64 size = 0); + undefined setVertexBuffer(GPUIndex32 slot, GPUBuffer buffer, optional GPUSize64 offset = 0, optional GPUSize64 size = 0); + + undefined draw(GPUSize32 vertexCount, optional GPUSize32 instanceCount = 1, + optional GPUSize32 firstVertex = 0, optional GPUSize32 firstInstance = 0); + undefined drawIndexed(GPUSize32 indexCount, optional GPUSize32 instanceCount = 1, + optional GPUSize32 firstIndex = 0, + optional GPUSignedOffset32 baseVertex = 0, + optional GPUSize32 firstInstance = 0); + + undefined drawIndirect(GPUBuffer indirectBuffer, GPUSize64 indirectOffset); + undefined drawIndexedIndirect(GPUBuffer indirectBuffer, GPUSize64 indirectOffset); +}; + +interface GPURenderPassEncoder { + undefined setViewport(float x, float y, + float width, float height, + float minDepth, float maxDepth); + + undefined setScissorRect(GPUIntegerCoordinate x, GPUIntegerCoordinate y, + GPUIntegerCoordinate width, GPUIntegerCoordinate height); + + undefined setBlendColor(GPUColor color); + undefined setStencilReference(GPUStencilValue reference); + + undefined beginOcclusionQuery(GPUSize32 queryIndex); + undefined endOcclusionQuery(); + + undefined beginPipelineStatisticsQuery(GPUQuerySet querySet, GPUSize32 queryIndex); + undefined endPipelineStatisticsQuery(); + + undefined writeTimestamp(GPUQuerySet querySet, GPUSize32 queryIndex); + + undefined executeBundles(sequence bundles); + undefined endPass(); +}; +GPURenderPassEncoder includes GPUObjectBase; +GPURenderPassEncoder includes GPUProgrammablePassEncoder; +GPURenderPassEncoder includes GPURenderEncoderBase; + +dictionary GPURenderPassDescriptor : GPUObjectDescriptorBase { + required sequence colorAttachments; + GPURenderPassDepthStencilAttachment depthStencilAttachment; + GPUQuerySet occlusionQuerySet; +}; + +dictionary GPURenderPassColorAttachment { + required GPUTextureView view; + GPUTextureView resolveTarget; + + required (GPULoadOp or GPUColor) loadValue; + GPUStoreOp storeOp = "store"; +}; + +dictionary GPURenderPassDepthStencilAttachment { + required GPUTextureView view; + + required (GPULoadOp or float) depthLoadValue; + required GPUStoreOp depthStoreOp; + boolean depthReadOnly = false; + + required (GPULoadOp or GPUStencilValue) stencilLoadValue; + required GPUStoreOp stencilStoreOp; + boolean stencilReadOnly = false; +}; + +enum GPULoadOp { + "load" +}; + +enum GPUStoreOp { + "store", + "clear" +}; + +interface GPURenderBundle { +}; +GPURenderBundle includes GPUObjectBase; + +dictionary GPURenderBundleDescriptor : GPUObjectDescriptorBase { +}; + +interface GPURenderBundleEncoder { + GPURenderBundle finish(optional GPURenderBundleDescriptor descriptor = {}); +}; +GPURenderBundleEncoder includes GPUObjectBase; +GPURenderBundleEncoder includes GPUProgrammablePassEncoder; +GPURenderBundleEncoder includes GPURenderEncoderBase; + +dictionary GPURenderBundleEncoderDescriptor : GPUObjectDescriptorBase { + required sequence colorFormats; + GPUTextureFormat depthStencilFormat; + GPUSize32 sampleCount = 1; +}; + +interface GPUQueue { + undefined submit(sequence commandBuffers); + + Promise onSubmittedWorkDone(); + + undefined writeBuffer( + GPUBuffer buffer, + GPUSize64 bufferOffset, + [AllowShared] BufferSource data, + optional GPUSize64 dataOffset = 0, + optional GPUSize64 size); + + undefined writeTexture( + GPUImageCopyTexture destination, + [AllowShared] BufferSource data, + GPUImageDataLayout dataLayout, + GPUExtent3D size); +}; +GPUQueue includes GPUObjectBase; + +interface GPUQuerySet { + undefined destroy(); +}; +GPUQuerySet includes GPUObjectBase; + +dictionary GPUQuerySetDescriptor : GPUObjectDescriptorBase { + required GPUQueryType type; + required GPUSize32 count; + sequence pipelineStatistics = []; +}; + +enum GPUQueryType { + "occlusion", + "pipeline-statistics", + "timestamp" +}; + +enum GPUPipelineStatisticName { + "vertex-shader-invocations", + "clipper-invocations", + "clipper-primitives-out", + "fragment-shader-invocations", + "compute-shader-invocations" +}; + +enum GPUDeviceLostReason { + "destroyed", +}; + +interface GPUDeviceLostInfo { + readonly attribute (GPUDeviceLostReason or undefined) reason; + readonly attribute DOMString message; +}; + +partial interface GPUDevice { + readonly attribute Promise lost; +}; + +enum GPUErrorFilter { + "out-of-memory", + "validation" +}; + +interface GPUOutOfMemoryError { + constructor(); +}; + +interface GPUValidationError { + constructor(DOMString message); + readonly attribute DOMString message; +}; + +typedef (GPUOutOfMemoryError or GPUValidationError) GPUError; + +partial interface GPUDevice { + undefined pushErrorScope(GPUErrorFilter filter); + Promise popErrorScope(); +}; + +[ + Exposed=(Window, DedicatedWorker) +] +interface GPUUncapturedErrorEvent : Event { + constructor( + DOMString type, + GPUUncapturedErrorEventInit gpuUncapturedErrorEventInitDict + ); + [SameObject] readonly attribute GPUError error; +}; + +dictionary GPUUncapturedErrorEventInit : EventInit { + required GPUError error; +}; + +partial interface GPUDevice { + [Exposed=(Window, DedicatedWorker)] + attribute EventHandler onuncapturederror; +}; + +typedef [EnforceRange] unsigned long GPUBufferDynamicOffset; +typedef [EnforceRange] unsigned long GPUStencilValue; +typedef [EnforceRange] unsigned long GPUSampleMask; +typedef [EnforceRange] long GPUDepthBias; + +typedef [EnforceRange] unsigned long long GPUSize64; +typedef [EnforceRange] unsigned long GPUIntegerCoordinate; +typedef [EnforceRange] unsigned long GPUIndex32; +typedef [EnforceRange] unsigned long GPUSize32; +typedef [EnforceRange] long GPUSignedOffset32; + +typedef unsigned long GPUFlagsConstant; + +dictionary GPUColorDict { + required double r; + required double g; + required double b; + required double a; +}; +typedef (sequence or GPUColorDict) GPUColor; + +dictionary GPUOrigin2DDict { + GPUIntegerCoordinate x = 0; + GPUIntegerCoordinate y = 0; +}; +typedef (sequence or GPUOrigin2DDict) GPUOrigin2D; + +dictionary GPUOrigin3DDict { + GPUIntegerCoordinate x = 0; + GPUIntegerCoordinate y = 0; + GPUIntegerCoordinate z = 0; +}; +typedef (sequence or GPUOrigin3DDict) GPUOrigin3D; + +dictionary GPUExtent3DDict { + GPUIntegerCoordinate width = 1; + GPUIntegerCoordinate height = 1; + GPUIntegerCoordinate depthOrArrayLayers = 1; +}; +typedef (sequence or GPUExtent3DDict) GPUExtent3D; diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index cdae5a40ccae6f..30912c07827a4e 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -23,6 +23,7 @@ deno_crypto = { path = "../op_crates/crypto", version = "0.13.0" } deno_fetch = { path = "../op_crates/fetch", version = "0.22.0" } deno_web = { path = "../op_crates/web", version = "0.30.0" } deno_websocket = { path = "../op_crates/websocket", version = "0.5.0" } +deno_webgpu = { path = "../op_crates/webgpu", version = "0.1.0" } [target.'cfg(windows)'.build-dependencies] winres = "0.1.11" @@ -34,6 +35,7 @@ deno_crypto = { path = "../op_crates/crypto", version = "0.13.0" } deno_fetch = { path = "../op_crates/fetch", version = "0.22.0" } deno_web = { path = "../op_crates/web", version = "0.30.0" } deno_websocket = { path = "../op_crates/websocket", version = "0.5.0" } +deno_webgpu = { path = "../op_crates/webgpu", version = "0.1.0" } atty = "0.2.14" dlopen = "0.1.8" diff --git a/runtime/build.rs b/runtime/build.rs index 5a99b86c8078aa..2481485684b762 100644 --- a/runtime/build.rs +++ b/runtime/build.rs @@ -17,6 +17,7 @@ fn create_snapshot( deno_fetch::init(&mut js_runtime); deno_websocket::init(&mut js_runtime); deno_crypto::init(&mut js_runtime); + deno_webgpu::init(&mut js_runtime); // TODO(nayeemrmn): https://github.com/rust-lang/cargo/issues/3946 to get the // workspace root. let display_root = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap(); diff --git a/runtime/errors.rs b/runtime/errors.rs index b3d4745043007f..a8152b0758d1fd 100644 --- a/runtime/errors.rs +++ b/runtime/errors.rs @@ -151,6 +151,7 @@ fn get_nix_error_class(error: &nix::Error) -> &'static str { pub fn get_error_class_name(e: &AnyError) -> Option<&'static str> { deno_core::error::get_custom_error_class(e) + .or_else(|| deno_webgpu::error::get_error_class_name(e)) .or_else(|| { e.downcast_ref::() .map(get_dlopen_error_class) diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js index 48508081289821..811412049aa0bc 100644 --- a/runtime/js/99_main.js +++ b/runtime/js/99_main.js @@ -27,6 +27,7 @@ delete Object.prototype.__proto__; const headers = window.__bootstrap.headers; const streams = window.__bootstrap.streams; const fileReader = window.__bootstrap.fileReader; + const webgpu = window.__bootstrap.webgpu; const webSocket = window.__bootstrap.webSocket; const file = window.__bootstrap.file; const fetch = window.__bootstrap.fetch; @@ -195,6 +196,11 @@ delete Object.prototype.__proto__; core.registerErrorClass("SyntaxError", SyntaxError); core.registerErrorClass("TypeError", TypeError); core.registerErrorClass("URIError", URIError); + core.registerErrorClass( + "DOMExceptionOperationError", + DOMException, + "OperationError", + ); } // https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope @@ -249,6 +255,37 @@ delete Object.prototype.__proto__; performance: util.writable(performance.performance), setInterval: util.writable(timers.setInterval), setTimeout: util.writable(timers.setTimeout), + + GPU: util.nonEnumerable(webgpu.GPU), + GPUAdapter: util.nonEnumerable(webgpu.GPUAdapter), + GPUAdapterLimits: util.nonEnumerable(webgpu.GPUAdapterLimits), + GPUAdapterFeatures: util.nonEnumerable(webgpu.GPUAdapterFeatures), + GPUDevice: util.nonEnumerable(webgpu.GPUDevice), + GPUQueue: util.nonEnumerable(webgpu.GPUQueue), + GPUBuffer: util.nonEnumerable(webgpu.GPUBuffer), + GPUBufferUsage: util.nonEnumerable(webgpu.GPUBufferUsage), + GPUMapMode: util.nonEnumerable(webgpu.GPUMapMode), + GPUTexture: util.nonEnumerable(webgpu.GPUTexture), + GPUTextureUsage: util.nonEnumerable(webgpu.GPUTextureUsage), + GPUTextureView: util.nonEnumerable(webgpu.GPUTextureView), + GPUSampler: util.nonEnumerable(webgpu.GPUSampler), + GPUBindGroupLayout: util.nonEnumerable(webgpu.GPUBindGroupLayout), + GPUPipelineLayout: util.nonEnumerable(webgpu.GPUPipelineLayout), + GPUBindGroup: util.nonEnumerable(webgpu.GPUBindGroup), + GPUShaderModule: util.nonEnumerable(webgpu.GPUShaderModule), + GPUShaderStage: util.nonEnumerable(webgpu.GPUShaderStage), + GPUComputePipeline: util.nonEnumerable(webgpu.GPUComputePipeline), + GPURenderPipeline: util.nonEnumerable(webgpu.GPURenderPipeline), + GPUColorWrite: util.nonEnumerable(webgpu.GPUColorWrite), + GPUCommandEncoder: util.nonEnumerable(webgpu.GPUCommandEncoder), + GPURenderPassEncoder: util.nonEnumerable(webgpu.GPURenderPassEncoder), + GPUComputePassEncoder: util.nonEnumerable(webgpu.GPUComputePassEncoder), + GPUCommandBuffer: util.nonEnumerable(webgpu.GPUCommandBuffer), + GPURenderBundleEncoder: util.nonEnumerable(webgpu.GPURenderBundleEncoder), + GPURenderBundle: util.nonEnumerable(webgpu.GPURenderBundle), + GPUQuerySet: util.nonEnumerable(webgpu.GPUQuerySet), + GPUOutOfMemoryError: util.nonEnumerable(webgpu.GPUOutOfMemoryError), + GPUValidationError: util.nonEnumerable(webgpu.GPUValidationError), }; // The console seems to be the only one that should be writable and non-enumerable @@ -256,12 +293,17 @@ delete Object.prototype.__proto__; // structure, it might be worth it to define a helper in `util` windowOrWorkerGlobalScope.console.enumerable = false; + const windowNavigatorProperties = { + gpu: webgpu.gpu, + }; + const mainRuntimeGlobalProperties = { Location: location.locationConstructorDescriptor, location: location.locationDescriptor, Window: globalInterfaces.windowConstructorDescriptor, window: util.readOnly(globalThis), self: util.readOnly(globalThis), + navigator: util.readOnly(windowNavigatorProperties), // TODO(bartlomieju): from MDN docs (https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope) // it seems those two properties should be available to workers as well onload: util.writable(null), @@ -273,12 +315,17 @@ delete Object.prototype.__proto__; prompt: util.writable(prompt.prompt), }; + const workerNavigatorProperties = { + gpu: webgpu.gpu, + }; + const workerRuntimeGlobalProperties = { WorkerLocation: location.workerLocationConstructorDescriptor, location: location.workerLocationDescriptor, WorkerGlobalScope: globalInterfaces.workerGlobalScopeConstructorDescriptor, DedicatedWorkerGlobalScope: globalInterfaces.dedicatedWorkerGlobalScopeConstructorDescriptor, + navigator: util.readOnly(workerNavigatorProperties), self: util.readOnly(globalThis), onmessage: util.writable(onmessage), onerror: util.writable(onerror), diff --git a/runtime/lib.rs b/runtime/lib.rs index 0d2179494870c7..c523b24b7abf71 100644 --- a/runtime/lib.rs +++ b/runtime/lib.rs @@ -10,6 +10,7 @@ extern crate log; pub use deno_crypto; pub use deno_fetch; pub use deno_web; +pub use deno_webgpu; pub use deno_websocket; pub mod colors; diff --git a/runtime/ops/mod.rs b/runtime/ops/mod.rs index e5dba723a8b24e..0ef04ff3cf8de9 100644 --- a/runtime/ops/mod.rs +++ b/runtime/ops/mod.rs @@ -21,6 +21,7 @@ pub mod timers; pub mod tls; pub mod tty; pub mod web_worker; +pub mod webgpu; pub mod websocket; pub mod worker_host; diff --git a/runtime/ops/webgpu.rs b/runtime/ops/webgpu.rs new file mode 100644 index 00000000000000..d3b9cfa19c5800 --- /dev/null +++ b/runtime/ops/webgpu.rs @@ -0,0 +1,421 @@ +use deno_webgpu::*; + +pub fn init(rt: &mut deno_core::JsRuntime) { + { + let op_state = rt.op_state(); + let mut state = op_state.borrow_mut(); + state.put(wgpu_core::hub::Global::new( + "webgpu", + wgpu_core::hub::IdentityManagerFactory, + wgpu_types::BackendBit::PRIMARY, + )); + let unstable_checker = state.borrow::(); + let unstable = unstable_checker.unstable; + state.put(Unstable(unstable)); + } + + super::reg_json_async( + rt, + "op_webgpu_request_adapter", + op_webgpu_request_adapter, + ); + super::reg_json_async( + rt, + "op_webgpu_request_device", + op_webgpu_request_device, + ); + super::reg_json_sync( + rt, + "op_webgpu_create_query_set", + op_webgpu_create_query_set, + ); + + { + // buffer + super::reg_json_sync( + rt, + "op_webgpu_create_buffer", + buffer::op_webgpu_create_buffer, + ); + super::reg_json_async( + rt, + "op_webgpu_buffer_get_map_async", + buffer::op_webgpu_buffer_get_map_async, + ); + super::reg_json_sync( + rt, + "op_webgpu_buffer_get_mapped_range", + buffer::op_webgpu_buffer_get_mapped_range, + ); + super::reg_json_sync( + rt, + "op_webgpu_buffer_unmap", + buffer::op_webgpu_buffer_unmap, + ); + } + { + // texture + super::reg_json_sync( + rt, + "op_webgpu_create_texture", + texture::op_webgpu_create_texture, + ); + super::reg_json_sync( + rt, + "op_webgpu_create_texture_view", + texture::op_webgpu_create_texture_view, + ); + } + { + // sampler + super::reg_json_sync( + rt, + "op_webgpu_create_sampler", + sampler::op_webgpu_create_sampler, + ); + } + { + // binding + super::reg_json_sync( + rt, + "op_webgpu_create_bind_group_layout", + binding::op_webgpu_create_bind_group_layout, + ); + super::reg_json_sync( + rt, + "op_webgpu_create_pipeline_layout", + binding::op_webgpu_create_pipeline_layout, + ); + super::reg_json_sync( + rt, + "op_webgpu_create_bind_group", + binding::op_webgpu_create_bind_group, + ); + } + { + // pipeline + super::reg_json_sync( + rt, + "op_webgpu_create_compute_pipeline", + pipeline::op_webgpu_create_compute_pipeline, + ); + super::reg_json_sync( + rt, + "op_webgpu_compute_pipeline_get_bind_group_layout", + pipeline::op_webgpu_compute_pipeline_get_bind_group_layout, + ); + super::reg_json_sync( + rt, + "op_webgpu_create_render_pipeline", + pipeline::op_webgpu_create_render_pipeline, + ); + super::reg_json_sync( + rt, + "op_webgpu_render_pipeline_get_bind_group_layout", + pipeline::op_webgpu_render_pipeline_get_bind_group_layout, + ); + } + { + // command_encoder + super::reg_json_sync( + rt, + "op_webgpu_create_command_encoder", + command_encoder::op_webgpu_create_command_encoder, + ); + super::reg_json_sync( + rt, + "op_webgpu_command_encoder_begin_render_pass", + command_encoder::op_webgpu_command_encoder_begin_render_pass, + ); + super::reg_json_sync( + rt, + "op_webgpu_command_encoder_begin_compute_pass", + command_encoder::op_webgpu_command_encoder_begin_compute_pass, + ); + super::reg_json_sync( + rt, + "op_webgpu_command_encoder_copy_buffer_to_buffer", + command_encoder::op_webgpu_command_encoder_copy_buffer_to_buffer, + ); + super::reg_json_sync( + rt, + "op_webgpu_command_encoder_copy_buffer_to_texture", + command_encoder::op_webgpu_command_encoder_copy_buffer_to_texture, + ); + super::reg_json_sync( + rt, + "op_webgpu_command_encoder_copy_texture_to_buffer", + command_encoder::op_webgpu_command_encoder_copy_texture_to_buffer, + ); + super::reg_json_sync( + rt, + "op_webgpu_command_encoder_copy_texture_to_texture", + command_encoder::op_webgpu_command_encoder_copy_texture_to_texture, + ); + super::reg_json_sync( + rt, + "op_webgpu_command_encoder_push_debug_group", + command_encoder::op_webgpu_command_encoder_push_debug_group, + ); + super::reg_json_sync( + rt, + "op_webgpu_command_encoder_pop_debug_group", + command_encoder::op_webgpu_command_encoder_pop_debug_group, + ); + super::reg_json_sync( + rt, + "op_webgpu_command_encoder_insert_debug_marker", + command_encoder::op_webgpu_command_encoder_insert_debug_marker, + ); + super::reg_json_sync( + rt, + "op_webgpu_command_encoder_write_timestamp", + command_encoder::op_webgpu_command_encoder_write_timestamp, + ); + super::reg_json_sync( + rt, + "op_webgpu_command_encoder_resolve_query_set", + command_encoder::op_webgpu_command_encoder_resolve_query_set, + ); + super::reg_json_sync( + rt, + "op_webgpu_command_encoder_finish", + command_encoder::op_webgpu_command_encoder_finish, + ); + } + { + // render_pass + super::reg_json_sync( + rt, + "op_webgpu_render_pass_set_viewport", + render_pass::op_webgpu_render_pass_set_viewport, + ); + super::reg_json_sync( + rt, + "op_webgpu_render_pass_set_scissor_rect", + render_pass::op_webgpu_render_pass_set_scissor_rect, + ); + super::reg_json_sync( + rt, + "op_webgpu_render_pass_set_blend_color", + render_pass::op_webgpu_render_pass_set_blend_color, + ); + super::reg_json_sync( + rt, + "op_webgpu_render_pass_set_stencil_reference", + render_pass::op_webgpu_render_pass_set_stencil_reference, + ); + super::reg_json_sync( + rt, + "op_webgpu_render_pass_begin_pipeline_statistics_query", + render_pass::op_webgpu_render_pass_begin_pipeline_statistics_query, + ); + super::reg_json_sync( + rt, + "op_webgpu_render_pass_end_pipeline_statistics_query", + render_pass::op_webgpu_render_pass_end_pipeline_statistics_query, + ); + super::reg_json_sync( + rt, + "op_webgpu_render_pass_write_timestamp", + render_pass::op_webgpu_render_pass_write_timestamp, + ); + super::reg_json_sync( + rt, + "op_webgpu_render_pass_execute_bundles", + render_pass::op_webgpu_render_pass_execute_bundles, + ); + super::reg_json_sync( + rt, + "op_webgpu_render_pass_end_pass", + render_pass::op_webgpu_render_pass_end_pass, + ); + super::reg_json_sync( + rt, + "op_webgpu_render_pass_set_bind_group", + render_pass::op_webgpu_render_pass_set_bind_group, + ); + super::reg_json_sync( + rt, + "op_webgpu_render_pass_push_debug_group", + render_pass::op_webgpu_render_pass_push_debug_group, + ); + super::reg_json_sync( + rt, + "op_webgpu_render_pass_pop_debug_group", + render_pass::op_webgpu_render_pass_pop_debug_group, + ); + super::reg_json_sync( + rt, + "op_webgpu_render_pass_insert_debug_marker", + render_pass::op_webgpu_render_pass_insert_debug_marker, + ); + super::reg_json_sync( + rt, + "op_webgpu_render_pass_set_pipeline", + render_pass::op_webgpu_render_pass_set_pipeline, + ); + super::reg_json_sync( + rt, + "op_webgpu_render_pass_set_index_buffer", + render_pass::op_webgpu_render_pass_set_index_buffer, + ); + super::reg_json_sync( + rt, + "op_webgpu_render_pass_set_vertex_buffer", + render_pass::op_webgpu_render_pass_set_vertex_buffer, + ); + super::reg_json_sync( + rt, + "op_webgpu_render_pass_draw", + render_pass::op_webgpu_render_pass_draw, + ); + super::reg_json_sync( + rt, + "op_webgpu_render_pass_draw_indexed", + render_pass::op_webgpu_render_pass_draw_indexed, + ); + super::reg_json_sync( + rt, + "op_webgpu_render_pass_draw_indirect", + render_pass::op_webgpu_render_pass_draw_indirect, + ); + super::reg_json_sync( + rt, + "op_webgpu_render_pass_draw_indexed_indirect", + render_pass::op_webgpu_render_pass_draw_indexed_indirect, + ); + } + { + // compute_pass + super::reg_json_sync( + rt, + "op_webgpu_compute_pass_set_pipeline", + compute_pass::op_webgpu_compute_pass_set_pipeline, + ); + super::reg_json_sync( + rt, + "op_webgpu_compute_pass_dispatch", + compute_pass::op_webgpu_compute_pass_dispatch, + ); + super::reg_json_sync( + rt, + "op_webgpu_compute_pass_dispatch_indirect", + compute_pass::op_webgpu_compute_pass_dispatch_indirect, + ); + super::reg_json_sync( + rt, + "op_webgpu_compute_pass_end_pass", + compute_pass::op_webgpu_compute_pass_end_pass, + ); + super::reg_json_sync( + rt, + "op_webgpu_compute_pass_set_bind_group", + compute_pass::op_webgpu_compute_pass_set_bind_group, + ); + super::reg_json_sync( + rt, + "op_webgpu_compute_pass_push_debug_group", + compute_pass::op_webgpu_compute_pass_push_debug_group, + ); + super::reg_json_sync( + rt, + "op_webgpu_compute_pass_pop_debug_group", + compute_pass::op_webgpu_compute_pass_pop_debug_group, + ); + super::reg_json_sync( + rt, + "op_webgpu_compute_pass_insert_debug_marker", + compute_pass::op_webgpu_compute_pass_insert_debug_marker, + ); + } + { + // bundle + super::reg_json_sync( + rt, + "op_webgpu_create_render_bundle_encoder", + bundle::op_webgpu_create_render_bundle_encoder, + ); + super::reg_json_sync( + rt, + "op_webgpu_render_bundle_encoder_finish", + bundle::op_webgpu_render_bundle_encoder_finish, + ); + super::reg_json_sync( + rt, + "op_webgpu_render_bundle_encoder_set_bind_group", + bundle::op_webgpu_render_bundle_encoder_set_bind_group, + ); + super::reg_json_sync( + rt, + "op_webgpu_render_bundle_encoder_push_debug_group", + bundle::op_webgpu_render_bundle_encoder_push_debug_group, + ); + super::reg_json_sync( + rt, + "op_webgpu_render_bundle_encoder_pop_debug_group", + bundle::op_webgpu_render_bundle_encoder_pop_debug_group, + ); + super::reg_json_sync( + rt, + "op_webgpu_render_bundle_encoder_insert_debug_marker", + bundle::op_webgpu_render_bundle_encoder_insert_debug_marker, + ); + super::reg_json_sync( + rt, + "op_webgpu_render_bundle_encoder_set_pipeline", + bundle::op_webgpu_render_bundle_encoder_set_pipeline, + ); + super::reg_json_sync( + rt, + "op_webgpu_render_bundle_encoder_set_index_buffer", + bundle::op_webgpu_render_bundle_encoder_set_index_buffer, + ); + super::reg_json_sync( + rt, + "op_webgpu_render_bundle_encoder_set_vertex_buffer", + bundle::op_webgpu_render_bundle_encoder_set_vertex_buffer, + ); + super::reg_json_sync( + rt, + "op_webgpu_render_bundle_encoder_draw", + bundle::op_webgpu_render_bundle_encoder_draw, + ); + super::reg_json_sync( + rt, + "op_webgpu_render_bundle_encoder_draw_indexed", + bundle::op_webgpu_render_bundle_encoder_draw_indexed, + ); + super::reg_json_sync( + rt, + "op_webgpu_render_bundle_encoder_draw_indirect", + bundle::op_webgpu_render_bundle_encoder_draw_indirect, + ); + } + { + // queue + super::reg_json_sync( + rt, + "op_webgpu_queue_submit", + queue::op_webgpu_queue_submit, + ); + super::reg_json_sync( + rt, + "op_webgpu_write_buffer", + queue::op_webgpu_write_buffer, + ); + super::reg_json_sync( + rt, + "op_webgpu_write_texture", + queue::op_webgpu_write_texture, + ); + } + { + // shader + super::reg_json_sync( + rt, + "op_webgpu_create_shader_module", + shader::op_webgpu_create_shader_module, + ); + } +} diff --git a/runtime/web_worker.rs b/runtime/web_worker.rs index 082cf7267af1ec..7f258b3eb994e8 100644 --- a/runtime/web_worker.rs +++ b/runtime/web_worker.rs @@ -237,6 +237,7 @@ impl WebWorker { deno_web::op_domain_to_ascii, ); ops::io::init(js_runtime); + ops::webgpu::init(js_runtime); ops::websocket::init( js_runtime, options.user_agent.clone(), diff --git a/runtime/worker.rs b/runtime/worker.rs index 0b3fe3e10f652d..be6951f5112e97 100644 --- a/runtime/worker.rs +++ b/runtime/worker.rs @@ -142,6 +142,7 @@ impl MainWorker { ops::signal::init(js_runtime); ops::tls::init(js_runtime); ops::tty::init(js_runtime); + ops::webgpu::init(js_runtime); ops::websocket::init( js_runtime, options.user_agent.clone(),