From e4f13dd561c1da45df7e2b9a4a643b60c24ae0b8 Mon Sep 17 00:00:00 2001 From: Dzmitry Malyshau Date: Mon, 6 May 2024 09:53:08 -0700 Subject: [PATCH] Blade window transparency (#10973) 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 --- Cargo.lock | 18 +- Cargo.toml | 4 +- crates/gpui/Cargo.toml | 1 + .../gpui/src/platform/blade/blade_renderer.rs | 130 ++++++------ crates/gpui/src/platform/blade/shaders.wgsl | 23 ++- .../gpui/src/platform/linux/wayland/client.rs | 8 +- .../gpui/src/platform/linux/wayland/window.rs | 64 +++++- crates/gpui/src/platform/linux/x11/window.rs | 187 ++++++++++++++---- .../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, 343 insertions(+), 127 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0347b4dd70..456e45e3be 100644 --- a/Cargo.lock +++ b/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" diff --git a/Cargo.toml b/Cargo.toml index 67ce732b61..11f58039c0 100644 --- a/Cargo.toml +++ b/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"] } diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 7a727e3793..1b1a135b12 100644 --- a/crates/gpui/Cargo.toml +++ b/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" diff --git a/crates/gpui/src/platform/blade/blade_renderer.rs b/crates/gpui/src/platform/blade/blade_renderer.rs index 56234058f5..ff9c2742ee 100644 --- a/crates/gpui/src/platform/blade/blade_renderer.rs +++ b/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, + 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::(); shader.check_struct_size::(); + // See https://apoorvaj.io/alpha-compositing-opengl-blending-and-premultiplied-alpha/ + let blend_mode = match surface_info.alpha { + gpu::AlphaMode::Ignored => gpu::BlendState::ALPHA_BLENDING, + gpu::AlphaMode::PreMultiplied => gpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING, + gpu::AlphaMode::PostMultiplied => gpu::BlendState::ALPHA_BLENDING, + }; + let color_targets = &[gpu::ColorTargetState { + format: surface_info.format, + blend: Some(blend_mode), + write_mask: gpu::ColorWrites::default(), + }]; + Self { quads: gpu.create_render_pipeline(gpu::RenderPipelineDesc { name: "quads", @@ -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, + surface_config: gpu::SurfaceConfig, + alpha_mode: gpu::AlphaMode, command_encoder: gpu::CommandEncoder, last_sync_point: Option, pipelines: BladePipelines, instance_belt: BladeBelt, - viewport_size: gpu::Extent, path_tiles: HashMap, atlas: Arc, 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, 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, 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 { @@ -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 { diff --git a/crates/gpui/src/platform/blade/shaders.wgsl b/crates/gpui/src/platform/blade/shaders.wgsl index c29f6bc67b..0cc11e88a9 100644 --- a/crates/gpui/src/platform/blade/shaders.wgsl +++ b/crates/gpui/src/platform/blade/shaders.wgsl @@ -1,6 +1,7 @@ struct GlobalParams { viewport_size: vec2, - pad: vec2, + premultiplied_alpha: u32, + pad: u32, } var globals: GlobalParams; @@ -176,6 +177,13 @@ fn quad_sdf(point: vec2, 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, alpha_factor: f32) -> vec4 { + let alpha = color.a * alpha_factor; + return select(vec4(color.rgb, alpha), vec4(color.rgb, 1.0) * alpha, globals.premultiplied_alpha != 0u); +} + // --- quads --- // struct Quad { @@ -266,7 +274,7 @@ fn fs_quad(input: QuadVarying) -> @location(0) vec4 { saturate(0.5 - inset_distance)); } - return color * vec4(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 { y += step; } - return input.color * vec4(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 { 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 { 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(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 { if (any(input.clip_distances < vec4(0.0))) { return vec4(0.0); } - return input.color * vec4(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 { let grayscale = dot(color.rgb, GRAYSCALE_FACTORS); color = vec4(vec3(grayscale), sample.a); } - color.a *= saturate(0.5 - distance); - return color; + return blend_color(color, saturate(0.5 - distance)); } // --- surfaces --- // diff --git a/crates/gpui/src/platform/linux/wayland/client.rs b/crates/gpui/src/platform/linux/wayland/client.rs index 50c2935a7b..8efd56acd8 100644 --- a/crates/gpui/src/platform/linux/wayland/client.rs +++ b/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, pub decoration_manager: Option, + pub blur_manager: Option, 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); diff --git a/crates/gpui/src/platform/linux/wayland/window.rs b/crates/gpui/src/platform/linux/wayland/window.rs index 05e9e29bdc..adecc089b4 100644 --- a/crates/gpui/src/platform/linux/wayland/window.rs +++ b/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, + blur: Option, toplevel: xdg_toplevel::XdgToplevel, viewport: Option, outputs: HashSet, @@ -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(®ion)); + } 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(®ion)); + 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) { diff --git a/crates/gpui/src/platform/linux/x11/window.rs b/crates/gpui/src/platform/linux/x11/window.rs index b28391a587..8a235a3911 100644 --- a/crates/gpui/src/platform/linux/x11/window.rs +++ b/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, + transparent: Option, + 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, - input_handler: Option, } @@ -106,7 +178,8 @@ pub(crate) struct X11WindowStatePtr { impl rwh::HasWindowHandle for RawWindow { fn window_handle(&self) -> Result { 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, diff --git a/crates/gpui/src/platform/mac/metal_renderer.rs b/crates/gpui/src/platform/mac/metal_renderer.rs index b4c6f1f6b2..03c8afcca8 100644 --- a/crates/gpui/src/platform/mac/metal_renderer.rs +++ b/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, + _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 } diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 75584196c0..c1d6e4afc3 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/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 { diff --git a/crates/gpui/src/platform/windows/window.rs b/crates/gpui/src/platform/windows/window.rs index ec27c5b254..d77a8327b8 100644 --- a/crates/gpui/src/platform/windows/window.rs +++ b/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, + 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, + 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) };