linux: hook up render event, basic renderer command buffer

Dzmitry Malyshau created

Change summary

crates/gpui/src/platform/linux/blade_renderer.rs | 51 ++++++++++++
crates/gpui/src/platform/linux/platform.rs       | 70 +++++++++--------
crates/gpui/src/platform/linux/window.rs         | 60 +++++++++++----
3 files changed, 129 insertions(+), 52 deletions(-)

Detailed changes

crates/gpui/src/platform/linux/blade_renderer.rs 🔗

@@ -1,11 +1,58 @@
+use crate::Scene;
+
 use std::sync::Arc;
 
+const SURFACE_FRAME_COUNT: u32 = 3;
+const MAX_FRAME_TIME_MS: u32 = 1000;
+
 pub struct BladeRenderer {
     gpu: Arc<blade::Context>,
+    command_encoder: blade::CommandEncoder,
+    last_sync_point: Option<blade::SyncPoint>,
 }
 
 impl BladeRenderer {
-    pub fn new(gpu: Arc<blade::Context>) -> Self {
-        Self { gpu }
+    pub fn new(gpu: Arc<blade::Context>, size: blade::Extent) -> Self {
+        let _surface_format = gpu.resize(blade::SurfaceConfig {
+            size,
+            usage: blade::TextureUsage::TARGET,
+            frame_count: SURFACE_FRAME_COUNT,
+        });
+        let command_encoder = gpu.create_command_encoder(blade::CommandEncoderDesc {
+            name: "main",
+            buffer_count: 2,
+        });
+        Self {
+            gpu,
+            command_encoder,
+            last_sync_point: None,
+        }
+    }
+
+    pub fn destroy(&mut self) {
+        self.gpu.destroy_command_encoder(&mut self.command_encoder);
+    }
+
+    pub fn resize(&mut self, size: blade::Extent) {
+        self.gpu.resize(blade::SurfaceConfig {
+            size,
+            usage: blade::TextureUsage::TARGET,
+            frame_count: SURFACE_FRAME_COUNT,
+        });
+    }
+
+    pub fn draw(&mut self, scene: &Scene) {
+        let frame = self.gpu.acquire_frame();
+        self.command_encoder.start();
+
+        self.command_encoder.present(frame);
+
+        let sync_point = self.gpu.submit(&mut self.command_encoder);
+        if let Some(ref last_sp) = self.last_sync_point {
+            if !self.gpu.wait_for(last_sp, MAX_FRAME_TIME_MS) {
+                panic!("GPU hung");
+            }
+        }
+        self.last_sync_point = Some(sync_point);
     }
 }

crates/gpui/src/platform/linux/platform.rs 🔗

@@ -85,44 +85,46 @@ impl Platform for LinuxPlatform {
     fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
         on_finish_launching();
 
-        let mut need_repaint = HashSet::<x::Window>::default();
-
         while !self.0.lock().windows.is_empty() {
             let event = self.0.lock().xcb_connection.wait_for_event().unwrap();
+            let mut repaint_x_window = None;
             match event {
                 xcb::Event::X(x::Event::ClientMessage(ev)) => {
                     if let x::ClientMessageData::Data32([atom, ..]) = ev.data() {
-                        let mut lock = self.0.lock();
-                        if atom == lock.atoms.wm_del_window.resource_id() {
+                        let mut this = self.0.lock();
+                        if atom == this.atoms.wm_del_window.resource_id() {
                             // window "x" button clicked by user, we gracefully exit
                             {
-                                let mut window = lock.windows[&ev.window()].lock();
+                                let mut window = this.windows[&ev.window()].lock();
                                 window.destroy();
                             }
-                            lock.windows.remove(&ev.window());
+                            this.xcb_connection.send_request(&x::UnmapWindow {
+                                window: ev.window(),
+                            });
+                            this.xcb_connection.send_request(&x::DestroyWindow {
+                                window: ev.window(),
+                            });
+                            this.windows.remove(&ev.window());
                             break;
                         }
                     }
                 }
-                _ => {} /*
-                        Event::Expose(event) => {
-                            if event.count == 0 {
-                                need_repaint.insert(event.window);
-                            }
-                        }
-                        Event::ConfigureNotify(event) => {
-                            let lock = self.0.lock();
-                            let mut window = lock.windows[&event.window].lock();
-                            window.resize(event.width, event.height);
-                        }
-                        _ => {}*/
+                xcb::Event::X(x::Event::Expose(ev)) => {
+                    repaint_x_window = Some(ev.window());
+                }
+                xcb::Event::X(x::Event::ResizeRequest(ev)) => {
+                    let this = self.0.lock();
+                    let mut window = this.windows[&ev.window()].lock();
+                    window.resize(ev.width(), ev.height());
+                }
+                _ => {}
             }
 
-            for x_window in need_repaint.drain() {
-                let lock = self.0.lock();
-                let mut window = lock.windows[&x_window].lock();
-                window.paint();
-                lock.xcb_connection.flush();
+            if let Some(x_window) = repaint_x_window {
+                let this = self.0.lock();
+                let mut window = this.windows[&x_window].lock();
+                window.request_frame();
+                this.xcb_connection.flush();
             }
         }
     }
@@ -140,22 +142,22 @@ impl Platform for LinuxPlatform {
     fn unhide_other_apps(&self) {}
 
     fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
-        let lock = self.0.lock();
-        let setup = lock.xcb_connection.get_setup();
+        let this = self.0.lock();
+        let setup = this.xcb_connection.get_setup();
         setup
             .roots()
             .enumerate()
             .map(|(root_id, _)| {
-                Rc::new(LinuxDisplay::new(&lock.xcb_connection, root_id as i32))
+                Rc::new(LinuxDisplay::new(&this.xcb_connection, root_id as i32))
                     as Rc<dyn PlatformDisplay>
             })
             .collect()
     }
 
     fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
-        let lock = self.0.lock();
+        let this = self.0.lock();
         Some(Rc::new(LinuxDisplay::new(
-            &lock.xcb_connection,
+            &this.xcb_connection,
             id.0 as i32,
         )))
     }
@@ -169,18 +171,18 @@ impl Platform for LinuxPlatform {
         handle: AnyWindowHandle,
         options: WindowOptions,
     ) -> Box<dyn PlatformWindow> {
-        let mut lock = self.0.lock();
-        let x_window = lock.xcb_connection.generate_id();
+        let mut this = self.0.lock();
+        let x_window = this.xcb_connection.generate_id();
 
         let window_ptr = LinuxWindowState::new_ptr(
             options,
             handle,
-            &lock.xcb_connection,
-            lock.x_root_index,
+            &this.xcb_connection,
+            this.x_root_index,
             x_window,
-            &lock.atoms,
+            &this.atoms,
         );
-        lock.windows.insert(x_window, window_ptr.clone());
+        this.windows.insert(x_window, window_ptr.clone());
         Box::new(LinuxWindow(window_ptr))
     }
 

crates/gpui/src/platform/linux/window.rs 🔗

@@ -11,6 +11,12 @@ use std::{
 };
 use xcb::{x, Xid as _};
 
+#[derive(Default)]
+struct Callbacks {
+    request_frame: Option<Box<dyn FnMut()>>,
+    resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
+}
+
 pub(crate) struct LinuxWindowState {
     display: Rc<dyn PlatformDisplay>,
     x_window: x::Window,
@@ -18,6 +24,7 @@ pub(crate) struct LinuxWindowState {
     content_size: Size<Pixels>,
     sprite_atlas: Arc<BladeAtlas>,
     renderer: BladeRenderer,
+    callbacks: Callbacks,
 }
 
 pub(crate) type LinuxWindowStatePtr = Arc<Mutex<LinuxWindowState>>;
@@ -65,7 +72,9 @@ impl LinuxWindowState {
 
         let xcb_values = [
             x::Cw::BackPixel(screen.white_pixel()),
-            x::Cw::EventMask(x::EventMask::EXPOSURE | x::EventMask::KEY_PRESS),
+            x::Cw::EventMask(
+                x::EventMask::EXPOSURE | x::EventMask::RESIZE_REDIRECT | x::EventMask::KEY_PRESS,
+            ),
         ];
 
         let (bound_x, bound_y, bound_width, bound_height) = match options.bounds {
@@ -137,6 +146,11 @@ impl LinuxWindowState {
             }
             .unwrap(),
         );
+        let gpu_extent = blade::Extent {
+            width: bound_width as u32,
+            height: bound_height as u32,
+            depth: 1,
+        };
 
         Arc::new(Mutex::new(Self {
             display: Rc::new(LinuxDisplay::new(xcb_connection, x_screen_index)),
@@ -147,7 +161,8 @@ impl LinuxWindowState {
                 height: Pixels(bound_height as f32),
             },
             sprite_atlas: Arc::new(BladeAtlas::new(&gpu)),
-            renderer: BladeRenderer::new(gpu),
+            renderer: BladeRenderer::new(gpu, gpu_extent),
+            callbacks: Callbacks::default(),
         }))
     }
 
@@ -156,14 +171,25 @@ impl LinuxWindowState {
             width: Pixels(width as f32),
             height: Pixels(height as f32),
         };
+        self.renderer.resize(blade::Extent {
+            width: width as u32,
+            height: height as u32,
+            depth: 1,
+        });
+        if let Some(ref mut fun) = self.callbacks.resize {
+            fun(self.content_size, 1.0);
+        }
     }
 
-    pub fn destroy(&mut self) {
-        self.sprite_atlas.destroy();
+    pub fn request_frame(&mut self) {
+        if let Some(ref mut fun) = self.callbacks.request_frame {
+            fun();
+        }
     }
 
-    pub fn paint(&mut self) {
-        //TODO
+    pub fn destroy(&mut self) {
+        self.sprite_atlas.destroy();
+        self.renderer.destroy();
     }
 }
 
@@ -243,27 +269,27 @@ impl PlatformWindow for LinuxWindow {
         unimplemented!()
     }
 
-    fn on_request_frame(&self, _callback: Box<dyn FnMut()>) {}
+    fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
+        self.0.lock().callbacks.request_frame = Some(callback);
+    }
 
     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_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
+        self.0.lock().callbacks.resize = Some(callback);
+    }
 
     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_should_close(&self, _callback: Box<dyn FnMut() -> bool>) {}
 
-    fn on_close(&self, _callback: Box<dyn FnOnce()>) {
-        unimplemented!()
-    }
+    fn on_close(&self, _callback: Box<dyn FnOnce()>) {}
 
-    fn on_appearance_changed(&self, _callback: Box<dyn FnMut()>) {
-        unimplemented!()
-    }
+    fn on_appearance_changed(&self, _callback: Box<dyn FnMut()>) {}
 
     fn is_topmost_for_position(&self, _position: crate::Point<Pixels>) -> bool {
         unimplemented!()
@@ -271,7 +297,9 @@ impl PlatformWindow for LinuxWindow {
 
     fn invalidate(&self) {}
 
-    fn draw(&self, _scene: &crate::Scene) {}
+    fn draw(&self, scene: &crate::Scene) {
+        self.0.lock().renderer.draw(scene);
+    }
 
     fn sprite_atlas(&self) -> sync::Arc<dyn crate::PlatformAtlas> {
         self.0.lock().sprite_atlas.clone()