diff --git a/Cargo.lock b/Cargo.lock index 291e86f0b5..c3df96e992 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3234,6 +3234,7 @@ dependencies = [ "bindgen 0.65.1", "bitflags 2.4.1", "block", + "bytemuck", "cbindgen", "cocoa", "collections", diff --git a/Cargo.toml b/Cargo.toml index a3eeb2d653..847fc77d62 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -182,6 +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 blade-graphics = { path = "/x/Code/blade/blade-graphics" } blade-macros = { path = "/x/Code/blade/blade-macros" } diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 08c5b609a2..72cc1bd253 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -26,8 +26,9 @@ anyhow.workspace = true async-task = "4.7" backtrace = { version = "0.3", optional = true } bitflags = "2.4.0" -blade = { package = "blade-graphics", version = "0.3" } +blade-graphics = "0.3" blade-macros = "0.2" +bytemuck = "1" collections = { path = "../collections" } ctor.workspace = true derive_more.workspace = true diff --git a/crates/gpui/src/platform/linux/blade_atlas.rs b/crates/gpui/src/platform/linux/blade_atlas.rs index b81b282925..56bb745764 100644 --- a/crates/gpui/src/platform/linux/blade_atlas.rs +++ b/crates/gpui/src/platform/linux/blade_atlas.rs @@ -4,6 +4,7 @@ use crate::{ Point, Size, }; use anyhow::Result; +use blade_graphics as gpu; use collections::FxHashMap; use etagere::BucketedAtlasAllocator; use parking_lot::Mutex; @@ -12,8 +13,8 @@ use std::{borrow::Cow, sync::Arc}; pub(crate) struct BladeAtlas(Mutex); struct BladeAtlasState { - gpu: Arc, - gpu_encoder: blade::CommandEncoder, + gpu: Arc, + gpu_encoder: gpu::CommandEncoder, upload_belt: BladeBelt, monochrome_textures: Vec, polychrome_textures: Vec, @@ -38,15 +39,15 @@ impl BladeAtlasState { } impl BladeAtlas { - pub(crate) fn new(gpu: &Arc) -> Self { + pub(crate) fn new(gpu: &Arc) -> Self { BladeAtlas(Mutex::new(BladeAtlasState { gpu: Arc::clone(gpu), - gpu_encoder: gpu.create_command_encoder(blade::CommandEncoderDesc { + gpu_encoder: gpu.create_command_encoder(gpu::CommandEncoderDesc { name: "atlas", buffer_count: 3, }), upload_belt: BladeBelt::new(BladeBeltDescriptor { - memory: blade::Memory::Upload, + memory: gpu::Memory::Upload, min_chunk_size: 0x10000, }), monochrome_textures: Default::default(), @@ -77,7 +78,7 @@ impl BladeAtlas { lock.gpu_encoder.start(); } - pub fn finish_frame(&self) -> blade::SyncPoint { + pub fn finish_frame(&self) -> gpu::SyncPoint { let mut lock = self.0.lock(); let gpu = lock.gpu.clone(); let sync_point = gpu.submit(&mut lock.gpu_encoder); @@ -137,32 +138,32 @@ impl BladeAtlasState { let usage; match kind { AtlasTextureKind::Monochrome => { - format = blade::TextureFormat::R8Unorm; - usage = blade::TextureUsage::COPY | blade::TextureUsage::RESOURCE; + format = gpu::TextureFormat::R8Unorm; + usage = gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE; } AtlasTextureKind::Polychrome => { - format = blade::TextureFormat::Bgra8Unorm; - usage = blade::TextureUsage::COPY | blade::TextureUsage::RESOURCE; + format = gpu::TextureFormat::Bgra8Unorm; + usage = gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE; } AtlasTextureKind::Path => { - format = blade::TextureFormat::R16Float; - usage = blade::TextureUsage::COPY - | blade::TextureUsage::RESOURCE - | blade::TextureUsage::TARGET; + format = gpu::TextureFormat::R16Float; + usage = gpu::TextureUsage::COPY + | gpu::TextureUsage::RESOURCE + | gpu::TextureUsage::TARGET; } } - let raw = self.gpu.create_texture(blade::TextureDesc { + let raw = self.gpu.create_texture(gpu::TextureDesc { name: "", format, - size: blade::Extent { + size: gpu::Extent { width: size.width.into(), height: size.height.into(), depth: 1, }, array_layer_count: 1, mip_level_count: 1, - dimension: blade::TextureDimension::D2, + dimension: gpu::TextureDimension::D2, usage, }); @@ -198,13 +199,13 @@ impl BladeAtlasState { transfers.copy_buffer_to_texture( src_data, bounds.size.width.to_bytes(texture.bytes_per_pixel()), - blade::TexturePiece { + gpu::TexturePiece { texture: texture.raw, mip_level: 0, array_layer: 0, origin: [bounds.origin.x.into(), bounds.origin.y.into(), 0], }, - blade::Extent { + gpu::Extent { width: bounds.size.width.into(), height: bounds.size.height.into(), depth: 1, @@ -216,8 +217,8 @@ impl BladeAtlasState { struct BladeAtlasTexture { id: AtlasTextureId, allocator: BucketedAtlasAllocator, - raw: blade::Texture, - format: blade::TextureFormat, + raw: gpu::Texture, + format: gpu::TextureFormat, } impl BladeAtlasTexture { diff --git a/crates/gpui/src/platform/linux/blade_belt.rs b/crates/gpui/src/platform/linux/blade_belt.rs index ff3d5c6692..17f100beb2 100644 --- a/crates/gpui/src/platform/linux/blade_belt.rs +++ b/crates/gpui/src/platform/linux/blade_belt.rs @@ -1,10 +1,13 @@ +use blade_graphics as gpu; +use std::mem; + struct ReusableBuffer { - raw: blade::Buffer, + raw: gpu::Buffer, size: u64, } pub struct BladeBeltDescriptor { - pub memory: blade::Memory, + pub memory: gpu::Memory, pub min_chunk_size: u64, } @@ -12,7 +15,7 @@ pub struct BladeBeltDescriptor { /// find staging space for uploads. pub struct BladeBelt { desc: BladeBeltDescriptor, - buffers: Vec<(ReusableBuffer, blade::SyncPoint)>, + buffers: Vec<(ReusableBuffer, gpu::SyncPoint)>, active: Vec<(ReusableBuffer, u64)>, } @@ -25,7 +28,7 @@ impl BladeBelt { } } - pub fn destroy(&mut self, gpu: &blade::Context) { + pub fn destroy(&mut self, gpu: &gpu::Context) { for (buffer, _) in self.buffers.drain(..) { gpu.destroy_buffer(buffer.raw); } @@ -34,7 +37,7 @@ impl BladeBelt { } } - pub fn alloc(&mut self, size: u64, gpu: &blade::Context) -> blade::BufferPiece { + pub fn alloc(&mut self, size: u64, gpu: &gpu::Context) -> gpu::BufferPiece { for &mut (ref rb, ref mut offset) in self.active.iter_mut() { if *offset + size <= rb.size { let piece = rb.raw.at(*offset); @@ -56,7 +59,7 @@ impl BladeBelt { let chunk_index = self.buffers.len() + self.active.len(); let chunk_size = size.max(self.desc.min_chunk_size); - let chunk = gpu.create_buffer(blade::BufferDesc { + let chunk = gpu.create_buffer(gpu::BufferDesc { name: &format!("chunk-{}", chunk_index), size: chunk_size, memory: self.desc.memory, @@ -69,15 +72,23 @@ impl BladeBelt { chunk.into() } - pub fn alloc_data(&mut self, data: &[u8], gpu: &blade::Context) -> blade::BufferPiece { - let bp = self.alloc(data.len() as u64, gpu); + //Note: assuming T: bytemuck::Zeroable + pub fn alloc_data(&mut self, data: &[T], gpu: &gpu::Context) -> gpu::BufferPiece { + assert!(!data.is_empty()); + let alignment = mem::align_of::() as u64; + let total_bytes = data.len() * mem::size_of::(); + let mut bp = self.alloc(alignment + (total_bytes - 1) as u64, gpu); + let rem = bp.offset % alignment; + if rem != 0 { + bp.offset += alignment - rem; + } unsafe { - std::ptr::copy_nonoverlapping(data.as_ptr(), bp.data(), data.len()); + std::ptr::copy_nonoverlapping(data.as_ptr() as *const u8, bp.data(), total_bytes); } bp } - pub fn flush(&mut self, sp: &blade::SyncPoint) { + pub fn flush(&mut self, sp: &gpu::SyncPoint) { self.buffers .extend(self.active.drain(..).map(|(rb, _)| (rb, sp.clone()))); } diff --git a/crates/gpui/src/platform/linux/blade_renderer.rs b/crates/gpui/src/platform/linux/blade_renderer.rs index 59b95d7274..095c652e95 100644 --- a/crates/gpui/src/platform/linux/blade_renderer.rs +++ b/crates/gpui/src/platform/linux/blade_renderer.rs @@ -1,38 +1,93 @@ -use crate::Scene; +use super::{BladeBelt, BladeBeltDescriptor}; +use crate::{PrimitiveBatch, Quad, Scene}; +use bytemuck::{Pod, Zeroable}; +use blade_graphics as gpu; use std::sync::Arc; const SURFACE_FRAME_COUNT: u32 = 3; const MAX_FRAME_TIME_MS: u32 = 1000; +#[repr(C)] +#[derive(Clone, Copy, Pod, Zeroable)] +struct GlobalParams { + viewport_size: [f32; 2], + pad: [u32; 2], +} + +#[derive(blade_macros::ShaderData)] +struct ShaderQuadsData { + globals: GlobalParams, + quads: gpu::BufferPiece, +} + +struct BladePipelines { + quads: gpu::RenderPipeline, +} + +impl BladePipelines { + fn new(gpu: &gpu::Context, surface_format: gpu::TextureFormat) -> Self { + let shader = gpu.create_shader(gpu::ShaderDesc { + source: include_str!("shaders.wgsl"), + }); + shader.check_struct_size::(); + let layout = ::layout(); + Self { + quads: gpu.create_render_pipeline(gpu::RenderPipelineDesc { + name: "quads", + data_layouts: &[&layout], + vertex: shader.at("vs_quads"), + primitive: gpu::PrimitiveState { + topology: gpu::PrimitiveTopology::TriangleStrip, + ..Default::default() + }, + depth_stencil: None, + fragment: shader.at("fs_quads"), + color_targets: &[gpu::ColorTargetState { + format: surface_format, + blend: Some(gpu::BlendState::ALPHA_BLENDING), + write_mask: gpu::ColorWrites::default(), + }], + }), + } + } +} + pub struct BladeRenderer { - gpu: Arc, - command_encoder: blade::CommandEncoder, - last_sync_point: Option, + gpu: Arc, + command_encoder: gpu::CommandEncoder, + last_sync_point: Option, + pipelines: BladePipelines, + instance_belt: BladeBelt, + viewport_size: gpu::Extent, } impl BladeRenderer { - pub fn new(gpu: Arc, size: blade::Extent) -> Self { - let _surface_format = gpu.resize(blade::SurfaceConfig { + pub fn new(gpu: Arc, size: gpu::Extent) -> Self { + let surface_format = gpu.resize(gpu::SurfaceConfig { size, - usage: blade::TextureUsage::TARGET, + usage: gpu::TextureUsage::TARGET, frame_count: SURFACE_FRAME_COUNT, }); - let command_encoder = gpu.create_command_encoder(blade::CommandEncoderDesc { + let command_encoder = gpu.create_command_encoder(gpu::CommandEncoderDesc { name: "main", buffer_count: 2, }); + let pipelines = BladePipelines::new(&gpu, surface_format); + let instance_belt = BladeBelt::new(BladeBeltDescriptor { + memory: gpu::Memory::Shared, + min_chunk_size: 0x1000, + }); Self { gpu, command_encoder, last_sync_point: None, + pipelines, + instance_belt, + viewport_size: size, } } - pub fn destroy(&mut self) { - self.gpu.destroy_command_encoder(&mut self.command_encoder); - } - fn wait_for_gpu(&mut self) { if let Some(last_sp) = self.last_sync_point.take() { if !self.gpu.wait_for(&last_sp, MAX_FRAME_TIME_MS) { @@ -41,13 +96,20 @@ impl BladeRenderer { } } - pub fn resize(&mut self, size: blade::Extent) { + pub fn destroy(&mut self) { self.wait_for_gpu(); - self.gpu.resize(blade::SurfaceConfig { + self.instance_belt.destroy(&self.gpu); + self.gpu.destroy_command_encoder(&mut self.command_encoder); + } + + pub fn resize(&mut self, size: gpu::Extent) { + self.wait_for_gpu(); + self.gpu.resize(gpu::SurfaceConfig { size, - usage: blade::TextureUsage::TARGET, + usage: gpu::TextureUsage::TARGET, frame_count: SURFACE_FRAME_COUNT, }); + self.viewport_size = size; } pub fn draw(&mut self, scene: &Scene) { @@ -55,9 +117,42 @@ impl BladeRenderer { self.command_encoder.start(); self.command_encoder.init_texture(frame.texture()); - self.command_encoder.present(frame); + if let mut pass = self.command_encoder.render(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, + }) { + for batch in scene.batches() { + match batch { + PrimitiveBatch::Quads(quads) => { + let instances = self.instance_belt.alloc_data(quads, &self.gpu); + let mut encoder = pass.with(&self.pipelines.quads); + 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, + }, + ); + encoder.draw(0, 4, 0, quads.len() as u32); + } + _ => continue, + } + } + } + self.command_encoder.present(frame); let sync_point = self.gpu.submit(&mut self.command_encoder); + self.instance_belt.flush(&sync_point); self.wait_for_gpu(); self.last_sync_point = Some(sync_point); } diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs index 5a5494cda8..08bf081853 100644 --- a/crates/gpui/src/platform/linux/platform.rs +++ b/crates/gpui/src/platform/linux/platform.rs @@ -115,6 +115,7 @@ impl Platform for LinuxPlatform { xcb::Event::X(x::Event::ResizeRequest(ev)) => { let this = self.0.lock(); LinuxWindowState::resize(&this.windows[&ev.window()], ev.width(), ev.height()); + repaint_x_window = Some(ev.window()); } _ => {} } diff --git a/crates/gpui/src/platform/linux/shaders.wgsl b/crates/gpui/src/platform/linux/shaders.wgsl new file mode 100644 index 0000000000..0cc70e5220 --- /dev/null +++ b/crates/gpui/src/platform/linux/shaders.wgsl @@ -0,0 +1,183 @@ +struct Bounds { + origin: vec2, + size: vec2, +} +struct Corners { + top_left: f32, + top_right: f32, + bottom_right: f32, + bottom_left: f32, +} +struct Edges { + top: f32, + right: f32, + bottom: f32, + left: f32, +} +struct Hsla { + h: f32, + s: f32, + l: f32, + 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); + return vec4(device_position, 0.0, 1.0); +} + +fn distance_from_clip_rect(unit_vertex: vec2, bounds: Bounds, clip_bounds: Bounds) -> vec4 { + let position = unit_vertex * vec2(bounds.size) + bounds.origin; + let tl = position - clip_bounds.origin; + let br = clip_bounds.origin + clip_bounds.size - position; + return vec4(tl.x, br.x, tl.y, br.y); +} + +fn hsla_to_rgba(hsla: Hsla) -> vec4 { + let h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range + let s = hsla.s; + let l = hsla.l; + let a = hsla.a; + + let c = (1.0 - abs(2.0 * l - 1.0)) * s; + let x = c * (1.0 - abs(h % 2.0 - 1.0)); + let m = l - c / 2.0; + + var color = vec4(m, m, m, a); + + if (h >= 0.0 && h < 1.0) { + color.r += c; + color.g += x; + } else if (h >= 1.0 && h < 2.0) { + color.r += x; + color.g += c; + } else if (h >= 2.0 && h < 3.0) { + color.g += c; + color.b += x; + } else if (h >= 3.0 && h < 4.0) { + color.g += x; + color.b += c; + } else if (h >= 4.0 && h < 5.0) { + color.r += x; + color.b += c; + } else { + color.r += c; + color.b += x; + } + + return color; +} + +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); +} + +@vertex +fn vs_quads(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) instance_id: u32) -> QuadsVarying { + let unit_vertex = vec2(f32(vertex_id & 1u), 0.5 * f32(vertex_id & 2u)); + let quad = quads[instance_id]; + + var out = QuadsVarying(); + 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); + out.quad_id = instance_id; + out.clip_distances = distance_from_clip_rect(unit_vertex, quad.bounds, quad.content_mask); + return out; +} + +@fragment +fn fs_quads(input: QuadsVarying) -> @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 quad = 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; + + var corner_radius = 0.0; + if (center_to_point.x < 0.0) { + if (center_to_point.y < 0.0) { + corner_radius = quad.corner_radii.top_left; + } else { + corner_radius = quad.corner_radii.bottom_left; + } + } else { + if (center_to_point.y < 0.) { + corner_radius = quad.corner_radii.top_right; + } else { + corner_radius = quad.corner_radii.bottom_right; + } + } + + let rounded_edge_to_point = abs(center_to_point) - half_size + corner_radius; + let distance = + length(max(vec2(0.0), rounded_edge_to_point)) + + min(0.0, max(rounded_edge_to_point.x, rounded_edge_to_point.y)) - + corner_radius; + + let vertical_border = select(quad.border_widths.left, quad.border_widths.right, center_to_point.x > 0.0); + let horizontal_border = select(quad.border_widths.top, quad.border_widths.bottom, center_to_point.y > 0.0); + let inset_size = half_size - corner_radius - vec2(vertical_border, horizontal_border); + let point_to_inset_corner = abs(center_to_point) - inset_size; + + var border_width = 0.0; + if (point_to_inset_corner.x < 0.0 && point_to_inset_corner.y < 0.0) { + border_width = 0.0; + } else if (point_to_inset_corner.y > point_to_inset_corner.x) { + border_width = horizontal_border; + } else { + border_width = vertical_border; + } + + var color = input.background_color; + if (border_width > 0.0) { + let inset_distance = distance + border_width; + // Blend the border on top of the background and then linearly interpolate + // between the two as we slide inside the background. + let blended_border = over(input.background_color, input.border_color); + color = mix(blended_border, input.background_color, + saturate(0.5 - inset_distance)); + } + + return color * vec4(1.0, 1.0, 1.0, saturate(0.5 - distance)); +} \ No newline at end of file diff --git a/crates/gpui/src/platform/linux/window.rs b/crates/gpui/src/platform/linux/window.rs index 85ede0ab5a..a0d8157aab 100644 --- a/crates/gpui/src/platform/linux/window.rs +++ b/crates/gpui/src/platform/linux/window.rs @@ -3,6 +3,7 @@ use crate::{ AnyWindowHandle, BladeAtlas, LinuxDisplay, Pixels, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, Size, WindowAppearance, WindowBounds, WindowOptions, XcbAtoms, }; +use blade_graphics as gpu; use parking_lot::Mutex; use std::{ ffi::c_void, @@ -15,6 +16,7 @@ use xcb::{x, Xid as _}; struct Callbacks { request_frame: Option>, resize: Option, f32)>>, + moved: Option>, } pub(crate) struct LinuxWindowState { @@ -24,6 +26,7 @@ pub(crate) struct LinuxWindowState { content_size: Size, sprite_atlas: Arc, renderer: BladeRenderer, + //TODO: move out into a separate struct callbacks: Callbacks, } @@ -136,9 +139,9 @@ impl LinuxWindowState { }; let gpu = Arc::new( unsafe { - blade::Context::init_windowed( + gpu::Context::init_windowed( &raw_window, - blade::ContextDesc { + gpu::ContextDesc { validation: cfg!(debug_assertions), capture: false, }, @@ -146,7 +149,7 @@ impl LinuxWindowState { } .unwrap(), ); - let gpu_extent = blade::Extent { + let gpu_extent = gpu::Extent { width: bound_width as u32, height: bound_height as u32, depth: 1, @@ -186,7 +189,7 @@ impl LinuxWindowState { let mut this = self_ptr.lock(); this.callbacks.resize = Some(fun); this.content_size = content_size; - this.renderer.resize(blade::Extent { + this.renderer.resize(gpu::Extent { width: width as u32, height: height as u32, depth: 1, @@ -294,7 +297,9 @@ impl PlatformWindow for LinuxWindow { fn on_fullscreen(&self, _callback: Box) {} - fn on_moved(&self, callback: Box) {} + fn on_moved(&self, callback: Box) { + self.0.lock().callbacks.moved = Some(callback); + } fn on_should_close(&self, _callback: Box bool>) {}