gpui: Remove blade, reimplement linux renderer with wgpu (#46758)

Leonard Seibold and John Tur created

The blade graphics library is a mess and causes several issues for both
Zed users as well as other 3rd party apps using GPUI. This PR removes
blade and implements the linux platform using `wgpu` which is the
de-facto standard in the rust UI and graphics ecosystem. This will not
just fix [issues that Zed users have
today](https://github.com/YaLTeR/niri/issues/2335), but also profit from
wgpu improvements in the futures, from other projects contributing (such
as the bevy game engine, Iced, or pretty much every other relevant
project).

This will close several related issues on the zed repo as well. See
https://github.com/zed-industries/zed/issues?q=frozen%20nvidia%20linux
(probably not all of them, have only tested the freeze on nvidia and
Smithay-based wayland compositors).

Some related issues:
https://github.com/zed-industries/zed/issues/44814
https://github.com/zed-industries/zed/issues/40481
https://github.com/YaLTeR/niri/issues/2335
https://github.com/zortax/zlaunch/issues/15

Would appreciate feedback if this is something the zed maintainers would
be interested in.

Release Notes:

- N/A

---------

Co-authored-by: John Tur <john-tur@outlook.com>

Change summary

Cargo.lock                                       |  499 ++++--
Cargo.toml                                       |   25 
crates/gpui/Cargo.toml                           |   32 
crates/gpui/build.rs                             |   51 
crates/gpui/src/platform.rs                      |   19 
crates/gpui/src/platform/blade.rs                |   11 
crates/gpui/src/platform/blade/apple_compat.rs   |   60 
crates/gpui/src/platform/blade/blade_atlas.rs    |  395 -----
crates/gpui/src/platform/blade/blade_context.rs  |   85 -
crates/gpui/src/platform/blade/blade_renderer.rs | 1121 --------------
crates/gpui/src/platform/linux/platform.rs       |   11 
crates/gpui/src/platform/linux/wayland/client.rs |    6 
crates/gpui/src/platform/linux/wayland/window.rs |   30 
crates/gpui/src/platform/linux/x11/client.rs     |    6 
crates/gpui/src/platform/linux/x11/window.rs     |   41 
crates/gpui/src/platform/mac.rs                  |    6 
crates/gpui/src/platform/mac/window.rs           |    4 
crates/gpui/src/platform/wgpu.rs                 |    7 
crates/gpui/src/platform/wgpu/shaders.wgsl       |   45 
crates/gpui/src/platform/wgpu/wgpu_atlas.rs      |  320 ++++
crates/gpui/src/platform/wgpu/wgpu_context.rs    |  169 ++
crates/gpui/src/platform/wgpu/wgpu_renderer.rs   | 1390 ++++++++++++++++++
crates/zed/resources/snap/snapcraft.yaml.in      |    2 
crates/zlog/src/filter.rs                        |    2 
docs/src/linux.md                                |    8 
25 files changed, 2,315 insertions(+), 2,030 deletions(-)

Detailed changes

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"

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"

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",

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() {

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;
 

crates/gpui/src/platform/blade.rs 🔗

@@ -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::*;

crates/gpui/src/platform/blade/apple_compat.rs 🔗

@@ -1,60 +0,0 @@
-use super::{BladeContext, BladeRenderer, BladeSurfaceConfig};
-use blade_graphics as gpu;
-use std::{ffi::c_void, ptr::NonNull};
-
-#[derive(Clone)]
-pub struct Context {
-    inner: BladeContext,
-}
-impl Default for Context {
-    fn default() -> Self {
-        Self {
-            inner: BladeContext::new().unwrap(),
-        }
-    }
-}
-
-pub type Renderer = BladeRenderer;
-
-pub unsafe fn new_renderer(
-    context: Context,
-    _native_window: *mut c_void,
-    native_view: *mut c_void,
-    bounds: crate::Size<f32>,
-    transparent: bool,
-) -> Renderer {
-    use raw_window_handle as rwh;
-    struct RawWindow {
-        view: *mut c_void,
-    }
-
-    impl rwh::HasWindowHandle for RawWindow {
-        fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
-            let view = NonNull::new(self.view).unwrap();
-            let handle = rwh::AppKitWindowHandle::new(view);
-            Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) })
-        }
-    }
-    impl rwh::HasDisplayHandle for RawWindow {
-        fn display_handle(&self) -> Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
-            let handle = rwh::AppKitDisplayHandle::new();
-            Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
-        }
-    }
-
-    BladeRenderer::new(
-        &context.inner,
-        &RawWindow {
-            view: native_view as *mut _,
-        },
-        BladeSurfaceConfig {
-            size: gpu::Extent {
-                width: bounds.width as u32,
-                height: bounds.height as u32,
-                depth: 1,
-            },
-            transparent,
-        },
-    )
-    .unwrap()
-}

crates/gpui/src/platform/blade/blade_atlas.rs 🔗

@@ -1,395 +0,0 @@
-use crate::{
-    AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas,
-    Point, Size, platform::AtlasTextureList,
-};
-use anyhow::Result;
-use blade_graphics as gpu;
-use blade_util::{BufferBelt, BufferBeltDescriptor};
-use collections::FxHashMap;
-use etagere::BucketedAtlasAllocator;
-use parking_lot::Mutex;
-use std::{borrow::Cow, ops, sync::Arc};
-
-pub(crate) struct BladeAtlas(Mutex<BladeAtlasState>);
-
-struct PendingUpload {
-    id: AtlasTextureId,
-    bounds: Bounds<DevicePixels>,
-    data: gpu::BufferPiece,
-}
-
-struct BladeAtlasState {
-    gpu: Arc<gpu::Context>,
-    upload_belt: BufferBelt,
-    storage: BladeAtlasStorage,
-    tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
-    initializations: Vec<AtlasTextureId>,
-    uploads: Vec<PendingUpload>,
-}
-
-#[cfg(gles)]
-unsafe impl Send for BladeAtlasState {}
-
-impl BladeAtlasState {
-    fn destroy(&mut self) {
-        self.storage.destroy(&self.gpu);
-        self.upload_belt.destroy(&self.gpu);
-    }
-}
-
-pub struct BladeTextureInfo {
-    pub raw_view: gpu::TextureView,
-}
-
-impl BladeAtlas {
-    pub(crate) fn new(gpu: &Arc<gpu::Context>) -> Self {
-        BladeAtlas(Mutex::new(BladeAtlasState {
-            gpu: Arc::clone(gpu),
-            upload_belt: BufferBelt::new(BufferBeltDescriptor {
-                memory: gpu::Memory::Upload,
-                min_chunk_size: 0x10000,
-                alignment: 64, // Vulkan `optimalBufferCopyOffsetAlignment` on Intel XE
-            }),
-            storage: BladeAtlasStorage::default(),
-            tiles_by_key: Default::default(),
-            initializations: Vec::new(),
-            uploads: Vec::new(),
-        }))
-    }
-
-    pub(crate) fn destroy(&self) {
-        self.0.lock().destroy();
-    }
-
-    pub fn before_frame(&self, gpu_encoder: &mut gpu::CommandEncoder) {
-        let mut lock = self.0.lock();
-        lock.flush(gpu_encoder);
-    }
-
-    pub fn after_frame(&self, sync_point: &gpu::SyncPoint) {
-        let mut lock = self.0.lock();
-        lock.upload_belt.flush(sync_point);
-    }
-
-    pub fn get_texture_info(&self, id: AtlasTextureId) -> BladeTextureInfo {
-        let lock = self.0.lock();
-        let texture = &lock.storage[id];
-        BladeTextureInfo {
-            raw_view: texture.raw_view,
-        }
-    }
-}
-
-impl PlatformAtlas for BladeAtlas {
-    fn get_or_insert_with<'a>(
-        &self,
-        key: &AtlasKey,
-        build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
-    ) -> Result<Option<AtlasTile>> {
-        let mut lock = self.0.lock();
-        if let Some(tile) = lock.tiles_by_key.get(key) {
-            Ok(Some(tile.clone()))
-        } else {
-            profiling::scope!("new tile");
-            let Some((size, bytes)) = build()? else {
-                return Ok(None);
-            };
-            let tile = lock.allocate(size, key.texture_kind());
-            lock.upload_texture(tile.texture_id, tile.bounds, &bytes);
-            lock.tiles_by_key.insert(key.clone(), tile.clone());
-            Ok(Some(tile))
-        }
-    }
-
-    fn remove(&self, key: &AtlasKey) {
-        let mut lock = self.0.lock();
-
-        let Some(id) = lock.tiles_by_key.remove(key).map(|tile| tile.texture_id) else {
-            return;
-        };
-
-        let Some(texture_slot) = lock.storage[id.kind].textures.get_mut(id.index as usize) else {
-            return;
-        };
-
-        if let Some(mut texture) = texture_slot.take() {
-            texture.decrement_ref_count();
-            if texture.is_unreferenced() {
-                lock.storage[id.kind]
-                    .free_list
-                    .push(texture.id.index as usize);
-                texture.destroy(&lock.gpu);
-            } else {
-                *texture_slot = Some(texture);
-            }
-        }
-    }
-}
-
-impl BladeAtlasState {
-    fn allocate(&mut self, size: Size<DevicePixels>, texture_kind: AtlasTextureKind) -> AtlasTile {
-        {
-            let textures = &mut self.storage[texture_kind];
-
-            if let Some(tile) = textures
-                .iter_mut()
-                .rev()
-                .find_map(|texture| texture.allocate(size))
-            {
-                return tile;
-            }
-        }
-
-        let texture = self.push_texture(size, texture_kind);
-        texture.allocate(size).unwrap()
-    }
-
-    fn push_texture(
-        &mut self,
-        min_size: Size<DevicePixels>,
-        kind: AtlasTextureKind,
-    ) -> &mut BladeAtlasTexture {
-        const DEFAULT_ATLAS_SIZE: Size<DevicePixels> = Size {
-            width: DevicePixels(1024),
-            height: DevicePixels(1024),
-        };
-
-        let size = min_size.max(&DEFAULT_ATLAS_SIZE);
-        let format;
-        let usage;
-        match kind {
-            AtlasTextureKind::Monochrome => {
-                format = gpu::TextureFormat::R8Unorm;
-                usage = gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE;
-            }
-            AtlasTextureKind::Subpixel => {
-                format = gpu::TextureFormat::Bgra8Unorm;
-                usage = gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE;
-            }
-            AtlasTextureKind::Polychrome => {
-                format = gpu::TextureFormat::Bgra8Unorm;
-                usage = gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE;
-            }
-        }
-
-        let raw = self.gpu.create_texture(gpu::TextureDesc {
-            name: "atlas",
-            format,
-            size: gpu::Extent {
-                width: size.width.into(),
-                height: size.height.into(),
-                depth: 1,
-            },
-            array_layer_count: 1,
-            mip_level_count: 1,
-            sample_count: 1,
-            dimension: gpu::TextureDimension::D2,
-            usage,
-            external: None,
-        });
-        let raw_view = self.gpu.create_texture_view(
-            raw,
-            gpu::TextureViewDesc {
-                name: "",
-                format,
-                dimension: gpu::ViewDimension::D2,
-                subresources: &Default::default(),
-            },
-        );
-
-        let texture_list = &mut self.storage[kind];
-        let index = texture_list.free_list.pop();
-
-        let atlas_texture = BladeAtlasTexture {
-            id: AtlasTextureId {
-                index: index.unwrap_or(texture_list.textures.len()) as u32,
-                kind,
-            },
-            allocator: etagere::BucketedAtlasAllocator::new(size.into()),
-            format,
-            raw,
-            raw_view,
-            live_atlas_keys: 0,
-        };
-
-        self.initializations.push(atlas_texture.id);
-
-        if let Some(ix) = index {
-            texture_list.textures[ix] = Some(atlas_texture);
-            texture_list.textures.get_mut(ix).unwrap().as_mut().unwrap()
-        } else {
-            texture_list.textures.push(Some(atlas_texture));
-            texture_list.textures.last_mut().unwrap().as_mut().unwrap()
-        }
-    }
-
-    fn upload_texture(&mut self, id: AtlasTextureId, bounds: Bounds<DevicePixels>, bytes: &[u8]) {
-        let data = self.upload_belt.alloc_bytes(bytes, &self.gpu);
-        self.uploads.push(PendingUpload { id, bounds, data });
-    }
-
-    fn flush_initializations(&mut self, encoder: &mut gpu::CommandEncoder) {
-        for id in self.initializations.drain(..) {
-            let texture = &self.storage[id];
-            encoder.init_texture(texture.raw);
-        }
-    }
-
-    fn flush(&mut self, encoder: &mut gpu::CommandEncoder) {
-        self.flush_initializations(encoder);
-
-        let mut transfers = encoder.transfer("atlas");
-        for upload in self.uploads.drain(..) {
-            let texture = &self.storage[upload.id];
-            transfers.copy_buffer_to_texture(
-                upload.data,
-                upload.bounds.size.width.to_bytes(texture.bytes_per_pixel()),
-                gpu::TexturePiece {
-                    texture: texture.raw,
-                    mip_level: 0,
-                    array_layer: 0,
-                    origin: [
-                        upload.bounds.origin.x.into(),
-                        upload.bounds.origin.y.into(),
-                        0,
-                    ],
-                },
-                gpu::Extent {
-                    width: upload.bounds.size.width.into(),
-                    height: upload.bounds.size.height.into(),
-                    depth: 1,
-                },
-            );
-        }
-    }
-}
-
-#[derive(Default)]
-struct BladeAtlasStorage {
-    monochrome_textures: AtlasTextureList<BladeAtlasTexture>,
-    subpixel_textures: AtlasTextureList<BladeAtlasTexture>,
-    polychrome_textures: AtlasTextureList<BladeAtlasTexture>,
-}
-
-impl ops::Index<AtlasTextureKind> for BladeAtlasStorage {
-    type Output = AtlasTextureList<BladeAtlasTexture>;
-    fn index(&self, kind: AtlasTextureKind) -> &Self::Output {
-        match kind {
-            crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
-            crate::AtlasTextureKind::Subpixel => &self.subpixel_textures,
-            crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
-        }
-    }
-}
-
-impl ops::IndexMut<AtlasTextureKind> for BladeAtlasStorage {
-    fn index_mut(&mut self, kind: AtlasTextureKind) -> &mut Self::Output {
-        match kind {
-            crate::AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
-            crate::AtlasTextureKind::Subpixel => &mut self.subpixel_textures,
-            crate::AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
-        }
-    }
-}
-
-impl ops::Index<AtlasTextureId> for BladeAtlasStorage {
-    type Output = BladeAtlasTexture;
-    fn index(&self, id: AtlasTextureId) -> &Self::Output {
-        let textures = match id.kind {
-            crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
-            crate::AtlasTextureKind::Subpixel => &self.subpixel_textures,
-            crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
-        };
-        textures[id.index as usize].as_ref().unwrap()
-    }
-}
-
-impl BladeAtlasStorage {
-    fn destroy(&mut self, gpu: &gpu::Context) {
-        for mut texture in self.monochrome_textures.drain().flatten() {
-            texture.destroy(gpu);
-        }
-        for mut texture in self.subpixel_textures.drain().flatten() {
-            texture.destroy(gpu);
-        }
-        for mut texture in self.polychrome_textures.drain().flatten() {
-            texture.destroy(gpu);
-        }
-    }
-}
-
-struct BladeAtlasTexture {
-    id: AtlasTextureId,
-    allocator: BucketedAtlasAllocator,
-    raw: gpu::Texture,
-    raw_view: gpu::TextureView,
-    format: gpu::TextureFormat,
-    live_atlas_keys: u32,
-}
-
-impl BladeAtlasTexture {
-    fn allocate(&mut self, size: Size<DevicePixels>) -> Option<AtlasTile> {
-        let allocation = self.allocator.allocate(size.into())?;
-        let tile = AtlasTile {
-            texture_id: self.id,
-            tile_id: allocation.id.into(),
-            padding: 0,
-            bounds: Bounds {
-                origin: allocation.rectangle.min.into(),
-                size,
-            },
-        };
-        self.live_atlas_keys += 1;
-        Some(tile)
-    }
-
-    fn destroy(&mut self, gpu: &gpu::Context) {
-        gpu.destroy_texture(self.raw);
-        gpu.destroy_texture_view(self.raw_view);
-    }
-
-    fn bytes_per_pixel(&self) -> u8 {
-        self.format.block_info().size
-    }
-
-    fn decrement_ref_count(&mut self) {
-        self.live_atlas_keys -= 1;
-    }
-
-    fn is_unreferenced(&mut self) -> bool {
-        self.live_atlas_keys == 0
-    }
-}
-
-impl From<Size<DevicePixels>> for etagere::Size {
-    fn from(size: Size<DevicePixels>) -> Self {
-        etagere::Size::new(size.width.into(), size.height.into())
-    }
-}
-
-impl From<etagere::Point> for Point<DevicePixels> {
-    fn from(value: etagere::Point) -> Self {
-        Point {
-            x: DevicePixels::from(value.x),
-            y: DevicePixels::from(value.y),
-        }
-    }
-}
-
-impl From<etagere::Size> for Size<DevicePixels> {
-    fn from(size: etagere::Size) -> Self {
-        Size {
-            width: DevicePixels::from(size.width),
-            height: DevicePixels::from(size.height),
-        }
-    }
-}
-
-impl From<etagere::Rectangle> for Bounds<DevicePixels> {
-    fn from(rectangle: etagere::Rectangle) -> Self {
-        Bounds {
-            origin: rectangle.min.into(),
-            size: rectangle.size().into(),
-        }
-    }
-}

