Blade window transparency (#10973)

Dzmitry Malyshau and Jan Solanti created

Release Notes:

- N/A

Following up to #10880
TODO:
- [x] create window as transparent
  - [x] X11
  - [x] Wayland
  - [ ] Windows
  - [x] MacOS (when used with Blade)  
- [x] enable GPU surface transparency
- [x] adjust the pipeline blend modes
- [x] adjust shader outputs


![transparency2](https://github.com/zed-industries/zed/assets/107301/d554a41b-5d3f-4420-a857-c64c1747c2d5)

Blurred results from @jansol (on Wayland), who contributed to this work:


![zed-blur](https://github.com/zed-industries/zed/assets/107301/a6822171-2dcf-4109-be55-b75557c586de)

---------

Co-authored-by: Jan Solanti <jhs@psonet.com>

Change summary

Cargo.lock                                       |  18 +
Cargo.toml                                       |   4 
crates/gpui/Cargo.toml                           |   1 
crates/gpui/src/platform/blade/blade_renderer.rs | 130 ++++++-----
crates/gpui/src/platform/blade/shaders.wgsl      |  23 +
crates/gpui/src/platform/linux/wayland/client.rs |   8 
crates/gpui/src/platform/linux/wayland/window.rs |  64 +++++
crates/gpui/src/platform/linux/x11/window.rs     | 189 ++++++++++++++---
crates/gpui/src/platform/mac/metal_renderer.rs   |   5 
crates/gpui/src/platform/mac/window.rs           |   6 
crates/gpui/src/platform/windows/window.rs       |  24 +
11 files changed, 344 insertions(+), 128 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -1480,7 +1480,7 @@ dependencies = [
 [[package]]
 name = "blade-graphics"
 version = "0.4.0"
-source = "git+https://github.com/kvark/blade?rev=e82eec97691c3acdb43494484be60d661edfebf3#e82eec97691c3acdb43494484be60d661edfebf3"
+source = "git+https://github.com/kvark/blade?rev=f5766863de9dcc092e90fdbbc5e0007a99e7f9bf#f5766863de9dcc092e90fdbbc5e0007a99e7f9bf"
 dependencies = [
  "ash",
  "ash-window",
@@ -1510,7 +1510,7 @@ dependencies = [
 [[package]]
 name = "blade-macros"
 version = "0.2.1"
-source = "git+https://github.com/kvark/blade?rev=e82eec97691c3acdb43494484be60d661edfebf3#e82eec97691c3acdb43494484be60d661edfebf3"
+source = "git+https://github.com/kvark/blade?rev=f5766863de9dcc092e90fdbbc5e0007a99e7f9bf#f5766863de9dcc092e90fdbbc5e0007a99e7f9bf"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -4604,6 +4604,7 @@ dependencies = [
  "wayland-client",
  "wayland-cursor",
  "wayland-protocols",
+ "wayland-protocols-plasma",
  "windows 0.53.0",
  "x11rb",
  "xkbcommon",
@@ -11780,6 +11781,19 @@ dependencies = [
  "wayland-scanner",
 ]
 
+[[package]]
+name = "wayland-protocols-plasma"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479"
+dependencies = [
+ "bitflags 2.4.2",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "wayland-scanner",
+]
+
 [[package]]
 name = "wayland-protocols-wlr"
 version = "0.2.0"

Cargo.toml 🔗

@@ -254,8 +254,8 @@ async-recursion = "1.0.0"
 async-tar = "0.4.2"
 async-trait = "0.1"
 bitflags = "2.4.2"
-blade-graphics = { git = "https://github.com/kvark/blade", rev = "e82eec97691c3acdb43494484be60d661edfebf3" }
-blade-macros = { git = "https://github.com/kvark/blade", rev = "e82eec97691c3acdb43494484be60d661edfebf3" }
+blade-graphics = { git = "https://github.com/kvark/blade", rev = "f5766863de9dcc092e90fdbbc5e0007a99e7f9bf" }
+blade-macros = { git = "https://github.com/kvark/blade", rev = "f5766863de9dcc092e90fdbbc5e0007a99e7f9bf" }
 cap-std = "3.0"
 chrono = { version = "0.4", features = ["serde"] }
 clap = { version = "4.4", features = ["derive"] }

crates/gpui/Cargo.toml 🔗

@@ -111,6 +111,7 @@ wayland-protocols = { version = "0.31.2", features = [
     "staging",
     "unstable",
 ] }
+wayland-protocols-plasma = { version = "0.2.0", features = ["client"] }
 oo7 = "0.3.0"
 open = "5.1.2"
 filedescriptor = "0.8.2"

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

@@ -28,6 +28,7 @@ pub unsafe fn new_renderer(
     _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 {
@@ -64,10 +65,13 @@ pub unsafe fn new_renderer(
 
     BladeRenderer::new(
         gpu,
-        gpu::Extent {
-            width: bounds.width as u32,
-            height: bounds.height as u32,
-            depth: 1,
+        BladeSurfaceConfig {
+            size: gpu::Extent {
+                width: bounds.width as u32,
+                height: bounds.height as u32,
+                depth: 1,
+            },
+            transparent,
         },
     )
 }
@@ -76,7 +80,8 @@ pub unsafe fn new_renderer(
 #[derive(Clone, Copy, Pod, Zeroable)]
 struct GlobalParams {
     viewport_size: [f32; 2],
-    pad: [u32; 2],
+    premultiplied_alpha: u32,
+    pad: u32,
 }
 
 //Note: we can't use `Bounds` directly here because
@@ -184,6 +189,10 @@ impl BladePipelines {
     fn new(gpu: &gpu::Context, surface_info: gpu::SurfaceInfo) -> 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"),
         });
@@ -200,6 +209,18 @@ impl BladePipelines {
         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",
@@ -212,11 +233,7 @@ impl BladePipelines {
                 },
                 depth_stencil: None,
                 fragment: shader.at("fs_quad"),
-                color_targets: &[gpu::ColorTargetState {
-                    format: surface_info.format,
-                    blend: Some(gpu::BlendState::ALPHA_BLENDING),
-                    write_mask: gpu::ColorWrites::default(),
-                }],
+                color_targets,
             }),
             shadows: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
                 name: "shadows",
@@ -229,11 +246,7 @@ impl BladePipelines {
                 },
                 depth_stencil: None,
                 fragment: shader.at("fs_shadow"),
-                color_targets: &[gpu::ColorTargetState {
-                    format: surface_info.format,
-                    blend: Some(gpu::BlendState::ALPHA_BLENDING),
-                    write_mask: gpu::ColorWrites::default(),
-                }],
+                color_targets,
             }),
             path_rasterization: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
                 name: "path_rasterization",
@@ -263,11 +276,7 @@ impl BladePipelines {
                 },
                 depth_stencil: None,
                 fragment: shader.at("fs_path"),
-                color_targets: &[gpu::ColorTargetState {
-                    format: surface_info.format,
-                    blend: Some(gpu::BlendState::ALPHA_BLENDING),
-                    write_mask: gpu::ColorWrites::default(),
-                }],
+                color_targets,
             }),
             underlines: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
                 name: "underlines",
@@ -280,11 +289,7 @@ impl BladePipelines {
                 },
                 depth_stencil: None,
                 fragment: shader.at("fs_underline"),
-                color_targets: &[gpu::ColorTargetState {
-                    format: surface_info.format,
-                    blend: Some(gpu::BlendState::ALPHA_BLENDING),
-                    write_mask: gpu::ColorWrites::default(),
-                }],
+                color_targets,
             }),
             mono_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
                 name: "mono-sprites",
@@ -297,11 +302,7 @@ impl BladePipelines {
                 },
                 depth_stencil: None,
                 fragment: shader.at("fs_mono_sprite"),
-                color_targets: &[gpu::ColorTargetState {
-                    format: surface_info.format,
-                    blend: Some(gpu::BlendState::ALPHA_BLENDING),
-                    write_mask: gpu::ColorWrites::default(),
-                }],
+                color_targets,
             }),
             poly_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
                 name: "poly-sprites",
@@ -314,11 +315,7 @@ impl BladePipelines {
                 },
                 depth_stencil: None,
                 fragment: shader.at("fs_poly_sprite"),
-                color_targets: &[gpu::ColorTargetState {
-                    format: surface_info.format,
-                    blend: Some(gpu::BlendState::ALPHA_BLENDING),
-                    write_mask: gpu::ColorWrites::default(),
-                }],
+                color_targets,
             }),
             surfaces: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
                 name: "surfaces",
