1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-01-21 04:52:26 -05:00
This commit is contained in:
Leo Kettmeir 2025-01-20 14:00:57 +01:00 committed by GitHub
commit ec4b7e5d92
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 6619 additions and 11247 deletions

225
Cargo.lock generated
View file

@ -212,11 +212,11 @@ dependencies = [
[[package]]
name = "ash"
version = "0.37.3+1.3.251"
version = "0.38.0+1.3.281"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a"
checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f"
dependencies = [
"libloading 0.7.4",
"libloading 0.8.5",
]
[[package]]
@ -231,7 +231,7 @@ dependencies = [
"nom 7.1.3",
"num-traits",
"rusticata-macros",
"thiserror 1.0.64",
"thiserror 1.0.69",
"time",
]
@ -525,7 +525,16 @@ version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
dependencies = [
"bit-vec",
"bit-vec 0.6.3",
]
[[package]]
name = "bit-set"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
dependencies = [
"bit-vec 0.8.0",
]
[[package]]
@ -534,6 +543,12 @@ version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
[[package]]
name = "bit-vec"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
[[package]]
name = "bitflags"
version = "1.3.2"
@ -646,9 +661,9 @@ dependencies = [
[[package]]
name = "bytemuck"
version = "1.15.0"
version = "1.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15"
checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3"
[[package]]
name = "byteorder"
@ -1157,17 +1172,6 @@ dependencies = [
"syn 2.0.87",
]
[[package]]
name = "d3d12"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b28bfe653d79bd16c77f659305b195b82bb5ce0c0eb2a4846b82ddbd77586813"
dependencies = [
"bitflags 2.6.0",
"libloading 0.8.5",
"winapi",
]
[[package]]
name = "darling"
version = "0.20.10"
@ -1482,7 +1486,7 @@ dependencies = [
"serde_json",
"sha2",
"sys_traits",
"thiserror 1.0.64",
"thiserror 1.0.69",
"url",
]
@ -1535,14 +1539,12 @@ dependencies = [
[[package]]
name = "deno_core"
version = "0.331.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce2d1779358cad2bc56d71176298767be628d707bb75585f6f8a4be2da8ccda1"
dependencies = [
"anyhow",
"az",
"bincode",
"bit-set",
"bit-vec",
"bit-set 0.5.3",
"bit-vec 0.6.3",
"bytes",
"capacity_builder 0.1.3",
"cooked-waker",
@ -2163,8 +2165,6 @@ dependencies = [
[[package]]
name = "deno_ops"
version = "0.207.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96f000a21f6969b4c945bc8e9e785aa439f11ca4fd3fbddcd5bebc102167eb37"
dependencies = [
"indexmap 2.3.0",
"proc-macro-rules",
@ -2409,7 +2409,7 @@ dependencies = [
"nix",
"os_pipe",
"path-dedot",
"thiserror 1.0.64",
"thiserror 1.0.69",
"tokio",
"windows-sys 0.59.0",
]
@ -2550,8 +2550,10 @@ version = "0.153.0"
dependencies = [
"deno_core",
"deno_error",
"indexmap 2.3.0",
"raw-window-handle",
"serde",
"serde_json",
"thiserror 2.0.3",
"tokio",
"wgpu-core",
@ -2918,9 +2920,9 @@ dependencies = [
[[package]]
name = "document-features"
version = "0.2.8"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef5282ad69563b5fc40319526ba27e0e7363d552a896f0297d54f767717f9b95"
checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0"
dependencies = [
"litrs",
]
@ -3157,7 +3159,7 @@ dependencies = [
"debug-ignore",
"indexmap 2.3.0",
"log",
"thiserror 1.0.64",
"thiserror 1.0.69",
"zerocopy",
]
@ -3283,7 +3285,7 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0678ab2d46fa5195aaf59ad034c083d351377d4af57f3e073c074d0da3e3c766"
dependencies = [
"bit-set",
"bit-set 0.5.3",
"regex",
]
@ -3296,7 +3298,7 @@ dependencies = [
"anyhow",
"async-trait",
"log",
"thiserror 1.0.64",
"thiserror 1.0.69",
"tokio",
"tokio-stream",
]
@ -3331,7 +3333,7 @@ dependencies = [
"rand",
"sha1",
"simdutf8",
"thiserror 1.0.64",
"thiserror 1.0.69",
"tokio",
"utf-8",
]
@ -3389,7 +3391,7 @@ dependencies = [
"deno_terminal 0.1.1",
"parking_lot",
"regex",
"thiserror 1.0.64",
"thiserror 1.0.69",
]
[[package]]
@ -3738,9 +3740,9 @@ dependencies = [
[[package]]
name = "glow"
version = "0.13.1"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1"
checksum = "d51fa363f025f5c111e03f13eda21162faeacb6911fe8caa0c0349f9cf0c4483"
dependencies = [
"js-sys",
"slotmap",
@ -3750,9 +3752,9 @@ dependencies = [
[[package]]
name = "glutin_wgl_sys"
version = "0.5.0"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8098adac955faa2d31079b65dc48841251f69efd3ac25477903fc424362ead"
checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e"
dependencies = [
"gl_generator",
]
@ -3776,6 +3778,18 @@ dependencies = [
"bitflags 2.6.0",
]
[[package]]
name = "gpu-allocator"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd"
dependencies = [
"log",
"presser",
"thiserror 1.0.69",
"windows 0.58.0",
]
[[package]]
name = "gpu-descriptor"
version = "0.3.0"
@ -3876,7 +3890,7 @@ dependencies = [
"pest_derive",
"serde",
"serde_json",
"thiserror 1.0.64",
"thiserror 1.0.69",
]
[[package]]
@ -4647,10 +4661,11 @@ dependencies = [
[[package]]
name = "js-sys"
version = "0.3.69"
version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
dependencies = [
"once_cell",
"wasm-bindgen",
]
@ -4682,7 +4697,7 @@ dependencies = [
"anyhow",
"serde",
"serde_json",
"thiserror 1.0.64",
"thiserror 1.0.69",
"uuid",
]
@ -5056,9 +5071,9 @@ dependencies = [
[[package]]
name = "metal"
version = "0.28.0"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5637e166ea14be6063a3f8ba5ccb9a4159df7d8f6d61c02fc3d480b1f90dcfcb"
checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21"
dependencies = [
"bitflags 2.6.0",
"block",
@ -5124,7 +5139,7 @@ dependencies = [
"rustc_version 0.4.0",
"smallvec",
"tagptr",
"thiserror 1.0.64",
"thiserror 1.0.69",
"uuid",
]
@ -5142,23 +5157,23 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
[[package]]
name = "naga"
version = "0.20.0"
version = "23.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e536ae46fcab0876853bd4a632ede5df4b1c2527a58f6c5a4150fe86be858231"
checksum = "364f94bc34f61332abebe8cad6f6cd82a5b65cff22c828d05d0968911462ca4f"
dependencies = [
"arrayvec",
"bit-set",
"bit-set 0.8.0",
"bitflags 2.6.0",
"cfg_aliases 0.1.1",
"codespan-reporting",
"hexf-parse",
"indexmap 2.3.0",
"log",
"num-traits",
"rustc-hash 1.1.0",
"serde",
"spirv",
"termcolor",
"thiserror 1.0.64",
"thiserror 1.0.69",
"unicode-xid",
]
@ -5457,7 +5472,7 @@ dependencies = [
"js-sys",
"once_cell",
"pin-project-lite",
"thiserror 1.0.64",
"thiserror 1.0.69",
]
[[package]]
@ -5487,7 +5502,7 @@ dependencies = [
"opentelemetry_sdk",
"prost",
"serde_json",
"thiserror 1.0.64",
"thiserror 1.0.69",
"tokio",
"tonic",
"tracing",
@ -5529,7 +5544,7 @@ dependencies = [
"percent-encoding",
"rand",
"serde_json",
"thiserror 1.0.64",
"thiserror 1.0.69",
"tracing",
]
@ -5719,7 +5734,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "311fb059dee1a7b802f036316d790138c613a4e8b180c822e3925a662e9f0c95"
dependencies = [
"memchr",
"thiserror 1.0.64",
"thiserror 1.0.69",
"ucd-trie",
]
@ -5938,6 +5953,12 @@ dependencies = [
"num-traits",
]
[[package]]
name = "presser"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa"
[[package]]
name = "pretty_assertions"
version = "1.4.0"
@ -6149,7 +6170,7 @@ dependencies = [
"indexmap 2.3.0",
"quick-xml",
"strip-ansi-escapes",
"thiserror 1.0.64",
"thiserror 1.0.69",
"uuid",
]
@ -6345,7 +6366,7 @@ checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891"
dependencies = [
"getrandom",
"libredox",
"thiserror 1.0.64",
"thiserror 1.0.69",
]
[[package]]
@ -6894,9 +6915,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "serde"
version = "1.0.205"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e33aedb1a7135da52b7c21791455563facbbcc43d0f0f66165b42c21b3dfb150"
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
dependencies = [
"serde_derive",
]
@ -6933,9 +6954,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.205"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "692d6f5ac90220161d6774db30c662202721e64aed9058d2c394f451261420c1"
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
dependencies = [
"proc-macro2",
"quote",
@ -6981,8 +7002,6 @@ dependencies = [
[[package]]
name = "serde_v8"
version = "0.240.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd0494d74c40ab94f53a19485de359ea6a55f05341b817b93440b673c1ce8ec6"
dependencies = [
"deno_error",
"num-bigint",
@ -8021,11 +8040,11 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.64"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl 1.0.64",
"thiserror-impl 1.0.69",
]
[[package]]
@ -8039,9 +8058,9 @@ dependencies = [
[[package]]
name = "thiserror-impl"
version = "1.0.64"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
@ -8211,7 +8230,7 @@ checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0"
dependencies = [
"either",
"futures-util",
"thiserror 1.0.64",
"thiserror 1.0.69",
"tokio",
]
@ -8548,9 +8567,9 @@ checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
[[package]]
name = "unicode-xid"
version = "0.2.4"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "unicode_categories"
@ -8667,7 +8686,7 @@ dependencies = [
"indexmap 2.3.0",
"num-bigint",
"serde",
"thiserror 1.0.64",
"thiserror 1.0.69",
"wtf8",
]
@ -8762,23 +8781,24 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
[[package]]
name = "wasm-bindgen"
version = "0.2.92"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.92"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.87",
@ -8799,9 +8819,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.92"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@ -8809,9 +8829,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.92"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
@ -8822,9 +8842,12 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.92"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
dependencies = [
"unicode-ident",
]
[[package]]
name = "wasm-streams"
@ -8851,9 +8874,9 @@ dependencies = [
[[package]]
name = "web-sys"
version = "0.3.69"
version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
dependencies = [
"js-sys",
"wasm-bindgen",
@ -8889,15 +8912,14 @@ dependencies = [
[[package]]
name = "wgpu-core"
version = "0.21.1"
version = "23.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d50819ab545b867d8a454d1d756b90cd5f15da1f2943334ca314af10583c9d39"
checksum = "d63c3c478de8e7e01786479919c8769f62a22eec16788d8c2ac77ce2c132778a"
dependencies = [
"arrayvec",
"bit-vec",
"bit-vec 0.8.0",
"bitflags 2.6.0",
"cfg_aliases 0.1.1",
"codespan-reporting",
"document-features",
"indexmap 2.3.0",
"log",
@ -8910,30 +8932,30 @@ dependencies = [
"rustc-hash 1.1.0",
"serde",
"smallvec",
"thiserror 1.0.64",
"web-sys",
"thiserror 1.0.69",
"wgpu-hal",
"wgpu-types",
]
[[package]]
name = "wgpu-hal"
version = "0.21.1"
version = "23.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "172e490a87295564f3fcc0f165798d87386f6231b04d4548bca458cbbfd63222"
checksum = "89364b8a0b211adc7b16aeaf1bd5ad4a919c1154b44c9ce27838213ba05fd821"
dependencies = [
"android_system_properties",
"arrayvec",
"ash",
"bit-set",
"bit-set 0.8.0",
"bitflags 2.6.0",
"block",
"bytemuck",
"cfg_aliases 0.1.1",
"core-graphics-types",
"d3d12",
"glow",
"glutin_wgl_sys",
"gpu-alloc",
"gpu-allocator",
"gpu-descriptor",
"js-sys",
"khronos-egl",
@ -8951,18 +8973,19 @@ dependencies = [
"raw-window-handle",
"rustc-hash 1.1.0",
"smallvec",
"thiserror 1.0.64",
"thiserror 1.0.69",
"wasm-bindgen",
"web-sys",
"wgpu-types",
"winapi",
"windows 0.58.0",
"windows-core 0.58.0",
]
[[package]]
name = "wgpu-types"
version = "0.20.0"
version = "23.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1353d9a46bff7f955a680577f34c69122628cc2076e1d6f3a9be6ef00ae793ef"
checksum = "610f6ff27778148c31093f3b03abc4840f9636d58d597ca2f5977433acfe0068"
dependencies = [
"bitflags 2.6.0",
"js-sys",
@ -9005,7 +9028,7 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b2b1bf557d947847a30eb73f79aa6cdb3eaf3ce02f5e9599438f77896a62b3c"
dependencies = [
"thiserror 1.0.64",
"thiserror 1.0.69",
"windows 0.52.0",
]
@ -9367,7 +9390,7 @@ dependencies = [
"nom 7.1.3",
"oid-registry",
"rusticata-macros",
"thiserror 1.0.64",
"thiserror 1.0.69",
"time",
]
@ -9511,7 +9534,7 @@ dependencies = [
"parking_lot",
"rand",
"regex",
"thiserror 1.0.64",
"thiserror 1.0.69",
"tokio",
"tokio-util",
"uuid",
@ -9552,7 +9575,7 @@ dependencies = [
"flate2",
"indexmap 2.3.0",
"memchr",
"thiserror 1.0.64",
"thiserror 1.0.69",
]
[[package]]

View file

@ -237,8 +237,8 @@ rsa = { version = "0.9.3", default-features = false, features = ["std", "pem", "
# webgpu
raw-window-handle = "0.6.0"
wgpu-core = "0.21.1"
wgpu-types = "0.20"
wgpu-core = "23.0.1"
wgpu-types = "23.0.0"
# macros
quote = "1"
@ -350,3 +350,8 @@ opt-level = 3
opt-level = 3
[profile.release.package.zstd-sys]
opt-level = 3
[patch.crates-io]
deno_core = { path = "../deno_core/core" }
deno_ops = { path = "../deno_core/ops" }
serde_v8 = { path = "../deno_core/serde_v8" }

File diff suppressed because it is too large Load diff

View file

@ -7,146 +7,16 @@
/// <reference path="./lib.deno_webgpu.d.ts" />
import { primordials } from "ext:core/mod.js";
import {
op_webgpu_surface_configure,
op_webgpu_surface_create,
op_webgpu_surface_get_current_texture,
op_webgpu_surface_present,
} from "ext:core/ops";
import { GPUCanvasContext, UnsafeWindowSurface } from "ext:core/ops";
const {
ObjectDefineProperty,
ObjectPrototypeIsPrototypeOf,
Symbol,
SymbolFor,
TypeError,
} = primordials;
import * as webidl from "ext:deno_webidl/00_webidl.js";
import { createFilteredInspectProxy } from "ext:deno_console/01_console.js";
import { loadWebGPU } from "ext:deno_webgpu/00_init.js";
const _surfaceRid = Symbol("[[surfaceRid]]");
const _configuration = Symbol("[[configuration]]");
const _canvas = Symbol("[[canvas]]");
const _currentTexture = Symbol("[[currentTexture]]");
const _present = Symbol("[[present]]");
const _dim = Symbol("[[dimensions]]");
class GPUCanvasContext {
/** @type {number} */
[_surfaceRid];
[_configuration];
[_canvas];
/** @type {GPUTexture | undefined} */
[_currentTexture];
[_dim];
get canvas() {
webidl.assertBranded(this, GPUCanvasContextPrototype);
return this[_canvas];
}
constructor() {
webidl.illegalConstructor();
}
configure(configuration) {
webidl.assertBranded(this, GPUCanvasContextPrototype);
const prefix = "Failed to execute 'configure' on 'GPUCanvasContext'";
webidl.requiredArguments(arguments.length, 1, { prefix });
configuration = webidl.converters.GPUCanvasConfiguration(configuration, {
prefix,
context: "Argument 1",
});
const { _device, assertDevice } = loadWebGPU();
this[_device] = configuration.device[_device];
this[_configuration] = configuration;
const device = assertDevice(this, {
prefix,
context: "configuration.device",
});
const { err } = op_webgpu_surface_configure({
surfaceRid: this[_surfaceRid],
deviceRid: device.rid,
format: configuration.format,
viewFormats: configuration.viewFormats,
usage: configuration.usage,
width: this[_dim].width,
height: this[_dim].height,
alphaMode: configuration.alphaMode,
});
device.pushError(err);
}
unconfigure() {
const { _device } = loadWebGPU();
webidl.assertBranded(this, GPUCanvasContextPrototype);
this[_configuration] = null;
this[_device] = null;
}
getCurrentTexture() {
webidl.assertBranded(this, GPUCanvasContextPrototype);
const prefix =
"Failed to execute 'getCurrentTexture' on 'GPUCanvasContext'";
if (this[_configuration] === null) {
throw new DOMException("Context is not configured", "InvalidStateError");
}
const { createGPUTexture, assertDevice } = loadWebGPU();
const device = assertDevice(this, { prefix, context: "this" });
if (this[_currentTexture]) {
return this[_currentTexture];
}
const { rid } = op_webgpu_surface_get_current_texture(
device.rid,
this[_surfaceRid],
);
const texture = createGPUTexture(
{
size: {
width: this[_dim].width,
height: this[_dim].height,
depthOrArrayLayers: 1,
},
mipLevelCount: 1,
sampleCount: 1,
dimension: "2d",
format: this[_configuration].format,
usage: this[_configuration].usage,
},
device,
rid,
);
device.trackResource(texture);
this[_currentTexture] = texture;
return texture;
}
// Required to present the texture; browser don't need this.
[_present]() {
const { assertDevice } = loadWebGPU();
webidl.assertBranded(this, GPUCanvasContextPrototype);
const prefix = "Failed to execute 'present' on 'GPUCanvasContext'";
const device = assertDevice(this[_currentTexture], {
prefix,
context: "this",
});
op_webgpu_surface_present(device.rid, this[_surfaceRid]);
this[_currentTexture].destroy();
this[_currentTexture] = undefined;
}
[SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) {
ObjectDefineProperty(GPUCanvasContext, SymbolFor("Deno.privateCustomInspect"), {
value(inspect, inspectOptions) {
return inspect(
createFilteredInspectProxy({
object: this,
@ -157,60 +27,8 @@ class GPUCanvasContext {
}),
inspectOptions,
);
}
}
},
});
const GPUCanvasContextPrototype = GPUCanvasContext.prototype;
function createCanvasContext(options) {
// lazy load webgpu if needed
const canvasContext = webidl.createBranded(GPUCanvasContext);
canvasContext[_surfaceRid] = options.surfaceRid;
canvasContext[_canvas] = options.canvas;
canvasContext[_dim] = { width: options.width, height: options.height };
return canvasContext;
}
// External webgpu surfaces
// TODO(@littledivy): This will extend `OffscreenCanvas` when we add it.
class UnsafeWindowSurface {
#ctx;
#surfaceRid;
#options;
constructor(options) {
if (typeof options !== "object") {
throw new TypeError("options must be provided.");
}
if (
typeof options.width !== "number" || typeof options.height !== "number"
) {
throw new TypeError("width and height must be provided.");
}
this.#surfaceRid = op_webgpu_surface_create(
options.system,
options.windowHandle,
options.displayHandle,
);
this.#options = options;
}
getContext(context) {
if (context !== "webgpu") {
throw new TypeError("Only 'webgpu' context is supported");
}
this.#ctx = createCanvasContext({
surfaceRid: this.#surfaceRid,
...this.#options,
});
return this.#ctx;
}
present() {
this.#ctx[_present]();
}
}
export { GPUCanvasContext, UnsafeWindowSurface };

View file

@ -27,6 +27,8 @@ tokio = { workspace = true, features = ["full"] }
wgpu-types = { workspace = true, features = ["serde"] }
raw-window-handle = { workspace = true }
thiserror.workspace = true
indexmap.workspace = true
serde_json.workspace = true
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.wgpu-core]
workspace = true

386
ext/webgpu/adapter.rs Normal file
View file

@ -0,0 +1,386 @@
// Copyright 2018-2025 the Deno authors. MIT license.
use std::collections::HashSet;
use std::rc::Rc;
use std::sync::Arc;
use deno_core::cppgc::SameObject;
use deno_core::op2;
use deno_core::v8;
use deno_core::GarbageCollected;
use deno_core::WebIDL;
use tokio::sync::Mutex;
use super::device::GPUDevice;
use crate::webidl::features_to_feature_names;
use crate::webidl::GPUFeatureName;
use crate::Instance;
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPURequestAdapterOptions {
pub power_preference: Option<GPUPowerPreference>,
#[webidl(default = false)]
pub force_fallback_adapter: bool,
}
#[derive(WebIDL)]
#[webidl(enum)]
pub(crate) enum GPUPowerPreference {
LowPower,
HighPerformance,
}
#[derive(WebIDL)]
#[webidl(dictionary)]
struct GPUDeviceDescriptor {
#[webidl(default = String::new())]
label: String,
#[webidl(default = vec![])]
required_features: Vec<GPUFeatureName>,
#[webidl(default = Default::default())]
#[options(enforce_range = true)]
required_limits: indexmap::IndexMap<String, u64>,
}
pub struct GPUAdapter {
pub instance: Instance,
pub id: wgpu_core::id::AdapterId,
pub features: SameObject<GPUSupportedFeatures>,
pub limits: SameObject<GPUSupportedLimits>,
pub info: Rc<SameObject<GPUAdapterInfo>>,
}
impl Drop for GPUAdapter {
fn drop(&mut self) {
self.instance.adapter_drop(self.id);
}
}
impl GarbageCollected for GPUAdapter {}
#[op2]
impl GPUAdapter {
#[getter]
#[global]
fn info(&self, scope: &mut v8::HandleScope) -> v8::Global<v8::Object> {
self.info.get(scope, |_| {
let info = self.instance.adapter_get_info(self.id);
GPUAdapterInfo(info)
})
}
#[getter]
#[global]
fn features(&self, scope: &mut v8::HandleScope) -> v8::Global<v8::Object> {
self.features.get(scope, |scope| {
let features = self.instance.adapter_features(self.id);
let features = features_to_feature_names(features);
GPUSupportedFeatures::new(scope, features)
})
}
#[getter]
#[global]
fn limits(&self, scope: &mut v8::HandleScope) -> v8::Global<v8::Object> {
self.limits.get(scope, |_| {
let adapter_limits = self.instance.adapter_limits(self.id);
GPUSupportedLimits(adapter_limits)
})
}
#[getter]
fn is_fallback_adapter(&self) -> bool {
// TODO(lucacasonato): report correctly from wgpu
false
}
#[async_method]
#[cppgc]
async fn request_device(
&self,
#[webidl] descriptor: GPUDeviceDescriptor,
) -> Result<GPUDevice, CreateDeviceError> {
let features = self.instance.adapter_features(self.id);
let supported_features = features_to_feature_names(features);
let required_features = descriptor
.required_features
.iter()
.cloned()
.collect::<HashSet<_>>();
if !required_features.is_subset(&supported_features) {
return Err(CreateDeviceError::RequiredFeaturesNotASubset);
}
let required_limits = serde_json::from_value(serde_json::to_value(
descriptor.required_limits,
)?)?;
let wgpu_descriptor = wgpu_types::DeviceDescriptor {
label: Some(std::borrow::Cow::Owned(descriptor.label.clone())),
required_features: super::webidl::feature_names_to_features(
descriptor.required_features,
),
required_limits,
memory_hints: Default::default(),
};
let (device, queue) = self.instance.adapter_request_device(
self.id,
&wgpu_descriptor,
std::env::var("DENO_WEBGPU_TRACE")
.ok()
.as_ref()
.map(std::path::Path::new),
None,
None,
)?;
let (sender, receiver) = tokio::sync::oneshot::channel();
Ok(GPUDevice {
instance: self.instance.clone(),
id: device,
queue,
label: descriptor.label,
queue_obj: SameObject::new(),
adapter_info: self.info.clone(),
error_handler: Arc::new(super::error::DeviceErrorHandler::new(sender)),
adapter: self.id,
lost_receiver: Mutex::new(Some(receiver)),
limits: SameObject::new(),
features: SameObject::new(),
})
}
}
#[derive(Debug, thiserror::Error, deno_error::JsError)]
pub enum CreateDeviceError {
#[class(type)]
#[error("requiredFeatures must be a subset of the adapter features")]
RequiredFeaturesNotASubset,
#[class(inherit)]
#[error(transparent)]
Serde(#[from] serde_json::Error),
#[class(type)]
#[error(transparent)]
Device(#[from] wgpu_core::instance::RequestDeviceError),
}
pub struct GPUSupportedLimits(pub wgpu_types::Limits);
impl GarbageCollected for GPUSupportedLimits {}
#[op2]
impl GPUSupportedLimits {
#[getter]
fn maxTextureDimension1D(&self) -> u32 {
self.0.max_texture_dimension_1d
}
#[getter]
fn maxTextureDimension2D(&self) -> u32 {
self.0.max_texture_dimension_2d
}
#[getter]
fn maxTextureDimension3D(&self) -> u32 {
self.0.max_texture_dimension_3d
}
#[getter]
fn maxTextureArrayLayers(&self) -> u32 {
self.0.max_texture_array_layers
}
#[getter]
fn maxBindGroups(&self) -> u32 {
self.0.max_bind_groups
}
// TODO(@crowlKats): support max_bind_groups_plus_vertex_buffers
#[getter]
fn maxBindingsPerBindGroup(&self) -> u32 {
self.0.max_bindings_per_bind_group
}
#[getter]
fn maxDynamicUniformBuffersPerPipelineLayout(&self) -> u32 {
self.0.max_dynamic_uniform_buffers_per_pipeline_layout
}
#[getter]
fn maxDynamicStorageBuffersPerPipelineLayout(&self) -> u32 {
self.0.max_dynamic_storage_buffers_per_pipeline_layout
}
#[getter]
fn maxSampledTexturesPerShaderStage(&self) -> u32 {
self.0.max_sampled_textures_per_shader_stage
}
#[getter]
fn maxSamplersPerShaderStage(&self) -> u32 {
self.0.max_samplers_per_shader_stage
}
#[getter]
fn maxStorageBuffersPerShaderStage(&self) -> u32 {
self.0.max_storage_buffers_per_shader_stage
}
#[getter]
fn maxStorageTexturesPerShaderStage(&self) -> u32 {
self.0.max_storage_textures_per_shader_stage
}
#[getter]
fn maxUniformBuffersPerShaderStage(&self) -> u32 {
self.0.max_uniform_buffers_per_shader_stage
}
#[getter]
fn maxUniformBufferBindingSize(&self) -> u32 {
self.0.max_uniform_buffer_binding_size
}
#[getter]
fn maxStorageBufferBindingSize(&self) -> u32 {
self.0.max_storage_buffer_binding_size
}
#[getter]
fn minUniformBufferOffsetAlignment(&self) -> u32 {
self.0.min_uniform_buffer_offset_alignment
}
#[getter]
fn minStorageBufferOffsetAlignment(&self) -> u32 {
self.0.min_storage_buffer_offset_alignment
}
#[getter]
fn maxVertexBuffers(&self) -> u32 {
self.0.max_vertex_buffers
}
#[getter]
#[number]
fn maxBufferSize(&self) -> u64 {
self.0.max_buffer_size
}
#[getter]
fn maxVertexAttributes(&self) -> u32 {
self.0.max_vertex_attributes
}
#[getter]
fn maxVertexBufferArrayStride(&self) -> u32 {
self.0.max_vertex_buffer_array_stride
}
// TODO(@crowlKats): support max_inter_stage_shader_variables
#[getter]
fn maxColorAttachments(&self) -> u32 {
self.0.max_color_attachments
}
#[getter]
fn maxColorAttachmentBytesPerSample(&self) -> u32 {
self.0.max_color_attachment_bytes_per_sample
}
#[getter]
fn maxComputeWorkgroupStorageSize(&self) -> u32 {
self.0.max_compute_workgroup_storage_size
}
#[getter]
fn maxComputeInvocationsPerWorkgroup(&self) -> u32 {
self.0.max_compute_invocations_per_workgroup
}
#[getter]
fn maxComputeWorkgroupSizeX(&self) -> u32 {
self.0.max_compute_workgroup_size_x
}
#[getter]
fn maxComputeWorkgroupSizeY(&self) -> u32 {
self.0.max_compute_workgroup_size_y
}
#[getter]
fn maxComputeWorkgroupSizeZ(&self) -> u32 {
self.0.max_compute_workgroup_size_z
}
#[getter]
fn maxComputeWorkgroupsPerDimension(&self) -> u32 {
self.0.max_compute_workgroups_per_dimension
}
}
pub struct GPUSupportedFeatures(v8::Global<v8::Value>);
impl GarbageCollected for GPUSupportedFeatures {}
impl GPUSupportedFeatures {
pub fn new(
scope: &mut v8::HandleScope,
features: HashSet<GPUFeatureName>,
) -> Self {
let set = v8::Set::new(scope);
for feature in features {
let key = v8::String::new(scope, feature.as_str()).unwrap();
set.add(scope, key.into());
}
Self(v8::Global::new(scope, <v8::Local<v8::Value>>::from(set)))
}
}
#[op2]
impl GPUSupportedFeatures {
#[global]
#[symbol("setlike_set")]
fn set(&self) -> v8::Global<v8::Value> {
self.0.clone()
}
}
pub struct GPUAdapterInfo(pub wgpu_types::AdapterInfo);
impl GarbageCollected for GPUAdapterInfo {}
#[op2]
impl GPUAdapterInfo {
#[getter]
#[string]
fn vendor(&self) -> String {
self.0.vendor.to_string()
}
#[getter]
#[string]
fn architecture(&self) -> &'static str {
"" // TODO: wgpu#2170
}
#[getter]
#[string]
fn device(&self) -> String {
self.0.device.to_string()
}
#[getter]
#[string]
fn description(&self) -> String {
self.0.name.clone()
}
}

122
ext/webgpu/bind_group.rs Normal file
View file

@ -0,0 +1,122 @@
// Copyright 2018-2025 the Deno authors. MIT license.
use std::borrow::Cow;
use deno_core::cppgc::Ptr;
use deno_core::op2;
use deno_core::v8::HandleScope;
use deno_core::v8::Local;
use deno_core::v8::Value;
use deno_core::webidl::ContextFn;
use deno_core::webidl::WebIdlConverter;
use deno_core::webidl::WebIdlError;
use deno_core::webidl::WebIdlInterfaceConverter;
use deno_core::GarbageCollected;
use deno_core::WebIDL;
use crate::buffer::GPUBuffer;
use crate::sampler::GPUSampler;
use crate::texture::GPUTextureView;
use crate::Instance;
pub struct GPUBindGroup {
pub instance: Instance,
pub id: wgpu_core::id::BindGroupId,
pub label: String,
}
impl Drop for GPUBindGroup {
fn drop(&mut self) {
self.instance.bind_group_drop(self.id);
}
}
impl WebIdlInterfaceConverter for GPUBindGroup {
const NAME: &'static str = "GPUBindGroup";
}
impl GarbageCollected for GPUBindGroup {}
#[op2]
impl GPUBindGroup {
#[getter]
#[string]
fn label(&self) -> String {
self.label.clone()
}
#[setter]
#[string]
fn label(&self, #[webidl] _label: String) {
// TODO(@crowlKats): no-op, needs wpgu to implement changing the label
}
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPUBindGroupDescriptor {
#[webidl(default = String::new())]
pub label: String,
pub layout: Ptr<super::bind_group_layout::GPUBindGroupLayout>,
pub entries: Vec<GPUBindGroupEntry>,
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPUBindGroupEntry {
#[options(enforce_range = true)]
pub binding: u32,
pub resource: GPUBindingResource,
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPUBufferBinding {
pub buffer: Ptr<GPUBuffer>,
#[webidl(default = 0)]
#[options(enforce_range = true)]
pub offset: u64,
#[options(enforce_range = true)]
pub size: Option<u64>,
}
pub(crate) enum GPUBindingResource {
Sampler(Ptr<GPUSampler>),
TextureView(Ptr<GPUTextureView>),
BufferBinding(GPUBufferBinding),
}
impl<'a> WebIdlConverter<'a> for GPUBindingResource {
type Options = ();
fn convert<'b>(
scope: &mut HandleScope<'a>,
value: Local<'a, Value>,
prefix: Cow<'static, str>,
context: ContextFn<'b>,
options: &Self::Options,
) -> Result<Self, WebIdlError> {
<Ptr<GPUSampler>>::convert(
scope,
value,
prefix.clone(),
context.borrowed(),
options,
)
.map(Self::Sampler)
.or_else(|_| {
<Ptr<GPUTextureView>>::convert(
scope,
value,
prefix.clone(),
context.borrowed(),
options,
)
.map(Self::TextureView)
})
.or_else(|_| {
GPUBufferBinding::convert(scope, value, prefix, context, options)
.map(Self::BufferBinding)
})
}
}

View file

@ -0,0 +1,180 @@
// Copyright 2018-2025 the Deno authors. MIT license.
use deno_core::op2;
use deno_core::GarbageCollected;
use deno_core::WebIDL;
use crate::texture::GPUTextureViewDimension;
use crate::Instance;
pub struct GPUBindGroupLayout {
pub instance: Instance,
pub id: wgpu_core::id::BindGroupLayoutId,
pub label: String,
}
impl Drop for GPUBindGroupLayout {
fn drop(&mut self) {
self.instance.bind_group_layout_drop(self.id);
}
}
impl deno_core::webidl::WebIdlInterfaceConverter for GPUBindGroupLayout {
const NAME: &'static str = "GPUBindGroupLayout";
}
impl GarbageCollected for GPUBindGroupLayout {}
#[op2]
impl GPUBindGroupLayout {
#[getter]
#[string]
fn label(&self) -> String {
self.label.clone()
}
#[setter]
#[string]
fn label(&self, #[webidl] _label: String) {
// TODO(@crowlKats): no-op, needs wpgu to implement changing the label
}
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPUBindGroupLayoutDescriptor {
#[webidl(default = String::new())]
pub label: String,
pub entries: Vec<GPUBindGroupLayoutEntry>,
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPUBindGroupLayoutEntry {
#[options(enforce_range = true)]
pub binding: u32,
#[options(enforce_range = true)]
pub visibility: u32,
pub buffer: Option<GPUBufferBindingLayout>,
pub sampler: Option<GPUSamplerBindingLayout>,
pub texture: Option<GPUTextureBindingLayout>,
pub storage_texture: Option<GPUStorageTextureBindingLayout>,
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPUBufferBindingLayout {
#[webidl(default = GPUBufferBindingType::Uniform)]
pub r#type: GPUBufferBindingType,
#[webidl(default = false)]
pub has_dynamic_offset: bool,
#[webidl(default = 0)]
pub min_binding_size: u64,
}
#[derive(WebIDL)]
#[webidl(enum)]
pub(crate) enum GPUBufferBindingType {
Uniform,
Storage,
ReadOnlyStorage,
}
impl From<GPUBufferBindingType> for wgpu_types::BufferBindingType {
fn from(value: GPUBufferBindingType) -> Self {
match value {
GPUBufferBindingType::Uniform => Self::Uniform,
GPUBufferBindingType::Storage => Self::Storage { read_only: false },
GPUBufferBindingType::ReadOnlyStorage => {
Self::Storage { read_only: true }
}
}
}
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPUSamplerBindingLayout {
#[webidl(default = GPUSamplerBindingType::Filtering)]
pub r#type: GPUSamplerBindingType,
}
#[derive(WebIDL)]
#[webidl(enum)]
pub(crate) enum GPUSamplerBindingType {
Filtering,
NonFiltering,
Comparison,
}
impl From<GPUSamplerBindingType> for wgpu_types::SamplerBindingType {
fn from(value: GPUSamplerBindingType) -> Self {
match value {
GPUSamplerBindingType::Filtering => Self::Filtering,
GPUSamplerBindingType::NonFiltering => Self::NonFiltering,
GPUSamplerBindingType::Comparison => Self::Comparison,
}
}
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPUTextureBindingLayout {
#[webidl(default = GPUTextureSampleType::Float)]
pub sample_type: GPUTextureSampleType,
#[webidl(default = GPUTextureViewDimension::D2)]
pub view_dimension: GPUTextureViewDimension,
#[webidl(default = false)]
pub multisampled: bool,
}
#[derive(WebIDL)]
#[webidl(enum)]
pub(crate) enum GPUTextureSampleType {
Float,
UnfilterableFloat,
Depth,
Sint,
Uint,
}
impl From<GPUTextureSampleType> for wgpu_types::TextureSampleType {
fn from(value: GPUTextureSampleType) -> Self {
match value {
GPUTextureSampleType::Float => Self::Float { filterable: true },
GPUTextureSampleType::UnfilterableFloat => {
Self::Float { filterable: false }
}
GPUTextureSampleType::Depth => Self::Depth,
GPUTextureSampleType::Sint => Self::Sint,
GPUTextureSampleType::Uint => Self::Uint,
}
}
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPUStorageTextureBindingLayout {
#[webidl(default = GPUStorageTextureAccess::WriteOnly)]
pub access: GPUStorageTextureAccess,
pub format: super::texture::GPUTextureFormat,
#[webidl(default = GPUTextureViewDimension::D2)]
pub view_dimension: GPUTextureViewDimension,
}
#[derive(WebIDL)]
#[webidl(enum)]
pub(crate) enum GPUStorageTextureAccess {
WriteOnly,
ReadOnly,
ReadWrite,
}
impl From<GPUStorageTextureAccess> for wgpu_types::StorageTextureAccess {
fn from(value: GPUStorageTextureAccess) -> Self {
match value {
GPUStorageTextureAccess::WriteOnly => Self::WriteOnly,
GPUStorageTextureAccess::ReadOnly => Self::ReadOnly,
GPUStorageTextureAccess::ReadWrite => Self::ReadWrite,
}
}
}

View file

@ -1,323 +0,0 @@
// Copyright 2018-2025 the Deno authors. MIT license.
use std::borrow::Cow;
use std::rc::Rc;
use deno_core::error::ResourceError;
use deno_core::op2;
use deno_core::OpState;
use deno_core::Resource;
use deno_core::ResourceId;
use serde::Deserialize;
use super::error::WebGpuResult;
pub(crate) struct WebGpuBindGroupLayout(
pub(crate) crate::Instance,
pub(crate) wgpu_core::id::BindGroupLayoutId,
);
impl Resource for WebGpuBindGroupLayout {
fn name(&self) -> Cow<str> {
"webGPUBindGroupLayout".into()
}
fn close(self: Rc<Self>) {
gfx_select!(self.1 => self.0.bind_group_layout_drop(self.1));
}
}
pub(crate) struct WebGpuBindGroup(
pub(crate) crate::Instance,
pub(crate) wgpu_core::id::BindGroupId,
);
impl Resource for WebGpuBindGroup {
fn name(&self) -> Cow<str> {
"webGPUBindGroup".into()
}
fn close(self: Rc<Self>) {
gfx_select!(self.1 => self.0.bind_group_drop(self.1));
}
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct GpuBufferBindingLayout {
r#type: GpuBufferBindingType,
has_dynamic_offset: bool,
min_binding_size: u64,
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
enum GpuBufferBindingType {
Uniform,
Storage,
ReadOnlyStorage,
}
impl From<GpuBufferBindingType> for wgpu_types::BufferBindingType {
fn from(binding_type: GpuBufferBindingType) -> Self {
match binding_type {
GpuBufferBindingType::Uniform => wgpu_types::BufferBindingType::Uniform,
GpuBufferBindingType::Storage => {
wgpu_types::BufferBindingType::Storage { read_only: false }
}
GpuBufferBindingType::ReadOnlyStorage => {
wgpu_types::BufferBindingType::Storage { read_only: true }
}
}
}
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct GpuSamplerBindingLayout {
r#type: wgpu_types::SamplerBindingType,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct GpuTextureBindingLayout {
sample_type: GpuTextureSampleType,
view_dimension: wgpu_types::TextureViewDimension,
multisampled: bool,
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
enum GpuTextureSampleType {
Float,
UnfilterableFloat,
Depth,
Sint,
Uint,
}
impl From<GpuTextureSampleType> for wgpu_types::TextureSampleType {
fn from(sample_type: GpuTextureSampleType) -> Self {
match sample_type {
GpuTextureSampleType::Float => {
wgpu_types::TextureSampleType::Float { filterable: true }
}
GpuTextureSampleType::UnfilterableFloat => {
wgpu_types::TextureSampleType::Float { filterable: false }
}
GpuTextureSampleType::Depth => wgpu_types::TextureSampleType::Depth,
GpuTextureSampleType::Sint => wgpu_types::TextureSampleType::Sint,
GpuTextureSampleType::Uint => wgpu_types::TextureSampleType::Uint,
}
}
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct GpuStorageTextureBindingLayout {
access: wgpu_types::StorageTextureAccess,
format: wgpu_types::TextureFormat,
view_dimension: wgpu_types::TextureViewDimension,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GpuBindGroupLayoutEntry {
binding: u32,
visibility: u32,
#[serde(flatten)]
binding_type: GpuBindingType,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
enum GpuBindingType {
Buffer(GpuBufferBindingLayout),
Sampler(GpuSamplerBindingLayout),
Texture(GpuTextureBindingLayout),
StorageTexture(GpuStorageTextureBindingLayout),
}
impl From<GpuBindingType> for wgpu_types::BindingType {
fn from(binding_type: GpuBindingType) -> wgpu_types::BindingType {
match binding_type {
GpuBindingType::Buffer(buffer) => wgpu_types::BindingType::Buffer {
ty: buffer.r#type.into(),
has_dynamic_offset: buffer.has_dynamic_offset,
min_binding_size: std::num::NonZeroU64::new(buffer.min_binding_size),
},
GpuBindingType::Sampler(sampler) => {
wgpu_types::BindingType::Sampler(sampler.r#type)
}
GpuBindingType::Texture(texture) => wgpu_types::BindingType::Texture {
sample_type: texture.sample_type.into(),
view_dimension: texture.view_dimension,
multisampled: texture.multisampled,
},
GpuBindingType::StorageTexture(storage_texture) => {
wgpu_types::BindingType::StorageTexture {
access: storage_texture.access,
format: storage_texture.format,
view_dimension: storage_texture.view_dimension,
}
}
}
}
}
#[op2]
#[serde]
pub fn op_webgpu_create_bind_group_layout(
state: &mut OpState,
#[smi] device_rid: ResourceId,
#[string] label: Cow<str>,
#[serde] entries: Vec<GpuBindGroupLayoutEntry>,
) -> Result<WebGpuResult, ResourceError> {
let instance = state.borrow::<super::Instance>();
let device_resource = state
.resource_table
.get::<super::WebGpuDevice>(device_rid)?;
let device = device_resource.1;
let entries = entries
.into_iter()
.map(|entry| {
wgpu_types::BindGroupLayoutEntry {
binding: entry.binding,
visibility: wgpu_types::ShaderStages::from_bits(entry.visibility)
.unwrap(),
ty: entry.binding_type.into(),
count: None, // native-only
}
})
.collect::<Vec<_>>();
let descriptor = wgpu_core::binding_model::BindGroupLayoutDescriptor {
label: Some(label),
entries: Cow::from(entries),
};
gfx_put!(device => instance.device_create_bind_group_layout(
device,
&descriptor,
None
) => state, WebGpuBindGroupLayout)
}
#[op2]
#[serde]
pub fn op_webgpu_create_pipeline_layout(
state: &mut OpState,
#[smi] device_rid: ResourceId,
#[string] label: Cow<str>,
#[serde] bind_group_layouts: Vec<u32>,
) -> Result<WebGpuResult, ResourceError> {
let instance = state.borrow::<super::Instance>();
let device_resource = state
.resource_table
.get::<super::WebGpuDevice>(device_rid)?;
let device = device_resource.1;
let bind_group_layouts = bind_group_layouts
.into_iter()
.map(|rid| {
let bind_group_layout =
state.resource_table.get::<WebGpuBindGroupLayout>(rid)?;
Ok(bind_group_layout.1)
})
.collect::<Result<Vec<_>, ResourceError>>()?;
let descriptor = wgpu_core::binding_model::PipelineLayoutDescriptor {
label: Some(label),
bind_group_layouts: Cow::from(bind_group_layouts),
push_constant_ranges: Default::default(),
};
gfx_put!(device => instance.device_create_pipeline_layout(
device,
&descriptor,
None
) => state, super::pipeline::WebGpuPipelineLayout)
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GpuBindGroupEntry {
binding: u32,
kind: String,
resource: ResourceId,
offset: Option<u64>,
size: Option<u64>,
}
#[op2]
#[serde]
pub fn op_webgpu_create_bind_group(
state: &mut OpState,
#[smi] device_rid: ResourceId,
#[string] label: Cow<str>,
#[smi] layout: ResourceId,
#[serde] entries: Vec<GpuBindGroupEntry>,
) -> Result<WebGpuResult, ResourceError> {
let instance = state.borrow::<super::Instance>();
let device_resource = state
.resource_table
.get::<super::WebGpuDevice>(device_rid)?;
let device = device_resource.1;
let entries = entries
.into_iter()
.map(|entry| {
Ok(wgpu_core::binding_model::BindGroupEntry {
binding: entry.binding,
resource: match entry.kind.as_str() {
"GPUSampler" => {
let sampler_resource =
state
.resource_table
.get::<super::sampler::WebGpuSampler>(entry.resource)?;
wgpu_core::binding_model::BindingResource::Sampler(
sampler_resource.1,
)
}
"GPUTextureView" => {
let texture_view_resource =
state
.resource_table
.get::<super::texture::WebGpuTextureView>(entry.resource)?;
wgpu_core::binding_model::BindingResource::TextureView(
texture_view_resource.1,
)
}
"GPUBufferBinding" => {
let buffer_resource =
state
.resource_table
.get::<super::buffer::WebGpuBuffer>(entry.resource)?;
wgpu_core::binding_model::BindingResource::Buffer(
wgpu_core::binding_model::BufferBinding {
buffer_id: buffer_resource.1,
offset: entry.offset.unwrap_or(0),
size: std::num::NonZeroU64::new(entry.size.unwrap_or(0)),
},
)
}
_ => unreachable!(),
},
})
})
.collect::<Result<Vec<_>, ResourceError>>()?;
let bind_group_layout =
state.resource_table.get::<WebGpuBindGroupLayout>(layout)?;
let descriptor = wgpu_core::binding_model::BindGroupDescriptor {
label: Some(label),
layout: bind_group_layout.1,
entries: Cow::from(entries),
};
gfx_put!(device => instance.device_create_bind_group(
device,
&descriptor,
None
) => state, WebGpuBindGroup)
}

View file

@ -1,209 +1,268 @@
// Copyright 2018-2025 the Deno authors. MIT license.
use std::borrow::Cow;
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Arc;
use std::sync::Mutex;
use std::time::Duration;
use deno_core::futures::channel::oneshot;
use deno_core::op2;
use deno_core::OpState;
use deno_core::Resource;
use deno_core::ResourceId;
use deno_core::v8;
use deno_core::webidl::WebIdlInterfaceConverter;
use deno_core::GarbageCollected;
use deno_core::WebIDL;
use deno_error::JsErrorBox;
use wgpu_core::device::HostMap as MapMode;
use wgpu_core::resource::BufferMapCallback;
use super::error::WebGpuResult;
use crate::Instance;
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPUBufferDescriptor {
#[webidl(default = String::new())]
pub label: String,
pub size: u64,
#[options(enforce_range = true)]
pub usage: u32,
#[webidl(default = false)]
pub mapped_at_creation: bool,
}
#[derive(Debug, thiserror::Error, deno_error::JsError)]
pub enum BufferError {
#[class(inherit)]
#[class(generic)]
#[error(transparent)]
Resource(
#[from]
#[inherit]
deno_core::error::ResourceError,
),
#[class(type)]
#[error("usage is not valid")]
InvalidUsage,
Canceled(#[from] oneshot::Canceled),
#[class("DOMExceptionOperationError")]
#[error(transparent)]
Access(wgpu_core::resource::BufferAccessError),
Access(#[from] wgpu_core::resource::BufferAccessError),
#[class("DOMExceptionOperationError")]
#[error("{0}")]
Operation(&'static str),
#[class(inherit)]
#[error(transparent)]
Other(#[from] JsErrorBox),
}
pub(crate) struct WebGpuBuffer(
pub(crate) super::Instance,
pub(crate) wgpu_core::id::BufferId,
);
impl Resource for WebGpuBuffer {
fn name(&self) -> Cow<str> {
"webGPUBuffer".into()
}
pub struct GPUBuffer {
pub instance: Instance,
pub error_handler: super::error::ErrorHandler,
fn close(self: Rc<Self>) {
gfx_select!(self.1 => self.0.buffer_drop(self.1, true));
pub id: wgpu_core::id::BufferId,
pub device: wgpu_core::id::DeviceId,
pub label: String,
pub size: u64,
pub usage: u32,
pub map_state: RefCell<&'static str>,
pub map_mode: RefCell<Option<MapMode>>,
pub mapped_js_buffers: RefCell<Vec<v8::Global<v8::ArrayBuffer>>>,
}
impl Drop for GPUBuffer {
fn drop(&mut self) {
self.instance.buffer_drop(self.id);
}
}
struct WebGpuBufferMapped(*mut u8, usize);
impl Resource for WebGpuBufferMapped {
fn name(&self) -> Cow<str> {
"webGPUBufferMapped".into()
}
impl WebIdlInterfaceConverter for GPUBuffer {
const NAME: &'static str = "GPUBuffer";
}
impl GarbageCollected for GPUBuffer {}
#[op2]
#[serde]
pub fn op_webgpu_create_buffer(
state: &mut OpState,
#[smi] device_rid: ResourceId,
#[string] label: Cow<str>,
#[number] size: u64,
usage: u32,
mapped_at_creation: bool,
) -> Result<WebGpuResult, BufferError> {
let instance = state.borrow::<super::Instance>();
let device_resource = state
.resource_table
.get::<super::WebGpuDevice>(device_rid)?;
let device = device_resource.1;
impl GPUBuffer {
#[getter]
#[string]
fn label(&self) -> String {
self.label.clone()
}
#[setter]
#[string]
fn label(&self, #[webidl] _label: String) {
// TODO(@crowlKats): no-op, needs wpgu to implement changing the label
}
let descriptor = wgpu_core::resource::BufferDescriptor {
label: Some(label),
size,
usage: wgpu_types::BufferUsages::from_bits(usage)
.ok_or(BufferError::InvalidUsage)?,
mapped_at_creation,
#[getter]
#[number]
fn size(&self) -> u64 {
self.size
}
#[getter]
fn usage(&self) -> u32 {
self.usage
}
#[getter]
#[string]
fn map_state(&self) -> &'static str {
*self.map_state.borrow()
}
#[async_method]
async fn map_async(
&self,
#[webidl(options(enforce_range = true))] mode: u32,
#[webidl(default = 0)] offset: u64,
#[webidl] size: Option<u64>,
) -> Result<(), BufferError> {
let read_mode = (mode & 0x0001) == 0x0001;
let write_mode = (mode & 0x0002) == 0x0002;
if (read_mode && write_mode) || (!read_mode && !write_mode) {
return Err(BufferError::Operation(
"exactly one of READ or WRITE map mode must be set",
));
}
let mode = if read_mode {
MapMode::Read
} else {
assert!(write_mode);
MapMode::Write
};
gfx_put!(device => instance.device_create_buffer(
device,
&descriptor,
None
) => state, WebGpuBuffer)
}
#[op2(async)]
#[serde]
pub async fn op_webgpu_buffer_get_map_async(
state: Rc<RefCell<OpState>>,
#[smi] buffer_rid: ResourceId,
#[smi] device_rid: ResourceId,
mode: u32,
#[number] offset: u64,
#[number] size: u64,
) -> Result<WebGpuResult, BufferError> {
let device;
let done = Arc::new(Mutex::new(None));
{
let state_ = state.borrow();
let instance = state_.borrow::<super::Instance>();
let buffer_resource =
state_.resource_table.get::<WebGpuBuffer>(buffer_rid)?;
let buffer = buffer_resource.1;
let device_resource = state_
.resource_table
.get::<super::WebGpuDevice>(device_rid)?;
device = device_resource.1;
*self.map_state.borrow_mut() = "pending";
}
let done_ = done.clone();
let (sender, receiver) =
oneshot::channel::<wgpu_core::resource::BufferAccessResult>();
{
let callback = Box::new(move |status| {
*done_.lock().unwrap() = Some(status);
sender.send(status).unwrap();
});
let maybe_err = gfx_select!(buffer => instance.buffer_map_async(
buffer,
let err = self
.instance
.buffer_map_async(
self.id,
offset,
Some(size),
size,
wgpu_core::resource::BufferMapOperation {
host: match mode {
1 => wgpu_core::device::HostMap::Read,
2 => wgpu_core::device::HostMap::Write,
_ => unreachable!(),
host: mode,
callback: Some(BufferMapCallback::from_rust(callback)),
},
callback: Some(wgpu_core::resource::BufferMapCallback::from_rust(callback)),
}
))
)
.err();
if maybe_err.is_some() {
return Ok(WebGpuResult::maybe_err(maybe_err));
if err.is_some() {
self.error_handler.push_error(err);
return Err(BufferError::Operation("validation error occurred"));
}
}
loop {
let result = done.lock().unwrap().take();
match result {
Some(Ok(())) => return Ok(WebGpuResult::empty()),
Some(Err(e)) => return Err(BufferError::Access(e)),
None => {
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::<super::Instance>();
gfx_select!(device => instance.device_poll(device, wgpu_types::Maintain::Poll)).unwrap();
self
.instance
.device_poll(self.device, wgpu_types::Maintain::wait())
.unwrap();
}
tokio::time::sleep(Duration::from_millis(10)).await;
}
}
}
}
Ok::<(), BufferError>(())
};
#[op2]
#[serde]
pub fn op_webgpu_buffer_get_mapped_range(
state: &mut OpState,
#[smi] buffer_rid: ResourceId,
#[number] offset: u64,
#[number] size: Option<u64>,
#[buffer] buf: &mut [u8],
) -> Result<WebGpuResult, BufferError> {
let instance = state.borrow::<super::Instance>();
let buffer_resource = state.resource_table.get::<WebGpuBuffer>(buffer_rid)?;
let buffer = buffer_resource.1;
let receiver_fut = async move {
receiver.await??;
let mut done = done_.borrow_mut();
*done = true;
Ok::<(), BufferError>(())
};
let (slice_pointer, range_size) =
gfx_select!(buffer => instance.buffer_get_mapped_range(
buffer,
offset,
size
))
tokio::try_join!(device_poll_fut, receiver_fut)?;
*self.map_state.borrow_mut() = "mapped";
*self.map_mode.borrow_mut() = Some(mode);
Ok(())
}
fn get_mapped_range<'s>(
&self,
scope: &mut v8::HandleScope<'s>,
#[webidl(default = 0)] offset: u64,
#[webidl] size: Option<u64>,
) -> Result<v8::Local<'s, v8::Uint8Array>, BufferError> {
let size = size.unwrap_or_else(|| self.size.saturating_sub(offset));
let (slice_pointer, range_size) = self
.instance
.buffer_get_mapped_range(self.id, offset, Some(size))
.map_err(BufferError::Access)?;
// SAFETY: guarantee to be safe from wgpu
let slice = unsafe {
std::slice::from_raw_parts_mut(slice_pointer, range_size as usize)
};
buf.copy_from_slice(slice);
let mode = self.map_mode.borrow();
let mode = mode.as_ref().unwrap();
let rid = state
.resource_table
.add(WebGpuBufferMapped(slice_pointer, range_size as usize));
Ok(WebGpuResult::rid(rid))
}
#[op2]
#[serde]
pub fn op_webgpu_buffer_unmap(
state: &mut OpState,
#[smi] buffer_rid: ResourceId,
#[smi] mapped_rid: ResourceId,
#[buffer] buf: Option<&[u8]>,
) -> Result<WebGpuResult, BufferError> {
let mapped_resource = state
.resource_table
.take::<WebGpuBufferMapped>(mapped_rid)?;
let instance = state.borrow::<super::Instance>();
let buffer_resource = state.resource_table.get::<WebGpuBuffer>(buffer_rid)?;
let buffer = buffer_resource.1;
if let Some(buf) = buf {
// SAFETY: guarantee to be safe from wgpu
let slice = unsafe {
std::slice::from_raw_parts_mut(mapped_resource.0, mapped_resource.1)
};
slice.copy_from_slice(buf);
let bs = if mode == &MapMode::Write {
unsafe extern "C" fn noop_deleter_callback(
_data: *mut std::ffi::c_void,
_byte_length: usize,
_deleter_data: *mut std::ffi::c_void,
) {
}
gfx_ok!(buffer => instance.buffer_unmap(buffer))
// SAFETY: creating a backing store from the pointer and length provided by wgpu
unsafe {
v8::ArrayBuffer::new_backing_store_from_ptr(
slice_pointer.as_ptr() as _,
range_size as usize,
noop_deleter_callback,
std::ptr::null_mut(),
)
}
} else {
// SAFETY: creating a vector from the pointer and length provided by wgpu
let slice = unsafe {
std::slice::from_raw_parts(slice_pointer.as_ptr(), range_size as usize)
};
v8::ArrayBuffer::new_backing_store_from_vec(slice.to_vec())
};
let shared_bs = bs.make_shared();
let ab = v8::ArrayBuffer::with_backing_store(scope, &shared_bs);
if mode == &MapMode::Write {
self
.mapped_js_buffers
.borrow_mut()
.push(v8::Global::new(scope, ab));
}
Ok(v8::Uint8Array::new(scope, ab, 0, range_size as usize).unwrap())
}
#[nofast]
fn unmap(&self, scope: &mut v8::HandleScope) -> Result<(), BufferError> {
for ab in self.mapped_js_buffers.replace(vec![]) {
let ab = ab.open(scope);
ab.detach(None);
}
self
.instance
.buffer_unmap(self.id)
.map_err(BufferError::Access)?;
*self.map_state.borrow_mut() = "unmapped";
Ok(())
}
#[fast]
fn destroy(&self) -> Result<(), JsErrorBox> {
self
.instance
.buffer_destroy(self.id)
.map_err(|e| JsErrorBox::generic(e.to_string()))
}
}

View file

@ -1,413 +0,0 @@
// Copyright 2018-2025 the Deno authors. MIT license.
use std::borrow::Cow;
use std::cell::RefCell;
use std::rc::Rc;
use deno_core::error::ResourceError;
use deno_core::op2;
use deno_core::OpState;
use deno_core::Resource;
use deno_core::ResourceId;
use serde::Deserialize;
use super::error::WebGpuResult;
#[derive(Debug, thiserror::Error, deno_error::JsError)]
pub enum BundleError {
#[class(inherit)]
#[error(transparent)]
Resource(
#[from]
#[inherit]
ResourceError,
),
#[class(type)]
#[error("size must be larger than 0")]
InvalidSize,
}
struct WebGpuRenderBundleEncoder(
RefCell<wgpu_core::command::RenderBundleEncoder>,
);
impl Resource for WebGpuRenderBundleEncoder {
fn name(&self) -> Cow<str> {
"webGPURenderBundleEncoder".into()
}
}
pub(crate) struct WebGpuRenderBundle(
pub(crate) super::Instance,
pub(crate) wgpu_core::id::RenderBundleId,
);
impl Resource for WebGpuRenderBundle {
fn name(&self) -> Cow<str> {
"webGPURenderBundle".into()
}
fn close(self: Rc<Self>) {
gfx_select!(self.1 => self.0.render_bundle_drop(self.1));
}
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateRenderBundleEncoderArgs {
device_rid: ResourceId,
label: String,
color_formats: Vec<Option<wgpu_types::TextureFormat>>,
depth_stencil_format: Option<wgpu_types::TextureFormat>,
sample_count: u32,
depth_read_only: bool,
stencil_read_only: bool,
}
#[op2]
#[serde]
pub fn op_webgpu_create_render_bundle_encoder(
state: &mut OpState,
#[serde] args: CreateRenderBundleEncoderArgs,
) -> Result<WebGpuResult, ResourceError> {
let device_resource = state
.resource_table
.get::<super::WebGpuDevice>(args.device_rid)?;
let device = device_resource.1;
let depth_stencil = args.depth_stencil_format.map(|format| {
wgpu_types::RenderBundleDepthStencil {
format,
depth_read_only: args.depth_read_only,
stencil_read_only: args.stencil_read_only,
}
});
let descriptor = wgpu_core::command::RenderBundleEncoderDescriptor {
label: Some(Cow::Owned(args.label)),
color_formats: Cow::from(args.color_formats),
sample_count: args.sample_count,
depth_stencil,
multiview: None,
};
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(WebGpuResult::rid_err(rid, maybe_err))
}
#[op2]
#[serde]
pub fn op_webgpu_render_bundle_encoder_finish(
state: &mut OpState,
#[smi] render_bundle_encoder_rid: ResourceId,
#[string] label: Cow<str>,
) -> Result<WebGpuResult, ResourceError> {
let render_bundle_encoder_resource =
state
.resource_table
.take::<WebGpuRenderBundleEncoder>(render_bundle_encoder_rid)?;
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::<super::Instance>();
gfx_put!(render_bundle_encoder.parent() => instance.render_bundle_encoder_finish(
render_bundle_encoder,
&wgpu_core::command::RenderBundleDescriptor {
label: Some(label),
},
None
) => state, WebGpuRenderBundle)
}
#[op2]
#[serde]
pub fn op_webgpu_render_bundle_encoder_set_bind_group(
state: &mut OpState,
#[smi] render_bundle_encoder_rid: ResourceId,
index: u32,
#[smi] bind_group: ResourceId,
#[buffer] dynamic_offsets_data: &[u32],
#[number] dynamic_offsets_data_start: usize,
#[number] dynamic_offsets_data_length: usize,
) -> Result<WebGpuResult, ResourceError> {
let bind_group_resource =
state
.resource_table
.get::<super::binding::WebGpuBindGroup>(bind_group)?;
let render_bundle_encoder_resource =
state
.resource_table
.get::<WebGpuRenderBundleEncoder>(render_bundle_encoder_rid)?;
let start = dynamic_offsets_data_start;
let len = dynamic_offsets_data_length;
// Assert that length and start are both in bounds
assert!(start <= dynamic_offsets_data.len());
assert!(len <= dynamic_offsets_data.len() - start);
let dynamic_offsets_data = &dynamic_offsets_data[start..start + len];
// SAFETY: the raw pointer and length are of the same slice, and that slice
// lives longer than the below function invocation.
unsafe {
wgpu_core::command::bundle_ffi::wgpu_render_bundle_set_bind_group(
&mut render_bundle_encoder_resource.0.borrow_mut(),
index,
bind_group_resource.1,
dynamic_offsets_data.as_ptr(),
dynamic_offsets_data.len(),
);
}
Ok(WebGpuResult::empty())
}
#[op2]
#[serde]
pub fn op_webgpu_render_bundle_encoder_push_debug_group(
state: &mut OpState,
#[smi] render_bundle_encoder_rid: ResourceId,
#[string] group_label: &str,
) -> Result<WebGpuResult, ResourceError> {
let render_bundle_encoder_resource =
state
.resource_table
.get::<WebGpuRenderBundleEncoder>(render_bundle_encoder_rid)?;
let label = std::ffi::CString::new(group_label).unwrap();
// SAFETY: the string the raw pointer points to lives longer than the below
// function invocation.
unsafe {
wgpu_core::command::bundle_ffi::wgpu_render_bundle_push_debug_group(
&mut render_bundle_encoder_resource.0.borrow_mut(),
label.as_ptr(),
);
}
Ok(WebGpuResult::empty())
}
#[op2]
#[serde]
pub fn op_webgpu_render_bundle_encoder_pop_debug_group(
state: &mut OpState,
#[smi] render_bundle_encoder_rid: ResourceId,
) -> Result<WebGpuResult, ResourceError> {
let render_bundle_encoder_resource =
state
.resource_table
.get::<WebGpuRenderBundleEncoder>(render_bundle_encoder_rid)?;
wgpu_core::command::bundle_ffi::wgpu_render_bundle_pop_debug_group(
&mut render_bundle_encoder_resource.0.borrow_mut(),
);
Ok(WebGpuResult::empty())
}
#[op2]
#[serde]
pub fn op_webgpu_render_bundle_encoder_insert_debug_marker(
state: &mut OpState,
#[smi] render_bundle_encoder_rid: ResourceId,
#[string] marker_label: &str,
) -> Result<WebGpuResult, ResourceError> {
let render_bundle_encoder_resource =
state
.resource_table
.get::<WebGpuRenderBundleEncoder>(render_bundle_encoder_rid)?;
let label = std::ffi::CString::new(marker_label).unwrap();
// SAFETY: the string the raw pointer points to lives longer than the below
// function invocation.
unsafe {
wgpu_core::command::bundle_ffi::wgpu_render_bundle_insert_debug_marker(
&mut render_bundle_encoder_resource.0.borrow_mut(),
label.as_ptr(),
);
}
Ok(WebGpuResult::empty())
}
#[op2]
#[serde]
pub fn op_webgpu_render_bundle_encoder_set_pipeline(
state: &mut OpState,
#[smi] render_bundle_encoder_rid: ResourceId,
#[smi] pipeline: ResourceId,
) -> Result<WebGpuResult, ResourceError> {
let render_pipeline_resource =
state
.resource_table
.get::<super::pipeline::WebGpuRenderPipeline>(pipeline)?;
let render_bundle_encoder_resource =
state
.resource_table
.get::<WebGpuRenderBundleEncoder>(render_bundle_encoder_rid)?;
wgpu_core::command::bundle_ffi::wgpu_render_bundle_set_pipeline(
&mut render_bundle_encoder_resource.0.borrow_mut(),
render_pipeline_resource.1,
);
Ok(WebGpuResult::empty())
}
#[op2]
#[serde]
pub fn op_webgpu_render_bundle_encoder_set_index_buffer(
state: &mut OpState,
#[smi] render_bundle_encoder_rid: ResourceId,
#[smi] buffer: ResourceId,
#[serde] index_format: wgpu_types::IndexFormat,
#[number] offset: u64,
#[number] size: u64,
) -> Result<WebGpuResult, BundleError> {
let buffer_resource = state
.resource_table
.get::<super::buffer::WebGpuBuffer>(buffer)?;
let render_bundle_encoder_resource =
state
.resource_table
.get::<WebGpuRenderBundleEncoder>(render_bundle_encoder_rid)?;
let size =
Some(std::num::NonZeroU64::new(size).ok_or(BundleError::InvalidSize)?);
render_bundle_encoder_resource
.0
.borrow_mut()
.set_index_buffer(buffer_resource.1, index_format, offset, size);
Ok(WebGpuResult::empty())
}
#[op2]
#[serde]
pub fn op_webgpu_render_bundle_encoder_set_vertex_buffer(
state: &mut OpState,
#[smi] render_bundle_encoder_rid: ResourceId,
slot: u32,
#[smi] buffer: ResourceId,
#[number] offset: u64,
#[number] size: Option<u64>,
) -> Result<WebGpuResult, BundleError> {
let buffer_resource = state
.resource_table
.get::<super::buffer::WebGpuBuffer>(buffer)?;
let render_bundle_encoder_resource =
state
.resource_table
.get::<WebGpuRenderBundleEncoder>(render_bundle_encoder_rid)?;
let size = if let Some(size) = size {
Some(std::num::NonZeroU64::new(size).ok_or(BundleError::InvalidSize)?)
} else {
None
};
wgpu_core::command::bundle_ffi::wgpu_render_bundle_set_vertex_buffer(
&mut render_bundle_encoder_resource.0.borrow_mut(),
slot,
buffer_resource.1,
offset,
size,
);
Ok(WebGpuResult::empty())
}
#[op2]
#[serde]
pub fn op_webgpu_render_bundle_encoder_draw(
state: &mut OpState,
#[smi] render_bundle_encoder_rid: ResourceId,
vertex_count: u32,
instance_count: u32,
first_vertex: u32,
first_instance: u32,
) -> Result<WebGpuResult, ResourceError> {
let render_bundle_encoder_resource =
state
.resource_table
.get::<WebGpuRenderBundleEncoder>(render_bundle_encoder_rid)?;
wgpu_core::command::bundle_ffi::wgpu_render_bundle_draw(
&mut render_bundle_encoder_resource.0.borrow_mut(),
vertex_count,
instance_count,
first_vertex,
first_instance,
);
Ok(WebGpuResult::empty())
}
#[op2]
#[serde]
pub fn op_webgpu_render_bundle_encoder_draw_indexed(
state: &mut OpState,
#[smi] render_bundle_encoder_rid: ResourceId,
index_count: u32,
instance_count: u32,
first_index: u32,
base_vertex: i32,
first_instance: u32,
) -> Result<WebGpuResult, ResourceError> {
let render_bundle_encoder_resource =
state
.resource_table
.get::<WebGpuRenderBundleEncoder>(render_bundle_encoder_rid)?;
wgpu_core::command::bundle_ffi::wgpu_render_bundle_draw_indexed(
&mut render_bundle_encoder_resource.0.borrow_mut(),
index_count,
instance_count,
first_index,
base_vertex,
first_instance,
);
Ok(WebGpuResult::empty())
}
#[op2]
#[serde]
pub fn op_webgpu_render_bundle_encoder_draw_indirect(
state: &mut OpState,
#[smi] render_bundle_encoder_rid: ResourceId,
#[smi] indirect_buffer: ResourceId,
#[number] indirect_offset: u64,
) -> Result<WebGpuResult, ResourceError> {
let buffer_resource = state
.resource_table
.get::<super::buffer::WebGpuBuffer>(indirect_buffer)?;
let render_bundle_encoder_resource =
state
.resource_table
.get::<WebGpuRenderBundleEncoder>(render_bundle_encoder_rid)?;
wgpu_core::command::bundle_ffi::wgpu_render_bundle_draw_indirect(
&mut render_bundle_encoder_resource.0.borrow_mut(),
buffer_resource.1,
indirect_offset,
);
Ok(WebGpuResult::empty())
}

View file

@ -1,5 +1,4 @@
// Copyright 2018-2025 the Deno authors. MIT license.
use std::cell::RefCell;
use std::ffi::c_void;
#[cfg(any(
target_os = "linux",
@ -9,11 +8,17 @@ use std::ffi::c_void;
))]
use std::ptr::NonNull;
use deno_core::cppgc::SameObject;
use deno_core::op2;
use deno_core::v8;
use deno_core::v8::Local;
use deno_core::v8::Value;
use deno_core::FromV8;
use deno_core::GarbageCollected;
use deno_core::OpState;
use deno_core::ResourceId;
use deno_error::JsErrorBox;
use crate::surface::WebGpuSurface;
use crate::surface::GPUCanvasContext;
#[derive(Debug, thiserror::Error, deno_error::JsError)]
pub enum ByowError {
@ -65,21 +70,33 @@ pub enum ByowError {
NSViewDisplay,
}
#[op2(fast)]
#[smi]
pub fn op_webgpu_surface_create(
// TODO(@littledivy): This will extend `OffscreenCanvas` when we add it.
pub struct UnsafeWindowSurface {
pub id: wgpu_core::id::SurfaceId,
pub width: RefCell<u32>,
pub height: RefCell<u32>,
pub context: SameObject<GPUCanvasContext>,
}
impl GarbageCollected for UnsafeWindowSurface {}
#[op2]
impl UnsafeWindowSurface {
#[constructor]
#[cppgc]
fn new(
state: &mut OpState,
#[string] system: &str,
p1: *const c_void,
p2: *const c_void,
) -> Result<ResourceId, ByowError> {
#[from_v8] options: UnsafeWindowSurfaceOptions,
) -> Result<UnsafeWindowSurface, ByowError> {
let instance = state
.try_borrow::<super::Instance>()
.ok_or(ByowError::WebGPUNotInitiated)?;
// Security note:
//
// The `p1` and `p2` parameters are pointers to platform-specific window
// handles.
// The `window_handle` and `display_handle` options are pointers to
// platform-specific window handles.
//
// The code below works under the assumption that:
//
@ -89,22 +106,120 @@ pub fn op_webgpu_surface_create(
// - `*const c_void` deserizalizes null and v8::External.
//
// - Only FFI can export v8::External to user code.
if p1.is_null() {
if options.window_handle.is_null() {
return Err(ByowError::InvalidParameters);
}
let (win_handle, display_handle) = raw_window(system, p1, p2)?;
let (win_handle, display_handle) = raw_window(
options.system,
options.window_handle,
options.display_handle,
)?;
// SAFETY: see above comment
let surface = unsafe {
let id = unsafe {
instance
.instance_create_surface(display_handle, win_handle, None)
.map_err(ByowError::CreateSurface)?
};
let rid = state
.resource_table
.add(WebGpuSurface(instance.clone(), surface));
Ok(rid)
Ok(UnsafeWindowSurface {
id,
width: RefCell::new(options.width),
height: RefCell::new(options.height),
context: SameObject::new(),
})
}
#[global]
fn get_context(&self, scope: &mut v8::HandleScope) -> v8::Global<v8::Object> {
self.context.get(scope, |_| GPUCanvasContext {
surface_id: self.id,
width: self.width.clone(),
height: self.height.clone(),
config: RefCell::new(None),
texture: RefCell::new(None),
})
}
#[nofast]
fn present(&self, scope: &mut v8::HandleScope) -> Result<(), JsErrorBox> {
let Some(context) = self.context.value(scope) else {
return Err(JsErrorBox::type_error("getContext was never called"));
};
context.present().map_err(JsErrorBox::from_err)
}
}
struct UnsafeWindowSurfaceOptions {
system: UnsafeWindowSurfaceSystem,
window_handle: *const c_void,
display_handle: *const c_void,
width: u32,
height: u32,
}
#[derive(Eq, PartialEq)]
enum UnsafeWindowSurfaceSystem {
Cocoa,
Win32,
X11,
Wayland,
}
impl<'a> FromV8<'a> for UnsafeWindowSurfaceOptions {
type Error = JsErrorBox;
fn from_v8(
scope: &mut v8::HandleScope<'a>,
value: Local<'a, Value>,
) -> Result<Self, Self::Error> {
let obj = value.try_cast::<v8::Object>().unwrap();
let key = v8::String::new(scope, "system").unwrap();
let val = obj.get(scope, key.into()).unwrap();
let s = String::from_v8(scope, val).unwrap();
let system = match s.as_str() {
"cocoa" => UnsafeWindowSurfaceSystem::Cocoa,
"win32" => UnsafeWindowSurfaceSystem::Win32,
"x11" => UnsafeWindowSurfaceSystem::X11,
"wayland" => UnsafeWindowSurfaceSystem::Wayland,
_ => {
return Err(JsErrorBox::type_error(format!(
"Invalid system kind '{s}'"
)))
}
};
let key = v8::String::new(scope, "windowHandle").unwrap();
let val = obj.get(scope, key.into()).unwrap();
let Some(window_handle) = deno_core::_ops::to_external_option(&val) else {
return Err(JsErrorBox::type_error("expected external"));
};
let key = v8::String::new(scope, "displayHandle").unwrap();
let val = obj.get(scope, key.into()).unwrap();
let Some(display_handle) = deno_core::_ops::to_external_option(&val) else {
return Err(JsErrorBox::type_error("expected external"));
};
let key = v8::String::new(scope, "width").unwrap();
let val = obj.get(scope, key.into()).unwrap();
let width = deno_core::convert::Number::<u32>::from_v8(scope, val)?.0;
let key = v8::String::new(scope, "height").unwrap();
let val = obj.get(scope, key.into()).unwrap();
let height = deno_core::convert::Number::<u32>::from_v8(scope, val)?.0;
Ok(Self {
system,
window_handle,
display_handle,
width,
height,
})
}
}
type RawHandles = (
@ -114,11 +229,11 @@ type RawHandles = (
#[cfg(target_os = "macos")]
fn raw_window(
system: &str,
system: UnsafeWindowSurfaceSystem,
_ns_window: *const c_void,
ns_view: *const c_void,
) -> Result<RawHandles, ByowError> {
if system != "cocoa" {
if system != UnsafeWindowSurfaceSystem::Cocoa {
return Err(ByowError::InvalidSystem);
}
@ -136,12 +251,12 @@ fn raw_window(
#[cfg(target_os = "windows")]
fn raw_window(
system: &str,
system: UnsafeWindowSurfaceSystem,
window: *const c_void,
hinstance: *const c_void,
) -> Result<RawHandles, ByowError> {
use raw_window_handle::WindowsDisplayHandle;
if system != "win32" {
if system != UnsafeWindowSurfaceSystem::Win32 {
return Err(ByowError::InvalidSystem);
}
@ -162,12 +277,12 @@ fn raw_window(
#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "openbsd"))]
fn raw_window(
system: &str,
system: UnsafeWindowSurfaceSystem,
window: *const c_void,
display: *const c_void,
) -> Result<RawHandles, ByowError> {
let (win_handle, display_handle);
if system == "x11" {
if system == UnsafeWindowSurfaceSystem::X11 {
win_handle = raw_window_handle::RawWindowHandle::Xlib(
raw_window_handle::XlibWindowHandle::new(window as *mut c_void as _),
);
@ -178,7 +293,7 @@ fn raw_window(
0,
),
);
} else if system == "wayland" {
} else if system == UnsafeWindowSurfaceSystem::Wayland {
win_handle = raw_window_handle::RawWindowHandle::Wayland(
raw_window_handle::WaylandWindowHandle::new(
NonNull::new(window as *mut c_void).ok_or(ByowError::NullWindow)?,
@ -205,7 +320,7 @@ fn raw_window(
target_os = "openbsd",
)))]
fn raw_window(
_system: &str,
_system: UnsafeWindowSurfaceSystem,
_window: *const c_void,
_display: *const c_void,
) -> Result<RawHandles, deno_error::JsErrorBox> {

View file

@ -0,0 +1,46 @@
// Copyright 2018-2025 the Deno authors. MIT license.
use deno_core::op2;
use deno_core::GarbageCollected;
use deno_core::WebIDL;
use crate::Instance;
pub struct GPUCommandBuffer {
pub instance: Instance,
pub id: wgpu_core::id::CommandBufferId,
pub label: String,
}
impl Drop for GPUCommandBuffer {
fn drop(&mut self) {
self.instance.command_buffer_drop(self.id);
}
}
impl deno_core::webidl::WebIdlInterfaceConverter for GPUCommandBuffer {
const NAME: &'static str = "GPUCommandBuffer";
}
impl GarbageCollected for GPUCommandBuffer {}
#[op2]
impl GPUCommandBuffer {
#[getter]
#[string]
fn label(&self) -> String {
self.label.clone()
}
#[setter]
#[string]
fn label(&self, #[webidl] _label: String) {
// TODO(@crowlKats): no-op, needs wpgu to implement changing the label
}
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPUCommandBufferDescriptor {
#[webidl(default = String::new())]
pub label: String,
}

View file

@ -2,633 +2,410 @@
use std::borrow::Cow;
use std::cell::RefCell;
use std::rc::Rc;
use deno_core::error::ResourceError;
use deno_core::cppgc::Ptr;
use deno_core::op2;
use deno_core::OpState;
use deno_core::Resource;
use deno_core::ResourceId;
use serde::Deserialize;
use deno_core::GarbageCollected;
use deno_core::WebIDL;
use deno_error::JsErrorBox;
use wgpu_core::command::ImageCopyBuffer;
use wgpu_core::command::PassChannel;
use super::error::WebGpuResult;
use crate::WebGpuQuerySet;
use crate::buffer::GPUBuffer;
use crate::command_buffer::GPUCommandBuffer;
use crate::compute_pass::GPUComputePassEncoder;
use crate::queue::GPUTexelCopyTextureInfo;
use crate::render_pass::GPULoadOp;
use crate::render_pass::GPURenderPassEncoder;
use crate::render_pass::GPUStoreOp;
use crate::webidl::GPUExtent3D;
use crate::Instance;
pub(crate) struct WebGpuCommandEncoder(
pub(crate) super::Instance,
pub(crate) wgpu_core::id::CommandEncoderId, // TODO: should maybe be option?
);
impl Resource for WebGpuCommandEncoder {
fn name(&self) -> Cow<str> {
"webGPUCommandEncoder".into()
}
pub struct GPUCommandEncoder {
pub instance: Instance,
pub error_handler: super::error::ErrorHandler,
fn close(self: Rc<Self>) {
gfx_select!(self.1 => self.0.command_encoder_drop(self.1));
pub id: wgpu_core::id::CommandEncoderId,
pub label: String,
}
impl Drop for GPUCommandEncoder {
fn drop(&mut self) {
self.instance.command_encoder_drop(self.id);
}
}
pub(crate) struct WebGpuCommandBuffer(
pub(crate) super::Instance,
pub(crate) RefCell<Option<wgpu_core::id::CommandBufferId>>,
);
impl Resource for WebGpuCommandBuffer {
fn name(&self) -> Cow<str> {
"webGPUCommandBuffer".into()
}
fn close(self: Rc<Self>) {
if let Some(id) = *self.1.borrow() {
gfx_select!(id => self.0.command_buffer_drop(id));
}
}
}
impl GarbageCollected for GPUCommandEncoder {}
#[op2]
#[serde]
pub fn op_webgpu_create_command_encoder(
state: &mut OpState,
#[smi] device_rid: ResourceId,
#[string] label: Cow<str>,
) -> Result<WebGpuResult, ResourceError> {
let instance = state.borrow::<super::Instance>();
let device_resource = state
.resource_table
.get::<super::WebGpuDevice>(device_rid)?;
let device = device_resource.1;
impl GPUCommandEncoder {
#[getter]
#[string]
fn label(&self) -> String {
self.label.clone()
}
#[setter]
#[string]
fn label(&self, #[webidl] _label: String) {
// TODO(@crowlKats): no-op, needs wpgu to implement changing the label
}
let descriptor = wgpu_types::CommandEncoderDescriptor { label: Some(label) };
gfx_put!(device => instance.device_create_command_encoder(
device,
&descriptor,
None
) => state, WebGpuCommandEncoder)
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GpuRenderPassColorAttachment {
view: ResourceId,
resolve_target: Option<ResourceId>,
clear_value: Option<wgpu_types::Color>,
load_op: wgpu_core::command::LoadOp,
store_op: wgpu_core::command::StoreOp,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GpuRenderPassDepthStencilAttachment {
view: ResourceId,
depth_clear_value: Option<f32>,
depth_load_op: Option<wgpu_core::command::LoadOp>,
depth_store_op: Option<wgpu_core::command::StoreOp>,
depth_read_only: bool,
stencil_clear_value: u32,
stencil_load_op: Option<wgpu_core::command::LoadOp>,
stencil_store_op: Option<wgpu_core::command::StoreOp>,
stencil_read_only: bool,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GPURenderPassTimestampWrites {
query_set: ResourceId,
beginning_of_pass_write_index: Option<u32>,
end_of_pass_write_index: Option<u32>,
}
#[op2]
#[serde]
pub fn op_webgpu_command_encoder_begin_render_pass(
state: &mut OpState,
#[smi] command_encoder_rid: ResourceId,
#[string] label: Cow<str>,
#[serde] color_attachments: Vec<Option<GpuRenderPassColorAttachment>>,
#[serde] depth_stencil_attachment: Option<
GpuRenderPassDepthStencilAttachment,
>,
#[smi] occlusion_query_set: Option<ResourceId>,
#[serde] timestamp_writes: Option<GPURenderPassTimestampWrites>,
) -> Result<WebGpuResult, ResourceError> {
let command_encoder_resource = state
.resource_table
.get::<WebGpuCommandEncoder>(command_encoder_rid)?;
let color_attachments = color_attachments
#[required(1)]
#[cppgc]
fn begin_render_pass(
&self,
#[webidl] descriptor: crate::render_pass::GPURenderPassDescriptor,
) -> Result<GPURenderPassEncoder, JsErrorBox> {
let color_attachments = Cow::Owned(
descriptor
.color_attachments
.into_iter()
.map(|color_attachment| {
let rp_at = if let Some(at) = color_attachment.as_ref() {
let texture_view_resource =
state
.resource_table
.get::<super::texture::WebGpuTextureView>(at.view)?;
let resolve_target = at
.resolve_target
.map(|rid| {
state
.resource_table
.get::<super::texture::WebGpuTextureView>(rid)
})
.transpose()?
.map(|texture| texture.1);
Some(wgpu_core::command::RenderPassColorAttachment {
view: texture_view_resource.1,
resolve_target,
channel: wgpu_core::command::PassChannel {
load_op: at.load_op,
store_op: at.store_op,
clear_value: at.clear_value.unwrap_or_default(),
.map(|attachment| {
attachment.into_option().map(|attachment| {
wgpu_core::command::RenderPassColorAttachment {
view: attachment.view.id,
resolve_target: attachment.resolve_target.map(|target| target.id),
channel: PassChannel {
load_op: attachment.load_op.into(),
store_op: attachment.store_op.into(),
clear_value: attachment
.clear_value
.map(Into::into)
.unwrap_or_default(),
read_only: false,
},
}
})
} else {
None
};
Ok(rp_at)
})
.collect::<Result<Vec<_>, ResourceError>>()?;
.collect::<Vec<_>>(),
);
let mut processed_depth_stencil_attachment = None;
let depth_stencil_attachment =
descriptor.depth_stencil_attachment.map(|attachment| {
if attachment.depth_load_op.as_ref().is_some_and(|op| matches!(op, GPULoadOp::Clear)) && attachment.depth_clear_value.is_none() {
return Err(JsErrorBox::type_error(r#"'depthClearValue' must be specified when 'depthLoadOp' is "clear""#));
}
if let Some(attachment) = depth_stencil_attachment {
let texture_view_resource =
state
.resource_table
.get::<super::texture::WebGpuTextureView>(attachment.view)?;
processed_depth_stencil_attachment =
Some(wgpu_core::command::RenderPassDepthStencilAttachment {
view: texture_view_resource.1,
depth: wgpu_core::command::PassChannel {
load_op: attachment
.depth_load_op
.unwrap_or(wgpu_core::command::LoadOp::Load),
Ok(wgpu_core::command::RenderPassDepthStencilAttachment {
view: attachment.view.id,
depth: PassChannel {
load_op: attachment.depth_load_op.unwrap_or(GPULoadOp::Load).into(),
store_op: attachment
.depth_store_op
.unwrap_or(wgpu_core::command::StoreOp::Store),
// In "01_webgpu.js", `depthLoadOp` is cheked to ensure its value is not "clear"
// when `depthClearValue` is undefined, so the default 0.0 doesn't matter.
clear_value: attachment.depth_clear_value.unwrap_or(0.0),
.unwrap_or(GPUStoreOp::Store)
.into(),
clear_value: attachment.depth_clear_value.unwrap_or_default(),
read_only: attachment.depth_read_only,
},
stencil: wgpu_core::command::PassChannel {
stencil: PassChannel {
load_op: attachment
.stencil_load_op
.unwrap_or(wgpu_core::command::LoadOp::Load),
.unwrap_or(GPULoadOp::Load)
.into(),
store_op: attachment
.stencil_store_op
.unwrap_or(wgpu_core::command::StoreOp::Store),
.unwrap_or(GPUStoreOp::Store)
.into(),
clear_value: attachment.stencil_clear_value,
read_only: attachment.stencil_read_only,
},
})
}).transpose()?;
let timestamp_writes =
descriptor.timestamp_writes.map(|timestamp_writes| {
wgpu_core::command::PassTimestampWrites {
query_set: timestamp_writes.query_set.id,
beginning_of_pass_write_index: timestamp_writes
.beginning_of_pass_write_index,
end_of_pass_write_index: timestamp_writes.end_of_pass_write_index,
}
});
let wgpu_descriptor = wgpu_core::command::RenderPassDescriptor {
label: Some(Cow::Owned(descriptor.label.clone())),
color_attachments,
depth_stencil_attachment: depth_stencil_attachment.as_ref(),
timestamp_writes: timestamp_writes.as_ref(),
occlusion_query_set: descriptor
.occlusion_query_set
.map(|query_set| query_set.id),
};
let (render_pass, err) = self
.instance
.command_encoder_create_render_pass(self.id, &wgpu_descriptor);
self.error_handler.push_error(err);
Ok(GPURenderPassEncoder {
instance: self.instance.clone(),
error_handler: self.error_handler.clone(),
render_pass: RefCell::new(render_pass),
label: descriptor.label,
})
}
let timestamp_writes = if let Some(timestamp_writes) = timestamp_writes {
let query_set_resource = state
.resource_table
.get::<WebGpuQuerySet>(timestamp_writes.query_set)?;
let query_set = query_set_resource.1;
Some(wgpu_core::command::RenderPassTimestampWrites {
query_set,
#[cppgc]
fn begin_compute_pass(
&self,
#[webidl] descriptor: crate::compute_pass::GPUComputePassDescriptor,
) -> GPUComputePassEncoder {
let timestamp_writes =
descriptor.timestamp_writes.map(|timestamp_writes| {
wgpu_core::command::PassTimestampWrites {
query_set: timestamp_writes.query_set.id,
beginning_of_pass_write_index: timestamp_writes
.beginning_of_pass_write_index,
end_of_pass_write_index: timestamp_writes.end_of_pass_write_index,
})
} else {
None
};
}
});
let occlusion_query_set_resource = occlusion_query_set
.map(|rid| state.resource_table.get::<WebGpuQuerySet>(rid))
.transpose()?
.map(|query_set| query_set.1);
let descriptor = wgpu_core::command::RenderPassDescriptor {
label: Some(label),
color_attachments: Cow::from(color_attachments),
depth_stencil_attachment: processed_depth_stencil_attachment.as_ref(),
timestamp_writes: timestamp_writes.as_ref(),
occlusion_query_set: occlusion_query_set_resource,
};
let render_pass = wgpu_core::command::RenderPass::new(
command_encoder_resource.1,
&descriptor,
);
let rid = state
.resource_table
.add(super::render_pass::WebGpuRenderPass(RefCell::new(
render_pass,
)));
Ok(WebGpuResult::rid(rid))
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GPUComputePassTimestampWrites {
query_set: ResourceId,
beginning_of_pass_write_index: Option<u32>,
end_of_pass_write_index: Option<u32>,
}
#[op2]
#[serde]
pub fn op_webgpu_command_encoder_begin_compute_pass(
state: &mut OpState,
#[smi] command_encoder_rid: ResourceId,
#[string] label: Cow<str>,
#[serde] timestamp_writes: Option<GPUComputePassTimestampWrites>,
) -> Result<WebGpuResult, ResourceError> {
let command_encoder_resource = state
.resource_table
.get::<WebGpuCommandEncoder>(command_encoder_rid)?;
let timestamp_writes = if let Some(timestamp_writes) = timestamp_writes {
let query_set_resource = state
.resource_table
.get::<WebGpuQuerySet>(timestamp_writes.query_set)?;
let query_set = query_set_resource.1;
Some(wgpu_core::command::ComputePassTimestampWrites {
query_set,
beginning_of_pass_write_index: timestamp_writes
.beginning_of_pass_write_index,
end_of_pass_write_index: timestamp_writes.end_of_pass_write_index,
})
} else {
None
};
let descriptor = wgpu_core::command::ComputePassDescriptor {
label: Some(label),
let wgpu_descriptor = wgpu_core::command::ComputePassDescriptor {
label: Some(Cow::Owned(descriptor.label.clone())),
timestamp_writes: timestamp_writes.as_ref(),
};
let compute_pass = wgpu_core::command::ComputePass::new(
command_encoder_resource.1,
&descriptor,
);
let (compute_pass, err) = self
.instance
.command_encoder_create_compute_pass(self.id, &wgpu_descriptor);
let rid = state
.resource_table
.add(super::compute_pass::WebGpuComputePass(RefCell::new(
compute_pass,
)));
self.error_handler.push_error(err);
Ok(WebGpuResult::rid(rid))
}
GPUComputePassEncoder {
instance: self.instance.clone(),
error_handler: self.error_handler.clone(),
compute_pass: RefCell::new(compute_pass),
label: descriptor.label,
}
}
#[op2]
#[serde]
pub fn op_webgpu_command_encoder_copy_buffer_to_buffer(
state: &mut OpState,
#[smi] command_encoder_rid: ResourceId,
#[smi] source: ResourceId,
#[number] source_offset: u64,
#[smi] destination: ResourceId,
#[number] destination_offset: u64,
#[number] size: u64,
) -> Result<WebGpuResult, ResourceError> {
let instance = state.borrow::<super::Instance>();
let command_encoder_resource = state
.resource_table
.get::<WebGpuCommandEncoder>(command_encoder_rid)?;
let command_encoder = command_encoder_resource.1;
let source_buffer_resource = state
.resource_table
.get::<super::buffer::WebGpuBuffer>(source)?;
let source_buffer = source_buffer_resource.1;
let destination_buffer_resource =
state
.resource_table
.get::<super::buffer::WebGpuBuffer>(destination)?;
let destination_buffer = destination_buffer_resource.1;
gfx_ok!(command_encoder => instance.command_encoder_copy_buffer_to_buffer(
command_encoder,
source_buffer,
#[required(5)]
fn copy_buffer_to_buffer(
&self,
#[webidl] source: Ptr<GPUBuffer>,
#[webidl(options(enforce_range = true))] source_offset: u64,
#[webidl] destination: Ptr<GPUBuffer>,
#[webidl(options(enforce_range = true))] destination_offset: u64,
#[webidl(options(enforce_range = true))] size: u64,
) {
let err = self
.instance
.command_encoder_copy_buffer_to_buffer(
self.id,
source.id,
source_offset,
destination_buffer,
destination.id,
destination_offset,
size
))
}
size,
)
.err();
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GpuImageCopyBuffer {
buffer: ResourceId,
offset: u64,
bytes_per_row: Option<u32>,
rows_per_image: Option<u32>,
}
self.error_handler.push_error(err);
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GpuImageCopyTexture {
pub texture: ResourceId,
pub mip_level: u32,
pub origin: wgpu_types::Origin3d,
pub aspect: wgpu_types::TextureAspect,
}
#[op2]
#[serde]
pub fn op_webgpu_command_encoder_copy_buffer_to_texture(
state: &mut OpState,
#[smi] command_encoder_rid: ResourceId,
#[serde] source: GpuImageCopyBuffer,
#[serde] destination: GpuImageCopyTexture,
#[serde] copy_size: wgpu_types::Extent3d,
) -> Result<WebGpuResult, ResourceError> {
let instance = state.borrow::<super::Instance>();
let command_encoder_resource = state
.resource_table
.get::<WebGpuCommandEncoder>(command_encoder_rid)?;
let command_encoder = command_encoder_resource.1;
let source_buffer_resource =
state
.resource_table
.get::<super::buffer::WebGpuBuffer>(source.buffer)?;
let destination_texture_resource =
state
.resource_table
.get::<super::texture::WebGpuTexture>(destination.texture)?;
let source = wgpu_core::command::ImageCopyBuffer {
buffer: source_buffer_resource.1,
#[required(3)]
fn copy_buffer_to_texture(
&self,
#[webidl] source: GPUTexelCopyBufferInfo,
#[webidl] destination: GPUTexelCopyTextureInfo,
#[webidl] copy_size: GPUExtent3D,
) {
let source = ImageCopyBuffer {
buffer: source.buffer.id,
layout: wgpu_types::ImageDataLayout {
offset: source.offset,
bytes_per_row: source.bytes_per_row,
rows_per_image: source.rows_per_image,
},
};
let destination = wgpu_core::command::ImageCopyTexture {
texture: destination_texture_resource.id,
let destination = wgpu_types::ImageCopyTexture {
texture: destination.texture.id,
mip_level: destination.mip_level,
origin: destination.origin,
aspect: destination.aspect,
origin: destination.origin.into(),
aspect: destination.aspect.into(),
};
gfx_ok!(command_encoder => instance.command_encoder_copy_buffer_to_texture(
command_encoder,
let err = self
.instance
.command_encoder_copy_buffer_to_texture(
self.id,
&source,
&destination,
&copy_size
))
}
&copy_size.into(),
)
.err();
#[op2]
#[serde]
pub fn op_webgpu_command_encoder_copy_texture_to_buffer(
state: &mut OpState,
#[smi] command_encoder_rid: ResourceId,
#[serde] source: GpuImageCopyTexture,
#[serde] destination: GpuImageCopyBuffer,
#[serde] copy_size: wgpu_types::Extent3d,
) -> Result<WebGpuResult, ResourceError> {
let instance = state.borrow::<super::Instance>();
let command_encoder_resource = state
.resource_table
.get::<WebGpuCommandEncoder>(command_encoder_rid)?;
let command_encoder = command_encoder_resource.1;
let source_texture_resource =
state
.resource_table
.get::<super::texture::WebGpuTexture>(source.texture)?;
let destination_buffer_resource =
state
.resource_table
.get::<super::buffer::WebGpuBuffer>(destination.buffer)?;
self.error_handler.push_error(err);
}
let source = wgpu_core::command::ImageCopyTexture {
texture: source_texture_resource.id,
#[required(3)]
fn copy_texture_to_buffer(
&self,
#[webidl] source: GPUTexelCopyTextureInfo,
#[webidl] destination: GPUTexelCopyBufferInfo,
#[webidl] copy_size: GPUExtent3D,
) {
let source = wgpu_types::ImageCopyTexture {
texture: source.texture.id,
mip_level: source.mip_level,
origin: source.origin,
aspect: source.aspect,
origin: source.origin.into(),
aspect: source.aspect.into(),
};
let destination = wgpu_core::command::ImageCopyBuffer {
buffer: destination_buffer_resource.1,
let destination = ImageCopyBuffer {
buffer: destination.buffer.id,
layout: wgpu_types::ImageDataLayout {
offset: destination.offset,
bytes_per_row: destination.bytes_per_row,
rows_per_image: destination.rows_per_image,
},
};
gfx_ok!(command_encoder => instance.command_encoder_copy_texture_to_buffer(
command_encoder,
let err = self
.instance
.command_encoder_copy_texture_to_buffer(
self.id,
&source,
&destination,
&copy_size
))
}
&copy_size.into(),
)
.err();
#[op2]
#[serde]
pub fn op_webgpu_command_encoder_copy_texture_to_texture(
state: &mut OpState,
#[smi] command_encoder_rid: ResourceId,
#[serde] source: GpuImageCopyTexture,
#[serde] destination: GpuImageCopyTexture,
#[serde] copy_size: wgpu_types::Extent3d,
) -> Result<WebGpuResult, ResourceError> {
let instance = state.borrow::<super::Instance>();
let command_encoder_resource = state
.resource_table
.get::<WebGpuCommandEncoder>(command_encoder_rid)?;
let command_encoder = command_encoder_resource.1;
let source_texture_resource =
state
.resource_table
.get::<super::texture::WebGpuTexture>(source.texture)?;
let destination_texture_resource =
state
.resource_table
.get::<super::texture::WebGpuTexture>(destination.texture)?;
self.error_handler.push_error(err);
}
let source = wgpu_core::command::ImageCopyTexture {
texture: source_texture_resource.id,
#[required(3)]
fn copy_texture_to_texture(
&self,
#[webidl] source: GPUTexelCopyTextureInfo,
#[webidl] destination: GPUTexelCopyTextureInfo,
#[webidl] copy_size: GPUExtent3D,
) {
let source = wgpu_types::ImageCopyTexture {
texture: source.texture.id,
mip_level: source.mip_level,
origin: source.origin,
aspect: source.aspect,
origin: source.origin.into(),
aspect: source.aspect.into(),
};
let destination = wgpu_core::command::ImageCopyTexture {
texture: destination_texture_resource.id,
let destination = wgpu_types::ImageCopyTexture {
texture: destination.texture.id,
mip_level: destination.mip_level,
origin: destination.origin,
aspect: destination.aspect,
origin: destination.origin.into(),
aspect: destination.aspect.into(),
};
gfx_ok!(command_encoder => instance.command_encoder_copy_texture_to_texture(
command_encoder,
let err = self
.instance
.command_encoder_copy_texture_to_texture(
self.id,
&source,
&destination,
&copy_size
))
}
&copy_size.into(),
)
.err();
#[op2]
#[serde]
pub fn op_webgpu_command_encoder_clear_buffer(
state: &mut OpState,
#[smi] command_encoder_rid: ResourceId,
#[smi] buffer_rid: ResourceId,
#[number] offset: u64,
#[number] size: u64,
) -> Result<WebGpuResult, ResourceError> {
let instance = state.borrow::<super::Instance>();
let command_encoder_resource = state
.resource_table
.get::<WebGpuCommandEncoder>(command_encoder_rid)?;
let command_encoder = command_encoder_resource.1;
let destination_resource = state
.resource_table
.get::<super::buffer::WebGpuBuffer>(buffer_rid)?;
self.error_handler.push_error(err);
}
gfx_ok!(command_encoder => instance.command_encoder_clear_buffer(
command_encoder,
destination_resource.1,
offset,
Some(size)
))
}
#[required(1)]
fn clear_buffer(
&self,
#[webidl] buffer: Ptr<GPUBuffer>,
#[webidl(default = 0, options(enforce_range = true))] offset: u64,
#[webidl(options(enforce_range = true))] size: Option<u64>,
) {
let err = self
.instance
.command_encoder_clear_buffer(self.id, buffer.id, offset, size)
.err();
self.error_handler.push_error(err);
}
#[op2]
#[serde]
pub fn op_webgpu_command_encoder_push_debug_group(
state: &mut OpState,
#[smi] command_encoder_rid: ResourceId,
#[string] group_label: &str,
) -> Result<WebGpuResult, ResourceError> {
let instance = state.borrow::<super::Instance>();
let command_encoder_resource = state
.resource_table
.get::<WebGpuCommandEncoder>(command_encoder_rid)?;
let command_encoder = command_encoder_resource.1;
gfx_ok!(command_encoder => instance.command_encoder_push_debug_group(command_encoder, group_label))
}
#[op2]
#[serde]
pub fn op_webgpu_command_encoder_pop_debug_group(
state: &mut OpState,
#[smi] command_encoder_rid: ResourceId,
) -> Result<WebGpuResult, ResourceError> {
let instance = state.borrow::<super::Instance>();
let command_encoder_resource = state
.resource_table
.get::<WebGpuCommandEncoder>(command_encoder_rid)?;
let command_encoder = command_encoder_resource.1;
gfx_ok!(command_encoder => instance.command_encoder_pop_debug_group(command_encoder))
}
#[op2]
#[serde]
pub fn op_webgpu_command_encoder_insert_debug_marker(
state: &mut OpState,
#[smi] command_encoder_rid: ResourceId,
#[string] marker_label: &str,
) -> Result<WebGpuResult, ResourceError> {
let instance = state.borrow::<super::Instance>();
let command_encoder_resource = state
.resource_table
.get::<WebGpuCommandEncoder>(command_encoder_rid)?;
let command_encoder = command_encoder_resource.1;
gfx_ok!(command_encoder => instance.command_encoder_insert_debug_marker(
command_encoder,
marker_label
))
}
#[op2]
#[serde]
pub fn op_webgpu_command_encoder_write_timestamp(
state: &mut OpState,
#[smi] command_encoder_rid: ResourceId,
#[smi] query_set: ResourceId,
query_index: u32,
) -> Result<WebGpuResult, ResourceError> {
let instance = state.borrow::<super::Instance>();
let command_encoder_resource = state
.resource_table
.get::<WebGpuCommandEncoder>(command_encoder_rid)?;
let command_encoder = command_encoder_resource.1;
let query_set_resource = state
.resource_table
.get::<super::WebGpuQuerySet>(query_set)?;
gfx_ok!(command_encoder => instance.command_encoder_write_timestamp(
command_encoder,
query_set_resource.1,
query_index
))
}
#[op2]
#[serde]
pub fn op_webgpu_command_encoder_resolve_query_set(
state: &mut OpState,
#[smi] command_encoder_rid: ResourceId,
#[smi] query_set: ResourceId,
first_query: u32,
query_count: u32,
#[smi] destination: ResourceId,
#[number] destination_offset: u64,
) -> Result<WebGpuResult, ResourceError> {
let instance = state.borrow::<super::Instance>();
let command_encoder_resource = state
.resource_table
.get::<WebGpuCommandEncoder>(command_encoder_rid)?;
let command_encoder = command_encoder_resource.1;
let query_set_resource = state
.resource_table
.get::<super::WebGpuQuerySet>(query_set)?;
let destination_resource = state
.resource_table
.get::<super::buffer::WebGpuBuffer>(destination)?;
gfx_ok!(command_encoder => instance.command_encoder_resolve_query_set(
command_encoder,
query_set_resource.1,
#[required(5)]
fn resolve_query_set(
&self,
#[webidl] query_set: Ptr<super::query_set::GPUQuerySet>,
#[webidl(options(enforce_range = true))] first_query: u32,
#[webidl(options(enforce_range = true))] query_count: u32,
#[webidl] destination: Ptr<GPUBuffer>,
#[webidl(options(enforce_range = true))] destination_offset: u64,
) {
let err = self
.instance
.command_encoder_resolve_query_set(
self.id,
query_set.id,
first_query,
query_count,
destination_resource.1,
destination_offset
))
destination.id,
destination_offset,
)
.err();
self.error_handler.push_error(err);
}
#[cppgc]
fn finish(
&self,
#[webidl] descriptor: crate::command_buffer::GPUCommandBufferDescriptor,
) -> GPUCommandBuffer {
let wgpu_descriptor = wgpu_types::CommandBufferDescriptor {
label: Some(Cow::Owned(descriptor.label.clone())),
};
let (id, err) = self
.instance
.command_encoder_finish(self.id, &wgpu_descriptor);
self.error_handler.push_error(err);
GPUCommandBuffer {
instance: self.instance.clone(),
id,
label: descriptor.label,
}
}
fn push_debug_group(&self, #[webidl] group_label: String) {
let err = self
.instance
.command_encoder_push_debug_group(self.id, &group_label)
.err();
self.error_handler.push_error(err);
}
#[fast]
fn pop_debug_group(&self) {
let err = self.instance.command_encoder_pop_debug_group(self.id).err();
self.error_handler.push_error(err);
}
fn insert_debug_marker(&self, #[webidl] marker_label: String) {
let err = self
.instance
.command_encoder_insert_debug_marker(self.id, &marker_label)
.err();
self.error_handler.push_error(err);
}
}
#[op2]
#[serde]
pub fn op_webgpu_command_encoder_finish(
state: &mut OpState,
#[smi] command_encoder_rid: ResourceId,
#[string] label: Cow<str>,
) -> Result<WebGpuResult, ResourceError> {
let command_encoder_resource = state
.resource_table
.take::<WebGpuCommandEncoder>(command_encoder_rid)?;
let command_encoder = command_encoder_resource.1;
let instance = state.borrow::<super::Instance>();
let descriptor = wgpu_types::CommandBufferDescriptor { label: Some(label) };
let (val, maybe_err) = gfx_select!(command_encoder => instance.command_encoder_finish(
command_encoder,
&descriptor
));
let rid = state.resource_table.add(WebGpuCommandBuffer(
instance.clone(),
RefCell::new(Some(val)),
));
Ok(WebGpuResult::rid_err(rid, maybe_err))
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPUCommandEncoderDescriptor {
#[webidl(default = String::new())]
pub label: String,
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPUTexelCopyBufferInfo {
pub buffer: Ptr<GPUBuffer>,
#[webidl(default = 0)]
#[options(enforce_range = true)]
offset: u64,
#[options(enforce_range = true)]
bytes_per_row: Option<u32>,
#[options(enforce_range = true)]
rows_per_image: Option<u32>,
}

View file

@ -3,209 +3,231 @@
use std::borrow::Cow;
use std::cell::RefCell;
use deno_core::error::ResourceError;
use deno_core::cppgc::Ptr;
use deno_core::op2;
use deno_core::OpState;
use deno_core::Resource;
use deno_core::ResourceId;
use deno_core::v8;
use deno_core::webidl::IntOptions;
use deno_core::webidl::Nullable;
use deno_core::webidl::WebIdlConverter;
use deno_core::webidl::WebIdlError;
use deno_core::GarbageCollected;
use deno_core::WebIDL;
use super::error::WebGpuResult;
use crate::Instance;
pub(crate) struct WebGpuComputePass(
pub(crate) RefCell<wgpu_core::command::ComputePass>,
);
impl Resource for WebGpuComputePass {
fn name(&self) -> Cow<str> {
"webGPUComputePass".into()
pub struct GPUComputePassEncoder {
pub instance: Instance,
pub error_handler: super::error::ErrorHandler,
pub compute_pass: RefCell<wgpu_core::command::ComputePass>,
pub label: String,
}
impl GarbageCollected for GPUComputePassEncoder {}
#[op2]
impl GPUComputePassEncoder {
#[getter]
#[string]
fn label(&self) -> String {
self.label.clone()
}
#[setter]
#[string]
fn label(&self, #[webidl] _label: String) {
// TODO(@crowlKats): no-op, needs wpgu to implement changing the label
}
fn set_pipeline(
&self,
#[webidl] pipeline: Ptr<crate::compute_pipeline::GPUComputePipeline>,
) {
let err = self
.instance
.compute_pass_set_pipeline(
&mut self.compute_pass.borrow_mut(),
pipeline.id,
)
.err();
self.error_handler.push_error(err);
}
fn dispatch_workgroups(
&self,
#[webidl(options(enforce_range = true))] work_group_count_x: u32,
#[webidl(default = 1, options(enforce_range = true))]
work_group_count_y: u32,
#[webidl(default = 1, options(enforce_range = true))]
work_group_count_z: u32,
) {
let err = self
.instance
.compute_pass_dispatch_workgroups(
&mut self.compute_pass.borrow_mut(),
work_group_count_x,
work_group_count_y,
work_group_count_z,
)
.err();
self.error_handler.push_error(err);
}
fn dispatch_workgroups_indirect(
&self,
#[webidl] indirect_buffer: Ptr<crate::buffer::GPUBuffer>,
#[webidl(options(enforce_range = true))] indirect_offset: u64,
) {
let err = self
.instance
.compute_pass_dispatch_workgroups_indirect(
&mut self.compute_pass.borrow_mut(),
indirect_buffer.id,
indirect_offset,
)
.err();
self.error_handler.push_error(err);
}
#[fast]
fn end(&self) {
let err = self
.instance
.compute_pass_end(&mut self.compute_pass.borrow_mut())
.err();
self.error_handler.push_error(err);
}
fn push_debug_group(&self, #[webidl] group_label: String) {
let err = self
.instance
.compute_pass_push_debug_group(
&mut self.compute_pass.borrow_mut(),
&group_label,
0, // wgpu#975
)
.err();
self.error_handler.push_error(err);
}
#[fast]
fn pop_debug_group(&self) {
let err = self
.instance
.compute_pass_pop_debug_group(&mut self.compute_pass.borrow_mut())
.err();
self.error_handler.push_error(err);
}
fn insert_debug_marker(&self, #[webidl] marker_label: String) {
let err = self
.instance
.compute_pass_insert_debug_marker(
&mut self.compute_pass.borrow_mut(),
&marker_label,
0, // wgpu#975
)
.err();
self.error_handler.push_error(err);
}
fn set_bind_group<'a>(
&self,
scope: &mut v8::HandleScope<'a>,
#[webidl(options(enforce_range = true))] index: u32,
#[webidl] bind_group: Nullable<Ptr<crate::bind_group::GPUBindGroup>>,
dynamic_offsets: v8::Local<'a, v8::Value>,
dynamic_offsets_data_start: v8::Local<'a, v8::Value>,
dynamic_offsets_data_length: v8::Local<'a, v8::Value>,
) -> Result<(), WebIdlError> {
const PREFIX: &str =
"Failed to execute 'setBindGroup' on 'GPUComputePassEncoder'";
let err = if let Ok(uint_32) = dynamic_offsets.try_cast::<v8::Uint32Array>()
{
let start = u64::convert(
scope,
dynamic_offsets_data_start,
Cow::Borrowed(PREFIX),
(|| Cow::Borrowed("Argument 4")).into(),
&IntOptions {
clamp: false,
enforce_range: true,
},
)? as usize;
let len = u32::convert(
scope,
dynamic_offsets_data_length,
Cow::Borrowed(PREFIX),
(|| Cow::Borrowed("Argument 5")).into(),
&IntOptions {
clamp: false,
enforce_range: true,
},
)? as usize;
let ab = uint_32.buffer(scope).unwrap();
let ptr = ab.data().unwrap();
let ab_len = ab.byte_length() / 4;
// SAFETY: compute_pass_set_bind_group internally calls extend_from_slice with this slice
let data =
unsafe { std::slice::from_raw_parts(ptr.as_ptr() as _, ab_len) };
let offsets = &data[start..(start + len)];
self
.instance
.compute_pass_set_bind_group(
&mut self.compute_pass.borrow_mut(),
index,
bind_group.into_option().map(|bind_group| bind_group.id),
offsets,
)
.err()
} else {
let offsets = <Option<Vec<u32>>>::convert(
scope,
dynamic_offsets,
Cow::Borrowed(PREFIX),
(|| Cow::Borrowed("Argument 3")).into(),
&IntOptions {
clamp: false,
enforce_range: true,
},
)?
.unwrap_or_default();
self
.instance
.compute_pass_set_bind_group(
&mut self.compute_pass.borrow_mut(),
index,
bind_group.into_option().map(|bind_group| bind_group.id),
&offsets,
)
.err()
};
self.error_handler.push_error(err);
Ok(())
}
}
#[op2]
#[serde]
pub fn op_webgpu_compute_pass_set_pipeline(
state: &mut OpState,
#[smi] compute_pass_rid: ResourceId,
#[smi] pipeline: ResourceId,
) -> Result<WebGpuResult, ResourceError> {
let compute_pipeline_resource =
state
.resource_table
.get::<super::pipeline::WebGpuComputePipeline>(pipeline)?;
let compute_pass_resource = state
.resource_table
.get::<WebGpuComputePass>(compute_pass_rid)?;
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPUComputePassDescriptor {
#[webidl(default = String::new())]
pub label: String,
wgpu_core::command::compute_commands::wgpu_compute_pass_set_pipeline(
&mut compute_pass_resource.0.borrow_mut(),
compute_pipeline_resource.1,
);
Ok(WebGpuResult::empty())
pub timestamp_writes: Option<GPUComputePassTimestampWrites>,
}
#[op2]
#[serde]
pub fn op_webgpu_compute_pass_dispatch_workgroups(
state: &mut OpState,
#[smi] compute_pass_rid: ResourceId,
x: u32,
y: u32,
z: u32,
) -> Result<WebGpuResult, ResourceError> {
let compute_pass_resource = state
.resource_table
.get::<WebGpuComputePass>(compute_pass_rid)?;
wgpu_core::command::compute_commands::wgpu_compute_pass_dispatch_workgroups(
&mut compute_pass_resource.0.borrow_mut(),
x,
y,
z,
);
Ok(WebGpuResult::empty())
}
#[op2]
#[serde]
pub fn op_webgpu_compute_pass_dispatch_workgroups_indirect(
state: &mut OpState,
#[smi] compute_pass_rid: ResourceId,
#[smi] indirect_buffer: ResourceId,
#[number] indirect_offset: u64,
) -> Result<WebGpuResult, ResourceError> {
let buffer_resource = state
.resource_table
.get::<super::buffer::WebGpuBuffer>(indirect_buffer)?;
let compute_pass_resource = state
.resource_table
.get::<WebGpuComputePass>(compute_pass_rid)?;
wgpu_core::command::compute_commands::wgpu_compute_pass_dispatch_workgroups_indirect(
&mut compute_pass_resource.0.borrow_mut(),
buffer_resource.1,
indirect_offset,
);
Ok(WebGpuResult::empty())
}
#[op2]
#[serde]
pub fn op_webgpu_compute_pass_end(
state: &mut OpState,
#[smi] command_encoder_rid: ResourceId,
#[smi] compute_pass_rid: ResourceId,
) -> Result<WebGpuResult, ResourceError> {
let command_encoder_resource = state
.resource_table
.get::<super::command_encoder::WebGpuCommandEncoder>(
command_encoder_rid,
)?;
let command_encoder = command_encoder_resource.1;
let compute_pass_resource = state
.resource_table
.take::<WebGpuComputePass>(compute_pass_rid)?;
let compute_pass = &compute_pass_resource.0.borrow();
let instance = state.borrow::<super::Instance>();
gfx_ok!(command_encoder => instance.command_encoder_run_compute_pass(
command_encoder,
compute_pass
))
}
#[op2]
#[serde]
pub fn op_webgpu_compute_pass_set_bind_group(
state: &mut OpState,
#[smi] compute_pass_rid: ResourceId,
index: u32,
#[smi] bind_group: ResourceId,
#[buffer] dynamic_offsets_data: &[u32],
#[number] dynamic_offsets_data_start: usize,
#[number] dynamic_offsets_data_length: usize,
) -> Result<WebGpuResult, ResourceError> {
let bind_group_resource =
state
.resource_table
.get::<super::binding::WebGpuBindGroup>(bind_group)?;
let compute_pass_resource = state
.resource_table
.get::<WebGpuComputePass>(compute_pass_rid)?;
let start = dynamic_offsets_data_start;
let len = dynamic_offsets_data_length;
// Assert that length and start are both in bounds
assert!(start <= dynamic_offsets_data.len());
assert!(len <= dynamic_offsets_data.len() - start);
let dynamic_offsets_data: &[u32] = &dynamic_offsets_data[start..start + len];
wgpu_core::command::compute_commands::wgpu_compute_pass_set_bind_group(
&mut compute_pass_resource.0.borrow_mut(),
index,
bind_group_resource.1,
dynamic_offsets_data,
);
Ok(WebGpuResult::empty())
}
#[op2]
#[serde]
pub fn op_webgpu_compute_pass_push_debug_group(
state: &mut OpState,
#[smi] compute_pass_rid: ResourceId,
#[string] group_label: &str,
) -> Result<WebGpuResult, ResourceError> {
let compute_pass_resource = state
.resource_table
.get::<WebGpuComputePass>(compute_pass_rid)?;
wgpu_core::command::compute_commands::wgpu_compute_pass_push_debug_group(
&mut compute_pass_resource.0.borrow_mut(),
group_label,
0, // wgpu#975
);
Ok(WebGpuResult::empty())
}
#[op2]
#[serde]
pub fn op_webgpu_compute_pass_pop_debug_group(
state: &mut OpState,
#[smi] compute_pass_rid: ResourceId,
) -> Result<WebGpuResult, ResourceError> {
let compute_pass_resource = state
.resource_table
.get::<WebGpuComputePass>(compute_pass_rid)?;
wgpu_core::command::compute_commands::wgpu_compute_pass_pop_debug_group(
&mut compute_pass_resource.0.borrow_mut(),
);
Ok(WebGpuResult::empty())
}
#[op2]
#[serde]
pub fn op_webgpu_compute_pass_insert_debug_marker(
state: &mut OpState,
#[smi] compute_pass_rid: ResourceId,
#[string] marker_label: &str,
) -> Result<WebGpuResult, ResourceError> {
let compute_pass_resource = state
.resource_table
.get::<WebGpuComputePass>(compute_pass_rid)?;
wgpu_core::command::compute_commands::wgpu_compute_pass_insert_debug_marker(
&mut compute_pass_resource.0.borrow_mut(),
marker_label,
0, // wgpu#975
);
Ok(WebGpuResult::empty())
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPUComputePassTimestampWrites {
pub query_set: Ptr<crate::query_set::GPUQuerySet>,
#[options(enforce_range = true)]
pub beginning_of_pass_write_index: Option<u32>,
#[options(enforce_range = true)]
pub end_of_pass_write_index: Option<u32>,
}

View file

@ -0,0 +1,82 @@
// Copyright 2018-2025 the Deno authors. MIT license.
use deno_core::cppgc::Ptr;
use deno_core::op2;
use deno_core::webidl::WebIdlInterfaceConverter;
use deno_core::GarbageCollected;
use deno_core::WebIDL;
use indexmap::IndexMap;
use crate::bind_group_layout::GPUBindGroupLayout;
use crate::shader::GPUShaderModule;
use crate::webidl::GPUPipelineLayoutOrGPUAutoLayoutMode;
use crate::Instance;
pub struct GPUComputePipeline {
pub instance: Instance,
pub error_handler: super::error::ErrorHandler,
pub id: wgpu_core::id::ComputePipelineId,
pub label: String,
}
impl Drop for GPUComputePipeline {
fn drop(&mut self) {
self.instance.compute_pipeline_drop(self.id);
}
}
impl WebIdlInterfaceConverter for GPUComputePipeline {
const NAME: &'static str = "GPUComputePipeline";
}
impl GarbageCollected for GPUComputePipeline {}
#[op2]
impl GPUComputePipeline {
#[getter]
#[string]
fn label(&self) -> String {
self.label.clone()
}
#[setter]
#[string]
fn label(&self, #[webidl] _label: String) {
// TODO(@crowlKats): no-op, needs wpgu to implement changing the label
}
#[cppgc]
fn get_bind_group_layout(&self, #[webidl] index: u32) -> GPUBindGroupLayout {
let (id, err) = self
.instance
.compute_pipeline_get_bind_group_layout(self.id, index, None);
self.error_handler.push_error(err);
// TODO: wgpu needs to support retrieving the label
GPUBindGroupLayout {
instance: self.instance.clone(),
id,
label: "".to_string(),
}
}
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPUComputePipelineDescriptor {
#[webidl(default = String::new())]
pub label: String,
pub compute: GPUProgrammableStage,
pub layout: GPUPipelineLayoutOrGPUAutoLayoutMode,
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPUProgrammableStage {
pub module: Ptr<GPUShaderModule>,
pub entry_point: Option<String>,
#[webidl(default = Default::default())]
pub constants: IndexMap<String, f64>,
}

869
ext/webgpu/device.rs Normal file
View file

@ -0,0 +1,869 @@
use std::borrow::Cow;
use std::cell::RefCell;
use std::future::Future;
use std::num::NonZeroU64;
use std::rc::Rc;
use deno_core::cppgc::SameObject;
use deno_core::op2;
use deno_core::v8;
use deno_core::webidl::WebIdlInterfaceConverter;
use deno_core::GarbageCollected;
use deno_error::JsErrorBox;
use wgpu_core::binding_model::BindingResource;
use wgpu_core::pipeline::ProgrammableStageDescriptor;
use wgpu_types::BindingType;
use super::bind_group::GPUBindGroup;
use super::bind_group::GPUBindingResource;
use super::bind_group_layout::GPUBindGroupLayout;
use super::buffer::GPUBuffer;
use super::compute_pipeline::GPUComputePipeline;
use super::pipeline_layout::GPUPipelineLayout;
use super::queue::GPUQueue;
use super::sampler::GPUSampler;
use super::shader::GPUShaderModule;
use super::texture::GPUTexture;
use crate::adapter::GPUAdapterInfo;
use crate::adapter::GPUSupportedFeatures;
use crate::adapter::GPUSupportedLimits;
use crate::command_encoder::GPUCommandEncoder;
use crate::query_set::GPUQuerySet;
use crate::render_bundle::GPURenderBundleEncoder;
use crate::render_pipeline::GPURenderPipeline;
use crate::webidl::features_to_feature_names;
use crate::Instance;
pub struct GPUDevice {
pub instance: Instance,
pub id: wgpu_core::id::DeviceId,
pub adapter: wgpu_core::id::AdapterId,
pub queue: wgpu_core::id::QueueId,
pub label: String,
pub features: SameObject<GPUSupportedFeatures>,
pub limits: SameObject<GPUSupportedLimits>,
pub adapter_info: Rc<SameObject<GPUAdapterInfo>>,
pub queue_obj: SameObject<GPUQueue>,
pub error_handler: super::error::ErrorHandler,
pub lost_receiver:
tokio::sync::Mutex<Option<tokio::sync::oneshot::Receiver<()>>>,
}
impl Drop for GPUDevice {
fn drop(&mut self) {
self.instance.device_drop(self.id);
}
}
impl WebIdlInterfaceConverter for GPUDevice {
const NAME: &'static str = "GPUDevice";
}
impl GarbageCollected for GPUDevice {}
// TODO: extend EventTarget
// TODO: setEventTargetData on instance
#[op2]
impl GPUDevice {
#[getter]
#[string]
fn label(&self) -> String {
self.label.clone()
}
#[setter]
#[string]
fn label(&self, #[webidl] _label: String) {
// TODO(@crowlKats): no-op, needs wpgu to implement changing the label
}
#[getter]
#[global]
fn features(&self, scope: &mut v8::HandleScope) -> v8::Global<v8::Object> {
self.features.get(scope, |scope| {
let features = self.instance.device_features(self.id);
let features = features_to_feature_names(features);
GPUSupportedFeatures::new(scope, features)
})
}
#[getter]
#[global]
fn limits(&self, scope: &mut v8::HandleScope) -> v8::Global<v8::Object> {
self.limits.get(scope, |_| {
let limits = self.instance.device_limits(self.id);
GPUSupportedLimits(limits)
})
}
#[getter]
#[global]
fn adapter_info(
&self,
scope: &mut v8::HandleScope,
) -> v8::Global<v8::Object> {
self.adapter_info.get(scope, |_| {
let info = self.instance.adapter_get_info(self.adapter);
GPUAdapterInfo(info)
})
}
#[getter]
#[global]
fn queue(&self, scope: &mut v8::HandleScope) -> v8::Global<v8::Object> {
self.queue_obj.get(scope, |_| GPUQueue {
id: self.queue,
error_handler: self.error_handler.clone(),
instance: self.instance.clone(),
label: self.label.clone(),
})
}
#[fast]
fn destroy(&self) {
self.instance.device_destroy(self.id);
}
#[required(1)]
#[cppgc]
fn create_buffer(
&self,
#[webidl] descriptor: super::buffer::GPUBufferDescriptor,
) -> Result<GPUBuffer, JsErrorBox> {
let wgpu_descriptor = wgpu_core::resource::BufferDescriptor {
label: Some(Cow::Owned(descriptor.label.clone())),
size: descriptor.size,
usage: wgpu_types::BufferUsages::from_bits(descriptor.usage)
.ok_or_else(|| JsErrorBox::type_error("usage is not valid"))?,
mapped_at_creation: descriptor.mapped_at_creation,
};
let (id, err) =
self
.instance
.device_create_buffer(self.id, &wgpu_descriptor, None);
self.error_handler.push_error(err);
Ok(GPUBuffer {
instance: self.instance.clone(),
error_handler: self.error_handler.clone(),
id,
device: self.id,
label: descriptor.label,
size: descriptor.size,
usage: descriptor.usage,
map_state: RefCell::new(if descriptor.mapped_at_creation {
"mapped"
} else {
"unmapped"
}),
map_mode: RefCell::new(None),
mapped_js_buffers: RefCell::new(vec![]),
})
}
#[required(1)]
#[cppgc]
fn create_texture(
&self,
#[webidl] descriptor: super::texture::GPUTextureDescriptor,
) -> Result<GPUTexture, JsErrorBox> {
let wgpu_descriptor = wgpu_core::resource::TextureDescriptor {
label: Some(Cow::Owned(descriptor.label.clone())),
size: descriptor.size.into(),
mip_level_count: descriptor.mip_level_count,
sample_count: descriptor.sample_count,
dimension: descriptor.dimension.clone().into(),
format: descriptor.format.clone().into(),
usage: wgpu_types::TextureUsages::from_bits(descriptor.usage)
.ok_or_else(|| JsErrorBox::type_error("usage is not valid"))?,
view_formats: descriptor
.view_formats
.into_iter()
.map(Into::into)
.collect(),
};
let (id, err) =
self
.instance
.device_create_texture(self.id, &wgpu_descriptor, None);
self.error_handler.push_error(err);
Ok(GPUTexture {
instance: self.instance.clone(),
error_handler: self.error_handler.clone(),
id,
label: descriptor.label,
size: wgpu_descriptor.size,
mip_level_count: wgpu_descriptor.mip_level_count,
sample_count: wgpu_descriptor.sample_count,
dimension: descriptor.dimension,
format: descriptor.format,
usage: descriptor.usage,
})
}
#[cppgc]
fn create_sampler(
&self,
#[webidl] descriptor: super::sampler::GPUSamplerDescriptor,
) -> Result<GPUSampler, JsErrorBox> {
let wgpu_descriptor = wgpu_core::resource::SamplerDescriptor {
label: Some(Cow::Owned(descriptor.label.clone())),
address_modes: [
descriptor.address_mode_u.into(),
descriptor.address_mode_v.into(),
descriptor.address_mode_w.into(),
],
mag_filter: descriptor.mag_filter.into(),
min_filter: descriptor.min_filter.into(),
mipmap_filter: descriptor.mipmap_filter.into(),
lod_min_clamp: descriptor.lod_min_clamp,
lod_max_clamp: descriptor.lod_max_clamp,
compare: descriptor.compare.map(Into::into),
anisotropy_clamp: descriptor.max_anisotropy,
border_color: None,
};
let (id, err) =
self
.instance
.device_create_sampler(self.id, &wgpu_descriptor, None);
self.error_handler.push_error(err);
Ok(GPUSampler {
instance: self.instance.clone(),
id,
label: descriptor.label,
})
}
#[required(1)]
#[cppgc]
fn create_bind_group_layout(
&self,
#[webidl]
descriptor: super::bind_group_layout::GPUBindGroupLayoutDescriptor,
) -> Result<GPUBindGroupLayout, JsErrorBox> {
let mut entries = Vec::with_capacity(descriptor.entries.len());
for entry in descriptor.entries {
let n_entries = [
entry.buffer.is_some(),
entry.sampler.is_some(),
entry.texture.is_some(),
entry.storage_texture.is_some(),
]
.into_iter()
.filter(|t| *t)
.count();
if n_entries != 1 {
return Err(JsErrorBox::type_error("Only one of 'buffer', 'sampler', 'texture' and 'storageTexture' may be specified"));
}
let ty = if let Some(buffer) = entry.buffer {
BindingType::Buffer {
ty: buffer.r#type.into(),
has_dynamic_offset: buffer.has_dynamic_offset,
min_binding_size: NonZeroU64::new(buffer.min_binding_size),
}
} else if let Some(sampler) = entry.sampler {
BindingType::Sampler(sampler.r#type.into())
} else if let Some(texture) = entry.texture {
BindingType::Texture {
sample_type: texture.sample_type.into(),
view_dimension: texture.view_dimension.into(),
multisampled: texture.multisampled,
}
} else if let Some(storage_texture) = entry.storage_texture {
BindingType::StorageTexture {
access: storage_texture.access.into(),
format: storage_texture.format.into(),
view_dimension: storage_texture.view_dimension.into(),
}
} else {
unreachable!()
};
entries.push(wgpu_types::BindGroupLayoutEntry {
binding: entry.binding,
visibility: wgpu_types::ShaderStages::from_bits(entry.visibility)
.ok_or_else(|| JsErrorBox::type_error("usage is not valid"))?,
ty,
count: None, // native-only
});
}
let wgpu_descriptor = wgpu_core::binding_model::BindGroupLayoutDescriptor {
label: Some(Cow::Owned(descriptor.label.clone())),
entries: Cow::Owned(entries),
};
let (id, err) = self.instance.device_create_bind_group_layout(
self.id,
&wgpu_descriptor,
None,
);
self.error_handler.push_error(err);
Ok(GPUBindGroupLayout {
instance: self.instance.clone(),
id,
label: descriptor.label,
})
}
#[required(1)]
#[cppgc]
fn create_pipeline_layout(
&self,
#[webidl] descriptor: super::pipeline_layout::GPUPipelineLayoutDescriptor,
) -> Result<GPUPipelineLayout, JsErrorBox> {
let bind_group_layouts = descriptor
.bind_group_layouts
.into_iter()
.map(|maybe_bind_group_layout| {
maybe_bind_group_layout
.into_option()
.map(|l| l.id)
.ok_or_else(|| {
JsErrorBox::type_error(
"Nullable GPUBindGroupLayouts are currently not supported",
)
})
})
.collect::<Result<_, JsErrorBox>>()?;
let wgpu_descriptor = wgpu_core::binding_model::PipelineLayoutDescriptor {
label: Some(Cow::Owned(descriptor.label.clone())),
bind_group_layouts: Cow::Owned(bind_group_layouts),
push_constant_ranges: Default::default(),
};
let (id, err) = self.instance.device_create_pipeline_layout(
self.id,
&wgpu_descriptor,
None,
);
self.error_handler.push_error(err);
Ok(GPUPipelineLayout {
instance: self.instance.clone(),
id,
label: descriptor.label,
})
}
#[required(1)]
#[cppgc]
fn create_bind_group(
&self,
#[webidl] descriptor: super::bind_group::GPUBindGroupDescriptor,
) -> GPUBindGroup {
let entries = descriptor
.entries
.into_iter()
.map(|entry| wgpu_core::binding_model::BindGroupEntry {
binding: entry.binding,
resource: match entry.resource {
GPUBindingResource::Sampler(sampler) => {
BindingResource::Sampler(sampler.id)
}
GPUBindingResource::TextureView(texture_view) => {
BindingResource::TextureView(texture_view.id)
}
GPUBindingResource::BufferBinding(buffer_binding) => {
BindingResource::Buffer(wgpu_core::binding_model::BufferBinding {
buffer_id: buffer_binding.buffer.id,
offset: buffer_binding.offset,
size: buffer_binding.size.and_then(NonZeroU64::new),
})
}
},
})
.collect::<Vec<_>>();
let wgpu_descriptor = wgpu_core::binding_model::BindGroupDescriptor {
label: Some(Cow::Owned(descriptor.label.clone())),
layout: descriptor.layout.id,
entries: Cow::Owned(entries),
};
let (id, err) =
self
.instance
.device_create_bind_group(self.id, &wgpu_descriptor, None);
self.error_handler.push_error(err);
GPUBindGroup {
instance: self.instance.clone(),
id,
label: descriptor.label,
}
}
#[required(1)]
#[cppgc]
fn create_shader_module(
&self,
#[webidl] descriptor: super::shader::GPUShaderModuleDescriptor,
) -> GPUShaderModule {
let wgpu_descriptor = wgpu_core::pipeline::ShaderModuleDescriptor {
label: Some(Cow::Owned(descriptor.label.clone())),
shader_bound_checks: wgpu_types::ShaderBoundChecks::default(),
};
let (id, err) = self.instance.device_create_shader_module(
self.id,
&wgpu_descriptor,
wgpu_core::pipeline::ShaderModuleSource::Wgsl(Cow::Owned(
descriptor.code,
)),
None,
);
self.error_handler.push_error(err);
GPUShaderModule {
instance: self.instance.clone(),
id,
label: descriptor.label,
}
}
#[required(1)]
#[cppgc]
fn create_compute_pipeline(
&self,
#[webidl] descriptor: super::compute_pipeline::GPUComputePipelineDescriptor,
) -> GPUComputePipeline {
self.new_compute_pipeline(descriptor)
}
#[required(1)]
#[cppgc]
fn create_render_pipeline(
&self,
#[webidl] descriptor: super::render_pipeline::GPURenderPipelineDescriptor,
) -> Result<GPURenderPipeline, JsErrorBox> {
self.new_render_pipeline(descriptor)
}
#[async_method]
#[required(1)]
#[cppgc]
async fn create_compute_pipeline_async(
&self,
#[webidl] descriptor: super::compute_pipeline::GPUComputePipelineDescriptor,
) -> GPUComputePipeline {
self.new_compute_pipeline(descriptor)
}
#[async_method]
#[required(1)]
#[cppgc]
async fn create_render_pipeline_async(
&self,
#[webidl] descriptor: super::render_pipeline::GPURenderPipelineDescriptor,
) -> Result<GPURenderPipeline, JsErrorBox> {
self.new_render_pipeline(descriptor)
}
#[required(1)]
#[cppgc]
fn create_command_encoder(
&self,
#[webidl] descriptor: super::command_encoder::GPUCommandEncoderDescriptor,
) -> GPUCommandEncoder {
let wgpu_descriptor = wgpu_types::CommandEncoderDescriptor {
label: Some(Cow::Owned(descriptor.label.clone())),
};
let (id, err) = self.instance.device_create_command_encoder(
self.id,
&wgpu_descriptor,
None,
);
self.error_handler.push_error(err);
GPUCommandEncoder {
instance: self.instance.clone(),
error_handler: self.error_handler.clone(),
id,
label: descriptor.label,
}
}
#[required(1)]
#[cppgc]
fn create_render_bundle_encoder(
&self,
#[webidl]
descriptor: super::render_bundle::GPURenderBundleEncoderDescriptor,
) -> GPURenderBundleEncoder {
let wgpu_descriptor = wgpu_core::command::RenderBundleEncoderDescriptor {
label: Some(Cow::Owned(descriptor.label.clone())),
color_formats: Cow::Owned(
descriptor
.color_formats
.into_iter()
.map(|format| format.into_option().map(Into::into))
.collect::<Vec<_>>(),
),
depth_stencil: descriptor.depth_stencil_format.map(|format| {
wgpu_types::RenderBundleDepthStencil {
format: format.into(),
depth_read_only: descriptor.depth_read_only,
stencil_read_only: descriptor.stencil_read_only,
}
}),
sample_count: descriptor.sample_count,
multiview: None,
};
let res = wgpu_core::command::RenderBundleEncoder::new(
&wgpu_descriptor,
self.id,
None,
);
let (encoder, err) = match res {
Ok(encoder) => (encoder, None),
Err(e) => (
wgpu_core::command::RenderBundleEncoder::dummy(self.id),
Some(e),
),
};
self.error_handler.push_error(err);
GPURenderBundleEncoder {
instance: self.instance.clone(),
error_handler: self.error_handler.clone(),
encoder: RefCell::new(Some(encoder)),
label: descriptor.label,
}
}
#[required(1)]
#[cppgc]
fn create_query_set(
&self,
#[webidl] descriptor: crate::query_set::GPUQuerySetDescriptor,
) -> GPUQuerySet {
let wgpu_descriptor = wgpu_core::resource::QuerySetDescriptor {
label: Some(Cow::Owned(descriptor.label.clone())),
ty: descriptor.r#type.clone().into(),
count: descriptor.count,
};
let (id, err) =
self
.instance
.device_create_query_set(self.id, &wgpu_descriptor, None);
self.error_handler.push_error(err);
GPUQuerySet {
instance: self.instance.clone(),
id,
r#type: descriptor.r#type,
count: descriptor.count,
label: descriptor.label,
}
}
// TODO: support returning same promise
#[async_method]
#[getter]
#[cppgc]
async fn lost(&self) -> GPUDeviceLostInfo {
if let Some(lost_receiver) = self.lost_receiver.lock().await.take() {
let _ = lost_receiver.await;
}
GPUDeviceLostInfo
}
#[required(1)]
fn push_error_scope(&self, #[webidl] filter: super::error::GPUErrorFilter) {
self
.error_handler
.scopes
.lock()
.unwrap()
.push((filter, vec![]));
}
#[async_method]
fn pop_error_scope<'a>(
&self,
scope: &mut v8::HandleScope<'a>,
) -> impl Future<Output = Result<v8::Local<'a, v8::Value>, JsErrorBox>> {
if self.error_handler.is_lost.get().is_some() {
return std::future::ready(Ok(v8::null(scope).into()));
}
let Some((_, errors)) = self.error_handler.scopes.lock().unwrap().pop() else {
return std::future::ready(Err(JsErrorBox::new(
"DOMExceptionOperationError",
"There are no error scopes on the error scope stack",
)));
};
let val = if let Some(err) = errors.into_iter().next() {
Ok(deno_core::error::to_v8_error(scope, &err))
} else {
Ok(v8::null(scope).into())
};
std::future::ready(val)
}
}
impl GPUDevice {
fn new_compute_pipeline(
&self,
descriptor: super::compute_pipeline::GPUComputePipelineDescriptor,
) -> GPUComputePipeline {
let wgpu_descriptor = wgpu_core::pipeline::ComputePipelineDescriptor {
label: Some(Cow::Owned(descriptor.label.clone())),
layout: descriptor.layout.into(),
stage: ProgrammableStageDescriptor {
module: descriptor.compute.module.id,
entry_point: descriptor.compute.entry_point.map(Into::into),
constants: Cow::Owned(
descriptor.compute.constants.into_iter().collect(),
),
zero_initialize_workgroup_memory: true,
},
cache: None,
};
let (id, err) = self.instance.device_create_compute_pipeline(
self.id,
&wgpu_descriptor,
None,
None,
);
self.error_handler.push_error(err);
GPUComputePipeline {
instance: self.instance.clone(),
error_handler: self.error_handler.clone(),
id,
label: descriptor.label.clone(),
}
}
fn new_render_pipeline(
&self,
descriptor: super::render_pipeline::GPURenderPipelineDescriptor,
) -> Result<GPURenderPipeline, JsErrorBox> {
let vertex = wgpu_core::pipeline::VertexState {
stage: ProgrammableStageDescriptor {
module: descriptor.vertex.module.id,
entry_point: descriptor.vertex.entry_point.map(Into::into),
constants: Cow::Owned(
descriptor.vertex.constants.into_iter().collect(),
),
zero_initialize_workgroup_memory: true,
},
buffers: Cow::Owned(
descriptor
.vertex
.buffers
.into_iter()
.map(|b| {
let layout = b.into_option().ok_or_else(|| {
JsErrorBox::type_error(
"Nullable GPUVertexBufferLayouts are currently not supported",
)
})?;
Ok(wgpu_core::pipeline::VertexBufferLayout {
array_stride: layout.array_stride,
step_mode: layout.step_mode.into(),
attributes: Cow::Owned(
layout
.attributes
.into_iter()
.map(|attr| wgpu_types::VertexAttribute {
format: attr.format.into(),
offset: attr.offset,
shader_location: attr.shader_location,
})
.collect(),
),
})
})
.collect::<Result<_, JsErrorBox>>()?,
),
};
let primitive = wgpu_types::PrimitiveState {
topology: descriptor.primitive.topology.into(),
strip_index_format: descriptor
.primitive
.strip_index_format
.map(Into::into),
front_face: descriptor.primitive.front_face.into(),
cull_mode: descriptor.primitive.cull_mode.into(),
unclipped_depth: descriptor.primitive.unclipped_depth,
polygon_mode: Default::default(),
conservative: false,
};
let depth_stencil = descriptor.depth_stencil.map(|depth_stencil| {
let front = wgpu_types::StencilFaceState {
compare: depth_stencil.stencil_front.compare.into(),
fail_op: depth_stencil.stencil_front.fail_op.into(),
depth_fail_op: depth_stencil.stencil_front.depth_fail_op.into(),
pass_op: depth_stencil.stencil_front.pass_op.into(),
};
let back = wgpu_types::StencilFaceState {
compare: depth_stencil.stencil_back.compare.into(),
fail_op: depth_stencil.stencil_back.fail_op.into(),
depth_fail_op: depth_stencil.stencil_back.depth_fail_op.into(),
pass_op: depth_stencil.stencil_back.pass_op.into(),
};
wgpu_types::DepthStencilState {
format: depth_stencil.format.into(),
depth_write_enabled: depth_stencil
.depth_write_enabled
.unwrap_or_default(),
depth_compare: depth_stencil
.depth_compare
.map(Into::into)
.unwrap_or(wgpu_types::CompareFunction::Never), // TODO: wgpu should be optional here
stencil: wgpu_types::StencilState {
front,
back,
read_mask: depth_stencil.stencil_read_mask,
write_mask: depth_stencil.stencil_write_mask,
},
bias: wgpu_types::DepthBiasState {
constant: depth_stencil.depth_bias,
slope_scale: depth_stencil.depth_bias_slope_scale,
clamp: depth_stencil.depth_bias_clamp,
},
}
});
let multisample = wgpu_types::MultisampleState {
count: descriptor.multisample.count,
mask: descriptor.multisample.mask as u64,
alpha_to_coverage_enabled: descriptor
.multisample
.alpha_to_coverage_enabled,
};
let fragment = descriptor
.fragment
.map(|fragment| {
Ok::<_, JsErrorBox>(wgpu_core::pipeline::FragmentState {
stage: ProgrammableStageDescriptor {
module: fragment.module.id,
entry_point: fragment.entry_point.map(Into::into),
constants: Cow::Owned(fragment.constants.into_iter().collect()),
zero_initialize_workgroup_memory: true,
},
targets: Cow::Owned(
fragment
.targets
.into_iter()
.map(|target| {
target
.into_option()
.map(|target| {
Ok(wgpu_types::ColorTargetState {
format: target.format.into(),
blend: target.blend.map(|blend| wgpu_types::BlendState {
color: wgpu_types::BlendComponent {
src_factor: blend.color.src_factor.into(),
dst_factor: blend.color.dst_factor.into(),
operation: blend.color.operation.into(),
},
alpha: wgpu_types::BlendComponent {
src_factor: blend.alpha.src_factor.into(),
dst_factor: blend.alpha.dst_factor.into(),
operation: blend.alpha.operation.into(),
},
}),
write_mask: wgpu_types::ColorWrites::from_bits(
target.write_mask,
)
.ok_or_else(|| {
JsErrorBox::type_error("usage is not valid")
})?,
})
})
.transpose()
})
.collect::<Result<_, JsErrorBox>>()?,
),
})
})
.transpose()?;
let wgpu_descriptor = wgpu_core::pipeline::RenderPipelineDescriptor {
label: Some(Cow::Owned(descriptor.label.clone())),
layout: descriptor.layout.into(),
vertex,
primitive,
depth_stencil,
multisample,
fragment,
cache: None,
multiview: None,
};
let (id, err) = self.instance.device_create_render_pipeline(
self.id,
&wgpu_descriptor,
None,
None,
);
self.error_handler.push_error(err);
Ok(GPURenderPipeline {
instance: self.instance.clone(),
error_handler: self.error_handler.clone(),
id,
label: descriptor.label,
})
}
}
pub struct GPUDeviceLostInfo;
impl GarbageCollected for GPUDeviceLostInfo {}
#[op2]
impl GPUDeviceLostInfo {
#[getter]
#[string]
fn reason(&self) -> &'static str {
"unknown"
}
#[getter]
#[string]
fn message(&self) -> &'static str {
"device was lost"
}
}

View file

@ -1,10 +1,10 @@
// Copyright 2018-2025 the Deno authors. MIT license.
use std::convert::From;
use std::error::Error;
use std::fmt::Display;
use std::fmt::Formatter;
use std::sync::Mutex;
use std::sync::OnceLock;
use deno_core::ResourceId;
use serde::Serialize;
use wgpu_core::binding_model::CreateBindGroupError;
use wgpu_core::binding_model::CreateBindGroupLayoutError;
use wgpu_core::binding_model::CreatePipelineLayoutError;
@ -31,7 +31,105 @@ use wgpu_core::resource::CreateSamplerError;
use wgpu_core::resource::CreateTextureError;
use wgpu_core::resource::CreateTextureViewError;
fn fmt_err(err: &(dyn Error + 'static)) -> String {
pub type ErrorHandler = std::sync::Arc<DeviceErrorHandler>;
pub struct DeviceErrorHandler {
pub is_lost: OnceLock<()>,
lost_sender: Mutex<Option<tokio::sync::oneshot::Sender<()>>>,
pub scopes: Mutex<Vec<(GPUErrorFilter, Vec<GPUError>)>>,
}
impl DeviceErrorHandler {
pub fn new(sender: tokio::sync::oneshot::Sender<()>) -> Self {
Self {
is_lost: Default::default(),
lost_sender: Mutex::new(Some(sender)),
scopes: Mutex::new(vec![]),
}
}
pub fn push_error<E: Into<GPUError>>(&self, err: Option<E>) {
let Some(err) = err else {
return;
};
if self.is_lost.get().is_some() {
return;
}
let err = err.into();
if matches!(err, GPUError::Lost) {
let _ = self.is_lost.set(());
if let Some(sender) = self.lost_sender.lock().unwrap().take() {
let _ = sender.send(());
}
return;
}
let error_filter = match err {
GPUError::Lost => unreachable!(),
GPUError::Validation(_) => GPUErrorFilter::Validation,
GPUError::OutOfMemory => GPUErrorFilter::OutOfMemory,
GPUError::Internal => GPUErrorFilter::Internal,
};
let mut scopes = self.scopes.lock().unwrap();
let scope = scopes
.iter_mut()
.rfind(|(filter, _)| filter == &error_filter);
if let Some(scope) = scope {
scope.1.push(err);
} else {
/* TODO:
this.device.dispatchEvent(
new GPUUncapturedErrorEvent("uncapturederror", {
error: constructedError,
}),
);
*/
}
}
}
#[derive(deno_core::WebIDL, Eq, PartialEq)]
#[webidl(enum)]
pub enum GPUErrorFilter {
Validation,
OutOfMemory,
Internal,
}
#[derive(Debug, deno_error::JsError)]
pub enum GPUError {
// TODO(@crowlKats): consider adding an unreachable value that uses unreachable!()
#[class("UNREACHABLE")]
Lost,
#[class("GPUValidationError")]
Validation(String),
#[class("GPUOutOfMemoryError")]
OutOfMemory,
#[class("GPUInternalError")]
Internal,
}
impl Display for GPUError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
GPUError::Lost => Ok(()),
GPUError::Validation(s) => f.write_str(s),
GPUError::OutOfMemory => f.write_str("not enough memory left"),
GPUError::Internal => Ok(()),
}
}
}
impl std::error::Error for GPUError {}
fn fmt_err(err: &(dyn std::error::Error + 'static)) -> String {
let mut output = err.to_string();
let mut e = err.source();
@ -40,248 +138,203 @@ fn fmt_err(err: &(dyn Error + 'static)) -> String {
e = source.source();
}
if output.is_empty() {
output.push_str("validation error");
}
output
}
#[derive(Serialize)]
pub struct WebGpuResult {
pub rid: Option<ResourceId>,
pub err: Option<WebGpuError>,
}
impl WebGpuResult {
pub fn rid(rid: ResourceId) -> Self {
Self {
rid: Some(rid),
err: None,
}
}
pub fn rid_err<T: Into<WebGpuError>>(
rid: ResourceId,
err: Option<T>,
) -> Self {
Self {
rid: Some(rid),
err: err.map(Into::into),
}
}
pub fn maybe_err<T: Into<WebGpuError>>(err: Option<T>) -> Self {
Self {
rid: None,
err: err.map(Into::into),
}
}
pub fn empty() -> Self {
Self {
rid: None,
err: None,
}
}
}
#[derive(Serialize)]
#[serde(tag = "type", content = "value")]
#[serde(rename_all = "kebab-case")]
pub enum WebGpuError {
Lost,
OutOfMemory,
Validation(String),
Internal,
}
impl From<CreateBufferError> for WebGpuError {
impl From<CreateBufferError> for GPUError {
fn from(err: CreateBufferError) -> Self {
match err {
CreateBufferError::Device(err) => err.into(),
CreateBufferError::AccessError(err) => err.into(),
err => WebGpuError::Validation(fmt_err(&err)),
err => GPUError::Validation(fmt_err(&err)),
}
}
}
impl From<DeviceError> for WebGpuError {
impl From<DeviceError> for GPUError {
fn from(err: DeviceError) -> Self {
match err {
DeviceError::Lost => WebGpuError::Lost,
DeviceError::OutOfMemory => WebGpuError::OutOfMemory,
_ => WebGpuError::Validation(fmt_err(&err)),
DeviceError::Lost => GPUError::Lost,
DeviceError::OutOfMemory => GPUError::OutOfMemory,
_ => GPUError::Validation(fmt_err(&err)),
}
}
}
impl From<BufferAccessError> for WebGpuError {
impl From<BufferAccessError> for GPUError {
fn from(err: BufferAccessError) -> Self {
match err {
BufferAccessError::Device(err) => err.into(),
err => WebGpuError::Validation(fmt_err(&err)),
err => GPUError::Validation(fmt_err(&err)),
}
}
}
impl From<CreateBindGroupLayoutError> for WebGpuError {
impl From<CreateBindGroupLayoutError> for GPUError {
fn from(err: CreateBindGroupLayoutError) -> Self {
match err {
CreateBindGroupLayoutError::Device(err) => err.into(),
err => WebGpuError::Validation(fmt_err(&err)),
err => GPUError::Validation(fmt_err(&err)),
}
}
}
impl From<CreatePipelineLayoutError> for WebGpuError {
impl From<CreatePipelineLayoutError> for GPUError {
fn from(err: CreatePipelineLayoutError) -> Self {
match err {
CreatePipelineLayoutError::Device(err) => err.into(),
err => WebGpuError::Validation(fmt_err(&err)),
err => GPUError::Validation(fmt_err(&err)),
}
}
}
impl From<CreateBindGroupError> for WebGpuError {
impl From<CreateBindGroupError> for GPUError {
fn from(err: CreateBindGroupError) -> Self {
match err {
CreateBindGroupError::Device(err) => err.into(),
err => WebGpuError::Validation(fmt_err(&err)),
err => GPUError::Validation(fmt_err(&err)),
}
}
}
impl From<RenderBundleError> for WebGpuError {
impl From<RenderBundleError> for GPUError {
fn from(err: RenderBundleError) -> Self {
WebGpuError::Validation(fmt_err(&err))
GPUError::Validation(fmt_err(&err))
}
}
impl From<CreateRenderBundleError> for WebGpuError {
impl From<CreateRenderBundleError> for GPUError {
fn from(err: CreateRenderBundleError) -> Self {
WebGpuError::Validation(fmt_err(&err))
GPUError::Validation(fmt_err(&err))
}
}
impl From<CopyError> for WebGpuError {
impl From<CopyError> for GPUError {
fn from(err: CopyError) -> Self {
WebGpuError::Validation(fmt_err(&err))
GPUError::Validation(fmt_err(&err))
}
}
impl From<CommandEncoderError> for WebGpuError {
impl From<CommandEncoderError> for GPUError {
fn from(err: CommandEncoderError) -> Self {
WebGpuError::Validation(fmt_err(&err))
GPUError::Validation(fmt_err(&err))
}
}
impl From<QueryError> for WebGpuError {
impl From<QueryError> for GPUError {
fn from(err: QueryError) -> Self {
WebGpuError::Validation(fmt_err(&err))
GPUError::Validation(fmt_err(&err))
}
}
impl From<ComputePassError> for WebGpuError {
impl From<ComputePassError> for GPUError {
fn from(err: ComputePassError) -> Self {
WebGpuError::Validation(fmt_err(&err))
GPUError::Validation(fmt_err(&err))
}
}
impl From<CreateComputePipelineError> for WebGpuError {
impl From<CreateComputePipelineError> for GPUError {
fn from(err: CreateComputePipelineError) -> Self {
match err {
CreateComputePipelineError::Device(err) => err.into(),
err => WebGpuError::Validation(fmt_err(&err)),
err => GPUError::Validation(fmt_err(&err)),
}
}
}
impl From<GetBindGroupLayoutError> for WebGpuError {
impl From<GetBindGroupLayoutError> for GPUError {
fn from(err: GetBindGroupLayoutError) -> Self {
WebGpuError::Validation(fmt_err(&err))
GPUError::Validation(fmt_err(&err))
}
}
impl From<CreateRenderPipelineError> for WebGpuError {
impl From<CreateRenderPipelineError> for GPUError {
fn from(err: CreateRenderPipelineError) -> Self {
match err {
CreateRenderPipelineError::Device(err) => err.into(),
err => WebGpuError::Validation(fmt_err(&err)),
err => GPUError::Validation(fmt_err(&err)),
}
}
}
impl From<RenderPassError> for WebGpuError {
impl From<RenderPassError> for GPUError {
fn from(err: RenderPassError) -> Self {
WebGpuError::Validation(fmt_err(&err))
GPUError::Validation(fmt_err(&err))
}
}
impl From<CreateSamplerError> for WebGpuError {
impl From<CreateSamplerError> for GPUError {
fn from(err: CreateSamplerError) -> Self {
match err {
CreateSamplerError::Device(err) => err.into(),
err => WebGpuError::Validation(fmt_err(&err)),
err => GPUError::Validation(fmt_err(&err)),
}
}
}
impl From<CreateShaderModuleError> for WebGpuError {
impl From<CreateShaderModuleError> for GPUError {
fn from(err: CreateShaderModuleError) -> Self {
match err {
CreateShaderModuleError::Device(err) => err.into(),
err => WebGpuError::Validation(fmt_err(&err)),
err => GPUError::Validation(fmt_err(&err)),
}
}
}
impl From<CreateTextureError> for WebGpuError {
impl From<CreateTextureError> for GPUError {
fn from(err: CreateTextureError) -> Self {
match err {
CreateTextureError::Device(err) => err.into(),
err => WebGpuError::Validation(fmt_err(&err)),
err => GPUError::Validation(fmt_err(&err)),
}
}
}
impl From<CreateTextureViewError> for WebGpuError {
impl From<CreateTextureViewError> for GPUError {
fn from(err: CreateTextureViewError) -> Self {
WebGpuError::Validation(fmt_err(&err))
GPUError::Validation(fmt_err(&err))
}
}
impl From<CreateQuerySetError> for WebGpuError {
impl From<CreateQuerySetError> for GPUError {
fn from(err: CreateQuerySetError) -> Self {
match err {
CreateQuerySetError::Device(err) => err.into(),
err => WebGpuError::Validation(fmt_err(&err)),
err => GPUError::Validation(fmt_err(&err)),
}
}
}
impl From<QueueSubmitError> for WebGpuError {
impl From<QueueSubmitError> for GPUError {
fn from(err: QueueSubmitError) -> Self {
match err {
QueueSubmitError::Queue(err) => err.into(),
err => WebGpuError::Validation(fmt_err(&err)),
err => GPUError::Validation(fmt_err(&err)),
}
}
}
impl From<QueueWriteError> for WebGpuError {
impl From<QueueWriteError> for GPUError {
fn from(err: QueueWriteError) -> Self {
match err {
QueueWriteError::Queue(err) => err.into(),
err => WebGpuError::Validation(fmt_err(&err)),
err => GPUError::Validation(fmt_err(&err)),
}
}
}
impl From<ClearError> for WebGpuError {
impl From<ClearError> for GPUError {
fn from(err: ClearError) -> Self {
WebGpuError::Validation(fmt_err(&err))
GPUError::Validation(fmt_err(&err))
}
}
impl From<ConfigureSurfaceError> for WebGpuError {
impl From<ConfigureSurfaceError> for GPUError {
fn from(err: ConfigureSurfaceError) -> Self {
WebGpuError::Validation(fmt_err(&err))
GPUError::Validation(fmt_err(&err))
}
}

View file

@ -2,20 +2,40 @@
#![cfg(not(target_arch = "wasm32"))]
#![warn(unsafe_op_in_unsafe_fn)]
use std::borrow::Cow;
use std::cell::RefCell;
use std::collections::HashSet;
use std::rc::Rc;
use std::sync::Arc;
use deno_core::cppgc::SameObject;
use deno_core::op2;
use deno_core::GarbageCollected;
use deno_core::OpState;
use deno_core::Resource;
use deno_core::ResourceId;
use error::WebGpuResult;
use serde::Deserialize;
use serde::Serialize;
pub use wgpu_core;
pub use wgpu_types;
use wgpu_types::PowerPreference;
mod adapter;
mod bind_group;
mod bind_group_layout;
mod buffer;
mod byow;
mod command_buffer;
mod command_encoder;
mod compute_pass;
mod compute_pipeline;
mod device;
mod error;
mod pipeline_layout;
mod query_set;
mod queue;
mod render_bundle;
mod render_pass;
mod render_pipeline;
mod sampler;
mod shader;
mod surface;
mod texture;
mod webidl;
pub const UNSTABLE_FEATURE_NAME: &str = "webgpu";
@ -38,406 +58,64 @@ pub fn print_linker_flags(name: &str) {
}
}
#[macro_use]
mod macros {
macro_rules! gfx_select {
($id:expr => $p0:ident.$p1:tt.$method:ident $params:tt) => {
gfx_select!($id => {$p0.$p1}, $method $params)
};
($id:expr => $p0:ident.$method:ident $params:tt) => {
gfx_select!($id => {$p0}, $method $params)
};
($id:expr => {$($c:tt)*}, $method:ident $params:tt) => {
match $id.backend() {
#[cfg(any(
all(not(target_arch = "wasm32"), not(target_os = "ios"), not(target_os = "macos")),
feature = "vulkan-portability"
))]
wgpu_types::Backend::Vulkan => $($c)*.$method::<wgpu_core::api::Vulkan> $params,
#[cfg(all(not(target_arch = "wasm32"), any(target_os = "ios", target_os = "macos")))]
wgpu_types::Backend::Metal => $($c)*.$method::<wgpu_core::api::Metal> $params,
#[cfg(all(not(target_arch = "wasm32"), windows))]
wgpu_types::Backend::Dx12 => $($c)*.$method::<wgpu_core::api::Dx12> $params,
#[cfg(any(
all(not(target_os = "macos"), not(target_os = "ios")),
feature = "angle",
target_arch = "wasm32"
))]
wgpu_types::Backend::Gl => $($c)*.$method::<wgpu_core::api::Gles> $params,
other => panic!("Unexpected backend {:?}", other),
}
};
}
macro_rules! gfx_put {
($id:expr => $global:ident.$method:ident( $($param:expr),* ) => $state:expr, $rc:expr) => {{
let (val, maybe_err) = gfx_select!($id => $global.$method($($param),*));
let rid = $state.resource_table.add($rc($global.clone(), val));
Ok(WebGpuResult::rid_err(rid, maybe_err))
}};
}
macro_rules! gfx_ok {
($id:expr => $global:ident.$method:ident( $($param:expr),* )) => {{
let maybe_err = gfx_select!($id => $global.$method($($param),*)).err();
Ok(WebGpuResult::maybe_err(maybe_err))
}};
}
}
pub mod binding;
pub mod buffer;
pub mod bundle;
pub mod byow;
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 surface;
pub mod texture;
#[derive(Debug, thiserror::Error, deno_error::JsError)]
pub enum InitError {
#[class(inherit)]
#[error(transparent)]
Resource(
#[from]
#[inherit]
deno_core::error::ResourceError,
),
#[class(generic)]
#[error(transparent)]
InvalidAdapter(wgpu_core::instance::InvalidAdapter),
#[class("DOMExceptionOperationError")]
#[error(transparent)]
RequestDevice(wgpu_core::instance::RequestDeviceError),
#[class(generic)]
#[error(transparent)]
InvalidDevice(wgpu_core::device::InvalidDevice),
}
pub type Instance = std::sync::Arc<wgpu_core::global::Global>;
struct WebGpuAdapter(Instance, wgpu_core::id::AdapterId);
impl Resource for WebGpuAdapter {
fn name(&self) -> Cow<str> {
"webGPUAdapter".into()
}
fn close(self: Rc<Self>) {
gfx_select!(self.1 => self.0.adapter_drop(self.1));
}
}
struct WebGpuDevice(Instance, wgpu_core::id::DeviceId);
impl Resource for WebGpuDevice {
fn name(&self) -> Cow<str> {
"webGPUDevice".into()
}
fn close(self: Rc<Self>) {
gfx_select!(self.1 => self.0.device_drop(self.1));
}
}
struct WebGpuQuerySet(Instance, wgpu_core::id::QuerySetId);
impl Resource for WebGpuQuerySet {
fn name(&self) -> Cow<str> {
"webGPUQuerySet".into()
}
fn close(self: Rc<Self>) {
gfx_select!(self.1 => self.0.query_set_drop(self.1));
}
}
pub type Instance = Arc<wgpu_core::global::Global>;
deno_core::extension!(
deno_webgpu,
deps = [deno_webidl, deno_web],
ops = [
// Request device/adapter
op_webgpu_request_adapter,
op_webgpu_request_device,
op_webgpu_request_adapter_info,
// Query Set
op_webgpu_create_query_set,
// buffer
buffer::op_webgpu_create_buffer,
buffer::op_webgpu_buffer_get_mapped_range,
buffer::op_webgpu_buffer_unmap,
// buffer async
buffer::op_webgpu_buffer_get_map_async,
// remaining sync ops
// texture
texture::op_webgpu_create_texture,
texture::op_webgpu_create_texture_view,
// sampler
sampler::op_webgpu_create_sampler,
// binding
binding::op_webgpu_create_bind_group_layout,
binding::op_webgpu_create_pipeline_layout,
binding::op_webgpu_create_bind_group,
// pipeline
pipeline::op_webgpu_create_compute_pipeline,
pipeline::op_webgpu_compute_pipeline_get_bind_group_layout,
pipeline::op_webgpu_create_render_pipeline,
pipeline::op_webgpu_render_pipeline_get_bind_group_layout,
// command_encoder
command_encoder::op_webgpu_create_command_encoder,
command_encoder::op_webgpu_command_encoder_begin_render_pass,
command_encoder::op_webgpu_command_encoder_begin_compute_pass,
command_encoder::op_webgpu_command_encoder_copy_buffer_to_buffer,
command_encoder::op_webgpu_command_encoder_copy_buffer_to_texture,
command_encoder::op_webgpu_command_encoder_copy_texture_to_buffer,
command_encoder::op_webgpu_command_encoder_copy_texture_to_texture,
command_encoder::op_webgpu_command_encoder_clear_buffer,
command_encoder::op_webgpu_command_encoder_push_debug_group,
command_encoder::op_webgpu_command_encoder_pop_debug_group,
command_encoder::op_webgpu_command_encoder_insert_debug_marker,
command_encoder::op_webgpu_command_encoder_write_timestamp,
command_encoder::op_webgpu_command_encoder_resolve_query_set,
command_encoder::op_webgpu_command_encoder_finish,
render_pass::op_webgpu_render_pass_set_viewport,
render_pass::op_webgpu_render_pass_set_scissor_rect,
render_pass::op_webgpu_render_pass_set_blend_constant,
render_pass::op_webgpu_render_pass_set_stencil_reference,
render_pass::op_webgpu_render_pass_begin_occlusion_query,
render_pass::op_webgpu_render_pass_end_occlusion_query,
render_pass::op_webgpu_render_pass_execute_bundles,
render_pass::op_webgpu_render_pass_end,
render_pass::op_webgpu_render_pass_set_bind_group,
render_pass::op_webgpu_render_pass_push_debug_group,
render_pass::op_webgpu_render_pass_pop_debug_group,
render_pass::op_webgpu_render_pass_insert_debug_marker,
render_pass::op_webgpu_render_pass_set_pipeline,
render_pass::op_webgpu_render_pass_set_index_buffer,
render_pass::op_webgpu_render_pass_set_vertex_buffer,
render_pass::op_webgpu_render_pass_draw,
render_pass::op_webgpu_render_pass_draw_indexed,
render_pass::op_webgpu_render_pass_draw_indirect,
render_pass::op_webgpu_render_pass_draw_indexed_indirect,
compute_pass::op_webgpu_compute_pass_set_pipeline,
compute_pass::op_webgpu_compute_pass_dispatch_workgroups,
compute_pass::op_webgpu_compute_pass_dispatch_workgroups_indirect,
compute_pass::op_webgpu_compute_pass_end,
compute_pass::op_webgpu_compute_pass_set_bind_group,
compute_pass::op_webgpu_compute_pass_push_debug_group,
compute_pass::op_webgpu_compute_pass_pop_debug_group,
compute_pass::op_webgpu_compute_pass_insert_debug_marker,
// bundle
bundle::op_webgpu_create_render_bundle_encoder,
bundle::op_webgpu_render_bundle_encoder_finish,
bundle::op_webgpu_render_bundle_encoder_set_bind_group,
bundle::op_webgpu_render_bundle_encoder_push_debug_group,
bundle::op_webgpu_render_bundle_encoder_pop_debug_group,
bundle::op_webgpu_render_bundle_encoder_insert_debug_marker,
bundle::op_webgpu_render_bundle_encoder_set_pipeline,
bundle::op_webgpu_render_bundle_encoder_set_index_buffer,
bundle::op_webgpu_render_bundle_encoder_set_vertex_buffer,
bundle::op_webgpu_render_bundle_encoder_draw,
bundle::op_webgpu_render_bundle_encoder_draw_indexed,
bundle::op_webgpu_render_bundle_encoder_draw_indirect,
// queue
queue::op_webgpu_queue_submit,
queue::op_webgpu_write_buffer,
queue::op_webgpu_write_texture,
// shader
shader::op_webgpu_create_shader_module,
// surface
surface::op_webgpu_surface_configure,
surface::op_webgpu_surface_get_current_texture,
surface::op_webgpu_surface_present,
// byow
byow::op_webgpu_surface_create,
ops = [create_gpu],
objects = [
GPU,
adapter::GPUAdapter,
adapter::GPUAdapterInfo,
bind_group::GPUBindGroup,
bind_group_layout::GPUBindGroupLayout,
buffer::GPUBuffer,
command_buffer::GPUCommandBuffer,
command_encoder::GPUCommandEncoder,
compute_pass::GPUComputePassEncoder,
compute_pipeline::GPUComputePipeline,
device::GPUDevice,
device::GPUDeviceLostInfo,
pipeline_layout::GPUPipelineLayout,
query_set::GPUQuerySet,
queue::GPUQueue,
render_bundle::GPURenderBundle,
render_bundle::GPURenderBundleEncoder,
render_pass::GPURenderPassEncoder,
render_pipeline::GPURenderPipeline,
sampler::GPUSampler,
shader::GPUShaderModule,
adapter::GPUSupportedFeatures,
adapter::GPUSupportedLimits,
texture::GPUTexture,
texture::GPUTextureView,
byow::UnsafeWindowSurface,
surface::GPUCanvasContext,
],
esm = ["00_init.js", "02_surface.js"],
lazy_loaded_esm = ["01_webgpu.js"],
);
fn deserialize_features(features: &wgpu_types::Features) -> Vec<&'static str> {
let mut return_features: Vec<&'static str> = vec![];
// api
if features.contains(wgpu_types::Features::DEPTH_CLIP_CONTROL) {
return_features.push("depth-clip-control");
}
if features.contains(wgpu_types::Features::TIMESTAMP_QUERY) {
return_features.push("timestamp-query");
}
if features.contains(wgpu_types::Features::INDIRECT_FIRST_INSTANCE) {
return_features.push("indirect-first-instance");
}
// shader
if features.contains(wgpu_types::Features::SHADER_F16) {
return_features.push("shader-f16");
}
// texture formats
if features.contains(wgpu_types::Features::DEPTH32FLOAT_STENCIL8) {
return_features.push("depth32float-stencil8");
}
if features.contains(wgpu_types::Features::TEXTURE_COMPRESSION_BC) {
return_features.push("texture-compression-bc");
}
if features.contains(wgpu_types::Features::TEXTURE_COMPRESSION_ETC2) {
return_features.push("texture-compression-etc2");
}
if features.contains(wgpu_types::Features::TEXTURE_COMPRESSION_ASTC) {
return_features.push("texture-compression-astc");
}
if features.contains(wgpu_types::Features::RG11B10UFLOAT_RENDERABLE) {
return_features.push("rg11b10ufloat-renderable");
}
if features.contains(wgpu_types::Features::BGRA8UNORM_STORAGE) {
return_features.push("bgra8unorm-storage");
}
if features.contains(wgpu_types::Features::FLOAT32_FILTERABLE) {
return_features.push("float32-filterable");
}
// extended from spec
// texture formats
if features.contains(wgpu_types::Features::TEXTURE_FORMAT_16BIT_NORM) {
return_features.push("texture-format-16-bit-norm");
}
if features.contains(wgpu_types::Features::TEXTURE_COMPRESSION_ASTC_HDR) {
return_features.push("texture-compression-astc-hdr");
}
if features
.contains(wgpu_types::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES)
{
return_features.push("texture-adapter-specific-format-features");
}
// api
if features.contains(wgpu_types::Features::PIPELINE_STATISTICS_QUERY) {
return_features.push("pipeline-statistics-query");
}
if features.contains(wgpu_types::Features::TIMESTAMP_QUERY_INSIDE_PASSES) {
return_features.push("timestamp-query-inside-passes");
}
if features.contains(wgpu_types::Features::MAPPABLE_PRIMARY_BUFFERS) {
return_features.push("mappable-primary-buffers");
}
if features.contains(wgpu_types::Features::TEXTURE_BINDING_ARRAY) {
return_features.push("texture-binding-array");
}
if features.contains(wgpu_types::Features::BUFFER_BINDING_ARRAY) {
return_features.push("buffer-binding-array");
}
if features.contains(wgpu_types::Features::STORAGE_RESOURCE_BINDING_ARRAY) {
return_features.push("storage-resource-binding-array");
}
if features.contains(
wgpu_types::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING,
) {
return_features.push("sampled-texture-and-storage-buffer-array-non-uniform-indexing");
}
if features.contains(
wgpu_types::Features::UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING,
) {
return_features.push("uniform-buffer-and-storage-texture-array-non-uniform-indexing");
}
if features.contains(wgpu_types::Features::PARTIALLY_BOUND_BINDING_ARRAY) {
return_features.push("partially-bound-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_ZERO) {
return_features.push("address-mode-clamp-to-zero");
}
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::POLYGON_MODE_LINE) {
return_features.push("polygon-mode-line");
}
if features.contains(wgpu_types::Features::POLYGON_MODE_POINT) {
return_features.push("polygon-mode-point");
}
if features.contains(wgpu_types::Features::CONSERVATIVE_RASTERIZATION) {
return_features.push("conservative-rasterization");
}
if features.contains(wgpu_types::Features::VERTEX_WRITABLE_STORAGE) {
return_features.push("vertex-writable-storage");
}
if features.contains(wgpu_types::Features::CLEAR_TEXTURE) {
return_features.push("clear-texture");
}
if features.contains(wgpu_types::Features::SPIRV_SHADER_PASSTHROUGH) {
return_features.push("spirv-shader-passthrough");
}
if features.contains(wgpu_types::Features::MULTIVIEW) {
return_features.push("multiview");
}
if features.contains(wgpu_types::Features::VERTEX_ATTRIBUTE_64BIT) {
return_features.push("vertex-attribute-64-bit");
}
// shader
if features.contains(wgpu_types::Features::SHADER_F64) {
return_features.push("shader-f64");
}
if features.contains(wgpu_types::Features::SHADER_I16) {
return_features.push("shader-i16");
}
if features.contains(wgpu_types::Features::SHADER_PRIMITIVE_INDEX) {
return_features.push("shader-primitive-index");
}
if features.contains(wgpu_types::Features::SHADER_EARLY_DEPTH_TEST) {
return_features.push("shader-early-depth-test");
}
if features.contains(wgpu_types::Features::SHADER_UNUSED_VERTEX_OUTPUT) {
return_features.push("shader-unused-vertex-output");
}
return_features
#[op2]
#[cppgc]
pub fn create_gpu() -> GPU {
GPU
}
#[derive(Serialize)]
#[serde(untagged)]
pub enum GpuAdapterResOrErr {
Error { err: String },
Features(GpuAdapterRes),
}
pub struct GPU;
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GpuAdapterRes {
rid: ResourceId,
limits: wgpu_types::Limits,
features: Vec<&'static str>,
is_fallback: bool,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GpuDeviceRes {
rid: ResourceId,
queue_rid: ResourceId,
limits: wgpu_types::Limits,
features: Vec<&'static str>,
}
impl GarbageCollected for GPU {}
#[op2]
#[serde]
pub fn op_webgpu_request_adapter(
impl GPU {
#[async_method]
#[cppgc]
async fn request_adapter(
&self,
state: Rc<RefCell<OpState>>,
#[serde] power_preference: Option<wgpu_types::PowerPreference>,
force_fallback_adapter: bool,
) -> Result<GpuAdapterResOrErr, InitError> {
#[webidl] options: adapter::GPURequestAdapterOptions,
) -> Option<adapter::GPUAdapter> {
let mut state = state.borrow_mut();
let backends = std::env::var("DENO_WEBGPU_BACKEND").map_or_else(
@ -447,7 +125,7 @@ pub fn op_webgpu_request_adapter(
let instance = if let Some(instance) = state.try_borrow::<Instance>() {
instance
} else {
state.put(std::sync::Arc::new(wgpu_core::global::Global::new(
state.put(Arc::new(wgpu_core::global::Global::new(
"webgpu",
wgpu_types::InstanceDescriptor {
backends,
@ -460,371 +138,36 @@ pub fn op_webgpu_request_adapter(
};
let descriptor = wgpu_core::instance::RequestAdapterOptions {
power_preference: power_preference.unwrap_or_default(),
force_fallback_adapter,
power_preference: options
.power_preference
.map(|pp| match pp {
adapter::GPUPowerPreference::LowPower => PowerPreference::LowPower,
adapter::GPUPowerPreference::HighPerformance => {
PowerPreference::HighPerformance
}
})
.unwrap_or_default(),
force_fallback_adapter: options.force_fallback_adapter,
compatible_surface: None, // windowless
};
let res = instance.request_adapter(
&descriptor,
wgpu_core::instance::AdapterInputs::Mask(backends, |_| None),
);
let id = instance.request_adapter(&descriptor, backends, None).ok()?;
let adapter = match res {
Ok(adapter) => adapter,
Err(err) => {
return Ok(GpuAdapterResOrErr::Error {
err: err.to_string(),
Some(adapter::GPUAdapter {
instance: instance.clone(),
features: SameObject::new(),
limits: SameObject::new(),
info: Rc::new(SameObject::new()),
id,
})
}
};
let adapter_features =
gfx_select!(adapter => instance.adapter_features(adapter))
.map_err(InitError::InvalidAdapter)?;
let features = deserialize_features(&adapter_features);
let adapter_limits = gfx_select!(adapter => instance.adapter_limits(adapter))
.map_err(InitError::InvalidAdapter)?;
let instance = instance.clone();
let rid = state.resource_table.add(WebGpuAdapter(instance, adapter));
Ok(GpuAdapterResOrErr::Features(GpuAdapterRes {
rid,
features,
limits: adapter_limits,
// TODO(lucacasonato): report correctly from wgpu
is_fallback: false,
}))
}
#[derive(Deserialize)]
pub struct GpuRequiredFeatures(HashSet<String>);
impl From<GpuRequiredFeatures> for wgpu_types::Features {
fn from(required_features: GpuRequiredFeatures) -> wgpu_types::Features {
let mut features: wgpu_types::Features = wgpu_types::Features::empty();
// api
features.set(
wgpu_types::Features::DEPTH_CLIP_CONTROL,
required_features.0.contains("depth-clip-control"),
);
features.set(
wgpu_types::Features::TIMESTAMP_QUERY,
required_features.0.contains("timestamp-query"),
);
features.set(
wgpu_types::Features::INDIRECT_FIRST_INSTANCE,
required_features.0.contains("indirect-first-instance"),
);
// shader
features.set(
wgpu_types::Features::SHADER_F16,
required_features.0.contains("shader-f16"),
);
// texture formats
features.set(
wgpu_types::Features::DEPTH32FLOAT_STENCIL8,
required_features.0.contains("depth32float-stencil8"),
);
features.set(
wgpu_types::Features::TEXTURE_COMPRESSION_BC,
required_features.0.contains("texture-compression-bc"),
);
features.set(
wgpu_types::Features::TEXTURE_COMPRESSION_ETC2,
required_features.0.contains("texture-compression-etc2"),
);
features.set(
wgpu_types::Features::TEXTURE_COMPRESSION_ASTC,
required_features.0.contains("texture-compression-astc"),
);
features.set(
wgpu_types::Features::RG11B10UFLOAT_RENDERABLE,
required_features.0.contains("rg11b10ufloat-renderable"),
);
features.set(
wgpu_types::Features::BGRA8UNORM_STORAGE,
required_features.0.contains("bgra8unorm-storage"),
);
features.set(
wgpu_types::Features::FLOAT32_FILTERABLE,
required_features.0.contains("float32-filterable"),
);
// extended from spec
// texture formats
features.set(
wgpu_types::Features::TEXTURE_FORMAT_16BIT_NORM,
required_features.0.contains("texture-format-16-bit-norm"),
);
features.set(
wgpu_types::Features::TEXTURE_COMPRESSION_ASTC_HDR,
required_features.0.contains("texture-compression-astc-hdr"),
);
features.set(
wgpu_types::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES,
required_features
.0
.contains("texture-adapter-specific-format-features"),
);
// api
features.set(
wgpu_types::Features::PIPELINE_STATISTICS_QUERY,
required_features.0.contains("pipeline-statistics-query"),
);
features.set(
wgpu_types::Features::TIMESTAMP_QUERY_INSIDE_PASSES,
required_features
.0
.contains("timestamp-query-inside-passes"),
);
features.set(
wgpu_types::Features::MAPPABLE_PRIMARY_BUFFERS,
required_features.0.contains("mappable-primary-buffers"),
);
features.set(
wgpu_types::Features::TEXTURE_BINDING_ARRAY,
required_features.0.contains("texture-binding-array"),
);
features.set(
wgpu_types::Features::BUFFER_BINDING_ARRAY,
required_features.0.contains("buffer-binding-array"),
);
features.set(
wgpu_types::Features::STORAGE_RESOURCE_BINDING_ARRAY,
required_features
.0
.contains("storage-resource-binding-array"),
);
features.set(
wgpu_types::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING,
required_features
.0
.contains("sampled-texture-and-storage-buffer-array-non-uniform-indexing"),
);
features.set(
wgpu_types::Features::UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING,
required_features
.0
.contains("uniform-buffer-and-storage-texture-array-non-uniform-indexing"),
);
features.set(
wgpu_types::Features::PARTIALLY_BOUND_BINDING_ARRAY,
required_features
.0
.contains("partially-bound-binding-array"),
);
features.set(
wgpu_types::Features::MULTI_DRAW_INDIRECT,
required_features.0.contains("multi-draw-indirect"),
);
features.set(
wgpu_types::Features::MULTI_DRAW_INDIRECT_COUNT,
required_features.0.contains("multi-draw-indirect-count"),
);
features.set(
wgpu_types::Features::PUSH_CONSTANTS,
required_features.0.contains("push-constants"),
);
features.set(
wgpu_types::Features::ADDRESS_MODE_CLAMP_TO_ZERO,
required_features.0.contains("address-mode-clamp-to-zero"),
);
features.set(
wgpu_types::Features::ADDRESS_MODE_CLAMP_TO_BORDER,
required_features.0.contains("address-mode-clamp-to-border"),
);
features.set(
wgpu_types::Features::POLYGON_MODE_LINE,
required_features.0.contains("polygon-mode-line"),
);
features.set(
wgpu_types::Features::POLYGON_MODE_POINT,
required_features.0.contains("polygon-mode-point"),
);
features.set(
wgpu_types::Features::CONSERVATIVE_RASTERIZATION,
required_features.0.contains("conservative-rasterization"),
);
features.set(
wgpu_types::Features::VERTEX_WRITABLE_STORAGE,
required_features.0.contains("vertex-writable-storage"),
);
features.set(
wgpu_types::Features::CLEAR_TEXTURE,
required_features.0.contains("clear-texture"),
);
features.set(
wgpu_types::Features::SPIRV_SHADER_PASSTHROUGH,
required_features.0.contains("spirv-shader-passthrough"),
);
features.set(
wgpu_types::Features::MULTIVIEW,
required_features.0.contains("multiview"),
);
features.set(
wgpu_types::Features::VERTEX_ATTRIBUTE_64BIT,
required_features.0.contains("vertex-attribute-64-bit"),
);
// shader
features.set(
wgpu_types::Features::SHADER_F64,
required_features.0.contains("shader-f64"),
);
features.set(
wgpu_types::Features::SHADER_I16,
required_features.0.contains("shader-i16"),
);
features.set(
wgpu_types::Features::SHADER_PRIMITIVE_INDEX,
required_features.0.contains("shader-primitive-index"),
);
features.set(
wgpu_types::Features::SHADER_EARLY_DEPTH_TEST,
required_features.0.contains("shader-early-depth-test"),
);
features.set(
wgpu_types::Features::SHADER_UNUSED_VERTEX_OUTPUT,
required_features.0.contains("shader-unused-vertex-output"),
);
features
}
}
#[op2]
#[serde]
pub fn op_webgpu_request_device(
state: Rc<RefCell<OpState>>,
#[smi] adapter_rid: ResourceId,
#[string] label: String,
#[serde] required_features: GpuRequiredFeatures,
#[serde] required_limits: Option<wgpu_types::Limits>,
) -> Result<GpuDeviceRes, InitError> {
let mut state = state.borrow_mut();
let adapter_resource =
state.resource_table.take::<WebGpuAdapter>(adapter_rid)?;
let adapter = adapter_resource.1;
let instance = state.borrow::<Instance>();
let descriptor = wgpu_types::DeviceDescriptor {
label: Some(Cow::Owned(label)),
required_features: required_features.into(),
required_limits: required_limits.unwrap_or_default(),
};
let (device, queue, 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),
None,
None
));
adapter_resource.close();
if let Some(err) = maybe_err {
return Err(InitError::RequestDevice(err));
}
let device_features = gfx_select!(device => instance.device_features(device))
.map_err(InitError::InvalidDevice)?;
let features = deserialize_features(&device_features);
let limits = gfx_select!(device => instance.device_limits(device))
.map_err(InitError::InvalidDevice)?;
let instance = instance.clone();
let instance2 = instance.clone();
let rid = state.resource_table.add(WebGpuDevice(instance, device));
let queue_rid = state
.resource_table
.add(queue::WebGpuQueue(instance2, queue));
Ok(GpuDeviceRes {
rid,
queue_rid,
features,
limits,
})
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GPUAdapterInfo {
vendor: String,
architecture: String,
device: String,
description: String,
}
#[op2]
#[serde]
pub fn op_webgpu_request_adapter_info(
state: Rc<RefCell<OpState>>,
#[smi] adapter_rid: ResourceId,
) -> Result<GPUAdapterInfo, InitError> {
let state = state.borrow_mut();
let adapter_resource =
state.resource_table.get::<WebGpuAdapter>(adapter_rid)?;
let adapter = adapter_resource.1;
let instance = state.borrow::<Instance>();
let info = gfx_select!(adapter => instance.adapter_get_info(adapter))
.map_err(InitError::InvalidAdapter)?;
Ok(GPUAdapterInfo {
vendor: info.vendor.to_string(),
architecture: String::new(), // TODO(#2170)
device: info.device.to_string(),
description: info.name,
})
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateQuerySetArgs {
device_rid: ResourceId,
label: String,
#[serde(flatten)]
r#type: GpuQueryType,
count: u32,
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case", tag = "type")]
enum GpuQueryType {
Occlusion,
Timestamp,
}
impl From<GpuQueryType> for wgpu_types::QueryType {
fn from(query_type: GpuQueryType) -> Self {
match query_type {
GpuQueryType::Occlusion => wgpu_types::QueryType::Occlusion,
GpuQueryType::Timestamp => wgpu_types::QueryType::Timestamp,
#[string]
fn getPreferredCanvasFormat(&self) -> &'static str {
// https://github.com/mozilla/gecko-dev/blob/b75080bb8b11844d18cb5f9ac6e68a866ef8e243/dom/webgpu/Instance.h#L42-L47
if cfg!(target_os = "android") {
texture::GPUTextureFormat::Rgba8unorm.as_str()
} else {
texture::GPUTextureFormat::Bgra8unorm.as_str()
}
}
}
#[op2]
#[serde]
pub fn op_webgpu_create_query_set(
state: &mut OpState,
#[serde] args: CreateQuerySetArgs,
) -> Result<WebGpuResult, InitError> {
let device_resource =
state.resource_table.get::<WebGpuDevice>(args.device_rid)?;
let device = device_resource.1;
let instance = state.borrow::<Instance>();
let descriptor = wgpu_types::QuerySetDescriptor {
label: Some(Cow::Owned(args.label)),
ty: args.r#type.into(),
count: args.count,
};
gfx_put!(device => instance.device_create_query_set(
device,
&descriptor,
None
) => state, WebGpuQuerySet)
}

View file

@ -1,460 +0,0 @@
// Copyright 2018-2025 the Deno authors. MIT license.
use std::borrow::Cow;
use std::collections::HashMap;
use std::rc::Rc;
use deno_core::error::ResourceError;
use deno_core::op2;
use deno_core::OpState;
use deno_core::Resource;
use deno_core::ResourceId;
use serde::Deserialize;
use serde::Serialize;
use super::error::WebGpuError;
use super::error::WebGpuResult;
const MAX_BIND_GROUPS: usize = 8;
pub(crate) struct WebGpuPipelineLayout(
pub(crate) crate::Instance,
pub(crate) wgpu_core::id::PipelineLayoutId,
);
impl Resource for WebGpuPipelineLayout {
fn name(&self) -> Cow<str> {
"webGPUPipelineLayout".into()
}
fn close(self: Rc<Self>) {
gfx_select!(self.1 => self.0.pipeline_layout_drop(self.1));
}
}
pub(crate) struct WebGpuComputePipeline(
pub(crate) crate::Instance,
pub(crate) wgpu_core::id::ComputePipelineId,
);
impl Resource for WebGpuComputePipeline {
fn name(&self) -> Cow<str> {
"webGPUComputePipeline".into()
}
fn close(self: Rc<Self>) {
gfx_select!(self.1 => self.0.compute_pipeline_drop(self.1));
}
}
pub(crate) struct WebGpuRenderPipeline(
pub(crate) crate::Instance,
pub(crate) wgpu_core::id::RenderPipelineId,
);
impl Resource for WebGpuRenderPipeline {
fn name(&self) -> Cow<str> {
"webGPURenderPipeline".into()
}
fn close(self: Rc<Self>) {
gfx_select!(self.1 => self.0.render_pipeline_drop(self.1));
}
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum GPUAutoLayoutMode {
Auto,
}
#[derive(Deserialize)]
#[serde(untagged)]
pub enum GPUPipelineLayoutOrGPUAutoLayoutMode {
Layout(ResourceId),
Auto(GPUAutoLayoutMode),
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GpuProgrammableStage {
module: ResourceId,
entry_point: Option<String>,
constants: Option<HashMap<String, f64>>,
}
#[op2]
#[serde]
pub fn op_webgpu_create_compute_pipeline(
state: &mut OpState,
#[smi] device_rid: ResourceId,
#[string] label: Cow<str>,
#[serde] layout: GPUPipelineLayoutOrGPUAutoLayoutMode,
#[serde] compute: GpuProgrammableStage,
) -> Result<WebGpuResult, ResourceError> {
let instance = state.borrow::<super::Instance>();
let device_resource = state
.resource_table
.get::<super::WebGpuDevice>(device_rid)?;
let device = device_resource.1;
let pipeline_layout = match layout {
GPUPipelineLayoutOrGPUAutoLayoutMode::Layout(rid) => {
let id = state.resource_table.get::<WebGpuPipelineLayout>(rid)?;
Some(id.1)
}
GPUPipelineLayoutOrGPUAutoLayoutMode::Auto(GPUAutoLayoutMode::Auto) => None,
};
let compute_shader_module_resource =
state
.resource_table
.get::<super::shader::WebGpuShaderModule>(compute.module)?;
let descriptor = wgpu_core::pipeline::ComputePipelineDescriptor {
label: Some(label),
layout: pipeline_layout,
stage: wgpu_core::pipeline::ProgrammableStageDescriptor {
module: compute_shader_module_resource.1,
entry_point: compute.entry_point.map(Cow::from),
constants: Cow::Owned(compute.constants.unwrap_or_default()),
zero_initialize_workgroup_memory: true,
},
};
let implicit_pipelines = match layout {
GPUPipelineLayoutOrGPUAutoLayoutMode::Layout(_) => None,
GPUPipelineLayoutOrGPUAutoLayoutMode::Auto(GPUAutoLayoutMode::Auto) => {
Some(wgpu_core::device::ImplicitPipelineIds {
root_id: None,
group_ids: &[None; MAX_BIND_GROUPS],
})
}
};
let (compute_pipeline, maybe_err) = gfx_select!(device => instance.device_create_compute_pipeline(
device,
&descriptor,
None,
implicit_pipelines
));
let rid = state
.resource_table
.add(WebGpuComputePipeline(instance.clone(), compute_pipeline));
Ok(WebGpuResult::rid_err(rid, maybe_err))
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PipelineLayout {
rid: ResourceId,
label: String,
err: Option<WebGpuError>,
}
#[op2]
#[serde]
pub fn op_webgpu_compute_pipeline_get_bind_group_layout(
state: &mut OpState,
#[smi] compute_pipeline_rid: ResourceId,
index: u32,
) -> Result<PipelineLayout, ResourceError> {
let instance = state.borrow::<super::Instance>();
let compute_pipeline_resource = state
.resource_table
.get::<WebGpuComputePipeline>(compute_pipeline_rid)?;
let compute_pipeline = compute_pipeline_resource.1;
let (bind_group_layout, maybe_err) = gfx_select!(compute_pipeline => instance.compute_pipeline_get_bind_group_layout(compute_pipeline, index, None));
let label = gfx_select!(bind_group_layout => instance.bind_group_layout_label(bind_group_layout));
let rid = state
.resource_table
.add(super::binding::WebGpuBindGroupLayout(
instance.clone(),
bind_group_layout,
));
Ok(PipelineLayout {
rid,
label,
err: maybe_err.map(WebGpuError::from),
})
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum GpuCullMode {
None,
Front,
Back,
}
impl From<GpuCullMode> for Option<wgpu_types::Face> {
fn from(value: GpuCullMode) -> Option<wgpu_types::Face> {
match value {
GpuCullMode::None => None,
GpuCullMode::Front => Some(wgpu_types::Face::Front),
GpuCullMode::Back => Some(wgpu_types::Face::Back),
}
}
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct GpuPrimitiveState {
topology: wgpu_types::PrimitiveTopology,
strip_index_format: Option<wgpu_types::IndexFormat>,
front_face: wgpu_types::FrontFace,
cull_mode: GpuCullMode,
unclipped_depth: bool,
}
impl From<GpuPrimitiveState> for wgpu_types::PrimitiveState {
fn from(value: GpuPrimitiveState) -> wgpu_types::PrimitiveState {
wgpu_types::PrimitiveState {
topology: value.topology,
strip_index_format: value.strip_index_format,
front_face: value.front_face,
cull_mode: value.cull_mode.into(),
unclipped_depth: value.unclipped_depth,
polygon_mode: Default::default(), // native-only
conservative: false, // native-only
}
}
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct GpuDepthStencilState {
format: wgpu_types::TextureFormat,
depth_write_enabled: bool,
depth_compare: wgpu_types::CompareFunction,
stencil_front: wgpu_types::StencilFaceState,
stencil_back: wgpu_types::StencilFaceState,
stencil_read_mask: u32,
stencil_write_mask: u32,
depth_bias: i32,
depth_bias_slope_scale: f32,
depth_bias_clamp: f32,
}
impl From<GpuDepthStencilState> for wgpu_types::DepthStencilState {
fn from(state: GpuDepthStencilState) -> wgpu_types::DepthStencilState {
wgpu_types::DepthStencilState {
format: state.format,
depth_write_enabled: state.depth_write_enabled,
depth_compare: state.depth_compare,
stencil: wgpu_types::StencilState {
front: state.stencil_front,
back: state.stencil_back,
read_mask: state.stencil_read_mask,
write_mask: state.stencil_write_mask,
},
bias: wgpu_types::DepthBiasState {
constant: state.depth_bias,
slope_scale: state.depth_bias_slope_scale,
clamp: state.depth_bias_clamp,
},
}
}
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct GpuVertexBufferLayout {
array_stride: u64,
step_mode: wgpu_types::VertexStepMode,
attributes: Vec<wgpu_types::VertexAttribute>,
}
impl<'a> From<GpuVertexBufferLayout>
for wgpu_core::pipeline::VertexBufferLayout<'a>
{
fn from(
layout: GpuVertexBufferLayout,
) -> wgpu_core::pipeline::VertexBufferLayout<'a> {
wgpu_core::pipeline::VertexBufferLayout {
array_stride: layout.array_stride,
step_mode: layout.step_mode,
attributes: Cow::Owned(layout.attributes),
}
}
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct GpuVertexState {
module: ResourceId,
entry_point: Option<String>,
constants: Option<HashMap<String, f64>>,
buffers: Vec<Option<GpuVertexBufferLayout>>,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct GpuMultisampleState {
count: u32,
mask: u64,
alpha_to_coverage_enabled: bool,
}
impl From<GpuMultisampleState> for wgpu_types::MultisampleState {
fn from(gms: GpuMultisampleState) -> wgpu_types::MultisampleState {
wgpu_types::MultisampleState {
count: gms.count,
mask: gms.mask,
alpha_to_coverage_enabled: gms.alpha_to_coverage_enabled,
}
}
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct GpuFragmentState {
targets: Vec<Option<wgpu_types::ColorTargetState>>,
module: u32,
entry_point: Option<String>,
constants: Option<HashMap<String, f64>>,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateRenderPipelineArgs {
device_rid: ResourceId,
label: String,
layout: GPUPipelineLayoutOrGPUAutoLayoutMode,
vertex: GpuVertexState,
primitive: GpuPrimitiveState,
depth_stencil: Option<GpuDepthStencilState>,
multisample: wgpu_types::MultisampleState,
fragment: Option<GpuFragmentState>,
}
#[op2]
#[serde]
pub fn op_webgpu_create_render_pipeline(
state: &mut OpState,
#[serde] args: CreateRenderPipelineArgs,
) -> Result<WebGpuResult, ResourceError> {
let instance = state.borrow::<super::Instance>();
let device_resource = state
.resource_table
.get::<super::WebGpuDevice>(args.device_rid)?;
let device = device_resource.1;
let layout = match args.layout {
GPUPipelineLayoutOrGPUAutoLayoutMode::Layout(rid) => {
let pipeline_layout_resource =
state.resource_table.get::<WebGpuPipelineLayout>(rid)?;
Some(pipeline_layout_resource.1)
}
GPUPipelineLayoutOrGPUAutoLayoutMode::Auto(GPUAutoLayoutMode::Auto) => None,
};
let vertex_shader_module_resource =
state
.resource_table
.get::<super::shader::WebGpuShaderModule>(args.vertex.module)?;
let fragment = if let Some(fragment) = args.fragment {
let fragment_shader_module_resource =
state
.resource_table
.get::<super::shader::WebGpuShaderModule>(fragment.module)?;
Some(wgpu_core::pipeline::FragmentState {
stage: wgpu_core::pipeline::ProgrammableStageDescriptor {
module: fragment_shader_module_resource.1,
entry_point: fragment.entry_point.map(Cow::from),
constants: Cow::Owned(fragment.constants.unwrap_or_default()),
// Required to be true for WebGPU
zero_initialize_workgroup_memory: true,
},
targets: Cow::Owned(fragment.targets),
})
} else {
None
};
let vertex_buffers = args
.vertex
.buffers
.into_iter()
.flatten()
.map(Into::into)
.collect();
let descriptor = wgpu_core::pipeline::RenderPipelineDescriptor {
label: Some(Cow::Owned(args.label)),
layout,
vertex: wgpu_core::pipeline::VertexState {
stage: wgpu_core::pipeline::ProgrammableStageDescriptor {
module: vertex_shader_module_resource.1,
entry_point: args.vertex.entry_point.map(Cow::Owned),
constants: Cow::Owned(args.vertex.constants.unwrap_or_default()),
// Required to be true for WebGPU
zero_initialize_workgroup_memory: true,
},
buffers: Cow::Owned(vertex_buffers),
},
primitive: args.primitive.into(),
depth_stencil: args.depth_stencil.map(Into::into),
multisample: args.multisample,
fragment,
multiview: None,
};
let implicit_pipelines = match args.layout {
GPUPipelineLayoutOrGPUAutoLayoutMode::Layout(_) => None,
GPUPipelineLayoutOrGPUAutoLayoutMode::Auto(GPUAutoLayoutMode::Auto) => {
Some(wgpu_core::device::ImplicitPipelineIds {
root_id: None,
group_ids: &[None; MAX_BIND_GROUPS],
})
}
};
let (render_pipeline, maybe_err) = gfx_select!(device => instance.device_create_render_pipeline(
device,
&descriptor,
None,
implicit_pipelines
));
let rid = state
.resource_table
.add(WebGpuRenderPipeline(instance.clone(), render_pipeline));
Ok(WebGpuResult::rid_err(rid, maybe_err))
}
#[op2]
#[serde]
pub fn op_webgpu_render_pipeline_get_bind_group_layout(
state: &mut OpState,
#[smi] render_pipeline_rid: ResourceId,
index: u32,
) -> Result<PipelineLayout, ResourceError> {
let instance = state.borrow::<super::Instance>();
let render_pipeline_resource = state
.resource_table
.get::<WebGpuRenderPipeline>(render_pipeline_rid)?;
let render_pipeline = render_pipeline_resource.1;
let (bind_group_layout, maybe_err) = gfx_select!(render_pipeline => instance.render_pipeline_get_bind_group_layout(render_pipeline, index, None));
let label = gfx_select!(bind_group_layout => instance.bind_group_layout_label(bind_group_layout));
let rid = state
.resource_table
.add(super::binding::WebGpuBindGroupLayout(
instance.clone(),
bind_group_layout,
));
Ok(PipelineLayout {
rid,
label,
err: maybe_err.map(WebGpuError::from),
})
}

View file

@ -0,0 +1,52 @@
// Copyright 2018-2025 the Deno authors. MIT license.
use deno_core::cppgc::Ptr;
use deno_core::op2;
use deno_core::webidl::Nullable;
use deno_core::webidl::WebIdlInterfaceConverter;
use deno_core::GarbageCollected;
use deno_core::WebIDL;
use crate::Instance;
pub struct GPUPipelineLayout {
pub instance: Instance,
pub id: wgpu_core::id::PipelineLayoutId,
pub label: String,
}
impl Drop for GPUPipelineLayout {
fn drop(&mut self) {
self.instance.pipeline_layout_drop(self.id);
}
}
impl WebIdlInterfaceConverter for GPUPipelineLayout {
const NAME: &'static str = "GPUPipelineLayout";
}
impl GarbageCollected for GPUPipelineLayout {}
#[op2]
impl GPUPipelineLayout {
#[getter]
#[string]
fn label(&self) -> String {
self.label.clone()
}
#[setter]
#[string]
fn label(&self, #[webidl] _label: String) {
// TODO(@crowlKats): no-op, needs wpgu to implement changing the label
}
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPUPipelineLayoutDescriptor {
#[webidl(default = String::new())]
pub label: String,
pub bind_group_layouts:
Vec<Nullable<Ptr<super::bind_group_layout::GPUBindGroupLayout>>>,
}

87
ext/webgpu/query_set.rs Normal file
View file

@ -0,0 +1,87 @@
// Copyright 2018-2025 the Deno authors. MIT license.
use deno_core::op2;
use deno_core::webidl::WebIdlInterfaceConverter;
use deno_core::GarbageCollected;
use deno_core::WebIDL;
use deno_error::JsErrorBox;
use crate::Instance;
pub struct GPUQuerySet {
pub instance: Instance,
pub id: wgpu_core::id::QuerySetId,
pub r#type: GPUQueryType,
pub count: u32,
pub label: String,
}
impl Drop for GPUQuerySet {
fn drop(&mut self) {
self.instance.query_set_drop(self.id);
}
}
impl WebIdlInterfaceConverter for GPUQuerySet {
const NAME: &'static str = "GPUQuerySet";
}
impl GarbageCollected for GPUQuerySet {}
#[op2]
impl GPUQuerySet {
#[getter]
#[string]
fn label(&self) -> String {
self.label.clone()
}
#[setter]
#[string]
fn label(&self, #[webidl] _label: String) {
// TODO(@crowlKats): no-op, needs wpgu to implement changing the label
}
#[fast]
fn destroy(&self) -> Result<(), JsErrorBox> {
Err(JsErrorBox::generic(
"This operation is currently not supported",
))
}
#[getter]
#[string]
fn r#type(&self) -> &'static str {
self.r#type.as_str()
}
#[getter]
fn count(&self) -> u32 {
self.count
}
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPUQuerySetDescriptor {
#[webidl(default = String::new())]
pub label: String,
pub r#type: GPUQueryType,
#[options(enforce_range = true)]
pub count: u32,
}
#[derive(WebIDL, Clone)]
#[webidl(enum)]
pub(crate) enum GPUQueryType {
Occlusion,
Timestamp,
}
impl From<GPUQueryType> for wgpu_types::QueryType {
fn from(value: GPUQueryType) -> Self {
match value {
GPUQueryType::Occlusion => Self::Occlusion,
GPUQueryType::Timestamp => Self::Timestamp,
}
}
}

View file

@ -1,144 +1,150 @@
// Copyright 2018-2025 the Deno authors. MIT license.
use std::borrow::Cow;
use std::rc::Rc;
use deno_core::error::ResourceError;
use deno_core::cppgc::Ptr;
use deno_core::op2;
use deno_core::OpState;
use deno_core::Resource;
use deno_core::ResourceId;
use serde::Deserialize;
use deno_core::GarbageCollected;
use deno_core::WebIDL;
use deno_error::JsErrorBox;
use super::error::WebGpuResult;
use crate::command_encoder::WebGpuCommandBuffer;
use crate::buffer::GPUBuffer;
use crate::command_buffer::GPUCommandBuffer;
use crate::texture::GPUTexture;
use crate::texture::GPUTextureAspect;
use crate::webidl::GPUExtent3D;
use crate::webidl::GPUOrigin3D;
use crate::Instance;
pub struct WebGpuQueue(pub Instance, pub wgpu_core::id::QueueId);
impl Resource for WebGpuQueue {
fn name(&self) -> Cow<str> {
"webGPUQueue".into()
}
pub struct GPUQueue {
pub instance: Instance,
pub error_handler: super::error::ErrorHandler,
fn close(self: Rc<Self>) {
gfx_select!(self.1 => self.0.queue_drop(self.1));
pub label: String,
pub id: wgpu_core::id::QueueId,
}
impl Drop for GPUQueue {
fn drop(&mut self) {
self.instance.queue_drop(self.id);
}
}
#[op2]
#[serde]
pub fn op_webgpu_queue_submit(
state: &mut OpState,
#[smi] queue_rid: ResourceId,
#[serde] command_buffers: Vec<ResourceId>,
) -> Result<WebGpuResult, ResourceError> {
let instance = state.borrow::<Instance>();
let queue_resource = state.resource_table.get::<WebGpuQueue>(queue_rid)?;
let queue = queue_resource.1;
impl GarbageCollected for GPUQueue {}
#[op2]
impl GPUQueue {
#[getter]
#[string]
fn label(&self) -> String {
self.label.clone()
}
#[setter]
#[string]
fn label(&self, #[webidl] _label: String) {
// TODO(@crowlKats): no-op, needs wpgu to implement changing the label
}
#[required(1)]
fn submit(&self, #[webidl] command_buffers: Vec<Ptr<GPUCommandBuffer>>) {
let ids = command_buffers
.iter()
.map(|rid| {
let buffer_resource =
state.resource_table.get::<WebGpuCommandBuffer>(*rid)?;
let mut id = buffer_resource.1.borrow_mut();
Ok(id.take().unwrap())
})
.collect::<Result<Vec<_>, ResourceError>>()?;
.into_iter()
.map(|cb| cb.id)
.collect::<Vec<_>>();
let maybe_err =
gfx_select!(queue => instance.queue_submit(queue, &ids)).err();
let err = self.instance.queue_submit(self.id, &ids).err();
for rid in command_buffers {
let resource = state.resource_table.take::<WebGpuCommandBuffer>(rid)?;
resource.close();
}
Ok(WebGpuResult::maybe_err(maybe_err))
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GpuImageDataLayout {
offset: u64,
bytes_per_row: Option<u32>,
rows_per_image: Option<u32>,
}
impl From<GpuImageDataLayout> for wgpu_types::ImageDataLayout {
fn from(layout: GpuImageDataLayout) -> Self {
wgpu_types::ImageDataLayout {
offset: layout.offset,
bytes_per_row: layout.bytes_per_row,
rows_per_image: layout.rows_per_image,
if let Some((_, err)) = err {
self.error_handler.push_error(Some(err));
}
}
}
#[op2]
#[serde]
pub fn op_webgpu_write_buffer(
state: &mut OpState,
#[smi] queue_rid: ResourceId,
#[smi] buffer: ResourceId,
#[number] buffer_offset: u64,
#[number] data_offset: usize,
#[number] size: Option<usize>,
#[buffer] buf: &[u8],
) -> Result<WebGpuResult, ResourceError> {
let instance = state.borrow::<Instance>();
let buffer_resource = state
.resource_table
.get::<super::buffer::WebGpuBuffer>(buffer)?;
let buffer = buffer_resource.1;
let queue_resource = state.resource_table.get::<WebGpuQueue>(queue_rid)?;
let queue = queue_resource.1;
let data = match size {
Some(size) => &buf[data_offset..(data_offset + size)],
None => &buf[data_offset..],
};
let maybe_err = gfx_select!(queue => instance.queue_write_buffer(
queue,
buffer,
buffer_offset,
data
#[async_method]
async fn on_submitted_work_done(&self) -> Result<(), JsErrorBox> {
Err(JsErrorBox::generic(
"This operation is currently not supported",
))
}
#[required(3)]
fn write_buffer(
&self,
#[webidl] buffer: Ptr<GPUBuffer>,
#[webidl(options(enforce_range = true))] buffer_offset: u64,
#[anybuffer] buf: &[u8],
#[webidl(default = 0, options(enforce_range = true))] data_offset: u64,
#[webidl(options(enforce_range = true))] size: Option<u64>,
) {
let data = match size {
Some(size) => {
&buf[(data_offset as usize)..((data_offset + size) as usize)]
}
None => &buf[(data_offset as usize)..],
};
let err = self
.instance
.queue_write_buffer(self.id, buffer.id, buffer_offset, data)
.err();
Ok(WebGpuResult::maybe_err(maybe_err))
}
#[op2]
#[serde]
pub fn op_webgpu_write_texture(
state: &mut OpState,
#[smi] queue_rid: ResourceId,
#[serde] destination: super::command_encoder::GpuImageCopyTexture,
#[serde] data_layout: GpuImageDataLayout,
#[serde] size: wgpu_types::Extent3d,
#[buffer] buf: &[u8],
) -> Result<WebGpuResult, ResourceError> {
let instance = state.borrow::<Instance>();
let texture_resource = state
.resource_table
.get::<super::texture::WebGpuTexture>(destination.texture)?;
let queue_resource = state.resource_table.get::<WebGpuQueue>(queue_rid)?;
let queue = queue_resource.1;
self.error_handler.push_error(err);
}
#[required(4)]
fn write_texture(
&self,
#[webidl] destination: GPUTexelCopyTextureInfo,
#[anybuffer] buf: &[u8],
#[webidl] data_layout: GPUTexelCopyBufferLayout,
#[webidl] size: GPUExtent3D,
) {
let destination = wgpu_core::command::ImageCopyTexture {
texture: texture_resource.id,
texture: destination.texture.id,
mip_level: destination.mip_level,
origin: destination.origin,
aspect: destination.aspect,
origin: destination.origin.into(),
aspect: destination.aspect.into(),
};
let data_layout = data_layout.into();
gfx_ok!(queue => instance.queue_write_texture(
queue,
let data_layout = wgpu_types::ImageDataLayout {
offset: data_layout.offset,
bytes_per_row: data_layout.bytes_per_row,
rows_per_image: data_layout.rows_per_image,
};
let err = self
.instance
.queue_write_texture(
self.id,
&destination,
buf,
&data_layout,
&size
))
&size.into(),
)
.err();
self.error_handler.push_error(err);
}
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPUTexelCopyTextureInfo {
pub texture: Ptr<GPUTexture>,
#[options(enforce_range = true)]
pub mip_level: u32,
#[webidl(default = Default::default())]
pub origin: GPUOrigin3D,
#[webidl(default = GPUTextureAspect::All)]
pub aspect: GPUTextureAspect,
}
#[derive(WebIDL)]
#[webidl(dictionary)]
struct GPUTexelCopyBufferLayout {
#[webidl(default = 0)]
#[options(enforce_range = true)]
offset: u64,
#[options(enforce_range = true)]
bytes_per_row: Option<u32>,
#[options(enforce_range = true)]
rows_per_image: Option<u32>,
}

416
ext/webgpu/render_bundle.rs Normal file
View file

@ -0,0 +1,416 @@
// Copyright 2018-2025 the Deno authors. MIT license.
use std::borrow::Cow;
use std::cell::RefCell;
use std::num::NonZeroU64;
use deno_core::cppgc::Ptr;
use deno_core::op2;
use deno_core::v8;
use deno_core::webidl::IntOptions;
use deno_core::webidl::Nullable;
use deno_core::webidl::WebIdlConverter;
use deno_core::webidl::WebIdlError;
use deno_core::webidl::WebIdlInterfaceConverter;
use deno_core::GarbageCollected;
use deno_core::WebIDL;
use deno_error::JsErrorBox;
use crate::buffer::GPUBuffer;
use crate::texture::GPUTextureFormat;
use crate::Instance;
pub struct GPURenderBundleEncoder {
pub instance: Instance,
pub error_handler: super::error::ErrorHandler,
pub encoder: RefCell<Option<wgpu_core::command::RenderBundleEncoder>>,
pub label: String,
}
impl GarbageCollected for GPURenderBundleEncoder {}
#[op2]
impl GPURenderBundleEncoder {
#[getter]
#[string]
fn label(&self) -> String {
self.label.clone()
}
#[setter]
#[string]
fn label(&self, #[webidl] _label: String) {
// TODO(@crowlKats): no-op, needs wpgu to implement changing the label
}
#[cppgc]
fn finish(
&self,
#[webidl] descriptor: GPURenderBundleDescriptor,
) -> GPURenderBundle {
let wgpu_descriptor = wgpu_core::command::RenderBundleDescriptor {
label: Some(Cow::Owned(descriptor.label.clone())),
};
let (id, err) = self.instance.render_bundle_encoder_finish(
self.encoder.borrow_mut().take().unwrap(),
&wgpu_descriptor,
None,
);
self.error_handler.push_error(err);
GPURenderBundle {
instance: self.instance.clone(),
id,
label: descriptor.label.clone(),
}
}
fn push_debug_group(
&self,
#[webidl] group_label: String,
) -> Result<(), JsErrorBox> {
let mut encoder = self.encoder.borrow_mut();
let encoder = encoder.as_mut().ok_or_else(|| {
JsErrorBox::generic("Encoder has already been finished")
})?;
let label = std::ffi::CString::new(group_label).unwrap();
unsafe {
wgpu_core::command::bundle_ffi::wgpu_render_bundle_push_debug_group(
encoder,
label.as_ptr(),
);
}
Ok(())
}
#[fast]
fn pop_debug_group(&self) -> Result<(), JsErrorBox> {
let mut encoder = self.encoder.borrow_mut();
let encoder = encoder.as_mut().ok_or_else(|| {
JsErrorBox::generic("Encoder has already been finished")
})?;
wgpu_core::command::bundle_ffi::wgpu_render_bundle_pop_debug_group(encoder);
Ok(())
}
fn insert_debug_marker(
&self,
#[webidl] marker_label: String,
) -> Result<(), JsErrorBox> {
let mut encoder = self.encoder.borrow_mut();
let encoder = encoder.as_mut().ok_or_else(|| {
JsErrorBox::generic("Encoder has already been finished")
})?;
let label = std::ffi::CString::new(marker_label).unwrap();
unsafe {
wgpu_core::command::bundle_ffi::wgpu_render_bundle_insert_debug_marker(
encoder,
label.as_ptr(),
);
}
Ok(())
}
fn set_bind_group<'a>(
&self,
scope: &mut v8::HandleScope<'a>,
#[webidl(options(enforce_range = true))] index: u32,
#[webidl] bind_group: Nullable<Ptr<crate::bind_group::GPUBindGroup>>,
dynamic_offsets: v8::Local<'a, v8::Value>,
dynamic_offsets_data_start: v8::Local<'a, v8::Value>,
dynamic_offsets_data_length: v8::Local<'a, v8::Value>,
) -> Result<(), SetBindGroupError> {
let mut encoder = self.encoder.borrow_mut();
let encoder = encoder.as_mut().ok_or_else(|| {
JsErrorBox::generic("Encoder has already been finished")
})?;
const PREFIX: &str =
"Failed to execute 'setBindGroup' on 'GPUComputePassEncoder'";
if let Ok(uint_32) = dynamic_offsets.try_cast::<v8::Uint32Array>() {
let start = u64::convert(
scope,
dynamic_offsets_data_start,
Cow::Borrowed(PREFIX),
(|| Cow::Borrowed("Argument 4")).into(),
&IntOptions {
clamp: false,
enforce_range: true,
},
)? as usize;
let len = u32::convert(
scope,
dynamic_offsets_data_length,
Cow::Borrowed(PREFIX),
(|| Cow::Borrowed("Argument 5")).into(),
&IntOptions {
clamp: false,
enforce_range: true,
},
)? as usize;
let ab = uint_32.buffer(scope).unwrap();
let ptr = ab.data().unwrap();
let ab_len = ab.byte_length() / 4;
let data =
unsafe { std::slice::from_raw_parts(ptr.as_ptr() as _, ab_len) };
let offsets = &data[start..(start + len)];
unsafe {
wgpu_core::command::bundle_ffi::wgpu_render_bundle_set_bind_group(
encoder,
index,
bind_group.into_option().map(|bind_group| bind_group.id),
offsets.as_ptr(),
offsets.len(),
);
}
} else {
let offsets = <Option<Vec<u32>>>::convert(
scope,
dynamic_offsets,
Cow::Borrowed(PREFIX),
(|| Cow::Borrowed("Argument 3")).into(),
&IntOptions {
clamp: false,
enforce_range: true,
},
)?
.unwrap_or_default();
unsafe {
wgpu_core::command::bundle_ffi::wgpu_render_bundle_set_bind_group(
encoder,
index,
bind_group.into_option().map(|bind_group| bind_group.id),
offsets.as_ptr(),
offsets.len(),
);
}
}
Ok(())
}
fn set_pipeline(
&self,
#[webidl] pipeline: Ptr<crate::render_pipeline::GPURenderPipeline>,
) -> Result<(), JsErrorBox> {
let mut encoder = self.encoder.borrow_mut();
let encoder = encoder.as_mut().ok_or_else(|| {
JsErrorBox::generic("Encoder has already been finished")
})?;
wgpu_core::command::bundle_ffi::wgpu_render_bundle_set_pipeline(
encoder,
pipeline.id,
);
Ok(())
}
#[required(2)]
fn set_index_buffer(
&self,
#[webidl] buffer: Ptr<GPUBuffer>,
#[webidl] index_format: crate::render_pipeline::GPUIndexFormat,
#[webidl(default = 0, options(enforce_range = true))] offset: u64,
#[webidl(options(enforce_range = true))] size: Option<u64>,
) -> Result<(), JsErrorBox> {
let mut encoder = self.encoder.borrow_mut();
let encoder = encoder.as_mut().ok_or_else(|| {
JsErrorBox::generic("Encoder has already been finished")
})?;
encoder.set_index_buffer(
buffer.id,
index_format.into(),
offset,
size.and_then(NonZeroU64::new),
);
Ok(())
}
#[required(2)]
fn set_vertex_buffer(
&self,
#[webidl(options(enforce_range = true))] slot: u32,
#[webidl] buffer: Ptr<GPUBuffer>, // TODO: support nullable buffer
#[webidl(default = 0, options(enforce_range = true))] offset: u64,
#[webidl(options(enforce_range = true))] size: Option<u64>,
) -> Result<(), JsErrorBox> {
let mut encoder = self.encoder.borrow_mut();
let encoder = encoder.as_mut().ok_or_else(|| {
JsErrorBox::generic("Encoder has already been finished")
})?;
wgpu_core::command::bundle_ffi::wgpu_render_bundle_set_vertex_buffer(
encoder,
slot,
buffer.id,
offset,
size.and_then(NonZeroU64::new),
);
Ok(())
}
#[required(1)]
fn draw(
&self,
#[webidl(options(enforce_range = true))] vertex_count: u32,
#[webidl(default = 1, options(enforce_range = true))] instance_count: u32,
#[webidl(default = 0, options(enforce_range = true))] first_vertex: u32,
#[webidl(default = 0, options(enforce_range = true))] first_instance: u32,
) -> Result<(), JsErrorBox> {
let mut encoder = self.encoder.borrow_mut();
let encoder = encoder.as_mut().ok_or_else(|| {
JsErrorBox::generic("Encoder has already been finished")
})?;
wgpu_core::command::bundle_ffi::wgpu_render_bundle_draw(
encoder,
vertex_count,
instance_count,
first_vertex,
first_instance,
);
Ok(())
}
#[required(1)]
fn draw_indexed(
&self,
#[webidl(options(enforce_range = true))] index_count: u32,
#[webidl(default = 1, options(enforce_range = true))] instance_count: u32,
#[webidl(default = 0, options(enforce_range = true))] first_index: u32,
#[webidl(default = 0, options(enforce_range = true))] base_vertex: i32,
#[webidl(default = 0, options(enforce_range = true))] first_instance: u32,
) -> Result<(), JsErrorBox> {
let mut encoder = self.encoder.borrow_mut();
let encoder = encoder.as_mut().ok_or_else(|| {
JsErrorBox::generic("Encoder has already been finished")
})?;
wgpu_core::command::bundle_ffi::wgpu_render_bundle_draw_indexed(
encoder,
index_count,
instance_count,
first_index,
base_vertex,
first_instance,
);
Ok(())
}
#[required(2)]
fn draw_indirect(
&self,
#[webidl] indirect_buffer: Ptr<GPUBuffer>,
#[webidl(options(enforce_range = true))] indirect_offset: u64,
) -> Result<(), JsErrorBox> {
let mut encoder = self.encoder.borrow_mut();
let encoder = encoder.as_mut().ok_or_else(|| {
JsErrorBox::generic("Encoder has already been finished")
})?;
wgpu_core::command::bundle_ffi::wgpu_render_bundle_draw_indirect(
encoder,
indirect_buffer.id,
indirect_offset,
);
Ok(())
}
#[required(2)]
fn draw_indexed_indirect(
&self,
#[webidl] indirect_buffer: Ptr<GPUBuffer>,
#[webidl(options(enforce_range = true))] indirect_offset: u64,
) -> Result<(), JsErrorBox> {
let mut encoder = self.encoder.borrow_mut();
let encoder = encoder.as_mut().ok_or_else(|| {
JsErrorBox::generic("Encoder has already been finished")
})?;
wgpu_core::command::bundle_ffi::wgpu_render_bundle_draw_indexed_indirect(
encoder,
indirect_buffer.id,
indirect_offset,
);
Ok(())
}
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPURenderBundleEncoderDescriptor {
#[webidl(default = String::new())]
pub label: String,
pub color_formats: Vec<Nullable<GPUTextureFormat>>,
pub depth_stencil_format: Option<GPUTextureFormat>,
#[webidl(default = 1)]
#[options(enforce_range = true)]
pub sample_count: u32,
#[webidl(default = false)]
pub depth_read_only: bool,
#[webidl(default = false)]
pub stencil_read_only: bool,
}
#[derive(Debug, thiserror::Error, deno_error::JsError)]
enum SetBindGroupError {
#[class(inherit)]
#[error(transparent)]
WebIDL(#[from] WebIdlError),
#[class(inherit)]
#[error(transparent)]
Other(#[from] JsErrorBox),
}
pub struct GPURenderBundle {
pub instance: Instance,
pub id: wgpu_core::id::RenderBundleId,
pub label: String,
}
impl Drop for GPURenderBundle {
fn drop(&mut self) {
self.instance.render_bundle_drop(self.id);
}
}
impl WebIdlInterfaceConverter for GPURenderBundle {
const NAME: &'static str = "GPURenderBundle";
}
impl GarbageCollected for GPURenderBundle {}
#[op2]
impl GPURenderBundle {
#[getter]
#[string]
fn label(&self) -> String {
self.label.clone()
}
#[setter]
#[string]
fn label(&self, #[webidl] _label: String) {
// TODO(@crowlKats): no-op, needs wpgu to implement changing the label
}
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPURenderBundleDescriptor {
#[webidl(default = String::new())]
pub label: String,
}

View file

@ -2,506 +2,496 @@
use std::borrow::Cow;
use std::cell::RefCell;
use std::num::NonZeroU64;
use deno_core::error::ResourceError;
use deno_core::cppgc::Ptr;
use deno_core::op2;
use deno_core::OpState;
use deno_core::Resource;
use deno_core::ResourceId;
use serde::Deserialize;
use deno_core::v8;
use deno_core::webidl::IntOptions;
use deno_core::webidl::Nullable;
use deno_core::webidl::WebIdlConverter;
use deno_core::webidl::WebIdlError;
use deno_core::GarbageCollected;
use deno_core::WebIDL;
use super::error::WebGpuResult;
use crate::buffer::GPUBuffer;
use crate::render_bundle::GPURenderBundle;
use crate::texture::GPUTextureView;
use crate::webidl::GPUColor;
use crate::Instance;
#[derive(Debug, thiserror::Error, deno_error::JsError)]
pub enum RenderPassError {
#[class(inherit)]
#[error(transparent)]
Resource(
#[from]
#[inherit]
ResourceError,
),
#[class(type)]
#[error("size must be larger than 0")]
InvalidSize,
pub struct GPURenderPassEncoder {
pub instance: Instance,
pub error_handler: super::error::ErrorHandler,
pub render_pass: RefCell<wgpu_core::command::RenderPass>,
pub label: String,
}
pub(crate) struct WebGpuRenderPass(
pub(crate) RefCell<wgpu_core::command::RenderPass>,
);
impl Resource for WebGpuRenderPass {
fn name(&self) -> Cow<str> {
"webGPURenderPass".into()
impl GarbageCollected for GPURenderPassEncoder {}
#[op2]
impl GPURenderPassEncoder {
#[getter]
#[string]
fn label(&self) -> String {
self.label.clone()
}
#[setter]
#[string]
fn label(&self, #[webidl] _label: String) {
// TODO(@crowlKats): no-op, needs wpgu to implement changing the label
}
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RenderPassSetViewportArgs {
render_pass_rid: ResourceId,
x: f32,
y: f32,
width: f32,
height: f32,
min_depth: f32,
max_depth: f32,
}
#[op2]
#[serde]
pub fn op_webgpu_render_pass_set_viewport(
state: &mut OpState,
#[serde] args: RenderPassSetViewportArgs,
) -> Result<WebGpuResult, ResourceError> {
let render_pass_resource = state
.resource_table
.get::<WebGpuRenderPass>(args.render_pass_rid)?;
wgpu_core::command::render_commands::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(WebGpuResult::empty())
}
#[op2]
#[serde]
pub fn op_webgpu_render_pass_set_scissor_rect(
state: &mut OpState,
#[smi] render_pass_rid: ResourceId,
x: u32,
y: u32,
width: u32,
height: u32,
) -> Result<WebGpuResult, ResourceError> {
let render_pass_resource = state
.resource_table
.get::<WebGpuRenderPass>(render_pass_rid)?;
wgpu_core::command::render_commands::wgpu_render_pass_set_scissor_rect(
&mut render_pass_resource.0.borrow_mut(),
#[required(6)]
fn set_viewport(
&self,
#[webidl] x: f32,
#[webidl] y: f32,
#[webidl] width: f32,
#[webidl] height: f32,
#[webidl] min_depth: f32,
#[webidl] max_depth: f32,
) {
let err = self
.instance
.render_pass_set_viewport(
&mut self.render_pass.borrow_mut(),
x,
y,
width,
height,
);
min_depth,
max_depth,
)
.err();
self.error_handler.push_error(err);
}
Ok(WebGpuResult::empty())
}
#[required(4)]
fn set_scissor_rect(
&self,
#[webidl(options(enforce_range = true))] x: u32,
#[webidl(options(enforce_range = true))] y: u32,
#[webidl(options(enforce_range = true))] width: u32,
#[webidl(options(enforce_range = true))] height: u32,
) {
let err = self
.instance
.render_pass_set_scissor_rect(
&mut self.render_pass.borrow_mut(),
x,
y,
width,
height,
)
.err();
self.error_handler.push_error(err);
}
#[op2]
#[serde]
pub fn op_webgpu_render_pass_set_blend_constant(
state: &mut OpState,
#[smi] render_pass_rid: ResourceId,
#[serde] color: wgpu_types::Color,
) -> Result<WebGpuResult, ResourceError> {
let render_pass_resource = state
.resource_table
.get::<WebGpuRenderPass>(render_pass_rid)?;
#[required(1)]
fn set_blend_constant(&self, #[webidl] color: GPUColor) {
let err = self
.instance
.render_pass_set_blend_constant(
&mut self.render_pass.borrow_mut(),
color.into(),
)
.err();
self.error_handler.push_error(err);
}
wgpu_core::command::render_commands::wgpu_render_pass_set_blend_constant(
&mut render_pass_resource.0.borrow_mut(),
&color,
);
Ok(WebGpuResult::empty())
}
#[op2]
#[serde]
pub fn op_webgpu_render_pass_set_stencil_reference(
state: &mut OpState,
#[smi] render_pass_rid: ResourceId,
reference: u32,
) -> Result<WebGpuResult, ResourceError> {
let render_pass_resource = state
.resource_table
.get::<WebGpuRenderPass>(render_pass_rid)?;
wgpu_core::command::render_commands::wgpu_render_pass_set_stencil_reference(
&mut render_pass_resource.0.borrow_mut(),
#[required(1)]
fn set_stencil_reference(
&self,
#[webidl(options(enforce_range = true))] reference: u32,
) {
let err = self
.instance
.render_pass_set_stencil_reference(
&mut self.render_pass.borrow_mut(),
reference,
);
)
.err();
self.error_handler.push_error(err);
}
Ok(WebGpuResult::empty())
}
#[op2]
#[serde]
pub fn op_webgpu_render_pass_begin_occlusion_query(
state: &mut OpState,
#[smi] render_pass_rid: ResourceId,
query_index: u32,
) -> Result<WebGpuResult, ResourceError> {
let render_pass_resource = state
.resource_table
.get::<WebGpuRenderPass>(render_pass_rid)?;
wgpu_core::command::render_commands::wgpu_render_pass_begin_occlusion_query(
&mut render_pass_resource.0.borrow_mut(),
#[required(1)]
fn begin_occlusion_query(
&self,
#[webidl(options(enforce_range = true))] query_index: u32,
) {
let err = self
.instance
.render_pass_begin_occlusion_query(
&mut self.render_pass.borrow_mut(),
query_index,
);
)
.err();
self.error_handler.push_error(err);
}
Ok(WebGpuResult::empty())
}
#[fast]
fn end_occlusion_query(&self) {
let err = self
.instance
.render_pass_end_occlusion_query(&mut self.render_pass.borrow_mut())
.err();
self.error_handler.push_error(err);
}
#[op2]
#[serde]
pub fn op_webgpu_render_pass_end_occlusion_query(
state: &mut OpState,
#[smi] render_pass_rid: ResourceId,
) -> Result<WebGpuResult, ResourceError> {
let render_pass_resource = state
.resource_table
.get::<WebGpuRenderPass>(render_pass_rid)?;
#[required(1)]
fn execute_bundles(&self, #[webidl] bundles: Vec<Ptr<GPURenderBundle>>) {
let err = self
.instance
.render_pass_execute_bundles(
&mut self.render_pass.borrow_mut(),
&bundles
.into_iter()
.map(|bundle| bundle.id)
.collect::<Vec<_>>(),
)
.err();
self.error_handler.push_error(err);
}
wgpu_core::command::render_commands::wgpu_render_pass_end_occlusion_query(
&mut render_pass_resource.0.borrow_mut(),
);
#[fast]
fn end(&self) {
let err = self
.instance
.render_pass_end(&mut self.render_pass.borrow_mut())
.err();
self.error_handler.push_error(err);
}
Ok(WebGpuResult::empty())
}
fn push_debug_group(&self, #[webidl] group_label: String) {
let err = self
.instance
.render_pass_push_debug_group(
&mut self.render_pass.borrow_mut(),
&group_label,
0, // wgpu#975
)
.err();
self.error_handler.push_error(err);
}
#[op2]
#[serde]
pub fn op_webgpu_render_pass_execute_bundles(
state: &mut OpState,
#[smi] render_pass_rid: ResourceId,
#[serde] bundles: Vec<u32>,
) -> Result<WebGpuResult, ResourceError> {
let bundles = bundles
.iter()
.map(|rid| {
let render_bundle_resource =
state
.resource_table
.get::<super::bundle::WebGpuRenderBundle>(*rid)?;
Ok(render_bundle_resource.1)
})
.collect::<Result<Vec<_>, ResourceError>>()?;
#[fast]
fn pop_debug_group(&self) {
let err = self
.instance
.render_pass_pop_debug_group(&mut self.render_pass.borrow_mut())
.err();
self.error_handler.push_error(err);
}
let render_pass_resource = state
.resource_table
.get::<WebGpuRenderPass>(render_pass_rid)?;
fn insert_debug_marker(&self, #[webidl] marker_label: String) {
let err = self
.instance
.render_pass_insert_debug_marker(
&mut self.render_pass.borrow_mut(),
&marker_label,
0, // wgpu#975
)
.err();
self.error_handler.push_error(err);
}
wgpu_core::command::render_commands::wgpu_render_pass_execute_bundles(
&mut render_pass_resource.0.borrow_mut(),
&bundles,
);
fn set_bind_group<'a>(
&self,
scope: &mut v8::HandleScope<'a>,
#[webidl(options(enforce_range = true))] index: u32,
#[webidl] bind_group: Nullable<Ptr<crate::bind_group::GPUBindGroup>>,
dynamic_offsets: v8::Local<'a, v8::Value>,
dynamic_offsets_data_start: v8::Local<'a, v8::Value>,
dynamic_offsets_data_length: v8::Local<'a, v8::Value>,
) -> Result<(), WebIdlError> {
const PREFIX: &str =
"Failed to execute 'setBindGroup' on 'GPUComputePassEncoder'";
Ok(WebGpuResult::empty())
}
let err = if let Ok(uint_32) = dynamic_offsets.try_cast::<v8::Uint32Array>()
{
let start = u64::convert(
scope,
dynamic_offsets_data_start,
Cow::Borrowed(PREFIX),
(|| Cow::Borrowed("Argument 4")).into(),
&IntOptions {
clamp: false,
enforce_range: true,
},
)? as usize;
let len = u32::convert(
scope,
dynamic_offsets_data_length,
Cow::Borrowed(PREFIX),
(|| Cow::Borrowed("Argument 5")).into(),
&IntOptions {
clamp: false,
enforce_range: true,
},
)? as usize;
#[op2]
#[serde]
pub fn op_webgpu_render_pass_end(
state: &mut OpState,
#[smi] command_encoder_rid: ResourceId,
#[smi] render_pass_rid: ResourceId,
) -> Result<WebGpuResult, ResourceError> {
let command_encoder_resource = state
.resource_table
.get::<super::command_encoder::WebGpuCommandEncoder>(
command_encoder_rid,
)?;
let command_encoder = command_encoder_resource.1;
let render_pass_resource = state
.resource_table
.take::<WebGpuRenderPass>(render_pass_rid)?;
let render_pass = &render_pass_resource.0.borrow();
let instance = state.borrow::<super::Instance>();
let ab = uint_32.buffer(scope).unwrap();
let ptr = ab.data().unwrap();
let ab_len = ab.byte_length() / 4;
gfx_ok!(command_encoder => instance.command_encoder_run_render_pass(command_encoder, render_pass))
}
let data =
unsafe { std::slice::from_raw_parts(ptr.as_ptr() as _, ab_len) };
#[op2]
#[serde]
pub fn op_webgpu_render_pass_set_bind_group(
state: &mut OpState,
#[smi] render_pass_rid: ResourceId,
index: u32,
bind_group: u32,
#[buffer] dynamic_offsets_data: &[u32],
#[number] dynamic_offsets_data_start: usize,
#[number] dynamic_offsets_data_length: usize,
) -> Result<WebGpuResult, ResourceError> {
let bind_group_resource =
state
.resource_table
.get::<super::binding::WebGpuBindGroup>(bind_group)?;
let render_pass_resource = state
.resource_table
.get::<WebGpuRenderPass>(render_pass_rid)?;
let offsets = &data[start..(start + len)];
let start = dynamic_offsets_data_start;
let len = dynamic_offsets_data_length;
// Assert that length and start are both in bounds
assert!(start <= dynamic_offsets_data.len());
assert!(len <= dynamic_offsets_data.len() - start);
let dynamic_offsets_data: &[u32] = &dynamic_offsets_data[start..start + len];
wgpu_core::command::render_commands::wgpu_render_pass_set_bind_group(
&mut render_pass_resource.0.borrow_mut(),
self
.instance
.render_pass_set_bind_group(
&mut self.render_pass.borrow_mut(),
index,
bind_group_resource.1,
dynamic_offsets_data,
);
Ok(WebGpuResult::empty())
}
#[op2]
#[serde]
pub fn op_webgpu_render_pass_push_debug_group(
state: &mut OpState,
#[smi] render_pass_rid: ResourceId,
#[string] group_label: &str,
) -> Result<WebGpuResult, ResourceError> {
let render_pass_resource = state
.resource_table
.get::<WebGpuRenderPass>(render_pass_rid)?;
wgpu_core::command::render_commands::wgpu_render_pass_push_debug_group(
&mut render_pass_resource.0.borrow_mut(),
group_label,
0, // wgpu#975
);
Ok(WebGpuResult::empty())
}
#[op2]
#[serde]
pub fn op_webgpu_render_pass_pop_debug_group(
state: &mut OpState,
#[smi] render_pass_rid: ResourceId,
) -> Result<WebGpuResult, ResourceError> {
let render_pass_resource = state
.resource_table
.get::<WebGpuRenderPass>(render_pass_rid)?;
wgpu_core::command::render_commands::wgpu_render_pass_pop_debug_group(
&mut render_pass_resource.0.borrow_mut(),
);
Ok(WebGpuResult::empty())
}
#[op2]
#[serde]
pub fn op_webgpu_render_pass_insert_debug_marker(
state: &mut OpState,
#[smi] render_pass_rid: ResourceId,
#[string] marker_label: &str,
) -> Result<WebGpuResult, ResourceError> {
let render_pass_resource = state
.resource_table
.get::<WebGpuRenderPass>(render_pass_rid)?;
wgpu_core::command::render_commands::wgpu_render_pass_insert_debug_marker(
&mut render_pass_resource.0.borrow_mut(),
marker_label,
0, // wgpu#975
);
Ok(WebGpuResult::empty())
}
#[op2]
#[serde]
pub fn op_webgpu_render_pass_set_pipeline(
state: &mut OpState,
#[smi] render_pass_rid: ResourceId,
pipeline: u32,
) -> Result<WebGpuResult, ResourceError> {
let render_pipeline_resource =
state
.resource_table
.get::<super::pipeline::WebGpuRenderPipeline>(pipeline)?;
let render_pass_resource = state
.resource_table
.get::<WebGpuRenderPass>(render_pass_rid)?;
wgpu_core::command::render_commands::wgpu_render_pass_set_pipeline(
&mut render_pass_resource.0.borrow_mut(),
render_pipeline_resource.1,
);
Ok(WebGpuResult::empty())
}
#[op2]
#[serde]
pub fn op_webgpu_render_pass_set_index_buffer(
state: &mut OpState,
#[smi] render_pass_rid: ResourceId,
buffer: u32,
#[serde] index_format: wgpu_types::IndexFormat,
#[number] offset: u64,
#[number] size: Option<u64>,
) -> Result<WebGpuResult, RenderPassError> {
let buffer_resource = state
.resource_table
.get::<super::buffer::WebGpuBuffer>(buffer)?;
let render_pass_resource = state
.resource_table
.get::<WebGpuRenderPass>(render_pass_rid)?;
let size = if let Some(size) = size {
Some(std::num::NonZeroU64::new(size).ok_or(RenderPassError::InvalidSize)?)
bind_group.into_option().map(|bind_group| bind_group.id),
offsets,
)
.err()
} else {
None
let offsets = <Option<Vec<u32>>>::convert(
scope,
dynamic_offsets,
Cow::Borrowed(PREFIX),
(|| Cow::Borrowed("Argument 3")).into(),
&IntOptions {
clamp: false,
enforce_range: true,
},
)?
.unwrap_or_default();
self
.instance
.render_pass_set_bind_group(
&mut self.render_pass.borrow_mut(),
index,
bind_group.into_option().map(|bind_group| bind_group.id),
&offsets,
)
.err()
};
render_pass_resource.0.borrow_mut().set_index_buffer(
buffer_resource.1,
index_format,
self.error_handler.push_error(err);
Ok(())
}
fn set_pipeline(
&self,
#[webidl] pipeline: Ptr<crate::render_pipeline::GPURenderPipeline>,
) {
let err = self
.instance
.render_pass_set_pipeline(&mut self.render_pass.borrow_mut(), pipeline.id)
.err();
self.error_handler.push_error(err);
}
#[required(2)]
fn set_index_buffer(
&self,
#[webidl] buffer: Ptr<GPUBuffer>,
#[webidl] index_format: crate::render_pipeline::GPUIndexFormat,
#[webidl(default = 0, options(enforce_range = true))] offset: u64,
#[webidl(options(enforce_range = true))] size: Option<u64>,
) {
let err = self
.instance
.render_pass_set_index_buffer(
&mut self.render_pass.borrow_mut(),
buffer.id,
index_format.into(),
offset,
size,
);
size.and_then(NonZeroU64::new),
)
.err();
self.error_handler.push_error(err);
}
Ok(WebGpuResult::empty())
}
#[op2]
#[serde]
pub fn op_webgpu_render_pass_set_vertex_buffer(
state: &mut OpState,
#[smi] render_pass_rid: ResourceId,
slot: u32,
buffer: u32,
#[number] offset: u64,
#[number] size: Option<u64>,
) -> Result<WebGpuResult, RenderPassError> {
let buffer_resource = state
.resource_table
.get::<super::buffer::WebGpuBuffer>(buffer)?;
let render_pass_resource = state
.resource_table
.get::<WebGpuRenderPass>(render_pass_rid)?;
let size = if let Some(size) = size {
Some(std::num::NonZeroU64::new(size).ok_or(RenderPassError::InvalidSize)?)
} else {
None
};
wgpu_core::command::render_commands::wgpu_render_pass_set_vertex_buffer(
&mut render_pass_resource.0.borrow_mut(),
#[required(2)]
fn set_vertex_buffer(
&self,
#[webidl(options(enforce_range = true))] slot: u32,
#[webidl] buffer: Ptr<GPUBuffer>, // TODO: support nullable buffer
#[webidl(default = 0, options(enforce_range = true))] offset: u64,
#[webidl(options(enforce_range = true))] size: Option<u64>,
) {
let err = self
.instance
.render_pass_set_vertex_buffer(
&mut self.render_pass.borrow_mut(),
slot,
buffer_resource.1,
buffer.id,
offset,
size,
);
size.and_then(NonZeroU64::new),
)
.err();
self.error_handler.push_error(err);
}
Ok(WebGpuResult::empty())
}
#[op2]
#[serde]
pub fn op_webgpu_render_pass_draw(
state: &mut OpState,
#[smi] render_pass_rid: ResourceId,
vertex_count: u32,
instance_count: u32,
first_vertex: u32,
first_instance: u32,
) -> Result<WebGpuResult, ResourceError> {
let render_pass_resource = state
.resource_table
.get::<WebGpuRenderPass>(render_pass_rid)?;
wgpu_core::command::render_commands::wgpu_render_pass_draw(
&mut render_pass_resource.0.borrow_mut(),
#[required(1)]
fn draw(
&self,
#[webidl(options(enforce_range = true))] vertex_count: u32,
#[webidl(default = 1, options(enforce_range = true))] instance_count: u32,
#[webidl(default = 0, options(enforce_range = true))] first_vertex: u32,
#[webidl(default = 0, options(enforce_range = true))] first_instance: u32,
) {
let err = self
.instance
.render_pass_draw(
&mut self.render_pass.borrow_mut(),
vertex_count,
instance_count,
first_vertex,
first_instance,
);
)
.err();
self.error_handler.push_error(err);
}
Ok(WebGpuResult::empty())
}
#[op2]
#[serde]
pub fn op_webgpu_render_pass_draw_indexed(
state: &mut OpState,
#[smi] render_pass_rid: ResourceId,
index_count: u32,
instance_count: u32,
first_index: u32,
base_vertex: i32,
first_instance: u32,
) -> Result<WebGpuResult, ResourceError> {
let render_pass_resource = state
.resource_table
.get::<WebGpuRenderPass>(render_pass_rid)?;
wgpu_core::command::render_commands::wgpu_render_pass_draw_indexed(
&mut render_pass_resource.0.borrow_mut(),
#[required(1)]
fn draw_indexed(
&self,
#[webidl(options(enforce_range = true))] index_count: u32,
#[webidl(default = 1, options(enforce_range = true))] instance_count: u32,
#[webidl(default = 0, options(enforce_range = true))] first_index: u32,
#[webidl(default = 0, options(enforce_range = true))] base_vertex: i32,
#[webidl(default = 0, options(enforce_range = true))] first_instance: u32,
) {
let err = self
.instance
.render_pass_draw_indexed(
&mut self.render_pass.borrow_mut(),
index_count,
instance_count,
first_index,
base_vertex,
first_instance,
);
)
.err();
self.error_handler.push_error(err);
}
Ok(WebGpuResult::empty())
}
#[op2]
#[serde]
pub fn op_webgpu_render_pass_draw_indirect(
state: &mut OpState,
#[smi] render_pass_rid: ResourceId,
indirect_buffer: u32,
#[number] indirect_offset: u64,
) -> Result<WebGpuResult, ResourceError> {
let buffer_resource = state
.resource_table
.get::<super::buffer::WebGpuBuffer>(indirect_buffer)?;
let render_pass_resource = state
.resource_table
.get::<WebGpuRenderPass>(render_pass_rid)?;
wgpu_core::command::render_commands::wgpu_render_pass_draw_indirect(
&mut render_pass_resource.0.borrow_mut(),
buffer_resource.1,
#[required(2)]
fn draw_indirect(
&self,
#[webidl] indirect_buffer: Ptr<GPUBuffer>,
#[webidl(options(enforce_range = true))] indirect_offset: u64,
) {
let err = self
.instance
.render_pass_draw_indirect(
&mut self.render_pass.borrow_mut(),
indirect_buffer.id,
indirect_offset,
);
)
.err();
self.error_handler.push_error(err);
}
Ok(WebGpuResult::empty())
}
#[op2]
#[serde]
pub fn op_webgpu_render_pass_draw_indexed_indirect(
state: &mut OpState,
#[smi] render_pass_rid: ResourceId,
indirect_buffer: u32,
#[number] indirect_offset: u64,
) -> Result<WebGpuResult, ResourceError> {
let buffer_resource = state
.resource_table
.get::<super::buffer::WebGpuBuffer>(indirect_buffer)?;
let render_pass_resource = state
.resource_table
.get::<WebGpuRenderPass>(render_pass_rid)?;
wgpu_core::command::render_commands::wgpu_render_pass_draw_indexed_indirect(
&mut render_pass_resource.0.borrow_mut(),
buffer_resource.1,
#[required(2)]
fn draw_indexed_indirect(
&self,
#[webidl] indirect_buffer: Ptr<GPUBuffer>,
#[webidl(options(enforce_range = true))] indirect_offset: u64,
) {
let err = self
.instance
.render_pass_draw_indexed_indirect(
&mut self.render_pass.borrow_mut(),
indirect_buffer.id,
indirect_offset,
);
Ok(WebGpuResult::empty())
)
.err();
self.error_handler.push_error(err);
}
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPURenderPassDescriptor {
#[webidl(default = String::new())]
pub label: String,
pub color_attachments: Vec<Nullable<GPURenderPassColorAttachment>>,
pub depth_stencil_attachment: Option<GPURenderPassDepthStencilAttachment>,
pub occlusion_query_set: Option<Ptr<crate::query_set::GPUQuerySet>>,
pub timestamp_writes: Option<GPURenderPassTimestampWrites>,
#[webidl(default = 50000000)]
#[options(enforce_range = true)]
pub max_draw_count: u64,
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPURenderPassColorAttachment {
pub view: Ptr<GPUTextureView>,
#[options(enforce_range = true)]
pub depth_slice: Option<u32>,
pub resolve_target: Option<Ptr<GPUTextureView>>,
pub clear_value: Option<GPUColor>,
pub load_op: GPULoadOp,
pub store_op: GPUStoreOp,
}
#[derive(WebIDL)]
#[webidl(enum)]
pub(crate) enum GPULoadOp {
Load,
Clear,
}
impl From<GPULoadOp> for wgpu_core::command::LoadOp {
fn from(value: GPULoadOp) -> Self {
match value {
GPULoadOp::Load => Self::Load,
GPULoadOp::Clear => Self::Clear,
}
}
}
#[derive(WebIDL)]
#[webidl(enum)]
pub(crate) enum GPUStoreOp {
Store,
Discard,
}
impl From<GPUStoreOp> for wgpu_core::command::StoreOp {
fn from(value: GPUStoreOp) -> Self {
match value {
GPUStoreOp::Store => Self::Store,
GPUStoreOp::Discard => Self::Discard,
}
}
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPURenderPassDepthStencilAttachment {
pub view: Ptr<GPUTextureView>,
pub depth_clear_value: Option<f32>,
pub depth_load_op: Option<GPULoadOp>,
pub depth_store_op: Option<GPUStoreOp>,
#[webidl(default = false)]
pub depth_read_only: bool,
#[webidl(default = 0)]
#[options(enforce_range = true)]
pub stencil_clear_value: u32,
pub stencil_load_op: Option<GPULoadOp>,
pub stencil_store_op: Option<GPUStoreOp>,
#[webidl(default = false)]
pub stencil_read_only: bool,
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPURenderPassTimestampWrites {
pub query_set: Ptr<crate::query_set::GPUQuerySet>,
#[options(enforce_range = true)]
pub beginning_of_pass_write_index: Option<u32>,
#[options(enforce_range = true)]
pub end_of_pass_write_index: Option<u32>,
}

View file

@ -0,0 +1,550 @@
// Copyright 2018-2025 the Deno authors. MIT license.
use deno_core::cppgc::Ptr;
use deno_core::op2;
use deno_core::webidl::Nullable;
use deno_core::webidl::WebIdlInterfaceConverter;
use deno_core::GarbageCollected;
use deno_core::WebIDL;
use indexmap::IndexMap;
use crate::bind_group_layout::GPUBindGroupLayout;
use crate::sampler::GPUCompareFunction;
use crate::shader::GPUShaderModule;
use crate::texture::GPUTextureFormat;
use crate::webidl::GPUPipelineLayoutOrGPUAutoLayoutMode;
use crate::Instance;
pub struct GPURenderPipeline {
pub instance: Instance,
pub error_handler: super::error::ErrorHandler,
pub id: wgpu_core::id::RenderPipelineId,
pub label: String,
}
impl Drop for GPURenderPipeline {
fn drop(&mut self) {
self.instance.render_pipeline_drop(self.id);
}
}
impl WebIdlInterfaceConverter for GPURenderPipeline {
const NAME: &'static str = "GPURenderPipeline";
}
impl GarbageCollected for GPURenderPipeline {}
#[op2]
impl GPURenderPipeline {
#[getter]
#[string]
fn label(&self) -> String {
self.label.clone()
}
#[setter]
#[string]
fn label(&self, #[webidl] _label: String) {
// TODO(@crowlKats): no-op, needs wpgu to implement changing the label
}
#[cppgc]
fn get_bind_group_layout(&self, #[webidl] index: u32) -> GPUBindGroupLayout {
let (id, err) = self
.instance
.render_pipeline_get_bind_group_layout(self.id, index, None);
self.error_handler.push_error(err);
// TODO: wgpu needs to add a way to retrieve the label
GPUBindGroupLayout {
instance: self.instance.clone(),
id,
label: "".to_string(),
}
}
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPURenderPipelineDescriptor {
#[webidl(default = String::new())]
pub label: String,
pub layout: GPUPipelineLayoutOrGPUAutoLayoutMode,
pub vertex: GPUVertexState,
pub primitive: GPUPrimitiveState, // TODO: default {}
pub depth_stencil: Option<GPUDepthStencilState>,
pub multisample: GPUMultisampleState, // TODO: default {}
pub fragment: Option<GPUFragmentState>,
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPUMultisampleState {
#[webidl(default = 1)]
#[options(enforce_range = true)]
pub count: u32,
#[webidl(default = 0xFFFFFFFF)]
#[options(enforce_range = true)]
pub mask: u32,
#[webidl(default = false)]
pub alpha_to_coverage_enabled: bool,
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPUDepthStencilState {
pub format: GPUTextureFormat,
pub depth_write_enabled: Option<bool>,
pub depth_compare: Option<GPUCompareFunction>,
pub stencil_front: GPUStencilFaceState, // TODO: default {}
pub stencil_back: GPUStencilFaceState, // TODO: default {}
#[webidl(default = 0xFFFFFFFF)]
#[options(enforce_range = true)]
pub stencil_read_mask: u32,
#[webidl(default = 0xFFFFFFFF)]
#[options(enforce_range = true)]
pub stencil_write_mask: u32,
#[webidl(default = 0)]
#[options(enforce_range = true)]
pub depth_bias: i32,
#[webidl(default = 0.0)]
pub depth_bias_slope_scale: f32,
#[webidl(default = 0.0)]
pub depth_bias_clamp: f32,
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPUStencilFaceState {
#[webidl(default = GPUCompareFunction::Always)]
pub compare: GPUCompareFunction,
#[webidl(default = GPUStencilOperation::Keep)]
pub fail_op: GPUStencilOperation,
#[webidl(default = GPUStencilOperation::Keep)]
pub depth_fail_op: GPUStencilOperation,
#[webidl(default = GPUStencilOperation::Keep)]
pub pass_op: GPUStencilOperation,
}
#[derive(WebIDL)]
#[webidl(enum)]
pub(crate) enum GPUStencilOperation {
Keep,
Zero,
Replace,
Invert,
IncrementClamp,
DecrementClamp,
IncrementWrap,
DecrementWrap,
}
impl From<GPUStencilOperation> for wgpu_types::StencilOperation {
fn from(value: GPUStencilOperation) -> Self {
match value {
GPUStencilOperation::Keep => Self::Keep,
GPUStencilOperation::Zero => Self::Zero,
GPUStencilOperation::Replace => Self::Replace,
GPUStencilOperation::Invert => Self::Invert,
GPUStencilOperation::IncrementClamp => Self::IncrementClamp,
GPUStencilOperation::DecrementClamp => Self::DecrementClamp,
GPUStencilOperation::IncrementWrap => Self::IncrementWrap,
GPUStencilOperation::DecrementWrap => Self::DecrementWrap,
}
}
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPUVertexState {
pub module: Ptr<GPUShaderModule>,
pub entry_point: Option<String>,
#[webidl(default = Default::default())]
pub constants: IndexMap<String, f64>,
#[webidl(default = vec![])]
pub buffers: Vec<Nullable<GPUVertexBufferLayout>>,
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPUFragmentState {
pub module: Ptr<GPUShaderModule>,
pub entry_point: Option<String>,
#[webidl(default = Default::default())]
pub constants: IndexMap<String, f64>,
pub targets: Vec<Nullable<GPUColorTargetState>>,
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPUColorTargetState {
pub format: GPUTextureFormat,
pub blend: Option<GPUBlendState>,
#[webidl(default = 0xF)]
#[options(enforce_range = true)]
pub write_mask: u32,
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPUBlendState {
pub color: GPUBlendComponent,
pub alpha: GPUBlendComponent,
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPUBlendComponent {
#[webidl(default = GPUBlendOperation::Add)]
pub operation: GPUBlendOperation,
#[webidl(default = GPUBlendFactor::One)]
pub src_factor: GPUBlendFactor,
#[webidl(default = GPUBlendFactor::Zero)]
pub dst_factor: GPUBlendFactor,
}
#[derive(WebIDL)]
#[webidl(enum)]
pub(crate) enum GPUBlendOperation {
Add,
Subtract,
ReverseSubtract,
Min,
Max,
}
impl From<GPUBlendOperation> for wgpu_types::BlendOperation {
fn from(value: GPUBlendOperation) -> Self {
match value {
GPUBlendOperation::Add => Self::Add,
GPUBlendOperation::Subtract => Self::Subtract,
GPUBlendOperation::ReverseSubtract => Self::ReverseSubtract,
GPUBlendOperation::Min => Self::Min,
GPUBlendOperation::Max => Self::Max,
}
}
}
#[derive(WebIDL)]
#[webidl(enum)]
pub(crate) enum GPUBlendFactor {
#[webidl(rename = "zero")]
Zero,
#[webidl(rename = "one")]
One,
#[webidl(rename = "src")]
Src,
#[webidl(rename = "one-minus-src")]
OneMinusSrc,
#[webidl(rename = "src-alpha")]
SrcAlpha,
#[webidl(rename = "one-minus-src-alpha")]
OneMinusSrcAlpha,
#[webidl(rename = "dst")]
Dst,
#[webidl(rename = "one-minus-dst")]
OneMinusDst,
#[webidl(rename = "dst-alpha")]
DstAlpha,
#[webidl(rename = "one-minus-dst-alpha")]
OneMinusDstAlpha,
#[webidl(rename = "src-alpha-saturated")]
SrcAlphaSaturated,
#[webidl(rename = "constant")]
Constant,
#[webidl(rename = "one-minus-constant")]
OneMinusConstant,
#[webidl(rename = "src1")]
Src1,
#[webidl(rename = "one-minus-src1")]
OneMinusSrc1,
#[webidl(rename = "src1-alpha")]
Src1Alpha,
#[webidl(rename = "one-minus-src1-alpha")]
OneMinusSrc1Alpha,
}
impl From<GPUBlendFactor> for wgpu_types::BlendFactor {
fn from(value: GPUBlendFactor) -> Self {
match value {
GPUBlendFactor::Zero => Self::Zero,
GPUBlendFactor::One => Self::One,
GPUBlendFactor::Src => Self::Src,
GPUBlendFactor::OneMinusSrc => Self::OneMinusSrc,
GPUBlendFactor::SrcAlpha => Self::SrcAlpha,
GPUBlendFactor::OneMinusSrcAlpha => Self::OneMinusSrcAlpha,
GPUBlendFactor::Dst => Self::Dst,
GPUBlendFactor::OneMinusDst => Self::OneMinusDst,
GPUBlendFactor::DstAlpha => Self::DstAlpha,
GPUBlendFactor::OneMinusDstAlpha => Self::OneMinusDstAlpha,
GPUBlendFactor::SrcAlphaSaturated => Self::SrcAlphaSaturated,
GPUBlendFactor::Constant => Self::Constant,
GPUBlendFactor::OneMinusConstant => Self::OneMinusConstant,
GPUBlendFactor::Src1 => Self::Src1,
GPUBlendFactor::OneMinusSrc1 => Self::OneMinusSrc1,
GPUBlendFactor::Src1Alpha => Self::Src1Alpha,
GPUBlendFactor::OneMinusSrc1Alpha => Self::OneMinusSrc1Alpha,
}
}
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPUPrimitiveState {
#[webidl(default = GPUPrimitiveTopology::TriangleList)]
pub topology: GPUPrimitiveTopology,
pub strip_index_format: Option<GPUIndexFormat>,
#[webidl(default = GPUFrontFace::Ccw)]
pub front_face: GPUFrontFace,
#[webidl(default = GPUCullMode::None)]
pub cull_mode: GPUCullMode,
#[webidl(default = false)]
pub unclipped_depth: bool,
}
#[derive(WebIDL)]
#[webidl(enum)]
pub(crate) enum GPUPrimitiveTopology {
PointList,
LineList,
LineStrip,
TriangleList,
TriangleStrip,
}
impl From<GPUPrimitiveTopology> for wgpu_types::PrimitiveTopology {
fn from(value: GPUPrimitiveTopology) -> Self {
match value {
GPUPrimitiveTopology::PointList => Self::PointList,
GPUPrimitiveTopology::LineList => Self::LineList,
GPUPrimitiveTopology::LineStrip => Self::LineStrip,
GPUPrimitiveTopology::TriangleList => Self::TriangleList,
GPUPrimitiveTopology::TriangleStrip => Self::TriangleStrip,
}
}
}
#[derive(WebIDL)]
#[webidl(enum)]
pub(crate) enum GPUIndexFormat {
#[webidl(rename = "uint16")]
Uint16,
#[webidl(rename = "uint32")]
Uint32,
}
impl From<GPUIndexFormat> for wgpu_types::IndexFormat {
fn from(value: GPUIndexFormat) -> Self {
match value {
GPUIndexFormat::Uint16 => Self::Uint16,
GPUIndexFormat::Uint32 => Self::Uint32,
}
}
}
#[derive(WebIDL)]
#[webidl(enum)]
pub(crate) enum GPUFrontFace {
Ccw,
Cw,
}
impl From<GPUFrontFace> for wgpu_types::FrontFace {
fn from(value: GPUFrontFace) -> Self {
match value {
GPUFrontFace::Ccw => Self::Ccw,
GPUFrontFace::Cw => Self::Cw,
}
}
}
#[derive(WebIDL)]
#[webidl(enum)]
pub(crate) enum GPUCullMode {
None,
Front,
Back,
}
impl From<GPUCullMode> for Option<wgpu_types::Face> {
fn from(value: GPUCullMode) -> Self {
match value {
GPUCullMode::None => None,
GPUCullMode::Front => Some(wgpu_types::Face::Front),
GPUCullMode::Back => Some(wgpu_types::Face::Back),
}
}
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPUVertexBufferLayout {
#[options(enforce_range = true)]
pub array_stride: u64,
#[webidl(default = GPUVertexStepMode::Vertex)]
pub step_mode: GPUVertexStepMode,
pub attributes: Vec<GPUVertexAttribute>,
}
#[derive(WebIDL)]
#[webidl(enum)]
pub(crate) enum GPUVertexStepMode {
Vertex,
Instance,
}
impl From<GPUVertexStepMode> for wgpu_types::VertexStepMode {
fn from(value: GPUVertexStepMode) -> Self {
match value {
GPUVertexStepMode::Vertex => Self::Vertex,
GPUVertexStepMode::Instance => Self::Instance,
}
}
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPUVertexAttribute {
pub format: GPUVertexFormat,
#[options(enforce_range = true)]
pub offset: u64,
#[options(enforce_range = true)]
pub shader_location: u32,
}
#[derive(WebIDL)]
#[webidl(enum)]
pub(crate) enum GPUVertexFormat {
// #[webidl(rename = "uint8")]
// Uint8,
#[webidl(rename = "uint8x2")]
Uint8x2,
#[webidl(rename = "uint8x4")]
Uint8x4,
// #[webidl(rename = "sint8")]
// Sint8,
#[webidl(rename = "sint8x2")]
Sint8x2,
#[webidl(rename = "sint8x4")]
Sint8x4,
// #[webidl(rename = "unorm8")]
// Unorm8,
#[webidl(rename = "unorm8x2")]
Unorm8x2,
#[webidl(rename = "unorm8x4")]
Unorm8x4,
// #[webidl(rename = "snorm8")]
// Snorm8,
#[webidl(rename = "snorm8x2")]
Snorm8x2,
#[webidl(rename = "snorm8x4")]
Snorm8x4,
// #[webidl(rename = "uint16")]
// Uint16,
#[webidl(rename = "uint16x2")]
Uint16x2,
#[webidl(rename = "uint16x4")]
Uint16x4,
// #[webidl(rename = "sint16")]
// Sint16,
#[webidl(rename = "sint16x2")]
Sint16x2,
#[webidl(rename = "sint16x4")]
Sint16x4,
// #[webidl(rename = "unorm16")]
// Unorm16,
#[webidl(rename = "unorm16x2")]
Unorm16x2,
#[webidl(rename = "unorm16x4")]
Unorm16x4,
// #[webidl(rename = "snorm16")]
// Snorm16,
#[webidl(rename = "snorm16x2")]
Snorm16x2,
#[webidl(rename = "snorm16x4")]
Snorm16x4,
// #[webidl(rename = "float16")]
// Float16,
#[webidl(rename = "float16x2")]
Float16x2,
#[webidl(rename = "float16x4")]
Float16x4,
#[webidl(rename = "float32")]
Float32,
#[webidl(rename = "float32x2")]
Float32x2,
#[webidl(rename = "float32x3")]
Float32x3,
#[webidl(rename = "float32x4")]
Float32x4,
#[webidl(rename = "uint32")]
Uint32,
#[webidl(rename = "uint32x2")]
Uint32x2,
#[webidl(rename = "uint32x3")]
Uint32x3,
#[webidl(rename = "uint32x4")]
Uint32x4,
#[webidl(rename = "sint32")]
Sint32,
#[webidl(rename = "sint32x2")]
Sint32x2,
#[webidl(rename = "sint32x3")]
Sint32x3,
#[webidl(rename = "sint32x4")]
Sint32x4,
#[webidl(rename = "unorm10-10-10-2")]
Unorm1010102,
// #[webidl(rename = "unorm8x4-bgra")]
// Unorm8x4Bgra,
}
impl From<GPUVertexFormat> for wgpu_types::VertexFormat {
fn from(value: GPUVertexFormat) -> Self {
match value {
//GPUVertexFormat::Uint8 => Self::Uint8,
GPUVertexFormat::Uint8x2 => Self::Uint8x2,
GPUVertexFormat::Uint8x4 => Self::Uint8x4,
//GPUVertexFormat::Sint8 => Self::Sint8,
GPUVertexFormat::Sint8x2 => Self::Sint8x2,
GPUVertexFormat::Sint8x4 => Self::Sint8x4,
//GPUVertexFormat::Unorm8 => Self::Unorm8,
GPUVertexFormat::Unorm8x2 => Self::Unorm8x2,
GPUVertexFormat::Unorm8x4 => Self::Unorm8x4,
//GPUVertexFormat::Snorm8 => Self::Snorm8,
GPUVertexFormat::Snorm8x2 => Self::Snorm8x2,
GPUVertexFormat::Snorm8x4 => Self::Snorm8x4,
//GPUVertexFormat::Uint16 => Self::Uint16,
GPUVertexFormat::Uint16x2 => Self::Uint16x2,
GPUVertexFormat::Uint16x4 => Self::Uint16x4,
//GPUVertexFormat::Sint16 => Self::Sint16,
GPUVertexFormat::Sint16x2 => Self::Sint16x2,
GPUVertexFormat::Sint16x4 => Self::Sint16x4,
//GPUVertexFormat::Unorm16 => Self::Unorm16,
GPUVertexFormat::Unorm16x2 => Self::Unorm16x2,
GPUVertexFormat::Unorm16x4 => Self::Unorm16x4,
//GPUVertexFormat::Snorm16 => Self::Snorm16,
GPUVertexFormat::Snorm16x2 => Self::Snorm16x2,
GPUVertexFormat::Snorm16x4 => Self::Snorm16x4,
//GPUVertexFormat::Float16 => Self::Float16,
GPUVertexFormat::Float16x2 => Self::Float16x2,
GPUVertexFormat::Float16x4 => Self::Float16x4,
GPUVertexFormat::Float32 => Self::Float32,
GPUVertexFormat::Float32x2 => Self::Float32x2,
GPUVertexFormat::Float32x3 => Self::Float32x3,
GPUVertexFormat::Float32x4 => Self::Float32x4,
GPUVertexFormat::Uint32 => Self::Uint32,
GPUVertexFormat::Uint32x2 => Self::Uint32x2,
GPUVertexFormat::Uint32x3 => Self::Uint32x3,
GPUVertexFormat::Uint32x4 => Self::Uint32x4,
GPUVertexFormat::Sint32 => Self::Sint32,
GPUVertexFormat::Sint32x2 => Self::Sint32x2,
GPUVertexFormat::Sint32x3 => Self::Sint32x3,
GPUVertexFormat::Sint32x4 => Self::Sint32x4,
GPUVertexFormat::Unorm1010102 => Self::Unorm10_10_10_2,
//GPUVertexFormat::Unorm8x4Bgra => Self::Unorm8x4Bgra,
}
}
}

View file

@ -1,79 +1,134 @@
// Copyright 2018-2025 the Deno authors. MIT license.
use std::borrow::Cow;
use std::rc::Rc;
use deno_core::op2;
use deno_core::OpState;
use deno_core::Resource;
use deno_core::ResourceId;
use serde::Deserialize;
use deno_core::webidl::WebIdlInterfaceConverter;
use deno_core::GarbageCollected;
use deno_core::WebIDL;
use super::error::WebGpuResult;
use crate::Instance;
pub(crate) struct WebGpuSampler(
pub(crate) crate::Instance,
pub(crate) wgpu_core::id::SamplerId,
);
impl Resource for WebGpuSampler {
fn name(&self) -> Cow<str> {
"webGPUSampler".into()
}
pub struct GPUSampler {
pub instance: Instance,
pub id: wgpu_core::id::SamplerId,
pub label: String,
}
fn close(self: Rc<Self>) {
gfx_select!(self.1 => self.0.sampler_drop(self.1));
impl Drop for GPUSampler {
fn drop(&mut self) {
self.instance.sampler_drop(self.id);
}
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateSamplerArgs {
device_rid: ResourceId,
label: String,
address_mode_u: wgpu_types::AddressMode,
address_mode_v: wgpu_types::AddressMode,
address_mode_w: wgpu_types::AddressMode,
mag_filter: wgpu_types::FilterMode,
min_filter: wgpu_types::FilterMode,
mipmap_filter: wgpu_types::FilterMode, // TODO: GPUMipmapFilterMode
lod_min_clamp: f32,
lod_max_clamp: f32,
compare: Option<wgpu_types::CompareFunction>,
max_anisotropy: u16,
impl WebIdlInterfaceConverter for GPUSampler {
const NAME: &'static str = "GPUSampler";
}
impl GarbageCollected for GPUSampler {}
#[op2]
#[serde]
pub fn op_webgpu_create_sampler(
state: &mut OpState,
#[serde] args: CreateSamplerArgs,
) -> Result<WebGpuResult, deno_core::error::ResourceError> {
let instance = state.borrow::<super::Instance>();
let device_resource = state
.resource_table
.get::<super::WebGpuDevice>(args.device_rid)?;
let device = device_resource.1;
let descriptor = wgpu_core::resource::SamplerDescriptor {
label: Some(Cow::Owned(args.label)),
address_modes: [
args.address_mode_u,
args.address_mode_v,
args.address_mode_w,
],
mag_filter: args.mag_filter,
min_filter: args.min_filter,
mipmap_filter: args.mipmap_filter,
lod_min_clamp: args.lod_min_clamp,
lod_max_clamp: args.lod_max_clamp,
compare: args.compare,
anisotropy_clamp: args.max_anisotropy,
border_color: None, // native-only
};
gfx_put!(device => instance.device_create_sampler(
device,
&descriptor,
None
) => state, WebGpuSampler)
impl GPUSampler {
#[getter]
#[string]
fn label(&self) -> String {
self.label.clone()
}
#[setter]
#[string]
fn label(&self, #[webidl] _label: String) {
// TODO(@crowlKats): no-op, needs wpgu to implement changing the label
}
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(super) struct GPUSamplerDescriptor {
#[webidl(default = String::new())]
pub label: String,
#[webidl(default = GPUAddressMode::ClampToEdge)]
pub address_mode_u: GPUAddressMode,
#[webidl(default = GPUAddressMode::ClampToEdge)]
pub address_mode_v: GPUAddressMode,
#[webidl(default = GPUAddressMode::ClampToEdge)]
pub address_mode_w: GPUAddressMode,
#[webidl(default = GPUFilterMode::Nearest)]
pub mag_filter: GPUFilterMode,
#[webidl(default = GPUFilterMode::Nearest)]
pub min_filter: GPUFilterMode,
#[webidl(default = GPUFilterMode::Nearest)]
pub mipmap_filter: GPUFilterMode,
#[webidl(default = 0.0)]
pub lod_min_clamp: f32,
#[webidl(default = 32.0)]
pub lod_max_clamp: f32,
pub compare: Option<GPUCompareFunction>,
#[webidl(default = 1)]
#[options(clamp = true)]
pub max_anisotropy: u16,
}
#[derive(WebIDL)]
#[webidl(enum)]
pub(crate) enum GPUAddressMode {
ClampToEdge,
Repeat,
MirrorRepeat,
}
impl From<GPUAddressMode> for wgpu_types::AddressMode {
fn from(value: GPUAddressMode) -> Self {
match value {
GPUAddressMode::ClampToEdge => Self::ClampToEdge,
GPUAddressMode::Repeat => Self::Repeat,
GPUAddressMode::MirrorRepeat => Self::MirrorRepeat,
}
}
}
// Same as GPUMipmapFilterMode
#[derive(WebIDL)]
#[webidl(enum)]
pub(crate) enum GPUFilterMode {
Nearest,
Linear,
}
impl From<GPUFilterMode> for wgpu_types::FilterMode {
fn from(value: GPUFilterMode) -> Self {
match value {
GPUFilterMode::Nearest => Self::Nearest,
GPUFilterMode::Linear => Self::Linear,
}
}
}
#[derive(WebIDL)]
#[webidl(enum)]
pub(crate) enum GPUCompareFunction {
Never,
Less,
Equal,
LessEqual,
Greater,
NotEqual,
GreaterEqual,
Always,
}
impl From<GPUCompareFunction> for wgpu_types::CompareFunction {
fn from(value: GPUCompareFunction) -> Self {
match value {
GPUCompareFunction::Never => Self::Never,
GPUCompareFunction::Less => Self::Less,
GPUCompareFunction::Equal => Self::Equal,
GPUCompareFunction::LessEqual => Self::LessEqual,
GPUCompareFunction::Greater => Self::Greater,
GPUCompareFunction::NotEqual => Self::NotEqual,
GPUCompareFunction::GreaterEqual => Self::GreaterEqual,
GPUCompareFunction::Always => Self::Always,
}
}
}

View file

@ -1,54 +1,49 @@
// Copyright 2018-2025 the Deno authors. MIT license.
use std::borrow::Cow;
use std::rc::Rc;
use deno_core::op2;
use deno_core::OpState;
use deno_core::Resource;
use deno_core::ResourceId;
use deno_core::webidl::WebIdlInterfaceConverter;
use deno_core::GarbageCollected;
use deno_core::WebIDL;
use super::error::WebGpuResult;
use crate::Instance;
pub(crate) struct WebGpuShaderModule(
pub(crate) super::Instance,
pub(crate) wgpu_core::id::ShaderModuleId,
);
impl Resource for WebGpuShaderModule {
fn name(&self) -> Cow<str> {
"webGPUShaderModule".into()
}
pub struct GPUShaderModule {
pub instance: Instance,
pub id: wgpu_core::id::ShaderModuleId,
pub label: String,
}
fn close(self: Rc<Self>) {
gfx_select!(self.1 => self.0.shader_module_drop(self.1));
impl Drop for GPUShaderModule {
fn drop(&mut self) {
self.instance.shader_module_drop(self.id);
}
}
impl WebIdlInterfaceConverter for GPUShaderModule {
const NAME: &'static str = "GPUShaderModule";
}
impl GarbageCollected for GPUShaderModule {}
#[op2]
#[serde]
pub fn op_webgpu_create_shader_module(
state: &mut OpState,
#[smi] device_rid: ResourceId,
#[string] label: Cow<str>,
#[string] code: Cow<str>,
) -> Result<WebGpuResult, deno_core::error::ResourceError> {
let instance = state.borrow::<super::Instance>();
let device_resource = state
.resource_table
.get::<super::WebGpuDevice>(device_rid)?;
let device = device_resource.1;
let source = wgpu_core::pipeline::ShaderModuleSource::Wgsl(code);
let descriptor = wgpu_core::pipeline::ShaderModuleDescriptor {
label: Some(label),
shader_bound_checks: wgpu_types::ShaderBoundChecks::default(),
};
gfx_put!(device => instance.device_create_shader_module(
device,
&descriptor,
source,
None
) => state, WebGpuShaderModule)
impl GPUShaderModule {
#[getter]
#[string]
fn label(&self) -> String {
self.label.clone()
}
#[setter]
#[string]
fn label(&self, #[webidl] _label: String) {
// TODO(@crowlKats): no-op, needs wpgu to implement changing the label
}
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPUShaderModuleDescriptor {
#[webidl(default = String::new())]
pub label: String,
pub code: String,
}

View file

@ -1,144 +1,229 @@
// Copyright 2018-2025 the Deno authors. MIT license.
use std::cell::RefCell;
use std::borrow::Cow;
use std::rc::Rc;
use deno_core::error::ResourceError;
use deno_core::op2;
use deno_core::OpState;
use deno_core::Resource;
use deno_core::ResourceId;
use serde::Deserialize;
use deno_core::v8;
use deno_core::GarbageCollected;
use deno_core::WebIDL;
use deno_core::_ops::make_cppgc_object;
use deno_core::cppgc::Ptr;
use deno_error::JsErrorBox;
use wgpu_types::SurfaceStatus;
use crate::error::WebGpuResult;
use crate::device::GPUDevice;
use crate::texture::GPUTexture;
use crate::texture::GPUTextureFormat;
#[derive(Debug, thiserror::Error, deno_error::JsError)]
pub enum SurfaceError {
#[class(inherit)]
#[error(transparent)]
Resource(
#[from]
#[inherit]
ResourceError,
),
#[class("DOMExceptionInvalidStateError")]
#[error("Context is not configured")]
UnconfiguredContext,
#[class(generic)]
#[error("Invalid Surface Status")]
InvalidStatus,
#[class(generic)]
#[error(transparent)]
Surface(wgpu_core::present::SurfaceError),
Surface(#[from] wgpu_core::present::SurfaceError),
}
pub struct WebGpuSurface(pub crate::Instance, pub wgpu_core::id::SurfaceId);
impl Resource for WebGpuSurface {
fn name(&self) -> Cow<str> {
"webGPUSurface".into()
}
fn close(self: Rc<Self>) {
self.0.surface_drop(self.1);
}
pub struct Configuration {
pub device: Ptr<GPUDevice>,
pub usage: u32,
pub format: GPUTextureFormat,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SurfaceConfigureArgs {
surface_rid: ResourceId,
device_rid: ResourceId,
format: wgpu_types::TextureFormat,
usage: u32,
width: u32,
height: u32,
present_mode: Option<wgpu_types::PresentMode>,
alpha_mode: wgpu_types::CompositeAlphaMode,
view_formats: Vec<wgpu_types::TextureFormat>,
pub struct GPUCanvasContext {
pub surface_id: wgpu_core::id::SurfaceId,
pub width: RefCell<u32>,
pub height: RefCell<u32>,
pub config: RefCell<Option<Configuration>>,
pub texture: RefCell<Option<v8::Global<v8::Object>>>,
}
impl GarbageCollected for GPUCanvasContext {}
#[op2]
#[serde]
pub fn op_webgpu_surface_configure(
state: &mut OpState,
#[serde] args: SurfaceConfigureArgs,
) -> Result<WebGpuResult, ResourceError> {
let instance = state.borrow::<super::Instance>();
let device_resource = state
.resource_table
.get::<super::WebGpuDevice>(args.device_rid)?;
let device = device_resource.1;
let surface_resource = state
.resource_table
.get::<WebGpuSurface>(args.surface_rid)?;
let surface = surface_resource.1;
impl GPUCanvasContext {
#[getter]
#[global]
fn canvas(&self) -> v8::Global<v8::Value> {
todo!()
}
let conf = wgpu_types::SurfaceConfiguration::<Vec<wgpu_types::TextureFormat>> {
usage: wgpu_types::TextureUsages::from_bits_truncate(args.usage),
format: args.format,
width: args.width,
height: args.height,
present_mode: args.present_mode.unwrap_or_default(),
alpha_mode: args.alpha_mode,
view_formats: args.view_formats,
fn configure(
&self,
#[webidl] configuration: GPUCanvasConfiguration,
) -> Result<(), JsErrorBox> {
let usage = wgpu_types::TextureUsages::from_bits(configuration.usage)
.ok_or_else(|| JsErrorBox::type_error("usage is not valid"))?;
let format = configuration.format.clone().into();
let conf = wgpu_types::SurfaceConfiguration {
usage,
format,
width: *self.width.borrow(),
height: *self.height.borrow(),
present_mode: configuration
.present_mode
.map(Into::into)
.unwrap_or_default(),
alpha_mode: configuration.alpha_mode.into(),
view_formats: configuration
.view_formats
.into_iter()
.map(Into::into)
.collect(),
desired_maximum_frame_latency: 2,
};
let device = configuration.device;
let err =
gfx_select!(device => instance.surface_configure(surface, device, &conf));
device
.instance
.surface_configure(self.surface_id, device.id, &conf);
Ok(WebGpuResult::maybe_err(err))
}
device.error_handler.push_error(err);
#[op2]
#[serde]
pub fn op_webgpu_surface_get_current_texture(
state: &mut OpState,
#[smi] device_rid: ResourceId,
#[smi] surface_rid: ResourceId,
) -> Result<WebGpuResult, SurfaceError> {
let instance = state.borrow::<super::Instance>();
let device_resource = state
.resource_table
.get::<super::WebGpuDevice>(device_rid)?;
let device = device_resource.1;
let surface_resource =
state.resource_table.get::<WebGpuSurface>(surface_rid)?;
let surface = surface_resource.1;
self.config.borrow_mut().replace(Configuration {
device,
usage: configuration.usage,
format: configuration.format,
});
let output =
gfx_select!(device => instance.surface_get_current_texture(surface, None))
.map_err(SurfaceError::Surface)?;
Ok(())
}
#[fast]
fn unconfigure(&self) {
*self.config.borrow_mut() = None;
}
#[global]
fn get_current_texture(
&self,
scope: &mut v8::HandleScope,
) -> Result<v8::Global<v8::Object>, SurfaceError> {
let config = self.config.borrow();
let Some(config) = config.as_ref() else {
return Err(SurfaceError::UnconfiguredContext);
};
{
if let Some(obj) = self.texture.borrow().as_ref() {
return Ok(obj.clone());
}
}
let output = config
.device
.instance
.surface_get_current_texture(self.surface_id, None)?;
match output.status {
SurfaceStatus::Good | SurfaceStatus::Suboptimal => {
let id = output.texture_id.unwrap();
let rid = state.resource_table.add(crate::texture::WebGpuTexture {
instance: instance.clone(),
let texture = GPUTexture {
instance: config.device.instance.clone(),
error_handler: config.device.error_handler.clone(),
id,
owned: false,
});
Ok(WebGpuResult::rid(rid))
label: "".to_string(),
size: wgpu_types::Extent3d {
width: *self.width.borrow(),
height: *self.height.borrow(),
depth_or_array_layers: 1,
},
mip_level_count: 0,
sample_count: 0,
dimension: crate::texture::GPUTextureDimension::D2,
format: config.format.clone(),
usage: config.usage,
};
let obj = make_cppgc_object(scope, texture);
let obj = v8::Global::new(scope, obj);
*self.texture.borrow_mut() = Some(obj.clone());
Ok(obj)
}
_ => Err(SurfaceError::InvalidStatus),
}
}
}
#[op2(fast)]
pub fn op_webgpu_surface_present(
state: &mut OpState,
#[smi] device_rid: ResourceId,
#[smi] surface_rid: ResourceId,
) -> Result<(), SurfaceError> {
let instance = state.borrow::<super::Instance>();
let device_resource = state
.resource_table
.get::<super::WebGpuDevice>(device_rid)?;
let device = device_resource.1;
let surface_resource =
state.resource_table.get::<WebGpuSurface>(surface_rid)?;
let surface = surface_resource.1;
impl GPUCanvasContext {
pub fn present(&self) -> Result<(), SurfaceError> {
let config = self.config.borrow();
let Some(config) = config.as_ref() else {
return Err(SurfaceError::UnconfiguredContext);
};
let _ = gfx_select!(device => instance.surface_present(surface))
.map_err(SurfaceError::Surface)?;
config.device.instance.surface_present(self.surface_id)?;
Ok(())
}
}
#[derive(WebIDL)]
#[webidl(dictionary)]
struct GPUCanvasConfiguration {
device: Ptr<GPUDevice>,
format: GPUTextureFormat,
#[webidl(default = wgpu_types::TextureUsages::RENDER_ATTACHMENT.bits())]
#[options(enforce_range = true)]
usage: u32,
#[webidl(default = GPUCanvasAlphaMode::Opaque)]
alpha_mode: GPUCanvasAlphaMode,
// Extended from spec
present_mode: Option<GPUPresentMode>,
#[webidl(default = vec![])]
view_formats: Vec<GPUTextureFormat>,
}
#[derive(WebIDL)]
#[webidl(enum)]
enum GPUCanvasAlphaMode {
Opaque,
Premultiplied,
}
impl From<GPUCanvasAlphaMode> for wgpu_types::CompositeAlphaMode {
fn from(value: GPUCanvasAlphaMode) -> Self {
match value {
GPUCanvasAlphaMode::Opaque => Self::Opaque,
GPUCanvasAlphaMode::Premultiplied => Self::PreMultiplied,
}
}
}
// Extended from spec
#[derive(WebIDL)]
#[webidl(enum)]
enum GPUPresentMode {
#[webidl(rename = "autoVsync")]
AutoVsync,
#[webidl(rename = "autoNoVsync")]
AutoNoVsync,
#[webidl(rename = "fifo")]
Fifo,
#[webidl(rename = "fifoRelaxed")]
FifoRelaxed,
#[webidl(rename = "immediate")]
Immediate,
#[webidl(rename = "mailbox")]
Mailbox,
}
impl From<GPUPresentMode> for wgpu_types::PresentMode {
fn from(value: GPUPresentMode) -> Self {
match value {
GPUPresentMode::AutoVsync => Self::AutoVsync,
GPUPresentMode::AutoNoVsync => Self::AutoNoVsync,
GPUPresentMode::Fifo => Self::Fifo,
GPUPresentMode::FifoRelaxed => Self::FifoRelaxed,
GPUPresentMode::Immediate => Self::Immediate,
GPUPresentMode::Mailbox => Self::Mailbox,
}
}
}

View file

@ -1,133 +1,661 @@
// Copyright 2018-2025 the Deno authors. MIT license.
use std::borrow::Cow;
use std::rc::Rc;
use deno_core::op2;
use deno_core::OpState;
use deno_core::Resource;
use deno_core::ResourceId;
use serde::Deserialize;
use deno_core::webidl::WebIdlInterfaceConverter;
use deno_core::GarbageCollected;
use deno_core::WebIDL;
use deno_error::JsErrorBox;
use wgpu_types::AstcBlock;
use wgpu_types::AstcChannel;
use wgpu_types::Extent3d;
use wgpu_types::TextureAspect;
use wgpu_types::TextureDimension;
use wgpu_types::TextureFormat;
use wgpu_types::TextureViewDimension;
use super::error::WebGpuResult;
pub(crate) struct WebGpuTexture {
pub(crate) instance: crate::Instance,
pub(crate) id: wgpu_core::id::TextureId,
pub(crate) owned: bool,
use crate::Instance;
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPUTextureDescriptor {
#[webidl(default = String::new())]
pub label: String,
pub size: super::webidl::GPUExtent3D,
#[webidl(default = 1)]
#[options(enforce_range = true)]
pub mip_level_count: u32,
#[webidl(default = 1)]
#[options(enforce_range = true)]
pub sample_count: u32,
#[webidl(default = GPUTextureDimension::D2)]
pub dimension: GPUTextureDimension,
pub format: GPUTextureFormat,
#[options(enforce_range = true)]
pub usage: u32,
#[webidl(default = vec![])]
pub view_formats: Vec<GPUTextureFormat>,
}
impl Resource for WebGpuTexture {
fn name(&self) -> Cow<str> {
"webGPUTexture".into()
}
pub struct GPUTexture {
pub instance: Instance,
pub error_handler: super::error::ErrorHandler,
fn close(self: Rc<Self>) {
if self.owned {
let instance = &self.instance;
gfx_select!(self.id => instance.texture_drop(self.id, true));
}
pub id: wgpu_core::id::TextureId,
pub label: String,
pub size: Extent3d,
pub mip_level_count: u32,
pub sample_count: u32,
pub dimension: GPUTextureDimension,
pub format: GPUTextureFormat,
pub usage: u32,
}
impl Drop for GPUTexture {
fn drop(&mut self) {
self.instance.texture_drop(self.id);
}
}
pub(crate) struct WebGpuTextureView(
pub(crate) crate::Instance,
pub(crate) wgpu_core::id::TextureViewId,
);
impl Resource for WebGpuTextureView {
fn name(&self) -> Cow<str> {
"webGPUTextureView".into()
}
fn close(self: Rc<Self>) {
gfx_select!(self.1 => self.0.texture_view_drop(self.1, true)).unwrap();
}
impl WebIdlInterfaceConverter for GPUTexture {
const NAME: &'static str = "GPUTexture";
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateTextureArgs {
device_rid: ResourceId,
label: String,
size: wgpu_types::Extent3d,
mip_level_count: u32,
sample_count: u32,
dimension: wgpu_types::TextureDimension,
format: wgpu_types::TextureFormat,
usage: u32,
view_formats: Vec<wgpu_types::TextureFormat>,
}
impl GarbageCollected for GPUTexture {}
#[op2]
#[serde]
pub fn op_webgpu_create_texture(
state: &mut OpState,
#[serde] args: CreateTextureArgs,
) -> Result<WebGpuResult, deno_core::error::ResourceError> {
let instance = state.borrow::<super::Instance>();
let device_resource = state
.resource_table
.get::<super::WebGpuDevice>(args.device_rid)?;
let device = device_resource.1;
impl GPUTexture {
#[getter]
#[string]
fn label(&self) -> String {
self.label.clone()
}
#[setter]
#[string]
fn label(&self, #[webidl] _label: String) {
// TODO(@crowlKats): no-op, needs wpgu to implement changing the label
}
let descriptor = wgpu_core::resource::TextureDescriptor {
label: Some(Cow::Owned(args.label)),
size: args.size,
mip_level_count: args.mip_level_count,
sample_count: args.sample_count,
dimension: args.dimension,
format: args.format,
usage: wgpu_types::TextureUsages::from_bits_truncate(args.usage),
view_formats: args.view_formats,
#[getter]
fn width(&self) -> u32 {
self.size.width
}
#[getter]
fn height(&self) -> u32 {
self.size.height
}
#[getter]
fn depth_or_array_layers(&self) -> u32 {
self.size.depth_or_array_layers
}
#[getter]
fn mip_level_count(&self) -> u32 {
self.mip_level_count
}
#[getter]
fn sample_count(&self) -> u32 {
self.sample_count
}
#[getter]
#[string]
fn dimension(&self) -> &'static str {
self.dimension.as_str()
}
#[getter]
#[string]
fn format(&self) -> &'static str {
self.format.as_str()
}
#[getter]
fn usage(&self) -> u32 {
self.usage
}
#[fast]
fn destroy(&self) -> Result<(), JsErrorBox> {
self
.instance
.texture_destroy(self.id)
.map_err(|e| JsErrorBox::generic(e.to_string()))
}
#[cppgc]
fn create_view(
&self,
#[webidl] descriptor: GPUTextureViewDescriptor,
) -> Result<GPUTextureView, JsErrorBox> {
let wgpu_descriptor = wgpu_core::resource::TextureViewDescriptor {
label: Some(Cow::Owned(descriptor.label.clone())),
format: descriptor.format.map(Into::into),
dimension: descriptor.dimension.map(Into::into),
range: wgpu_types::ImageSubresourceRange {
aspect: descriptor.aspect.into(),
base_mip_level: descriptor.base_mip_level,
mip_level_count: descriptor.mip_level_count,
base_array_layer: descriptor.base_array_layer,
array_layer_count: descriptor.array_layer_count,
},
};
let (val, maybe_err) = gfx_select!(device => instance.device_create_texture(
device,
&descriptor,
None
));
let (id, err) =
self
.instance
.texture_create_view(self.id, &wgpu_descriptor, None);
let rid = state.resource_table.add(WebGpuTexture {
instance: instance.clone(),
id: val,
owned: true,
});
self.error_handler.push_error(err);
Ok(WebGpuResult::rid_err(rid, maybe_err))
Ok(GPUTextureView {
instance: self.instance.clone(),
id,
label: descriptor.label,
})
}
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateTextureViewArgs {
texture_rid: ResourceId,
#[derive(WebIDL)]
#[webidl(dictionary)]
struct GPUTextureViewDescriptor {
#[webidl(default = String::new())]
label: String,
format: Option<wgpu_types::TextureFormat>,
dimension: Option<wgpu_types::TextureViewDimension>,
#[serde(flatten)]
range: wgpu_types::ImageSubresourceRange,
format: Option<GPUTextureFormat>,
dimension: Option<GPUTextureViewDimension>,
#[webidl(default = GPUTextureAspect::All)]
aspect: GPUTextureAspect,
#[webidl(default = 0)]
#[options(enforce_range = true)]
base_mip_level: u32,
#[options(enforce_range = true)]
mip_level_count: Option<u32>,
#[webidl(default = 0)]
#[options(enforce_range = true)]
base_array_layer: u32,
#[options(enforce_range = true)]
array_layer_count: Option<u32>,
}
#[derive(WebIDL)]
#[webidl(enum)]
pub(crate) enum GPUTextureViewDimension {
#[webidl(rename = "1d")]
D1,
#[webidl(rename = "2d")]
D2,
#[webidl(rename = "2d-array")]
D2Array,
#[webidl(rename = "cube")]
Cube,
#[webidl(rename = "cube-array")]
CubeArray,
#[webidl(rename = "3d")]
D3,
}
impl From<GPUTextureViewDimension> for TextureViewDimension {
fn from(value: GPUTextureViewDimension) -> Self {
match value {
GPUTextureViewDimension::D1 => Self::D1,
GPUTextureViewDimension::D2 => Self::D2,
GPUTextureViewDimension::D3 => Self::D3,
GPUTextureViewDimension::D2Array => Self::D2Array,
GPUTextureViewDimension::Cube => Self::Cube,
GPUTextureViewDimension::CubeArray => Self::CubeArray,
}
}
}
#[derive(WebIDL)]
#[webidl(enum)]
pub enum GPUTextureAspect {
All,
StencilOnly,
DepthOnly,
}
impl From<GPUTextureAspect> for TextureAspect {
fn from(value: GPUTextureAspect) -> Self {
match value {
GPUTextureAspect::All => Self::All,
GPUTextureAspect::StencilOnly => Self::StencilOnly,
GPUTextureAspect::DepthOnly => Self::DepthOnly,
}
}
}
pub struct GPUTextureView {
pub instance: Instance,
pub id: wgpu_core::id::TextureViewId,
pub label: String,
}
impl Drop for GPUTextureView {
fn drop(&mut self) {
let _ = self.instance.texture_view_drop(self.id);
}
}
impl WebIdlInterfaceConverter for GPUTextureView {
const NAME: &'static str = "GPUTextureView";
}
impl GarbageCollected for GPUTextureView {}
// TODO: weakref in texture for view
#[op2]
#[serde]
pub fn op_webgpu_create_texture_view(
state: &mut OpState,
#[serde] args: CreateTextureViewArgs,
) -> Result<WebGpuResult, deno_core::error::ResourceError> {
let instance = state.borrow::<super::Instance>();
let texture_resource = state
.resource_table
.get::<WebGpuTexture>(args.texture_rid)?;
let texture = texture_resource.id;
let descriptor = wgpu_core::resource::TextureViewDescriptor {
label: Some(Cow::Owned(args.label)),
format: args.format,
dimension: args.dimension,
range: args.range,
};
gfx_put!(texture => instance.texture_create_view(
texture,
&descriptor,
None
) => state, WebGpuTextureView)
impl GPUTextureView {
#[getter]
#[string]
fn label(&self) -> String {
self.label.clone()
}
#[setter]
#[string]
fn label(&self, #[webidl] _label: String) {
// TODO(@crowlKats): no-op, needs wpgu to implement changing the label
}
}
#[derive(WebIDL, Clone)]
#[webidl(enum)]
pub enum GPUTextureDimension {
#[webidl(rename = "1d")]
D1,
#[webidl(rename = "2d")]
D2,
#[webidl(rename = "3d")]
D3,
}
impl From<GPUTextureDimension> for TextureDimension {
fn from(value: GPUTextureDimension) -> Self {
match value {
GPUTextureDimension::D1 => Self::D1,
GPUTextureDimension::D2 => Self::D2,
GPUTextureDimension::D3 => Self::D3,
}
}
}
#[derive(WebIDL, Clone)]
#[webidl(enum)]
pub(crate) enum GPUTextureFormat {
#[webidl(rename = "r8unorm")]
R8unorm,
#[webidl(rename = "r8snorm")]
R8snorm,
#[webidl(rename = "r8uint")]
R8uint,
#[webidl(rename = "r8sint")]
R8sint,
#[webidl(rename = "r16uint")]
R16uint,
#[webidl(rename = "r16sint")]
R16sint,
#[webidl(rename = "r16float")]
R16float,
#[webidl(rename = "rg8unorm")]
Rg8unorm,
#[webidl(rename = "rg8snorm")]
Rg8snorm,
#[webidl(rename = "rg8uint")]
Rg8uint,
#[webidl(rename = "rg8sint")]
Rg8sint,
#[webidl(rename = "r32uint")]
R32uint,
#[webidl(rename = "r32sint")]
R32sint,
#[webidl(rename = "r32float")]
R32float,
#[webidl(rename = "rg16uint")]
Rg16uint,
#[webidl(rename = "rg16sint")]
Rg16sint,
#[webidl(rename = "rg16float")]
Rg16float,
#[webidl(rename = "rgba8unorm")]
Rgba8unorm,
#[webidl(rename = "rgba8unorm-srgb")]
Rgba8unormSrgb,
#[webidl(rename = "rgba8snorm")]
Rgba8snorm,
#[webidl(rename = "rgba8uint")]
Rgba8uint,
#[webidl(rename = "rgba8sint")]
Rgba8sint,
#[webidl(rename = "bgra8unorm")]
Bgra8unorm,
#[webidl(rename = "bgra8unorm-srgb")]
Bgra8unormSrgb,
#[webidl(rename = "rgb9e5ufloat")]
Rgb9e5ufloat,
#[webidl(rename = "rgb10a2uint")]
Rgb10a2uint,
#[webidl(rename = "rgb10a2unorm")]
Rgb10a2unorm,
#[webidl(rename = "rg11b10ufloat")]
Rg11b10ufloat,
#[webidl(rename = "rg32uint")]
Rg32uint,
#[webidl(rename = "rg32sint")]
Rg32sint,
#[webidl(rename = "rg32float")]
Rg32float,
#[webidl(rename = "rgba16uint")]
Rgba16uint,
#[webidl(rename = "rgba16sint")]
Rgba16sint,
#[webidl(rename = "rgba16float")]
Rgba16float,
#[webidl(rename = "rgba32uint")]
Rgba32uint,
#[webidl(rename = "rgba32sint")]
Rgba32sint,
#[webidl(rename = "rgba32float")]
Rgba32float,
#[webidl(rename = "stencil8")]
Stencil8,
#[webidl(rename = "depth16unorm")]
Depth16unorm,
#[webidl(rename = "depth24plus")]
Depth24plus,
#[webidl(rename = "depth24plus-stencil8")]
Depth24plusStencil8,
#[webidl(rename = "depth32float")]
Depth32float,
#[webidl(rename = "depth32float-stencil8")]
Depth32floatStencil8,
#[webidl(rename = "bc1-rgba-unorm")]
Bc1RgbaUnorm,
#[webidl(rename = "bc1-rgba-unorm-srgb")]
Bc1RgbaUnormSrgb,
#[webidl(rename = "bc2-rgba-unorm")]
Bc2RgbaUnorm,
#[webidl(rename = "bc2-rgba-unorm-srgb")]
Bc2RgbaUnormSrgb,
#[webidl(rename = "bc3-rgba-unorm")]
Bc3RgbaUnorm,
#[webidl(rename = "bc3-rgba-unorm-srgb")]
Bc3RgbaUnormSrgb,
#[webidl(rename = "bc4-r-unorm")]
Bc4RUnorm,
#[webidl(rename = "bc4-r-snorm")]
Bc4RSnorm,
#[webidl(rename = "bc5-rg-unorm")]
Bc5RgUnorm,
#[webidl(rename = "bc5-rg-snorm")]
Bc5RgSnorm,
#[webidl(rename = "bc6h-rgb-ufloat")]
Bc6hRgbUfloat,
#[webidl(rename = "bc6h-rgb-float")]
Bc6hRgbFloat,
#[webidl(rename = "bc7-rgba-unorm")]
Bc7RgbaUnorm,
#[webidl(rename = "bc7-rgba-unorm-srgb")]
Bc7RgbaUnormSrgb,
#[webidl(rename = "etc2-rgb8unorm")]
Etc2Rgb8unorm,
#[webidl(rename = "etc2-rgb8unorm-srgb")]
Etc2Rgb8unormSrgb,
#[webidl(rename = "etc2-rgb8a1unorm")]
Etc2Rgb8a1unorm,
#[webidl(rename = "etc2-rgb8a1unorm-srgb")]
Etc2Rgb8a1unormSrgb,
#[webidl(rename = "etc2-rgba8unorm")]
Etc2Rgba8unorm,
#[webidl(rename = "etc2-rgba8unorm-srgb")]
Etc2Rgba8unormSrgb,
#[webidl(rename = "eac-r11unorm")]
EacR11unorm,
#[webidl(rename = "eac-r11snorm")]
EacR11snorm,
#[webidl(rename = "eac-rg11unorm")]
EacRg11unorm,
#[webidl(rename = "eac-rg11snorm")]
EacRg11snorm,
#[webidl(rename = "astc-4x4-unorm")]
Astc4x4Unorm,
#[webidl(rename = "astc-4x4-unorm-srgb")]
Astc4x4UnormSrgb,
#[webidl(rename = "astc-5x4-unorm")]
Astc5x4Unorm,
#[webidl(rename = "astc-5x4-unorm-srgb")]
Astc5x4UnormSrgb,
#[webidl(rename = "astc-5x5-unorm")]
Astc5x5Unorm,
#[webidl(rename = "astc-5x5-unorm-srgb")]
Astc5x5UnormSrgb,
#[webidl(rename = "astc-6x5-unorm")]
Astc6x5Unorm,
#[webidl(rename = "astc-6x5-unorm-srgb")]
Astc6x5UnormSrgb,
#[webidl(rename = "astc-6x6-unorm")]
Astc6x6Unorm,
#[webidl(rename = "astc-6x6-unorm-srgb")]
Astc6x6UnormSrgb,
#[webidl(rename = "astc-8x5-unorm")]
Astc8x5Unorm,
#[webidl(rename = "astc-8x5-unorm-srgb")]
Astc8x5UnormSrgb,
#[webidl(rename = "astc-8x6-unorm")]
Astc8x6Unorm,
#[webidl(rename = "astc-8x6-unorm-srgb")]
Astc8x6UnormSrgb,
#[webidl(rename = "astc-8x8-unorm")]
Astc8x8Unorm,
#[webidl(rename = "astc-8x8-unorm-srgb")]
Astc8x8UnormSrgb,
#[webidl(rename = "astc-10x5-unorm")]
Astc10x5Unorm,
#[webidl(rename = "astc-10x5-unorm-srgb")]
Astc10x5UnormSrgb,
#[webidl(rename = "astc-10x6-unorm")]
Astc10x6Unorm,
#[webidl(rename = "astc-10x6-unorm-srgb")]
Astc10x6UnormSrgb,
#[webidl(rename = "astc-10x8-unorm")]
Astc10x8Unorm,
#[webidl(rename = "astc-10x8-unorm-srgb")]
Astc10x8UnormSrgb,
#[webidl(rename = "astc-10x10-unorm")]
Astc10x10Unorm,
#[webidl(rename = "astc-10x10-unorm-srgb")]
Astc10x10UnormSrgb,
#[webidl(rename = "astc-12x10-unorm")]
Astc12x10Unorm,
#[webidl(rename = "astc-12x10-unorm-srgb")]
Astc12x10UnormSrgb,
#[webidl(rename = "astc-12x12-unorm")]
Astc12x12Unorm,
#[webidl(rename = "astc-12x12-unorm-srgb")]
Astc12x12UnormSrgb,
}
impl From<GPUTextureFormat> for TextureFormat {
fn from(value: GPUTextureFormat) -> Self {
match value {
GPUTextureFormat::R8unorm => Self::R8Unorm,
GPUTextureFormat::R8snorm => Self::R8Snorm,
GPUTextureFormat::R8uint => Self::R8Uint,
GPUTextureFormat::R8sint => Self::R8Sint,
GPUTextureFormat::R16uint => Self::R16Uint,
GPUTextureFormat::R16sint => Self::R16Sint,
GPUTextureFormat::R16float => Self::R16Float,
GPUTextureFormat::Rg8unorm => Self::Rg8Unorm,
GPUTextureFormat::Rg8snorm => Self::Rg8Snorm,
GPUTextureFormat::Rg8uint => Self::Rg8Uint,
GPUTextureFormat::Rg8sint => Self::Rg8Sint,
GPUTextureFormat::R32uint => Self::R32Uint,
GPUTextureFormat::R32sint => Self::R32Sint,
GPUTextureFormat::R32float => Self::R32Float,
GPUTextureFormat::Rg16uint => Self::Rg16Uint,
GPUTextureFormat::Rg16sint => Self::Rg16Sint,
GPUTextureFormat::Rg16float => Self::Rg16Float,
GPUTextureFormat::Rgba8unorm => Self::Rgba8Unorm,
GPUTextureFormat::Rgba8unormSrgb => Self::Rgba8UnormSrgb,
GPUTextureFormat::Rgba8snorm => Self::Rgba8Snorm,
GPUTextureFormat::Rgba8uint => Self::Rgba8Uint,
GPUTextureFormat::Rgba8sint => Self::Rgba8Sint,
GPUTextureFormat::Bgra8unorm => Self::Bgra8Unorm,
GPUTextureFormat::Bgra8unormSrgb => Self::Bgra8UnormSrgb,
GPUTextureFormat::Rgb9e5ufloat => Self::Rgb9e5Ufloat,
GPUTextureFormat::Rgb10a2uint => Self::Rgb10a2Uint,
GPUTextureFormat::Rgb10a2unorm => Self::Rgb10a2Unorm,
GPUTextureFormat::Rg11b10ufloat => Self::Rg11b10Ufloat,
GPUTextureFormat::Rg32uint => Self::Rg32Uint,
GPUTextureFormat::Rg32sint => Self::Rg32Sint,
GPUTextureFormat::Rg32float => Self::Rg32Float,
GPUTextureFormat::Rgba16uint => Self::Rgba16Uint,
GPUTextureFormat::Rgba16sint => Self::Rgba16Sint,
GPUTextureFormat::Rgba16float => Self::Rgba16Float,
GPUTextureFormat::Rgba32uint => Self::Rgba32Uint,
GPUTextureFormat::Rgba32sint => Self::Rgba32Sint,
GPUTextureFormat::Rgba32float => Self::Rgba32Float,
GPUTextureFormat::Stencil8 => Self::Stencil8,
GPUTextureFormat::Depth16unorm => Self::Depth16Unorm,
GPUTextureFormat::Depth24plus => Self::Depth24Plus,
GPUTextureFormat::Depth24plusStencil8 => Self::Depth24PlusStencil8,
GPUTextureFormat::Depth32float => Self::Depth32Float,
GPUTextureFormat::Depth32floatStencil8 => Self::Depth32FloatStencil8,
GPUTextureFormat::Bc1RgbaUnorm => Self::Bc1RgbaUnorm,
GPUTextureFormat::Bc1RgbaUnormSrgb => Self::Bc1RgbaUnormSrgb,
GPUTextureFormat::Bc2RgbaUnorm => Self::Bc2RgbaUnorm,
GPUTextureFormat::Bc2RgbaUnormSrgb => Self::Bc2RgbaUnormSrgb,
GPUTextureFormat::Bc3RgbaUnorm => Self::Bc3RgbaUnorm,
GPUTextureFormat::Bc3RgbaUnormSrgb => Self::Bc3RgbaUnormSrgb,
GPUTextureFormat::Bc4RUnorm => Self::Bc4RUnorm,
GPUTextureFormat::Bc4RSnorm => Self::Bc4RSnorm,
GPUTextureFormat::Bc5RgUnorm => Self::Bc5RgUnorm,
GPUTextureFormat::Bc5RgSnorm => Self::Bc5RgSnorm,
GPUTextureFormat::Bc6hRgbUfloat => Self::Bc6hRgbUfloat,
GPUTextureFormat::Bc6hRgbFloat => Self::Bc6hRgbFloat,
GPUTextureFormat::Bc7RgbaUnorm => Self::Bc7RgbaUnorm,
GPUTextureFormat::Bc7RgbaUnormSrgb => Self::Bc7RgbaUnormSrgb,
GPUTextureFormat::Etc2Rgb8unorm => Self::Etc2Rgb8Unorm,
GPUTextureFormat::Etc2Rgb8unormSrgb => Self::Etc2Rgb8UnormSrgb,
GPUTextureFormat::Etc2Rgb8a1unorm => Self::Etc2Rgb8A1Unorm,
GPUTextureFormat::Etc2Rgb8a1unormSrgb => Self::Etc2Rgb8A1UnormSrgb,
GPUTextureFormat::Etc2Rgba8unorm => Self::Etc2Rgba8Unorm,
GPUTextureFormat::Etc2Rgba8unormSrgb => Self::Etc2Rgba8UnormSrgb,
GPUTextureFormat::EacR11unorm => Self::EacR11Unorm,
GPUTextureFormat::EacR11snorm => Self::EacR11Snorm,
GPUTextureFormat::EacRg11unorm => Self::EacRg11Unorm,
GPUTextureFormat::EacRg11snorm => Self::EacRg11Snorm,
GPUTextureFormat::Astc4x4Unorm => Self::Astc {
block: AstcBlock::B4x4,
channel: AstcChannel::Unorm,
},
GPUTextureFormat::Astc4x4UnormSrgb => Self::Astc {
block: AstcBlock::B5x4,
channel: AstcChannel::UnormSrgb,
},
GPUTextureFormat::Astc5x4Unorm => Self::Astc {
block: AstcBlock::B5x4,
channel: AstcChannel::Unorm,
},
GPUTextureFormat::Astc5x4UnormSrgb => Self::Astc {
block: AstcBlock::B5x4,
channel: AstcChannel::UnormSrgb,
},
GPUTextureFormat::Astc5x5Unorm => Self::Astc {
block: AstcBlock::B5x5,
channel: AstcChannel::Unorm,
},
GPUTextureFormat::Astc5x5UnormSrgb => Self::Astc {
block: AstcBlock::B5x5,
channel: AstcChannel::UnormSrgb,
},
GPUTextureFormat::Astc6x5Unorm => Self::Astc {
block: AstcBlock::B6x5,
channel: AstcChannel::Unorm,
},
GPUTextureFormat::Astc6x5UnormSrgb => Self::Astc {
block: AstcBlock::B6x5,
channel: AstcChannel::UnormSrgb,
},
GPUTextureFormat::Astc6x6Unorm => Self::Astc {
block: AstcBlock::B6x6,
channel: AstcChannel::Unorm,
},
GPUTextureFormat::Astc6x6UnormSrgb => Self::Astc {
block: AstcBlock::B6x6,
channel: AstcChannel::UnormSrgb,
},
GPUTextureFormat::Astc8x5Unorm => Self::Astc {
block: AstcBlock::B8x5,
channel: AstcChannel::Unorm,
},
GPUTextureFormat::Astc8x5UnormSrgb => Self::Astc {
block: AstcBlock::B8x5,
channel: AstcChannel::UnormSrgb,
},
GPUTextureFormat::Astc8x6Unorm => Self::Astc {
block: AstcBlock::B8x6,
channel: AstcChannel::Unorm,
},
GPUTextureFormat::Astc8x6UnormSrgb => Self::Astc {
block: AstcBlock::B8x6,
channel: AstcChannel::UnormSrgb,
},
GPUTextureFormat::Astc8x8Unorm => Self::Astc {
block: AstcBlock::B8x8,
channel: AstcChannel::Unorm,
},
GPUTextureFormat::Astc8x8UnormSrgb => Self::Astc {
block: AstcBlock::B8x8,
channel: AstcChannel::UnormSrgb,
},
GPUTextureFormat::Astc10x5Unorm => Self::Astc {
block: AstcBlock::B10x5,
channel: AstcChannel::Unorm,
},
GPUTextureFormat::Astc10x5UnormSrgb => Self::Astc {
block: AstcBlock::B10x5,
channel: AstcChannel::UnormSrgb,
},
GPUTextureFormat::Astc10x6Unorm => Self::Astc {
block: AstcBlock::B10x6,
channel: AstcChannel::Unorm,
},
GPUTextureFormat::Astc10x6UnormSrgb => Self::Astc {
block: AstcBlock::B10x6,
channel: AstcChannel::UnormSrgb,
},
GPUTextureFormat::Astc10x8Unorm => Self::Astc {
block: AstcBlock::B10x8,
channel: AstcChannel::Unorm,
},
GPUTextureFormat::Astc10x8UnormSrgb => Self::Astc {
block: AstcBlock::B10x8,
channel: AstcChannel::UnormSrgb,
},
GPUTextureFormat::Astc10x10Unorm => Self::Astc {
block: AstcBlock::B10x10,
channel: AstcChannel::Unorm,
},
GPUTextureFormat::Astc10x10UnormSrgb => Self::Astc {
block: AstcBlock::B10x10,
channel: AstcChannel::UnormSrgb,
},
GPUTextureFormat::Astc12x10Unorm => Self::Astc {
block: AstcBlock::B12x10,
channel: AstcChannel::Unorm,
},
GPUTextureFormat::Astc12x10UnormSrgb => Self::Astc {
block: AstcBlock::B12x10,
channel: AstcChannel::UnormSrgb,
},
GPUTextureFormat::Astc12x12Unorm => Self::Astc {
block: AstcBlock::B12x12,
channel: AstcChannel::Unorm,
},
GPUTextureFormat::Astc12x12UnormSrgb => Self::Astc {
block: AstcBlock::B12x12,
channel: AstcChannel::UnormSrgb,
},
}
}
}

655
ext/webgpu/webidl.rs Normal file
View file

@ -0,0 +1,655 @@
// Copyright 2018-2025 the Deno authors. MIT license.
use std::borrow::Cow;
use std::collections::HashSet;
use deno_core::cppgc::Ptr;
use deno_core::v8;
use deno_core::webidl::ContextFn;
use deno_core::webidl::IntOptions;
use deno_core::webidl::WebIdlConverter;
use deno_core::webidl::WebIdlError;
use deno_core::webidl::WebIdlErrorKind;
use deno_core::WebIDL;
use deno_error::JsErrorBox;
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPUExtent3DDict {
#[options(enforce_range = true)]
width: u32,
#[webidl(default = 1)]
#[options(enforce_range = true)]
height: u32,
#[webidl(default = 1)]
#[options(enforce_range = true)]
depth_or_array_layers: u32,
}
pub(crate) enum GPUExtent3D {
Dict(GPUExtent3DDict),
Sequence((u32, u32, u32)),
}
impl<'a> WebIdlConverter<'a> for GPUExtent3D {
type Options = ();
fn convert<'b>(
scope: &mut v8::HandleScope<'a>,
value: v8::Local<'a, v8::Value>,
prefix: Cow<'static, str>,
context: ContextFn<'b>,
options: &Self::Options,
) -> Result<Self, WebIdlError> {
if value.is_null_or_undefined() {
return Ok(GPUExtent3D::Dict(GPUExtent3DDict::convert(
scope,
value,
prefix,
context.borrowed(),
&options,
)?));
}
if let Ok(obj) = value.try_cast::<v8::Object>() {
let iter = v8::Symbol::get_iterator(scope);
if let Some(iter) = obj.get(scope, iter.into()) {
if !iter.is_undefined() {
let conv = <Vec<u32>>::convert(
scope,
value,
prefix.clone(),
context.borrowed(),
&IntOptions {
clamp: false,
enforce_range: true,
},
)?;
if !(conv.len() > 1 && conv.len() < 3) {
return Err(WebIdlError::other(prefix, context, JsErrorBox::type_error(format!("A sequence of number used as a GPUExtent3D must have between 1 and 3 elements, received {} elements", conv.len()))));
}
let mut iter = conv.into_iter();
return Ok(GPUExtent3D::Sequence((
iter.next().unwrap(),
iter.next().unwrap_or(1),
iter.next().unwrap_or(1),
)));
}
}
return Ok(GPUExtent3D::Dict(GPUExtent3DDict::convert(
scope, value, prefix, context, &options,
)?));
}
Err(WebIdlError::new(
prefix,
context,
WebIdlErrorKind::ConvertToConverterType(
"sequence<GPUIntegerCoordinate> or GPUExtent3DDict",
),
))
}
}
impl From<GPUExtent3D> for wgpu_types::Extent3d {
fn from(value: GPUExtent3D) -> Self {
match value {
GPUExtent3D::Dict(dict) => Self {
width: dict.width,
height: dict.height,
depth_or_array_layers: dict.depth_or_array_layers,
},
GPUExtent3D::Sequence((width, height, depth)) => Self {
width,
height,
depth_or_array_layers: depth,
},
}
}
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPUOrigin3DDict {
#[webidl(default = 0)]
#[options(enforce_range = true)]
x: u32,
#[webidl(default = 0)]
#[options(enforce_range = true)]
y: u32,
#[webidl(default = 0)]
#[options(enforce_range = true)]
z: u32,
}
pub(crate) enum GPUOrigin3D {
Dict(GPUOrigin3DDict),
Sequence((u32, u32, u32)),
}
impl Default for GPUOrigin3D {
fn default() -> Self {
GPUOrigin3D::Sequence((0, 0, 0))
}
}
impl<'a> WebIdlConverter<'a> for GPUOrigin3D {
type Options = ();
fn convert<'b>(
scope: &mut v8::HandleScope<'a>,
value: v8::Local<'a, v8::Value>,
prefix: Cow<'static, str>,
context: ContextFn<'b>,
options: &Self::Options,
) -> Result<Self, WebIdlError> {
if value.is_null_or_undefined() {
return Ok(GPUOrigin3D::Dict(GPUOrigin3DDict::convert(
scope,
value,
prefix,
context.borrowed(),
&options,
)?));
}
if let Ok(obj) = value.try_cast::<v8::Object>() {
let iter = v8::Symbol::get_iterator(scope);
if let Some(iter) = obj.get(scope, iter.into()) {
if !iter.is_undefined() {
let conv = <Vec<u32>>::convert(
scope,
value,
prefix.clone(),
context.borrowed(),
&IntOptions {
clamp: false,
enforce_range: true,
},
)?;
if !(conv.len() > 1 && conv.len() < 3) {
return Err(WebIdlError::other(prefix, context, JsErrorBox::type_error(format!("A sequence of number used as a GPUOrigin3D must have between 1 and 3 elements, received {} elements", conv.len()))));
}
let mut iter = conv.into_iter();
return Ok(GPUOrigin3D::Sequence((
iter.next().unwrap(),
iter.next().unwrap_or(0),
iter.next().unwrap_or(0),
)));
}
}
return Ok(GPUOrigin3D::Dict(GPUOrigin3DDict::convert(
scope, value, prefix, context, &options,
)?));
}
Err(WebIdlError::new(
prefix,
context,
WebIdlErrorKind::ConvertToConverterType(
"sequence<GPUIntegerCoordinate> or GPUOrigin3DDict",
),
))
}
}
impl From<GPUOrigin3D> for wgpu_types::Origin3d {
fn from(value: GPUOrigin3D) -> Self {
match value {
GPUOrigin3D::Dict(dict) => Self {
x: dict.x,
y: dict.y,
z: dict.z,
},
GPUOrigin3D::Sequence((x, y, z)) => Self { x, y, z },
}
}
}
#[derive(WebIDL)]
#[webidl(dictionary)]
pub(crate) struct GPUColorDict {
r: f64,
g: f64,
b: f64,
a: f64,
}
pub(crate) enum GPUColor {
Dict(GPUColorDict),
Sequence((f64, f64, f64, f64)),
}
impl<'a> WebIdlConverter<'a> for GPUColor {
type Options = ();
fn convert<'b>(
scope: &mut v8::HandleScope<'a>,
value: v8::Local<'a, v8::Value>,
prefix: Cow<'static, str>,
context: ContextFn<'b>,
options: &Self::Options,
) -> Result<Self, WebIdlError> {
if value.is_null_or_undefined() {
return Ok(GPUColor::Dict(GPUColorDict::convert(
scope,
value,
prefix,
context.borrowed(),
options,
)?));
}
if let Ok(obj) = value.try_cast::<v8::Object>() {
let iter = v8::Symbol::get_iterator(scope);
if let Some(iter) = obj.get(scope, iter.into()) {
if !iter.is_undefined() {
let conv = <Vec<f64>>::convert(
scope,
value,
prefix.clone(),
context.borrowed(),
options,
)?;
if conv.len() != 4 {
return Err(WebIdlError::other(prefix, context, JsErrorBox::type_error(format!("A sequence of number used as a GPUColor must have 4 elements, received {} elements", conv.len()))));
}
let mut iter = conv.into_iter();
return Ok(GPUColor::Sequence((
iter.next().unwrap(),
iter.next().unwrap(),
iter.next().unwrap(),
iter.next().unwrap(),
)));
}
}
return Ok(GPUColor::Dict(GPUColorDict::convert(
scope, value, prefix, context, options,
)?));
}
Err(WebIdlError::new(
prefix,
context,
WebIdlErrorKind::ConvertToConverterType(
"sequence<GPUIntegerCoordinate> or GPUOrigin3DDict",
),
))
}
}
impl From<GPUColor> for wgpu_types::Color {
fn from(value: GPUColor) -> Self {
match value {
GPUColor::Dict(dict) => Self {
r: dict.r,
g: dict.g,
b: dict.b,
a: dict.a,
},
GPUColor::Sequence((r, g, b, a)) => Self { r, g, b, a },
}
}
}
#[derive(WebIDL)]
#[webidl(enum)]
pub(crate) enum GPUAutoLayoutMode {
Auto,
}
pub(crate) enum GPUPipelineLayoutOrGPUAutoLayoutMode {
PipelineLayout(Ptr<crate::pipeline_layout::GPUPipelineLayout>),
AutoLayoutMode(GPUAutoLayoutMode),
}
impl From<GPUPipelineLayoutOrGPUAutoLayoutMode>
for Option<wgpu_core::id::PipelineLayoutId>
{
fn from(value: GPUPipelineLayoutOrGPUAutoLayoutMode) -> Self {
match value {
GPUPipelineLayoutOrGPUAutoLayoutMode::PipelineLayout(layout) => {
Some(layout.id)
}
GPUPipelineLayoutOrGPUAutoLayoutMode::AutoLayoutMode(
GPUAutoLayoutMode::Auto,
) => None,
}
}
}
impl<'a> WebIdlConverter<'a> for GPUPipelineLayoutOrGPUAutoLayoutMode {
type Options = ();
fn convert<'b>(
scope: &mut v8::HandleScope<'a>,
value: v8::Local<'a, v8::Value>,
prefix: Cow<'static, str>,
context: ContextFn<'b>,
options: &Self::Options,
) -> Result<Self, WebIdlError> {
if value.is_object() {
Ok(Self::PipelineLayout(WebIdlConverter::convert(
scope, value, prefix, context, options,
)?))
} else {
Ok(Self::AutoLayoutMode(WebIdlConverter::convert(
scope, value, prefix, context, options,
)?))
}
}
}
#[derive(WebIDL, Clone, Hash, Eq, PartialEq)]
#[webidl(enum)]
pub enum GPUFeatureName {
// api
#[webidl(rename = "depth-clip-control")]
DepthClipControl,
#[webidl(rename = "timestamp-query")]
TimestampQuery,
#[webidl(rename = "indirect-first-instance")]
IndirectFirstInstance,
// shader
#[webidl(rename = "shader-f16")]
ShaderF16,
// texture formats
#[webidl(rename = "depth32float-stencil8")]
Depth32floatStencil8,
#[webidl(rename = "texture-compression-bc")]
TextureCompressionBc,
#[webidl(rename = "texture-compression-bc-sliced-3d")]
TextureCompressionBcSliced3d,
#[webidl(rename = "texture-compression-etc2")]
TextureCompressionEtc2,
#[webidl(rename = "texture-compression-astc")]
TextureCompressionAstc,
#[webidl(rename = "rg11b10ufloat-renderable")]
Rg11b10ufloatRenderable,
#[webidl(rename = "bgra8unorm-storage")]
Bgra8unormStorage,
#[webidl(rename = "float32-filterable")]
Float32Filterable,
#[webidl(rename = "dual-source-blending")]
DualSourceBlending,
// extended from spec
// texture formats
#[webidl(rename = "texture-format-16-bit-norm")]
TextureFormat16BitNorm,
#[webidl(rename = "texture-compression-astc-hdr")]
TextureCompressionAstcHdr,
#[webidl(rename = "texture-adapter-specific-format-features")]
TextureAdapterSpecificFormatFeatures,
// api
#[webidl(rename = "pipeline-statistics-query")]
PipelineStatisticsQuery,
#[webidl(rename = "timestamp-query-inside-passes")]
TimestampQueryInsidePasses,
#[webidl(rename = "mappable-primary-buffers")]
MappablePrimaryBuffers,
#[webidl(rename = "texture-binding-array")]
TextureBindingArray,
#[webidl(rename = "buffer-binding-array")]
BufferBindingArray,
#[webidl(rename = "storage-resource-binding-array")]
StorageResourceBindingArray,
#[webidl(
rename = "sampled-texture-and-storage-buffer-array-non-uniform-indexing"
)]
SampledTextureAndStorageBufferArrayNonUniformIndexing,
#[webidl(
rename = "uniform-buffer-and-storage-texture-array-non-uniform-indexing"
)]
UniformBufferAndStorageTextureArrayNonUniformIndexing,
#[webidl(rename = "partially-bound-binding-array")]
PartiallyBoundBindingArray,
#[webidl(rename = "multi-draw-indirect")]
MultiDrawIndirect,
#[webidl(rename = "multi-draw-indirect-count")]
MultiDrawIndirectCount,
#[webidl(rename = "push-constants")]
PushConstants,
#[webidl(rename = "address-mode-clamp-to-zero")]
AddressModeClampToZero,
#[webidl(rename = "address-mode-clamp-to-border")]
AddressModeClampToBorder,
#[webidl(rename = "polygon-mode-line")]
PolygonModeLine,
#[webidl(rename = "polygon-mode-point")]
PolygonModePoint,
#[webidl(rename = "conservative-rasterization")]
ConservativeRasterization,
#[webidl(rename = "vertex-writable-storage")]
VertexWritableStorage,
#[webidl(rename = "clear-texture")]
ClearTexture,
#[webidl(rename = "spirv-shader-passthrough")]
SpirvShaderPassthrough,
#[webidl(rename = "multiview")]
Multiview,
#[webidl(rename = "vertex-attribute-64-bit")]
VertexAttribute64Bit,
// shader
#[webidl(rename = "shader-f64")]
ShaderF64,
#[webidl(rename = "shader-i16")]
ShaderI16,
#[webidl(rename = "shader-primitive-index")]
ShaderPrimitiveIndex,
#[webidl(rename = "shader-early-depth-test")]
ShaderEarlyDepthTest,
}
pub fn feature_names_to_features(
names: Vec<GPUFeatureName>,
) -> wgpu_types::Features {
use wgpu_types::Features;
let mut features = Features::empty();
for name in names {
#[rustfmt::skip]
let feature = match name {
GPUFeatureName::DepthClipControl => Features::DEPTH_CLIP_CONTROL,
GPUFeatureName::TimestampQuery => Features::TIMESTAMP_QUERY,
GPUFeatureName::IndirectFirstInstance => Features::INDIRECT_FIRST_INSTANCE,
GPUFeatureName::ShaderF16 => Features::SHADER_F16,
GPUFeatureName::Depth32floatStencil8 => Features::DEPTH32FLOAT_STENCIL8,
GPUFeatureName::TextureCompressionBc => Features::TEXTURE_COMPRESSION_BC,
GPUFeatureName::TextureCompressionBcSliced3d => Features::TEXTURE_COMPRESSION_BC_SLICED_3D,
GPUFeatureName::TextureCompressionEtc2 => Features::TEXTURE_COMPRESSION_ETC2,
GPUFeatureName::TextureCompressionAstc => Features::TEXTURE_COMPRESSION_ASTC,
GPUFeatureName::Rg11b10ufloatRenderable => Features::RG11B10UFLOAT_RENDERABLE,
GPUFeatureName::Bgra8unormStorage => Features::BGRA8UNORM_STORAGE,
GPUFeatureName::Float32Filterable => Features::FLOAT32_FILTERABLE,
GPUFeatureName::DualSourceBlending => Features::DUAL_SOURCE_BLENDING,
GPUFeatureName::TextureFormat16BitNorm => Features::TEXTURE_FORMAT_16BIT_NORM,
GPUFeatureName::TextureCompressionAstcHdr => Features::TEXTURE_COMPRESSION_ASTC_HDR,
GPUFeatureName::TextureAdapterSpecificFormatFeatures => Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES,
GPUFeatureName::PipelineStatisticsQuery => Features::PIPELINE_STATISTICS_QUERY,
GPUFeatureName::TimestampQueryInsidePasses => Features::TIMESTAMP_QUERY_INSIDE_PASSES,
GPUFeatureName::MappablePrimaryBuffers => Features::MAPPABLE_PRIMARY_BUFFERS,
GPUFeatureName::TextureBindingArray => Features::TEXTURE_BINDING_ARRAY,
GPUFeatureName::BufferBindingArray => Features::BUFFER_BINDING_ARRAY,
GPUFeatureName::StorageResourceBindingArray => Features::STORAGE_RESOURCE_BINDING_ARRAY,
GPUFeatureName::SampledTextureAndStorageBufferArrayNonUniformIndexing => Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING,
GPUFeatureName::UniformBufferAndStorageTextureArrayNonUniformIndexing => Features::UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING,
GPUFeatureName::PartiallyBoundBindingArray => Features::PARTIALLY_BOUND_BINDING_ARRAY,
GPUFeatureName::MultiDrawIndirect => Features::MULTI_DRAW_INDIRECT,
GPUFeatureName::MultiDrawIndirectCount => Features::MULTI_DRAW_INDIRECT_COUNT,
GPUFeatureName::PushConstants => Features::PUSH_CONSTANTS,
GPUFeatureName::AddressModeClampToZero => Features::ADDRESS_MODE_CLAMP_TO_ZERO,
GPUFeatureName::AddressModeClampToBorder => Features::ADDRESS_MODE_CLAMP_TO_BORDER,
GPUFeatureName::PolygonModeLine => Features::POLYGON_MODE_LINE,
GPUFeatureName::PolygonModePoint => Features::POLYGON_MODE_POINT,
GPUFeatureName::ConservativeRasterization => Features::CONSERVATIVE_RASTERIZATION,
GPUFeatureName::VertexWritableStorage => Features::VERTEX_WRITABLE_STORAGE,
GPUFeatureName::ClearTexture => Features::CLEAR_TEXTURE,
GPUFeatureName::SpirvShaderPassthrough => Features::SPIRV_SHADER_PASSTHROUGH,
GPUFeatureName::Multiview => Features::MULTIVIEW,
GPUFeatureName::VertexAttribute64Bit => Features::VERTEX_ATTRIBUTE_64BIT,
GPUFeatureName::ShaderF64 => Features::SHADER_F64,
GPUFeatureName::ShaderI16 => Features::SHADER_F16,
GPUFeatureName::ShaderPrimitiveIndex => Features::SHADER_PRIMITIVE_INDEX,
GPUFeatureName::ShaderEarlyDepthTest => Features::SHADER_EARLY_DEPTH_TEST,
};
features.set(feature, true);
}
features
}
pub fn features_to_feature_names(
features: wgpu_types::Features,
) -> HashSet<GPUFeatureName> {
use GPUFeatureName::*;
let mut return_features = HashSet::new();
// api
if features.contains(wgpu_types::Features::DEPTH_CLIP_CONTROL) {
return_features.insert(DepthClipControl);
}
if features.contains(wgpu_types::Features::TIMESTAMP_QUERY) {
return_features.insert(TimestampQuery);
}
if features.contains(wgpu_types::Features::INDIRECT_FIRST_INSTANCE) {
return_features.insert(IndirectFirstInstance);
}
// shader
if features.contains(wgpu_types::Features::SHADER_F16) {
return_features.insert(ShaderF16);
}
// texture formats
if features.contains(wgpu_types::Features::DEPTH32FLOAT_STENCIL8) {
return_features.insert(Depth32floatStencil8);
}
if features.contains(wgpu_types::Features::TEXTURE_COMPRESSION_BC) {
return_features.insert(TextureCompressionBc);
}
if features.contains(wgpu_types::Features::TEXTURE_COMPRESSION_BC_SLICED_3D) {
return_features.insert(TextureCompressionBcSliced3d);
}
if features.contains(wgpu_types::Features::TEXTURE_COMPRESSION_ETC2) {
return_features.insert(TextureCompressionEtc2);
}
if features.contains(wgpu_types::Features::TEXTURE_COMPRESSION_ASTC) {
return_features.insert(TextureCompressionAstc);
}
if features.contains(wgpu_types::Features::RG11B10UFLOAT_RENDERABLE) {
return_features.insert(Rg11b10ufloatRenderable);
}
if features.contains(wgpu_types::Features::BGRA8UNORM_STORAGE) {
return_features.insert(Bgra8unormStorage);
}
if features.contains(wgpu_types::Features::FLOAT32_FILTERABLE) {
return_features.insert(Float32Filterable);
}
if features.contains(wgpu_types::Features::DUAL_SOURCE_BLENDING) {
return_features.insert(DualSourceBlending);
}
// extended from spec
// texture formats
if features.contains(wgpu_types::Features::TEXTURE_FORMAT_16BIT_NORM) {
return_features.insert(TextureFormat16BitNorm);
}
if features.contains(wgpu_types::Features::TEXTURE_COMPRESSION_ASTC_HDR) {
return_features.insert(TextureCompressionAstcHdr);
}
if features
.contains(wgpu_types::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES)
{
return_features.insert(TextureAdapterSpecificFormatFeatures);
}
// api
if features.contains(wgpu_types::Features::PIPELINE_STATISTICS_QUERY) {
return_features.insert(PipelineStatisticsQuery);
}
if features.contains(wgpu_types::Features::TIMESTAMP_QUERY_INSIDE_PASSES) {
return_features.insert(TimestampQueryInsidePasses);
}
if features.contains(wgpu_types::Features::MAPPABLE_PRIMARY_BUFFERS) {
return_features.insert(MappablePrimaryBuffers);
}
if features.contains(wgpu_types::Features::TEXTURE_BINDING_ARRAY) {
return_features.insert(TextureBindingArray);
}
if features.contains(wgpu_types::Features::BUFFER_BINDING_ARRAY) {
return_features.insert(BufferBindingArray);
}
if features.contains(wgpu_types::Features::STORAGE_RESOURCE_BINDING_ARRAY) {
return_features.insert(StorageResourceBindingArray);
}
if features.contains(
wgpu_types::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING,
) {
return_features.insert(SampledTextureAndStorageBufferArrayNonUniformIndexing);
}
if features.contains(
wgpu_types::Features::UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING,
) {
return_features.insert(UniformBufferAndStorageTextureArrayNonUniformIndexing);
}
if features.contains(wgpu_types::Features::PARTIALLY_BOUND_BINDING_ARRAY) {
return_features.insert(PartiallyBoundBindingArray);
}
if features.contains(wgpu_types::Features::MULTI_DRAW_INDIRECT) {
return_features.insert(MultiDrawIndirect);
}
if features.contains(wgpu_types::Features::MULTI_DRAW_INDIRECT_COUNT) {
return_features.insert(MultiDrawIndirectCount);
}
if features.contains(wgpu_types::Features::PUSH_CONSTANTS) {
return_features.insert(PushConstants);
}
if features.contains(wgpu_types::Features::ADDRESS_MODE_CLAMP_TO_ZERO) {
return_features.insert(AddressModeClampToZero);
}
if features.contains(wgpu_types::Features::ADDRESS_MODE_CLAMP_TO_BORDER) {
return_features.insert(AddressModeClampToBorder);
}
if features.contains(wgpu_types::Features::POLYGON_MODE_LINE) {
return_features.insert(PolygonModeLine);
}
if features.contains(wgpu_types::Features::POLYGON_MODE_POINT) {
return_features.insert(PolygonModePoint);
}
if features.contains(wgpu_types::Features::CONSERVATIVE_RASTERIZATION) {
return_features.insert(ConservativeRasterization);
}
if features.contains(wgpu_types::Features::VERTEX_WRITABLE_STORAGE) {
return_features.insert(VertexWritableStorage);
}
if features.contains(wgpu_types::Features::CLEAR_TEXTURE) {
return_features.insert(ClearTexture);
}
if features.contains(wgpu_types::Features::SPIRV_SHADER_PASSTHROUGH) {
return_features.insert(SpirvShaderPassthrough);
}
if features.contains(wgpu_types::Features::MULTIVIEW) {
return_features.insert(Multiview);
}
if features.contains(wgpu_types::Features::VERTEX_ATTRIBUTE_64BIT) {
return_features.insert(VertexAttribute64Bit);
}
// shader
if features.contains(wgpu_types::Features::SHADER_F64) {
return_features.insert(ShaderF64);
}
if features.contains(wgpu_types::Features::SHADER_I16) {
return_features.insert(ShaderI16);
}
if features.contains(wgpu_types::Features::SHADER_PRIMITIVE_INDEX) {
return_features.insert(ShaderPrimitiveIndex);
}
if features.contains(wgpu_types::Features::SHADER_EARLY_DEPTH_TEST) {
return_features.insert(ShaderEarlyDepthTest);
}
return_features
}

View file

@ -38,6 +38,7 @@ const {
MathRound,
MathTrunc,
Number,
SymbolFor,
NumberIsFinite,
NumberIsNaN,
NumberMAX_SAFE_INTEGER,
@ -1311,18 +1312,17 @@ function configureProperties(obj) {
}
}
const setlikeInner = Symbol("[[set]]");
const setlikeInner = SymbolFor("setlike_set");
// Ref: https://webidl.spec.whatwg.org/#es-setlike
function setlike(obj, objPrototype, readonly) {
ObjectDefineProperties(obj, {
ObjectDefineProperties(objPrototype, {
size: {
__proto__: null,
configurable: true,
enumerable: true,
get() {
assertBranded(this, objPrototype);
return obj[setlikeInner].size;
return this[setlikeInner].size;
},
},
[SymbolIterator]: {
@ -1331,8 +1331,7 @@ function setlike(obj, objPrototype, readonly) {
enumerable: false,
writable: true,
value() {
assertBranded(this, objPrototype);
return obj[setlikeInner][SymbolIterator]();
return this[setlikeInner][SymbolIterator]();
},
},
entries: {
@ -1341,8 +1340,7 @@ function setlike(obj, objPrototype, readonly) {
enumerable: true,
writable: true,
value() {
assertBranded(this, objPrototype);
return SetPrototypeEntries(obj[setlikeInner]);
return SetPrototypeEntries(this[setlikeInner]);
},
},
keys: {
@ -1351,8 +1349,7 @@ function setlike(obj, objPrototype, readonly) {
enumerable: true,
writable: true,
value() {
assertBranded(this, objPrototype);
return SetPrototypeKeys(obj[setlikeInner]);
return SetPrototypeKeys(this[setlikeInner]);
},
},
values: {
@ -1361,8 +1358,7 @@ function setlike(obj, objPrototype, readonly) {
enumerable: true,
writable: true,
value() {
assertBranded(this, objPrototype);
return SetPrototypeValues(obj[setlikeInner]);
return SetPrototypeValues(this[setlikeInner]);
},
},
forEach: {
@ -1371,8 +1367,7 @@ function setlike(obj, objPrototype, readonly) {
enumerable: true,
writable: true,
value(callbackfn, thisArg) {
assertBranded(this, objPrototype);
return SetPrototypeForEach(obj[setlikeInner], callbackfn, thisArg);
return SetPrototypeForEach(this[setlikeInner], callbackfn, thisArg);
},
},
has: {
@ -1381,22 +1376,20 @@ function setlike(obj, objPrototype, readonly) {
enumerable: true,
writable: true,
value(value) {
assertBranded(this, objPrototype);
return SetPrototypeHas(obj[setlikeInner], value);
return SetPrototypeHas(this[setlikeInner], value);
},
},
});
if (!readonly) {
ObjectDefineProperties(obj, {
ObjectDefineProperties(objPrototype, {
add: {
__proto__: null,
configurable: true,
enumerable: true,
writable: true,
value(value) {
assertBranded(this, objPrototype);
return SetPrototypeAdd(obj[setlikeInner], value);
return SetPrototypeAdd(this[setlikeInner], value);
},
},
delete: {
@ -1405,8 +1398,7 @@ function setlike(obj, objPrototype, readonly) {
enumerable: true,
writable: true,
value(value) {
assertBranded(this, objPrototype);
return SetPrototypeDelete(obj[setlikeInner], value);
return SetPrototypeDelete(this[setlikeInner], value);
},
},
clear: {
@ -1415,8 +1407,7 @@ function setlike(obj, objPrototype, readonly) {
enumerable: true,
writable: true,
value() {
assertBranded(this, objPrototype);
return SetPrototypeClear(obj[setlikeInner]);
return SetPrototypeClear(this[setlikeInner]);
},
},
});

View file

@ -66,6 +66,7 @@ ObjectDefineProperties(Navigator.prototype, {
get() {
webidl.assertBranded(this, NavigatorPrototype);
const webgpu = loadWebGPU();
webgpu.initGPU();
return webgpu.gpu;
},
},

View file

@ -64,6 +64,7 @@ ObjectDefineProperties(WorkerNavigator.prototype, {
get() {
webidl.assertBranded(this, WorkerNavigatorPrototype);
const webgpu = loadWebGPU();
webgpu.initGPU();
return webgpu.gpu;
},
},