crates/gpui/src/platform/blade/blade_context.rs 🔗

@@ -1,85 +0,0 @@
-use anyhow::Context as _;
-use blade_graphics as gpu;
-use std::sync::Arc;
-use util::ResultExt;
-
-#[cfg_attr(target_os = "macos", derive(Clone))]
-pub struct BladeContext {
-    pub(super) gpu: Arc<gpu::Context>,
-}
-
-impl BladeContext {
-    pub fn new() -> anyhow::Result<Self> {
-        let device_id_forced = match std::env::var("ZED_DEVICE_ID") {
-            Ok(val) => parse_pci_id(&val)
-                .context("Failed to parse device ID from `ZED_DEVICE_ID` environment variable")
-                .log_err(),
-            Err(std::env::VarError::NotPresent) => None,
-            err => {
-                err.context("Failed to read value of `ZED_DEVICE_ID` environment variable")
-                    .log_err();
-                None
-            }
-        };
-        let gpu = Arc::new(
-            unsafe {
-                gpu::Context::init(gpu::ContextDesc {
-                    presentation: true,
-                    validation: false,
-                    device_id: device_id_forced.unwrap_or(0),
-                    ..Default::default()
-                })
-            }
-            .map_err(|e| anyhow::anyhow!("{e:?}"))?,
-        );
-        Ok(Self { gpu })
-    }
-
-    #[allow(dead_code)]
-    pub fn supports_dual_source_blending(&self) -> bool {
-        self.gpu.capabilities().dual_source_blending
-    }
-}
-
-fn parse_pci_id(id: &str) -> anyhow::Result<u32> {
-    let mut id = id.trim();
-
-    if id.starts_with("0x") || id.starts_with("0X") {
-        id = &id[2..];
-    }
-    let is_hex_string = id.chars().all(|c| c.is_ascii_hexdigit());
-    let is_4_chars = id.len() == 4;
-    anyhow::ensure!(
-        is_4_chars && is_hex_string,
-        "Expected a 4 digit PCI ID in hexadecimal format"
-    );
-
-    u32::from_str_radix(id, 16).context("parsing PCI ID as hex")
-}
-
-#[cfg(test)]
-mod tests {
-    use super::parse_pci_id;
-
-    #[test]
-    fn test_parse_device_id() {
-        assert!(parse_pci_id("0xABCD").is_ok());
-        assert!(parse_pci_id("ABCD").is_ok());
-        assert!(parse_pci_id("abcd").is_ok());
-        assert!(parse_pci_id("1234").is_ok());
-        assert!(parse_pci_id("123").is_err());
-        assert_eq!(
-            parse_pci_id(&format!("{:x}", 0x1234)).unwrap(),
-            parse_pci_id(&format!("{:X}", 0x1234)).unwrap(),
-        );
-
-        assert_eq!(
-            parse_pci_id(&format!("{:#x}", 0x1234)).unwrap(),
-            parse_pci_id(&format!("{:#X}", 0x1234)).unwrap(),
-        );
-        assert_eq!(
-            parse_pci_id(&format!("{:#x}", 0x1234)).unwrap(),
-            parse_pci_id(&format!("{:#X}", 0x1234)).unwrap(),
-        );
-    }
-}

crates/gpui/src/platform/blade/blade_renderer.rs 🔗