@@ -331,23 +328,25 @@ impl BladePipelines {
                 },
                 depth_stencil: None,
                 fragment: shader.at("fs_surface"),
-                color_targets: &[gpu::ColorTargetState {
-                    format: surface_info.format,
-                    blend: Some(gpu::BlendState::ALPHA_BLENDING),
-                    write_mask: gpu::ColorWrites::default(),
-                }],
+                color_targets,
             }),
         }
     }
 }
 
+pub struct BladeSurfaceConfig {
+    pub size: gpu::Extent,
+    pub transparent: bool,
+}
+
 pub struct BladeRenderer {
     gpu: Arc<gpu::Context>,
+    surface_config: gpu::SurfaceConfig,
+    alpha_mode: gpu::AlphaMode,
     command_encoder: gpu::CommandEncoder,
     last_sync_point: Option<gpu::SyncPoint>,
     pipelines: BladePipelines,
     instance_belt: BladeBelt,
-    viewport_size: gpu::Extent,
     path_tiles: HashMap<PathId, AtlasTile>,
     atlas: Arc<BladeAtlas>,
     atlas_sampler: gpu::Sampler,
@@ -356,21 +355,19 @@ pub struct BladeRenderer {
 }
 
 impl BladeRenderer {
-    fn make_surface_config(size: gpu::Extent) -> gpu::SurfaceConfig {
-        gpu::SurfaceConfig {
-            size,
+    pub fn new(gpu: Arc<gpu::Context>, config: BladeSurfaceConfig) -> Self {
+        let surface_config = gpu::SurfaceConfig {
+            size: config.size,
             usage: gpu::TextureUsage::TARGET,
             display_sync: gpu::DisplaySync::Recent,
             //Note: this matches the original logic of the Metal backend,
             // but ultimaterly we need to switch to `Linear`.
             color_space: gpu::ColorSpace::Srgb,
             allow_exclusive_full_screen: false,
-            transparent: false,
-        }
-    }
+            transparent: config.transparent,
+        };
+        let surface_info = gpu.resize(surface_config);
 
-    pub fn new(gpu: Arc<gpu::Context>, size: gpu::Extent) -> Self {
-        let surface_info = gpu.resize(Self::make_surface_config(size));
         let command_encoder = gpu.create_command_encoder(gpu::CommandEncoderDesc {
             name: "main",
             buffer_count: 2,
@@ -397,11 +394,12 @@ impl BladeRenderer {
 
         Self {
             gpu,
+            surface_config,
+            alpha_mode: surface_info.alpha,
             command_encoder,
             last_sync_point: None,
             pipelines,
             instance_belt,
-            viewport_size: size,
             path_tiles: HashMap::default(),
             atlas,
             atlas_sampler,
@@ -425,15 +423,26 @@ impl BladeRenderer {
             depth: 1,
         };
 
-        if gpu_size != self.viewport_size() {
+        if gpu_size != self.surface_config.size {
             self.wait_for_gpu();
-            self.gpu.resize(Self::make_surface_config(gpu_size));
-            self.viewport_size = gpu_size;
+            self.surface_config.size = gpu_size;
+            self.gpu.resize(self.surface_config);
         }
     }
 
+    pub fn update_transparency(&mut self, transparent: bool) {
+        if transparent != self.surface_config.transparent {
+            self.wait_for_gpu();
+            self.surface_config.transparent = transparent;
+            let surface_info = self.gpu.resize(self.surface_config);
+            self.pipelines = BladePipelines::new(&self.gpu, surface_info);
+            self.alpha_mode = surface_info.alpha;
+        }
+    }
+
+    #[cfg_attr(target_os = "macos", allow(dead_code))]
     pub fn viewport_size(&self) -> gpu::Extent {
-        self.viewport_size
+        self.surface_config.size
     }
 
     pub fn sprite_atlas(&self) -> &Arc<BladeAtlas> {
@@ -481,7 +490,8 @@ impl BladeRenderer {
             let tex_info = self.atlas.get_texture_info(texture_id);
             let globals = GlobalParams {
                 viewport_size: [tex_info.size.width as f32, tex_info.size.height as f32],
-                pad: [0; 2],
+                premultiplied_alpha: 0,
+                pad: 0,
             };
 
             let vertex_buf = unsafe { self.instance_belt.alloc_data(&vertices, &self.gpu) };
@@ -526,10 +536,14 @@ impl BladeRenderer {
 
         let globals = GlobalParams {
             viewport_size: [
-                self.viewport_size.width as f32,
-                self.viewport_size.height as f32,
+                self.surface_config.size.width as f32,
+                self.surface_config.size.height as f32,
             ],
-            pad: [0; 2],
+            premultiplied_alpha: match self.alpha_mode {
+                gpu::AlphaMode::Ignored | gpu::AlphaMode::PostMultiplied => 0,
+                gpu::AlphaMode::PreMultiplied => 1,
+            },
+            pad: 0,
         };
 
         if let mut pass = self.command_encoder.render(gpu::RenderTargetSet {

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

@@ -1,6 +1,7 @@
 struct GlobalParams {
     viewport_size: vec2<f32>,
-    pad: vec2<u32>,
+    premultiplied_alpha: u32,
+    pad: u32,
 }
 
 var<uniform> globals: GlobalParams;
@@ -176,6 +177,13 @@ fn quad_sdf(point: vec2<f32>, bounds: Bounds, corner_radii: Corners) -> f32 {
         corner_radius;
 }
 
+// Abstract away the final color transformation based on the
+// target alpha compositing mode.
+fn blend_color(color: vec4<f32>, alpha_factor: f32) -> vec4<f32> {
+    let alpha = color.a * alpha_factor;
+    return select(vec4<f32>(color.rgb, alpha), vec4<f32>(color.rgb, 1.0) * alpha, globals.premultiplied_alpha != 0u);
+}
+
 // --- quads --- //
 
 struct Quad {
@@ -266,7 +274,7 @@ fn fs_quad(input: QuadVarying) -> @location(0) vec4<f32> {
                     saturate(0.5 - inset_distance));
     }
 
-    return color * vec4<f32>(1.0, 1.0, 1.0, saturate(0.5 - distance));
+    return blend_color(color, saturate(0.5 - distance));
 }
 
 // --- shadows --- //
@@ -339,7 +347,7 @@ fn fs_shadow(input: ShadowVarying) -> @location(0) vec4<f32> {
         y += step;
     }
 
-    return input.color * vec4<f32>(1.0, 1.0, 1.0, alpha);
+    return blend_color(input.color, alpha);
 }
 
 // --- path rasterization --- //
@@ -415,7 +423,7 @@ fn vs_path(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) insta
 fn fs_path(input: PathVarying) -> @location(0) vec4<f32> {
     let sample = textureSample(t_sprite, s_sprite, input.tile_position).r;
     let mask = 1.0 - abs(1.0 - sample % 2.0);
-    return input.color * mask;
+    return blend_color(input.color, mask);
 }
 
 // --- underlines --- //
@@ -476,7 +484,7 @@ fn fs_underline(input: UnderlineVarying) -> @location(0) vec4<f32> {
     let distance_from_top_border = distance_in_pixels - half_thickness;
     let distance_from_bottom_border = distance_in_pixels + half_thickness;
     let alpha = saturate(0.5 - max(-distance_from_bottom_border, distance_from_top_border));
-    return input.color * vec4<f32>(1.0, 1.0, 1.0, alpha);
+    return blend_color(input.color, alpha);
 }
 
 // --- monochrome sprites --- //
@@ -520,7 +528,7 @@ fn fs_mono_sprite(input: MonoSpriteVarying) -> @location(0) vec4<f32> {
     if (any(input.clip_distances < vec4<f32>(0.0))) {
         return vec4<f32>(0.0);
     }
-    return input.color * vec4<f32>(1.0, 1.0, 1.0, sample);
+    return blend_color(input.color, sample);
 }
 
 // --- polychrome sprites --- //
@@ -571,8 +579,7 @@ fn fs_poly_sprite(input: PolySpriteVarying) -> @location(0) vec4<f32> {
         let grayscale = dot(color.rgb, GRAYSCALE_FACTORS);
         color = vec4<f32>(vec3<f32>(grayscale), sample.a);
     }
-    color.a *= saturate(0.5 - distance);
-    return color;
+    return blend_color(color, saturate(0.5 - distance));
 }
 
 // --- surfaces --- //

crates/gpui/src/platform/linux/wayland/client.rs 🔗

@@ -24,7 +24,7 @@ use wayland_client::protocol::wl_callback::{self, WlCallback};
 use wayland_client::protocol::wl_data_device_manager::DndAction;
 use wayland_client::protocol::wl_pointer::{AxisRelativeDirection, AxisSource};
 use wayland_client::protocol::{
-    wl_data_device, wl_data_device_manager, wl_data_offer, wl_data_source, wl_output,
+    wl_data_device, wl_data_device_manager, wl_data_offer, wl_data_source, wl_output, wl_region,
 };
 use wayland_client::{
     delegate_noop,
@@ -47,6 +47,7 @@ use wayland_protocols::xdg::decoration::zv1::client::{
     zxdg_decoration_manager_v1, zxdg_toplevel_decoration_v1,
 };
 use wayland_protocols::xdg::shell::client::{xdg_surface, xdg_toplevel, xdg_wm_base};
+use wayland_protocols_plasma::blur::client::{org_kde_kwin_blur, org_kde_kwin_blur_manager};
 use xkbcommon::xkb::ffi::XKB_KEYMAP_FORMAT_TEXT_V1;
 use xkbcommon::xkb::{self, Keycode, KEYMAP_COMPILE_NO_FLAGS};
 
@@ -82,6 +83,7 @@ pub struct Globals {
     pub fractional_scale_manager:
         Option<wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1>,
     pub decoration_manager: Option<zxdg_decoration_manager_v1::ZxdgDecorationManagerV1>,
+    pub blur_manager: Option<org_kde_kwin_blur_manager::OrgKdeKwinBlurManager>,
     pub executor: ForegroundExecutor,
 }
 
@@ -114,6 +116,7 @@ impl Globals {
             viewporter: globals.bind(&qh, 1..=1, ()).ok(),
             fractional_scale_manager: globals.bind(&qh, 1..=1, ()).ok(),
             decoration_manager: globals.bind(&qh, 1..=1, ()).ok(),
+            blur_manager: globals.bind(&qh, 1..=1, ()).ok(),
             executor,
             qh,
         }
@@ -557,8 +560,11 @@ delegate_noop!(WaylandClientStatePtr: ignore wl_data_device_manager::WlDataDevic
 delegate_noop!(WaylandClientStatePtr: ignore wl_shm::WlShm);
 delegate_noop!(WaylandClientStatePtr: ignore wl_shm_pool::WlShmPool);
 delegate_noop!(WaylandClientStatePtr: ignore wl_buffer::WlBuffer);
+delegate_noop!(WaylandClientStatePtr: ignore wl_region::WlRegion);
 delegate_noop!(WaylandClientStatePtr: ignore wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1);
 delegate_noop!(WaylandClientStatePtr: ignore zxdg_decoration_manager_v1::ZxdgDecorationManagerV1);
+delegate_noop!(WaylandClientStatePtr: ignore org_kde_kwin_blur_manager::OrgKdeKwinBlurManager);
+delegate_noop!(WaylandClientStatePtr: ignore org_kde_kwin_blur::OrgKdeKwinBlur);
 delegate_noop!(WaylandClientStatePtr: ignore wp_viewporter::WpViewporter);
 delegate_noop!(WaylandClientStatePtr: ignore wp_viewport::WpViewport);
 

crates/gpui/src/platform/linux/wayland/window.rs 🔗

@@ -11,6 +11,7 @@ use collections::{HashMap, HashSet};
 use futures::channel::oneshot::Receiver;
 use raw_window_handle as rwh;
 use wayland_backend::client::ObjectId;
+use wayland_client::protocol::wl_region::WlRegion;
 use wayland_client::WEnum;
 use wayland_client::{protocol::wl_surface, Proxy};
 use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1;
@@ -18,8 +19,9 @@ use wayland_protocols::wp::viewporter::client::wp_viewport;
 use wayland_protocols::xdg::decoration::zv1::client::zxdg_toplevel_decoration_v1;
 use wayland_protocols::xdg::shell::client::xdg_surface;
 use wayland_protocols::xdg::shell::client::xdg_toplevel::{self, WmCapabilities};
+use wayland_protocols_plasma::blur::client::{org_kde_kwin_blur, org_kde_kwin_blur_manager};
 
-use crate::platform::blade::BladeRenderer;
+use crate::platform::blade::{BladeRenderer, BladeSurfaceConfig};
 use crate::platform::linux::wayland::display::WaylandDisplay;
 use crate::platform::{PlatformAtlas, PlatformInputHandler, PlatformWindow};
 use crate::scene::Scene;
@@ -67,6 +69,7 @@ pub struct WaylandWindowState {
     acknowledged_first_configure: bool,
     pub surface: wl_surface::WlSurface,
     decoration: Option<zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1>,
+    blur: Option<org_kde_kwin_blur::OrgKdeKwinBlur>,
     toplevel: xdg_toplevel::XdgToplevel,
     viewport: Option<wp_viewport::WpViewport>,
     outputs: HashSet<ObjectId>,
@@ -124,10 +127,13 @@ impl WaylandWindowState {
             }
             .unwrap(),
         );
-        let extent = gpu::Extent {
-            width: bounds.size.width,
-            height: bounds.size.height,
-            depth: 1,
+        let config = BladeSurfaceConfig {
+            size: gpu::Extent {
+                width: bounds.size.width,
+                height: bounds.size.height,
+                depth: 1,
+            },
+            transparent: options.window_background != WindowBackgroundAppearance::Opaque,
         };
 
         Self {
@@ -135,13 +141,12 @@ impl WaylandWindowState {
             acknowledged_first_configure: false,
             surface,
             decoration,
+            blur: None,
             toplevel,
             viewport,
             globals,
-
             outputs: HashSet::default(),
-
-            renderer: BladeRenderer::new(gpu, extent),
+            renderer: BladeRenderer::new(gpu, config),
             bounds,
             scale: 1.0,
             input_handler: None,
@@ -166,6 +171,9 @@ impl Drop for WaylandWindow {
         if let Some(decoration) = &state.decoration {
             decoration.destroy();
         }
+        if let Some(blur) = &state.blur {
+            blur.release();
+        }
         state.toplevel.destroy();
         if let Some(viewport) = &state.viewport {
             viewport.destroy();
@@ -615,8 +623,44 @@ impl PlatformWindow for WaylandWindow {
         self.borrow_mut().toplevel.set_app_id(app_id.to_owned());
     }
 
-    fn set_background_appearance(&mut self, _background_appearance: WindowBackgroundAppearance) {
-        // todo(linux)
+    fn set_background_appearance(&mut self, background_appearance: WindowBackgroundAppearance) {
+        let opaque = background_appearance == WindowBackgroundAppearance::Opaque;
+        let mut state = self.borrow_mut();
+        state.renderer.update_transparency(!opaque);
+
+        let region = state
+            .globals
+            .compositor
+            .create_region(&state.globals.qh, ());
+        region.add(0, 0, i32::MAX, i32::MAX);
+
+        if opaque {
+            // Promise the compositor that this region of the window surface
+            // contains no transparent pixels. This allows the compositor to
+            // do skip whatever is behind the surface for better performance.
+            state.surface.set_opaque_region(Some(&region));
+        } else {
+            state.surface.set_opaque_region(None);
+        }
+
+        if let Some(ref blur_manager) = state.globals.blur_manager {
+            if (background_appearance == WindowBackgroundAppearance::Blurred) {
+                if (state.blur.is_none()) {
+                    let blur = blur_manager.create(&state.surface, &state.globals.qh, ());
+                    blur.set_region(Some(&region));
+                    state.blur = Some(blur);
+                }
+                state.blur.as_ref().unwrap().commit();
+            } else {
+                // It probably doesn't hurt to clear the blur for opaque windows
+                blur_manager.unset(&state.surface);
+                if let Some(b) = state.blur.take() {
+                    b.release()
+                }
+            }
+        }
+
+        region.destroy();
     }
 
     fn set_edited(&mut self, edited: bool) {

crates/gpui/src/platform/linux/x11/window.rs 🔗

@@ -2,20 +2,22 @@
 #![allow(unused)]
 
 use crate::{
-    platform::blade::BladeRenderer, size, Bounds, DevicePixels, ForegroundExecutor, Modifiers,
-    Pixels, Platform, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler,
-    PlatformWindow, Point, PromptLevel, Scene, Size, WindowAppearance, WindowBackgroundAppearance,
-    WindowOptions, WindowParams, X11Client, X11ClientState, X11ClientStatePtr,
+    platform::blade::{BladeRenderer, BladeSurfaceConfig},
+    size, Bounds, DevicePixels, ForegroundExecutor, Modifiers, Pixels, Platform, PlatformAtlas,
+    PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point, PromptLevel,
+    Scene, Size, WindowAppearance, WindowBackgroundAppearance, WindowOptions, WindowParams,
+    X11Client, X11ClientState, X11ClientStatePtr,
 };
 use blade_graphics as gpu;
 use parking_lot::Mutex;
 use raw_window_handle as rwh;
 use util::ResultExt;
 use x11rb::{
-    connection::Connection,
+    connection::{Connection as _, RequestConnection as _},
     protocol::{
+        render::{self, ConnectionExt as _},
         xinput,
-        xproto::{self, ConnectionExt as _, CreateWindowAux},
+        xproto::{self, ConnectionExt as _},
     },
     resource_manager::Database,
     wrapper::ConnectionExt,
@@ -24,6 +26,7 @@ use x11rb::{
 
 use std::{
     cell::{Ref, RefCell, RefMut},
+    collections::HashMap,
     ffi::c_void,
     iter::Zip,
     mem,
@@ -61,6 +64,76 @@ fn query_render_extent(xcb_connection: &XCBConnection, x_window: xproto::Window)
     }
 }
 
+#[derive(Debug)]
+struct Visual {
+    id: xproto::Visualid,
+    colormap: u32,
+    depth: u8,
+}
+
+struct VisualSet {
+    inherit: Visual,
+    opaque: Option<Visual>,
+    transparent: Option<Visual>,
+    root: u32,
+    black_pixel: u32,
+}
+
+fn find_visuals(xcb_connection: &XCBConnection, screen_index: usize) -> VisualSet {
+    let screen = &xcb_connection.setup().roots[screen_index];
+    let mut set = VisualSet {
+        inherit: Visual {
+            id: screen.root_visual,
+            colormap: screen.default_colormap,
+            depth: screen.root_depth,
+        },
+        opaque: None,
+        transparent: None,
+        root: screen.root,
+        black_pixel: screen.black_pixel,
+    };
+
+    for depth_info in screen.allowed_depths.iter() {
+        for visual_type in depth_info.visuals.iter() {
+            let visual = Visual {
+                id: visual_type.visual_id,
+                colormap: 0,
+                depth: depth_info.depth,
+            };
+            log::debug!("Visual id: {}, class: {:?}, depth: {}, bits_per_value: {}, masks: 0x{:x} 0x{:x} 0x{:x}",
+                visual_type.visual_id,
+                visual_type.class,
+                depth_info.depth,
+                visual_type.bits_per_rgb_value,
+                visual_type.red_mask, visual_type.green_mask, visual_type.blue_mask,
+            );
+
+            if (
+                visual_type.red_mask,
+                visual_type.green_mask,
+                visual_type.blue_mask,
+            ) != (0xFF0000, 0xFF00, 0xFF)
+            {
+                continue;
+            }
+            let color_mask = visual_type.red_mask | visual_type.green_mask | visual_type.blue_mask;
+            let alpha_mask = color_mask as usize ^ ((1usize << depth_info.depth) - 1);
+
+            if alpha_mask == 0 {
+                if set.opaque.is_none() {
+                    set.opaque = Some(visual);
+                }
+            } else {
+                if set.transparent.is_none() {
+                    set.transparent = Some(visual);
+                }
+            }
+        }
+    }
+
+    set
+}
+
 struct RawWindow {
     connection: *mut c_void,
     screen_id: usize,
@@ -90,7 +163,6 @@ pub(crate) struct X11WindowState {
     scale_factor: f32,
     renderer: BladeRenderer,
     display: Rc<dyn PlatformDisplay>,
-
     input_handler: Option<PlatformInputHandler>,
 }
 
@@ -106,7 +178,8 @@ pub(crate) struct X11WindowStatePtr {
 impl rwh::HasWindowHandle for RawWindow {
     fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
         let non_zero = NonZeroU32::new(self.window_id).unwrap();
-        let handle = rwh::XcbWindowHandle::new(non_zero);
+        let mut handle = rwh::XcbWindowHandle::new(non_zero);
+        handle.visual_id = NonZeroU32::new(self.visual_id);
         Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) })
     }
 }
@@ -144,39 +217,77 @@ impl X11WindowState {
         let x_screen_index = params
             .display_id
             .map_or(x_main_screen_index, |did| did.0 as usize);
-        let screen = xcb_connection.setup().roots.get(x_screen_index).unwrap();
-
-        let win_aux = xproto::CreateWindowAux::new().event_mask(
-            xproto::EventMask::EXPOSURE
-                | xproto::EventMask::STRUCTURE_NOTIFY
-                | xproto::EventMask::ENTER_WINDOW
-                | xproto::EventMask::LEAVE_WINDOW
-                | xproto::EventMask::FOCUS_CHANGE
-                | xproto::EventMask::KEY_PRESS
-                | xproto::EventMask::KEY_RELEASE
-                | xproto::EventMask::BUTTON_PRESS
-                | xproto::EventMask::BUTTON_RELEASE
-                | xproto::EventMask::POINTER_MOTION
-                | xproto::EventMask::BUTTON1_MOTION
-                | xproto::EventMask::BUTTON2_MOTION
-                | xproto::EventMask::BUTTON3_MOTION
-                | xproto::EventMask::BUTTON_MOTION,
-        );
+
+        let visual_set = find_visuals(&xcb_connection, x_screen_index);
+        let visual_maybe = match params.window_background {
+            WindowBackgroundAppearance::Opaque => visual_set.opaque,
+            WindowBackgroundAppearance::Transparent | WindowBackgroundAppearance::Blurred => {
+                visual_set.transparent
+            }
+        };
+        let visual = match visual_maybe {
+            Some(visual) => visual,
+            None => {
+                log::warn!(
+                    "Unable to find a matching visual for {:?}",
+                    params.window_background
+                );
+                visual_set.inherit
+            }
+        };
+        log::info!("Using {:?}", visual);
+
+        let colormap = if visual.colormap != 0 {
+            visual.colormap
+        } else {
+            let id = xcb_connection.generate_id().unwrap();
+            log::info!("Creating colormap {}", id);
+            xcb_connection
+                .create_colormap(xproto::ColormapAlloc::NONE, id, visual_set.root, visual.id)
+                .unwrap()
+                .check()
+                .unwrap();
+            id
+        };
+
+        let win_aux = xproto::CreateWindowAux::new()
+            .background_pixel(x11rb::NONE)
+            // https://stackoverflow.com/questions/43218127/x11-xlib-xcb-creating-a-window-requires-border-pixel-if-specifying-colormap-wh
+            .border_pixel(visual_set.black_pixel)
+            .colormap(colormap)
+            .event_mask(
+                xproto::EventMask::EXPOSURE
+                    | xproto::EventMask::STRUCTURE_NOTIFY
+                    | xproto::EventMask::ENTER_WINDOW
+                    | xproto::EventMask::LEAVE_WINDOW
+                    | xproto::EventMask::FOCUS_CHANGE
+                    | xproto::EventMask::KEY_PRESS
+                    | xproto::EventMask::KEY_RELEASE
+                    | xproto::EventMask::BUTTON_PRESS
+                    | xproto::EventMask::BUTTON_RELEASE
+                    | xproto::EventMask::POINTER_MOTION
+                    | xproto::EventMask::BUTTON1_MOTION
+                    | xproto::EventMask::BUTTON2_MOTION
+                    | xproto::EventMask::BUTTON3_MOTION
+                    | xproto::EventMask::BUTTON_MOTION,
+            );
 
         xcb_connection
             .create_window(
-                x11rb::COPY_FROM_PARENT as _,
+                visual.depth,
                 x_window,
-                screen.root,
+                visual_set.root,
                 params.bounds.origin.x.0 as i16,
                 params.bounds.origin.y.0 as i16,
                 params.bounds.size.width.0 as u16,
                 params.bounds.size.height.0 as u16,
                 0,
                 xproto::WindowClass::INPUT_OUTPUT,
-                screen.root_visual,
+                visual.id,
                 &win_aux,
             )
+            .unwrap()
+            .check()
             .unwrap();
 
         xinput::ConnectionExt::xinput_xi_select_events(
@@ -224,7 +335,7 @@ impl X11WindowState {
             ) as *mut _,
             screen_id: x_screen_index,
             window_id: x_window,
-            visual_id: screen.root_visual,
+            visual_id: visual.id,
         };
         let gpu = Arc::new(
             unsafe {
@@ -240,9 +351,12 @@ impl X11WindowState {
             .unwrap(),
         );
 
-        // Note: this has to be done after the GPU init, or otherwise
-        // the sizes are immediately invalidated.
-        let gpu_extent = query_render_extent(xcb_connection, x_window);
+        let config = BladeSurfaceConfig {
+            // Note: this has to be done after the GPU init, or otherwise
+            // the sizes are immediately invalidated.
+            size: query_render_extent(xcb_connection, x_window),
+            transparent: params.window_background != WindowBackgroundAppearance::Opaque,
+        };
 
         Self {
             client,
@@ -251,9 +365,8 @@ impl X11WindowState {
             raw,
             bounds: params.bounds.map(|v| v.0),
             scale_factor,
-            renderer: BladeRenderer::new(gpu, gpu_extent),
+            renderer: BladeRenderer::new(gpu, config),
             atoms: *atoms,
-
             input_handler: None,
         }
     }
@@ -533,8 +646,10 @@ impl PlatformWindow for X11Window {
     // todo(linux)
     fn set_edited(&mut self, edited: bool) {}
 
-    fn set_background_appearance(&mut self, _background_appearance: WindowBackgroundAppearance) {
-        // todo(linux)
+    fn set_background_appearance(&mut self, background_appearance: WindowBackgroundAppearance) {
+        let mut inner = self.0.state.borrow_mut();
+        let transparent = background_appearance != WindowBackgroundAppearance::Opaque;
+        inner.renderer.update_transparency(transparent);
     }
 
     // todo(linux), this corresponds to `orderFrontCharacterPalette` on macOS,

crates/gpui/src/platform/mac/metal_renderer.rs 🔗

@@ -37,6 +37,7 @@ pub unsafe fn new_renderer(
     _native_window: *mut c_void,
     _native_view: *mut c_void,
     _bounds: crate::Size<f32>,
+    _transparent: bool,
 ) -> Renderer {
     MetalRenderer::new(context)
 }
@@ -231,6 +232,10 @@ impl MetalRenderer {
         }
     }
 
+    pub fn update_transparency(&mut self, _transparent: bool) {
+        // todo(mac)?
+    }
+
     pub fn destroy(&mut self) {
         // nothing to do
     }

crates/gpui/src/platform/mac/window.rs 🔗

@@ -626,6 +626,7 @@ impl MacWindow {
                     native_window as *mut _,
                     native_view as *mut _,
                     window_size,
+                    window_background != WindowBackgroundAppearance::Opaque,
                 ),
                 request_frame_callback: None,
                 event_callback: None,
@@ -979,7 +980,10 @@ impl PlatformWindow for MacWindow {
     fn set_app_id(&mut self, _app_id: &str) {}
 
     fn set_background_appearance(&mut self, background_appearance: WindowBackgroundAppearance) {
-        let this = self.0.as_ref().lock();
+        let mut this = self.0.as_ref().lock();
+        this.renderer
+            .update_transparency(background_appearance != WindowBackgroundAppearance::Opaque);
+
         let blur_radius = if background_appearance == WindowBackgroundAppearance::Blurred {
             80
         } else {

crates/gpui/src/platform/windows/window.rs 🔗

@@ -35,7 +35,7 @@ use windows::{
     },
 };
 
-use crate::platform::blade::BladeRenderer;
+use crate::platform::blade::{BladeRenderer, BladeSurfaceConfig};
 use crate::*;
 
 pub(crate) struct WindowsWindowInner {
@@ -62,6 +62,7 @@ impl WindowsWindowInner {
         handle: AnyWindowHandle,
         hide_title_bar: bool,
         display: Rc<WindowsDisplay>,
+        transparent: bool,
     ) -> Self {
         let monitor_dpi = unsafe { GetDpiForWindow(hwnd) } as f32;
         let origin = Cell::new(Point {
@@ -95,7 +96,7 @@ impl WindowsWindowInner {
             }
         }
 
-        let raw = RawWindow { hwnd: hwnd.0 as _ };
+        let raw = RawWindow { hwnd: hwnd.0 };
         let gpu = Arc::new(
             unsafe {
                 gpu::Context::init_windowed(
@@ -109,12 +110,11 @@ impl WindowsWindowInner {
             }
             .unwrap(),
         );
-        let extent = gpu::Extent {
-            width: 1,
-            height: 1,
-            depth: 1,
+        let config = BladeSurfaceConfig {
+            size: gpu::Extent::default(),
+            transparent,
         };
-        let renderer = RefCell::new(BladeRenderer::new(gpu, extent));
+        let renderer = RefCell::new(BladeRenderer::new(gpu, config));
         let callbacks = RefCell::new(Callbacks::default());
         let display = RefCell::new(display);
         let click_state = RefCell::new(ClickState::new());
@@ -1241,6 +1241,7 @@ struct WindowCreateContext {
     handle: AnyWindowHandle,
     hide_title_bar: bool,
     display: Rc<WindowsDisplay>,
+    transparent: bool,
 }
 
 impl WindowsWindow {
@@ -1279,6 +1280,7 @@ impl WindowsWindow {
             // todo(windows) move window to target monitor
             // options.display_id
             display: Rc::new(WindowsDisplay::primary_monitor().unwrap()),
+            transparent: options.window_background != WindowBackgroundAppearance::Opaque,
         };
         let lpparam = Some(&context as *const _ as *const _);
         unsafe {
@@ -1511,8 +1513,11 @@ impl PlatformWindow for WindowsWindow {
 
     fn set_app_id(&mut self, _app_id: &str) {}
 
-    fn set_background_appearance(&mut self, _background_appearance: WindowBackgroundAppearance) {
-        // todo(windows)
+    fn set_background_appearance(&mut self, background_appearance: WindowBackgroundAppearance) {
+        self.inner
+            .renderer
+            .borrow_mut()
+            .update_transparency(background_appearance != WindowBackgroundAppearance::Opaque);
     }
 
     // todo(windows)
@@ -1783,6 +1788,7 @@ unsafe extern "system" fn wnd_proc(
             ctx.handle,
             ctx.hide_title_bar,
             ctx.display.clone(),
+            ctx.transparent,
         ));
         let weak = Box::new(Rc::downgrade(&inner));
         unsafe { set_window_long(hwnd, GWLP_USERDATA, Box::into_raw(weak) as isize) };