From af8ea0d6c26192c45f44f473c0d4a7d6f72ed018 Mon Sep 17 00:00:00 2001 From: Leonard Seibold Date: Fri, 13 Feb 2026 08:55:42 +0100 Subject: [PATCH] gpui: Remove blade, reimplement linux renderer with wgpu (#46758) The blade graphics library is a mess and causes several issues for both Zed users as well as other 3rd party apps using GPUI. This PR removes blade and implements the linux platform using `wgpu` which is the de-facto standard in the rust UI and graphics ecosystem. This will not just fix [issues that Zed users have today](https://github.com/YaLTeR/niri/issues/2335), but also profit from wgpu improvements in the futures, from other projects contributing (such as the bevy game engine, Iced, or pretty much every other relevant project). This will close several related issues on the zed repo as well. See https://github.com/zed-industries/zed/issues?q=frozen%20nvidia%20linux (probably not all of them, have only tested the freeze on nvidia and Smithay-based wayland compositors). Some related issues: https://github.com/zed-industries/zed/issues/44814 https://github.com/zed-industries/zed/issues/40481 https://github.com/YaLTeR/niri/issues/2335 https://github.com/zortax/zlaunch/issues/15 Would appreciate feedback if this is something the zed maintainers would be interested in. Release Notes: - N/A --------- Co-authored-by: John Tur --- Cargo.lock | 499 +++--- Cargo.toml | 25 +- crates/gpui/Cargo.toml | 32 +- crates/gpui/build.rs | 51 +- crates/gpui/src/platform.rs | 19 +- crates/gpui/src/platform/blade.rs | 11 - .../gpui/src/platform/blade/apple_compat.rs | 60 - crates/gpui/src/platform/blade/blade_atlas.rs | 395 ----- .../gpui/src/platform/blade/blade_context.rs | 85 - .../gpui/src/platform/blade/blade_renderer.rs | 1121 ------------- crates/gpui/src/platform/linux/platform.rs | 11 +- .../gpui/src/platform/linux/wayland/client.rs | 6 +- .../gpui/src/platform/linux/wayland/window.rs | 30 +- crates/gpui/src/platform/linux/x11/client.rs | 6 +- crates/gpui/src/platform/linux/x11/window.rs | 41 +- crates/gpui/src/platform/mac.rs | 6 - crates/gpui/src/platform/mac/window.rs | 4 - crates/gpui/src/platform/wgpu.rs | 7 + .../src/platform/{blade => wgpu}/shaders.wgsl | 45 +- crates/gpui/src/platform/wgpu/wgpu_atlas.rs | 320 ++++ crates/gpui/src/platform/wgpu/wgpu_context.rs | 169 ++ .../gpui/src/platform/wgpu/wgpu_renderer.rs | 1390 +++++++++++++++++ crates/zed/resources/snap/snapcraft.yaml.in | 2 +- crates/zlog/src/filter.rs | 2 +- docs/src/linux.md | 8 +- 25 files changed, 2315 insertions(+), 2030 deletions(-) delete mode 100644 crates/gpui/src/platform/blade.rs delete mode 100644 crates/gpui/src/platform/blade/apple_compat.rs delete mode 100644 crates/gpui/src/platform/blade/blade_atlas.rs delete mode 100644 crates/gpui/src/platform/blade/blade_context.rs delete mode 100644 crates/gpui/src/platform/blade/blade_renderer.rs create mode 100644 crates/gpui/src/platform/wgpu.rs rename crates/gpui/src/platform/{blade => wgpu}/shaders.wgsl (97%) create mode 100644 crates/gpui/src/platform/wgpu/wgpu_atlas.rs create mode 100644 crates/gpui/src/platform/wgpu/wgpu_context.rs create mode 100644 crates/gpui/src/platform/wgpu/wgpu_renderer.rs diff --git a/Cargo.lock b/Cargo.lock index 38d70b9dbfc439700145f22ac8350e110f0534d4..f26ed1e1261e48386108e950dd1077e7795a1470 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index f8a6ee68c21fe2e3e921b692bffb23ca7fc4f6d9..3c34f6ec3a2e34e42240f96f2715bd0b601adce9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 60aa9fb43799b09428e04d31b85d4a6d9ee9a433..120cd00d3552cab59103c66bcbf3cff9e6b3e599 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -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", diff --git a/crates/gpui/build.rs b/crates/gpui/build.rs index 67032a9afdf7c2a234da80b940732783efcd966a..9363128fc26d7a87f2242e38d0e8a30ed72b3b0e 100644 --- a/crates/gpui/build.rs +++ b/crates/gpui/build.rs @@ -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() { diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index f8107760b9e810347fbfa60248fe5f6a69beb04d..1043ebdff4aa8b1af234a5e063e84200065c67cc 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -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; diff --git a/crates/gpui/src/platform/blade.rs b/crates/gpui/src/platform/blade.rs deleted file mode 100644 index 9d966d8a4e069a1c5ad904930f7fa9364b501e04..0000000000000000000000000000000000000000 --- a/crates/gpui/src/platform/blade.rs +++ /dev/null @@ -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::*; diff --git a/crates/gpui/src/platform/blade/apple_compat.rs b/crates/gpui/src/platform/blade/apple_compat.rs deleted file mode 100644 index a75ddfa69a3daa2e43eaf00673a34d8c22e1cd25..0000000000000000000000000000000000000000 --- a/crates/gpui/src/platform/blade/apple_compat.rs +++ /dev/null @@ -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, - 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::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::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() -} diff --git a/crates/gpui/src/platform/blade/blade_atlas.rs b/crates/gpui/src/platform/blade/blade_atlas.rs deleted file mode 100644 index 3a02564ead6e11f64dba20d1c31db0cc5af8f358..0000000000000000000000000000000000000000 --- a/crates/gpui/src/platform/blade/blade_atlas.rs +++ /dev/null @@ -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); - -struct PendingUpload { - id: AtlasTextureId, - bounds: Bounds, - data: gpu::BufferPiece, -} - -struct BladeAtlasState { - gpu: Arc, - upload_belt: BufferBelt, - storage: BladeAtlasStorage, - tiles_by_key: FxHashMap, - initializations: Vec, - uploads: Vec, -} - -#[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) -> 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, Cow<'a, [u8]>)>>, - ) -> Result> { - 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, 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, - kind: AtlasTextureKind, - ) -> &mut BladeAtlasTexture { - const DEFAULT_ATLAS_SIZE: Size = 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, 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, - subpixel_textures: AtlasTextureList, - polychrome_textures: AtlasTextureList, -} - -impl ops::Index for BladeAtlasStorage { - type Output = AtlasTextureList; - 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 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 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) -> Option { - 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> for etagere::Size { - fn from(size: Size) -> Self { - etagere::Size::new(size.width.into(), size.height.into()) - } -} - -impl From for Point { - fn from(value: etagere::Point) -> Self { - Point { - x: DevicePixels::from(value.x), - y: DevicePixels::from(value.y), - } - } -} - -impl From for Size { - fn from(size: etagere::Size) -> Self { - Size { - width: DevicePixels::from(size.width), - height: DevicePixels::from(size.height), - } - } -} - -impl From for Bounds { - fn from(rectangle: etagere::Rectangle) -> Self { - Bounds { - origin: rectangle.min.into(), - size: rectangle.size().into(), - } - } -} diff --git a/crates/gpui/src/platform/blade/blade_context.rs b/crates/gpui/src/platform/blade/blade_context.rs deleted file mode 100644 index 5a5382c9c44e64bddac1a457191ecb6c98ffbff7..0000000000000000000000000000000000000000 --- a/crates/gpui/src/platform/blade/blade_context.rs +++ /dev/null @@ -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, -} - -impl BladeContext { - pub fn new() -> anyhow::Result { - 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 { - 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(), - ); - } -} diff --git a/crates/gpui/src/platform/blade/blade_renderer.rs b/crates/gpui/src/platform/blade/blade_renderer.rs deleted file mode 100644 index 4d1afa1763a9acbdfd7b0d60db76f84094dedab9..0000000000000000000000000000000000000000 --- a/crates/gpui/src/platform/blade/blade_renderer.rs +++ /dev/null @@ -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> for PodBounds { - fn from(bounds: Bounds) -> 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, -} - -#[derive(Clone, Debug)] -#[repr(C)] -struct PathRasterizationVertex { - xy_position: Point, - st_position: Point, - color: Background, - bounds: Bounds, -} - -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::(); - shader.check_struct_size::(); - shader.check_struct_size::(); - shader.check_struct_size::(); - shader.check_struct_size::(); - shader.check_struct_size::(); - shader.check_struct_size::(); - shader.check_struct_size::(); - shader.check_struct_size::(); - - // 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, - surface: gpu::Surface, - surface_config: gpu::SurfaceConfig, - command_encoder: gpu::CommandEncoder, - last_sync_point: Option, - pipelines: BladePipelines, - instance_belt: BufferBelt, - atlas: Arc, - 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, - path_intermediate_msaa_texture_view: Option, - rendering_parameters: RenderingParameters, -} - -impl BladeRenderer { - pub fn new( - context: &BladeContext, - window: &I, - config: BladeSurfaceConfig, - ) -> anyhow::Result { - 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) { - 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) { - self.update_drawable_size_impl(size, true); - } - - fn update_drawable_size_impl(&mut self, size: Size, 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 { - &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], - 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 { - 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, - } - } -} diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs index 4ed42608d73b7a875857d01687a4fd095eceb098..429c7c86035f01233e3f7612d35a855e48f2fd5d 100644 --- a/crates/gpui/src/platform/linux/platform.rs +++ b/crates/gpui/src/platform/linux/platform.rs @@ -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, b: Point) -> bool { let diff = a - b; diff.x.abs() <= DOUBLE_CLICK_DISTANCE && diff.y.abs() <= DOUBLE_CLICK_DISTANCE diff --git a/crates/gpui/src/platform/linux/wayland/client.rs b/crates/gpui/src/platform/linux/wayland/client.rs index c88067788208830b43aa17f69ff17c42dcac6d4c..41f12916b971d173181225dce185872f4dba6c72 100644 --- a/crates/gpui/src/platform/linux/wayland/client.rs +++ b/crates/gpui/src/platform/linux/wayland/client.rs @@ -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_keyboard: Option, @@ -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( diff --git a/crates/gpui/src/platform/linux/wayland/window.rs b/crates/gpui/src/platform/linux/wayland/window.rs index 7adaf055d94bdd241ca6e8db82720191e337bcd0..7642b93ffe1b8fc7ee9d227fe3711704a370ce87 100644 --- a/crates/gpui/src/platform/linux/wayland/window.rs +++ b/crates/gpui/src/platform/linux/wayland/window.rs @@ -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::HandleError> { let window = NonNull::new(self.window).unwrap(); @@ -97,7 +102,7 @@ pub struct WaylandWindowState { outputs: HashMap, display: Option<(ObjectId, Output)>, globals: Globals, - renderer: BladeRenderer, + renderer: WgpuRenderer, bounds: Bounds, scale: f32, input_handler: Option, @@ -314,7 +319,7 @@ impl WaylandWindowState { viewport: Option, client: WaylandClientStatePtr, globals: Globals, - gpu_context: &BladeContext, + gpu_context: &WgpuContext, options: WindowParams, parent: Option, ) -> anyhow::Result { @@ -328,15 +333,14 @@ impl WaylandWindowState { .display_ptr() .cast::(), }; - 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, diff --git a/crates/gpui/src/platform/linux/x11/client.rs b/crates/gpui/src/platform/linux/x11/client.rs index f470dc6b209ab9b390caad9bc31fedfafddf8fc8..08d756d3620e0ec63ba562646f78c2d0f059e78d 100644 --- a/crates/gpui/src/platform/linux/x11/client.rs +++ b/crates/gpui/src/platform/linux/x11/client.rs @@ -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, 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")?; diff --git a/crates/gpui/src/platform/linux/x11/window.rs b/crates/gpui/src/platform/linux/x11/window.rs index ee29f0d103d808b4db064969b992d2af75c1a187..93a9003be641e0f7bb44e324672c1992ec5e2d28 100644 --- a/crates/gpui/src/platform/linux/x11/window.rs +++ b/crates/gpui/src/platform/linux/x11/window.rs @@ -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, x_window: xproto::Window, -) -> anyhow::Result { +) -> anyhow::Result> { 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>, @@ -261,7 +265,7 @@ pub struct X11WindowState { pub(crate) last_sync_counter: Option, bounds: Bounds, scale_factor: f32, - renderer: BladeRenderer, + renderer: WgpuRenderer, display: Rc, input_handler: Option, appearance: WindowAppearance, @@ -389,7 +393,7 @@ impl X11WindowState { handle: AnyWindowHandle, client: X11ClientStatePtr, executor: ForegroundExecutor, - gpu_context: &BladeContext, + gpu_context: &WgpuContext, params: WindowParams, xcb: &Rc, 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 { - 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, 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() { diff --git a/crates/gpui/src/platform/mac.rs b/crates/gpui/src/platform/mac.rs index a229ec7dce928597ec73b1f4be50edd1ea3e5114..1c019b8ccebb7cf9dbd03fbf47055bf3a6518d20 100644 --- a/crates/gpui/src/platform/mac.rs +++ b/crates/gpui/src/platform/mac.rs @@ -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; diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 5d067c1ba0366fa930da68eb68a52301f271b056..5a93fe0fd570c1980b6ec104592a7726942a5fd0 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -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(); } diff --git a/crates/gpui/src/platform/wgpu.rs b/crates/gpui/src/platform/wgpu.rs new file mode 100644 index 0000000000000000000000000000000000000000..cb1bafe04bae1783a6898debb76a2aa8ccd37072 --- /dev/null +++ b/crates/gpui/src/platform/wgpu.rs @@ -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::*; diff --git a/crates/gpui/src/platform/blade/shaders.wgsl b/crates/gpui/src/platform/wgpu/shaders.wgsl similarity index 97% rename from crates/gpui/src/platform/blade/shaders.wgsl rename to crates/gpui/src/platform/wgpu/shaders.wgsl index 95d6ac76b436953fe709579c047d6d2543048f60..58e9de109e6602d999433aa9b42d3b80d06ca4ad 100644 --- a/crates/gpui/src/platform/blade/shaders.wgsl +++ b/crates/gpui/src/platform/wgpu/shaders.wgsl @@ -84,12 +84,17 @@ struct GlobalParams { pad: u32, } -var globals: GlobalParams; -var gamma_ratios: vec4; -var grayscale_enhanced_contrast: f32; -var subpixel_enhanced_contrast: f32; -var t_sprite: texture_2d; -var s_sprite: sampler; +struct GammaParams { + gamma_ratios: vec4, + grayscale_enhanced_contrast: f32, + subpixel_enhanced_contrast: f32, + pad: vec2, +} + +@group(0) @binding(0) var globals: GlobalParams; +@group(0) @binding(1) var gamma_params: GammaParams; +@group(1) @binding(1) var t_sprite: texture_2d; +@group(1) @binding(2) var s_sprite: sampler; const M_PI_F: f32 = 3.1415926; const GRAYSCALE_FACTORS: vec3 = vec3(0.2126, 0.7152, 0.0722); @@ -521,7 +526,7 @@ struct Quad { corner_radii: Corners, border_widths: Edges, } -var b_quads: array; +@group(1) @binding(0) var b_quads: array; struct QuadVarying { @builtin(position) position: vec4, @@ -951,7 +956,7 @@ struct Shadow { content_mask: Bounds, color: Hsla, } -var b_shadows: array; +@group(1) @binding(0) var b_shadows: array; struct ShadowVarying { @builtin(position) position: vec4, @@ -1023,7 +1028,7 @@ struct PathRasterizationVertex { bounds: Bounds, } -var b_path_vertices: array; +@group(1) @binding(0) var b_path_vertices: array; struct PathRasterizationVarying { @builtin(position) position: vec4, @@ -1083,7 +1088,7 @@ fn fs_path_rasterization(input: PathRasterizationVarying) -> @location(0) vec4 b_path_sprites: array; +@group(1) @binding(0) var b_path_sprites: array; struct PathVarying { @builtin(position) position: vec4, @@ -1124,7 +1129,7 @@ struct Underline { thickness: f32, wavy: u32, } -var b_underlines: array; +@group(1) @binding(0) var b_underlines: array; struct UnderlineVarying { @builtin(position) position: vec4, @@ -1190,7 +1195,7 @@ struct MonochromeSprite { tile: AtlasTile, transformation: TransformationMatrix, } -var b_mono_sprites: array; +@group(1) @binding(0) var b_mono_sprites: array; struct MonoSpriteVarying { @builtin(position) position: vec4, @@ -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 { 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(0.0))) { @@ -1238,7 +1243,7 @@ struct PolychromeSprite { corner_radii: Corners, tile: AtlasTile, } -var b_poly_sprites: array; +@group(1) @binding(0) var b_poly_sprites: array; struct PolySpriteVarying { @builtin(position) position: vec4, @@ -1286,10 +1291,10 @@ struct SurfaceParams { content_mask: Bounds, } -var surface_locals: SurfaceParams; -var t_y: texture_2d; -var t_cb_cr: texture_2d; -var s_surface: sampler; +@group(1) @binding(0) var surface_locals: SurfaceParams; +@group(1) @binding(1) var t_y: texture_2d; +@group(1) @binding(2) var t_cb_cr: texture_2d; +@group(1) @binding(3) var s_surface: sampler; const ycbcr_to_RGB = mat4x4( vec4( 1.0000f, 1.0000f, 1.0000f, 0.0), @@ -1341,7 +1346,7 @@ struct SubpixelSprite { tile: AtlasTile, transformation: TransformationMatrix, } -var b_subpixel_sprites: array; +@group(1) @binding(0) var b_subpixel_sprites: array; struct SubpixelSpriteOutput { @builtin(position) position: vec4, @@ -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(0.0))) { diff --git a/crates/gpui/src/platform/wgpu/wgpu_atlas.rs b/crates/gpui/src/platform/wgpu/wgpu_atlas.rs new file mode 100644 index 0000000000000000000000000000000000000000..f9e4aecc370434cc659afc75e2abd64d7202c98b --- /dev/null +++ b/crates/gpui/src/platform/wgpu/wgpu_atlas.rs @@ -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) -> etagere::Size { + size2(size.width.0, size.height.0) +} + +fn etagere_point_to_device(point: etagere::Point) -> Point { + Point { + x: DevicePixels(point.x), + y: DevicePixels(point.y), + } +} + +pub(crate) struct WgpuAtlas(Mutex); + +struct PendingUpload { + id: AtlasTextureId, + bounds: Bounds, + data: Vec, +} + +struct WgpuAtlasState { + device: Arc, + queue: Arc, + storage: WgpuAtlasStorage, + tiles_by_key: FxHashMap, + pending_uploads: Vec, +} + +pub struct WgpuTextureInfo { + pub view: wgpu::TextureView, +} + +impl WgpuAtlas { + pub(crate) fn new(device: Arc, queue: Arc) -> 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, Cow<'a, [u8]>)>>, + ) -> Result> { + 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, 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, + kind: AtlasTextureKind, + ) -> &mut WgpuAtlasTexture { + const DEFAULT_ATLAS_SIZE: Size = 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, 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, + subpixel_textures: AtlasTextureList, + polychrome_textures: AtlasTextureList, +} + +impl ops::Index for WgpuAtlasStorage { + type Output = AtlasTextureList; + 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 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 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) -> Option { + 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 + } +} diff --git a/crates/gpui/src/platform/wgpu/wgpu_context.rs b/crates/gpui/src/platform/wgpu/wgpu_context.rs new file mode 100644 index 0000000000000000000000000000000000000000..b0de623f0e9d611863825f2aa446d1e120a7091e --- /dev/null +++ b/crates/gpui/src/platform/wgpu/wgpu_context.rs @@ -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, + pub queue: Arc, + dual_source_blending: bool, +} + +impl WgpuContext { + pub fn new() -> anyhow::Result { + 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, + ) -> anyhow::Result { + 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 = 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 { + 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(), + ); + } +} diff --git a/crates/gpui/src/platform/wgpu/wgpu_renderer.rs b/crates/gpui/src/platform/wgpu/wgpu_renderer.rs new file mode 100644 index 0000000000000000000000000000000000000000..972d6f586341985e53327e3c7588e4b362f8dfba --- /dev/null +++ b/crates/gpui/src/platform/wgpu/wgpu_renderer.rs @@ -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> for PodBounds { + fn from(bounds: Bounds) -> 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, +} + +#[derive(Clone, Debug)] +#[repr(C)] +struct PathRasterizationVertex { + xy_position: Point, + st_position: Point, + color: Background, + bounds: Bounds, +} + +pub struct WgpuSurfaceConfig { + pub size: Size, + 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, + 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, + queue: Arc, + surface: wgpu::Surface<'static>, + surface_config: wgpu::SurfaceConfiguration, + pipelines: WgpuPipelines, + bind_group_layouts: WgpuBindGroupLayouts, + atlas: Arc, + 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, + path_msaa_view: Option, + 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( + context: &WgpuContext, + window: &W, + config: WgpuSurfaceConfig, + ) -> anyhow::Result { + 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::() as u64; + let gamma_size = std::mem::size_of::() 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::() 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::() 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::() 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], + 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) { + 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 { + Size { + width: DevicePixels(self.surface_config.width as i32), + height: DevicePixels(self.surface_config.height as i32), + } + } + + pub fn sprite_atlas(&self) -> &Arc { + &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(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], + instance_offset: &mut u64, + pass: &mut wgpu::RenderPass<'_>, + ) -> bool { + let first_path = &paths[0]; + let sprites: Vec = 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], + 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, + } + } +} diff --git a/crates/zed/resources/snap/snapcraft.yaml.in b/crates/zed/resources/snap/snapcraft.yaml.in index 4c94a9fd031f79f5f50d4f7bfa3aeade2af35c21..7220b4f16b0b3c73c291d5a6b891a899cfef3a59 100644 --- a/crates/zed/resources/snap/snapcraft.yaml.in +++ b/crates/zed/resources/snap/snapcraft.yaml.in @@ -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. diff --git a/crates/zlog/src/filter.rs b/crates/zlog/src/filter.rs index 0be6f4ead5bf64aa47f7a60391bf377c9998cfb4..a6b6facfe9903a11865ab3e897e144ccde468fe6 100644 --- a/crates/zlog/src/filter.rs +++ b/crates/zlog/src/filter.rs @@ -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 diff --git a/docs/src/linux.md b/docs/src/linux.md index 2e6bdc7d6eb5062074035a2e23d2aa3f06aa1f72..dc8403c64b6df9bd2741af3b3e6b7358e3a8e705 100644 --- a/docs/src/linux.md +++ b/docs/src/linux.md @@ -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.