linux: basic window, display, and atlas

Dzmitry Malyshau created

Change summary

crates/gpui/Cargo.toml                        |   2 
crates/gpui/src/platform/linux.rs             |   9 
crates/gpui/src/platform/linux/blade_atlas.rs | 258 +++++++++++++++++++++
crates/gpui/src/platform/linux/blade_belt.rs  |  84 ++++++
crates/gpui/src/platform/linux/display.rs     |  23 +
crates/gpui/src/platform/linux/platform.rs    | 141 ++++------
crates/gpui/src/platform/linux/text_system.rs |   6 
crates/gpui/src/platform/linux/window.rs      | 143 +++++++++++
8 files changed, 582 insertions(+), 84 deletions(-)

Detailed changes

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

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::*;

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<BladeAtlasState>);
+
+struct BladeAtlasState {
+    gpu: Arc<blade::Context>,
+    gpu_encoder: blade::CommandEncoder,
+    upload_belt: BladeBelt,
+    monochrome_textures: Vec<BladeAtlasTexture>,
+    polychrome_textures: Vec<BladeAtlasTexture>,
+    path_textures: Vec<BladeAtlasTexture>,
+    tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
+}
+
+impl BladeAtlas {
+    pub(crate) fn new(gpu: &Arc<blade::Context>) -> 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<DevicePixels>, Cow<'a, [u8]>)>,
+    ) -> Result<AtlasTile> {
+        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<DevicePixels>, 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<DevicePixels>,
+        kind: AtlasTextureKind,
+    ) -> &mut BladeAtlasTexture {
+        const DEFAULT_ATLAS_SIZE: Size<DevicePixels> = 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<DevicePixels>, 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<DevicePixels>) -> Option<AtlasTile> {
+        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<Size<DevicePixels>> for etagere::Size {
+    fn from(size: Size<DevicePixels>) -> Self {
+        etagere::Size::new(size.width.into(), size.height.into())
+    }
+}
+
+impl From<etagere::Point> for Point<DevicePixels> {
+    fn from(value: etagere::Point) -> Self {
+        Point {
+            x: DevicePixels::from(value.x),
+            y: DevicePixels::from(value.y),
+        }
+    }
+}
+
+impl From<etagere::Size> for Size<DevicePixels> {
+    fn from(size: etagere::Size) -> Self {
+        Size {
+            width: DevicePixels::from(size.width),
+            height: DevicePixels::from(size.height),
+        }
+    }
+}
+
+impl From<etagere::Rectangle> for Bounds<DevicePixels> {
+    fn from(rectangle: etagere::Rectangle) -> Self {
+        Bounds {
+            origin: rectangle.min.into(),
+            size: rectangle.size().into(),
+        }
+    }
+}

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())));
+    }
+}

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<Uuid> {
+        Ok(Uuid::from_bytes([0; 16]))
+    }
+
+    fn bounds(&self) -> Bounds<GlobalPixels> {
+        Bounds {
+            origin: point(GlobalPixels(0.0), GlobalPixels(0.0)),
+            size: size(GlobalPixels(100.0), GlobalPixels(100.0)),
+        }
+    }
+}

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<LinuxPlatformState>);
 
 pub(crate) struct LinuxPlatformState {
+    gpu: Arc<blade::Context>,
     background_executor: BackgroundExecutor,
     foreground_executor: ForegroundExecutor,
     text_system: Arc<LinuxTextSystem>,
@@ -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<dyn FnOnce()>) {
-        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<Rc<dyn PlatformDisplay>> {
-        unimplemented!()
+        Vec::new()
     }
 
     fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
-        unimplemented!()
+        None
     }
 
     fn active_window(&self) -> Option<AnyWindowHandle> {
-        unimplemented!()
+        None
     }
 
     fn open_window(
@@ -101,7 +100,13 @@ impl Platform for LinuxPlatform {
         handle: AnyWindowHandle,
         options: WindowOptions,
     ) -> Box<dyn PlatformWindow> {
-        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<dyn FnMut(Vec<String>)>) {
-        unimplemented!()
-    }
+    fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {}
 
     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<dyn FnMut()>) {
-        unimplemented!()
-    }
+    fn on_become_active(&self, callback: Box<dyn FnMut()>) {}
 
-    fn on_resign_active(&self, callback: Box<dyn FnMut()>) {
-        unimplemented!()
-    }
+    fn on_resign_active(&self, callback: Box<dyn FnMut()>) {}
 
-    fn on_quit(&self, callback: Box<dyn FnMut()>) {
-        unimplemented!()
-    }
+    fn on_quit(&self, callback: Box<dyn FnMut()>) {}
 
