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.