diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index dbae48a5aa..42978e203b 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -26,7 +26,7 @@ anyhow.workspace = true async-task = "4.7" backtrace = { version = "0.3", optional = true } bitflags = "2.4.0" -blade-graphics = "0.3" +blade = { package = "blade-graphics", version = "0.3" } blade-macros = "0.2" collections = { path = "../collections" } ctor.workspace = true diff --git a/crates/gpui/src/platform/linux.rs b/crates/gpui/src/platform/linux.rs index c762c3135e..ba70c6fe67 100644 --- a/crates/gpui/src/platform/linux.rs +++ b/crates/gpui/src/platform/linux.rs @@ -1,7 +1,16 @@ +mod blade_atlas; +mod blade_belt; mod dispatcher; +mod display; mod platform; mod text_system; +mod window; +pub(crate) use blade_atlas::*; pub(crate) use dispatcher::*; +pub(crate) use display::*; pub(crate) use platform::*; pub(crate) use text_system::*; +pub(crate) use window::*; + +use blade_belt::*; diff --git a/crates/gpui/src/platform/linux/blade_atlas.rs b/crates/gpui/src/platform/linux/blade_atlas.rs new file mode 100644 index 0000000000..24a9bbde8d --- /dev/null +++ b/crates/gpui/src/platform/linux/blade_atlas.rs @@ -0,0 +1,258 @@ +use super::{BladeBelt, BladeBeltDescriptor}; +use crate::{ + AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas, + Point, Size, +}; +use anyhow::Result; +use collections::FxHashMap; +use derive_more::{Deref, DerefMut}; +use etagere::BucketedAtlasAllocator; +use parking_lot::Mutex; +use std::{borrow::Cow, sync::Arc}; + +pub(crate) struct BladeAtlas(Mutex); + +struct BladeAtlasState { + gpu: Arc, + gpu_encoder: blade::CommandEncoder, + upload_belt: BladeBelt, + monochrome_textures: Vec, + polychrome_textures: Vec, + path_textures: Vec, + tiles_by_key: FxHashMap, +} + +impl BladeAtlas { + pub(crate) fn new(gpu: &Arc) -> Self { + BladeAtlas(Mutex::new(BladeAtlasState { + gpu: Arc::clone(gpu), + gpu_encoder: gpu.create_command_encoder(blade::CommandEncoderDesc { + name: "atlas", + buffer_count: 3, + }), + upload_belt: BladeBelt::new(BladeBeltDescriptor { + memory: blade::Memory::Upload, + min_chunk_size: 0x10000, + }), + monochrome_textures: Default::default(), + polychrome_textures: Default::default(), + path_textures: Default::default(), + tiles_by_key: Default::default(), + })) + } + + pub(crate) fn clear_textures(&self, texture_kind: AtlasTextureKind) { + let mut lock = self.0.lock(); + let textures = match texture_kind { + AtlasTextureKind::Monochrome => &mut lock.monochrome_textures, + AtlasTextureKind::Polychrome => &mut lock.polychrome_textures, + AtlasTextureKind::Path => &mut lock.path_textures, + }; + for texture in textures { + texture.clear(); + } + } + + pub fn start_frame(&self) { + let mut lock = self.0.lock(); + lock.gpu_encoder.start(); + } + + pub fn finish_frame(&self) -> blade::SyncPoint { + let mut lock = self.0.lock(); + let gpu = lock.gpu.clone(); + let sync_point = gpu.submit(&mut lock.gpu_encoder); + lock.upload_belt.flush(&sync_point); + sync_point + } +} + +impl PlatformAtlas for BladeAtlas { + fn get_or_insert_with<'a>( + &self, + key: &AtlasKey, + build: &mut dyn FnMut() -> Result<(Size, Cow<'a, [u8]>)>, + ) -> Result { + let mut lock = self.0.lock(); + if let Some(tile) = lock.tiles_by_key.get(key) { + Ok(tile.clone()) + } else { + let (size, bytes) = build()?; + let tile = lock.allocate(size, key.texture_kind()); + lock.upload_texture(tile.texture_id, tile.bounds, &bytes); + lock.tiles_by_key.insert(key.clone(), tile.clone()); + Ok(tile) + } + } +} + +impl BladeAtlasState { + fn allocate(&mut self, size: Size, texture_kind: AtlasTextureKind) -> AtlasTile { + let textures = match texture_kind { + AtlasTextureKind::Monochrome => &mut self.monochrome_textures, + AtlasTextureKind::Polychrome => &mut self.polychrome_textures, + AtlasTextureKind::Path => &mut self.path_textures, + }; + textures + .iter_mut() + .rev() + .find_map(|texture| texture.allocate(size)) + .unwrap_or_else(|| { + let texture = self.push_texture(size, texture_kind); + texture.allocate(size).unwrap() + }) + } + + fn push_texture( + &mut self, + min_size: Size, + kind: AtlasTextureKind, + ) -> &mut BladeAtlasTexture { + const DEFAULT_ATLAS_SIZE: Size = Size { + width: DevicePixels(1024), + height: DevicePixels(1024), + }; + + let size = min_size.max(&DEFAULT_ATLAS_SIZE); + let format; + let usage; + match kind { + AtlasTextureKind::Monochrome => { + format = blade::TextureFormat::R8Unorm; + usage = blade::TextureUsage::COPY | blade::TextureUsage::RESOURCE; + } + AtlasTextureKind::Polychrome => { + format = blade::TextureFormat::Bgra8Unorm; + usage = blade::TextureUsage::COPY | blade::TextureUsage::RESOURCE; + } + AtlasTextureKind::Path => { + format = blade::TextureFormat::R16Float; + usage = blade::TextureUsage::COPY + | blade::TextureUsage::RESOURCE + | blade::TextureUsage::TARGET; + } + } + + let raw = self.gpu.create_texture(blade::TextureDesc { + name: "", + format, + size: blade::Extent { + width: size.width.into(), + height: size.height.into(), + depth: 1, + }, + array_layer_count: 1, + mip_level_count: 1, + dimension: blade::TextureDimension::D2, + usage, + }); + + let textures = match kind { + AtlasTextureKind::Monochrome => &mut self.monochrome_textures, + AtlasTextureKind::Polychrome => &mut self.polychrome_textures, + AtlasTextureKind::Path => &mut self.path_textures, + }; + let atlas_texture = BladeAtlasTexture { + id: AtlasTextureId { + index: textures.len() as u32, + kind, + }, + allocator: etagere::BucketedAtlasAllocator::new(size.into()), + format, + raw, + }; + textures.push(atlas_texture); + textures.last_mut().unwrap() + } + + fn upload_texture(&mut self, id: AtlasTextureId, bounds: Bounds, bytes: &[u8]) { + let textures = match id.kind { + crate::AtlasTextureKind::Monochrome => &self.monochrome_textures, + crate::AtlasTextureKind::Polychrome => &self.polychrome_textures, + crate::AtlasTextureKind::Path => &self.path_textures, + }; + let texture = &textures[id.index as usize]; + + let src_data = self.upload_belt.alloc_data(bytes, &self.gpu); + + let mut transfers = self.gpu_encoder.transfer(); + transfers.copy_buffer_to_texture( + src_data, + bounds.size.width.to_bytes(texture.bytes_per_pixel()), + blade::TexturePiece { + texture: texture.raw, + mip_level: 0, + array_layer: 0, + origin: [bounds.origin.x.into(), bounds.origin.y.into(), 0], + }, + blade::Extent { + width: bounds.size.width.into(), + height: bounds.size.height.into(), + depth: 1, + }, + ); + } +} + +struct BladeAtlasTexture { + id: AtlasTextureId, + allocator: BucketedAtlasAllocator, + raw: blade::Texture, + format: blade::TextureFormat, +} + +impl BladeAtlasTexture { + fn clear(&mut self) { + self.allocator.clear(); + } + + fn allocate(&mut self, size: Size) -> Option { + let allocation = self.allocator.allocate(size.into())?; + let tile = AtlasTile { + texture_id: self.id, + tile_id: allocation.id.into(), + bounds: Bounds { + origin: allocation.rectangle.min.into(), + size, + }, + }; + Some(tile) + } + + fn bytes_per_pixel(&self) -> u8 { + self.format.block_info().size + } +} + +impl From> for etagere::Size { + fn from(size: Size) -> Self { + etagere::Size::new(size.width.into(), size.height.into()) + } +} + +impl From for Point { + fn from(value: etagere::Point) -> Self { + Point { + x: DevicePixels::from(value.x), + y: DevicePixels::from(value.y), + } + } +} + +impl From for Size { + fn from(size: etagere::Size) -> Self { + Size { + width: DevicePixels::from(size.width), + height: DevicePixels::from(size.height), + } + } +} + +impl From for Bounds { + fn from(rectangle: etagere::Rectangle) -> Self { + Bounds { + origin: rectangle.min.into(), + size: rectangle.size().into(), + } + } +} diff --git a/crates/gpui/src/platform/linux/blade_belt.rs b/crates/gpui/src/platform/linux/blade_belt.rs new file mode 100644 index 0000000000..ff3d5c6692 --- /dev/null +++ b/crates/gpui/src/platform/linux/blade_belt.rs @@ -0,0 +1,84 @@ +struct ReusableBuffer { + raw: blade::Buffer, + size: u64, +} + +pub struct BladeBeltDescriptor { + pub memory: blade::Memory, + pub min_chunk_size: u64, +} + +/// A belt of buffers, used by the BladeAtlas to cheaply +/// find staging space for uploads. +pub struct BladeBelt { + desc: BladeBeltDescriptor, + buffers: Vec<(ReusableBuffer, blade::SyncPoint)>, + active: Vec<(ReusableBuffer, u64)>, +} + +impl BladeBelt { + pub fn new(desc: BladeBeltDescriptor) -> Self { + Self { + desc, + buffers: Vec::new(), + active: Vec::new(), + } + } + + pub fn destroy(&mut self, gpu: &blade::Context) { + for (buffer, _) in self.buffers.drain(..) { + gpu.destroy_buffer(buffer.raw); + } + for (buffer, _) in self.active.drain(..) { + gpu.destroy_buffer(buffer.raw); + } + } + + pub fn alloc(&mut self, size: u64, gpu: &blade::Context) -> blade::BufferPiece { + for &mut (ref rb, ref mut offset) in self.active.iter_mut() { + if *offset + size <= rb.size { + let piece = rb.raw.at(*offset); + *offset += size; + return piece; + } + } + + let index_maybe = self + .buffers + .iter() + .position(|&(ref rb, ref sp)| size <= rb.size && gpu.wait_for(sp, 0)); + if let Some(index) = index_maybe { + let (rb, _) = self.buffers.remove(index); + let piece = rb.raw.into(); + self.active.push((rb, size)); + return piece; + } + + 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 { + name: &format!("chunk-{}", chunk_index), + size: chunk_size, + memory: self.desc.memory, + }); + let rb = ReusableBuffer { + raw: chunk, + size: chunk_size, + }; + self.active.push((rb, size)); + chunk.into() + } + + pub fn alloc_data(&mut self, data: &[u8], gpu: &blade::Context) -> blade::BufferPiece { + let bp = self.alloc(data.len() as u64, gpu); + unsafe { + std::ptr::copy_nonoverlapping(data.as_ptr(), bp.data(), data.len()); + } + bp + } + + pub fn flush(&mut self, sp: &blade::SyncPoint) { + self.buffers + .extend(self.active.drain(..).map(|(rb, _)| (rb, sp.clone()))); + } +} diff --git a/crates/gpui/src/platform/linux/display.rs b/crates/gpui/src/platform/linux/display.rs new file mode 100644 index 0000000000..507edad3b0 --- /dev/null +++ b/crates/gpui/src/platform/linux/display.rs @@ -0,0 +1,23 @@ +use crate::{point, size, Bounds, DisplayId, GlobalPixels, PlatformDisplay}; +use anyhow::Result; +use uuid::Uuid; + +#[derive(Debug)] +pub(crate) struct LinuxDisplay; + +impl PlatformDisplay for LinuxDisplay { + fn id(&self) -> DisplayId { + DisplayId(0) + } + + fn uuid(&self) -> Result { + Ok(Uuid::from_bytes([0; 16])) + } + + fn bounds(&self) -> Bounds { + Bounds { + origin: point(GlobalPixels(0.0), GlobalPixels(0.0)), + size: size(GlobalPixels(100.0), GlobalPixels(100.0)), + } + } +} diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs index 327ec59d16..8136b77292 100644 --- a/crates/gpui/src/platform/linux/platform.rs +++ b/crates/gpui/src/platform/linux/platform.rs @@ -2,9 +2,9 @@ use crate::{ Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, - ForegroundExecutor, Keymap, LinuxDispatcher, LinuxTextSystem, Menu, PathPromptOptions, - Platform, PlatformDisplay, PlatformInput, PlatformTextSystem, PlatformWindow, Result, - SemanticVersion, Task, WindowOptions, + ForegroundExecutor, Keymap, LinuxDispatcher, LinuxDisplay, LinuxTextSystem, LinuxWindow, Menu, + PathPromptOptions, Platform, PlatformDisplay, PlatformInput, PlatformTextSystem, + PlatformWindow, Result, SemanticVersion, Task, WindowOptions, }; use futures::channel::oneshot; @@ -21,6 +21,7 @@ use time::UtcOffset; pub(crate) struct LinuxPlatform(Mutex); pub(crate) struct LinuxPlatformState { + gpu: Arc, background_executor: BackgroundExecutor, foreground_executor: ForegroundExecutor, text_system: Arc, @@ -35,7 +36,17 @@ impl Default for LinuxPlatform { impl LinuxPlatform { pub(crate) fn new() -> Self { let dispatcher = Arc::new(LinuxDispatcher::new()); + let gpu = Arc::new( + unsafe { + blade::Context::init(blade::ContextDesc { + validation: true, //FIXME + capture: false, + }) + } + .unwrap(), + ); Self(Mutex::new(LinuxPlatformState { + gpu, background_executor: BackgroundExecutor::new(dispatcher.clone()), foreground_executor: ForegroundExecutor::new(dispatcher), text_system: Arc::new(LinuxTextSystem::new()), @@ -57,43 +68,31 @@ impl Platform for LinuxPlatform { } fn run(&self, on_finish_launching: Box) { - unimplemented!() + on_finish_launching() } - fn quit(&self) { - unimplemented!() - } + fn quit(&self) {} - fn restart(&self) { - unimplemented!() - } + fn restart(&self) {} - fn activate(&self, ignoring_other_apps: bool) { - unimplemented!() - } + fn activate(&self, ignoring_other_apps: bool) {} - fn hide(&self) { - unimplemented!() - } + fn hide(&self) {} - fn hide_other_apps(&self) { - unimplemented!() - } + fn hide_other_apps(&self) {} - fn unhide_other_apps(&self) { - unimplemented!() - } + fn unhide_other_apps(&self) {} fn displays(&self) -> Vec> { - unimplemented!() + Vec::new() } fn display(&self, id: DisplayId) -> Option> { - unimplemented!() + None } fn active_window(&self) -> Option { - unimplemented!() + None } fn open_window( @@ -101,7 +100,13 @@ impl Platform for LinuxPlatform { handle: AnyWindowHandle, options: WindowOptions, ) -> Box { - unimplemented!() + let lock = self.0.lock(); + Box::new(LinuxWindow::new( + options, + handle, + Rc::new(LinuxDisplay), + &lock.gpu, + )) } fn set_display_link_output_callback( @@ -112,21 +117,13 @@ impl Platform for LinuxPlatform { unimplemented!() } - fn start_display_link(&self, display_id: DisplayId) { - unimplemented!() - } + fn start_display_link(&self, display_id: DisplayId) {} - fn stop_display_link(&self, display_id: DisplayId) { - unimplemented!() - } + fn stop_display_link(&self, display_id: DisplayId) {} - fn open_url(&self, url: &str) { - unimplemented!() - } + fn open_url(&self, url: &str) {} - fn on_open_urls(&self, callback: Box)>) { - unimplemented!() - } + fn on_open_urls(&self, callback: Box)>) {} fn prompt_for_paths( &self, @@ -139,88 +136,72 @@ impl Platform for LinuxPlatform { unimplemented!() } - fn reveal_path(&self, path: &Path) { - unimplemented!() - } + fn reveal_path(&self, path: &Path) {} - fn on_become_active(&self, callback: Box) { - unimplemented!() - } + fn on_become_active(&self, callback: Box) {} - fn on_resign_active(&self, callback: Box) { - unimplemented!() - } + fn on_resign_active(&self, callback: Box) {} - fn on_quit(&self, callback: Box) { - unimplemented!() - } + fn on_quit(&self, callback: Box) {} - fn on_reopen(&self, callback: Box) { - unimplemented!() - } + fn on_reopen(&self, callback: Box) {} - fn on_event(&self, callback: Box bool>) { - unimplemented!() - } + fn on_event(&self, callback: Box bool>) {} - fn on_app_menu_action(&self, callback: Box) { - unimplemented!() - } + fn on_app_menu_action(&self, callback: Box) {} - fn on_will_open_app_menu(&self, callback: Box) { - unimplemented!() - } + fn on_will_open_app_menu(&self, callback: Box) {} - fn on_validate_app_menu_command(&self, callback: Box bool>) { - unimplemented!() - } + fn on_validate_app_menu_command(&self, callback: Box bool>) {} fn os_name(&self) -> &'static str { "Linux" } fn double_click_interval(&self) -> Duration { - unimplemented!() + Duration::default() } fn os_version(&self) -> Result { - unimplemented!() + Ok(SemanticVersion { + major: 1, + minor: 0, + patch: 0, + }) } fn app_version(&self) -> Result { - unimplemented!() + Ok(SemanticVersion { + major: 1, + minor: 0, + patch: 0, + }) } fn app_path(&self) -> Result { unimplemented!() } - fn set_menus(&self, menus: Vec, keymap: &Keymap) { - unimplemented!() - } + fn set_menus(&self, menus: Vec, keymap: &Keymap) {} fn local_timezone(&self) -> UtcOffset { - unimplemented!() + UtcOffset::UTC } fn path_for_auxiliary_executable(&self, name: &str) -> Result { unimplemented!() } - fn set_cursor_style(&self, style: CursorStyle) { - unimplemented!() - } + fn set_cursor_style(&self, style: CursorStyle) {} fn should_auto_hide_scrollbars(&self) -> bool { - unimplemented!() + false } - fn write_to_clipboard(&self, item: ClipboardItem) { - unimplemented!() - } + fn write_to_clipboard(&self, item: ClipboardItem) {} fn read_from_clipboard(&self) -> Option { - unimplemented!() + None } fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task> { diff --git a/crates/gpui/src/platform/linux/text_system.rs b/crates/gpui/src/platform/linux/text_system.rs index 30f65c0ece..09913b7077 100644 --- a/crates/gpui/src/platform/linux/text_system.rs +++ b/crates/gpui/src/platform/linux/text_system.rs @@ -55,13 +55,13 @@ impl Default for LinuxTextSystem { #[allow(unused)] impl PlatformTextSystem for LinuxTextSystem { fn add_fonts(&self, fonts: &[Arc>]) -> Result<()> { - unimplemented!() + Ok(()) //TODO } fn all_font_names(&self) -> Vec { - unimplemented!() + Vec::new() } fn all_font_families(&self) -> Vec { - unimplemented!() + Vec::new() } fn font_id(&self, descriptor: &Font) -> Result { unimplemented!() diff --git a/crates/gpui/src/platform/linux/window.rs b/crates/gpui/src/platform/linux/window.rs new file mode 100644 index 0000000000..72155dacab --- /dev/null +++ b/crates/gpui/src/platform/linux/window.rs @@ -0,0 +1,143 @@ +use crate::{ + px, AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, BladeAtlas, Bounds, KeyDownEvent, + Keystroke, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, + PlatformWindow, Point, Size, TileId, WindowAppearance, WindowBounds, WindowOptions, +}; +use collections::HashMap; +use parking_lot::Mutex; +use std::{ + rc::{Rc, Weak}, + sync::{self, Arc}, +}; + +pub(crate) struct LinuxWindowState { + display: Rc, + sprite_atlas: Arc, +} + +#[derive(Clone)] +pub(crate) struct LinuxWindow(pub(crate) Arc>); + +impl LinuxWindow { + pub fn new( + options: WindowOptions, + handle: AnyWindowHandle, + display: Rc, + gpu: &Arc, + ) -> Self { + Self(Arc::new(Mutex::new(LinuxWindowState { + display, + sprite_atlas: Arc::new(BladeAtlas::new(gpu)), + }))) + } +} + +impl PlatformWindow for LinuxWindow { + fn bounds(&self) -> WindowBounds { + unimplemented!() + } + + fn content_size(&self) -> Size { + unimplemented!() + } + + fn scale_factor(&self) -> f32 { + 1.0 + } + + fn titlebar_height(&self) -> Pixels { + unimplemented!() + } + + fn appearance(&self) -> WindowAppearance { + unimplemented!() + } + + fn display(&self) -> Rc { + Rc::clone(&self.0.lock().display) + } + + fn mouse_position(&self) -> Point { + Point::default() + } + + fn modifiers(&self) -> crate::Modifiers { + crate::Modifiers::default() + } + + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self + } + + fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {} + + fn take_input_handler(&mut self) -> Option { + None + } + + fn prompt( + &self, + _level: crate::PromptLevel, + _msg: &str, + _detail: Option<&str>, + _answers: &[&str], + ) -> futures::channel::oneshot::Receiver { + unimplemented!() + } + + fn activate(&self) {} + + fn set_title(&mut self, title: &str) {} + + fn set_edited(&mut self, edited: bool) {} + + fn show_character_palette(&self) { + unimplemented!() + } + + fn minimize(&self) { + unimplemented!() + } + + fn zoom(&self) { + unimplemented!() + } + + fn toggle_full_screen(&self) { + unimplemented!() + } + + fn on_request_frame(&self, _callback: Box) {} + + fn on_input(&self, callback: Box bool>) {} + + fn on_active_status_change(&self, callback: Box) {} + + fn on_resize(&self, callback: Box, f32)>) {} + + fn on_fullscreen(&self, _callback: Box) {} + + fn on_moved(&self, callback: Box) {} + + fn on_should_close(&self, callback: Box bool>) {} + + fn on_close(&self, _callback: Box) { + unimplemented!() + } + + fn on_appearance_changed(&self, _callback: Box) { + unimplemented!() + } + + fn is_topmost_for_position(&self, _position: crate::Point) -> bool { + unimplemented!() + } + + fn invalidate(&self) {} + + fn draw(&self, _scene: &crate::Scene) {} + + fn sprite_atlas(&self) -> sync::Arc { + self.0.lock().sprite_atlas.clone() + } +}