From 666b134d2067d4f1bb153908230e3e8e69d86535 Mon Sep 17 00:00:00 2001 From: Dzmitry Malyshau Date: Wed, 31 Jan 2024 00:04:05 -0800 Subject: [PATCH] linux: shadow rendering --- Cargo.toml | 7 +- crates/gpui/Cargo.toml | 4 +- crates/gpui/build.rs | 4 +- .../gpui/src/platform/linux/blade_renderer.rs | 70 ++++-- crates/gpui/src/platform/linux/platform.rs | 5 +- crates/gpui/src/platform/linux/shaders.wgsl | 200 ++++++++++++++---- crates/gpui/src/platform/linux/window.rs | 6 +- crates/gpui/src/scene.rs | 1 + crates/gpui/src/window/element_cx.rs | 1 + 9 files changed, 236 insertions(+), 62 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 847fc77d62..019cb3f356 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -182,7 +182,7 @@ tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "1d897 wasmtime = { git = "https://github.com/bytecodealliance/wasmtime", rev = "v16.0.0" } # TODO - Remove when corresponding Blade versions are published -# Currently in https://github.com/kvark/blade/tree/zed +[patch."https://github.com/kvark/blade"] blade-graphics = { path = "/x/Code/blade/blade-graphics" } blade-macros = { path = "/x/Code/blade/blade-macros" } @@ -190,6 +190,11 @@ blade-macros = { path = "/x/Code/blade/blade-macros" } split-debuginfo = "unpacked" debug = "limited" +# TODO - Remove this +[profile.dev.package.blade-graphics] +split-debuginfo = "off" +debug = "full" + [profile.dev.package.taffy] opt-level = 3 diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 72cc1bd253..6dd64752cc 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -26,8 +26,8 @@ anyhow.workspace = true async-task = "4.7" backtrace = { version = "0.3", optional = true } bitflags = "2.4.0" -blade-graphics = "0.3" -blade-macros = "0.2" +blade-graphics = { git = "https://github.com/kvark/blade", branch = "zed" } +blade-macros = { git = "https://github.com/kvark/blade", branch = "zed" } bytemuck = "1" collections = { path = "../collections" } ctor.workspace = true diff --git a/crates/gpui/build.rs b/crates/gpui/build.rs index 32da465007..70c3d8d8e4 100644 --- a/crates/gpui/build.rs +++ b/crates/gpui/build.rs @@ -7,7 +7,7 @@ use cbindgen::Config; fn main() { //generate_dispatch_bindings(); - let _header_path = generate_shader_bindings(); + //let header_path = generate_shader_bindings(); //#[cfg(feature = "runtime_shaders")] //emit_stitched_shaders(&header_path); //#[cfg(not(feature = "runtime_shaders"))] @@ -38,7 +38,7 @@ fn _generate_dispatch_bindings() { .expect("couldn't write dispatch bindings"); } -fn generate_shader_bindings() -> PathBuf { +fn _generate_shader_bindings() -> PathBuf { let output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("scene.h"); let crate_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); let mut config = Config::default(); diff --git a/crates/gpui/src/platform/linux/blade_renderer.rs b/crates/gpui/src/platform/linux/blade_renderer.rs index 095c652e95..4d58cbd1e6 100644 --- a/crates/gpui/src/platform/linux/blade_renderer.rs +++ b/crates/gpui/src/platform/linux/blade_renderer.rs @@ -1,5 +1,8 @@ +// Doing `if let` gives you nice scoping with passes/encoders +#![allow(irrefutable_let_patterns)] + use super::{BladeBelt, BladeBeltDescriptor}; -use crate::{PrimitiveBatch, Quad, Scene}; +use crate::{PrimitiveBatch, Quad, Scene, Shadow}; use bytemuck::{Pod, Zeroable}; use blade_graphics as gpu; @@ -18,11 +21,18 @@ struct GlobalParams { #[derive(blade_macros::ShaderData)] struct ShaderQuadsData { globals: GlobalParams, - quads: gpu::BufferPiece, + b_quads: gpu::BufferPiece, +} + +#[derive(blade_macros::ShaderData)] +struct ShaderShadowsData { + globals: GlobalParams, + b_shadows: gpu::BufferPiece, } struct BladePipelines { quads: gpu::RenderPipeline, + shadows: gpu::RenderPipeline, } impl BladePipelines { @@ -31,18 +41,36 @@ impl BladePipelines { source: include_str!("shaders.wgsl"), }); shader.check_struct_size::(); - let layout = ::layout(); + shader.check_struct_size::(); + let quads_layout = ::layout(); + let shadows_layout = ::layout(); Self { quads: gpu.create_render_pipeline(gpu::RenderPipelineDesc { name: "quads", - data_layouts: &[&layout], - vertex: shader.at("vs_quads"), + data_layouts: &[&quads_layout], + vertex: shader.at("vs_quad"), primitive: gpu::PrimitiveState { topology: gpu::PrimitiveTopology::TriangleStrip, ..Default::default() }, depth_stencil: None, - fragment: shader.at("fs_quads"), + fragment: shader.at("fs_quad"), + color_targets: &[gpu::ColorTargetState { + format: surface_format, + blend: Some(gpu::BlendState::ALPHA_BLENDING), + write_mask: gpu::ColorWrites::default(), + }], + }), + shadows: gpu.create_render_pipeline(gpu::RenderPipelineDesc { + name: "shadows", + data_layouts: &[&shadows_layout], + vertex: shader.at("vs_shadow"), + primitive: gpu::PrimitiveState { + topology: gpu::PrimitiveTopology::TriangleStrip, + ..Default::default() + }, + depth_stencil: None, + fragment: shader.at("fs_shadow"), color_targets: &[gpu::ColorTargetState { format: surface_format, blend: Some(gpu::BlendState::ALPHA_BLENDING), @@ -117,6 +145,14 @@ impl BladeRenderer { self.command_encoder.start(); self.command_encoder.init_texture(frame.texture()); + let globals = GlobalParams { + viewport_size: [ + self.viewport_size.width as f32, + self.viewport_size.height as f32, + ], + pad: [0; 2], + }; + if let mut pass = self.command_encoder.render(gpu::RenderTargetSet { colors: &[gpu::RenderTarget { view: frame.texture_view(), @@ -133,18 +169,24 @@ impl BladeRenderer { encoder.bind( 0, &ShaderQuadsData { - globals: GlobalParams { - viewport_size: [ - self.viewport_size.width as f32, - self.viewport_size.height as f32, - ], - pad: [0; 2], - }, - quads: instances, + globals, + b_quads: instances, }, ); encoder.draw(0, 4, 0, quads.len() as u32); } + PrimitiveBatch::Shadows(shadows) => { + let instances = self.instance_belt.alloc_data(shadows, &self.gpu); + let mut encoder = pass.with(&self.pipelines.shadows); + encoder.bind( + 0, + &ShaderShadowsData { + globals, + b_shadows: instances, + }, + ); + encoder.draw(0, 4, 0, shadows.len() as u32); + } _ => continue, } } diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs index 08bf081853..398d741ff3 100644 --- a/crates/gpui/src/platform/linux/platform.rs +++ b/crates/gpui/src/platform/linux/platform.rs @@ -112,10 +112,10 @@ impl Platform for LinuxPlatform { xcb::Event::X(x::Event::Expose(ev)) => { repaint_x_window = Some(ev.window()); } - xcb::Event::X(x::Event::ResizeRequest(ev)) => { + xcb::Event::X(x::Event::ConfigureNotify(ev)) => { let this = self.0.lock(); LinuxWindowState::resize(&this.windows[&ev.window()], ev.width(), ev.height()); - repaint_x_window = Some(ev.window()); + this.xcb_connection.flush(); } _ => {} } @@ -175,7 +175,6 @@ impl Platform for LinuxPlatform { let window_ptr = LinuxWindowState::new_ptr( options, - handle, &this.xcb_connection, this.x_root_index, x_window, diff --git a/crates/gpui/src/platform/linux/shaders.wgsl b/crates/gpui/src/platform/linux/shaders.wgsl index 0cc70e5220..a4e280cbef 100644 --- a/crates/gpui/src/platform/linux/shaders.wgsl +++ b/crates/gpui/src/platform/linux/shaders.wgsl @@ -1,3 +1,17 @@ +struct Globals { + viewport_size: vec2, + pad: vec2, +} + +var globals: Globals; + +const M_PI_F: f32 = 3.1415926; + +struct ViewId { + lo: u32, + hi: u32, +} + struct Bounds { origin: vec2, size: vec2, @@ -21,35 +35,6 @@ struct Hsla { a: f32, } -struct Quad { - view_id: vec2, - layer_id: u32, - order: u32, - bounds: Bounds, - content_mask: Bounds, - background: Hsla, - border_color: Hsla, - corner_radii: Corners, - border_widths: Edges, -} - -struct Globals { - viewport_size: vec2, - pad: vec2, -} - -var globals: Globals; -var quads: array; - -struct QuadsVarying { - @builtin(position) position: vec4, - @location(0) @interpolate(flat) background_color: vec4, - @location(1) @interpolate(flat) border_color: vec4, - @location(2) @interpolate(flat) quad_id: u32, - //TODO: use `clip_distance` once Naga supports it - @location(3) clip_distances: vec4, -} - fn to_device_position(unit_vertex: vec2, bounds: Bounds) -> vec4 { let position = unit_vertex * vec2(bounds.size) + bounds.origin; let device_position = position / globals.viewport_size * vec2(2.0, -2.0) + vec2(-1.0, 1.0); @@ -99,17 +84,62 @@ fn hsla_to_rgba(hsla: Hsla) -> vec4 { } fn over(below: vec4, above: vec4) -> vec4 { - let alpha = above.a + below.a * (1.0 - above.a); - let color = (above.rgb * above.a + below.rgb * below.a * (1.0 - above.a)) / alpha; - return vec4(color, alpha); + let alpha = above.a + below.a * (1.0 - above.a); + let color = (above.rgb * above.a + below.rgb * below.a * (1.0 - above.a)) / alpha; + return vec4(color, alpha); +} + +// A standard gaussian function, used for weighting samples +fn gaussian(x: f32, sigma: f32) -> f32{ + return exp(-(x * x) / (2.0 * sigma * sigma)) / (sqrt(2.0 * M_PI_F) * sigma); +} + +// This approximates the error function, needed for the gaussian integral +fn erf(v: vec2) -> vec2 { + let s = sign(v); + let a = abs(v); + let r1 = 1.0 + (0.278393 + (0.230389 + 0.078108 * (a * a)) * a) * a; + let r2 = r1 * r1; + return s - s / (r2 * r2); +} + +fn blur_along_x(x: f32, y: f32, sigma: f32, corner: f32, half_size: vec2) -> f32 { + let delta = min(half_size.y - corner - abs(y), 0.0); + let curved = half_size.x - corner + sqrt(max(0.0, corner * corner - delta * delta)); + let integral = 0.5 + 0.5 * erf((x + vec2(-curved, curved)) * (sqrt(0.5) / sigma)); + return integral.y - integral.x; +} + +// --- quads --- // + +struct Quad { + view_id: ViewId, + layer_id: u32, + order: u32, + bounds: Bounds, + content_mask: Bounds, + background: Hsla, + border_color: Hsla, + corner_radii: Corners, + border_widths: Edges, +} +var b_quads: array; + +struct QuadVarying { + @builtin(position) position: vec4, + @location(0) @interpolate(flat) background_color: vec4, + @location(1) @interpolate(flat) border_color: vec4, + @location(2) @interpolate(flat) quad_id: u32, + //TODO: use `clip_distance` once Naga supports it + @location(3) clip_distances: vec4, } @vertex -fn vs_quads(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) instance_id: u32) -> QuadsVarying { +fn vs_quad(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) instance_id: u32) -> QuadVarying { let unit_vertex = vec2(f32(vertex_id & 1u), 0.5 * f32(vertex_id & 2u)); - let quad = quads[instance_id]; + let quad = b_quads[instance_id]; - var out = QuadsVarying(); + var out = QuadVarying(); out.position = to_device_position(unit_vertex, quad.bounds); out.background_color = hsla_to_rgba(quad.background); out.border_color = hsla_to_rgba(quad.border_color); @@ -119,7 +149,7 @@ fn vs_quads(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) inst } @fragment -fn fs_quads(input: QuadsVarying) -> @location(0) vec4 { +fn fs_quad(input: QuadVarying) -> @location(0) vec4 { // Alpha clip first, since we don't have `clip_distance`. let min_distance = min( min(input.clip_distances.x, input.clip_distances.y), @@ -129,7 +159,7 @@ fn fs_quads(input: QuadsVarying) -> @location(0) vec4 { return vec4(0.0); } - let quad = quads[input.quad_id]; + let quad = b_quads[input.quad_id]; let half_size = quad.bounds.size / 2.0; let center = quad.bounds.origin + half_size; let center_to_point = input.position.xy - center; @@ -180,4 +210,98 @@ fn fs_quads(input: QuadsVarying) -> @location(0) vec4 { } return color * vec4(1.0, 1.0, 1.0, saturate(0.5 - distance)); -} \ No newline at end of file +} + +// --- shadows --- // + +struct Shadow { + view_id: ViewId, + layer_id: u32, + order: u32, + bounds: Bounds, + corner_radii: Corners, + content_mask: Bounds, + color: Hsla, + blur_radius: f32, + pad: u32, +} +var b_shadows: array; + +struct ShadowVarying { + @builtin(position) position: vec4, + @location(0) @interpolate(flat) color: vec4, + @location(1) @interpolate(flat) shadow_id: u32, + //TODO: use `clip_distance` once Naga supports it + @location(3) clip_distances: vec4, +} + +@vertex +fn vs_shadow(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) instance_id: u32) -> ShadowVarying { + let unit_vertex = vec2(f32(vertex_id & 1u), 0.5 * f32(vertex_id & 2u)); + let shadow = b_shadows[instance_id]; + + let margin = 3.0 * shadow.blur_radius; + // Set the bounds of the shadow and adjust its size based on the shadow's + // spread radius to achieve the spreading effect + var bounds = shadow.bounds; + bounds.origin -= vec2(margin); + bounds.size += 2.0 * vec2(margin); + + var out = ShadowVarying(); + out.position = to_device_position(unit_vertex, shadow.bounds); + out.color = hsla_to_rgba(shadow.color); + out.shadow_id = instance_id; + out.clip_distances = distance_from_clip_rect(unit_vertex, shadow.bounds, shadow.content_mask); + return out; +} + +@fragment +fn fs_shadow(input: ShadowVarying) -> @location(0) vec4 { + // Alpha clip first, since we don't have `clip_distance`. + let min_distance = min( + min(input.clip_distances.x, input.clip_distances.y), + min(input.clip_distances.z, input.clip_distances.w) + ); + if min_distance <= 0.0 { + return vec4(0.0); + } + + let shadow = b_shadows[input.shadow_id]; + let half_size = shadow.bounds.size / 2.0; + let center = shadow.bounds.origin + half_size; + let center_to_point = input.position.xy - center; + + var corner_radius = 0.0; + if (center_to_point.x < 0.0) { + if (center_to_point.y < 0.0) { + corner_radius = shadow.corner_radii.top_left; + } else { + corner_radius = shadow.corner_radii.bottom_left; + } + } else { + if (center_to_point.y < 0.) { + corner_radius = shadow.corner_radii.top_right; + } else { + corner_radius = shadow.corner_radii.bottom_right; + } + } + + // The signal is only non-zero in a limited range, so don't waste samples + let low = center_to_point.y - half_size.y; + let high = center_to_point.y + half_size.y; + let start = clamp(-3.0 * shadow.blur_radius, low, high); + let end = clamp(3.0 * shadow.blur_radius, low, high); + + // Accumulate samples (we can get away with surprisingly few samples) + let step = (end - start) / 4.0; + var y = start + step * 0.5; + var alpha = 0.0; + for (var i = 0; i < 4; i += 1) { + let blur = blur_along_x(center_to_point.x, center_to_point.y - y, + shadow.blur_radius, corner_radius, half_size); + alpha += blur * gaussian(y, shadow.blur_radius) * step; + y += step; + } + + return input.color * vec4(1.0, 1.0, 1.0, alpha); +} diff --git a/crates/gpui/src/platform/linux/window.rs b/crates/gpui/src/platform/linux/window.rs index a0d8157aab..0f0916b0db 100644 --- a/crates/gpui/src/platform/linux/window.rs +++ b/crates/gpui/src/platform/linux/window.rs @@ -38,11 +38,13 @@ struct RawWindow { connection: *mut c_void, screen_id: i32, window_id: u32, + visual_id: u32, } unsafe impl raw_window_handle::HasRawWindowHandle for RawWindow { fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle { let mut wh = raw_window_handle::XcbWindowHandle::empty(); wh.window = self.window_id; + wh.visual_id = self.visual_id; wh.into() } } @@ -58,7 +60,6 @@ unsafe impl raw_window_handle::HasRawDisplayHandle for RawWindow { impl LinuxWindowState { pub fn new_ptr( options: WindowOptions, - handle: AnyWindowHandle, xcb_connection: &xcb::Connection, x_main_screen_index: i32, x_window: x::Window, @@ -76,7 +77,7 @@ impl LinuxWindowState { let xcb_values = [ x::Cw::BackPixel(screen.white_pixel()), x::Cw::EventMask( - x::EventMask::EXPOSURE | x::EventMask::RESIZE_REDIRECT | x::EventMask::KEY_PRESS, + x::EventMask::EXPOSURE | x::EventMask::STRUCTURE_NOTIFY | x::EventMask::KEY_PRESS, ), ]; @@ -136,6 +137,7 @@ impl LinuxWindowState { ) as *mut _, screen_id: x_screen_index, window_id: x_window.resource_id(), + visual_id: screen.root_visual(), }; let gpu = Arc::new( unsafe { diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index e1aa7fda20..f5fcade711 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -577,6 +577,7 @@ pub(crate) struct Shadow { pub content_mask: ContentMask, pub color: Hsla, pub blur_radius: ScaledPixels, + pub pad: u32, // align to 8 bytes } impl Ord for Shadow { diff --git a/crates/gpui/src/window/element_cx.rs b/crates/gpui/src/window/element_cx.rs index 6e2e5bc725..cde5f17b05 100644 --- a/crates/gpui/src/window/element_cx.rs +++ b/crates/gpui/src/window/element_cx.rs @@ -677,6 +677,7 @@ impl<'a> ElementContext<'a> { corner_radii: corner_radii.scale(scale_factor), color: shadow.color, blur_radius: shadow.blur_radius.scale(scale_factor), + pad: 0, }, ); }