-    fn on_reopen(&self, callback: Box<dyn FnMut()>) {
-        unimplemented!()
-    }
+    fn on_reopen(&self, callback: Box<dyn FnMut()>) {}
 
-    fn on_event(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>) {
-        unimplemented!()
-    }
+    fn on_event(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>) {}
 
-    fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>) {
-        unimplemented!()
-    }
+    fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>) {}
 
-    fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>) {
-        unimplemented!()
-    }
+    fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>) {}
 
-    fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
-        unimplemented!()
-    }
+    fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {}
 
     fn os_name(&self) -> &'static str {
         "Linux"
     }
 
     fn double_click_interval(&self) -> Duration {
-        unimplemented!()
+        Duration::default()
     }
 
     fn os_version(&self) -> Result<SemanticVersion> {
-        unimplemented!()
+        Ok(SemanticVersion {
+            major: 1,
+            minor: 0,
+            patch: 0,
+        })
     }
 
     fn app_version(&self) -> Result<SemanticVersion> {
-        unimplemented!()
+        Ok(SemanticVersion {
+            major: 1,
+            minor: 0,
+            patch: 0,
+        })
     }
 
     fn app_path(&self) -> Result<PathBuf> {
         unimplemented!()
     }
 
-    fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap) {
-        unimplemented!()
-    }
+    fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap) {}
 
     fn local_timezone(&self) -> UtcOffset {
-        unimplemented!()
+        UtcOffset::UTC
     }
 
     fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
         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<ClipboardItem> {
-        unimplemented!()
+        None
     }
 
     fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>> {

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<Vec<u8>>]) -> Result<()> {
-        unimplemented!()
+        Ok(()) //TODO
     }
     fn all_font_names(&self) -> Vec<String> {
-        unimplemented!()
+        Vec::new()
     }
     fn all_font_families(&self) -> Vec<String> {
-        unimplemented!()
+        Vec::new()
     }
     fn font_id(&self, descriptor: &Font) -> Result<FontId> {
         unimplemented!()

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<dyn crate::PlatformDisplay>,
+    sprite_atlas: Arc<BladeAtlas>,
+}
+
+#[derive(Clone)]
+pub(crate) struct LinuxWindow(pub(crate) Arc<Mutex<LinuxWindowState>>);
+
+impl LinuxWindow {
+    pub fn new(
+        options: WindowOptions,
+        handle: AnyWindowHandle,
+        display: Rc<dyn PlatformDisplay>,
+        gpu: &Arc<blade::Context>,
+    ) -> 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<Pixels> {
+        unimplemented!()
+    }
+
+    fn scale_factor(&self) -> f32 {
+        1.0
+    }
+
+    fn titlebar_height(&self) -> Pixels {
+        unimplemented!()
+    }
+
+    fn appearance(&self) -> WindowAppearance {
+        unimplemented!()
+    }
+
+    fn display(&self) -> Rc<dyn crate::PlatformDisplay> {
+        Rc::clone(&self.0.lock().display)
+    }
+
+    fn mouse_position(&self) -> Point<Pixels> {
+        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<PlatformInputHandler> {
+        None
+    }
+
+    fn prompt(
+        &self,
+        _level: crate::PromptLevel,
+        _msg: &str,
+        _detail: Option<&str>,
+        _answers: &[&str],
+    ) -> futures::channel::oneshot::Receiver<usize> {
+        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<dyn FnMut()>) {}
+
+    fn on_input(&self, callback: Box<dyn FnMut(crate::PlatformInput) -> bool>) {}
+
+    fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {}
+
+    fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {}
+
+    fn on_fullscreen(&self, _callback: Box<dyn FnMut(bool)>) {}
+
+    fn on_moved(&self, callback: Box<dyn FnMut()>) {}
+
+    fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {}
+
+    fn on_close(&self, _callback: Box<dyn FnOnce()>) {
+        unimplemented!()
+    }
+
+    fn on_appearance_changed(&self, _callback: Box<dyn FnMut()>) {
+        unimplemented!()
+    }
+
+    fn is_topmost_for_position(&self, _position: crate::Point<Pixels>) -> bool {
+        unimplemented!()
+    }
+
+    fn invalidate(&self) {}
+
+    fn draw(&self, _scene: &crate::Scene) {}
+
+    fn sprite_atlas(&self) -> sync::Arc<dyn crate::PlatformAtlas> {
+        self.0.lock().sprite_atlas.clone()
+    }
+}