@@ -1,1121 +0,0 @@
-// Doing `if let` gives you nice scoping with passes/encoders
-#![allow(irrefutable_let_patterns)]
-
-use super::{BladeAtlas, BladeContext};
-use crate::{
-    Background, Bounds, DevicePixels, GpuSpecs, MonochromeSprite, Path, Point, PolychromeSprite,
-    PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size, Underline,
-    get_gamma_correction_ratios,
-};
-#[cfg(any(test, feature = "test-support"))]
-use anyhow::Result;
-use blade_graphics as gpu;
-use blade_util::{BufferBelt, BufferBeltDescriptor};
-use bytemuck::{Pod, Zeroable};
-#[cfg(any(test, feature = "test-support"))]
-use image::RgbaImage;
-#[cfg(target_os = "macos")]
-use media::core_video::CVMetalTextureCache;
-use std::sync::Arc;
-
-const MAX_FRAME_TIME_MS: u32 = 10000;
-
-#[repr(C)]
-#[derive(Clone, Copy, Pod, Zeroable)]
-struct GlobalParams {
-    viewport_size: [f32; 2],
-    premultiplied_alpha: u32,
-    pad: u32,
-}
-
-//Note: we can't use `Bounds` directly here because
-// it doesn't implement Pod + Zeroable
-#[repr(C)]
-#[derive(Clone, Copy, Pod, Zeroable)]
-struct PodBounds {
-    origin: [f32; 2],
-    size: [f32; 2],
-}
-
-impl From<Bounds<ScaledPixels>> for PodBounds {
-    fn from(bounds: Bounds<ScaledPixels>) -> Self {
-        Self {
-            origin: [bounds.origin.x.0, bounds.origin.y.0],
-            size: [bounds.size.width.0, bounds.size.height.0],
-        }
-    }
-}
-
-#[repr(C)]
-#[derive(Clone, Copy, Pod, Zeroable)]
-struct SurfaceParams {
-    bounds: PodBounds,
-    content_mask: PodBounds,
-}
-
-#[derive(blade_macros::ShaderData)]
-struct ShaderQuadsData {
-    globals: GlobalParams,
-    b_quads: gpu::BufferPiece,
-}
-
-#[derive(blade_macros::ShaderData)]
-struct ShaderShadowsData {
-    globals: GlobalParams,
-    b_shadows: gpu::BufferPiece,
-}
-
-#[derive(blade_macros::ShaderData)]
-struct ShaderPathRasterizationData {
-    globals: GlobalParams,
-    b_path_vertices: gpu::BufferPiece,
-}
-
-#[derive(blade_macros::ShaderData)]
-struct ShaderPathsData {
-    globals: GlobalParams,
-    t_sprite: gpu::TextureView,
-    s_sprite: gpu::Sampler,
-    b_path_sprites: gpu::BufferPiece,
-}
-
-#[derive(blade_macros::ShaderData)]
-struct ShaderUnderlinesData {
-    globals: GlobalParams,
-    b_underlines: gpu::BufferPiece,
-}
-
-#[derive(blade_macros::ShaderData)]
-struct ShaderMonoSpritesData {
-    globals: GlobalParams,
-    gamma_ratios: [f32; 4],
-    grayscale_enhanced_contrast: f32,
-    t_sprite: gpu::TextureView,
-    s_sprite: gpu::Sampler,
-    b_mono_sprites: gpu::BufferPiece,
-}
-
-#[derive(blade_macros::ShaderData)]
-struct ShaderSubpixelSpritesData {
-    globals: GlobalParams,
-    gamma_ratios: [f32; 4],
-    subpixel_enhanced_contrast: f32,
-    t_sprite: gpu::TextureView,
-    s_sprite: gpu::Sampler,
-    b_subpixel_sprites: gpu::BufferPiece,
-}
-
-#[derive(blade_macros::ShaderData)]
-struct ShaderPolySpritesData {
-    globals: GlobalParams,
-    t_sprite: gpu::TextureView,
-    s_sprite: gpu::Sampler,
-    b_poly_sprites: gpu::BufferPiece,
-}
-
-#[derive(blade_macros::ShaderData)]
-struct ShaderSurfacesData {
-    globals: GlobalParams,
-    surface_locals: SurfaceParams,
-    t_y: gpu::TextureView,
-    t_cb_cr: gpu::TextureView,
-    s_surface: gpu::Sampler,
-}
-
-#[derive(Clone, Debug, Eq, PartialEq)]
-#[repr(C)]
-struct PathSprite {
-    bounds: Bounds<ScaledPixels>,
-}
-
-#[derive(Clone, Debug)]
-#[repr(C)]
-struct PathRasterizationVertex {
-    xy_position: Point<ScaledPixels>,
-    st_position: Point<f32>,
-    color: Background,
-    bounds: Bounds<ScaledPixels>,
-}
-
-struct BladePipelines {
-    quads: gpu::RenderPipeline,
-    shadows: gpu::RenderPipeline,
-    path_rasterization: gpu::RenderPipeline,
-    paths: gpu::RenderPipeline,
-    underlines: gpu::RenderPipeline,
-    mono_sprites: gpu::RenderPipeline,
-    subpixel_sprites: gpu::RenderPipeline,
-    poly_sprites: gpu::RenderPipeline,
-    surfaces: gpu::RenderPipeline,
-}
-
-impl BladePipelines {
-    fn new(gpu: &gpu::Context, surface_info: gpu::SurfaceInfo, path_sample_count: u32) -> Self {
-        use gpu::ShaderData as _;
-
-        log::info!(
-            "Initializing Blade pipelines for surface {:?}",
-            surface_info
-        );
-        let shader = gpu.create_shader(gpu::ShaderDesc {
-            source: include_str!("shaders.wgsl"),
-        });
-        shader.check_struct_size::<GlobalParams>();
-        shader.check_struct_size::<SurfaceParams>();
-        shader.check_struct_size::<Quad>();
-        shader.check_struct_size::<Shadow>();
-        shader.check_struct_size::<PathRasterizationVertex>();
-        shader.check_struct_size::<PathSprite>();
-        shader.check_struct_size::<Underline>();
-        shader.check_struct_size::<MonochromeSprite>();
-        shader.check_struct_size::<PolychromeSprite>();
-
-        // See https://apoorvaj.io/alpha-compositing-opengl-blending-and-premultiplied-alpha/
-        let blend_mode = match surface_info.alpha {
-            gpu::AlphaMode::Ignored => gpu::BlendState::ALPHA_BLENDING,
-            gpu::AlphaMode::PreMultiplied => gpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING,
-            gpu::AlphaMode::PostMultiplied => gpu::BlendState::ALPHA_BLENDING,
-        };
-        let color_targets = &[gpu::ColorTargetState {
-            format: surface_info.format,
-            blend: Some(blend_mode),
-            write_mask: gpu::ColorWrites::default(),
-        }];
-
-        Self {
-            quads: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
-                name: "quads",
-                data_layouts: &[&ShaderQuadsData::layout()],
-                vertex: shader.at("vs_quad"),
-                vertex_fetches: &[],
-                primitive: gpu::PrimitiveState {
-                    topology: gpu::PrimitiveTopology::TriangleStrip,
-                    ..Default::default()
-                },
-                depth_stencil: None,
-                fragment: Some(shader.at("fs_quad")),
-                color_targets,
-                multisample_state: gpu::MultisampleState::default(),
-            }),
-            shadows: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
-                name: "shadows",
-                data_layouts: &[&ShaderShadowsData::layout()],
-                vertex: shader.at("vs_shadow"),
-                vertex_fetches: &[],
-                primitive: gpu::PrimitiveState {
-                    topology: gpu::PrimitiveTopology::TriangleStrip,
-                    ..Default::default()
-                },
-                depth_stencil: None,
-                fragment: Some(shader.at("fs_shadow")),
-                color_targets,
-                multisample_state: gpu::MultisampleState::default(),
-            }),
-            path_rasterization: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
-                name: "path_rasterization",
-                data_layouts: &[&ShaderPathRasterizationData::layout()],
-                vertex: shader.at("vs_path_rasterization"),
-                vertex_fetches: &[],
-                primitive: gpu::PrimitiveState {
-                    topology: gpu::PrimitiveTopology::TriangleList,
-                    ..Default::default()
-                },
-                depth_stencil: None,
-                fragment: Some(shader.at("fs_path_rasterization")),
-                // The original implementation was using ADDITIVE blende mode,
-                // I don't know why
-                // color_targets: &[gpu::ColorTargetState {
-                //     format: PATH_TEXTURE_FORMAT,
-                //     blend: Some(gpu::BlendState::ADDITIVE),
-                //     write_mask: gpu::ColorWrites::default(),
-                // }],
-                color_targets: &[gpu::ColorTargetState {
-                    format: surface_info.format,
-                    blend: Some(gpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
-                    write_mask: gpu::ColorWrites::default(),
-                }],
-                multisample_state: gpu::MultisampleState {
-                    sample_count: path_sample_count,
-                    ..Default::default()
-                },
-            }),
-            paths: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
-                name: "paths",
-                data_layouts: &[&ShaderPathsData::layout()],
-                vertex: shader.at("vs_path"),
-                vertex_fetches: &[],
-                primitive: gpu::PrimitiveState {
-                    topology: gpu::PrimitiveTopology::TriangleStrip,
-                    ..Default::default()
-                },
-                depth_stencil: None,
-                fragment: Some(shader.at("fs_path")),
-                color_targets: &[gpu::ColorTargetState {
-                    format: surface_info.format,
-                    blend: Some(gpu::BlendState {
-                        color: gpu::BlendComponent::OVER,
-                        alpha: gpu::BlendComponent::ADDITIVE,
-                    }),
-                    write_mask: gpu::ColorWrites::default(),
-                }],
-                multisample_state: gpu::MultisampleState::default(),
-            }),
-            underlines: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
-                name: "underlines",
-                data_layouts: &[&ShaderUnderlinesData::layout()],
-                vertex: shader.at("vs_underline"),
-                vertex_fetches: &[],
-                primitive: gpu::PrimitiveState {
-                    topology: gpu::PrimitiveTopology::TriangleStrip,
-                    ..Default::default()
-                },
-                depth_stencil: None,
-                fragment: Some(shader.at("fs_underline")),
-                color_targets,
-                multisample_state: gpu::MultisampleState::default(),
-            }),
-            mono_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
-                name: "mono-sprites",
-                data_layouts: &[&ShaderMonoSpritesData::layout()],
-                vertex: shader.at("vs_mono_sprite"),
-                vertex_fetches: &[],
-                primitive: gpu::PrimitiveState {
-                    topology: gpu::PrimitiveTopology::TriangleStrip,
-                    ..Default::default()
-                },
-                depth_stencil: None,
-                fragment: Some(shader.at("fs_mono_sprite")),
-                color_targets,
-                multisample_state: gpu::MultisampleState::default(),
-            }),
-            subpixel_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
-                name: "subpixel-sprites",
-                data_layouts: &[&ShaderSubpixelSpritesData::layout()],
-                vertex: shader.at("vs_subpixel_sprite"),
-                vertex_fetches: &[],
-                primitive: gpu::PrimitiveState {
-                    topology: gpu::PrimitiveTopology::TriangleStrip,
-                    ..Default::default()
-                },
-                depth_stencil: None,
-                fragment: Some(shader.at("fs_subpixel_sprite")),
-                color_targets: &[gpu::ColorTargetState {
-                    format: surface_info.format,
-                    blend: Some(gpu::BlendState {
-                        color: gpu::BlendComponent {
-                            src_factor: gpu::BlendFactor::Src1,
-                            dst_factor: gpu::BlendFactor::OneMinusSrc1,
-                            operation: gpu::BlendOperation::Add,
-                        },
-                        alpha: gpu::BlendComponent::OVER,
-                    }),
-                    write_mask: gpu::ColorWrites::COLOR,
-                }],
-                multisample_state: gpu::MultisampleState::default(),
-            }),
-            poly_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
-                name: "poly-sprites",
-                data_layouts: &[&ShaderPolySpritesData::layout()],
-                vertex: shader.at("vs_poly_sprite"),
-                vertex_fetches: &[],
-                primitive: gpu::PrimitiveState {
-                    topology: gpu::PrimitiveTopology::TriangleStrip,
-                    ..Default::default()
-                },
-                depth_stencil: None,
-                fragment: Some(shader.at("fs_poly_sprite")),
-                color_targets,
-                multisample_state: gpu::MultisampleState::default(),
-            }),
-            surfaces: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
-                name: "surfaces",
-                data_layouts: &[&ShaderSurfacesData::layout()],
-                vertex: shader.at("vs_surface"),
-                vertex_fetches: &[],
-                primitive: gpu::PrimitiveState {
-                    topology: gpu::PrimitiveTopology::TriangleStrip,
-                    ..Default::default()
-                },
-                depth_stencil: None,
-                fragment: Some(shader.at("fs_surface")),
-                color_targets,
-                multisample_state: gpu::MultisampleState::default(),
-            }),
-        }
-    }
-
-    fn destroy(&mut self, gpu: &gpu::Context) {
-        gpu.destroy_render_pipeline(&mut self.quads);
-        gpu.destroy_render_pipeline(&mut self.shadows);
-        gpu.destroy_render_pipeline(&mut self.path_rasterization);
-        gpu.destroy_render_pipeline(&mut self.paths);
-        gpu.destroy_render_pipeline(&mut self.underlines);
-        gpu.destroy_render_pipeline(&mut self.mono_sprites);
-        gpu.destroy_render_pipeline(&mut self.subpixel_sprites);
-        gpu.destroy_render_pipeline(&mut self.poly_sprites);
-        gpu.destroy_render_pipeline(&mut self.surfaces);
-    }
-}
-
-pub struct BladeSurfaceConfig {
-    pub size: gpu::Extent,
-    pub transparent: bool,
-}
-
-//Note: we could see some of these fields moved into `BladeContext`
-// so that they are shared between windows. E.g. `pipelines`.
-// But that is complicated by the fact that pipelines depend on
-// the format and alpha mode.
-pub struct BladeRenderer {
-    gpu: Arc<gpu::Context>,
-    surface: gpu::Surface,
-    surface_config: gpu::SurfaceConfig,
-    command_encoder: gpu::CommandEncoder,
-    last_sync_point: Option<gpu::SyncPoint>,
-    pipelines: BladePipelines,
-    instance_belt: BufferBelt,
-    atlas: Arc<BladeAtlas>,
-    atlas_sampler: gpu::Sampler,
-    #[cfg(target_os = "macos")]
-    core_video_texture_cache: CVMetalTextureCache,
-    path_intermediate_texture: gpu::Texture,
-    path_intermediate_texture_view: gpu::TextureView,
-    path_intermediate_msaa_texture: Option<gpu::Texture>,
-    path_intermediate_msaa_texture_view: Option<gpu::TextureView>,
-    rendering_parameters: RenderingParameters,
-}
-
-impl BladeRenderer {
-    pub fn new<I: raw_window_handle::HasWindowHandle + raw_window_handle::HasDisplayHandle>(
-        context: &BladeContext,
-        window: &I,
-        config: BladeSurfaceConfig,
-    ) -> anyhow::Result<Self> {
-        let surface_config = gpu::SurfaceConfig {
-            size: config.size,
-            usage: gpu::TextureUsage::TARGET,
-            display_sync: gpu::DisplaySync::Recent,
-            color_space: gpu::ColorSpace::Srgb,
-            allow_exclusive_full_screen: false,
-            transparent: config.transparent,
-        };
-        let surface = context
-            .gpu
-            .create_surface_configured(window, surface_config)
-            .map_err(|err| anyhow::anyhow!("Failed to create surface: {err:?}"))?;
-
-        let command_encoder = context.gpu.create_command_encoder(gpu::CommandEncoderDesc {
-            name: "main",
-            buffer_count: 2,
-        });
-        let rendering_parameters = RenderingParameters::from_env(context);
-        let pipelines = BladePipelines::new(
-            &context.gpu,
-            surface.info(),
-            rendering_parameters.path_sample_count,
-        );
-        let instance_belt = BufferBelt::new(BufferBeltDescriptor {
-            memory: gpu::Memory::Shared,
-            min_chunk_size: 0x1000,
-            alignment: 0x40, // Vulkan `minStorageBufferOffsetAlignment` on Intel Xe
-        });
-        let atlas = Arc::new(BladeAtlas::new(&context.gpu));
-        let atlas_sampler = context.gpu.create_sampler(gpu::SamplerDesc {
-            name: "path rasterization sampler",
-            mag_filter: gpu::FilterMode::Linear,
-            min_filter: gpu::FilterMode::Linear,
-            ..Default::default()
-        });
-
-        let (path_intermediate_texture, path_intermediate_texture_view) =
-            create_path_intermediate_texture(
-                &context.gpu,
-                surface.info().format,
-                config.size.width,
-                config.size.height,
-            );
-        let (path_intermediate_msaa_texture, path_intermediate_msaa_texture_view) =
-            create_msaa_texture_if_needed(
-                &context.gpu,
-                surface.info().format,
-                config.size.width,
-                config.size.height,
-                rendering_parameters.path_sample_count,
-            )
-            .unzip();
-
-        #[cfg(target_os = "macos")]
-        let core_video_texture_cache = unsafe {
-            CVMetalTextureCache::new(
-                objc2::rc::Retained::as_ptr(&context.gpu.metal_device()) as *mut _
-            )
-            .unwrap()
-        };
-
-        Ok(Self {
-            gpu: Arc::clone(&context.gpu),
-            surface,
-            surface_config,
-            command_encoder,
-            last_sync_point: None,
-            pipelines,
-            instance_belt,
-            atlas,
-            atlas_sampler,
-            #[cfg(target_os = "macos")]
-            core_video_texture_cache,
-            path_intermediate_texture,
-            path_intermediate_texture_view,
-            path_intermediate_msaa_texture,
-            path_intermediate_msaa_texture_view,
-            rendering_parameters,
-        })
-    }
-
-    fn wait_for_gpu(&mut self) {
-        if let Some(last_sp) = self.last_sync_point.take()
-            && !self.gpu.wait_for(&last_sp, MAX_FRAME_TIME_MS)
-        {
-            log::error!("GPU hung");
-            #[cfg(target_os = "linux")]
-            if self.gpu.device_information().driver_name == "radv" {
-                log::error!(
-                    "there's a known bug with amdgpu/radv, try setting ZED_PATH_SAMPLE_COUNT=0 as a workaround"
-                );
-                log::error!(
-                    "if that helps you're running into https://github.com/zed-industries/zed/issues/26143"
-                );
-            }
-            log::error!(
-                "your device information is: {:?}",
-                self.gpu.device_information()
-            );
-            while !self.gpu.wait_for(&last_sp, MAX_FRAME_TIME_MS) {}
-        }
-    }
-
-    pub fn update_drawable_size(&mut self, size: Size<DevicePixels>) {
-        self.update_drawable_size_impl(size, false);
-    }
-
-    /// Like `update_drawable_size` but skips the check that the size has changed. This is useful in
-    /// cases like restoring a window from minimization where the size is the same but the
-    /// renderer's swap chain needs to be recreated.
-    #[cfg_attr(
-        any(target_os = "macos", target_os = "linux", target_os = "freebsd"),
-        allow(dead_code)
-    )]
-    pub fn update_drawable_size_even_if_unchanged(&mut self, size: Size<DevicePixels>) {
-        self.update_drawable_size_impl(size, true);
-    }
-
-    fn update_drawable_size_impl(&mut self, size: Size<DevicePixels>, always_resize: bool) {
-        let gpu_size = gpu::Extent {
-            width: size.width.0 as u32,
-            height: size.height.0 as u32,
-            depth: 1,
-        };
-
-        if always_resize || gpu_size != self.surface_config.size {
-            self.wait_for_gpu();
-            self.surface_config.size = gpu_size;
-            self.gpu
-                .reconfigure_surface(&mut self.surface, self.surface_config);
-            self.gpu.destroy_texture(self.path_intermediate_texture);
-            self.gpu
-                .destroy_texture_view(self.path_intermediate_texture_view);
-            if let Some(msaa_texture) = self.path_intermediate_msaa_texture {
-                self.gpu.destroy_texture(msaa_texture);
-            }
-            if let Some(msaa_view) = self.path_intermediate_msaa_texture_view {
-                self.gpu.destroy_texture_view(msaa_view);
-            }
-            let (path_intermediate_texture, path_intermediate_texture_view) =
-                create_path_intermediate_texture(
-                    &self.gpu,
-                    self.surface.info().format,
-                    gpu_size.width,
-                    gpu_size.height,
-                );
-            self.path_intermediate_texture = path_intermediate_texture;
-            self.path_intermediate_texture_view = path_intermediate_texture_view;
-            let (path_intermediate_msaa_texture, path_intermediate_msaa_texture_view) =
-                create_msaa_texture_if_needed(
-                    &self.gpu,
-                    self.surface.info().format,
-                    gpu_size.width,
-                    gpu_size.height,
-                    self.rendering_parameters.path_sample_count,
-                )
-                .unzip();
-            self.path_intermediate_msaa_texture = path_intermediate_msaa_texture;
-            self.path_intermediate_msaa_texture_view = path_intermediate_msaa_texture_view;
-        }
-    }
-
-    pub fn update_transparency(&mut self, transparent: bool) {
-        if transparent != self.surface_config.transparent {
-            self.wait_for_gpu();
-            self.surface_config.transparent = transparent;
-            self.gpu
-                .reconfigure_surface(&mut self.surface, self.surface_config);
-            self.pipelines.destroy(&self.gpu);
-            self.pipelines = BladePipelines::new(
-                &self.gpu,
-                self.surface.info(),
-                self.rendering_parameters.path_sample_count,
-            );
-        }
-    }
-
-    #[cfg_attr(
-        any(target_os = "macos", feature = "wayland", target_os = "windows"),
-        allow(dead_code)
-    )]
-    pub fn viewport_size(&self) -> gpu::Extent {
-        self.surface_config.size
-    }
-
-    pub fn sprite_atlas(&self) -> &Arc<BladeAtlas> {
-        &self.atlas
-    }
-
-    #[cfg_attr(target_os = "macos", allow(dead_code))]
-    pub fn gpu_specs(&self) -> GpuSpecs {
-        let info = self.gpu.device_information();
-
-        GpuSpecs {
-            is_software_emulated: info.is_software_emulated,
-            device_name: info.device_name.clone(),
-            driver_name: info.driver_name.clone(),
-            driver_info: info.driver_info.clone(),
-        }
-    }
-
-    #[cfg(target_os = "macos")]
-    pub fn layer(&self) -> metal::MetalLayer {
-        unsafe { foreign_types::ForeignType::from_ptr(self.layer_ptr()) }
-    }
-
-    #[cfg(target_os = "macos")]
-    pub fn layer_ptr(&self) -> *mut metal::CAMetalLayer {
-        objc2::rc::Retained::as_ptr(&self.surface.metal_layer()) as *mut _
-    }
-
-    #[profiling::function]
-    fn draw_paths_to_intermediate(
-        &mut self,
-        paths: &[Path<ScaledPixels>],
-        width: f32,
-        height: f32,
-    ) {
-        self.command_encoder
-            .init_texture(self.path_intermediate_texture);
-        if let Some(msaa_texture) = self.path_intermediate_msaa_texture {
-            self.command_encoder.init_texture(msaa_texture);
-        }
-
-        let target = if let Some(msaa_view) = self.path_intermediate_msaa_texture_view {
-            gpu::RenderTarget {
-                view: msaa_view,
-                init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
-                finish_op: gpu::FinishOp::ResolveTo(self.path_intermediate_texture_view),
-            }
-        } else {
-            gpu::RenderTarget {
-                view: self.path_intermediate_texture_view,
-                init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
-                finish_op: gpu::FinishOp::Store,
-            }
-        };
-        if let mut pass = self.command_encoder.render(
-            "rasterize paths",
-            gpu::RenderTargetSet {
-                colors: &[target],
-                depth_stencil: None,
-            },
-        ) {
-            let globals = GlobalParams {
-                viewport_size: [width, height],
-                premultiplied_alpha: 0,
-                pad: 0,
-            };
-            let mut encoder = pass.with(&self.pipelines.path_rasterization);
-
-            let mut vertices = Vec::new();
-            for path in paths {
-                vertices.extend(path.vertices.iter().map(|v| PathRasterizationVertex {
-                    xy_position: v.xy_position,
-                    st_position: v.st_position,
-                    color: path.color,
-                    bounds: path.clipped_bounds(),
-                }));
-            }
-            let vertex_buf = unsafe { self.instance_belt.alloc_typed(&vertices, &self.gpu) };
-            encoder.bind(
-                0,
-                &ShaderPathRasterizationData {
-                    globals,
-                    b_path_vertices: vertex_buf,
-                },
-            );
-            encoder.draw(0, vertices.len() as u32, 0, 1);
-        }
-    }
-
-    pub fn destroy(&mut self) {
-        self.wait_for_gpu();
-        self.atlas.destroy();
-        self.gpu.destroy_sampler(self.atlas_sampler);
-        self.instance_belt.destroy(&self.gpu);
-        self.gpu.destroy_command_encoder(&mut self.command_encoder);
-        self.pipelines.destroy(&self.gpu);
-        self.gpu.destroy_surface(&mut self.surface);
-        self.gpu.destroy_texture(self.path_intermediate_texture);
-        self.gpu
-            .destroy_texture_view(self.path_intermediate_texture_view);
-        if let Some(msaa_texture) = self.path_intermediate_msaa_texture {
-            self.gpu.destroy_texture(msaa_texture);
-        }
-        if let Some(msaa_view) = self.path_intermediate_msaa_texture_view {
-            self.gpu.destroy_texture_view(msaa_view);
-        }
-    }
-
-    pub fn draw(&mut self, scene: &Scene) {
-        self.command_encoder.start();
-        self.atlas.before_frame(&mut self.command_encoder);
-
-        let frame = {
-            profiling::scope!("acquire frame");
-            self.surface.acquire_frame()
-        };
-        self.command_encoder.init_texture(frame.texture());
-
-        let globals = GlobalParams {
-            viewport_size: [
-                self.surface_config.size.width as f32,
-                self.surface_config.size.height as f32,
-            ],
-            premultiplied_alpha: match self.surface.info().alpha {
-                gpu::AlphaMode::Ignored | gpu::AlphaMode::PostMultiplied => 0,
-                gpu::AlphaMode::PreMultiplied => 1,
-            },
-            pad: 0,
-        };
-
-        let mut pass = self.command_encoder.render(
-            "main",
-            gpu::RenderTargetSet {
-                colors: &[gpu::RenderTarget {
-                    view: frame.texture_view(),
-                    init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
-                    finish_op: gpu::FinishOp::Store,
-                }],
-                depth_stencil: None,
-            },
-        );
-
-        profiling::scope!("render pass");
-        for batch in scene.batches() {
-            match batch {
-                PrimitiveBatch::Quads(range) => {
-                    let quads = &scene.quads[range];
-                    let instance_buf = unsafe { self.instance_belt.alloc_typed(quads, &self.gpu) };
-                    let mut encoder = pass.with(&self.pipelines.quads);
-                    encoder.bind(
-                        0,
-                        &ShaderQuadsData {
-                            globals,
-                            b_quads: instance_buf,
-                        },
-                    );
-                    encoder.draw(0, 4, 0, quads.len() as u32);
-                }
-                PrimitiveBatch::Shadows(range) => {
-                    let shadows = &scene.shadows[range];
-                    let instance_buf =
-                        unsafe { self.instance_belt.alloc_typed(shadows, &self.gpu) };
-                    let mut encoder = pass.with(&self.pipelines.shadows);
-                    encoder.bind(
-                        0,
-                        &ShaderShadowsData {
-                            globals,
-                            b_shadows: instance_buf,
-                        },
-                    );
-                    encoder.draw(0, 4, 0, shadows.len() as u32);
-                }
-                PrimitiveBatch::Paths(range) => {
-                    let paths = &scene.paths[range];
-                    let Some(first_path) = paths.first() else {
-                        continue;
-                    };
-                    drop(pass);
-                    self.draw_paths_to_intermediate(
-                        paths,
-                        self.surface_config.size.width as f32,
-                        self.surface_config.size.height as f32,
-                    );
-                    pass = self.command_encoder.render(
-                        "main",
-                        gpu::RenderTargetSet {
-                            colors: &[gpu::RenderTarget {
-                                view: frame.texture_view(),
-                                init_op: gpu::InitOp::Load,
-                                finish_op: gpu::FinishOp::Store,
-                            }],
-                            depth_stencil: None,
-                        },
-                    );
-                    let mut encoder = pass.with(&self.pipelines.paths);
-                    // When copying paths from the intermediate texture to the drawable,
-                    // each pixel must only be copied once, in case of transparent paths.
-                    //
-                    // If all paths have the same draw order, then their bounds are all
-                    // disjoint, so we can copy each path's bounds individually. If this
-                    // batch combines different draw orders, we perform a single copy
-                    // for a minimal spanning rect.
-                    let sprites = if paths.last().unwrap().order == first_path.order {
-                        paths
-                            .iter()
-                            .map(|path| PathSprite {
-                                bounds: path.clipped_bounds(),
-                            })
-                            .collect()
-                    } else {
-                        let mut bounds = first_path.clipped_bounds();
-                        for path in paths.iter().skip(1) {
-                            bounds = bounds.union(&path.clipped_bounds());
-                        }
-                        vec![PathSprite { bounds }]
-                    };
-                    let instance_buf =
-                        unsafe { self.instance_belt.alloc_typed(&sprites, &self.gpu) };
-                    encoder.bind(
-                        0,
-                        &ShaderPathsData {
-                            globals,
-                            t_sprite: self.path_intermediate_texture_view,
-                            s_sprite: self.atlas_sampler,
-                            b_path_sprites: instance_buf,
-                        },
-                    );
-                    encoder.draw(0, 4, 0, sprites.len() as u32);
-                }
-                PrimitiveBatch::Underlines(range) => {
-                    let underlines = &scene.underlines[range];
-                    let instance_buf =
-                        unsafe { self.instance_belt.alloc_typed(underlines, &self.gpu) };
-                    let mut encoder = pass.with(&self.pipelines.underlines);
-                    encoder.bind(
-                        0,
-                        &ShaderUnderlinesData {
-                            globals,
-                            b_underlines: instance_buf,
-                        },
-                    );
-                    encoder.draw(0, 4, 0, underlines.len() as u32);
-                }
-                PrimitiveBatch::MonochromeSprites { texture_id, range } => {
-                    let sprites = &scene.monochrome_sprites[range];
-                    let tex_info = self.atlas.get_texture_info(texture_id);
-                    let instance_buf =
-                        unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) };
-                    let mut encoder = pass.with(&self.pipelines.mono_sprites);
-                    encoder.bind(
-                        0,
-                        &ShaderMonoSpritesData {
-                            globals,
-                            gamma_ratios: self.rendering_parameters.gamma_ratios,
-                            grayscale_enhanced_contrast: self
-                                .rendering_parameters
-                                .grayscale_enhanced_contrast,
-                            t_sprite: tex_info.raw_view,
-                            s_sprite: self.atlas_sampler,
-                            b_mono_sprites: instance_buf,
-                        },
-                    );
-                    encoder.draw(0, 4, 0, sprites.len() as u32);
-                }
-                PrimitiveBatch::PolychromeSprites { texture_id, range } => {
-                    let sprites = &scene.polychrome_sprites[range];
-                    let tex_info = self.atlas.get_texture_info(texture_id);
-                    let instance_buf =
-                        unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) };
-                    let mut encoder = pass.with(&self.pipelines.poly_sprites);
-                    encoder.bind(
-                        0,
-                        &ShaderPolySpritesData {
-                            globals,
-                            t_sprite: tex_info.raw_view,
-                            s_sprite: self.atlas_sampler,
-                            b_poly_sprites: instance_buf,
-                        },
-                    );
-                    encoder.draw(0, 4, 0, sprites.len() as u32);
-                }
-                PrimitiveBatch::SubpixelSprites { texture_id, range } => {
-                    let sprites = &scene.subpixel_sprites[range];
-                    let tex_info = self.atlas.get_texture_info(texture_id);
-                    let instance_buf =
-                        unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) };
-                    let mut encoder = pass.with(&self.pipelines.subpixel_sprites);
-                    encoder.bind(
-                        0,
-                        &ShaderSubpixelSpritesData {
-                            globals,
-                            gamma_ratios: self.rendering_parameters.gamma_ratios,
-                            subpixel_enhanced_contrast: self
-                                .rendering_parameters
-                                .subpixel_enhanced_contrast,
-                            t_sprite: tex_info.raw_view,
-                            s_sprite: self.atlas_sampler,
-                            b_subpixel_sprites: instance_buf,
-                        },
-                    );
-                    encoder.draw(0, 4, 0, sprites.len() as u32);
-                }
-                PrimitiveBatch::Surfaces(range) => {
-                    let surfaces = &scene.surfaces[range];
-                    let mut _encoder = pass.with(&self.pipelines.surfaces);
-
-                    for surface in surfaces {
-                        #[cfg(not(target_os = "macos"))]
-                        {
-                            let _ = surface;
-                            continue;
-                        };
-
-                        #[cfg(target_os = "macos")]
-                        {
-                            let (t_y, t_cb_cr) = unsafe {
-                                use core_foundation::base::TCFType as _;
-                                use std::ptr;
-
-                                assert_eq!(
-                                        surface.image_buffer.get_pixel_format(),
-                                        core_video::pixel_buffer::kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
-                                    );
-
-                                let y_texture = self
-                                    .core_video_texture_cache
-                                    .create_texture_from_image(
-                                        surface.image_buffer.as_concrete_TypeRef(),
-                                        ptr::null(),
-                                        metal::MTLPixelFormat::R8Unorm,
-                                        surface.image_buffer.get_width_of_plane(0),
-                                        surface.image_buffer.get_height_of_plane(0),
-                                        0,
-                                    )
-                                    .unwrap();
-                                let cb_cr_texture = self
-                                    .core_video_texture_cache
-                                    .create_texture_from_image(
-                                        surface.image_buffer.as_concrete_TypeRef(),
-                                        ptr::null(),
-                                        metal::MTLPixelFormat::RG8Unorm,
-                                        surface.image_buffer.get_width_of_plane(1),
-                                        surface.image_buffer.get_height_of_plane(1),
-                                        1,
-                                    )
-                                    .unwrap();
-                                (
-                                    gpu::TextureView::from_metal_texture(
-                                        &objc2::rc::Retained::retain(
-                                            foreign_types::ForeignTypeRef::as_ptr(
-                                                y_texture.as_texture_ref(),
-                                            )
-                                                as *mut objc2::runtime::ProtocolObject<
-                                                    dyn objc2_metal::MTLTexture,
-                                                >,
-                                        )
-                                        .unwrap(),
-                                        gpu::TexelAspects::COLOR,
-                                    ),
-                                    gpu::TextureView::from_metal_texture(
-                                        &objc2::rc::Retained::retain(
-                                            foreign_types::ForeignTypeRef::as_ptr(
-                                                cb_cr_texture.as_texture_ref(),
-                                            )
-                                                as *mut objc2::runtime::ProtocolObject<
-                                                    dyn objc2_metal::MTLTexture,
-                                                >,
-                                        )
-                                        .unwrap(),
-                                        gpu::TexelAspects::COLOR,
-                                    ),
-                                )
-                            };
-
-                            _encoder.bind(
-                                0,
-                                &ShaderSurfacesData {
-                                    globals,
-                                    surface_locals: SurfaceParams {
-                                        bounds: surface.bounds.into(),
-                                        content_mask: surface.content_mask.bounds.into(),
-                                    },
-                                    t_y,
-                                    t_cb_cr,
-                                    s_surface: self.atlas_sampler,
-                                },
-                            );
-
-                            _encoder.draw(0, 4, 0, 1);
-                        }
-                    }
-                }
-            }
-        }
-        drop(pass);
-
-        self.command_encoder.present(frame);
-        let sync_point = self.gpu.submit(&mut self.command_encoder);
-
-        profiling::scope!("finish");
-        self.instance_belt.flush(&sync_point);
-        self.atlas.after_frame(&sync_point);
-
-        self.wait_for_gpu();
-        self.last_sync_point = Some(sync_point);
-    }
-
-    /// Renders the scene to a texture and returns the pixel data as an RGBA image.
-    /// This is not yet implemented for BladeRenderer.
-    #[cfg(any(test, feature = "test-support"))]
-    #[allow(dead_code)]
-    pub fn render_to_image(&mut self, _scene: &Scene) -> Result<RgbaImage> {
-        anyhow::bail!("render_to_image is not yet implemented for BladeRenderer")
-    }
-}
-
-fn create_path_intermediate_texture(
-    gpu: &gpu::Context,
-    format: gpu::TextureFormat,
-    width: u32,
-    height: u32,
-) -> (gpu::Texture, gpu::TextureView) {
-    let texture = gpu.create_texture(gpu::TextureDesc {
-        name: "path intermediate",
-        format,
-        size: gpu::Extent {
-            width,
-            height,
-            depth: 1,
-        },
-        array_layer_count: 1,
-        mip_level_count: 1,
-        sample_count: 1,
-        dimension: gpu::TextureDimension::D2,
-        usage: gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE | gpu::TextureUsage::TARGET,
-        external: None,
-    });
-    let texture_view = gpu.create_texture_view(
-        texture,
-        gpu::TextureViewDesc {
-            name: "path intermediate view",
-            format,
-            dimension: gpu::ViewDimension::D2,
-            subresources: &Default::default(),
-        },
-    );
-    (texture, texture_view)
-}
-
-fn create_msaa_texture_if_needed(
-    gpu: &gpu::Context,
-    format: gpu::TextureFormat,
-    width: u32,
-    height: u32,
-    sample_count: u32,
-) -> Option<(gpu::Texture, gpu::TextureView)> {
-    if sample_count <= 1 {
-        return None;
-    }
-    let texture_msaa = gpu.create_texture(gpu::TextureDesc {
-        name: "path intermediate msaa",
-        format,
-        size: gpu::Extent {
-            width,
-            height,
-            depth: 1,
-        },
-        array_layer_count: 1,
-        mip_level_count: 1,
-        sample_count,
-        dimension: gpu::TextureDimension::D2,
-        usage: gpu::TextureUsage::TARGET,
-        external: None,
-    });
-    let texture_view_msaa = gpu.create_texture_view(
-        texture_msaa,
-        gpu::TextureViewDesc {
-            name: "path intermediate msaa view",
-            format,
-            dimension: gpu::ViewDimension::D2,
-            subresources: &Default::default(),
-        },
-    );
-
-    Some((texture_msaa, texture_view_msaa))
-}
-
-/// A set of parameters that can be set using a corresponding environment variable.
-struct RenderingParameters {
-    // Env var: ZED_PATH_SAMPLE_COUNT
-    // workaround for https://github.com/zed-industries/zed/issues/26143
-    path_sample_count: u32,
-
-    // Env var: ZED_FONTS_GAMMA
-    // Allowed range [1.0, 2.2], other values are clipped
-    // Default: 1.8
-    gamma_ratios: [f32; 4],
-    // Env var: ZED_FONTS_GRAYSCALE_ENHANCED_CONTRAST
-    // Allowed range: [0.0, ..), other values are clipped
-    // Default: 1.0
-    grayscale_enhanced_contrast: f32,
-    // Env var: ZED_FONTS_SUBPIXEL_ENHANCED_CONTRAST
-    // Allowed range: [0.0, ..), other values are clipped
-    // Default: 0.5
-    subpixel_enhanced_contrast: f32,
-}
-
-impl RenderingParameters {
-    fn from_env(context: &BladeContext) -> Self {
-        use std::env;
-
-        let path_sample_count = env::var("ZED_PATH_SAMPLE_COUNT")
-            .ok()
-            .and_then(|v| v.parse().ok())
-            .or_else(|| {
-                [4, 2, 1]
-                    .into_iter()
-                    .find(|&n| (context.gpu.capabilities().sample_count_mask & n) != 0)
-            })
-            .unwrap_or(1);
-        let gamma = env::var("ZED_FONTS_GAMMA")
-            .ok()
-            .and_then(|v| v.parse().ok())
-            .unwrap_or(1.8_f32)
-            .clamp(1.0, 2.2);
-        let gamma_ratios = get_gamma_correction_ratios(gamma);
-        let grayscale_enhanced_contrast = env::var("ZED_FONTS_GRAYSCALE_ENHANCED_CONTRAST")
-            .ok()
-            .and_then(|v| v.parse().ok())
-            .unwrap_or(1.0_f32)
-            .max(0.0);
-        let subpixel_enhanced_contrast = env::var("ZED_FONTS_SUBPIXEL_ENHANCED_CONTRAST")
-            .ok()
-            .and_then(|v| v.parse().ok())
-            .unwrap_or(0.5_f32)
-            .max(0.0);
-
-        Self {
-            path_sample_count,
-            gamma_ratios,
-            grayscale_enhanced_contrast,
-            subpixel_enhanced_contrast,
-        }
-    }
-}

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<Pixels>, b: Point<Pixels>) -> bool {
     let diff = a - b;
     diff.x.abs() <= DOUBLE_CLICK_DISTANCE && diff.y.abs() <= DOUBLE_CLICK_DISTANCE

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_pointer::WlPointer>,
     wl_keyboard: Option<wl_keyboard::WlKeyboard>,
@@ -520,7 +520,7 @@ impl WaylandClient {
             .unwrap();
 
         // This could be unified with the notification handling in zed/main:fail_to_open_window.
-        let gpu_context = BladeContext::new().notify_err("Unable to init GPU context");
+        let gpu_context = WgpuContext::new().notify_err("Unable to init GPU context");
 
         let seat = seat.unwrap();
         let globals = Globals::new(

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::WindowHandle<'_>, rwh::HandleError> {
         let window = NonNull::new(self.window).unwrap();
@@ -97,7 +102,7 @@ pub struct WaylandWindowState {
     outputs: HashMap<ObjectId, Output>,
     display: Option<(ObjectId, Output)>,
     globals: Globals,
-    renderer: BladeRenderer,
+    renderer: WgpuRenderer,
     bounds: Bounds<Pixels>,
     scale: f32,
     input_handler: Option<PlatformInputHandler>,
@@ -314,7 +319,7 @@ impl WaylandWindowState {
         viewport: Option<wp_viewport::WpViewport>,
         client: WaylandClientStatePtr,
         globals: Globals,
-        gpu_context: &BladeContext,
+        gpu_context: &WgpuContext,
         options: WindowParams,
         parent: Option<WaylandWindowStatePtr>,
     ) -> anyhow::Result<Self> {
@@ -328,15 +333,14 @@ impl WaylandWindowState {
                     .display_ptr()
                     .cast::<c_void>(),
             };
-            let config = BladeSurfaceConfig {
-                size: gpu::Extent {
-                    width: options.bounds.size.width.0 as u32,
-                    height: options.bounds.size.height.0 as u32,
-                    depth: 1,
+            let config = WgpuSurfaceConfig {
+                size: Size {
+                    width: DevicePixels(options.bounds.size.width.0 as i32),
+                    height: DevicePixels(options.bounds.size.height.0 as i32),
                 },
                 transparent: true,
             };
-            BladeRenderer::new(gpu_context, &raw_window, config)?
+            WgpuRenderer::new(gpu_context, &raw_window, config)?
         };
 
         if let WaylandSurfaceState::Xdg(ref xdg_state) = surface_state {
@@ -479,7 +483,7 @@ impl WaylandWindow {
     pub fn new(
         handle: AnyWindowHandle,
         globals: Globals,
-        gpu_context: &BladeContext,
+        gpu_context: &WgpuContext,
         client: WaylandClientStatePtr,
         params: WindowParams,
         appearance: WindowAppearance,

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<Pixels>,
     pub(crate) current_count: usize,
 
-    pub(crate) gpu_context: BladeContext,
+    pub(crate) gpu_context: WgpuContext,
 
     pub(crate) scale_factor: f32,
 
@@ -420,7 +420,7 @@ impl X11Client {
             .to_string();
         let keyboard_layout = LinuxKeyboardLayout::new(layout_name.into());
 
-        let gpu_context = BladeContext::new().notify_err("Unable to init GPU context");
+        let gpu_context = WgpuContext::new().notify_err("Unable to init GPU context");
 
         let resource_database = x11rb::resource_manager::new_from_default(&xcb_connection)
             .context("Failed to create resource database")?;

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<XCBConnection>,
     x_window: xproto::Window,
-) -> anyhow::Result<gpu::Extent> {
+) -> anyhow::Result<Size<DevicePixels>> {
     let reply = get_reply(|| "X11 GetGeometry failed.", xcb.get_geometry(x_window))?;
-    Ok(gpu::Extent {
-        width: reply.width as u32,
-        height: reply.height as u32,
-        depth: 1,
+    Ok(Size {
+        width: DevicePixels(reply.width as i32),
+        height: DevicePixels(reply.height as i32),
     })
 }
 
@@ -236,6 +234,12 @@ struct RawWindow {
     visual_id: u32,
 }
 
+// Safety: The raw pointers in RawWindow point to X11 connection
+// which is valid for the window's lifetime. These are used only for
+// passing to wgpu which needs Send+Sync for surface creation.
+unsafe impl Send for RawWindow {}
+unsafe impl Sync for RawWindow {}
+
 #[derive(Default)]
 pub struct Callbacks {
     request_frame: Option<Box<dyn FnMut(RequestFrameOptions)>>,
@@ -261,7 +265,7 @@ pub struct X11WindowState {
     pub(crate) last_sync_counter: Option<sync::Int64>,
     bounds: Bounds<Pixels>,
     scale_factor: f32,
-    renderer: BladeRenderer,
+    renderer: WgpuRenderer,
     display: Rc<dyn PlatformDisplay>,
     input_handler: Option<PlatformInputHandler>,
     appearance: WindowAppearance,
@@ -389,7 +393,7 @@ impl X11WindowState {
         handle: AnyWindowHandle,
         client: X11ClientStatePtr,
         executor: ForegroundExecutor,
-        gpu_context: &BladeContext,
+        gpu_context: &WgpuContext,
         params: WindowParams,
         xcb: &Rc<XCBConnection>,
         client_side_decorations_supported: bool,
@@ -682,7 +686,7 @@ impl X11WindowState {
                     window_id: x_window,
                     visual_id: visual.id,
                 };
-                let config = BladeSurfaceConfig {
+                let config = WgpuSurfaceConfig {
                     // Note: this has to be done after the GPU init, or otherwise
                     // the sizes are immediately invalidated.
                     size: query_render_extent(xcb, x_window)?,
@@ -692,7 +696,7 @@ impl X11WindowState {
                     // too
                     transparent: false,
                 };
-                BladeRenderer::new(gpu_context, &raw_window, config)?
+                WgpuRenderer::new(gpu_context, &raw_window, config)?
             };
 
             let display = Rc::new(X11Display::new(xcb, scale_factor, x_screen_index)?);
@@ -740,11 +744,7 @@ impl X11WindowState {
     }
 
     fn content_size(&self) -> Size<Pixels> {
-        let size = self.renderer.viewport_size();
-        Size {
-            width: size.width.into(),
-            height: size.height.into(),
-        }
+        self.bounds.size
     }
 }
 
@@ -800,7 +800,7 @@ impl X11Window {
         handle: AnyWindowHandle,
         client: X11ClientStatePtr,
         executor: ForegroundExecutor,
-        gpu_context: &BladeContext,
+        gpu_context: &WgpuContext,
         params: WindowParams,
         xcb: &Rc<XCBConnection>,
         client_side_decorations_supported: bool,
@@ -1167,10 +1167,7 @@ impl X11WindowStatePtr {
 
             let gpu_size = query_render_extent(&self.xcb, self.x_window)?;
             if true {
-                state.renderer.update_drawable_size(size(
-                    DevicePixels(gpu_size.width as i32),
-                    DevicePixels(gpu_size.height as i32),
-                ));
+                state.renderer.update_drawable_size(gpu_size);
                 resize_args = Some((state.content_size(), state.scale_factor));
             }
             if let Some(value) = state.last_sync_counter.take() {

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;
 

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();
     }

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::*;

crates/gpui/src/platform/blade/shaders.wgsl → crates/gpui/src/platform/wgpu/shaders.wgsl 🔗

@@ -84,12 +84,17 @@ struct GlobalParams {
     pad: u32,
 }
 
-var<uniform> globals: GlobalParams;
-var<uniform> gamma_ratios: vec4<f32>;
-var<uniform> grayscale_enhanced_contrast: f32;
-var<uniform> subpixel_enhanced_contrast: f32;
-var t_sprite: texture_2d<f32>;
-var s_sprite: sampler;
+struct GammaParams {
+    gamma_ratios: vec4<f32>,
+    grayscale_enhanced_contrast: f32,
+    subpixel_enhanced_contrast: f32,
+    pad: vec2<f32>,
+}
+
+@group(0) @binding(0) var<uniform> globals: GlobalParams;
+@group(0) @binding(1) var<uniform> gamma_params: GammaParams;
+@group(1) @binding(1) var t_sprite: texture_2d<f32>;
+@group(1) @binding(2) var s_sprite: sampler;
 
 const M_PI_F: f32 = 3.1415926;
 const GRAYSCALE_FACTORS: vec3<f32> = vec3<f32>(0.2126, 0.7152, 0.0722);
@@ -521,7 +526,7 @@ struct Quad {
     corner_radii: Corners,
     border_widths: Edges,
 }
-var<storage, read> b_quads: array<Quad>;
+@group(1) @binding(0) var<storage, read> b_quads: array<Quad>;
 
 struct QuadVarying {
     @builtin(position) position: vec4<f32>,
@@ -951,7 +956,7 @@ struct Shadow {
     content_mask: Bounds,
     color: Hsla,
 }
-var<storage, read> b_shadows: array<Shadow>;
+@group(1) @binding(0) var<storage, read> b_shadows: array<Shadow>;
 
 struct ShadowVarying {
     @builtin(position) position: vec4<f32>,
@@ -1023,7 +1028,7 @@ struct PathRasterizationVertex {
     bounds: Bounds,
 }
 
-var<storage, read> b_path_vertices: array<PathRasterizationVertex>;
+@group(1) @binding(0) var<storage, read> b_path_vertices: array<PathRasterizationVertex>;
 
 struct PathRasterizationVarying {
     @builtin(position) position: vec4<f32>,
@@ -1083,7 +1088,7 @@ fn fs_path_rasterization(input: PathRasterizationVarying) -> @location(0) vec4<f
 struct PathSprite {
     bounds: Bounds,
 }
-var<storage, read> b_path_sprites: array<PathSprite>;
+@group(1) @binding(0) var<storage, read> b_path_sprites: array<PathSprite>;
 
 struct PathVarying {
     @builtin(position) position: vec4<f32>,
@@ -1124,7 +1129,7 @@ struct Underline {
     thickness: f32,
     wavy: u32,
 }
-var<storage, read> b_underlines: array<Underline>;
+@group(1) @binding(0) var<storage, read> b_underlines: array<Underline>;
 
 struct UnderlineVarying {
     @builtin(position) position: vec4<f32>,
@@ -1190,7 +1195,7 @@ struct MonochromeSprite {
     tile: AtlasTile,
     transformation: TransformationMatrix,
 }
-var<storage, read> b_mono_sprites: array<MonochromeSprite>;
+@group(1) @binding(0) var<storage, read> b_mono_sprites: array<MonochromeSprite>;
 
 struct MonoSpriteVarying {
     @builtin(position) position: vec4<f32>,
@@ -1216,7 +1221,7 @@ fn vs_mono_sprite(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index
 @fragment
 fn fs_mono_sprite(input: MonoSpriteVarying) -> @location(0) vec4<f32> {
     let sample = textureSample(t_sprite, s_sprite, input.tile_position).r;
-    let alpha_corrected = apply_contrast_and_gamma_correction(sample, input.color.rgb, grayscale_enhanced_contrast, gamma_ratios);
+    let alpha_corrected = apply_contrast_and_gamma_correction(sample, input.color.rgb, gamma_params.grayscale_enhanced_contrast, gamma_params.gamma_ratios);
 
     // Alpha clip after using the derivatives.
     if (any(input.clip_distances < vec4<f32>(0.0))) {
@@ -1238,7 +1243,7 @@ struct PolychromeSprite {
     corner_radii: Corners,
     tile: AtlasTile,
 }
-var<storage, read> b_poly_sprites: array<PolychromeSprite>;
+@group(1) @binding(0) var<storage, read> b_poly_sprites: array<PolychromeSprite>;
 
 struct PolySpriteVarying {
     @builtin(position) position: vec4<f32>,
@@ -1286,10 +1291,10 @@ struct SurfaceParams {
     content_mask: Bounds,
 }
 
-var<uniform> surface_locals: SurfaceParams;
-var t_y: texture_2d<f32>;
-var t_cb_cr: texture_2d<f32>;
-var s_surface: sampler;
+@group(1) @binding(0) var<uniform> surface_locals: SurfaceParams;
+@group(1) @binding(1) var t_y: texture_2d<f32>;
+@group(1) @binding(2) var t_cb_cr: texture_2d<f32>;
+@group(1) @binding(3) var s_surface: sampler;
 
 const ycbcr_to_RGB = mat4x4<f32>(
     vec4<f32>( 1.0000f,  1.0000f,  1.0000f, 0.0),
@@ -1341,7 +1346,7 @@ struct SubpixelSprite {
     tile: AtlasTile,
     transformation: TransformationMatrix,
 }
-var<storage, read> b_subpixel_sprites: array<SubpixelSprite>;
+@group(1) @binding(0) var<storage, read> b_subpixel_sprites: array<SubpixelSprite>;
 
 struct SubpixelSpriteOutput {
     @builtin(position) position: vec4<f32>,
@@ -1371,7 +1376,7 @@ fn vs_subpixel_sprite(@builtin(vertex_index) vertex_id: u32, @builtin(instance_i
 @fragment
 fn fs_subpixel_sprite(input: SubpixelSpriteOutput) -> SubpixelSpriteFragmentOutput {
     let sample = textureSample(t_sprite, s_sprite, input.tile_position).rgb;
-    let alpha_corrected = apply_contrast_and_gamma_correction3(sample, input.color.rgb, subpixel_enhanced_contrast, gamma_ratios);
+    let alpha_corrected = apply_contrast_and_gamma_correction3(sample, input.color.rgb, gamma_params.subpixel_enhanced_contrast, gamma_params.gamma_ratios);
 
     // Alpha clip after using the derivatives.
     if (any(input.clip_distances < vec4<f32>(0.0))) {

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<DevicePixels>) -> etagere::Size {
+    size2(size.width.0, size.height.0)
+}
+
+fn etagere_point_to_device(point: etagere::Point) -> Point<DevicePixels> {
+    Point {
+        x: DevicePixels(point.x),
+        y: DevicePixels(point.y),
+    }
+}
+
+pub(crate) struct WgpuAtlas(Mutex<WgpuAtlasState>);
+
+struct PendingUpload {
+    id: AtlasTextureId,
+    bounds: Bounds<DevicePixels>,
+    data: Vec<u8>,
+}
+
+struct WgpuAtlasState {
+    device: Arc<wgpu::Device>,
+    queue: Arc<wgpu::Queue>,
+    storage: WgpuAtlasStorage,
+    tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
+    pending_uploads: Vec<PendingUpload>,
+}
+
+pub struct WgpuTextureInfo {
+    pub view: wgpu::TextureView,
+}
+
+impl WgpuAtlas {
+    pub(crate) fn new(device: Arc<wgpu::Device>, queue: Arc<wgpu::Queue>) -> Self {
+        WgpuAtlas(Mutex::new(WgpuAtlasState {
+            device,
+            queue,
+            storage: WgpuAtlasStorage::default(),
+            tiles_by_key: Default::default(),
+            pending_uploads: Vec::new(),
+        }))
+    }
+
+    pub fn before_frame(&self) {
+        let mut lock = self.0.lock();
+        lock.flush_uploads();
+    }
+
+    pub fn get_texture_info(&self, id: AtlasTextureId) -> WgpuTextureInfo {
+        let lock = self.0.lock();
+        let texture = &lock.storage[id];
+        WgpuTextureInfo {
+            view: texture.view.clone(),
+        }
+    }
+}
+
+impl PlatformAtlas for WgpuAtlas {
+    fn get_or_insert_with<'a>(
+        &self,
+        key: &AtlasKey,
+        build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
+    ) -> Result<Option<AtlasTile>> {
+        let mut lock = self.0.lock();
+        if let Some(tile) = lock.tiles_by_key.get(key) {
+            Ok(Some(tile.clone()))
+        } else {
+            profiling::scope!("new tile");
+            let Some((size, bytes)) = build()? else {
+                return Ok(None);
+            };
+            let tile = lock.allocate(size, key.texture_kind());
+            lock.upload_texture(tile.texture_id, tile.bounds, &bytes);
+            lock.tiles_by_key.insert(key.clone(), tile.clone());
+            Ok(Some(tile))
+        }
+    }
+
+    fn remove(&self, key: &AtlasKey) {
+        let mut lock = self.0.lock();
+
+        let Some(id) = lock.tiles_by_key.remove(key).map(|tile| tile.texture_id) else {
+            return;
+        };
+
+        let Some(texture_slot) = lock.storage[id.kind].textures.get_mut(id.index as usize) else {
+            return;
+        };
+
+        if let Some(mut texture) = texture_slot.take() {
+            texture.decrement_ref_count();
+            if texture.is_unreferenced() {
+                lock.storage[id.kind]
+                    .free_list
+                    .push(texture.id.index as usize);
+            } else {
+                *texture_slot = Some(texture);
+            }
+        }
+    }
+}
+
+impl WgpuAtlasState {
+    fn allocate(&mut self, size: Size<DevicePixels>, texture_kind: AtlasTextureKind) -> AtlasTile {
+        {
+            let textures = &mut self.storage[texture_kind];
+
+            if let Some(tile) = textures
+                .iter_mut()
+                .rev()
+                .find_map(|texture| texture.allocate(size))
+            {
+                return tile;
+            }
+        }
+
+        let texture = self.push_texture(size, texture_kind);
+        texture
+            .allocate(size)
+            .expect("Failed to allocate from newly created texture")
+    }
+
+    fn push_texture(
+        &mut self,
+        min_size: Size<DevicePixels>,
+        kind: AtlasTextureKind,
+    ) -> &mut WgpuAtlasTexture {
+        const DEFAULT_ATLAS_SIZE: Size<DevicePixels> = Size {
+            width: DevicePixels(1024),
+            height: DevicePixels(1024),
+        };
+
+        let size = min_size.max(&DEFAULT_ATLAS_SIZE);
+        let format = match kind {
+            AtlasTextureKind::Monochrome => wgpu::TextureFormat::R8Unorm,
+            AtlasTextureKind::Subpixel => wgpu::TextureFormat::Bgra8Unorm,
+            AtlasTextureKind::Polychrome => wgpu::TextureFormat::Bgra8Unorm,
+        };
+
+        let texture = self.device.create_texture(&wgpu::TextureDescriptor {
+            label: Some("atlas"),
+            size: wgpu::Extent3d {
+                width: size.width.0 as u32,
+                height: size.height.0 as u32,
+                depth_or_array_layers: 1,
+            },
+            mip_level_count: 1,
+            sample_count: 1,
+            dimension: wgpu::TextureDimension::D2,
+            format,
+            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
+            view_formats: &[],
+        });
+
+        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
+
+        let texture_list = &mut self.storage[kind];
+        let index = texture_list.free_list.pop();
+
+        let atlas_texture = WgpuAtlasTexture {
+            id: AtlasTextureId {
+                index: index.unwrap_or(texture_list.textures.len()) as u32,
+                kind,
+            },
+            allocator: BucketedAtlasAllocator::new(device_size_to_etagere(size)),
+            format,
+            texture,
+            view,
+            live_atlas_keys: 0,
+        };
+
+        if let Some(ix) = index {
+            texture_list.textures[ix] = Some(atlas_texture);
+            texture_list
+                .textures
+                .get_mut(ix)
+                .and_then(|t| t.as_mut())
+                .expect("texture must exist")
+        } else {
+            texture_list.textures.push(Some(atlas_texture));
+            texture_list
+                .textures
+                .last_mut()
+                .and_then(|t| t.as_mut())
+                .expect("texture must exist")
+        }
+    }
+
+    fn upload_texture(&mut self, id: AtlasTextureId, bounds: Bounds<DevicePixels>, bytes: &[u8]) {
+        self.pending_uploads.push(PendingUpload {
+            id,
+            bounds,
+            data: bytes.to_vec(),
+        });
+    }
+
+    fn flush_uploads(&mut self) {
+        for upload in self.pending_uploads.drain(..) {
+            let texture = &self.storage[upload.id];
+            let bytes_per_pixel = texture.bytes_per_pixel();
+
+            self.queue.write_texture(
+                wgpu::TexelCopyTextureInfo {
+                    texture: &texture.texture,
+                    mip_level: 0,
+                    origin: wgpu::Origin3d {
+                        x: upload.bounds.origin.x.0 as u32,
+                        y: upload.bounds.origin.y.0 as u32,
+                        z: 0,
+                    },
+                    aspect: wgpu::TextureAspect::All,
+                },
+                &upload.data,
+                wgpu::TexelCopyBufferLayout {
+                    offset: 0,
+                    bytes_per_row: Some(upload.bounds.size.width.0 as u32 * bytes_per_pixel as u32),
+                    rows_per_image: None,
+                },
+                wgpu::Extent3d {
+                    width: upload.bounds.size.width.0 as u32,
+                    height: upload.bounds.size.height.0 as u32,
+                    depth_or_array_layers: 1,
+                },
+            );
+        }
+    }
+}
+
+#[derive(Default)]
+struct WgpuAtlasStorage {
+    monochrome_textures: AtlasTextureList<WgpuAtlasTexture>,
+    subpixel_textures: AtlasTextureList<WgpuAtlasTexture>,
+    polychrome_textures: AtlasTextureList<WgpuAtlasTexture>,
+}
+
+impl ops::Index<AtlasTextureKind> for WgpuAtlasStorage {
+    type Output = AtlasTextureList<WgpuAtlasTexture>;
+    fn index(&self, kind: AtlasTextureKind) -> &Self::Output {
+        match kind {
+            AtlasTextureKind::Monochrome => &self.monochrome_textures,
+            AtlasTextureKind::Subpixel => &self.subpixel_textures,
+            AtlasTextureKind::Polychrome => &self.polychrome_textures,
+        }
+    }
+}
+
+impl ops::IndexMut<AtlasTextureKind> for WgpuAtlasStorage {
+    fn index_mut(&mut self, kind: AtlasTextureKind) -> &mut Self::Output {
+        match kind {
+            AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
+            AtlasTextureKind::Subpixel => &mut self.subpixel_textures,
+            AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
+        }
+    }
+}
+
+impl ops::Index<AtlasTextureId> for WgpuAtlasStorage {
+    type Output = WgpuAtlasTexture;
+    fn index(&self, id: AtlasTextureId) -> &Self::Output {
+        let textures = match id.kind {
+            AtlasTextureKind::Monochrome => &self.monochrome_textures,
+            AtlasTextureKind::Subpixel => &self.subpixel_textures,
+            AtlasTextureKind::Polychrome => &self.polychrome_textures,
+        };
+        textures[id.index as usize]
+            .as_ref()
+            .expect("texture must exist")
+    }
+}
+
+struct WgpuAtlasTexture {
+    id: AtlasTextureId,
+    allocator: BucketedAtlasAllocator,
+    texture: wgpu::Texture,
+    view: wgpu::TextureView,
+    format: wgpu::TextureFormat,
+    live_atlas_keys: u32,
+}
+
+impl WgpuAtlasTexture {
+    fn allocate(&mut self, size: Size<DevicePixels>) -> Option<AtlasTile> {
+        let allocation = self.allocator.allocate(device_size_to_etagere(size))?;
+        let tile = AtlasTile {
+            texture_id: self.id,
+            tile_id: allocation.id.into(),
+            padding: 0,
+            bounds: Bounds {
+                origin: etagere_point_to_device(allocation.rectangle.min),
+                size,
+            },
+        };
+        self.live_atlas_keys += 1;
+        Some(tile)
+    }
+
+    fn bytes_per_pixel(&self) -> u8 {
+        match self.format {
+            wgpu::TextureFormat::R8Unorm => 1,
+            wgpu::TextureFormat::Bgra8Unorm => 4,
+            _ => 4,
+        }
+    }
+
+    fn decrement_ref_count(&mut self) {
+        self.live_atlas_keys -= 1;
+    }
+
+    fn is_unreferenced(&self) -> bool {
+        self.live_atlas_keys == 0
+    }
+}

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<wgpu::Device>,
+    pub queue: Arc<wgpu::Queue>,
+    dual_source_blending: bool,
+}
+
+impl WgpuContext {
+    pub fn new() -> anyhow::Result<Self> {
+        let device_id_filter = match std::env::var("ZED_DEVICE_ID") {
+            Ok(val) => parse_pci_id(&val)
+                .context("Failed to parse device ID from `ZED_DEVICE_ID` environment variable")
+                .log_err(),
+            Err(std::env::VarError::NotPresent) => None,
+            err => {
+                err.context("Failed to read value of `ZED_DEVICE_ID` environment variable")
+                    .log_err();
+                None
+            }
+        };
+
+        let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
+            backends: wgpu::Backends::VULKAN | wgpu::Backends::GL,
+            flags: wgpu::InstanceFlags::default(),
+            backend_options: wgpu::BackendOptions::default(),
+            memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
+        });
+
+        let adapter = smol::block_on(Self::select_adapter(&instance, device_id_filter))?;
+
+        log::info!(
+            "Selected GPU adapter: {:?} ({:?})",
+            adapter.get_info().name,
+            adapter.get_info().backend
+        );
+
+        let dual_source_blending_available = adapter
+            .features()
+            .contains(wgpu::Features::DUAL_SOURCE_BLENDING);
+
+        let mut required_features = wgpu::Features::empty();
+        if dual_source_blending_available {
+            required_features |= wgpu::Features::DUAL_SOURCE_BLENDING;
+        } else {
+            log::warn!(
+                "Dual-source blending not available on this GPU. \
+                Subpixel text antialiasing will be disabled."
+            );
+        }
+
+        let (device, queue) = smol::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
+            label: Some("gpui_device"),
+            required_features,
+            required_limits: wgpu::Limits::default(),
+            memory_hints: wgpu::MemoryHints::MemoryUsage,
+            trace: wgpu::Trace::Off,
+            experimental_features: wgpu::ExperimentalFeatures::disabled(),
+        }))
+        .map_err(|e| anyhow::anyhow!("Failed to create wgpu device: {e}"))?;
+
+        Ok(Self {
+            instance,
+            adapter,
+            device: Arc::new(device),
+            queue: Arc::new(queue),
+            dual_source_blending: dual_source_blending_available,
+        })
+    }
+
+    async fn select_adapter(
+        instance: &wgpu::Instance,
+        device_id_filter: Option<u32>,
+    ) -> anyhow::Result<wgpu::Adapter> {
+        if let Some(device_id) = device_id_filter {
+            let adapters: Vec<_> = instance.enumerate_adapters(wgpu::Backends::all()).await;
+
+            if adapters.is_empty() {
+                anyhow::bail!("No GPU adapters found");
+            }
+
+            let mut non_matching_adapter_infos: Vec<wgpu::AdapterInfo> = Vec::new();
+
+            for adapter in adapters.into_iter() {
+                let info = adapter.get_info();
+                if info.device == device_id {
+                    log::info!(
+                        "Found GPU matching ZED_DEVICE_ID={:#06x}: {}",
+                        device_id,
+                        info.name
+                    );
+                    return Ok(adapter);
+                } else {
+                    non_matching_adapter_infos.push(info);
+                }
+            }
+
+            log::warn!(
+                "No GPU found matching ZED_DEVICE_ID={:#06x}. Available devices:",
+                device_id
+            );
+
+            for info in &non_matching_adapter_infos {
+                log::warn!(
+                    "  - {} (device_id={:#06x}, backend={})",
+                    info.name,
+                    info.device,
+                    info.backend
+                );
+            }
+        }
+
+        instance
+            .request_adapter(&wgpu::RequestAdapterOptions {
+                power_preference: wgpu::PowerPreference::None,
+                compatible_surface: None,
+                force_fallback_adapter: false,
+            })
+            .await
+            .map_err(|e| anyhow::anyhow!("Failed to request GPU adapter: {e}"))
+    }
+
+    pub fn supports_dual_source_blending(&self) -> bool {
+        self.dual_source_blending
+    }
+}
+
+fn parse_pci_id(id: &str) -> anyhow::Result<u32> {
+    let mut id = id.trim();
+
+    if id.starts_with("0x") || id.starts_with("0X") {
+        id = &id[2..];
+    }
+    let is_hex_string = id.chars().all(|c| c.is_ascii_hexdigit());
+    let is_4_chars = id.len() == 4;
+    anyhow::ensure!(
+        is_4_chars && is_hex_string,
+        "Expected a 4 digit PCI ID in hexadecimal format"
+    );
+
+    u32::from_str_radix(id, 16).context("parsing PCI ID as hex")
+}
+
+#[cfg(test)]
+mod tests {
+    use super::parse_pci_id;
+
+    #[test]
+    fn test_parse_device_id() {
+        assert!(parse_pci_id("0xABCD").is_ok());
+        assert!(parse_pci_id("ABCD").is_ok());
+        assert!(parse_pci_id("abcd").is_ok());
+        assert!(parse_pci_id("1234").is_ok());
+        assert!(parse_pci_id("123").is_err());
+        assert_eq!(
+            parse_pci_id(&format!("{:x}", 0x1234)).unwrap(),
+            parse_pci_id(&format!("{:X}", 0x1234)).unwrap(),
+        );
+
+        assert_eq!(
+            parse_pci_id(&format!("{:#x}", 0x1234)).unwrap(),
+            parse_pci_id(&format!("{:#X}", 0x1234)).unwrap(),
+        );
+    }
+}

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<Bounds<ScaledPixels>> for PodBounds {
+    fn from(bounds: Bounds<ScaledPixels>) -> Self {
+        Self {
+            origin: [bounds.origin.x.0, bounds.origin.y.0],
+            size: [bounds.size.width.0, bounds.size.height.0],
+        }
+    }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Pod, Zeroable)]
+struct SurfaceParams {
+    bounds: PodBounds,
+    content_mask: PodBounds,
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Pod, Zeroable)]
+struct GammaParams {
+    gamma_ratios: [f32; 4],
+    grayscale_enhanced_contrast: f32,
+    subpixel_enhanced_contrast: f32,
+    _pad: [f32; 2],
+}
+
+#[derive(Clone, Debug)]
+#[repr(C)]
+struct PathSprite {
+    bounds: Bounds<ScaledPixels>,
+}
+
+#[derive(Clone, Debug)]
+#[repr(C)]
+struct PathRasterizationVertex {
+    xy_position: Point<ScaledPixels>,
+    st_position: Point<f32>,
+    color: Background,
+    bounds: Bounds<ScaledPixels>,
+}
+
+pub struct WgpuSurfaceConfig {
+    pub size: Size<DevicePixels>,
+    pub transparent: bool,
+}
+
+struct WgpuPipelines {
+    quads: wgpu::RenderPipeline,
+    shadows: wgpu::RenderPipeline,
+    path_rasterization: wgpu::RenderPipeline,
+    paths: wgpu::RenderPipeline,
+    underlines: wgpu::RenderPipeline,
+    mono_sprites: wgpu::RenderPipeline,
+    subpixel_sprites: Option<wgpu::RenderPipeline>,
+    poly_sprites: wgpu::RenderPipeline,
+    #[allow(dead_code)]
+    surfaces: wgpu::RenderPipeline,
+}
+
+struct WgpuBindGroupLayouts {
+    globals: wgpu::BindGroupLayout,
+    instances: wgpu::BindGroupLayout,
+    instances_with_texture: wgpu::BindGroupLayout,
+    surfaces: wgpu::BindGroupLayout,
+}
+
+pub struct WgpuRenderer {
+    device: Arc<wgpu::Device>,
+    queue: Arc<wgpu::Queue>,
+    surface: wgpu::Surface<'static>,
+    surface_config: wgpu::SurfaceConfiguration,
+    pipelines: WgpuPipelines,
+    bind_group_layouts: WgpuBindGroupLayouts,
+    atlas: Arc<WgpuAtlas>,
+    atlas_sampler: wgpu::Sampler,
+    globals_buffer: wgpu::Buffer,
+    path_globals_offset: u64,
+    gamma_offset: u64,
+    globals_bind_group: wgpu::BindGroup,
+    path_globals_bind_group: wgpu::BindGroup,
+    instance_buffer: wgpu::Buffer,
+    instance_buffer_capacity: u64,
+    storage_buffer_alignment: u64,
+    path_intermediate_texture: wgpu::Texture,
+    path_intermediate_view: wgpu::TextureView,
+    path_msaa_texture: Option<wgpu::Texture>,
+    path_msaa_view: Option<wgpu::TextureView>,
+    rendering_params: RenderingParameters,
+    dual_source_blending: bool,
+    adapter_info: wgpu::AdapterInfo,
+    transparent_alpha_mode: wgpu::CompositeAlphaMode,
+    opaque_alpha_mode: wgpu::CompositeAlphaMode,
+}
+
+impl WgpuRenderer {
+    /// Creates a new WgpuRenderer from raw window handles.
+    ///
+    /// # Safety
+    /// The caller must ensure that the window handle remains valid for the lifetime
+    /// of the returned renderer.
+    pub fn new<W: HasWindowHandle + HasDisplayHandle>(
+        context: &WgpuContext,
+        window: &W,
+        config: WgpuSurfaceConfig,
+    ) -> anyhow::Result<Self> {
+        let window_handle = window
+            .window_handle()
+            .map_err(|e| anyhow::anyhow!("Failed to get window handle: {e}"))?;
+        let display_handle = window
+            .display_handle()
+            .map_err(|e| anyhow::anyhow!("Failed to get display handle: {e}"))?;
+
+        let target = wgpu::SurfaceTargetUnsafe::RawHandle {
+            raw_display_handle: display_handle.as_raw(),
+            raw_window_handle: window_handle.as_raw(),
+        };
+
+        // Safety: The caller guarantees that the window handle is valid for the
+        // lifetime of this renderer. In practice, the RawWindow struct is created
+        // from the native window handles and the surface is dropped before the window.
+        let surface = unsafe {
+            context
+                .instance
+                .create_surface_unsafe(target)
+                .map_err(|e| anyhow::anyhow!("Failed to create surface: {e}"))?
+        };
+
+        let surface_caps = surface.get_capabilities(&context.adapter);
+        // Prefer standard 8-bit non-sRGB formats that don't require special features.
+        // Other formats like Rgba16Unorm require TEXTURE_FORMAT_16BIT_NORM which may
+        // not be available on all devices.
+        let preferred_formats = [
+            wgpu::TextureFormat::Bgra8Unorm,
+            wgpu::TextureFormat::Rgba8Unorm,
+        ];
+        let surface_format = preferred_formats
+            .iter()
+            .find(|f| surface_caps.formats.contains(f))
+            .copied()
+            .or_else(|| surface_caps.formats.iter().find(|f| !f.is_srgb()).copied())
+            .unwrap_or(surface_caps.formats[0]);
+
+        let pick_alpha_mode =
+            |preferences: &[wgpu::CompositeAlphaMode]| -> wgpu::CompositeAlphaMode {
+                preferences
+                    .iter()
+                    .find(|p| surface_caps.alpha_modes.contains(p))
+                    .copied()
+                    .unwrap_or(surface_caps.alpha_modes[0])
+            };
+
+        let transparent_alpha_mode = pick_alpha_mode(&[
+            wgpu::CompositeAlphaMode::PreMultiplied,
+            wgpu::CompositeAlphaMode::Inherit,
+        ]);
+
+        let opaque_alpha_mode = pick_alpha_mode(&[
+            wgpu::CompositeAlphaMode::Opaque,
+            wgpu::CompositeAlphaMode::Inherit,
+        ]);
+
+        let alpha_mode = if config.transparent {
+            transparent_alpha_mode
+        } else {
+            opaque_alpha_mode
+        };
+
+        let surface_config = wgpu::SurfaceConfiguration {
+            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
+            format: surface_format,
+            width: config.size.width.0 as u32,
+            height: config.size.height.0 as u32,
+            present_mode: wgpu::PresentMode::Fifo,
+            desired_maximum_frame_latency: 2,
+            alpha_mode,
+            view_formats: vec![],
+        };
+        surface.configure(&context.device, &surface_config);
+
+        let device = Arc::clone(&context.device);
+        let queue = Arc::clone(&context.queue);
+        let dual_source_blending = context.supports_dual_source_blending();
+
+        let rendering_params = RenderingParameters::new(&context.adapter, surface_format);
+        let bind_group_layouts = Self::create_bind_group_layouts(&device);
+        let pipelines = Self::create_pipelines(
+            &device,
+            &bind_group_layouts,
+            surface_format,
+            alpha_mode,
+            rendering_params.path_sample_count,
+            dual_source_blending,
+        );
+
+        let atlas = Arc::new(WgpuAtlas::new(Arc::clone(&device), Arc::clone(&queue)));
+        let atlas_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
+            label: Some("atlas_sampler"),
+            mag_filter: wgpu::FilterMode::Linear,
+            min_filter: wgpu::FilterMode::Linear,
+            ..Default::default()
+        });
+
+        let uniform_alignment = device.limits().min_uniform_buffer_offset_alignment as u64;
+        let globals_size = std::mem::size_of::<GlobalParams>() as u64;
+        let gamma_size = std::mem::size_of::<GammaParams>() as u64;
+        let path_globals_offset = globals_size.next_multiple_of(uniform_alignment);
+        let gamma_offset = (path_globals_offset + globals_size).next_multiple_of(uniform_alignment);
+
+        let globals_buffer = device.create_buffer(&wgpu::BufferDescriptor {
+            label: Some("globals_buffer"),
+            size: gamma_offset + gamma_size,
+            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
+            mapped_at_creation: false,
+        });
+
+        let storage_buffer_alignment = device.limits().min_storage_buffer_offset_alignment as u64;
+        let initial_instance_buffer_capacity = 2 * 1024 * 1024;
+        let instance_buffer = device.create_buffer(&wgpu::BufferDescriptor {
+            label: Some("instance_buffer"),
+            size: initial_instance_buffer_capacity,
+            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
+            mapped_at_creation: false,
+        });
+
+        let (path_intermediate_texture, path_intermediate_view) = Self::create_path_intermediate(
+            &device,
+            surface_format,
+            config.size.width.0 as u32,
+            config.size.height.0 as u32,
+        );
+
+        let (path_msaa_texture, path_msaa_view) = Self::create_msaa_if_needed(
+            &device,
+            surface_format,
+            config.size.width.0 as u32,
+            config.size.height.0 as u32,
+            rendering_params.path_sample_count,
+        )
+        .map(|(t, v)| (Some(t), Some(v)))
+        .unwrap_or((None, None));
+
+        let globals_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
+            label: Some("globals_bind_group"),
+            layout: &bind_group_layouts.globals,
+            entries: &[
+                wgpu::BindGroupEntry {
+                    binding: 0,
+                    resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
+                        buffer: &globals_buffer,
+                        offset: 0,
+                        size: Some(NonZeroU64::new(globals_size).unwrap()),
+                    }),
+                },
+                wgpu::BindGroupEntry {
+                    binding: 1,
+                    resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
+                        buffer: &globals_buffer,
+                        offset: gamma_offset,
+                        size: Some(NonZeroU64::new(gamma_size).unwrap()),
+                    }),
+                },
+            ],
+        });
+
+        let path_globals_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
+            label: Some("path_globals_bind_group"),
+            layout: &bind_group_layouts.globals,
+            entries: &[
+                wgpu::BindGroupEntry {
+                    binding: 0,
+                    resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
+                        buffer: &globals_buffer,
+                        offset: path_globals_offset,
+                        size: Some(NonZeroU64::new(globals_size).unwrap()),
+                    }),
+                },
+                wgpu::BindGroupEntry {
+                    binding: 1,
+                    resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
+                        buffer: &globals_buffer,
+                        offset: gamma_offset,
+                        size: Some(NonZeroU64::new(gamma_size).unwrap()),
+                    }),
+                },
+            ],
+        });
+
+        let adapter_info = context.adapter.get_info();
+
+        Ok(Self {
+            device,
+            queue,
+            surface,
+            surface_config,
+            pipelines,
+            bind_group_layouts,
+            atlas,
+            atlas_sampler,
+            globals_buffer,
+            path_globals_offset,
+            gamma_offset,
+            globals_bind_group,
+            path_globals_bind_group,
+            instance_buffer,
+            instance_buffer_capacity: initial_instance_buffer_capacity,
+            storage_buffer_alignment,
+            path_intermediate_texture,
+            path_intermediate_view,
+            path_msaa_texture,
+            path_msaa_view,
+            rendering_params,
+            dual_source_blending,
+            adapter_info,
+            transparent_alpha_mode,
+            opaque_alpha_mode,
+        })
+    }
+
+    fn create_bind_group_layouts(device: &wgpu::Device) -> WgpuBindGroupLayouts {
+        let globals =
+            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+                label: Some("globals_layout"),
+                entries: &[
+                    wgpu::BindGroupLayoutEntry {
+                        binding: 0,
+                        visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
+                        ty: wgpu::BindingType::Buffer {
+                            ty: wgpu::BufferBindingType::Uniform,
+                            has_dynamic_offset: false,
+                            min_binding_size: NonZeroU64::new(
+                                std::mem::size_of::<GlobalParams>() as u64
+                            ),
+                        },
+                        count: None,
+                    },
+                    wgpu::BindGroupLayoutEntry {
+                        binding: 1,
+                        visibility: wgpu::ShaderStages::FRAGMENT,
+                        ty: wgpu::BindingType::Buffer {
+                            ty: wgpu::BufferBindingType::Uniform,
+                            has_dynamic_offset: false,
+                            min_binding_size: NonZeroU64::new(
+                                std::mem::size_of::<GammaParams>() as u64
+                            ),
+                        },
+                        count: None,
+                    },
+                ],
+            });
+
+        let storage_buffer_entry = |binding: u32| wgpu::BindGroupLayoutEntry {
+            binding,
+            visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
+            ty: wgpu::BindingType::Buffer {
+                ty: wgpu::BufferBindingType::Storage { read_only: true },
+                has_dynamic_offset: false,
+                min_binding_size: None,
+            },
+            count: None,
+        };
+
+        let instances = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+            label: Some("instances_layout"),
+            entries: &[storage_buffer_entry(0)],
+        });
+
+        let instances_with_texture =
+            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+                label: Some("instances_with_texture_layout"),
+                entries: &[
+                    storage_buffer_entry(0),
+                    wgpu::BindGroupLayoutEntry {
+                        binding: 1,
+                        visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
+                        ty: wgpu::BindingType::Texture {
+                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
+                            view_dimension: wgpu::TextureViewDimension::D2,
+                            multisampled: false,
+                        },
+                        count: None,
+                    },
+                    wgpu::BindGroupLayoutEntry {
+                        binding: 2,
+                        visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
+                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
+                        count: None,
+                    },
+                ],
+            });
+
+        let surfaces = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+            label: Some("surfaces_layout"),
+            entries: &[
+                wgpu::BindGroupLayoutEntry {
+                    binding: 0,
+                    visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
+                    ty: wgpu::BindingType::Buffer {
+                        ty: wgpu::BufferBindingType::Uniform,
+                        has_dynamic_offset: false,
+                        min_binding_size: NonZeroU64::new(
+                            std::mem::size_of::<SurfaceParams>() as u64
+                        ),
+                    },
+                    count: None,
+                },
+                wgpu::BindGroupLayoutEntry {
+                    binding: 1,
+                    visibility: wgpu::ShaderStages::FRAGMENT,
+                    ty: wgpu::BindingType::Texture {
+                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
+                        view_dimension: wgpu::TextureViewDimension::D2,
+                        multisampled: false,
+                    },
+                    count: None,
+                },
+                wgpu::BindGroupLayoutEntry {
+                    binding: 2,
+                    visibility: wgpu::ShaderStages::FRAGMENT,
+                    ty: wgpu::BindingType::Texture {
+                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
+                        view_dimension: wgpu::TextureViewDimension::D2,
+                        multisampled: false,
+                    },
+                    count: None,
+                },
+                wgpu::BindGroupLayoutEntry {
+                    binding: 3,
+                    visibility: wgpu::ShaderStages::FRAGMENT,
+                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
+                    count: None,
+                },
+            ],
+        });
+
+        WgpuBindGroupLayouts {
+            globals,
+            instances,
+            instances_with_texture,
+            surfaces,
+        }
+    }
+
+    fn create_pipelines(
+        device: &wgpu::Device,
+        layouts: &WgpuBindGroupLayouts,
+        surface_format: wgpu::TextureFormat,
+        alpha_mode: wgpu::CompositeAlphaMode,
+        path_sample_count: u32,
+        dual_source_blending: bool,
+    ) -> WgpuPipelines {
+        let shader_source = include_str!("shaders.wgsl");
+        let shader_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
+            label: Some("gpui_shaders"),
+            source: wgpu::ShaderSource::Wgsl(shader_source.into()),
+        });
+
+        let blend_mode = match alpha_mode {
+            wgpu::CompositeAlphaMode::PreMultiplied => {
+                wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING
+            }
+            _ => wgpu::BlendState::ALPHA_BLENDING,
+        };
+
+        let color_target = wgpu::ColorTargetState {
+            format: surface_format,
+            blend: Some(blend_mode),
+            write_mask: wgpu::ColorWrites::ALL,
+        };
+
+        let create_pipeline = |name: &str,
+                               vs_entry: &str,
+                               fs_entry: &str,
+                               globals_layout: &wgpu::BindGroupLayout,
+                               data_layout: &wgpu::BindGroupLayout,
+                               topology: wgpu::PrimitiveTopology,
+                               color_targets: &[Option<wgpu::ColorTargetState>],
+                               sample_count: u32| {
+            let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
+                label: Some(&format!("{name}_layout")),
+                bind_group_layouts: &[globals_layout, data_layout],
+                immediate_size: 0,
+            });
+
+            device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
+                label: Some(name),
+                layout: Some(&pipeline_layout),
+                vertex: wgpu::VertexState {
+                    module: &shader_module,
+                    entry_point: Some(vs_entry),
+                    buffers: &[],
+                    compilation_options: wgpu::PipelineCompilationOptions::default(),
+                },
+                fragment: Some(wgpu::FragmentState {
+                    module: &shader_module,
+                    entry_point: Some(fs_entry),
+                    targets: color_targets,
+                    compilation_options: wgpu::PipelineCompilationOptions::default(),
+                }),
+                primitive: wgpu::PrimitiveState {
+                    topology,
+                    strip_index_format: None,
+                    front_face: wgpu::FrontFace::Ccw,
+                    cull_mode: None,
+                    polygon_mode: wgpu::PolygonMode::Fill,
+                    unclipped_depth: false,
+                    conservative: false,
+                },
+                depth_stencil: None,
+                multisample: wgpu::MultisampleState {
+                    count: sample_count,
+                    mask: !0,
+                    alpha_to_coverage_enabled: false,
+                },
+                multiview_mask: None,
+                cache: None,
+            })
+        };
+
+        let quads = create_pipeline(
+            "quads",
+            "vs_quad",
+            "fs_quad",
+            &layouts.globals,
+            &layouts.instances,
+            wgpu::PrimitiveTopology::TriangleStrip,
+            &[Some(color_target.clone())],
+            1,
+        );
+
+        let shadows = create_pipeline(
+            "shadows",
+            "vs_shadow",
+            "fs_shadow",
+            &layouts.globals,
+            &layouts.instances,
+            wgpu::PrimitiveTopology::TriangleStrip,
+            &[Some(color_target.clone())],
+            1,
+        );
+
+        let path_rasterization = create_pipeline(
+            "path_rasterization",
+            "vs_path_rasterization",
+            "fs_path_rasterization",
+            &layouts.globals,
+            &layouts.instances,
+            wgpu::PrimitiveTopology::TriangleList,
+            &[Some(wgpu::ColorTargetState {
+                format: surface_format,
+                blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
+                write_mask: wgpu::ColorWrites::ALL,
+            })],
+            path_sample_count,
+        );
+
+        let paths_blend = wgpu::BlendState {
+            color: wgpu::BlendComponent {
+                src_factor: wgpu::BlendFactor::One,
+                dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
+                operation: wgpu::BlendOperation::Add,
+            },
+            alpha: wgpu::BlendComponent {
+                src_factor: wgpu::BlendFactor::One,
+                dst_factor: wgpu::BlendFactor::One,
+                operation: wgpu::BlendOperation::Add,
+            },
+        };
+
+        let paths = create_pipeline(
+            "paths",
+            "vs_path",
+            "fs_path",
+            &layouts.globals,
+            &layouts.instances_with_texture,
+            wgpu::PrimitiveTopology::TriangleStrip,
+            &[Some(wgpu::ColorTargetState {
+                format: surface_format,
+                blend: Some(paths_blend),
+                write_mask: wgpu::ColorWrites::ALL,
+            })],
+            1,
+        );
+
+        let underlines = create_pipeline(
+            "underlines",
+            "vs_underline",
+            "fs_underline",
+            &layouts.globals,
+            &layouts.instances,
+            wgpu::PrimitiveTopology::TriangleStrip,
+            &[Some(color_target.clone())],
+            1,
+        );
+
+        let mono_sprites = create_pipeline(
+            "mono_sprites",
+            "vs_mono_sprite",
+            "fs_mono_sprite",
+            &layouts.globals,
+            &layouts.instances_with_texture,
+            wgpu::PrimitiveTopology::TriangleStrip,
+            &[Some(color_target.clone())],
+            1,
+        );
+
+        let subpixel_sprites = if dual_source_blending {
+            let subpixel_blend = wgpu::BlendState {
+                color: wgpu::BlendComponent {
+                    src_factor: wgpu::BlendFactor::Src1,
+                    dst_factor: wgpu::BlendFactor::OneMinusSrc1,
+                    operation: wgpu::BlendOperation::Add,
+                },
+                alpha: wgpu::BlendComponent {
+                    src_factor: wgpu::BlendFactor::One,
+                    dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
+                    operation: wgpu::BlendOperation::Add,
+                },
+            };
+
+            Some(create_pipeline(
+                "subpixel_sprites",
+                "vs_subpixel_sprite",
+                "fs_subpixel_sprite",
+                &layouts.globals,
+                &layouts.instances_with_texture,
+                wgpu::PrimitiveTopology::TriangleStrip,
+                &[Some(wgpu::ColorTargetState {
+                    format: surface_format,
+                    blend: Some(subpixel_blend),
+                    write_mask: wgpu::ColorWrites::COLOR,
+                })],
+                1,
+            ))
+        } else {
+            None
+        };
+
+        let poly_sprites = create_pipeline(
+            "poly_sprites",
+            "vs_poly_sprite",
+            "fs_poly_sprite",
+            &layouts.globals,
+            &layouts.instances_with_texture,
+            wgpu::PrimitiveTopology::TriangleStrip,
+            &[Some(color_target.clone())],
+            1,
+        );
+
+        let surfaces = create_pipeline(
+            "surfaces",
+            "vs_surface",
+            "fs_surface",
+            &layouts.globals,
+            &layouts.surfaces,
+            wgpu::PrimitiveTopology::TriangleStrip,
+            &[Some(color_target)],
+            1,
+        );
+
+        WgpuPipelines {
+            quads,
+            shadows,
+            path_rasterization,
+            paths,
+            underlines,
+            mono_sprites,
+            subpixel_sprites,
+            poly_sprites,
+            surfaces,
+        }
+    }
+
+    fn create_path_intermediate(
+        device: &wgpu::Device,
+        format: wgpu::TextureFormat,
+        width: u32,
+        height: u32,
+    ) -> (wgpu::Texture, wgpu::TextureView) {
+        let texture = device.create_texture(&wgpu::TextureDescriptor {
+            label: Some("path_intermediate"),
+            size: wgpu::Extent3d {
+                width: width.max(1),
+                height: height.max(1),
+                depth_or_array_layers: 1,
+            },
+            mip_level_count: 1,
+            sample_count: 1,
+            dimension: wgpu::TextureDimension::D2,
+            format,
+            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
+            view_formats: &[],
+        });
+        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
+        (texture, view)
+    }
+
+    fn create_msaa_if_needed(
+        device: &wgpu::Device,
+        format: wgpu::TextureFormat,
+        width: u32,
+        height: u32,
+        sample_count: u32,
+    ) -> Option<(wgpu::Texture, wgpu::TextureView)> {
+        if sample_count <= 1 {
+            return None;
+        }
+        let texture = device.create_texture(&wgpu::TextureDescriptor {
+            label: Some("path_msaa"),
+            size: wgpu::Extent3d {
+                width: width.max(1),
+                height: height.max(1),
+                depth_or_array_layers: 1,
+            },
+            mip_level_count: 1,
+            sample_count,
+            dimension: wgpu::TextureDimension::D2,
+            format,
+            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
+            view_formats: &[],
+        });
+        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
+        Some((texture, view))
+    }
+
+    pub fn update_drawable_size(&mut self, size: Size<DevicePixels>) {
+        let width = size.width.0 as u32;
+        let height = size.height.0 as u32;
+
+        if width != self.surface_config.width || height != self.surface_config.height {
+            self.surface_config.width = width.max(1);
+            self.surface_config.height = height.max(1);
+            self.surface.configure(&self.device, &self.surface_config);
+
+            let (path_intermediate_texture, path_intermediate_view) =
+                Self::create_path_intermediate(
+                    &self.device,
+                    self.surface_config.format,
+                    self.surface_config.width,
+                    self.surface_config.height,
+                );
+            self.path_intermediate_texture = path_intermediate_texture;
+            self.path_intermediate_view = path_intermediate_view;
+
+            let (path_msaa_texture, path_msaa_view) = Self::create_msaa_if_needed(
+                &self.device,
+                self.surface_config.format,
+                self.surface_config.width,
+                self.surface_config.height,
+                self.rendering_params.path_sample_count,
+            )
+            .map(|(t, v)| (Some(t), Some(v)))
+            .unwrap_or((None, None));
+            self.path_msaa_texture = path_msaa_texture;
+            self.path_msaa_view = path_msaa_view;
+        }
+    }
+
+    pub fn update_transparency(&mut self, transparent: bool) {
+        let new_alpha_mode = if transparent {
+            self.transparent_alpha_mode
+        } else {
+            self.opaque_alpha_mode
+        };
+
+        if new_alpha_mode != self.surface_config.alpha_mode {
+            self.surface_config.alpha_mode = new_alpha_mode;
+            self.surface.configure(&self.device, &self.surface_config);
+            self.pipelines = Self::create_pipelines(
+                &self.device,
+                &self.bind_group_layouts,
+                self.surface_config.format,
+                self.surface_config.alpha_mode,
+                self.rendering_params.path_sample_count,
+                self.dual_source_blending,
+            );
+        }
+    }
+
+    #[allow(dead_code)]
+    pub fn viewport_size(&self) -> Size<DevicePixels> {
+        Size {
+            width: DevicePixels(self.surface_config.width as i32),
+            height: DevicePixels(self.surface_config.height as i32),
+        }
+    }
+
+    pub fn sprite_atlas(&self) -> &Arc<WgpuAtlas> {
+        &self.atlas
+    }
+
+    pub fn gpu_specs(&self) -> GpuSpecs {
+        GpuSpecs {
+            is_software_emulated: self.adapter_info.device_type == wgpu::DeviceType::Cpu,
+            device_name: self.adapter_info.name.clone(),
+            driver_name: self.adapter_info.driver.clone(),
+            driver_info: self.adapter_info.driver_info.clone(),
+        }
+    }
+
+    pub fn draw(&mut self, scene: &Scene) {
+        self.atlas.before_frame();
+
+        let frame = match self.surface.get_current_texture() {
+            Ok(frame) => frame,
+            Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
+                self.surface.configure(&self.device, &self.surface_config);
+                return;
+            }
+            Err(e) => {
+                log::error!("Failed to acquire surface texture: {e}");
+                return;
+            }
+        };
+        let frame_view = frame
+            .texture
+            .create_view(&wgpu::TextureViewDescriptor::default());
+
+        let gamma_params = GammaParams {
+            gamma_ratios: self.rendering_params.gamma_ratios,
+            grayscale_enhanced_contrast: self.rendering_params.grayscale_enhanced_contrast,
+            subpixel_enhanced_contrast: self.rendering_params.subpixel_enhanced_contrast,
+            _pad: [0.0; 2],
+        };
+
+        let globals = GlobalParams {
+            viewport_size: [
+                self.surface_config.width as f32,
+                self.surface_config.height as f32,
+            ],
+            premultiplied_alpha: if self.surface_config.alpha_mode
+                == wgpu::CompositeAlphaMode::PreMultiplied
+            {
+                1
+            } else {
+                0
+            },
+            pad: 0,
+        };
+
+        let path_globals = GlobalParams {
+            premultiplied_alpha: 0,
+            ..globals
+        };
+
+        self.queue
+            .write_buffer(&self.globals_buffer, 0, bytemuck::bytes_of(&globals));
+        self.queue.write_buffer(
+            &self.globals_buffer,
+            self.path_globals_offset,
+            bytemuck::bytes_of(&path_globals),
+        );
+        self.queue.write_buffer(
+            &self.globals_buffer,
+            self.gamma_offset,
+            bytemuck::bytes_of(&gamma_params),
+        );
+
+        loop {
+            let mut instance_offset: u64 = 0;
+            let mut overflow = false;
+
+            let mut encoder = self
+                .device
+                .create_command_encoder(&wgpu::CommandEncoderDescriptor {
+                    label: Some("main_encoder"),
+                });
+
+            {
+                let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
+                    label: Some("main_pass"),
+                    color_attachments: &[Some(wgpu::RenderPassColorAttachment {
+                        view: &frame_view,
+                        resolve_target: None,
+                        ops: wgpu::Operations {
+                            load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
+                            store: wgpu::StoreOp::Store,
+                        },
+                        depth_slice: None,
+                    })],
+                    depth_stencil_attachment: None,
+                    ..Default::default()
+                });
+
+                for batch in scene.batches() {
+                    let ok = match batch {
+                        PrimitiveBatch::Quads(range) => {
+                            self.draw_quads(&scene.quads[range], &mut instance_offset, &mut pass)
+                        }
+                        PrimitiveBatch::Shadows(range) => self.draw_shadows(
+                            &scene.shadows[range],
+                            &mut instance_offset,
+                            &mut pass,
+                        ),
+                        PrimitiveBatch::Paths(range) => {
+                            let paths = &scene.paths[range];
+                            if paths.is_empty() {
+                                continue;
+                            }
+
+                            drop(pass);
+
+                            let did_draw = self.draw_paths_to_intermediate(
+                                &mut encoder,
+                                paths,
+                                &mut instance_offset,
+                            );
+
+                            pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
+                                label: Some("main_pass_continued"),
+                                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
+                                    view: &frame_view,
+                                    resolve_target: None,
+                                    ops: wgpu::Operations {
+                                        load: wgpu::LoadOp::Load,
+                                        store: wgpu::StoreOp::Store,
+                                    },
+                                    depth_slice: None,
+                                })],
+                                depth_stencil_attachment: None,
+                                ..Default::default()
+                            });
+
+                            if did_draw {
+                                self.draw_paths_from_intermediate(
+                                    paths,
+                                    &mut instance_offset,
+                                    &mut pass,
+                                )
+                            } else {
+                                false
+                            }
+                        }
+                        PrimitiveBatch::Underlines(range) => self.draw_underlines(
+                            &scene.underlines[range],
+                            &mut instance_offset,
+                            &mut pass,
+                        ),
+                        PrimitiveBatch::MonochromeSprites { texture_id, range } => self
+                            .draw_monochrome_sprites(
+                                &scene.monochrome_sprites[range],
+                                texture_id,
+                                &mut instance_offset,
+                                &mut pass,
+                            ),
+                        PrimitiveBatch::SubpixelSprites { texture_id, range } => self
+                            .draw_subpixel_sprites(
+                                &scene.subpixel_sprites[range],
+                                texture_id,
+                                &mut instance_offset,
+                                &mut pass,
+                            ),
+                        PrimitiveBatch::PolychromeSprites { texture_id, range } => self
+                            .draw_polychrome_sprites(
+                                &scene.polychrome_sprites[range],
+                                texture_id,
+                                &mut instance_offset,
+                                &mut pass,
+                            ),
+                        PrimitiveBatch::Surfaces(_surfaces) => {
+                            // Surfaces are macOS-only for video playback
+                            // Not implemented for Linux/wgpu
+                            true
+                        }
+                    };
+                    if !ok {
+                        overflow = true;
+                        break;
+                    }
+                }
+            }
+
+            if overflow {
+                drop(encoder);
+                if self.instance_buffer_capacity >= 256 * 1024 * 1024 {
+                    log::error!(
+                        "instance buffer size grew too large: {}",
+                        self.instance_buffer_capacity
+                    );
+                    frame.present();
+                    return;
+                }
+                self.grow_instance_buffer();
+                continue;
+            }
+
+            self.queue.submit(std::iter::once(encoder.finish()));
+            frame.present();
+            return;
+        }
+    }
+
+    fn draw_quads(
+        &self,
+        quads: &[Quad],
+        instance_offset: &mut u64,
+        pass: &mut wgpu::RenderPass<'_>,
+    ) -> bool {
+        let data = unsafe { Self::instance_bytes(quads) };
+        self.draw_instances(
+            data,
+            quads.len() as u32,
+            &self.pipelines.quads,
+            instance_offset,
+            pass,
+        )
+    }
+
+    fn draw_shadows(
+        &self,
+        shadows: &[Shadow],
+        instance_offset: &mut u64,
+        pass: &mut wgpu::RenderPass<'_>,
+    ) -> bool {
+        let data = unsafe { Self::instance_bytes(shadows) };
+        self.draw_instances(
+            data,
+            shadows.len() as u32,
+            &self.pipelines.shadows,
+            instance_offset,
+            pass,
+        )
+    }
+
+    fn draw_underlines(
+        &self,
+        underlines: &[Underline],
+        instance_offset: &mut u64,
+        pass: &mut wgpu::RenderPass<'_>,
+    ) -> bool {
+        let data = unsafe { Self::instance_bytes(underlines) };
+        self.draw_instances(
+            data,
+            underlines.len() as u32,
+            &self.pipelines.underlines,
+            instance_offset,
+            pass,
+        )
+    }
+
+    fn draw_monochrome_sprites(
+        &self,
+        sprites: &[MonochromeSprite],
+        texture_id: AtlasTextureId,
+        instance_offset: &mut u64,
+        pass: &mut wgpu::RenderPass<'_>,
+    ) -> bool {
+        let tex_info = self.atlas.get_texture_info(texture_id);
+        let data = unsafe { Self::instance_bytes(sprites) };
+        self.draw_instances_with_texture(
+            data,
+            sprites.len() as u32,
+            &tex_info.view,
+            &self.pipelines.mono_sprites,
+            instance_offset,
+            pass,
+        )
+    }
+
+    fn draw_subpixel_sprites(
+        &self,
+        sprites: &[SubpixelSprite],
+        texture_id: AtlasTextureId,
+        instance_offset: &mut u64,
+        pass: &mut wgpu::RenderPass<'_>,
+    ) -> bool {
+        let tex_info = self.atlas.get_texture_info(texture_id);
+        let data = unsafe { Self::instance_bytes(sprites) };
+        let pipeline = self
+            .pipelines
+            .subpixel_sprites
+            .as_ref()
+            .unwrap_or(&self.pipelines.mono_sprites);
+        self.draw_instances_with_texture(
+            data,
+            sprites.len() as u32,
+            &tex_info.view,
+            pipeline,
+            instance_offset,
+            pass,
+        )
+    }
+
+    fn draw_polychrome_sprites(
+        &self,
+        sprites: &[PolychromeSprite],
+        texture_id: AtlasTextureId,
+        instance_offset: &mut u64,
+        pass: &mut wgpu::RenderPass<'_>,
+    ) -> bool {
+        let tex_info = self.atlas.get_texture_info(texture_id);
+        let data = unsafe { Self::instance_bytes(sprites) };
+        self.draw_instances_with_texture(
+            data,
+            sprites.len() as u32,
+            &tex_info.view,
+            &self.pipelines.poly_sprites,
+            instance_offset,
+            pass,
+        )
+    }
+
+    fn draw_instances(
+        &self,
+        data: &[u8],
+        instance_count: u32,
+        pipeline: &wgpu::RenderPipeline,
+        instance_offset: &mut u64,
+        pass: &mut wgpu::RenderPass<'_>,
+    ) -> bool {
+        if instance_count == 0 {
+            return true;
+        }
+        let Some((offset, size)) = self.write_to_instance_buffer(instance_offset, data) else {
+            return false;
+        };
+        let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
+            label: None,
+            layout: &self.bind_group_layouts.instances,
+            entries: &[wgpu::BindGroupEntry {
+                binding: 0,
+                resource: self.instance_binding(offset, size),
+            }],
+        });
+        pass.set_pipeline(pipeline);
+        pass.set_bind_group(0, &self.globals_bind_group, &[]);
+        pass.set_bind_group(1, &bind_group, &[]);
+        pass.draw(0..4, 0..instance_count);
+        true
+    }
+
+    fn draw_instances_with_texture(
+        &self,
+        data: &[u8],
+        instance_count: u32,
+        texture_view: &wgpu::TextureView,
+        pipeline: &wgpu::RenderPipeline,
+        instance_offset: &mut u64,
+        pass: &mut wgpu::RenderPass<'_>,
+    ) -> bool {
+        if instance_count == 0 {
+            return true;
+        }
+        let Some((offset, size)) = self.write_to_instance_buffer(instance_offset, data) else {
+            return false;
+        };
+        let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
+            label: None,
+            layout: &self.bind_group_layouts.instances_with_texture,
+            entries: &[
+                wgpu::BindGroupEntry {
+                    binding: 0,
+                    resource: self.instance_binding(offset, size),
+                },
+                wgpu::BindGroupEntry {
+                    binding: 1,
+                    resource: wgpu::BindingResource::TextureView(texture_view),
+                },
+                wgpu::BindGroupEntry {
+                    binding: 2,
+                    resource: wgpu::BindingResource::Sampler(&self.atlas_sampler),
+                },
+            ],
+        });
+        pass.set_pipeline(pipeline);
+        pass.set_bind_group(0, &self.globals_bind_group, &[]);
+        pass.set_bind_group(1, &bind_group, &[]);
+        pass.draw(0..4, 0..instance_count);
+        true
+    }
+
+    unsafe fn instance_bytes<T>(instances: &[T]) -> &[u8] {
+        unsafe {
+            std::slice::from_raw_parts(
+                instances.as_ptr() as *const u8,
+                std::mem::size_of_val(instances),
+            )
+        }
+    }
+
+    fn draw_paths_from_intermediate(
+        &self,
+        paths: &[Path<ScaledPixels>],
+        instance_offset: &mut u64,
+        pass: &mut wgpu::RenderPass<'_>,
+    ) -> bool {
+        let first_path = &paths[0];
+        let sprites: Vec<PathSprite> = if paths.last().map(|p| &p.order) == Some(&first_path.order)
+        {
+            paths
+                .iter()
+                .map(|p| PathSprite {
+                    bounds: p.clipped_bounds(),
+                })
+                .collect()
+        } else {
+            let mut bounds = first_path.clipped_bounds();
+            for path in paths.iter().skip(1) {
+                bounds = bounds.union(&path.clipped_bounds());
+            }
+            vec![PathSprite { bounds }]
+        };
+
+        let sprite_data = unsafe { Self::instance_bytes(&sprites) };
+        self.draw_instances_with_texture(
+            sprite_data,
+            sprites.len() as u32,
+            &self.path_intermediate_view,
+            &self.pipelines.paths,
+            instance_offset,
+            pass,
+        )
+    }
+
+    fn draw_paths_to_intermediate(
+        &self,
+        encoder: &mut wgpu::CommandEncoder,
+        paths: &[Path<ScaledPixels>],
+        instance_offset: &mut u64,
+    ) -> bool {
+        let mut vertices = Vec::new();
+        for path in paths {
+            let bounds = path.clipped_bounds();
+            vertices.extend(path.vertices.iter().map(|v| PathRasterizationVertex {
+                xy_position: v.xy_position,
+                st_position: v.st_position,
+                color: path.color,
+                bounds,
+            }));
+        }
+
+        if vertices.is_empty() {
+            return true;
+        }
+
+        let vertex_data = unsafe { Self::instance_bytes(&vertices) };
+        let Some((vertex_offset, vertex_size)) =
+            self.write_to_instance_buffer(instance_offset, vertex_data)
+        else {
+            return false;
+        };
+
+        let data_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
+            label: Some("path_rasterization_bind_group"),
+            layout: &self.bind_group_layouts.instances,
+            entries: &[wgpu::BindGroupEntry {
+                binding: 0,
+                resource: self.instance_binding(vertex_offset, vertex_size),
+            }],
+        });
+
+        let (target_view, resolve_target) = if let Some(ref msaa_view) = self.path_msaa_view {
+            (msaa_view, Some(&self.path_intermediate_view))
+        } else {
+            (&self.path_intermediate_view, None)
+        };
+
+        {
+            let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
+                label: Some("path_rasterization_pass"),
+                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
+                    view: target_view,
+                    resolve_target,
+                    ops: wgpu::Operations {
+                        load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
+                        store: wgpu::StoreOp::Store,
+                    },
+                    depth_slice: None,
+                })],
+                depth_stencil_attachment: None,
+                ..Default::default()
+            });
+
+            pass.set_pipeline(&self.pipelines.path_rasterization);
+            pass.set_bind_group(0, &self.path_globals_bind_group, &[]);
+            pass.set_bind_group(1, &data_bind_group, &[]);
+            pass.draw(0..vertices.len() as u32, 0..1);
+        }
+
+        true
+    }
+
+    fn grow_instance_buffer(&mut self) {
+        let new_capacity = self.instance_buffer_capacity * 2;
+        log::info!("increased instance buffer size to {}", new_capacity);
+        self.instance_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
+            label: Some("instance_buffer"),
+            size: new_capacity,
+            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
+            mapped_at_creation: false,
+        });
+        self.instance_buffer_capacity = new_capacity;
+    }
+
+    fn write_to_instance_buffer(
+        &self,
+        instance_offset: &mut u64,
+        data: &[u8],
+    ) -> Option<(u64, NonZeroU64)> {
+        let offset = (*instance_offset).next_multiple_of(self.storage_buffer_alignment);
+        let size = (data.len() as u64).max(16);
+        if offset + size > self.instance_buffer_capacity {
+            return None;
+        }
+        self.queue.write_buffer(&self.instance_buffer, offset, data);
+        *instance_offset = offset + size;
+        Some((offset, NonZeroU64::new(size).expect("size is at least 16")))
+    }
+
+    fn instance_binding(&self, offset: u64, size: NonZeroU64) -> wgpu::BindingResource<'_> {
+        wgpu::BindingResource::Buffer(wgpu::BufferBinding {
+            buffer: &self.instance_buffer,
+            offset,
+            size: Some(size),
+        })
+    }
+
+    pub fn destroy(&mut self) {
+        // wgpu resources are automatically cleaned up when dropped
+    }
+}
+
+struct RenderingParameters {
+    path_sample_count: u32,
+    gamma_ratios: [f32; 4],
+    grayscale_enhanced_contrast: f32,
+    subpixel_enhanced_contrast: f32,
+}
+
+impl RenderingParameters {
+    fn new(adapter: &wgpu::Adapter, surface_format: wgpu::TextureFormat) -> Self {
+        use std::env;
+
+        let format_features = adapter.get_texture_format_features(surface_format);
+        let path_sample_count = [4, 2, 1]
+            .into_iter()
+            .find(|&n| format_features.flags.sample_count_supported(n))
+            .unwrap_or(1);
+
+        let gamma = env::var("ZED_FONTS_GAMMA")
+            .ok()
+            .and_then(|v| v.parse().ok())
+            .unwrap_or(1.8_f32)
+            .clamp(1.0, 2.2);
+        let gamma_ratios = get_gamma_correction_ratios(gamma);
+
+        let grayscale_enhanced_contrast = env::var("ZED_FONTS_GRAYSCALE_ENHANCED_CONTRAST")
+            .ok()
+            .and_then(|v| v.parse().ok())
+            .unwrap_or(1.0_f32)
+            .max(0.0);
+
+        let subpixel_enhanced_contrast = env::var("ZED_FONTS_SUBPIXEL_ENHANCED_CONTRAST")
+            .ok()
+            .and_then(|v| v.parse().ok())
+            .unwrap_or(0.5_f32)
+            .max(0.0);
+
+        Self {
+            path_sample_count,
+            gamma_ratios,
+            grayscale_enhanced_contrast,
+            subpixel_enhanced_contrast,
+        }
+    }
+}

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.

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

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.