Detailed changes
@@ -754,17 +754,6 @@ dependencies = [
"libloading",
]
-[[package]]
-name = "ash-window"
-version = "0.13.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52bca67b61cb81e5553babde81b8211f713cb6db79766f80168f3e5f40ea6c82"
-dependencies = [
- "ash",
- "raw-window-handle",
- "raw-window-metal",
-]
-
[[package]]
name = "ashpd"
version = "0.12.1"
@@ -2151,61 +2140,6 @@ dependencies = [
"wyz",
]
-[[package]]
-name = "blade-graphics"
-version = "0.7.0"
-source = "git+https://github.com/kvark/blade?rev=e3cf011ca18a6dfd907d1dedd93e85e21f005fe3#e3cf011ca18a6dfd907d1dedd93e85e21f005fe3"
-dependencies = [
- "ash",
- "ash-window",
- "bitflags 2.10.0",
- "bytemuck",
- "codespan-reporting 0.12.0",
- "glow",
- "gpu-alloc",
- "gpu-alloc-ash",
- "hidden-trait",
- "js-sys",
- "khronos-egl",
- "libloading",
- "log",
- "mint",
- "naga",
- "objc2",
- "objc2-app-kit",
- "objc2-core-foundation",
- "objc2-foundation",
- "objc2-metal",
- "objc2-quartz-core",
- "objc2-ui-kit",
- "once_cell",
- "raw-window-handle",
- "slab",
- "wasm-bindgen",
- "web-sys",
-]
-
-[[package]]
-name = "blade-macros"
-version = "0.3.0"
-source = "git+https://github.com/kvark/blade?rev=e3cf011ca18a6dfd907d1dedd93e85e21f005fe3#e3cf011ca18a6dfd907d1dedd93e85e21f005fe3"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.106",
-]
-
-[[package]]
-name = "blade-util"
-version = "0.3.0"
-source = "git+https://github.com/kvark/blade?rev=e3cf011ca18a6dfd907d1dedd93e85e21f005fe3#e3cf011ca18a6dfd907d1dedd93e85e21f005fe3"
-dependencies = [
- "blade-graphics",
- "bytemuck",
- "log",
- "profiling",
-]
-
[[package]]
name = "block"
version = "0.1.6"
@@ -3900,7 +3834,7 @@ dependencies = [
"core-graphics2",
"io-surface",
"libc",
- "metal",
+ "metal 0.29.0",
]
[[package]]
@@ -5117,6 +5051,15 @@ dependencies = [
"zlog",
]
+[[package]]
+name = "document-features"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61"
+dependencies = [
+ "litrs",
+]
+
[[package]]
name = "documented"
version = "0.9.2"
@@ -7092,7 +7035,7 @@ dependencies = [
"serde",
"serde_json",
"serde_yaml",
- "strum_macros 0.27.2",
+ "strum_macros",
]
[[package]]
@@ -7294,6 +7237,17 @@ dependencies = [
"ztracing",
]
+[[package]]
+name = "gl_generator"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d"
+dependencies = [
+ "khronos_api",
+ "log",
+ "xml-rs",
+]
+
[[package]]
name = "glob"
version = "0.3.3"
@@ -7337,6 +7291,15 @@ dependencies = [
"web-sys",
]
+[[package]]
+name = "glutin_wgl_sys"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e"
+dependencies = [
+ "gl_generator",
+]
+
[[package]]
name = "go_to_line"
version = "0.1.0"
@@ -7386,31 +7349,35 @@ dependencies = [
]
[[package]]
-name = "gpu-alloc"
-version = "0.6.0"
+name = "gpu-allocator"
+version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171"
+checksum = "51255ea7cfaadb6c5f1528d43e92a82acb2b96c43365989a28b2d44ee38f8795"
dependencies = [
- "bitflags 2.10.0",
- "gpu-alloc-types",
+ "ash",
+ "hashbrown 0.16.1",
+ "log",
+ "presser",
+ "thiserror 2.0.17",
+ "windows 0.61.3",
]
[[package]]
-name = "gpu-alloc-ash"
-version = "0.7.0"
+name = "gpu-descriptor"
+version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cbda7a18a29bc98c2e0de0435c347df935bf59489935d0cbd0b73f1679b6f79a"
+checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca"
dependencies = [
- "ash",
- "gpu-alloc-types",
- "tinyvec",
+ "bitflags 2.10.0",
+ "gpu-descriptor-types",
+ "hashbrown 0.15.5",
]
[[package]]
-name = "gpu-alloc-types"
-version = "0.3.0"
+name = "gpu-descriptor-types"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4"
+checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91"
dependencies = [
"bitflags 2.10.0",
]
@@ -7426,9 +7393,6 @@ dependencies = [
"backtrace",
"bindgen 0.71.1",
"bitflags 2.10.0",
- "blade-graphics",
- "blade-macros",
- "blade-util",
"block",
"bytemuck",
"calloop",
@@ -7463,7 +7427,7 @@ dependencies = [
"lyon",
"mach2 0.5.0",
"media",
- "metal",
+ "metal 0.29.0",
"naga",
"num_cpus",
"objc",
@@ -7511,9 +7475,10 @@ dependencies = [
"wayland-protocols",
"wayland-protocols-plasma",
"wayland-protocols-wlr",
+ "wgpu",
"windows 0.61.3",
"windows-core 0.61.2",
- "windows-numerics",
+ "windows-numerics 0.2.0",
"windows-registry 0.5.3",
"x11-clipboard",
"x11rb",
@@ -7820,17 +7785,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
-[[package]]
-name = "hidden-trait"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68ed9e850438ac849bec07e7d09fbe9309cbd396a5988c30b010580ce08860df"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 1.0.109",
-]
-
[[package]]
name = "hkdf"
version = "0.12.4"
@@ -8915,8 +8869,15 @@ checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76"
dependencies = [
"libc",
"libloading",
+ "pkg-config",
]
+[[package]]
+name = "khronos_api"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
+
[[package]]
name = "kqueue"
version = "1.1.1"
@@ -9492,6 +9453,12 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
+[[package]]
+name = "litrs"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
+
[[package]]
name = "livekit"
version = "0.7.8"
@@ -10021,7 +9988,7 @@ dependencies = [
"core-video",
"ctor",
"foreign-types 0.5.0",
- "metal",
+ "metal 0.29.0",
"objc",
]
@@ -10103,6 +10070,21 @@ dependencies = [
"paste",
]
+[[package]]
+name = "metal"
+version = "0.33.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7047791b5bc903b8cd963014b355f71dc9864a9a0b727057676c1dcae5cbc15"
+dependencies = [
+ "bitflags 2.10.0",
+ "block",
+ "core-graphics-types 0.2.0",
+ "foreign-types 0.5.0",
+ "log",
+ "objc",
+ "paste",
+]
+
[[package]]
name = "migrator"
version = "0.1.0"
@@ -10232,12 +10214,6 @@ dependencies = [
"simd-adler32",
]
-[[package]]
-name = "mint"
-version = "0.5.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff"
-
[[package]]
name = "mio"
version = "0.8.11"
@@ -10368,25 +10344,26 @@ checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084"
[[package]]
name = "naga"
-version = "25.0.1"
+version = "28.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b977c445f26e49757f9aca3631c3b8b836942cb278d69a92e7b80d3b24da632"
+checksum = "618f667225063219ddfc61251087db8a9aec3c3f0950c916b614e403486f1135"
dependencies = [
"arrayvec",
"bit-set",
"bitflags 2.10.0",
+ "cfg-if",
"cfg_aliases 0.2.1",
"codespan-reporting 0.12.0",
"half",
- "hashbrown 0.15.5",
+ "hashbrown 0.16.1",
"hexf-parse",
"indexmap",
+ "libm",
"log",
"num-traits",
"once_cell",
"rustc-hash 1.1.0",
"spirv",
- "strum 0.26.3",
"thiserror 2.0.17",
"unicode-ident",
]
@@ -10891,19 +10868,6 @@ dependencies = [
"objc2-encode",
]
-[[package]]
-name = "objc2-app-kit"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc"
-dependencies = [
- "bitflags 2.10.0",
- "objc2",
- "objc2-core-foundation",
- "objc2-foundation",
- "objc2-quartz-core",
-]
-
[[package]]
name = "objc2-audio-toolbox"
version = "0.3.1"
@@ -11008,32 +10972,6 @@ dependencies = [
"objc2-foundation",
]
-[[package]]
-name = "objc2-quartz-core"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90ffb6a0cd5f182dc964334388560b12a57f7b74b3e2dec5e2722aa2dfb2ccd5"
-dependencies = [
- "bitflags 2.10.0",
- "objc2",
- "objc2-core-foundation",
- "objc2-foundation",
- "objc2-metal",
-]
-
-[[package]]
-name = "objc2-ui-kit"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25b1312ad7bc8a0e92adae17aa10f90aae1fb618832f9b993b022b591027daed"
-dependencies = [
- "bitflags 2.10.0",
- "objc2",
- "objc2-core-foundation",
- "objc2-foundation",
- "objc2-quartz-core",
-]
-
[[package]]
name = "objc_exception"
version = "0.1.2"
@@ -12569,6 +12507,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
+[[package]]
+name = "presser"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa"
+
[[package]]
name = "prettier"
version = "0.1.0"
@@ -13357,6 +13301,12 @@ dependencies = [
"rand 0.9.2",
]
+[[package]]
+name = "range-alloc"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d6831663a5098ea164f89cff59c6284e95f4e3c76ce9848d4529f5ccca9bde"
+
[[package]]
name = "range-map"
version = "0.2.0"
@@ -13446,18 +13396,6 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
-[[package]]
-name = "raw-window-metal"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76e8caa82e31bb98fee12fa8f051c94a6aa36b07cddb03f0d4fc558988360ff1"
-dependencies = [
- "cocoa 0.25.0",
- "core-graphics 0.23.2",
- "objc",
- "raw-window-handle",
-]
-
[[package]]
name = "rayon"
version = "1.11.0"
@@ -13836,6 +13774,12 @@ dependencies = [
"bytecheck",
]
+[[package]]
+name = "renderdoc-sys"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
+
[[package]]
name = "repl"
version = "0.1.0"
@@ -16119,9 +16063,6 @@ name = "strum"
version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
-dependencies = [
- "strum_macros 0.26.4",
-]
[[package]]
name = "strum"
@@ -16129,20 +16070,7 @@ version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
dependencies = [
- "strum_macros 0.27.2",
-]
-
-[[package]]
-name = "strum_macros"
-version = "0.26.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
-dependencies = [
- "heck 0.5.0",
- "proc-macro2",
- "quote",
- "rustversion",
- "syn 2.0.106",
+ "strum_macros",
]
[[package]]
@@ -19510,6 +19438,156 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3"
+[[package]]
+name = "wgpu"
+version = "28.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9cb534d5ffd109c7d1135f34cdae29e60eab94855a625dcfe1705f8bc7ad79f"
+dependencies = [
+ "arrayvec",
+ "bitflags 2.10.0",
+ "bytemuck",
+ "cfg-if",
+ "cfg_aliases 0.2.1",
+ "document-features",
+ "hashbrown 0.16.1",
+ "js-sys",
+ "log",
+ "naga",
+ "parking_lot",
+ "portable-atomic",
+ "profiling",
+ "raw-window-handle",
+ "smallvec",
+ "static_assertions",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "wgpu-core",
+ "wgpu-hal",
+ "wgpu-types",
+]
+
+[[package]]
+name = "wgpu-core"
+version = "28.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8bb4c8b5db5f00e56f1f08869d870a0dff7c8bc7ebc01091fec140b0cf0211a9"
+dependencies = [
+ "arrayvec",
+ "bit-set",
+ "bit-vec",
+ "bitflags 2.10.0",
+ "bytemuck",
+ "cfg_aliases 0.2.1",
+ "document-features",
+ "hashbrown 0.16.1",
+ "indexmap",
+ "log",
+ "naga",
+ "once_cell",
+ "parking_lot",
+ "portable-atomic",
+ "profiling",
+ "raw-window-handle",
+ "rustc-hash 1.1.0",
+ "smallvec",
+ "thiserror 2.0.17",
+ "wgpu-core-deps-apple",
+ "wgpu-core-deps-emscripten",
+ "wgpu-core-deps-windows-linux-android",
+ "wgpu-hal",
+ "wgpu-types",
+]
+
+[[package]]
+name = "wgpu-core-deps-apple"
+version = "28.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87b7b696b918f337c486bf93142454080a32a37832ba8a31e4f48221890047da"
+dependencies = [
+ "wgpu-hal",
+]
+
+[[package]]
+name = "wgpu-core-deps-emscripten"
+version = "28.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34b251c331f84feac147de3c4aa3aa45112622a95dd7ee1b74384fa0458dbd79"
+dependencies = [
+ "wgpu-hal",
+]
+
+[[package]]
+name = "wgpu-core-deps-windows-linux-android"
+version = "28.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68ca976e72b2c9964eb243e281f6ce7f14a514e409920920dcda12ae40febaae"
+dependencies = [
+ "wgpu-hal",
+]
+
+[[package]]
+name = "wgpu-hal"
+version = "28.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "293080d77fdd14d6b08a67c5487dfddbf874534bb7921526db56a7b75d7e3bef"
+dependencies = [
+ "android_system_properties",
+ "arrayvec",
+ "ash",
+ "bit-set",
+ "bitflags 2.10.0",
+ "block",
+ "bytemuck",
+ "cfg-if",
+ "cfg_aliases 0.2.1",
+ "core-graphics-types 0.2.0",
+ "glow",
+ "glutin_wgl_sys",
+ "gpu-allocator",
+ "gpu-descriptor",
+ "hashbrown 0.16.1",
+ "js-sys",
+ "khronos-egl",
+ "libc",
+ "libloading",
+ "log",
+ "metal 0.33.0",
+ "naga",
+ "ndk-sys",
+ "objc",
+ "once_cell",
+ "ordered-float 4.6.0",
+ "parking_lot",
+ "portable-atomic",
+ "portable-atomic-util",
+ "profiling",
+ "range-alloc",
+ "raw-window-handle",
+ "renderdoc-sys",
+ "smallvec",
+ "thiserror 2.0.17",
+ "wasm-bindgen",
+ "web-sys",
+ "wgpu-types",
+ "windows 0.62.2",
+ "windows-core 0.62.2",
+]
+
+[[package]]
+name = "wgpu-types"
+version = "28.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e18308757e594ed2cd27dddbb16a139c42a683819d32a2e0b1b0167552f5840c"
+dependencies = [
+ "bitflags 2.10.0",
+ "bytemuck",
+ "js-sys",
+ "log",
+ "web-sys",
+]
+
[[package]]
name = "which"
version = "4.4.2"
@@ -19675,11 +19753,23 @@ version = "0.61.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
dependencies = [
- "windows-collections",
+ "windows-collections 0.2.0",
"windows-core 0.61.2",
- "windows-future",
+ "windows-future 0.2.1",
"windows-link 0.1.3",
- "windows-numerics",
+ "windows-numerics 0.2.0",
+]
+
+[[package]]
+name = "windows"
+version = "0.62.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
+dependencies = [
+ "windows-collections 0.3.2",
+ "windows-core 0.62.2",
+ "windows-future 0.3.2",
+ "windows-numerics 0.3.1",
]
[[package]]
@@ -19693,7 +19783,7 @@ dependencies = [
"rayon",
"thiserror 2.0.17",
"windows 0.61.3",
- "windows-future",
+ "windows-future 0.2.1",
]
[[package]]
@@ -19705,6 +19795,15 @@ dependencies = [
"windows-core 0.61.2",
]
+[[package]]
+name = "windows-collections"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
+dependencies = [
+ "windows-core 0.62.2",
+]
+
[[package]]
name = "windows-core"
version = "0.57.0"
@@ -19764,7 +19863,18 @@ checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
dependencies = [
"windows-core 0.61.2",
"windows-link 0.1.3",
- "windows-threading",
+ "windows-threading 0.1.0",
+]
+
+[[package]]
+name = "windows-future"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
+dependencies = [
+ "windows-core 0.62.2",
+ "windows-link 0.2.1",
+ "windows-threading 0.2.1",
]
[[package]]
@@ -19855,6 +19965,16 @@ dependencies = [
"windows-link 0.1.3",
]
+[[package]]
+name = "windows-numerics"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
+dependencies = [
+ "windows-core 0.62.2",
+ "windows-link 0.2.1",
+]
+
[[package]]
name = "windows-registry"
version = "0.4.0"
@@ -20087,6 +20207,15 @@ dependencies = [
"windows-link 0.1.3",
]
+[[package]]
+name = "windows-threading"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
+dependencies = [
+ "windows-link 0.2.1",
+]
+
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
@@ -20795,6 +20924,12 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
+[[package]]
+name = "xml-rs"
+version = "0.8.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f"
+
[[package]]
name = "xml5ever"
version = "0.18.1"
@@ -284,7 +284,7 @@ collections = { path = "crates/collections", version = "0.1.0" }
command_palette = { path = "crates/command_palette" }
command_palette_hooks = { path = "crates/command_palette_hooks" }
component = { path = "crates/component" }
-component_preview = { path = "crates/component_preview" }
+component_preview = { path = "crates/component_preview" }
context_server = { path = "crates/context_server" }
copilot = { path = "crates/copilot" }
copilot_chat = { path = "crates/copilot_chat" }
@@ -466,7 +466,9 @@ alacritty_terminal = { git = "https://github.com/zed-industries/alacritty", rev
any_vec = "0.14"
anyhow = "1.0.86"
arrayvec = { version = "0.7.4", features = ["serde"] }
-ashpd = { version = "0.12.1", default-features = false, features = ["async-std"] }
+ashpd = { version = "0.12.1", default-features = false, features = [
+ "async-std",
+] }
async-compat = "0.2.1"
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
async-dispatcher = "0.1"
@@ -492,9 +494,6 @@ backtrace = "0.3"
base64 = "0.22"
bincode = "1.2.1"
bitflags = "2.6.0"
-blade-graphics = { git = "https://github.com/kvark/blade", rev = "e3cf011ca18a6dfd907d1dedd93e85e21f005fe3" }
-blade-macros = { git = "https://github.com/kvark/blade", rev = "e3cf011ca18a6dfd907d1dedd93e85e21f005fe3" }
-blade-util = { git = "https://github.com/kvark/blade", rev = "e3cf011ca18a6dfd907d1dedd93e85e21f005fe3" }
brotli = "8.0.2"
bytes = "1.0"
cargo_metadata = "0.19"
@@ -565,7 +564,7 @@ markup5ever_rcdom = "0.3.0"
metal = "0.29"
minidumper = "0.8"
moka = { version = "0.12.10", features = ["sync"] }
-naga = { version = "25.0", features = ["wgsl-in"] }
+naga = { version = "28.0", features = ["wgsl-in"] }
nanoid = "0.4"
nbformat = "1.0.0"
nix = "0.29"
@@ -594,7 +593,7 @@ objc2-foundation = { version = "=0.3.1", default-features = false, features = [
"NSUndoManager",
"NSValue",
"objc2-core-foundation",
- "std"
+ "std",
] }
open = "5.0.0"
ordered-float = "2.1.1"
@@ -687,9 +686,16 @@ time = { version = "0.3", features = [
tiny_http = "0.8"
tokio = { version = "1" }
tokio-tungstenite = { version = "0.26", features = ["__rustls-tls"] }
-tokio-socks = { version = "0.5.2", default-features = false, features = ["futures-io", "tokio"] }
+tokio-socks = { version = "0.5.2", default-features = false, features = [
+ "futures-io",
+ "tokio",
+] }
toml = "0.8"
-toml_edit = { version = "0.22", default-features = false, features = ["display", "parse", "serde"] }
+toml_edit = { version = "0.22", default-features = false, features = [
+ "display",
+ "parse",
+ "serde",
+] }
tower-http = "0.4.4"
tree-sitter = { version = "0.26", features = ["wasm"] }
tree-sitter-bash = "0.25.1"
@@ -738,6 +744,7 @@ wasmtime = { version = "33", default-features = false, features = [
wasmtime-wasi = "33"
wax = "0.7"
which = "6.0.0"
+wgpu = "28.0"
windows-core = "0.61"
yawc = "0.2.5"
zeroize = "1.8"
@@ -29,19 +29,9 @@ test-support = [
inspector = ["gpui_macros/inspector"]
leak-detection = ["backtrace"]
runtime_shaders = []
-macos-blade = [
- "blade-graphics",
- "blade-macros",
- "blade-util",
- "bytemuck",
- "objc2",
- "objc2-metal",
-]
wayland = [
"bitflags",
- "blade-graphics",
- "blade-macros",
- "blade-util",
+ "wgpu",
"bytemuck",
"ashpd/wayland",
"cosmic-text",
@@ -58,9 +48,7 @@ wayland = [
"open",
]
x11 = [
- "blade-graphics",
- "blade-macros",
- "blade-util",
+ "wgpu",
"bytemuck",
"ashpd",
"cosmic-text",
@@ -88,9 +76,6 @@ anyhow.workspace = true
async-task = "4.7"
backtrace = { workspace = true, optional = true }
bitflags = { workspace = true, optional = true }
-blade-graphics = { workspace = true, optional = true }
-blade-macros = { workspace = true, optional = true }
-blade-util = { workspace = true, optional = true }
bytemuck = { version = "1", optional = true }
collections.workspace = true
ctor.workspace = true
@@ -178,20 +163,17 @@ oo7 = { version = "0.5.0", default-features = false, features = [
# Used in both windowing options
ashpd = { workspace = true, optional = true }
-blade-graphics = { workspace = true, optional = true }
-blade-macros = { workspace = true, optional = true }
-blade-util = { workspace = true, optional = true }
-bytemuck = { version = "1", optional = true }
+wgpu = { workspace = true, optional = true }
cosmic-text = { version = "0.17.0", optional = true }
swash = { version = "0.2.6" }
# WARNING: If you change this, you must also publish a new version of zed-font-kit to crates.io
font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "110523127440aefb11ce0cf280ae7c5071337ec5", package = "zed-font-kit", version = "0.14.1-zed", features = [
"source-fontconfig-dlopen",
], optional = true }
-
-calloop = { version = "0.14.3" }
+calloop = "0.14.3"
filedescriptor = { version = "0.8.2", optional = true }
open = { version = "5.2.0", optional = true }
+xkbcommon = { version = "0.8.0", features = ["wayland", "x11"], optional = true }
# Wayland
calloop-wayland-source = { version = "0.4.1", optional = true }
@@ -224,10 +206,6 @@ x11rb = { version = "0.13.1", features = [
"resource_manager",
"sync",
], optional = true }
-xkbcommon = { version = "0.8.0", features = [
- "wayland",
- "x11",
-], optional = true }
# WARNING: If you change this, you must also publish a new version of zed-xim to crates.io
xim = { git = "https://github.com/zed-industries/xim-rs.git", rev = "16f35a2c881b815a2b6cdfd6687988e84f8447d8" , features = [
"x11rb-xcb",
@@ -1,8 +1,5 @@
#![allow(clippy::disallowed_methods, reason = "build scripts are exempt")]
-#![cfg_attr(any(not(target_os = "macos"), feature = "macos-blade"), allow(unused))]
-
-//TODO: consider generating shader code for WGSL
-//TODO: deprecate "runtime-shaders" and "macos-blade"
+#![cfg_attr(not(target_os = "macos"), allow(unused))]
use std::env;
@@ -10,12 +7,6 @@ fn main() {
let target = env::var("CARGO_CFG_TARGET_OS");
println!("cargo::rustc-check-cfg=cfg(gles)");
- #[cfg(any(
- not(any(target_os = "macos", target_os = "windows")),
- all(target_os = "macos", feature = "macos-blade")
- ))]
- check_wgsl_shaders();
-
match target.as_deref() {
Ok("macos") => {
#[cfg(target_os = "macos")]
@@ -28,32 +19,6 @@ fn main() {
_ => (),
};
}
-
-#[cfg(any(
- not(any(target_os = "macos", target_os = "windows")),
- all(target_os = "macos", feature = "macos-blade")
-))]
-fn check_wgsl_shaders() {
- use std::path::PathBuf;
- use std::process;
- use std::str::FromStr;
-
- let shader_source_path = "./src/platform/blade/shaders.wgsl";
- let shader_path = PathBuf::from_str(shader_source_path).unwrap();
- println!("cargo:rerun-if-changed={}", &shader_path.display());
-
- let shader_source = std::fs::read_to_string(&shader_path).unwrap();
-
- match naga::front::wgsl::parse_str(&shader_source) {
- Ok(_) => {
- // All clear
- }
- Err(e) => {
- println!("cargo::error=WGSL shader compilation failed:\n{}", e);
- process::exit(1);
- }
- }
-}
#[cfg(target_os = "macos")]
mod macos {
use std::{
@@ -65,15 +30,13 @@ mod macos {
pub(super) fn build() {
generate_dispatch_bindings();
- #[cfg(not(feature = "macos-blade"))]
- {
- let header_path = generate_shader_bindings();
- #[cfg(feature = "runtime_shaders")]
- emit_stitched_shaders(&header_path);
- #[cfg(not(feature = "runtime_shaders"))]
- compile_metal_shaders(&header_path);
- }
+ let header_path = generate_shader_bindings();
+
+ #[cfg(feature = "runtime_shaders")]
+ emit_stitched_shaders(&header_path);
+ #[cfg(not(feature = "runtime_shaders"))]
+ compile_metal_shaders(&header_path);
}
fn generate_dispatch_bindings() {
@@ -8,14 +8,11 @@ mod linux;
#[cfg(target_os = "macos")]
mod mac;
-#[cfg(any(
- all(
- any(target_os = "linux", target_os = "freebsd"),
- any(feature = "x11", feature = "wayland")
- ),
- all(target_os = "macos", feature = "macos-blade")
+#[cfg(all(
+ any(target_os = "linux", target_os = "freebsd"),
+ any(feature = "wayland", feature = "x11")
))]
-mod blade;
+mod wgpu;
#[cfg(any(test, feature = "test-support"))]
mod test;
@@ -28,13 +25,7 @@ mod windows;
#[cfg(all(
feature = "screen-capture",
- any(
- target_os = "windows",
- all(
- any(target_os = "linux", target_os = "freebsd"),
- any(feature = "wayland", feature = "x11"),
- )
- )
+ any(target_os = "windows", target_os = "linux", target_os = "freebsd",)
))]
pub(crate) mod scap_screen_capture;
@@ -1,11 +0,0 @@
-#[cfg(target_os = "macos")]
-mod apple_compat;
-mod blade_atlas;
-mod blade_context;
-mod blade_renderer;
-
-#[cfg(target_os = "macos")]
-pub(crate) use apple_compat::*;
-pub(crate) use blade_atlas::*;
-pub(crate) use blade_context::*;
-pub(crate) use blade_renderer::*;
@@ -1,60 +0,0 @@
-use super::{BladeContext, BladeRenderer, BladeSurfaceConfig};
-use blade_graphics as gpu;
-use std::{ffi::c_void, ptr::NonNull};
-
-#[derive(Clone)]
-pub struct Context {
- inner: BladeContext,
-}
-impl Default for Context {
- fn default() -> Self {
- Self {
- inner: BladeContext::new().unwrap(),
- }
- }
-}
-
-pub type Renderer = BladeRenderer;
-
-pub unsafe fn new_renderer(
- context: Context,
- _native_window: *mut c_void,
- native_view: *mut c_void,
- bounds: crate::Size<f32>,
- transparent: bool,
-) -> Renderer {
- use raw_window_handle as rwh;
- struct RawWindow {
- view: *mut c_void,
- }
-
- impl rwh::HasWindowHandle for RawWindow {
- fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
- let view = NonNull::new(self.view).unwrap();
- let handle = rwh::AppKitWindowHandle::new(view);
- Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) })
- }
- }
- impl rwh::HasDisplayHandle for RawWindow {
- fn display_handle(&self) -> Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
- let handle = rwh::AppKitDisplayHandle::new();
- Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
- }
- }
-
- BladeRenderer::new(
- &context.inner,
- &RawWindow {
- view: native_view as *mut _,
- },
- BladeSurfaceConfig {
- size: gpu::Extent {
- width: bounds.width as u32,
- height: bounds.height as u32,
- depth: 1,
- },
- transparent,
- },
- )
- .unwrap()
-}
@@ -1,395 +0,0 @@
-use crate::{
- AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas,
- Point, Size, platform::AtlasTextureList,
-};
-use anyhow::Result;
-use blade_graphics as gpu;
-use blade_util::{BufferBelt, BufferBeltDescriptor};
-use collections::FxHashMap;
-use etagere::BucketedAtlasAllocator;
-use parking_lot::Mutex;
-use std::{borrow::Cow, ops, sync::Arc};
-
-pub(crate) struct BladeAtlas(Mutex<BladeAtlasState>);
-
-struct PendingUpload {
- id: AtlasTextureId,
- bounds: Bounds<DevicePixels>,
- data: gpu::BufferPiece,
-}
-
-struct BladeAtlasState {
- gpu: Arc<gpu::Context>,
- upload_belt: BufferBelt,
- storage: BladeAtlasStorage,
- tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
- initializations: Vec<AtlasTextureId>,
- uploads: Vec<PendingUpload>,
-}
-
-#[cfg(gles)]
-unsafe impl Send for BladeAtlasState {}
-
-impl BladeAtlasState {
- fn destroy(&mut self) {
- self.storage.destroy(&self.gpu);
- self.upload_belt.destroy(&self.gpu);
- }
-}
-
-pub struct BladeTextureInfo {
- pub raw_view: gpu::TextureView,
-}
-
-impl BladeAtlas {
- pub(crate) fn new(gpu: &Arc<gpu::Context>) -> Self {
- BladeAtlas(Mutex::new(BladeAtlasState {
- gpu: Arc::clone(gpu),
- upload_belt: BufferBelt::new(BufferBeltDescriptor {
- memory: gpu::Memory::Upload,
- min_chunk_size: 0x10000,
- alignment: 64, // Vulkan `optimalBufferCopyOffsetAlignment` on Intel XE
- }),
- storage: BladeAtlasStorage::default(),
- tiles_by_key: Default::default(),
- initializations: Vec::new(),
- uploads: Vec::new(),
- }))
- }
-
- pub(crate) fn destroy(&self) {
- self.0.lock().destroy();
- }
-
- pub fn before_frame(&self, gpu_encoder: &mut gpu::CommandEncoder) {
- let mut lock = self.0.lock();
- lock.flush(gpu_encoder);
- }
-
- pub fn after_frame(&self, sync_point: &gpu::SyncPoint) {
- let mut lock = self.0.lock();
- lock.upload_belt.flush(sync_point);
- }
-
- pub fn get_texture_info(&self, id: AtlasTextureId) -> BladeTextureInfo {
- let lock = self.0.lock();
- let texture = &lock.storage[id];
- BladeTextureInfo {
- raw_view: texture.raw_view,
- }
- }
-}
-
-impl PlatformAtlas for BladeAtlas {
- fn get_or_insert_with<'a>(
- &self,
- key: &AtlasKey,
- build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
- ) -> Result<Option<AtlasTile>> {
- let mut lock = self.0.lock();
- if let Some(tile) = lock.tiles_by_key.get(key) {
- Ok(Some(tile.clone()))
- } else {
- profiling::scope!("new tile");
- let Some((size, bytes)) = build()? else {
- return Ok(None);
- };
- let tile = lock.allocate(size, key.texture_kind());
- lock.upload_texture(tile.texture_id, tile.bounds, &bytes);
- lock.tiles_by_key.insert(key.clone(), tile.clone());
- Ok(Some(tile))
- }
- }
-
- fn remove(&self, key: &AtlasKey) {
- let mut lock = self.0.lock();
-
- let Some(id) = lock.tiles_by_key.remove(key).map(|tile| tile.texture_id) else {
- return;
- };
-
- let Some(texture_slot) = lock.storage[id.kind].textures.get_mut(id.index as usize) else {
- return;
- };
-
- if let Some(mut texture) = texture_slot.take() {
- texture.decrement_ref_count();
- if texture.is_unreferenced() {
- lock.storage[id.kind]
- .free_list
- .push(texture.id.index as usize);
- texture.destroy(&lock.gpu);
- } else {
- *texture_slot = Some(texture);
- }
- }
- }
-}
-
-impl BladeAtlasState {
- fn allocate(&mut self, size: Size<DevicePixels>, texture_kind: AtlasTextureKind) -> AtlasTile {
- {
- let textures = &mut self.storage[texture_kind];
-
- if let Some(tile) = textures
- .iter_mut()
- .rev()
- .find_map(|texture| texture.allocate(size))
- {
- return tile;
- }
- }
-
- let texture = self.push_texture(size, texture_kind);
- texture.allocate(size).unwrap()
- }
-
- fn push_texture(
- &mut self,
- min_size: Size<DevicePixels>,
- kind: AtlasTextureKind,
- ) -> &mut BladeAtlasTexture {
- const DEFAULT_ATLAS_SIZE: Size<DevicePixels> = Size {
- width: DevicePixels(1024),
- height: DevicePixels(1024),
- };
-
- let size = min_size.max(&DEFAULT_ATLAS_SIZE);
- let format;
- let usage;
- match kind {
- AtlasTextureKind::Monochrome => {
- format = gpu::TextureFormat::R8Unorm;
- usage = gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE;
- }
- AtlasTextureKind::Subpixel => {
- format = gpu::TextureFormat::Bgra8Unorm;
- usage = gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE;
- }
- AtlasTextureKind::Polychrome => {
- format = gpu::TextureFormat::Bgra8Unorm;
- usage = gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE;
- }
- }
-
- let raw = self.gpu.create_texture(gpu::TextureDesc {
- name: "atlas",
- format,
- size: gpu::Extent {
- width: size.width.into(),
- height: size.height.into(),
- depth: 1,
- },
- array_layer_count: 1,
- mip_level_count: 1,
- sample_count: 1,
- dimension: gpu::TextureDimension::D2,
- usage,
- external: None,
- });
- let raw_view = self.gpu.create_texture_view(
- raw,
- gpu::TextureViewDesc {
- name: "",
- format,
- dimension: gpu::ViewDimension::D2,
- subresources: &Default::default(),
- },
- );
-
- let texture_list = &mut self.storage[kind];
- let index = texture_list.free_list.pop();
-
- let atlas_texture = BladeAtlasTexture {
- id: AtlasTextureId {
- index: index.unwrap_or(texture_list.textures.len()) as u32,
- kind,
- },
- allocator: etagere::BucketedAtlasAllocator::new(size.into()),
- format,
- raw,
- raw_view,
- live_atlas_keys: 0,
- };
-
- self.initializations.push(atlas_texture.id);
-
- if let Some(ix) = index {
- texture_list.textures[ix] = Some(atlas_texture);
- texture_list.textures.get_mut(ix).unwrap().as_mut().unwrap()
- } else {
- texture_list.textures.push(Some(atlas_texture));
- texture_list.textures.last_mut().unwrap().as_mut().unwrap()
- }
- }
-
- fn upload_texture(&mut self, id: AtlasTextureId, bounds: Bounds<DevicePixels>, bytes: &[u8]) {
- let data = self.upload_belt.alloc_bytes(bytes, &self.gpu);
- self.uploads.push(PendingUpload { id, bounds, data });
- }
-
- fn flush_initializations(&mut self, encoder: &mut gpu::CommandEncoder) {
- for id in self.initializations.drain(..) {
- let texture = &self.storage[id];
- encoder.init_texture(texture.raw);
- }
- }
-
- fn flush(&mut self, encoder: &mut gpu::CommandEncoder) {
- self.flush_initializations(encoder);
-
- let mut transfers = encoder.transfer("atlas");
- for upload in self.uploads.drain(..) {
- let texture = &self.storage[upload.id];
- transfers.copy_buffer_to_texture(
- upload.data,
- upload.bounds.size.width.to_bytes(texture.bytes_per_pixel()),
- gpu::TexturePiece {
- texture: texture.raw,
- mip_level: 0,
- array_layer: 0,
- origin: [
- upload.bounds.origin.x.into(),
- upload.bounds.origin.y.into(),
- 0,
- ],
- },
- gpu::Extent {
- width: upload.bounds.size.width.into(),
- height: upload.bounds.size.height.into(),
- depth: 1,
- },
- );
- }
- }
-}
-
-#[derive(Default)]
-struct BladeAtlasStorage {
- monochrome_textures: AtlasTextureList<BladeAtlasTexture>,
- subpixel_textures: AtlasTextureList<BladeAtlasTexture>,
- polychrome_textures: AtlasTextureList<BladeAtlasTexture>,
-}
-
-impl ops::Index<AtlasTextureKind> for BladeAtlasStorage {
- type Output = AtlasTextureList<BladeAtlasTexture>;
- fn index(&self, kind: AtlasTextureKind) -> &Self::Output {
- match kind {
- crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
- crate::AtlasTextureKind::Subpixel => &self.subpixel_textures,
- crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
- }
- }
-}
-
-impl ops::IndexMut<AtlasTextureKind> for BladeAtlasStorage {
- fn index_mut(&mut self, kind: AtlasTextureKind) -> &mut Self::Output {
- match kind {
- crate::AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
- crate::AtlasTextureKind::Subpixel => &mut self.subpixel_textures,
- crate::AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
- }
- }
-}
-
-impl ops::Index<AtlasTextureId> for BladeAtlasStorage {
- type Output = BladeAtlasTexture;
- fn index(&self, id: AtlasTextureId) -> &Self::Output {
- let textures = match id.kind {
- crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
- crate::AtlasTextureKind::Subpixel => &self.subpixel_textures,
- crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
- };
- textures[id.index as usize].as_ref().unwrap()
- }
-}
-
-impl BladeAtlasStorage {
- fn destroy(&mut self, gpu: &gpu::Context) {
- for mut texture in self.monochrome_textures.drain().flatten() {
- texture.destroy(gpu);
- }
- for mut texture in self.subpixel_textures.drain().flatten() {
- texture.destroy(gpu);
- }
- for mut texture in self.polychrome_textures.drain().flatten() {
- texture.destroy(gpu);
- }
- }
-}
-
-struct BladeAtlasTexture {
- id: AtlasTextureId,
- allocator: BucketedAtlasAllocator,
- raw: gpu::Texture,
- raw_view: gpu::TextureView,
- format: gpu::TextureFormat,
- live_atlas_keys: u32,
-}
-
-impl BladeAtlasTexture {
- fn allocate(&mut self, size: Size<DevicePixels>) -> Option<AtlasTile> {
- let allocation = self.allocator.allocate(size.into())?;
- let tile = AtlasTile {
- texture_id: self.id,
- tile_id: allocation.id.into(),
- padding: 0,
- bounds: Bounds {
- origin: allocation.rectangle.min.into(),
- size,
- },
- };
- self.live_atlas_keys += 1;
- Some(tile)
- }
-
- fn destroy(&mut self, gpu: &gpu::Context) {
- gpu.destroy_texture(self.raw);
- gpu.destroy_texture_view(self.raw_view);
- }
-
- fn bytes_per_pixel(&self) -> u8 {
- self.format.block_info().size
- }
-
- fn decrement_ref_count(&mut self) {
- self.live_atlas_keys -= 1;
- }
-
- fn is_unreferenced(&mut self) -> bool {
- self.live_atlas_keys == 0
- }
-}
-
-impl From<Size<DevicePixels>> for etagere::Size {
- fn from(size: Size<DevicePixels>) -> Self {
- etagere::Size::new(size.width.into(), size.height.into())
- }
-}
-
-impl From<etagere::Point> for Point<DevicePixels> {
- fn from(value: etagere::Point) -> Self {
- Point {
- x: DevicePixels::from(value.x),
- y: DevicePixels::from(value.y),
- }
- }
-}
-
-impl From<etagere::Size> for Size<DevicePixels> {
- fn from(size: etagere::Size) -> Self {
- Size {
- width: DevicePixels::from(size.width),
- height: DevicePixels::from(size.height),
- }
- }
-}
-
-impl From<etagere::Rectangle> for Bounds<DevicePixels> {
- fn from(rectangle: etagere::Rectangle) -> Self {
- Bounds {
- origin: rectangle.min.into(),
- size: rectangle.size().into(),
- }
- }
-}
@@ -1,85 +0,0 @@
-use anyhow::Context as _;
-use blade_graphics as gpu;
-use std::sync::Arc;
-use util::ResultExt;
-
-#[cfg_attr(target_os = "macos", derive(Clone))]
-pub struct BladeContext {
- pub(super) gpu: Arc<gpu::Context>,
-}
-
-impl BladeContext {
- pub fn new() -> anyhow::Result<Self> {
- let device_id_forced = match std::env::var("ZED_DEVICE_ID") {
- Ok(val) => parse_pci_id(&val)
- .context("Failed to parse device ID from `ZED_DEVICE_ID` environment variable")
- .log_err(),
- Err(std::env::VarError::NotPresent) => None,
- err => {
- err.context("Failed to read value of `ZED_DEVICE_ID` environment variable")
- .log_err();
- None
- }
- };
- let gpu = Arc::new(
- unsafe {
- gpu::Context::init(gpu::ContextDesc {
- presentation: true,
- validation: false,
- device_id: device_id_forced.unwrap_or(0),
- ..Default::default()
- })
- }
- .map_err(|e| anyhow::anyhow!("{e:?}"))?,
- );
- Ok(Self { gpu })
- }
-
- #[allow(dead_code)]
- pub fn supports_dual_source_blending(&self) -> bool {
- self.gpu.capabilities().dual_source_blending
- }
-}
-
-fn parse_pci_id(id: &str) -> anyhow::Result<u32> {
- let mut id = id.trim();
-
- if id.starts_with("0x") || id.starts_with("0X") {
- id = &id[2..];
- }
- let is_hex_string = id.chars().all(|c| c.is_ascii_hexdigit());
- let is_4_chars = id.len() == 4;
- anyhow::ensure!(
- is_4_chars && is_hex_string,
- "Expected a 4 digit PCI ID in hexadecimal format"
- );
-
- u32::from_str_radix(id, 16).context("parsing PCI ID as hex")
-}
-
-#[cfg(test)]
-mod tests {
- use super::parse_pci_id;
-
- #[test]
- fn test_parse_device_id() {
- assert!(parse_pci_id("0xABCD").is_ok());
- assert!(parse_pci_id("ABCD").is_ok());
- assert!(parse_pci_id("abcd").is_ok());
- assert!(parse_pci_id("1234").is_ok());
- assert!(parse_pci_id("123").is_err());
- assert_eq!(
- parse_pci_id(&format!("{:x}", 0x1234)).unwrap(),
- parse_pci_id(&format!("{:X}", 0x1234)).unwrap(),
- );
-
- assert_eq!(
- parse_pci_id(&format!("{:#x}", 0x1234)).unwrap(),
- parse_pci_id(&format!("{:#X}", 0x1234)).unwrap(),
- );
- assert_eq!(
- parse_pci_id(&format!("{:#x}", 0x1234)).unwrap(),
- parse_pci_id(&format!("{:#X}", 0x1234)).unwrap(),
- );
- }
-}
@@ -1,1121 +0,0 @@
-// Doing `if let` gives you nice scoping with passes/encoders
-#![allow(irrefutable_let_patterns)]
-
-use super::{BladeAtlas, BladeContext};
-use crate::{
- Background, Bounds, DevicePixels, GpuSpecs, MonochromeSprite, Path, Point, PolychromeSprite,
- PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size, Underline,
- get_gamma_correction_ratios,
-};
-#[cfg(any(test, feature = "test-support"))]
-use anyhow::Result;
-use blade_graphics as gpu;
-use blade_util::{BufferBelt, BufferBeltDescriptor};
-use bytemuck::{Pod, Zeroable};
-#[cfg(any(test, feature = "test-support"))]
-use image::RgbaImage;
-#[cfg(target_os = "macos")]
-use media::core_video::CVMetalTextureCache;
-use std::sync::Arc;
-
-const MAX_FRAME_TIME_MS: u32 = 10000;
-
-#[repr(C)]
-#[derive(Clone, Copy, Pod, Zeroable)]
-struct GlobalParams {
- viewport_size: [f32; 2],
- premultiplied_alpha: u32,
- pad: u32,
-}
-
-//Note: we can't use `Bounds` directly here because
-// it doesn't implement Pod + Zeroable
-#[repr(C)]
-#[derive(Clone, Copy, Pod, Zeroable)]
-struct PodBounds {
- origin: [f32; 2],
- size: [f32; 2],
-}
-
-impl From<Bounds<ScaledPixels>> for PodBounds {
- fn from(bounds: Bounds<ScaledPixels>) -> Self {
- Self {
- origin: [bounds.origin.x.0, bounds.origin.y.0],
- size: [bounds.size.width.0, bounds.size.height.0],
- }
- }
-}
-
-#[repr(C)]
-#[derive(Clone, Copy, Pod, Zeroable)]
-struct SurfaceParams {
- bounds: PodBounds,
- content_mask: PodBounds,
-}
-
-#[derive(blade_macros::ShaderData)]
-struct ShaderQuadsData {
- globals: GlobalParams,
- b_quads: gpu::BufferPiece,
-}
-
-#[derive(blade_macros::ShaderData)]
-struct ShaderShadowsData {
- globals: GlobalParams,
- b_shadows: gpu::BufferPiece,
-}
-
-#[derive(blade_macros::ShaderData)]
-struct ShaderPathRasterizationData {
- globals: GlobalParams,
- b_path_vertices: gpu::BufferPiece,
-}
-
-#[derive(blade_macros::ShaderData)]
-struct ShaderPathsData {
- globals: GlobalParams,
- t_sprite: gpu::TextureView,
- s_sprite: gpu::Sampler,
- b_path_sprites: gpu::BufferPiece,
-}
-
-#[derive(blade_macros::ShaderData)]
-struct ShaderUnderlinesData {
- globals: GlobalParams,
- b_underlines: gpu::BufferPiece,
-}
-
-#[derive(blade_macros::ShaderData)]
-struct ShaderMonoSpritesData {
- globals: GlobalParams,
- gamma_ratios: [f32; 4],
- grayscale_enhanced_contrast: f32,
- t_sprite: gpu::TextureView,
- s_sprite: gpu::Sampler,
- b_mono_sprites: gpu::BufferPiece,
-}
-
-#[derive(blade_macros::ShaderData)]
-struct ShaderSubpixelSpritesData {
- globals: GlobalParams,
- gamma_ratios: [f32; 4],
- subpixel_enhanced_contrast: f32,
- t_sprite: gpu::TextureView,
- s_sprite: gpu::Sampler,
- b_subpixel_sprites: gpu::BufferPiece,
-}
-
-#[derive(blade_macros::ShaderData)]
-struct ShaderPolySpritesData {
- globals: GlobalParams,
- t_sprite: gpu::TextureView,
- s_sprite: gpu::Sampler,
- b_poly_sprites: gpu::BufferPiece,
-}
-
-#[derive(blade_macros::ShaderData)]
-struct ShaderSurfacesData {
- globals: GlobalParams,
- surface_locals: SurfaceParams,
- t_y: gpu::TextureView,
- t_cb_cr: gpu::TextureView,
- s_surface: gpu::Sampler,
-}
-
-#[derive(Clone, Debug, Eq, PartialEq)]
-#[repr(C)]
-struct PathSprite {
- bounds: Bounds<ScaledPixels>,
-}
-
-#[derive(Clone, Debug)]
-#[repr(C)]
-struct PathRasterizationVertex {
- xy_position: Point<ScaledPixels>,
- st_position: Point<f32>,
- color: Background,
- bounds: Bounds<ScaledPixels>,
-}
-
-struct BladePipelines {
- quads: gpu::RenderPipeline,
- shadows: gpu::RenderPipeline,
- path_rasterization: gpu::RenderPipeline,
- paths: gpu::RenderPipeline,
- underlines: gpu::RenderPipeline,
- mono_sprites: gpu::RenderPipeline,
- subpixel_sprites: gpu::RenderPipeline,
- poly_sprites: gpu::RenderPipeline,
- surfaces: gpu::RenderPipeline,
-}
-
-impl BladePipelines {
- fn new(gpu: &gpu::Context, surface_info: gpu::SurfaceInfo, path_sample_count: u32) -> Self {
- use gpu::ShaderData as _;
-
- log::info!(
- "Initializing Blade pipelines for surface {:?}",
- surface_info
- );
- let shader = gpu.create_shader(gpu::ShaderDesc {
- source: include_str!("shaders.wgsl"),
- });
- shader.check_struct_size::<GlobalParams>();
- shader.check_struct_size::<SurfaceParams>();
- shader.check_struct_size::<Quad>();
- shader.check_struct_size::<Shadow>();
- shader.check_struct_size::<PathRasterizationVertex>();
- shader.check_struct_size::<PathSprite>();
- shader.check_struct_size::<Underline>();
- shader.check_struct_size::<MonochromeSprite>();
- shader.check_struct_size::<PolychromeSprite>();
-
- // See https://apoorvaj.io/alpha-compositing-opengl-blending-and-premultiplied-alpha/
- let blend_mode = match surface_info.alpha {
- gpu::AlphaMode::Ignored => gpu::BlendState::ALPHA_BLENDING,
- gpu::AlphaMode::PreMultiplied => gpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING,
- gpu::AlphaMode::PostMultiplied => gpu::BlendState::ALPHA_BLENDING,
- };
- let color_targets = &[gpu::ColorTargetState {
- format: surface_info.format,
- blend: Some(blend_mode),
- write_mask: gpu::ColorWrites::default(),
- }];
-
- Self {
- quads: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
- name: "quads",
- data_layouts: &[&ShaderQuadsData::layout()],
- vertex: shader.at("vs_quad"),
- vertex_fetches: &[],
- primitive: gpu::PrimitiveState {
- topology: gpu::PrimitiveTopology::TriangleStrip,
- ..Default::default()
- },
- depth_stencil: None,
- fragment: Some(shader.at("fs_quad")),
- color_targets,
- multisample_state: gpu::MultisampleState::default(),
- }),
- shadows: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
- name: "shadows",
- data_layouts: &[&ShaderShadowsData::layout()],
- vertex: shader.at("vs_shadow"),
- vertex_fetches: &[],
- primitive: gpu::PrimitiveState {
- topology: gpu::PrimitiveTopology::TriangleStrip,
- ..Default::default()
- },
- depth_stencil: None,
- fragment: Some(shader.at("fs_shadow")),
- color_targets,
- multisample_state: gpu::MultisampleState::default(),
- }),
- path_rasterization: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
- name: "path_rasterization",
- data_layouts: &[&ShaderPathRasterizationData::layout()],
- vertex: shader.at("vs_path_rasterization"),
- vertex_fetches: &[],
- primitive: gpu::PrimitiveState {
- topology: gpu::PrimitiveTopology::TriangleList,
- ..Default::default()
- },
- depth_stencil: None,
- fragment: Some(shader.at("fs_path_rasterization")),
- // The original implementation was using ADDITIVE blende mode,
- // I don't know why
- // color_targets: &[gpu::ColorTargetState {
- // format: PATH_TEXTURE_FORMAT,
- // blend: Some(gpu::BlendState::ADDITIVE),
- // write_mask: gpu::ColorWrites::default(),
- // }],
- color_targets: &[gpu::ColorTargetState {
- format: surface_info.format,
- blend: Some(gpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
- write_mask: gpu::ColorWrites::default(),
- }],
- multisample_state: gpu::MultisampleState {
- sample_count: path_sample_count,
- ..Default::default()
- },
- }),
- paths: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
- name: "paths",
- data_layouts: &[&ShaderPathsData::layout()],
- vertex: shader.at("vs_path"),
- vertex_fetches: &[],
- primitive: gpu::PrimitiveState {
- topology: gpu::PrimitiveTopology::TriangleStrip,
- ..Default::default()
- },
- depth_stencil: None,
- fragment: Some(shader.at("fs_path")),
- color_targets: &[gpu::ColorTargetState {
- format: surface_info.format,
- blend: Some(gpu::BlendState {
- color: gpu::BlendComponent::OVER,
- alpha: gpu::BlendComponent::ADDITIVE,
- }),
- write_mask: gpu::ColorWrites::default(),
- }],
- multisample_state: gpu::MultisampleState::default(),
- }),
- underlines: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
- name: "underlines",
- data_layouts: &[&ShaderUnderlinesData::layout()],
- vertex: shader.at("vs_underline"),
- vertex_fetches: &[],
- primitive: gpu::PrimitiveState {
- topology: gpu::PrimitiveTopology::TriangleStrip,
- ..Default::default()
- },
- depth_stencil: None,
- fragment: Some(shader.at("fs_underline")),
- color_targets,
- multisample_state: gpu::MultisampleState::default(),
- }),
- mono_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
- name: "mono-sprites",
- data_layouts: &[&ShaderMonoSpritesData::layout()],
- vertex: shader.at("vs_mono_sprite"),
- vertex_fetches: &[],
- primitive: gpu::PrimitiveState {
- topology: gpu::PrimitiveTopology::TriangleStrip,
- ..Default::default()
- },
- depth_stencil: None,
- fragment: Some(shader.at("fs_mono_sprite")),
- color_targets,
- multisample_state: gpu::MultisampleState::default(),
- }),
- subpixel_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
- name: "subpixel-sprites",
- data_layouts: &[&ShaderSubpixelSpritesData::layout()],
- vertex: shader.at("vs_subpixel_sprite"),
- vertex_fetches: &[],
- primitive: gpu::PrimitiveState {
- topology: gpu::PrimitiveTopology::TriangleStrip,
- ..Default::default()
- },
- depth_stencil: None,
- fragment: Some(shader.at("fs_subpixel_sprite")),
- color_targets: &[gpu::ColorTargetState {
- format: surface_info.format,
- blend: Some(gpu::BlendState {
- color: gpu::BlendComponent {
- src_factor: gpu::BlendFactor::Src1,
- dst_factor: gpu::BlendFactor::OneMinusSrc1,
- operation: gpu::BlendOperation::Add,
- },
- alpha: gpu::BlendComponent::OVER,
- }),
- write_mask: gpu::ColorWrites::COLOR,
- }],
- multisample_state: gpu::MultisampleState::default(),
- }),
- poly_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
- name: "poly-sprites",
- data_layouts: &[&ShaderPolySpritesData::layout()],
- vertex: shader.at("vs_poly_sprite"),
- vertex_fetches: &[],
- primitive: gpu::PrimitiveState {
- topology: gpu::PrimitiveTopology::TriangleStrip,
- ..Default::default()
- },
- depth_stencil: None,
- fragment: Some(shader.at("fs_poly_sprite")),
- color_targets,
- multisample_state: gpu::MultisampleState::default(),
- }),
- surfaces: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
- name: "surfaces",
- data_layouts: &[&ShaderSurfacesData::layout()],
- vertex: shader.at("vs_surface"),
- vertex_fetches: &[],
- primitive: gpu::PrimitiveState {
- topology: gpu::PrimitiveTopology::TriangleStrip,
- ..Default::default()
- },
- depth_stencil: None,
- fragment: Some(shader.at("fs_surface")),
- color_targets,
- multisample_state: gpu::MultisampleState::default(),
- }),
- }
- }
-
- fn destroy(&mut self, gpu: &gpu::Context) {
- gpu.destroy_render_pipeline(&mut self.quads);
- gpu.destroy_render_pipeline(&mut self.shadows);
- gpu.destroy_render_pipeline(&mut self.path_rasterization);
- gpu.destroy_render_pipeline(&mut self.paths);
- gpu.destroy_render_pipeline(&mut self.underlines);
- gpu.destroy_render_pipeline(&mut self.mono_sprites);
- gpu.destroy_render_pipeline(&mut self.subpixel_sprites);
- gpu.destroy_render_pipeline(&mut self.poly_sprites);
- gpu.destroy_render_pipeline(&mut self.surfaces);
- }
-}
-
-pub struct BladeSurfaceConfig {
- pub size: gpu::Extent,
- pub transparent: bool,
-}
-
-//Note: we could see some of these fields moved into `BladeContext`
-// so that they are shared between windows. E.g. `pipelines`.
-// But that is complicated by the fact that pipelines depend on
-// the format and alpha mode.
-pub struct BladeRenderer {
- gpu: Arc<gpu::Context>,
- surface: gpu::Surface,
- surface_config: gpu::SurfaceConfig,
- command_encoder: gpu::CommandEncoder,
- last_sync_point: Option<gpu::SyncPoint>,
- pipelines: BladePipelines,
- instance_belt: BufferBelt,
- atlas: Arc<BladeAtlas>,
- atlas_sampler: gpu::Sampler,
- #[cfg(target_os = "macos")]
- core_video_texture_cache: CVMetalTextureCache,
- path_intermediate_texture: gpu::Texture,
- path_intermediate_texture_view: gpu::TextureView,
- path_intermediate_msaa_texture: Option<gpu::Texture>,
- path_intermediate_msaa_texture_view: Option<gpu::TextureView>,
- rendering_parameters: RenderingParameters,
-}
-
-impl BladeRenderer {
- pub fn new<I: raw_window_handle::HasWindowHandle + raw_window_handle::HasDisplayHandle>(
- context: &BladeContext,
- window: &I,
- config: BladeSurfaceConfig,
- ) -> anyhow::Result<Self> {
- let surface_config = gpu::SurfaceConfig {
- size: config.size,
- usage: gpu::TextureUsage::TARGET,
- display_sync: gpu::DisplaySync::Recent,
- color_space: gpu::ColorSpace::Srgb,
- allow_exclusive_full_screen: false,
- transparent: config.transparent,
- };
- let surface = context
- .gpu
- .create_surface_configured(window, surface_config)
- .map_err(|err| anyhow::anyhow!("Failed to create surface: {err:?}"))?;
-
- let command_encoder = context.gpu.create_command_encoder(gpu::CommandEncoderDesc {
- name: "main",
- buffer_count: 2,
- });
- let rendering_parameters = RenderingParameters::from_env(context);
- let pipelines = BladePipelines::new(
- &context.gpu,
- surface.info(),
- rendering_parameters.path_sample_count,
- );
- let instance_belt = BufferBelt::new(BufferBeltDescriptor {
- memory: gpu::Memory::Shared,
- min_chunk_size: 0x1000,
- alignment: 0x40, // Vulkan `minStorageBufferOffsetAlignment` on Intel Xe
- });
- let atlas = Arc::new(BladeAtlas::new(&context.gpu));
- let atlas_sampler = context.gpu.create_sampler(gpu::SamplerDesc {
- name: "path rasterization sampler",
- mag_filter: gpu::FilterMode::Linear,
- min_filter: gpu::FilterMode::Linear,
- ..Default::default()
- });
-
- let (path_intermediate_texture, path_intermediate_texture_view) =
- create_path_intermediate_texture(
- &context.gpu,
- surface.info().format,
- config.size.width,
- config.size.height,
- );
- let (path_intermediate_msaa_texture, path_intermediate_msaa_texture_view) =
- create_msaa_texture_if_needed(
- &context.gpu,
- surface.info().format,
- config.size.width,
- config.size.height,
- rendering_parameters.path_sample_count,
- )
- .unzip();
-
- #[cfg(target_os = "macos")]
- let core_video_texture_cache = unsafe {
- CVMetalTextureCache::new(
- objc2::rc::Retained::as_ptr(&context.gpu.metal_device()) as *mut _
- )
- .unwrap()
- };
-
- Ok(Self {
- gpu: Arc::clone(&context.gpu),
- surface,
- surface_config,
- command_encoder,
- last_sync_point: None,
- pipelines,
- instance_belt,
- atlas,
- atlas_sampler,
- #[cfg(target_os = "macos")]
- core_video_texture_cache,
- path_intermediate_texture,
- path_intermediate_texture_view,
- path_intermediate_msaa_texture,
- path_intermediate_msaa_texture_view,
- rendering_parameters,
- })
- }
-
- fn wait_for_gpu(&mut self) {
- if let Some(last_sp) = self.last_sync_point.take()
- && !self.gpu.wait_for(&last_sp, MAX_FRAME_TIME_MS)
- {
- log::error!("GPU hung");
- #[cfg(target_os = "linux")]
- if self.gpu.device_information().driver_name == "radv" {
- log::error!(
- "there's a known bug with amdgpu/radv, try setting ZED_PATH_SAMPLE_COUNT=0 as a workaround"
- );
- log::error!(
- "if that helps you're running into https://github.com/zed-industries/zed/issues/26143"
- );
- }
- log::error!(
- "your device information is: {:?}",
- self.gpu.device_information()
- );
- while !self.gpu.wait_for(&last_sp, MAX_FRAME_TIME_MS) {}
- }
- }
-
- pub fn update_drawable_size(&mut self, size: Size<DevicePixels>) {
- self.update_drawable_size_impl(size, false);
- }
-
- /// Like `update_drawable_size` but skips the check that the size has changed. This is useful in
- /// cases like restoring a window from minimization where the size is the same but the
- /// renderer's swap chain needs to be recreated.
- #[cfg_attr(
- any(target_os = "macos", target_os = "linux", target_os = "freebsd"),
- allow(dead_code)
- )]
- pub fn update_drawable_size_even_if_unchanged(&mut self, size: Size<DevicePixels>) {
- self.update_drawable_size_impl(size, true);
- }
-
- fn update_drawable_size_impl(&mut self, size: Size<DevicePixels>, always_resize: bool) {
- let gpu_size = gpu::Extent {
- width: size.width.0 as u32,
- height: size.height.0 as u32,
- depth: 1,
- };
-
- if always_resize || gpu_size != self.surface_config.size {
- self.wait_for_gpu();
- self.surface_config.size = gpu_size;
- self.gpu
- .reconfigure_surface(&mut self.surface, self.surface_config);
- self.gpu.destroy_texture(self.path_intermediate_texture);
- self.gpu
- .destroy_texture_view(self.path_intermediate_texture_view);
- if let Some(msaa_texture) = self.path_intermediate_msaa_texture {
- self.gpu.destroy_texture(msaa_texture);
- }
- if let Some(msaa_view) = self.path_intermediate_msaa_texture_view {
- self.gpu.destroy_texture_view(msaa_view);
- }
- let (path_intermediate_texture, path_intermediate_texture_view) =
- create_path_intermediate_texture(
- &self.gpu,
- self.surface.info().format,
- gpu_size.width,
- gpu_size.height,
- );
- self.path_intermediate_texture = path_intermediate_texture;
- self.path_intermediate_texture_view = path_intermediate_texture_view;
- let (path_intermediate_msaa_texture, path_intermediate_msaa_texture_view) =
- create_msaa_texture_if_needed(
- &self.gpu,
- self.surface.info().format,
- gpu_size.width,
- gpu_size.height,
- self.rendering_parameters.path_sample_count,
- )
- .unzip();
- self.path_intermediate_msaa_texture = path_intermediate_msaa_texture;
- self.path_intermediate_msaa_texture_view = path_intermediate_msaa_texture_view;
- }
- }
-
- pub fn update_transparency(&mut self, transparent: bool) {
- if transparent != self.surface_config.transparent {
- self.wait_for_gpu();
- self.surface_config.transparent = transparent;
- self.gpu
- .reconfigure_surface(&mut self.surface, self.surface_config);
- self.pipelines.destroy(&self.gpu);
- self.pipelines = BladePipelines::new(
- &self.gpu,
- self.surface.info(),
- self.rendering_parameters.path_sample_count,
- );
- }
- }
-
- #[cfg_attr(
- any(target_os = "macos", feature = "wayland", target_os = "windows"),
- allow(dead_code)
- )]
- pub fn viewport_size(&self) -> gpu::Extent {
- self.surface_config.size
- }
-
- pub fn sprite_atlas(&self) -> &Arc<BladeAtlas> {
- &self.atlas
- }
-
- #[cfg_attr(target_os = "macos", allow(dead_code))]
- pub fn gpu_specs(&self) -> GpuSpecs {
- let info = self.gpu.device_information();
-
- GpuSpecs {
- is_software_emulated: info.is_software_emulated,
- device_name: info.device_name.clone(),
- driver_name: info.driver_name.clone(),
- driver_info: info.driver_info.clone(),
- }
- }
-
- #[cfg(target_os = "macos")]
- pub fn layer(&self) -> metal::MetalLayer {
- unsafe { foreign_types::ForeignType::from_ptr(self.layer_ptr()) }
- }
-
- #[cfg(target_os = "macos")]
- pub fn layer_ptr(&self) -> *mut metal::CAMetalLayer {
- objc2::rc::Retained::as_ptr(&self.surface.metal_layer()) as *mut _
- }
-
- #[profiling::function]
- fn draw_paths_to_intermediate(
- &mut self,
- paths: &[Path<ScaledPixels>],
- width: f32,
- height: f32,
- ) {
- self.command_encoder
- .init_texture(self.path_intermediate_texture);
- if let Some(msaa_texture) = self.path_intermediate_msaa_texture {
- self.command_encoder.init_texture(msaa_texture);
- }
-
- let target = if let Some(msaa_view) = self.path_intermediate_msaa_texture_view {
- gpu::RenderTarget {
- view: msaa_view,
- init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
- finish_op: gpu::FinishOp::ResolveTo(self.path_intermediate_texture_view),
- }
- } else {
- gpu::RenderTarget {
- view: self.path_intermediate_texture_view,
- init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
- finish_op: gpu::FinishOp::Store,
- }
- };
- if let mut pass = self.command_encoder.render(
- "rasterize paths",
- gpu::RenderTargetSet {
- colors: &[target],
- depth_stencil: None,
- },
- ) {
- let globals = GlobalParams {
- viewport_size: [width, height],
- premultiplied_alpha: 0,
- pad: 0,
- };
- let mut encoder = pass.with(&self.pipelines.path_rasterization);
-
- let mut vertices = Vec::new();
- for path in paths {
- vertices.extend(path.vertices.iter().map(|v| PathRasterizationVertex {
- xy_position: v.xy_position,
- st_position: v.st_position,
- color: path.color,
- bounds: path.clipped_bounds(),
- }));
- }
- let vertex_buf = unsafe { self.instance_belt.alloc_typed(&vertices, &self.gpu) };
- encoder.bind(
- 0,
- &ShaderPathRasterizationData {
- globals,
- b_path_vertices: vertex_buf,
- },
- );
- encoder.draw(0, vertices.len() as u32, 0, 1);
- }
- }
-
- pub fn destroy(&mut self) {
- self.wait_for_gpu();
- self.atlas.destroy();
- self.gpu.destroy_sampler(self.atlas_sampler);
- self.instance_belt.destroy(&self.gpu);
- self.gpu.destroy_command_encoder(&mut self.command_encoder);
- self.pipelines.destroy(&self.gpu);
- self.gpu.destroy_surface(&mut self.surface);
- self.gpu.destroy_texture(self.path_intermediate_texture);
- self.gpu
- .destroy_texture_view(self.path_intermediate_texture_view);
- if let Some(msaa_texture) = self.path_intermediate_msaa_texture {
- self.gpu.destroy_texture(msaa_texture);
- }
- if let Some(msaa_view) = self.path_intermediate_msaa_texture_view {
- self.gpu.destroy_texture_view(msaa_view);
- }
- }
-
- pub fn draw(&mut self, scene: &Scene) {
- self.command_encoder.start();
- self.atlas.before_frame(&mut self.command_encoder);
-
- let frame = {
- profiling::scope!("acquire frame");
- self.surface.acquire_frame()
- };
- self.command_encoder.init_texture(frame.texture());
-
- let globals = GlobalParams {
- viewport_size: [
- self.surface_config.size.width as f32,
- self.surface_config.size.height as f32,
- ],
- premultiplied_alpha: match self.surface.info().alpha {
- gpu::AlphaMode::Ignored | gpu::AlphaMode::PostMultiplied => 0,
- gpu::AlphaMode::PreMultiplied => 1,
- },
- pad: 0,
- };
-
- let mut pass = self.command_encoder.render(
- "main",
- gpu::RenderTargetSet {
- colors: &[gpu::RenderTarget {
- view: frame.texture_view(),
- init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
- finish_op: gpu::FinishOp::Store,
- }],
- depth_stencil: None,
- },
- );
-
- profiling::scope!("render pass");
- for batch in scene.batches() {
- match batch {
- PrimitiveBatch::Quads(range) => {
- let quads = &scene.quads[range];
- let instance_buf = unsafe { self.instance_belt.alloc_typed(quads, &self.gpu) };
- let mut encoder = pass.with(&self.pipelines.quads);
- encoder.bind(
- 0,
- &ShaderQuadsData {
- globals,
- b_quads: instance_buf,
- },
- );
- encoder.draw(0, 4, 0, quads.len() as u32);
- }
- PrimitiveBatch::Shadows(range) => {
- let shadows = &scene.shadows[range];
- let instance_buf =
- unsafe { self.instance_belt.alloc_typed(shadows, &self.gpu) };
- let mut encoder = pass.with(&self.pipelines.shadows);
- encoder.bind(
- 0,
- &ShaderShadowsData {
- globals,
- b_shadows: instance_buf,
- },
- );
- encoder.draw(0, 4, 0, shadows.len() as u32);
- }
- PrimitiveBatch::Paths(range) => {
- let paths = &scene.paths[range];
- let Some(first_path) = paths.first() else {
- continue;
- };
- drop(pass);
- self.draw_paths_to_intermediate(
- paths,
- self.surface_config.size.width as f32,
- self.surface_config.size.height as f32,
- );
- pass = self.command_encoder.render(
- "main",
- gpu::RenderTargetSet {
- colors: &[gpu::RenderTarget {
- view: frame.texture_view(),
- init_op: gpu::InitOp::Load,
- finish_op: gpu::FinishOp::Store,
- }],
- depth_stencil: None,
- },
- );
- let mut encoder = pass.with(&self.pipelines.paths);
- // When copying paths from the intermediate texture to the drawable,
- // each pixel must only be copied once, in case of transparent paths.
- //
- // If all paths have the same draw order, then their bounds are all
- // disjoint, so we can copy each path's bounds individually. If this
- // batch combines different draw orders, we perform a single copy
- // for a minimal spanning rect.
- let sprites = if paths.last().unwrap().order == first_path.order {
- paths
- .iter()
- .map(|path| PathSprite {
- bounds: path.clipped_bounds(),
- })
- .collect()
- } else {
- let mut bounds = first_path.clipped_bounds();
- for path in paths.iter().skip(1) {
- bounds = bounds.union(&path.clipped_bounds());
- }
- vec![PathSprite { bounds }]
- };
- let instance_buf =
- unsafe { self.instance_belt.alloc_typed(&sprites, &self.gpu) };
- encoder.bind(
- 0,
- &ShaderPathsData {
- globals,
- t_sprite: self.path_intermediate_texture_view,
- s_sprite: self.atlas_sampler,
- b_path_sprites: instance_buf,
- },
- );
- encoder.draw(0, 4, 0, sprites.len() as u32);
- }
- PrimitiveBatch::Underlines(range) => {
- let underlines = &scene.underlines[range];
- let instance_buf =
- unsafe { self.instance_belt.alloc_typed(underlines, &self.gpu) };
- let mut encoder = pass.with(&self.pipelines.underlines);
- encoder.bind(
- 0,
- &ShaderUnderlinesData {
- globals,
- b_underlines: instance_buf,
- },
- );
- encoder.draw(0, 4, 0, underlines.len() as u32);
- }
- PrimitiveBatch::MonochromeSprites { texture_id, range } => {
- let sprites = &scene.monochrome_sprites[range];
- let tex_info = self.atlas.get_texture_info(texture_id);
- let instance_buf =
- unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) };
- let mut encoder = pass.with(&self.pipelines.mono_sprites);
- encoder.bind(
- 0,
- &ShaderMonoSpritesData {
- globals,
- gamma_ratios: self.rendering_parameters.gamma_ratios,
- grayscale_enhanced_contrast: self
- .rendering_parameters
- .grayscale_enhanced_contrast,
- t_sprite: tex_info.raw_view,
- s_sprite: self.atlas_sampler,
- b_mono_sprites: instance_buf,
- },
- );
- encoder.draw(0, 4, 0, sprites.len() as u32);
- }
- PrimitiveBatch::PolychromeSprites { texture_id, range } => {
- let sprites = &scene.polychrome_sprites[range];
- let tex_info = self.atlas.get_texture_info(texture_id);
- let instance_buf =
- unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) };
- let mut encoder = pass.with(&self.pipelines.poly_sprites);
- encoder.bind(
- 0,
- &ShaderPolySpritesData {
- globals,
- t_sprite: tex_info.raw_view,
- s_sprite: self.atlas_sampler,
- b_poly_sprites: instance_buf,
- },
- );
- encoder.draw(0, 4, 0, sprites.len() as u32);
- }
- PrimitiveBatch::SubpixelSprites { texture_id, range } => {
- let sprites = &scene.subpixel_sprites[range];
- let tex_info = self.atlas.get_texture_info(texture_id);
- let instance_buf =
- unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) };
- let mut encoder = pass.with(&self.pipelines.subpixel_sprites);
- encoder.bind(
- 0,
- &ShaderSubpixelSpritesData {
- globals,
- gamma_ratios: self.rendering_parameters.gamma_ratios,
- subpixel_enhanced_contrast: self
- .rendering_parameters
- .subpixel_enhanced_contrast,
- t_sprite: tex_info.raw_view,
- s_sprite: self.atlas_sampler,
- b_subpixel_sprites: instance_buf,
- },
- );
- encoder.draw(0, 4, 0, sprites.len() as u32);
- }
- PrimitiveBatch::Surfaces(range) => {
- let surfaces = &scene.surfaces[range];
- let mut _encoder = pass.with(&self.pipelines.surfaces);
-
- for surface in surfaces {
- #[cfg(not(target_os = "macos"))]
- {
- let _ = surface;
- continue;
- };
-
- #[cfg(target_os = "macos")]
- {
- let (t_y, t_cb_cr) = unsafe {
- use core_foundation::base::TCFType as _;
- use std::ptr;
-
- assert_eq!(
- surface.image_buffer.get_pixel_format(),
- core_video::pixel_buffer::kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
- );
-
- let y_texture = self
- .core_video_texture_cache
- .create_texture_from_image(
- surface.image_buffer.as_concrete_TypeRef(),
- ptr::null(),
- metal::MTLPixelFormat::R8Unorm,
- surface.image_buffer.get_width_of_plane(0),
- surface.image_buffer.get_height_of_plane(0),
- 0,
- )
- .unwrap();
- let cb_cr_texture = self
- .core_video_texture_cache
- .create_texture_from_image(
- surface.image_buffer.as_concrete_TypeRef(),
- ptr::null(),
- metal::MTLPixelFormat::RG8Unorm,
- surface.image_buffer.get_width_of_plane(1),
- surface.image_buffer.get_height_of_plane(1),
- 1,
- )
- .unwrap();
- (
- gpu::TextureView::from_metal_texture(
- &objc2::rc::Retained::retain(
- foreign_types::ForeignTypeRef::as_ptr(
- y_texture.as_texture_ref(),
- )
- as *mut objc2::runtime::ProtocolObject<
- dyn objc2_metal::MTLTexture,
- >,
- )
- .unwrap(),
- gpu::TexelAspects::COLOR,
- ),
- gpu::TextureView::from_metal_texture(
- &objc2::rc::Retained::retain(
- foreign_types::ForeignTypeRef::as_ptr(
- cb_cr_texture.as_texture_ref(),
- )
- as *mut objc2::runtime::ProtocolObject<
- dyn objc2_metal::MTLTexture,
- >,
- )
- .unwrap(),
- gpu::TexelAspects::COLOR,
- ),
- )
- };
-
- _encoder.bind(
- 0,
- &ShaderSurfacesData {
- globals,
- surface_locals: SurfaceParams {
- bounds: surface.bounds.into(),
- content_mask: surface.content_mask.bounds.into(),
- },
- t_y,
- t_cb_cr,
- s_surface: self.atlas_sampler,
- },
- );
-
- _encoder.draw(0, 4, 0, 1);
- }
- }
- }
- }
- }
- drop(pass);
-
- self.command_encoder.present(frame);
- let sync_point = self.gpu.submit(&mut self.command_encoder);
-
- profiling::scope!("finish");
- self.instance_belt.flush(&sync_point);
- self.atlas.after_frame(&sync_point);
-
- self.wait_for_gpu();
- self.last_sync_point = Some(sync_point);
- }
-
- /// Renders the scene to a texture and returns the pixel data as an RGBA image.
- /// This is not yet implemented for BladeRenderer.
- #[cfg(any(test, feature = "test-support"))]
- #[allow(dead_code)]
- pub fn render_to_image(&mut self, _scene: &Scene) -> Result<RgbaImage> {
- anyhow::bail!("render_to_image is not yet implemented for BladeRenderer")
- }
-}
-
-fn create_path_intermediate_texture(
- gpu: &gpu::Context,
- format: gpu::TextureFormat,
- width: u32,
- height: u32,
-) -> (gpu::Texture, gpu::TextureView) {
- let texture = gpu.create_texture(gpu::TextureDesc {
- name: "path intermediate",
- format,
- size: gpu::Extent {
- width,
- height,
- depth: 1,
- },
- array_layer_count: 1,
- mip_level_count: 1,
- sample_count: 1,
- dimension: gpu::TextureDimension::D2,
- usage: gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE | gpu::TextureUsage::TARGET,
- external: None,
- });
- let texture_view = gpu.create_texture_view(
- texture,
- gpu::TextureViewDesc {
- name: "path intermediate view",
- format,
- dimension: gpu::ViewDimension::D2,
- subresources: &Default::default(),
- },
- );
- (texture, texture_view)
-}
-
-fn create_msaa_texture_if_needed(
- gpu: &gpu::Context,
- format: gpu::TextureFormat,
- width: u32,
- height: u32,
- sample_count: u32,
-) -> Option<(gpu::Texture, gpu::TextureView)> {
- if sample_count <= 1 {
- return None;
- }
- let texture_msaa = gpu.create_texture(gpu::TextureDesc {
- name: "path intermediate msaa",
- format,
- size: gpu::Extent {
- width,
- height,
- depth: 1,
- },
- array_layer_count: 1,
- mip_level_count: 1,
- sample_count,
- dimension: gpu::TextureDimension::D2,
- usage: gpu::TextureUsage::TARGET,
- external: None,
- });
- let texture_view_msaa = gpu.create_texture_view(
- texture_msaa,
- gpu::TextureViewDesc {
- name: "path intermediate msaa view",
- format,
- dimension: gpu::ViewDimension::D2,
- subresources: &Default::default(),
- },
- );
-
- Some((texture_msaa, texture_view_msaa))
-}
-
-/// A set of parameters that can be set using a corresponding environment variable.
-struct RenderingParameters {
- // Env var: ZED_PATH_SAMPLE_COUNT
- // workaround for https://github.com/zed-industries/zed/issues/26143
- path_sample_count: u32,
-
- // Env var: ZED_FONTS_GAMMA
- // Allowed range [1.0, 2.2], other values are clipped
- // Default: 1.8
- gamma_ratios: [f32; 4],
- // Env var: ZED_FONTS_GRAYSCALE_ENHANCED_CONTRAST
- // Allowed range: [0.0, ..), other values are clipped
- // Default: 1.0
- grayscale_enhanced_contrast: f32,
- // Env var: ZED_FONTS_SUBPIXEL_ENHANCED_CONTRAST
- // Allowed range: [0.0, ..), other values are clipped
- // Default: 0.5
- subpixel_enhanced_contrast: f32,
-}
-
-impl RenderingParameters {
- fn from_env(context: &BladeContext) -> Self {
- use std::env;
-
- let path_sample_count = env::var("ZED_PATH_SAMPLE_COUNT")
- .ok()
- .and_then(|v| v.parse().ok())
- .or_else(|| {
- [4, 2, 1]
- .into_iter()
- .find(|&n| (context.gpu.capabilities().sample_count_mask & n) != 0)
- })
- .unwrap_or(1);
- let gamma = env::var("ZED_FONTS_GAMMA")
- .ok()
- .and_then(|v| v.parse().ok())
- .unwrap_or(1.8_f32)
- .clamp(1.0, 2.2);
- let gamma_ratios = get_gamma_correction_ratios(gamma);
- let grayscale_enhanced_contrast = env::var("ZED_FONTS_GRAYSCALE_ENHANCED_CONTRAST")
- .ok()
- .and_then(|v| v.parse().ok())
- .unwrap_or(1.0_f32)
- .max(0.0);
- let subpixel_enhanced_contrast = env::var("ZED_FONTS_SUBPIXEL_ENHANCED_CONTRAST")
- .ok()
- .and_then(|v| v.parse().ok())
- .unwrap_or(0.5_f32)
- .max(0.0);
-
- Self {
- path_sample_count,
- gamma_ratios,
- grayscale_enhanced_contrast,
- subpixel_enhanced_contrast,
- }
- }
-}
@@ -24,10 +24,12 @@ use xkbcommon::xkb::{self, Keycode, Keysym, State};
use crate::{
Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
ForegroundExecutor, Keymap, LinuxDispatcher, Menu, MenuItem, OwnedMenu, PathPromptOptions,
- Pixels, Platform, PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper,
- PlatformTextSystem, PlatformWindow, Point, PriorityQueueCalloopReceiver, Result,
- RunnableVariant, Task, ThermalState, WindowAppearance, WindowParams, px,
+ Platform, PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper, PlatformTextSystem,
+ PlatformWindow, PriorityQueueCalloopReceiver, Result, RunnableVariant, Task, ThermalState,
+ WindowAppearance, WindowParams,
};
+#[cfg(any(feature = "wayland", feature = "x11"))]
+use crate::{Pixels, Point, px};
#[cfg(any(feature = "wayland", feature = "x11"))]
pub(crate) const SCROLL_LINES: f32 = 3.0;
@@ -36,6 +38,7 @@ pub(crate) const SCROLL_LINES: f32 = 3.0;
// Taken from https://github.com/GNOME/gtk/blob/main/gtk/gtksettings.c#L320
#[cfg(any(feature = "wayland", feature = "x11"))]
pub(crate) const DOUBLE_CLICK_INTERVAL: Duration = Duration::from_millis(400);
+#[cfg(any(feature = "wayland", feature = "x11"))]
pub(crate) const DOUBLE_CLICK_DISTANCE: Pixels = px(5.0);
pub(crate) const KEYRING_LABEL: &str = "zed-github-account";
@@ -708,7 +711,7 @@ pub(super) fn reveal_path_internal(
.detach();
}
-#[allow(unused)]
+#[cfg(any(feature = "wayland", feature = "x11"))]
pub(super) fn is_within_click_distance(a: Point<Pixels>, b: Point<Pixels>) -> bool {
let diff = a - b;
diff.x.abs() <= DOUBLE_CLICK_DISTANCE && diff.y.abs() <= DOUBLE_CLICK_DISTANCE
@@ -97,7 +97,7 @@ use crate::{
};
use crate::{
TaskTiming,
- platform::{PlatformWindow, blade::BladeContext},
+ platform::{PlatformWindow, wgpu::WgpuContext},
};
/// Used to convert evdev scancode to xkb scancode
@@ -204,7 +204,7 @@ pub struct Output {
pub(crate) struct WaylandClientState {
serial_tracker: SerialTracker,
globals: Globals,
- pub gpu_context: BladeContext,
+ pub gpu_context: WgpuContext,
wl_seat: wl_seat::WlSeat, // TODO: Multi seat support
wl_pointer: Option<wl_pointer::WlPointer>,
wl_keyboard: Option<wl_keyboard::WlKeyboard>,
@@ -520,7 +520,7 @@ impl WaylandClient {
.unwrap();
// This could be unified with the notification handling in zed/main:fail_to_open_window.
- let gpu_context = BladeContext::new().notify_err("Unable to init GPU context");
+ let gpu_context = WgpuContext::new().notify_err("Unable to init GPU context");
let seat = seat.unwrap();
let globals = Globals::new(
@@ -6,7 +6,6 @@ use std::{
sync::Arc,
};
-use blade_graphics as gpu;
use collections::{FxHashSet, HashMap};
use futures::channel::oneshot::Receiver;
@@ -26,8 +25,8 @@ use wayland_protocols_plasma::blur::client::org_kde_kwin_blur;
use wayland_protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1;
use crate::{
- AnyWindowHandle, Bounds, Decorations, Globals, GpuSpecs, Modifiers, Output, Pixels,
- PlatformDisplay, PlatformInput, Point, PromptButton, PromptLevel, RequestFrameOptions,
+ AnyWindowHandle, Bounds, Decorations, DevicePixels, Globals, GpuSpecs, Modifiers, Output,
+ Pixels, PlatformDisplay, PlatformInput, Point, PromptButton, PromptLevel, RequestFrameOptions,
ResizeEdge, Size, Tiling, WaylandClientStatePtr, WindowAppearance, WindowBackgroundAppearance,
WindowBounds, WindowControlArea, WindowControls, WindowDecorations, WindowParams, get_window,
layer_shell::LayerShellNotSupportedError, px, size,
@@ -36,8 +35,8 @@ use crate::{
Capslock,
platform::{
PlatformAtlas, PlatformInputHandler, PlatformWindow,
- blade::{BladeContext, BladeRenderer, BladeSurfaceConfig},
linux::wayland::{display::WaylandDisplay, serial::SerialKind},
+ wgpu::{WgpuContext, WgpuRenderer, WgpuSurfaceConfig},
},
};
use crate::{WindowKind, scene::Scene};
@@ -60,6 +59,12 @@ struct RawWindow {
display: *mut c_void,
}
+// Safety: The raw pointers in RawWindow point to Wayland surface/display
+// which are valid for the window's lifetime. These are used only for
+// passing to wgpu which needs Send+Sync for surface creation.
+unsafe impl Send for RawWindow {}
+unsafe impl Sync for RawWindow {}
+
impl rwh::HasWindowHandle for RawWindow {
fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
let window = NonNull::new(self.window).unwrap();
@@ -97,7 +102,7 @@ pub struct WaylandWindowState {
outputs: HashMap<ObjectId, Output>,
display: Option<(ObjectId, Output)>,
globals: Globals,
- renderer: BladeRenderer,
+ renderer: WgpuRenderer,
bounds: Bounds<Pixels>,
scale: f32,
input_handler: Option<PlatformInputHandler>,
@@ -314,7 +319,7 @@ impl WaylandWindowState {
viewport: Option<wp_viewport::WpViewport>,
client: WaylandClientStatePtr,
globals: Globals,
- gpu_context: &BladeContext,
+ gpu_context: &WgpuContext,
options: WindowParams,
parent: Option<WaylandWindowStatePtr>,
) -> anyhow::Result<Self> {
@@ -328,15 +333,14 @@ impl WaylandWindowState {
.display_ptr()
.cast::<c_void>(),
};
- let config = BladeSurfaceConfig {
- size: gpu::Extent {
- width: options.bounds.size.width.0 as u32,
- height: options.bounds.size.height.0 as u32,
- depth: 1,
+ let config = WgpuSurfaceConfig {
+ size: Size {
+ width: DevicePixels(options.bounds.size.width.0 as i32),
+ height: DevicePixels(options.bounds.size.height.0 as i32),
},
transparent: true,
};
- BladeRenderer::new(gpu_context, &raw_window, config)?
+ WgpuRenderer::new(gpu_context, &raw_window, config)?
};
if let WaylandSurfaceState::Xdg(ref xdg_state) = surface_state {
@@ -479,7 +483,7 @@ impl WaylandWindow {
pub fn new(
handle: AnyWindowHandle,
globals: Globals,
- gpu_context: &BladeContext,
+ gpu_context: &WgpuContext,
client: WaylandClientStatePtr,
params: WindowParams,
appearance: WindowAppearance,
@@ -50,7 +50,6 @@ use super::{
use crate::platform::{
LinuxCommon, PlatformWindow,
- blade::BladeContext,
linux::{
DEFAULT_CURSOR_ICON_NAME, LinuxClient, get_xkb_compose_state, is_within_click_distance,
log_cursor_icon_warning, open_uri_internal,
@@ -58,6 +57,7 @@ use crate::platform::{
reveal_path_internal,
xdg_desktop_portal::{Event as XDPEvent, XDPEventSource},
},
+ wgpu::WgpuContext,
};
use crate::{
AnyWindowHandle, Bounds, ClipboardItem, CursorStyle, DisplayId, FileDropEvent, Keystroke,
@@ -177,7 +177,7 @@ pub struct X11ClientState {
pub(crate) last_location: Point<Pixels>,
pub(crate) current_count: usize,
- pub(crate) gpu_context: BladeContext,
+ pub(crate) gpu_context: WgpuContext,
pub(crate) scale_factor: f32,
@@ -420,7 +420,7 @@ impl X11Client {
.to_string();
let keyboard_layout = LinuxKeyboardLayout::new(layout_name.into());
- let gpu_context = BladeContext::new().notify_err("Unable to init GPU context");
+ let gpu_context = WgpuContext::new().notify_err("Unable to init GPU context");
let resource_database = x11rb::resource_manager::new_from_default(&xcb_connection)
.context("Failed to create resource database")?;
@@ -1,16 +1,15 @@
use anyhow::{Context as _, anyhow};
use x11rb::connection::RequestConnection;
-use crate::platform::blade::{BladeContext, BladeRenderer, BladeSurfaceConfig};
+use crate::platform::wgpu::{WgpuContext, WgpuRenderer, WgpuSurfaceConfig};
use crate::{
AnyWindowHandle, Bounds, Decorations, DevicePixels, ForegroundExecutor, GpuSpecs, Modifiers,
Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow,
Point, PromptButton, PromptLevel, RequestFrameOptions, ResizeEdge, ScaledPixels, Scene, Size,
Tiling, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowControlArea,
- WindowDecorations, WindowKind, WindowParams, X11ClientStatePtr, px, size,
+ WindowDecorations, WindowKind, WindowParams, X11ClientStatePtr, px,
};
-use blade_graphics as gpu;
use collections::FxHashSet;
use raw_window_handle as rwh;
use util::{ResultExt, maybe};
@@ -89,12 +88,11 @@ x11rb::atom_manager! {
fn query_render_extent(
xcb: &Rc<XCBConnection>,
x_window: xproto::Window,
-) -> anyhow::Result<gpu::Extent> {
+) -> anyhow::Result<Size<DevicePixels>> {
let reply = get_reply(|| "X11 GetGeometry failed.", xcb.get_geometry(x_window))?;
- Ok(gpu::Extent {
- width: reply.width as u32,
- height: reply.height as u32,
- depth: 1,
+ Ok(Size {
+ width: DevicePixels(reply.width as i32),
+ height: DevicePixels(reply.height as i32),
})
}
@@ -236,6 +234,12 @@ struct RawWindow {
visual_id: u32,
}
+// Safety: The raw pointers in RawWindow point to X11 connection
+// which is valid for the window's lifetime. These are used only for
+// passing to wgpu which needs Send+Sync for surface creation.
+unsafe impl Send for RawWindow {}
+unsafe impl Sync for RawWindow {}
+
#[derive(Default)]
pub struct Callbacks {
request_frame: Option<Box<dyn FnMut(RequestFrameOptions)>>,
@@ -261,7 +265,7 @@ pub struct X11WindowState {
pub(crate) last_sync_counter: Option<sync::Int64>,
bounds: Bounds<Pixels>,
scale_factor: f32,
- renderer: BladeRenderer,
+ renderer: WgpuRenderer,
display: Rc<dyn PlatformDisplay>,
input_handler: Option<PlatformInputHandler>,
appearance: WindowAppearance,
@@ -389,7 +393,7 @@ impl X11WindowState {
handle: AnyWindowHandle,
client: X11ClientStatePtr,
executor: ForegroundExecutor,
- gpu_context: &BladeContext,
+ gpu_context: &WgpuContext,
params: WindowParams,
xcb: &Rc<XCBConnection>,
client_side_decorations_supported: bool,
@@ -682,7 +686,7 @@ impl X11WindowState {
window_id: x_window,
visual_id: visual.id,
};
- let config = BladeSurfaceConfig {
+ let config = WgpuSurfaceConfig {
// Note: this has to be done after the GPU init, or otherwise
// the sizes are immediately invalidated.
size: query_render_extent(xcb, x_window)?,
@@ -692,7 +696,7 @@ impl X11WindowState {
// too
transparent: false,
};
- BladeRenderer::new(gpu_context, &raw_window, config)?
+ WgpuRenderer::new(gpu_context, &raw_window, config)?
};
let display = Rc::new(X11Display::new(xcb, scale_factor, x_screen_index)?);
@@ -740,11 +744,7 @@ impl X11WindowState {
}
fn content_size(&self) -> Size<Pixels> {
- let size = self.renderer.viewport_size();
- Size {
- width: size.width.into(),
- height: size.height.into(),
- }
+ self.bounds.size
}
}
@@ -800,7 +800,7 @@ impl X11Window {
handle: AnyWindowHandle,
client: X11ClientStatePtr,
executor: ForegroundExecutor,
- gpu_context: &BladeContext,
+ gpu_context: &WgpuContext,
params: WindowParams,
xcb: &Rc<XCBConnection>,
client_side_decorations_supported: bool,
@@ -1167,10 +1167,7 @@ impl X11WindowStatePtr {
let gpu_size = query_render_extent(&self.xcb, self.x_window)?;
if true {
- state.renderer.update_drawable_size(size(
- DevicePixels(gpu_size.width as i32),
- DevicePixels(gpu_size.height as i32),
- ));
+ state.renderer.update_drawable_size(gpu_size);
resize_args = Some((state.content_size(), state.scale_factor));
}
if let Some(value) = state.last_sync_counter.take() {
@@ -10,18 +10,12 @@ mod pasteboard;
#[cfg(feature = "screen-capture")]
mod screen_capture;
-#[cfg(not(feature = "macos-blade"))]
mod metal_atlas;
-#[cfg(not(feature = "macos-blade"))]
pub mod metal_renderer;
use core_video::image_buffer::CVImageBuffer;
-#[cfg(not(feature = "macos-blade"))]
use metal_renderer as renderer;
-#[cfg(feature = "macos-blade")]
-use crate::platform::blade as renderer;
-
#[cfg(feature = "font-kit")]
mod open_type;
@@ -2116,7 +2116,6 @@ extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id)
if lock.activated_least_once {
if let Some(mut callback) = lock.request_frame_callback.take() {
- #[cfg(not(feature = "macos-blade"))]
lock.renderer.set_presents_with_transaction(true);
lock.stop_display_link();
drop(lock);
@@ -2124,7 +2123,6 @@ extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id)
let mut lock = window_state.lock();
lock.request_frame_callback = Some(callback);
- #[cfg(not(feature = "macos-blade"))]
lock.renderer.set_presents_with_transaction(false);
lock.start_display_link();
}
@@ -2224,7 +2222,6 @@ extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
let window_state = unsafe { get_window_state(this) };
let mut lock = window_state.lock();
if let Some(mut callback) = lock.request_frame_callback.take() {
- #[cfg(not(feature = "macos-blade"))]
lock.renderer.set_presents_with_transaction(true);
lock.stop_display_link();
drop(lock);
@@ -2232,7 +2229,6 @@ extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
let mut lock = window_state.lock();
lock.request_frame_callback = Some(callback);
- #[cfg(not(feature = "macos-blade"))]
lock.renderer.set_presents_with_transaction(false);
lock.start_display_link();
}
@@ -0,0 +1,7 @@
+mod wgpu_atlas;
+mod wgpu_context;
+mod wgpu_renderer;
+
+pub(crate) use wgpu_atlas::*;
+pub(crate) use wgpu_context::*;
+pub(crate) use wgpu_renderer::*;
@@ -84,12 +84,17 @@ struct GlobalParams {
pad: u32,
}
-var<uniform> globals: GlobalParams;
-var<uniform> gamma_ratios: vec4<f32>;
-var<uniform> grayscale_enhanced_contrast: f32;
-var<uniform> subpixel_enhanced_contrast: f32;
-var t_sprite: texture_2d<f32>;
-var s_sprite: sampler;
+struct GammaParams {
+ gamma_ratios: vec4<f32>,
+ grayscale_enhanced_contrast: f32,
+ subpixel_enhanced_contrast: f32,
+ pad: vec2<f32>,
+}
+
+@group(0) @binding(0) var<uniform> globals: GlobalParams;
+@group(0) @binding(1) var<uniform> gamma_params: GammaParams;
+@group(1) @binding(1) var t_sprite: texture_2d<f32>;
+@group(1) @binding(2) var s_sprite: sampler;
const M_PI_F: f32 = 3.1415926;
const GRAYSCALE_FACTORS: vec3<f32> = vec3<f32>(0.2126, 0.7152, 0.0722);
@@ -521,7 +526,7 @@ struct Quad {
corner_radii: Corners,
border_widths: Edges,
}
-var<storage, read> b_quads: array<Quad>;
+@group(1) @binding(0) var<storage, read> b_quads: array<Quad>;
struct QuadVarying {
@builtin(position) position: vec4<f32>,
@@ -951,7 +956,7 @@ struct Shadow {
content_mask: Bounds,
color: Hsla,
}
-var<storage, read> b_shadows: array<Shadow>;
+@group(1) @binding(0) var<storage, read> b_shadows: array<Shadow>;
struct ShadowVarying {
@builtin(position) position: vec4<f32>,
@@ -1023,7 +1028,7 @@ struct PathRasterizationVertex {
bounds: Bounds,
}
-var<storage, read> b_path_vertices: array<PathRasterizationVertex>;
+@group(1) @binding(0) var<storage, read> b_path_vertices: array<PathRasterizationVertex>;
struct PathRasterizationVarying {
@builtin(position) position: vec4<f32>,
@@ -1083,7 +1088,7 @@ fn fs_path_rasterization(input: PathRasterizationVarying) -> @location(0) vec4<f
struct PathSprite {
bounds: Bounds,
}
-var<storage, read> b_path_sprites: array<PathSprite>;
+@group(1) @binding(0) var<storage, read> b_path_sprites: array<PathSprite>;
struct PathVarying {
@builtin(position) position: vec4<f32>,
@@ -1124,7 +1129,7 @@ struct Underline {
thickness: f32,
wavy: u32,
}
-var<storage, read> b_underlines: array<Underline>;
+@group(1) @binding(0) var<storage, read> b_underlines: array<Underline>;
struct UnderlineVarying {
@builtin(position) position: vec4<f32>,
@@ -1190,7 +1195,7 @@ struct MonochromeSprite {
tile: AtlasTile,
transformation: TransformationMatrix,
}
-var<storage, read> b_mono_sprites: array<MonochromeSprite>;
+@group(1) @binding(0) var<storage, read> b_mono_sprites: array<MonochromeSprite>;
struct MonoSpriteVarying {
@builtin(position) position: vec4<f32>,
@@ -1216,7 +1221,7 @@ fn vs_mono_sprite(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index
@fragment
fn fs_mono_sprite(input: MonoSpriteVarying) -> @location(0) vec4<f32> {
let sample = textureSample(t_sprite, s_sprite, input.tile_position).r;
- let alpha_corrected = apply_contrast_and_gamma_correction(sample, input.color.rgb, grayscale_enhanced_contrast, gamma_ratios);
+ let alpha_corrected = apply_contrast_and_gamma_correction(sample, input.color.rgb, gamma_params.grayscale_enhanced_contrast, gamma_params.gamma_ratios);
// Alpha clip after using the derivatives.
if (any(input.clip_distances < vec4<f32>(0.0))) {
@@ -1238,7 +1243,7 @@ struct PolychromeSprite {
corner_radii: Corners,
tile: AtlasTile,
}
-var<storage, read> b_poly_sprites: array<PolychromeSprite>;
+@group(1) @binding(0) var<storage, read> b_poly_sprites: array<PolychromeSprite>;
struct PolySpriteVarying {
@builtin(position) position: vec4<f32>,
@@ -1286,10 +1291,10 @@ struct SurfaceParams {
content_mask: Bounds,
}
-var<uniform> surface_locals: SurfaceParams;
-var t_y: texture_2d<f32>;
-var t_cb_cr: texture_2d<f32>;
-var s_surface: sampler;
+@group(1) @binding(0) var<uniform> surface_locals: SurfaceParams;
+@group(1) @binding(1) var t_y: texture_2d<f32>;
+@group(1) @binding(2) var t_cb_cr: texture_2d<f32>;
+@group(1) @binding(3) var s_surface: sampler;
const ycbcr_to_RGB = mat4x4<f32>(
vec4<f32>( 1.0000f, 1.0000f, 1.0000f, 0.0),
@@ -1341,7 +1346,7 @@ struct SubpixelSprite {
tile: AtlasTile,
transformation: TransformationMatrix,
}
-var<storage, read> b_subpixel_sprites: array<SubpixelSprite>;
+@group(1) @binding(0) var<storage, read> b_subpixel_sprites: array<SubpixelSprite>;
struct SubpixelSpriteOutput {
@builtin(position) position: vec4<f32>,
@@ -1371,7 +1376,7 @@ fn vs_subpixel_sprite(@builtin(vertex_index) vertex_id: u32, @builtin(instance_i
@fragment
fn fs_subpixel_sprite(input: SubpixelSpriteOutput) -> SubpixelSpriteFragmentOutput {
let sample = textureSample(t_sprite, s_sprite, input.tile_position).rgb;
- let alpha_corrected = apply_contrast_and_gamma_correction3(sample, input.color.rgb, subpixel_enhanced_contrast, gamma_ratios);
+ let alpha_corrected = apply_contrast_and_gamma_correction3(sample, input.color.rgb, gamma_params.subpixel_enhanced_contrast, gamma_params.gamma_ratios);
// Alpha clip after using the derivatives.
if (any(input.clip_distances < vec4<f32>(0.0))) {
@@ -0,0 +1,320 @@
+use crate::{
+ AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas,
+ Point, Size, platform::AtlasTextureList,
+};
+use anyhow::Result;
+use collections::FxHashMap;
+use etagere::{BucketedAtlasAllocator, size2};
+use parking_lot::Mutex;
+use std::{borrow::Cow, ops, sync::Arc};
+
+fn device_size_to_etagere(size: Size<DevicePixels>) -> etagere::Size {
+ size2(size.width.0, size.height.0)
+}
+
+fn etagere_point_to_device(point: etagere::Point) -> Point<DevicePixels> {
+ Point {
+ x: DevicePixels(point.x),
+ y: DevicePixels(point.y),
+ }
+}
+
+pub(crate) struct WgpuAtlas(Mutex<WgpuAtlasState>);
+
+struct PendingUpload {
+ id: AtlasTextureId,
+ bounds: Bounds<DevicePixels>,
+ data: Vec<u8>,
+}
+
+struct WgpuAtlasState {
+ device: Arc<wgpu::Device>,
+ queue: Arc<wgpu::Queue>,
+ storage: WgpuAtlasStorage,
+ tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
+ pending_uploads: Vec<PendingUpload>,
+}
+
+pub struct WgpuTextureInfo {
+ pub view: wgpu::TextureView,
+}
+
+impl WgpuAtlas {
+ pub(crate) fn new(device: Arc<wgpu::Device>, queue: Arc<wgpu::Queue>) -> Self {
+ WgpuAtlas(Mutex::new(WgpuAtlasState {
+ device,
+ queue,
+ storage: WgpuAtlasStorage::default(),
+ tiles_by_key: Default::default(),
+ pending_uploads: Vec::new(),
+ }))
+ }
+
+ pub fn before_frame(&self) {
+ let mut lock = self.0.lock();
+ lock.flush_uploads();
+ }
+
+ pub fn get_texture_info(&self, id: AtlasTextureId) -> WgpuTextureInfo {
+ let lock = self.0.lock();
+ let texture = &lock.storage[id];
+ WgpuTextureInfo {
+ view: texture.view.clone(),
+ }
+ }
+}
+
+impl PlatformAtlas for WgpuAtlas {
+ fn get_or_insert_with<'a>(
+ &self,
+ key: &AtlasKey,
+ build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
+ ) -> Result<Option<AtlasTile>> {
+ let mut lock = self.0.lock();
+ if let Some(tile) = lock.tiles_by_key.get(key) {
+ Ok(Some(tile.clone()))
+ } else {
+ profiling::scope!("new tile");
+ let Some((size, bytes)) = build()? else {
+ return Ok(None);
+ };
+ let tile = lock.allocate(size, key.texture_kind());
+ lock.upload_texture(tile.texture_id, tile.bounds, &bytes);
+ lock.tiles_by_key.insert(key.clone(), tile.clone());
+ Ok(Some(tile))
+ }
+ }
+
+ fn remove(&self, key: &AtlasKey) {
+ let mut lock = self.0.lock();
+
+ let Some(id) = lock.tiles_by_key.remove(key).map(|tile| tile.texture_id) else {
+ return;
+ };
+
+ let Some(texture_slot) = lock.storage[id.kind].textures.get_mut(id.index as usize) else {
+ return;
+ };
+
+ if let Some(mut texture) = texture_slot.take() {
+ texture.decrement_ref_count();
+ if texture.is_unreferenced() {
+ lock.storage[id.kind]
+ .free_list
+ .push(texture.id.index as usize);
+ } else {
+ *texture_slot = Some(texture);
+ }
+ }
+ }
+}
+
+impl WgpuAtlasState {
+ fn allocate(&mut self, size: Size<DevicePixels>, texture_kind: AtlasTextureKind) -> AtlasTile {
+ {
+ let textures = &mut self.storage[texture_kind];
+
+ if let Some(tile) = textures
+ .iter_mut()
+ .rev()
+ .find_map(|texture| texture.allocate(size))
+ {
+ return tile;
+ }
+ }
+
+ let texture = self.push_texture(size, texture_kind);
+ texture
+ .allocate(size)
+ .expect("Failed to allocate from newly created texture")
+ }
+
+ fn push_texture(
+ &mut self,
+ min_size: Size<DevicePixels>,
+ kind: AtlasTextureKind,
+ ) -> &mut WgpuAtlasTexture {
+ const DEFAULT_ATLAS_SIZE: Size<DevicePixels> = Size {
+ width: DevicePixels(1024),
+ height: DevicePixels(1024),
+ };
+
+ let size = min_size.max(&DEFAULT_ATLAS_SIZE);
+ let format = match kind {
+ AtlasTextureKind::Monochrome => wgpu::TextureFormat::R8Unorm,
+ AtlasTextureKind::Subpixel => wgpu::TextureFormat::Bgra8Unorm,
+ AtlasTextureKind::Polychrome => wgpu::TextureFormat::Bgra8Unorm,
+ };
+
+ let texture = self.device.create_texture(&wgpu::TextureDescriptor {
+ label: Some("atlas"),
+ size: wgpu::Extent3d {
+ width: size.width.0 as u32,
+ height: size.height.0 as u32,
+ depth_or_array_layers: 1,
+ },
+ mip_level_count: 1,
+ sample_count: 1,
+ dimension: wgpu::TextureDimension::D2,
+ format,
+ usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
+ view_formats: &[],
+ });
+
+ let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
+
+ let texture_list = &mut self.storage[kind];
+ let index = texture_list.free_list.pop();
+
+ let atlas_texture = WgpuAtlasTexture {
+ id: AtlasTextureId {
+ index: index.unwrap_or(texture_list.textures.len()) as u32,
+ kind,
+ },
+ allocator: BucketedAtlasAllocator::new(device_size_to_etagere(size)),
+ format,
+ texture,
+ view,
+ live_atlas_keys: 0,
+ };
+
+ if let Some(ix) = index {
+ texture_list.textures[ix] = Some(atlas_texture);
+ texture_list
+ .textures
+ .get_mut(ix)
+ .and_then(|t| t.as_mut())
+ .expect("texture must exist")
+ } else {
+ texture_list.textures.push(Some(atlas_texture));
+ texture_list
+ .textures
+ .last_mut()
+ .and_then(|t| t.as_mut())
+ .expect("texture must exist")
+ }
+ }
+
+ fn upload_texture(&mut self, id: AtlasTextureId, bounds: Bounds<DevicePixels>, bytes: &[u8]) {
+ self.pending_uploads.push(PendingUpload {
+ id,
+ bounds,
+ data: bytes.to_vec(),
+ });
+ }
+
+ fn flush_uploads(&mut self) {
+ for upload in self.pending_uploads.drain(..) {
+ let texture = &self.storage[upload.id];
+ let bytes_per_pixel = texture.bytes_per_pixel();
+
+ self.queue.write_texture(
+ wgpu::TexelCopyTextureInfo {
+ texture: &texture.texture,
+ mip_level: 0,
+ origin: wgpu::Origin3d {
+ x: upload.bounds.origin.x.0 as u32,
+ y: upload.bounds.origin.y.0 as u32,
+ z: 0,
+ },
+ aspect: wgpu::TextureAspect::All,
+ },
+ &upload.data,
+ wgpu::TexelCopyBufferLayout {
+ offset: 0,
+ bytes_per_row: Some(upload.bounds.size.width.0 as u32 * bytes_per_pixel as u32),
+ rows_per_image: None,
+ },
+ wgpu::Extent3d {
+ width: upload.bounds.size.width.0 as u32,
+ height: upload.bounds.size.height.0 as u32,
+ depth_or_array_layers: 1,
+ },
+ );
+ }
+ }
+}
+
+#[derive(Default)]
+struct WgpuAtlasStorage {
+ monochrome_textures: AtlasTextureList<WgpuAtlasTexture>,
+ subpixel_textures: AtlasTextureList<WgpuAtlasTexture>,
+ polychrome_textures: AtlasTextureList<WgpuAtlasTexture>,
+}
+
+impl ops::Index<AtlasTextureKind> for WgpuAtlasStorage {
+ type Output = AtlasTextureList<WgpuAtlasTexture>;
+ fn index(&self, kind: AtlasTextureKind) -> &Self::Output {
+ match kind {
+ AtlasTextureKind::Monochrome => &self.monochrome_textures,
+ AtlasTextureKind::Subpixel => &self.subpixel_textures,
+ AtlasTextureKind::Polychrome => &self.polychrome_textures,
+ }
+ }
+}
+
+impl ops::IndexMut<AtlasTextureKind> for WgpuAtlasStorage {
+ fn index_mut(&mut self, kind: AtlasTextureKind) -> &mut Self::Output {
+ match kind {
+ AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
+ AtlasTextureKind::Subpixel => &mut self.subpixel_textures,
+ AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
+ }
+ }
+}
+
+impl ops::Index<AtlasTextureId> for WgpuAtlasStorage {
+ type Output = WgpuAtlasTexture;
+ fn index(&self, id: AtlasTextureId) -> &Self::Output {
+ let textures = match id.kind {
+ AtlasTextureKind::Monochrome => &self.monochrome_textures,
+ AtlasTextureKind::Subpixel => &self.subpixel_textures,
+ AtlasTextureKind::Polychrome => &self.polychrome_textures,
+ };
+ textures[id.index as usize]
+ .as_ref()
+ .expect("texture must exist")
+ }
+}
+
+struct WgpuAtlasTexture {
+ id: AtlasTextureId,
+ allocator: BucketedAtlasAllocator,
+ texture: wgpu::Texture,
+ view: wgpu::TextureView,
+ format: wgpu::TextureFormat,
+ live_atlas_keys: u32,
+}
+
+impl WgpuAtlasTexture {
+ fn allocate(&mut self, size: Size<DevicePixels>) -> Option<AtlasTile> {
+ let allocation = self.allocator.allocate(device_size_to_etagere(size))?;
+ let tile = AtlasTile {
+ texture_id: self.id,
+ tile_id: allocation.id.into(),
+ padding: 0,
+ bounds: Bounds {
+ origin: etagere_point_to_device(allocation.rectangle.min),
+ size,
+ },
+ };
+ self.live_atlas_keys += 1;
+ Some(tile)
+ }
+
+ fn bytes_per_pixel(&self) -> u8 {
+ match self.format {
+ wgpu::TextureFormat::R8Unorm => 1,
+ wgpu::TextureFormat::Bgra8Unorm => 4,
+ _ => 4,
+ }
+ }
+
+ fn decrement_ref_count(&mut self) {
+ self.live_atlas_keys -= 1;
+ }
+
+ fn is_unreferenced(&self) -> bool {
+ self.live_atlas_keys == 0
+ }
+}
@@ -0,0 +1,169 @@
+use anyhow::Context as _;
+use std::sync::Arc;
+use util::ResultExt;
+
+pub struct WgpuContext {
+ pub instance: wgpu::Instance,
+ pub adapter: wgpu::Adapter,
+ pub device: Arc<wgpu::Device>,
+ pub queue: Arc<wgpu::Queue>,
+ dual_source_blending: bool,
+}
+
+impl WgpuContext {
+ pub fn new() -> anyhow::Result<Self> {
+ let device_id_filter = match std::env::var("ZED_DEVICE_ID") {
+ Ok(val) => parse_pci_id(&val)
+ .context("Failed to parse device ID from `ZED_DEVICE_ID` environment variable")
+ .log_err(),
+ Err(std::env::VarError::NotPresent) => None,
+ err => {
+ err.context("Failed to read value of `ZED_DEVICE_ID` environment variable")
+ .log_err();
+ None
+ }
+ };
+
+ let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
+ backends: wgpu::Backends::VULKAN | wgpu::Backends::GL,
+ flags: wgpu::InstanceFlags::default(),
+ backend_options: wgpu::BackendOptions::default(),
+ memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
+ });
+
+ let adapter = smol::block_on(Self::select_adapter(&instance, device_id_filter))?;
+
+ log::info!(
+ "Selected GPU adapter: {:?} ({:?})",
+ adapter.get_info().name,
+ adapter.get_info().backend
+ );
+
+ let dual_source_blending_available = adapter
+ .features()
+ .contains(wgpu::Features::DUAL_SOURCE_BLENDING);
+
+ let mut required_features = wgpu::Features::empty();
+ if dual_source_blending_available {
+ required_features |= wgpu::Features::DUAL_SOURCE_BLENDING;
+ } else {
+ log::warn!(
+ "Dual-source blending not available on this GPU. \
+ Subpixel text antialiasing will be disabled."
+ );
+ }
+
+ let (device, queue) = smol::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
+ label: Some("gpui_device"),
+ required_features,
+ required_limits: wgpu::Limits::default(),
+ memory_hints: wgpu::MemoryHints::MemoryUsage,
+ trace: wgpu::Trace::Off,
+ experimental_features: wgpu::ExperimentalFeatures::disabled(),
+ }))
+ .map_err(|e| anyhow::anyhow!("Failed to create wgpu device: {e}"))?;
+
+ Ok(Self {
+ instance,
+ adapter,
+ device: Arc::new(device),
+ queue: Arc::new(queue),
+ dual_source_blending: dual_source_blending_available,
+ })
+ }
+
+ async fn select_adapter(
+ instance: &wgpu::Instance,
+ device_id_filter: Option<u32>,
+ ) -> anyhow::Result<wgpu::Adapter> {
+ if let Some(device_id) = device_id_filter {
+ let adapters: Vec<_> = instance.enumerate_adapters(wgpu::Backends::all()).await;
+
+ if adapters.is_empty() {
+ anyhow::bail!("No GPU adapters found");
+ }
+
+ let mut non_matching_adapter_infos: Vec<wgpu::AdapterInfo> = Vec::new();
+
+ for adapter in adapters.into_iter() {
+ let info = adapter.get_info();
+ if info.device == device_id {
+ log::info!(
+ "Found GPU matching ZED_DEVICE_ID={:#06x}: {}",
+ device_id,
+ info.name
+ );
+ return Ok(adapter);
+ } else {
+ non_matching_adapter_infos.push(info);
+ }
+ }
+
+ log::warn!(
+ "No GPU found matching ZED_DEVICE_ID={:#06x}. Available devices:",
+ device_id
+ );
+
+ for info in &non_matching_adapter_infos {
+ log::warn!(
+ " - {} (device_id={:#06x}, backend={})",
+ info.name,
+ info.device,
+ info.backend
+ );
+ }
+ }
+
+ instance
+ .request_adapter(&wgpu::RequestAdapterOptions {
+ power_preference: wgpu::PowerPreference::None,
+ compatible_surface: None,
+ force_fallback_adapter: false,
+ })
+ .await
+ .map_err(|e| anyhow::anyhow!("Failed to request GPU adapter: {e}"))
+ }
+
+ pub fn supports_dual_source_blending(&self) -> bool {
+ self.dual_source_blending
+ }
+}
+
+fn parse_pci_id(id: &str) -> anyhow::Result<u32> {
+ let mut id = id.trim();
+
+ if id.starts_with("0x") || id.starts_with("0X") {
+ id = &id[2..];
+ }
+ let is_hex_string = id.chars().all(|c| c.is_ascii_hexdigit());
+ let is_4_chars = id.len() == 4;
+ anyhow::ensure!(
+ is_4_chars && is_hex_string,
+ "Expected a 4 digit PCI ID in hexadecimal format"
+ );
+
+ u32::from_str_radix(id, 16).context("parsing PCI ID as hex")
+}
+
+#[cfg(test)]
+mod tests {
+ use super::parse_pci_id;
+
+ #[test]
+ fn test_parse_device_id() {
+ assert!(parse_pci_id("0xABCD").is_ok());
+ assert!(parse_pci_id("ABCD").is_ok());
+ assert!(parse_pci_id("abcd").is_ok());
+ assert!(parse_pci_id("1234").is_ok());
+ assert!(parse_pci_id("123").is_err());
+ assert_eq!(
+ parse_pci_id(&format!("{:x}", 0x1234)).unwrap(),
+ parse_pci_id(&format!("{:X}", 0x1234)).unwrap(),
+ );
+
+ assert_eq!(
+ parse_pci_id(&format!("{:#x}", 0x1234)).unwrap(),
+ parse_pci_id(&format!("{:#X}", 0x1234)).unwrap(),
+ );
+ }
+}
@@ -0,0 +1,1390 @@
+use super::{WgpuAtlas, WgpuContext};
+use crate::{
+ AtlasTextureId, Background, Bounds, DevicePixels, GpuSpecs, MonochromeSprite, Path, Point,
+ PolychromeSprite, PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size, SubpixelSprite,
+ Underline, get_gamma_correction_ratios,
+};
+use bytemuck::{Pod, Zeroable};
+use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
+use std::num::NonZeroU64;
+use std::sync::Arc;
+
+#[repr(C)]
+#[derive(Clone, Copy, Pod, Zeroable)]
+struct GlobalParams {
+ viewport_size: [f32; 2],
+ premultiplied_alpha: u32,
+ pad: u32,
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Pod, Zeroable)]
+struct PodBounds {
+ origin: [f32; 2],
+ size: [f32; 2],
+}
+
+impl From<Bounds<ScaledPixels>> for PodBounds {
+ fn from(bounds: Bounds<ScaledPixels>) -> Self {
+ Self {
+ origin: [bounds.origin.x.0, bounds.origin.y.0],
+ size: [bounds.size.width.0, bounds.size.height.0],
+ }
+ }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Pod, Zeroable)]
+struct SurfaceParams {
+ bounds: PodBounds,
+ content_mask: PodBounds,
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Pod, Zeroable)]
+struct GammaParams {
+ gamma_ratios: [f32; 4],
+ grayscale_enhanced_contrast: f32,
+ subpixel_enhanced_contrast: f32,
+ _pad: [f32; 2],
+}
+
+#[derive(Clone, Debug)]
+#[repr(C)]
+struct PathSprite {
+ bounds: Bounds<ScaledPixels>,
+}
+
+#[derive(Clone, Debug)]
+#[repr(C)]
+struct PathRasterizationVertex {
+ xy_position: Point<ScaledPixels>,
+ st_position: Point<f32>,
+ color: Background,
+ bounds: Bounds<ScaledPixels>,
+}
+
+pub struct WgpuSurfaceConfig {
+ pub size: Size<DevicePixels>,
+ pub transparent: bool,
+}
+
+struct WgpuPipelines {
+ quads: wgpu::RenderPipeline,
+ shadows: wgpu::RenderPipeline,
+ path_rasterization: wgpu::RenderPipeline,
+ paths: wgpu::RenderPipeline,
+ underlines: wgpu::RenderPipeline,
+ mono_sprites: wgpu::RenderPipeline,
+ subpixel_sprites: Option<wgpu::RenderPipeline>,
+ poly_sprites: wgpu::RenderPipeline,
+ #[allow(dead_code)]
+ surfaces: wgpu::RenderPipeline,
+}
+
+struct WgpuBindGroupLayouts {
+ globals: wgpu::BindGroupLayout,
+ instances: wgpu::BindGroupLayout,
+ instances_with_texture: wgpu::BindGroupLayout,
+ surfaces: wgpu::BindGroupLayout,
+}
+
+pub struct WgpuRenderer {
+ device: Arc<wgpu::Device>,
+ queue: Arc<wgpu::Queue>,
+ surface: wgpu::Surface<'static>,
+ surface_config: wgpu::SurfaceConfiguration,
+ pipelines: WgpuPipelines,
+ bind_group_layouts: WgpuBindGroupLayouts,
+ atlas: Arc<WgpuAtlas>,
+ atlas_sampler: wgpu::Sampler,
+ globals_buffer: wgpu::Buffer,
+ path_globals_offset: u64,
+ gamma_offset: u64,
+ globals_bind_group: wgpu::BindGroup,
+ path_globals_bind_group: wgpu::BindGroup,
+ instance_buffer: wgpu::Buffer,
+ instance_buffer_capacity: u64,
+ storage_buffer_alignment: u64,
+ path_intermediate_texture: wgpu::Texture,
+ path_intermediate_view: wgpu::TextureView,
+ path_msaa_texture: Option<wgpu::Texture>,
+ path_msaa_view: Option<wgpu::TextureView>,
+ rendering_params: RenderingParameters,
+ dual_source_blending: bool,
+ adapter_info: wgpu::AdapterInfo,
+ transparent_alpha_mode: wgpu::CompositeAlphaMode,
+ opaque_alpha_mode: wgpu::CompositeAlphaMode,
+}
+
+impl WgpuRenderer {
+ /// Creates a new WgpuRenderer from raw window handles.
+ ///
+ /// # Safety
+ /// The caller must ensure that the window handle remains valid for the lifetime
+ /// of the returned renderer.
+ pub fn new<W: HasWindowHandle + HasDisplayHandle>(
+ context: &WgpuContext,
+ window: &W,
+ config: WgpuSurfaceConfig,
+ ) -> anyhow::Result<Self> {
+ let window_handle = window
+ .window_handle()
+ .map_err(|e| anyhow::anyhow!("Failed to get window handle: {e}"))?;
+ let display_handle = window
+ .display_handle()
+ .map_err(|e| anyhow::anyhow!("Failed to get display handle: {e}"))?;
+
+ let target = wgpu::SurfaceTargetUnsafe::RawHandle {
+ raw_display_handle: display_handle.as_raw(),
+ raw_window_handle: window_handle.as_raw(),
+ };
+
+ // Safety: The caller guarantees that the window handle is valid for the
+ // lifetime of this renderer. In practice, the RawWindow struct is created
+ // from the native window handles and the surface is dropped before the window.
+ let surface = unsafe {
+ context
+ .instance
+ .create_surface_unsafe(target)
+ .map_err(|e| anyhow::anyhow!("Failed to create surface: {e}"))?
+ };
+
+ let surface_caps = surface.get_capabilities(&context.adapter);
+ // Prefer standard 8-bit non-sRGB formats that don't require special features.
+ // Other formats like Rgba16Unorm require TEXTURE_FORMAT_16BIT_NORM which may
+ // not be available on all devices.
+ let preferred_formats = [
+ wgpu::TextureFormat::Bgra8Unorm,
+ wgpu::TextureFormat::Rgba8Unorm,
+ ];
+ let surface_format = preferred_formats
+ .iter()
+ .find(|f| surface_caps.formats.contains(f))
+ .copied()
+ .or_else(|| surface_caps.formats.iter().find(|f| !f.is_srgb()).copied())
+ .unwrap_or(surface_caps.formats[0]);
+
+ let pick_alpha_mode =
+ |preferences: &[wgpu::CompositeAlphaMode]| -> wgpu::CompositeAlphaMode {
+ preferences
+ .iter()
+ .find(|p| surface_caps.alpha_modes.contains(p))
+ .copied()
+ .unwrap_or(surface_caps.alpha_modes[0])
+ };
+
+ let transparent_alpha_mode = pick_alpha_mode(&[
+ wgpu::CompositeAlphaMode::PreMultiplied,
+ wgpu::CompositeAlphaMode::Inherit,
+ ]);
+
+ let opaque_alpha_mode = pick_alpha_mode(&[
+ wgpu::CompositeAlphaMode::Opaque,
+ wgpu::CompositeAlphaMode::Inherit,
+ ]);
+
+ let alpha_mode = if config.transparent {
+ transparent_alpha_mode
+ } else {
+ opaque_alpha_mode
+ };
+
+ let surface_config = wgpu::SurfaceConfiguration {
+ usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
+ format: surface_format,
+ width: config.size.width.0 as u32,
+ height: config.size.height.0 as u32,
+ present_mode: wgpu::PresentMode::Fifo,
+ desired_maximum_frame_latency: 2,
+ alpha_mode,
+ view_formats: vec![],
+ };
+ surface.configure(&context.device, &surface_config);
+
+ let device = Arc::clone(&context.device);
+ let queue = Arc::clone(&context.queue);
+ let dual_source_blending = context.supports_dual_source_blending();
+
+ let rendering_params = RenderingParameters::new(&context.adapter, surface_format);
+ let bind_group_layouts = Self::create_bind_group_layouts(&device);
+ let pipelines = Self::create_pipelines(
+ &device,
+ &bind_group_layouts,
+ surface_format,
+ alpha_mode,
+ rendering_params.path_sample_count,
+ dual_source_blending,
+ );
+
+ let atlas = Arc::new(WgpuAtlas::new(Arc::clone(&device), Arc::clone(&queue)));
+ let atlas_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
+ label: Some("atlas_sampler"),
+ mag_filter: wgpu::FilterMode::Linear,
+ min_filter: wgpu::FilterMode::Linear,
+ ..Default::default()
+ });
+
+ let uniform_alignment = device.limits().min_uniform_buffer_offset_alignment as u64;
+ let globals_size = std::mem::size_of::<GlobalParams>() as u64;
+ let gamma_size = std::mem::size_of::<GammaParams>() as u64;
+ let path_globals_offset = globals_size.next_multiple_of(uniform_alignment);
+ let gamma_offset = (path_globals_offset + globals_size).next_multiple_of(uniform_alignment);
+
+ let globals_buffer = device.create_buffer(&wgpu::BufferDescriptor {
+ label: Some("globals_buffer"),
+ size: gamma_offset + gamma_size,
+ usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
+ mapped_at_creation: false,
+ });
+
+ let storage_buffer_alignment = device.limits().min_storage_buffer_offset_alignment as u64;
+ let initial_instance_buffer_capacity = 2 * 1024 * 1024;
+ let instance_buffer = device.create_buffer(&wgpu::BufferDescriptor {
+ label: Some("instance_buffer"),
+ size: initial_instance_buffer_capacity,
+ usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
+ mapped_at_creation: false,
+ });
+
+ let (path_intermediate_texture, path_intermediate_view) = Self::create_path_intermediate(
+ &device,
+ surface_format,
+ config.size.width.0 as u32,
+ config.size.height.0 as u32,
+ );
+
+ let (path_msaa_texture, path_msaa_view) = Self::create_msaa_if_needed(
+ &device,
+ surface_format,
+ config.size.width.0 as u32,
+ config.size.height.0 as u32,
+ rendering_params.path_sample_count,
+ )
+ .map(|(t, v)| (Some(t), Some(v)))
+ .unwrap_or((None, None));
+
+ let globals_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
+ label: Some("globals_bind_group"),
+ layout: &bind_group_layouts.globals,
+ entries: &[
+ wgpu::BindGroupEntry {
+ binding: 0,
+ resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
+ buffer: &globals_buffer,
+ offset: 0,
+ size: Some(NonZeroU64::new(globals_size).unwrap()),
+ }),
+ },
+ wgpu::BindGroupEntry {
+ binding: 1,
+ resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
+ buffer: &globals_buffer,
+ offset: gamma_offset,
+ size: Some(NonZeroU64::new(gamma_size).unwrap()),
+ }),
+ },
+ ],
+ });
+
+ let path_globals_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
+ label: Some("path_globals_bind_group"),
+ layout: &bind_group_layouts.globals,
+ entries: &[
+ wgpu::BindGroupEntry {
+ binding: 0,
+ resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
+ buffer: &globals_buffer,
+ offset: path_globals_offset,
+ size: Some(NonZeroU64::new(globals_size).unwrap()),
+ }),
+ },
+ wgpu::BindGroupEntry {
+ binding: 1,
+ resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
+ buffer: &globals_buffer,
+ offset: gamma_offset,
+ size: Some(NonZeroU64::new(gamma_size).unwrap()),
+ }),
+ },
+ ],
+ });
+
+ let adapter_info = context.adapter.get_info();
+
+ Ok(Self {
+ device,
+ queue,
+ surface,
+ surface_config,
+ pipelines,
+ bind_group_layouts,
+ atlas,
+ atlas_sampler,
+ globals_buffer,
+ path_globals_offset,
+ gamma_offset,
+ globals_bind_group,
+ path_globals_bind_group,
+ instance_buffer,
+ instance_buffer_capacity: initial_instance_buffer_capacity,
+ storage_buffer_alignment,
+ path_intermediate_texture,
+ path_intermediate_view,
+ path_msaa_texture,
+ path_msaa_view,
+ rendering_params,
+ dual_source_blending,
+ adapter_info,
+ transparent_alpha_mode,
+ opaque_alpha_mode,
+ })
+ }
+
+ fn create_bind_group_layouts(device: &wgpu::Device) -> WgpuBindGroupLayouts {
+ let globals =
+ device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+ label: Some("globals_layout"),
+ entries: &[
+ wgpu::BindGroupLayoutEntry {
+ binding: 0,
+ visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
+ ty: wgpu::BindingType::Buffer {
+ ty: wgpu::BufferBindingType::Uniform,
+ has_dynamic_offset: false,
+ min_binding_size: NonZeroU64::new(
+ std::mem::size_of::<GlobalParams>() as u64
+ ),
+ },
+ count: None,
+ },
+ wgpu::BindGroupLayoutEntry {
+ binding: 1,
+ visibility: wgpu::ShaderStages::FRAGMENT,
+ ty: wgpu::BindingType::Buffer {
+ ty: wgpu::BufferBindingType::Uniform,
+ has_dynamic_offset: false,
+ min_binding_size: NonZeroU64::new(
+ std::mem::size_of::<GammaParams>() as u64
+ ),
+ },
+ count: None,
+ },
+ ],
+ });
+
+ let storage_buffer_entry = |binding: u32| wgpu::BindGroupLayoutEntry {
+ binding,
+ visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
+ ty: wgpu::BindingType::Buffer {
+ ty: wgpu::BufferBindingType::Storage { read_only: true },
+ has_dynamic_offset: false,
+ min_binding_size: None,
+ },
+ count: None,
+ };
+
+ let instances = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+ label: Some("instances_layout"),
+ entries: &[storage_buffer_entry(0)],
+ });
+
+ let instances_with_texture =
+ device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+ label: Some("instances_with_texture_layout"),
+ entries: &[
+ storage_buffer_entry(0),
+ wgpu::BindGroupLayoutEntry {
+ binding: 1,
+ visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
+ ty: wgpu::BindingType::Texture {
+ sample_type: wgpu::TextureSampleType::Float { filterable: true },
+ view_dimension: wgpu::TextureViewDimension::D2,
+ multisampled: false,
+ },
+ count: None,
+ },
+ wgpu::BindGroupLayoutEntry {
+ binding: 2,
+ visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
+ ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
+ count: None,
+ },
+ ],
+ });
+
+ let surfaces = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+ label: Some("surfaces_layout"),
+ entries: &[
+ wgpu::BindGroupLayoutEntry {
+ binding: 0,
+ visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
+ ty: wgpu::BindingType::Buffer {
+ ty: wgpu::BufferBindingType::Uniform,
+ has_dynamic_offset: false,
+ min_binding_size: NonZeroU64::new(
+ std::mem::size_of::<SurfaceParams>() as u64
+ ),
+ },
+ count: None,
+ },
+ wgpu::BindGroupLayoutEntry {
+ binding: 1,
+ visibility: wgpu::ShaderStages::FRAGMENT,
+ ty: wgpu::BindingType::Texture {
+ sample_type: wgpu::TextureSampleType::Float { filterable: true },
+ view_dimension: wgpu::TextureViewDimension::D2,
+ multisampled: false,
+ },
+ count: None,
+ },
+ wgpu::BindGroupLayoutEntry {
+ binding: 2,
+ visibility: wgpu::ShaderStages::FRAGMENT,
+ ty: wgpu::BindingType::Texture {
+ sample_type: wgpu::TextureSampleType::Float { filterable: true },
+ view_dimension: wgpu::TextureViewDimension::D2,
+ multisampled: false,
+ },
+ count: None,
+ },
+ wgpu::BindGroupLayoutEntry {
+ binding: 3,
+ visibility: wgpu::ShaderStages::FRAGMENT,
+ ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
+ count: None,
+ },
+ ],
+ });
+
+ WgpuBindGroupLayouts {
+ globals,
+ instances,
+ instances_with_texture,
+ surfaces,
+ }
+ }
+
+ fn create_pipelines(
+ device: &wgpu::Device,
+ layouts: &WgpuBindGroupLayouts,
+ surface_format: wgpu::TextureFormat,
+ alpha_mode: wgpu::CompositeAlphaMode,
+ path_sample_count: u32,
+ dual_source_blending: bool,
+ ) -> WgpuPipelines {
+ let shader_source = include_str!("shaders.wgsl");
+ let shader_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
+ label: Some("gpui_shaders"),
+ source: wgpu::ShaderSource::Wgsl(shader_source.into()),
+ });
+
+ let blend_mode = match alpha_mode {
+ wgpu::CompositeAlphaMode::PreMultiplied => {
+ wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING
+ }
+ _ => wgpu::BlendState::ALPHA_BLENDING,
+ };
+
+ let color_target = wgpu::ColorTargetState {
+ format: surface_format,
+ blend: Some(blend_mode),
+ write_mask: wgpu::ColorWrites::ALL,
+ };
+
+ let create_pipeline = |name: &str,
+ vs_entry: &str,
+ fs_entry: &str,
+ globals_layout: &wgpu::BindGroupLayout,
+ data_layout: &wgpu::BindGroupLayout,
+ topology: wgpu::PrimitiveTopology,
+ color_targets: &[Option<wgpu::ColorTargetState>],
+ sample_count: u32| {
+ let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
+ label: Some(&format!("{name}_layout")),
+ bind_group_layouts: &[globals_layout, data_layout],
+ immediate_size: 0,
+ });
+
+ device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
+ label: Some(name),
+ layout: Some(&pipeline_layout),
+ vertex: wgpu::VertexState {
+ module: &shader_module,
+ entry_point: Some(vs_entry),
+ buffers: &[],
+ compilation_options: wgpu::PipelineCompilationOptions::default(),
+ },
+ fragment: Some(wgpu::FragmentState {
+ module: &shader_module,
+ entry_point: Some(fs_entry),
+ targets: color_targets,
+ compilation_options: wgpu::PipelineCompilationOptions::default(),
+ }),
+ primitive: wgpu::PrimitiveState {
+ topology,
+ strip_index_format: None,
+ front_face: wgpu::FrontFace::Ccw,
+ cull_mode: None,
+ polygon_mode: wgpu::PolygonMode::Fill,
+ unclipped_depth: false,
+ conservative: false,
+ },
+ depth_stencil: None,
+ multisample: wgpu::MultisampleState {
+ count: sample_count,
+ mask: !0,
+ alpha_to_coverage_enabled: false,
+ },
+ multiview_mask: None,
+ cache: None,
+ })
+ };
+
+ let quads = create_pipeline(
+ "quads",
+ "vs_quad",
+ "fs_quad",
+ &layouts.globals,
+ &layouts.instances,
+ wgpu::PrimitiveTopology::TriangleStrip,
+ &[Some(color_target.clone())],
+ 1,
+ );
+
+ let shadows = create_pipeline(
+ "shadows",
+ "vs_shadow",
+ "fs_shadow",
+ &layouts.globals,
+ &layouts.instances,
+ wgpu::PrimitiveTopology::TriangleStrip,
+ &[Some(color_target.clone())],
+ 1,
+ );
+
+ let path_rasterization = create_pipeline(
+ "path_rasterization",
+ "vs_path_rasterization",
+ "fs_path_rasterization",
+ &layouts.globals,
+ &layouts.instances,
+ wgpu::PrimitiveTopology::TriangleList,
+ &[Some(wgpu::ColorTargetState {
+ format: surface_format,
+ blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
+ write_mask: wgpu::ColorWrites::ALL,
+ })],
+ path_sample_count,
+ );
+
+ let paths_blend = wgpu::BlendState {
+ color: wgpu::BlendComponent {
+ src_factor: wgpu::BlendFactor::One,
+ dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
+ operation: wgpu::BlendOperation::Add,
+ },
+ alpha: wgpu::BlendComponent {
+ src_factor: wgpu::BlendFactor::One,
+ dst_factor: wgpu::BlendFactor::One,
+ operation: wgpu::BlendOperation::Add,
+ },
+ };
+
+ let paths = create_pipeline(
+ "paths",
+ "vs_path",
+ "fs_path",
+ &layouts.globals,
+ &layouts.instances_with_texture,
+ wgpu::PrimitiveTopology::TriangleStrip,
+ &[Some(wgpu::ColorTargetState {
+ format: surface_format,
+ blend: Some(paths_blend),
+ write_mask: wgpu::ColorWrites::ALL,
+ })],
+ 1,
+ );
+
+ let underlines = create_pipeline(
+ "underlines",
+ "vs_underline",
+ "fs_underline",
+ &layouts.globals,
+ &layouts.instances,
+ wgpu::PrimitiveTopology::TriangleStrip,
+ &[Some(color_target.clone())],
+ 1,
+ );
+
+ let mono_sprites = create_pipeline(
+ "mono_sprites",
+ "vs_mono_sprite",
+ "fs_mono_sprite",
+ &layouts.globals,
+ &layouts.instances_with_texture,
+ wgpu::PrimitiveTopology::TriangleStrip,
+ &[Some(color_target.clone())],
+ 1,
+ );
+
+ let subpixel_sprites = if dual_source_blending {
+ let subpixel_blend = wgpu::BlendState {
+ color: wgpu::BlendComponent {
+ src_factor: wgpu::BlendFactor::Src1,
+ dst_factor: wgpu::BlendFactor::OneMinusSrc1,
+ operation: wgpu::BlendOperation::Add,
+ },
+ alpha: wgpu::BlendComponent {
+ src_factor: wgpu::BlendFactor::One,
+ dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
+ operation: wgpu::BlendOperation::Add,
+ },
+ };
+
+ Some(create_pipeline(
+ "subpixel_sprites",
+ "vs_subpixel_sprite",
+ "fs_subpixel_sprite",
+ &layouts.globals,
+ &layouts.instances_with_texture,
+ wgpu::PrimitiveTopology::TriangleStrip,
+ &[Some(wgpu::ColorTargetState {
+ format: surface_format,
+ blend: Some(subpixel_blend),
+ write_mask: wgpu::ColorWrites::COLOR,
+ })],
+ 1,
+ ))
+ } else {
+ None
+ };
+
+ let poly_sprites = create_pipeline(
+ "poly_sprites",
+ "vs_poly_sprite",
+ "fs_poly_sprite",
+ &layouts.globals,
+ &layouts.instances_with_texture,
+ wgpu::PrimitiveTopology::TriangleStrip,
+ &[Some(color_target.clone())],
+ 1,
+ );
+
+ let surfaces = create_pipeline(
+ "surfaces",
+ "vs_surface",
+ "fs_surface",
+ &layouts.globals,
+ &layouts.surfaces,
+ wgpu::PrimitiveTopology::TriangleStrip,
+ &[Some(color_target)],
+ 1,
+ );
+
+ WgpuPipelines {
+ quads,
+ shadows,
+ path_rasterization,
+ paths,
+ underlines,
+ mono_sprites,
+ subpixel_sprites,
+ poly_sprites,
+ surfaces,
+ }
+ }
+
+ fn create_path_intermediate(
+ device: &wgpu::Device,
+ format: wgpu::TextureFormat,
+ width: u32,
+ height: u32,
+ ) -> (wgpu::Texture, wgpu::TextureView) {
+ let texture = device.create_texture(&wgpu::TextureDescriptor {
+ label: Some("path_intermediate"),
+ size: wgpu::Extent3d {
+ width: width.max(1),
+ height: height.max(1),
+ depth_or_array_layers: 1,
+ },
+ mip_level_count: 1,
+ sample_count: 1,
+ dimension: wgpu::TextureDimension::D2,
+ format,
+ usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
+ view_formats: &[],
+ });
+ let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
+ (texture, view)
+ }
+
+ fn create_msaa_if_needed(
+ device: &wgpu::Device,
+ format: wgpu::TextureFormat,
+ width: u32,
+ height: u32,
+ sample_count: u32,
+ ) -> Option<(wgpu::Texture, wgpu::TextureView)> {
+ if sample_count <= 1 {
+ return None;
+ }
+ let texture = device.create_texture(&wgpu::TextureDescriptor {
+ label: Some("path_msaa"),
+ size: wgpu::Extent3d {
+ width: width.max(1),
+ height: height.max(1),
+ depth_or_array_layers: 1,
+ },
+ mip_level_count: 1,
+ sample_count,
+ dimension: wgpu::TextureDimension::D2,
+ format,
+ usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
+ view_formats: &[],
+ });
+ let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
+ Some((texture, view))
+ }
+
+ pub fn update_drawable_size(&mut self, size: Size<DevicePixels>) {
+ let width = size.width.0 as u32;
+ let height = size.height.0 as u32;
+
+ if width != self.surface_config.width || height != self.surface_config.height {
+ self.surface_config.width = width.max(1);
+ self.surface_config.height = height.max(1);
+ self.surface.configure(&self.device, &self.surface_config);
+
+ let (path_intermediate_texture, path_intermediate_view) =
+ Self::create_path_intermediate(
+ &self.device,
+ self.surface_config.format,
+ self.surface_config.width,
+ self.surface_config.height,
+ );
+ self.path_intermediate_texture = path_intermediate_texture;
+ self.path_intermediate_view = path_intermediate_view;
+
+ let (path_msaa_texture, path_msaa_view) = Self::create_msaa_if_needed(
+ &self.device,
+ self.surface_config.format,
+ self.surface_config.width,
+ self.surface_config.height,
+ self.rendering_params.path_sample_count,
+ )
+ .map(|(t, v)| (Some(t), Some(v)))
+ .unwrap_or((None, None));
+ self.path_msaa_texture = path_msaa_texture;
+ self.path_msaa_view = path_msaa_view;
+ }
+ }
+
+ pub fn update_transparency(&mut self, transparent: bool) {
+ let new_alpha_mode = if transparent {
+ self.transparent_alpha_mode
+ } else {
+ self.opaque_alpha_mode
+ };
+
+ if new_alpha_mode != self.surface_config.alpha_mode {
+ self.surface_config.alpha_mode = new_alpha_mode;
+ self.surface.configure(&self.device, &self.surface_config);
+ self.pipelines = Self::create_pipelines(
+ &self.device,
+ &self.bind_group_layouts,
+ self.surface_config.format,
+ self.surface_config.alpha_mode,
+ self.rendering_params.path_sample_count,
+ self.dual_source_blending,
+ );
+ }
+ }
+
+ #[allow(dead_code)]
+ pub fn viewport_size(&self) -> Size<DevicePixels> {
+ Size {
+ width: DevicePixels(self.surface_config.width as i32),
+ height: DevicePixels(self.surface_config.height as i32),
+ }
+ }
+
+ pub fn sprite_atlas(&self) -> &Arc<WgpuAtlas> {
+ &self.atlas
+ }
+
+ pub fn gpu_specs(&self) -> GpuSpecs {
+ GpuSpecs {
+ is_software_emulated: self.adapter_info.device_type == wgpu::DeviceType::Cpu,
+ device_name: self.adapter_info.name.clone(),
+ driver_name: self.adapter_info.driver.clone(),
+ driver_info: self.adapter_info.driver_info.clone(),
+ }
+ }
+
+ pub fn draw(&mut self, scene: &Scene) {
+ self.atlas.before_frame();
+
+ let frame = match self.surface.get_current_texture() {
+ Ok(frame) => frame,
+ Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
+ self.surface.configure(&self.device, &self.surface_config);
+ return;
+ }
+ Err(e) => {
+ log::error!("Failed to acquire surface texture: {e}");
+ return;
+ }
+ };
+ let frame_view = frame
+ .texture
+ .create_view(&wgpu::TextureViewDescriptor::default());
+
+ let gamma_params = GammaParams {
+ gamma_ratios: self.rendering_params.gamma_ratios,
+ grayscale_enhanced_contrast: self.rendering_params.grayscale_enhanced_contrast,
+ subpixel_enhanced_contrast: self.rendering_params.subpixel_enhanced_contrast,
+ _pad: [0.0; 2],
+ };
+
+ let globals = GlobalParams {
+ viewport_size: [
+ self.surface_config.width as f32,
+ self.surface_config.height as f32,
+ ],
+ premultiplied_alpha: if self.surface_config.alpha_mode
+ == wgpu::CompositeAlphaMode::PreMultiplied
+ {
+ 1
+ } else {
+ 0
+ },
+ pad: 0,
+ };
+
+ let path_globals = GlobalParams {
+ premultiplied_alpha: 0,
+ ..globals
+ };
+
+ self.queue
+ .write_buffer(&self.globals_buffer, 0, bytemuck::bytes_of(&globals));
+ self.queue.write_buffer(
+ &self.globals_buffer,
+ self.path_globals_offset,
+ bytemuck::bytes_of(&path_globals),
+ );
+ self.queue.write_buffer(
+ &self.globals_buffer,
+ self.gamma_offset,
+ bytemuck::bytes_of(&gamma_params),
+ );
+
+ loop {
+ let mut instance_offset: u64 = 0;
+ let mut overflow = false;
+
+ let mut encoder = self
+ .device
+ .create_command_encoder(&wgpu::CommandEncoderDescriptor {
+ label: Some("main_encoder"),
+ });
+
+ {
+ let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
+ label: Some("main_pass"),
+ color_attachments: &[Some(wgpu::RenderPassColorAttachment {
+ view: &frame_view,
+ resolve_target: None,
+ ops: wgpu::Operations {
+ load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
+ store: wgpu::StoreOp::Store,
+ },
+ depth_slice: None,
+ })],
+ depth_stencil_attachment: None,
+ ..Default::default()
+ });
+
+ for batch in scene.batches() {
+ let ok = match batch {
+ PrimitiveBatch::Quads(range) => {
+ self.draw_quads(&scene.quads[range], &mut instance_offset, &mut pass)
+ }
+ PrimitiveBatch::Shadows(range) => self.draw_shadows(
+ &scene.shadows[range],
+ &mut instance_offset,
+ &mut pass,
+ ),
+ PrimitiveBatch::Paths(range) => {
+ let paths = &scene.paths[range];
+ if paths.is_empty() {
+ continue;
+ }
+
+ drop(pass);
+
+ let did_draw = self.draw_paths_to_intermediate(
+ &mut encoder,
+ paths,
+ &mut instance_offset,
+ );
+
+ pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
+ label: Some("main_pass_continued"),
+ color_attachments: &[Some(wgpu::RenderPassColorAttachment {
+ view: &frame_view,
+ resolve_target: None,
+ ops: wgpu::Operations {
+ load: wgpu::LoadOp::Load,
+ store: wgpu::StoreOp::Store,
+ },
+ depth_slice: None,
+ })],
+ depth_stencil_attachment: None,
+ ..Default::default()
+ });
+
+ if did_draw {
+ self.draw_paths_from_intermediate(
+ paths,
+ &mut instance_offset,
+ &mut pass,
+ )
+ } else {
+ false
+ }
+ }
+ PrimitiveBatch::Underlines(range) => self.draw_underlines(
+ &scene.underlines[range],
+ &mut instance_offset,
+ &mut pass,
+ ),
+ PrimitiveBatch::MonochromeSprites { texture_id, range } => self
+ .draw_monochrome_sprites(
+ &scene.monochrome_sprites[range],
+ texture_id,
+ &mut instance_offset,
+ &mut pass,
+ ),
+ PrimitiveBatch::SubpixelSprites { texture_id, range } => self
+ .draw_subpixel_sprites(
+ &scene.subpixel_sprites[range],
+ texture_id,
+ &mut instance_offset,
+ &mut pass,
+ ),
+ PrimitiveBatch::PolychromeSprites { texture_id, range } => self
+ .draw_polychrome_sprites(
+ &scene.polychrome_sprites[range],
+ texture_id,
+ &mut instance_offset,
+ &mut pass,
+ ),
+ PrimitiveBatch::Surfaces(_surfaces) => {
+ // Surfaces are macOS-only for video playback
+ // Not implemented for Linux/wgpu
+ true
+ }
+ };
+ if !ok {
+ overflow = true;
+ break;
+ }
+ }
+ }
+
+ if overflow {
+ drop(encoder);
+ if self.instance_buffer_capacity >= 256 * 1024 * 1024 {
+ log::error!(
+ "instance buffer size grew too large: {}",
+ self.instance_buffer_capacity
+ );
+ frame.present();
+ return;
+ }
+ self.grow_instance_buffer();
+ continue;
+ }
+
+ self.queue.submit(std::iter::once(encoder.finish()));
+ frame.present();
+ return;
+ }
+ }
+
+ fn draw_quads(
+ &self,
+ quads: &[Quad],
+ instance_offset: &mut u64,
+ pass: &mut wgpu::RenderPass<'_>,
+ ) -> bool {
+ let data = unsafe { Self::instance_bytes(quads) };
+ self.draw_instances(
+ data,
+ quads.len() as u32,
+ &self.pipelines.quads,
+ instance_offset,
+ pass,
+ )
+ }
+
+ fn draw_shadows(
+ &self,
+ shadows: &[Shadow],
+ instance_offset: &mut u64,
+ pass: &mut wgpu::RenderPass<'_>,
+ ) -> bool {
+ let data = unsafe { Self::instance_bytes(shadows) };
+ self.draw_instances(
+ data,
+ shadows.len() as u32,
+ &self.pipelines.shadows,
+ instance_offset,
+ pass,
+ )
+ }
+
+ fn draw_underlines(
+ &self,
+ underlines: &[Underline],
+ instance_offset: &mut u64,
+ pass: &mut wgpu::RenderPass<'_>,
+ ) -> bool {
+ let data = unsafe { Self::instance_bytes(underlines) };
+ self.draw_instances(
+ data,
+ underlines.len() as u32,
+ &self.pipelines.underlines,
+ instance_offset,
+ pass,
+ )
+ }
+
+ fn draw_monochrome_sprites(
+ &self,
+ sprites: &[MonochromeSprite],
+ texture_id: AtlasTextureId,
+ instance_offset: &mut u64,
+ pass: &mut wgpu::RenderPass<'_>,
+ ) -> bool {
+ let tex_info = self.atlas.get_texture_info(texture_id);
+ let data = unsafe { Self::instance_bytes(sprites) };
+ self.draw_instances_with_texture(
+ data,
+ sprites.len() as u32,
+ &tex_info.view,
+ &self.pipelines.mono_sprites,
+ instance_offset,
+ pass,
+ )
+ }
+
+ fn draw_subpixel_sprites(
+ &self,
+ sprites: &[SubpixelSprite],
+ texture_id: AtlasTextureId,
+ instance_offset: &mut u64,
+ pass: &mut wgpu::RenderPass<'_>,
+ ) -> bool {
+ let tex_info = self.atlas.get_texture_info(texture_id);
+ let data = unsafe { Self::instance_bytes(sprites) };
+ let pipeline = self
+ .pipelines
+ .subpixel_sprites
+ .as_ref()
+ .unwrap_or(&self.pipelines.mono_sprites);
+ self.draw_instances_with_texture(
+ data,
+ sprites.len() as u32,
+ &tex_info.view,
+ pipeline,
+ instance_offset,
+ pass,
+ )
+ }
+
+ fn draw_polychrome_sprites(
+ &self,
+ sprites: &[PolychromeSprite],
+ texture_id: AtlasTextureId,
+ instance_offset: &mut u64,
+ pass: &mut wgpu::RenderPass<'_>,
+ ) -> bool {
+ let tex_info = self.atlas.get_texture_info(texture_id);
+ let data = unsafe { Self::instance_bytes(sprites) };
+ self.draw_instances_with_texture(
+ data,
+ sprites.len() as u32,
+ &tex_info.view,
+ &self.pipelines.poly_sprites,
+ instance_offset,
+ pass,
+ )
+ }
+
+ fn draw_instances(
+ &self,
+ data: &[u8],
+ instance_count: u32,
+ pipeline: &wgpu::RenderPipeline,
+ instance_offset: &mut u64,
+ pass: &mut wgpu::RenderPass<'_>,
+ ) -> bool {
+ if instance_count == 0 {
+ return true;
+ }
+ let Some((offset, size)) = self.write_to_instance_buffer(instance_offset, data) else {
+ return false;
+ };
+ let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
+ label: None,
+ layout: &self.bind_group_layouts.instances,
+ entries: &[wgpu::BindGroupEntry {
+ binding: 0,
+ resource: self.instance_binding(offset, size),
+ }],
+ });
+ pass.set_pipeline(pipeline);
+ pass.set_bind_group(0, &self.globals_bind_group, &[]);
+ pass.set_bind_group(1, &bind_group, &[]);
+ pass.draw(0..4, 0..instance_count);
+ true
+ }
+
+ fn draw_instances_with_texture(
+ &self,
+ data: &[u8],
+ instance_count: u32,
+ texture_view: &wgpu::TextureView,
+ pipeline: &wgpu::RenderPipeline,
+ instance_offset: &mut u64,
+ pass: &mut wgpu::RenderPass<'_>,
+ ) -> bool {
+ if instance_count == 0 {
+ return true;
+ }
+ let Some((offset, size)) = self.write_to_instance_buffer(instance_offset, data) else {
+ return false;
+ };
+ let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
+ label: None,
+ layout: &self.bind_group_layouts.instances_with_texture,
+ entries: &[
+ wgpu::BindGroupEntry {
+ binding: 0,
+ resource: self.instance_binding(offset, size),
+ },
+ wgpu::BindGroupEntry {
+ binding: 1,
+ resource: wgpu::BindingResource::TextureView(texture_view),
+ },
+ wgpu::BindGroupEntry {
+ binding: 2,
+ resource: wgpu::BindingResource::Sampler(&self.atlas_sampler),
+ },
+ ],
+ });
+ pass.set_pipeline(pipeline);
+ pass.set_bind_group(0, &self.globals_bind_group, &[]);
+ pass.set_bind_group(1, &bind_group, &[]);
+ pass.draw(0..4, 0..instance_count);
+ true
+ }
+
+ unsafe fn instance_bytes<T>(instances: &[T]) -> &[u8] {
+ unsafe {
+ std::slice::from_raw_parts(
+ instances.as_ptr() as *const u8,
+ std::mem::size_of_val(instances),
+ )
+ }
+ }
+
+ fn draw_paths_from_intermediate(
+ &self,
+ paths: &[Path<ScaledPixels>],
+ instance_offset: &mut u64,
+ pass: &mut wgpu::RenderPass<'_>,
+ ) -> bool {
+ let first_path = &paths[0];
+ let sprites: Vec<PathSprite> = if paths.last().map(|p| &p.order) == Some(&first_path.order)
+ {
+ paths
+ .iter()
+ .map(|p| PathSprite {
+ bounds: p.clipped_bounds(),
+ })
+ .collect()
+ } else {
+ let mut bounds = first_path.clipped_bounds();
+ for path in paths.iter().skip(1) {
+ bounds = bounds.union(&path.clipped_bounds());
+ }
+ vec![PathSprite { bounds }]
+ };
+
+ let sprite_data = unsafe { Self::instance_bytes(&sprites) };
+ self.draw_instances_with_texture(
+ sprite_data,
+ sprites.len() as u32,
+ &self.path_intermediate_view,
+ &self.pipelines.paths,
+ instance_offset,
+ pass,
+ )
+ }
+
+ fn draw_paths_to_intermediate(
+ &self,
+ encoder: &mut wgpu::CommandEncoder,
+ paths: &[Path<ScaledPixels>],
+ instance_offset: &mut u64,
+ ) -> bool {
+ let mut vertices = Vec::new();
+ for path in paths {
+ let bounds = path.clipped_bounds();
+ vertices.extend(path.vertices.iter().map(|v| PathRasterizationVertex {
+ xy_position: v.xy_position,
+ st_position: v.st_position,
+ color: path.color,
+ bounds,
+ }));
+ }
+
+ if vertices.is_empty() {
+ return true;
+ }
+
+ let vertex_data = unsafe { Self::instance_bytes(&vertices) };
+ let Some((vertex_offset, vertex_size)) =
+ self.write_to_instance_buffer(instance_offset, vertex_data)
+ else {
+ return false;
+ };
+
+ let data_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
+ label: Some("path_rasterization_bind_group"),
+ layout: &self.bind_group_layouts.instances,
+ entries: &[wgpu::BindGroupEntry {
+ binding: 0,
+ resource: self.instance_binding(vertex_offset, vertex_size),
+ }],
+ });
+
+ let (target_view, resolve_target) = if let Some(ref msaa_view) = self.path_msaa_view {
+ (msaa_view, Some(&self.path_intermediate_view))
+ } else {
+ (&self.path_intermediate_view, None)
+ };
+
+ {
+ let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
+ label: Some("path_rasterization_pass"),
+ color_attachments: &[Some(wgpu::RenderPassColorAttachment {
+ view: target_view,
+ resolve_target,
+ ops: wgpu::Operations {
+ load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
+ store: wgpu::StoreOp::Store,
+ },
+ depth_slice: None,
+ })],
+ depth_stencil_attachment: None,
+ ..Default::default()
+ });
+
+ pass.set_pipeline(&self.pipelines.path_rasterization);
+ pass.set_bind_group(0, &self.path_globals_bind_group, &[]);
+ pass.set_bind_group(1, &data_bind_group, &[]);
+ pass.draw(0..vertices.len() as u32, 0..1);
+ }
+
+ true
+ }
+
+ fn grow_instance_buffer(&mut self) {
+ let new_capacity = self.instance_buffer_capacity * 2;
+ log::info!("increased instance buffer size to {}", new_capacity);
+ self.instance_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
+ label: Some("instance_buffer"),
+ size: new_capacity,
+ usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
+ mapped_at_creation: false,
+ });
+ self.instance_buffer_capacity = new_capacity;
+ }
+
+ fn write_to_instance_buffer(
+ &self,
+ instance_offset: &mut u64,
+ data: &[u8],
+ ) -> Option<(u64, NonZeroU64)> {
+ let offset = (*instance_offset).next_multiple_of(self.storage_buffer_alignment);
+ let size = (data.len() as u64).max(16);
+ if offset + size > self.instance_buffer_capacity {
+ return None;
+ }
+ self.queue.write_buffer(&self.instance_buffer, offset, data);
+ *instance_offset = offset + size;
+ Some((offset, NonZeroU64::new(size).expect("size is at least 16")))
+ }
+
+ fn instance_binding(&self, offset: u64, size: NonZeroU64) -> wgpu::BindingResource<'_> {
+ wgpu::BindingResource::Buffer(wgpu::BufferBinding {
+ buffer: &self.instance_buffer,
+ offset,
+ size: Some(size),
+ })
+ }
+
+ pub fn destroy(&mut self) {
+ // wgpu resources are automatically cleaned up when dropped
+ }
+}
+
+struct RenderingParameters {
+ path_sample_count: u32,
+ gamma_ratios: [f32; 4],
+ grayscale_enhanced_contrast: f32,
+ subpixel_enhanced_contrast: f32,
+}
+
+impl RenderingParameters {
+ fn new(adapter: &wgpu::Adapter, surface_format: wgpu::TextureFormat) -> Self {
+ use std::env;
+
+ let format_features = adapter.get_texture_format_features(surface_format);
+ let path_sample_count = [4, 2, 1]
+ .into_iter()
+ .find(|&n| format_features.flags.sample_count_supported(n))
+ .unwrap_or(1);
+
+ let gamma = env::var("ZED_FONTS_GAMMA")
+ .ok()
+ .and_then(|v| v.parse().ok())
+ .unwrap_or(1.8_f32)
+ .clamp(1.0, 2.2);
+ let gamma_ratios = get_gamma_correction_ratios(gamma);
+
+ let grayscale_enhanced_contrast = env::var("ZED_FONTS_GRAYSCALE_ENHANCED_CONTRAST")
+ .ok()
+ .and_then(|v| v.parse().ok())
+ .unwrap_or(1.0_f32)
+ .max(0.0);
+
+ let subpixel_enhanced_contrast = env::var("ZED_FONTS_SUBPIXEL_ENHANCED_CONTRAST")
+ .ok()
+ .and_then(|v| v.parse().ok())
+ .unwrap_or(0.5_f32)
+ .max(0.0);
+
+ Self {
+ path_sample_count,
+ gamma_ratios,
+ grayscale_enhanced_contrast,
+ subpixel_enhanced_contrast,
+ }
+ }
+}
@@ -27,7 +27,7 @@ parts:
stage-packages:
- libasound2t64
# snapcraft has a lint that this is unused, but without it Zed exits with
- # "Missing Vulkan entry points: LibraryLoadFailure" in blade_graphics.
+ # "Missing Vulkan entry points: LibraryLoadFailure" in wgpu.
- libvulkan1
# snapcraft has a lint that this is unused, but without it Zed exits with
# "NoWaylandLib" when run with Wayland.
@@ -38,7 +38,7 @@ const DEFAULT_FILTERS: &[(&str, log::LevelFilter)] = &[
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
("zbus", log::LevelFilter::Warn),
#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "windows"))]
- ("blade_graphics", log::LevelFilter::Warn),
+ ("wgpu", log::LevelFilter::Warn),
#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "windows"))]
("naga::back::spv::writer", log::LevelFilter::Warn),
// usvg prints a lot of warnings on rendering an SVG with partial errors, which
@@ -160,8 +160,6 @@ On some systems the file `/etc/prime-discrete` can be used to enforce the use of
On others, you may be able to the environment variable `DRI_PRIME=1` when running Zed to force the use of the discrete GPU.
-If you're using an AMD GPU and Zed crashes when selecting long lines, try setting the `ZED_PATH_SAMPLE_COUNT=0` environment variable. (See [#26143](https://github.com/zed-industries/zed/issues/26143))
-
If you're using an AMD GPU, you might get a 'Broken Pipe' error. Try using the RADV or Mesa drivers. (See [#13880](https://github.com/zed-industries/zed/issues/13880))
If you are using `amdvlk`, the default open-source AMD graphics driver, you may find that Zed consistently fails to launch. This is a known issue for some users, for example on Omarchy (see issue [#28851](https://github.com/zed-industries/zed/issues/28851)). To fix this, you will need to use a different driver. We recommend removing the `amdvlk` and `lib32-amdvlk` packages and installing `vulkan-radeon` instead (see issue [#14141](https://github.com/zed-industries/zed/issues/14141)).
@@ -216,7 +214,7 @@ Additionally, it is extremely beneficial to provide the contents of your Zed log
```sh
truncate -s 0 ~/.local/share/zed/logs/Zed.log # Clear the log file
-ZED_LOG=blade_graphics=info zed .
+ZED_LOG=wgpu=info zed .
cat ~/.local/share/zed/logs/Zed.log
# copy the output
```
@@ -224,7 +222,7 @@ cat ~/.local/share/zed/logs/Zed.log
Or, if you have the Zed cli setup, you can do
```sh
-ZED_LOG=blade_graphics=info /path/to/zed/cli --foreground .
+ZED_LOG=wgpu=info /path/to/zed/cli --foreground .
# copy the output
```
@@ -384,7 +382,7 @@ Replace `192` with your desired DPI value. This affects the system globally and
### Font rendering parameters
-When using Blade rendering (Linux platforms and self-compiled builds with the Blade renderer enabled), Zed reads `ZED_FONTS_GAMMA` and `ZED_FONTS_GRAYSCALE_ENHANCED_CONTRAST` environment variables for the values to use for font rendering.
+On Linux, Zed reads `ZED_FONTS_GAMMA` and `ZED_FONTS_GRAYSCALE_ENHANCED_CONTRAST` environment variables for the values to use for font rendering.
`ZED_FONTS_GAMMA` corresponds to [getgamma](https://learn.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwriterenderingparams-getgamma) values.
Allowed range [1.0, 2.2], other values are clipped.