Checkpoint

Antonio Scandurra and Nathan Sobo created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

crates/gpui3/src/app.rs                 |  24 ++-
crates/gpui3/src/executor.rs            |  17 +
crates/gpui3/src/geometry.rs            |  16 ++
crates/gpui3/src/gpui3.rs               |  12 
crates/gpui3/src/platform.rs            |  30 +++
crates/gpui3/src/platform/mac/window.rs |  18 -
crates/gpui3/src/renderer.rs            | 205 +++++++++++++-------------
crates/gpui3/src/window.rs              |  37 +++-
8 files changed, 215 insertions(+), 144 deletions(-)

Detailed changes

crates/gpui3/src/app.rs 🔗

@@ -72,10 +72,14 @@ impl AppContext {
         &self.text_system
     }
 
-    pub fn with_platform<R: Send + 'static>(
+    pub fn spawn_on_main<F, R>(
         &mut self,
-        f: impl FnOnce(&dyn Platform, &mut Self) -> R + Send + 'static,
-    ) -> impl Future<Output = R> {
+        f: impl FnOnce(&dyn Platform, &mut Self) -> F + Send + 'static,
+    ) -> impl Future<Output = R>
+    where
+        F: Future<Output = R> + Send + 'static,
+        R: Send + 'static,
+    {
         let this = self.this.upgrade().unwrap();
         self.platform.read(move |platform| {
             let cx = &mut *this.lock();
@@ -88,17 +92,21 @@ impl AppContext {
         options: crate::WindowOptions,
         build_root_view: impl FnOnce(&mut WindowContext) -> RootView<S> + Send + 'static,
     ) -> impl Future<Output = WindowHandle<S>> {
-        self.with_platform(move |platform, cx| {
-            let id = cx.windows.insert(None);
-            let handle = WindowHandle::new(id);
+        let id = self.windows.insert(None);
+        let handle = WindowHandle::new(id);
+        let window =
+            self.spawn_on_main(move |platform, _cx| Window::new(handle.into(), options, platform));
 
-            let mut window = Window::new(handle.into(), options, platform);
+        let this = self.this.upgrade().unwrap();
+        async move {
+            let mut window = window.await;
+            let cx = &mut *this.lock();
             let root_view = build_root_view(&mut WindowContext::mutable(cx, &mut window));
             window.root_view.replace(Box::new(root_view));
 
             cx.windows.get_mut(id).unwrap().replace(window);
             handle
-        })
+        }
     }
 
     pub(crate) fn update_window<R>(

crates/gpui3/src/executor.rs 🔗

@@ -19,18 +19,25 @@ use std::{
 
 /// Enqueues the given closure to be run on the application's event loop. Can be
 /// called on any thread.
-pub(crate) fn spawn_on_main<R>(
+pub(crate) fn spawn_on_main<F, R>(
     dispatcher: Arc<dyn PlatformDispatcher>,
-    future: impl Future<Output = R> + Send + 'static,
+    func: impl FnOnce() -> F + Send + 'static,
 ) -> impl Future<Output = R>
 where
+    F: Future<Output = R> + 'static,
     R: Send + 'static,
 {
     let (tx, rx) = oneshot::channel();
     let (runnable, task) = async_task::spawn(
-        async move {
-            let result = future.await;
-            let _ = tx.send(result);
+        {
+            let dispatcher = dispatcher.clone();
+            async move {
+                let future = func();
+                let _ = spawn_on_main_local(dispatcher, async move {
+                    let result = future.await;
+                    let _ = tx.send(result);
+                });
+            }
         },
         move |runnable| dispatcher.run_on_main_thread(runnable),
     );

crates/gpui3/src/geometry.rs 🔗

@@ -235,10 +235,26 @@ impl Edges<Pixels> {
 #[repr(transparent)]
 pub struct Pixels(pub(crate) f32);
 
+#[derive(
+    Clone, Copy, Debug, Default, Add, AddAssign, Sub, SubAssign, Div, PartialEq, PartialOrd,
+)]
+#[repr(transparent)]
+pub struct DevicePixels(pub(crate) u32);
+
+impl From<DevicePixels> for u32 {
+    fn from(device_pixels: DevicePixels) -> Self {
+        device_pixels.0
+    }
+}
+
 impl Pixels {
     pub fn round(&self) -> Self {
         Self(self.0.round())
     }
+
+    pub fn to_device_pixels(&self, scale: f32) -> DevicePixels {
+        DevicePixels((self.0 * scale).ceil() as u32)
+    }
 }
 
 impl Mul<f32> for Pixels {

crates/gpui3/src/gpui3.rs 🔗

@@ -22,11 +22,11 @@ pub use color::*;
 pub use element::*;
 pub use elements::*;
 pub use executor::*;
-use futures::channel::oneshot;
 pub use geometry::*;
 pub use gpui3_macros::*;
 pub use platform::*;
 pub use refineable::*;
+use renderer::*;
 pub use scene::*;
 pub use serde;
 pub use serde_json;
@@ -142,18 +142,20 @@ impl<T: 'static + ?Sized> MainThreadOnly<T> {
         &self.value
     }
 
-    pub(crate) fn read<R>(
+    pub(crate) fn read<R, F>(
         &self,
-        f: impl FnOnce(&T) -> R + Send + 'static,
+        f: impl FnOnce(&T) -> F + Send + 'static,
     ) -> impl Future<Output = R>
     where
+        F: Future<Output = R> + 'static,
         R: Send + 'static,
     {
         let this = self.clone();
-        crate::spawn_on_main(self.dispatcher.clone(), async move {
+        crate::spawn_on_main(self.dispatcher.clone(), || async move {
             // Required so we move `this` instead of this.value. Only `this` is `Send`.
             let this = this;
-            f(&this.value)
+            let result = f(&this.value);
+            result.await
         })
     }
 }

crates/gpui3/src/platform.rs 🔗

@@ -7,12 +7,14 @@ mod test;
 
 use crate::{
     AnyWindowHandle, Bounds, FontFeatures, FontId, FontMetrics, FontStyle, FontWeight, GlyphId,
-    LineLayout, Pixels, Point, Result, RunStyle, SharedString, Size,
+    LineLayout, Pixels, Point, RenderTarget, Result, RunStyle, SharedString, Size,
 };
 use anyhow::anyhow;
 use async_task::Runnable;
 use futures::channel::oneshot;
-use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
+use raw_window_handle::{
+    HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle,
+};
 use seahash::SeaHasher;
 use serde::{Deserialize, Serialize};
 use std::ffi::c_void;
@@ -114,7 +116,10 @@ impl Debug for PlatformScreenHandle {
 
 unsafe impl Send for PlatformScreenHandle {}
 
-pub trait PlatformWindow: HasRawWindowHandle + HasRawDisplayHandle {
+pub trait PlatformWindow {
+    fn raw_window_handle(&self) -> RawWindowHandle;
+    fn raw_display_handle(&self) -> RawDisplayHandle;
+
     fn bounds(&self) -> WindowBounds;
     fn content_size(&self) -> Size<Pixels>;
     fn scale_factor(&self) -> f32;
@@ -148,6 +153,25 @@ pub trait PlatformWindow: HasRawWindowHandle + HasRawDisplayHandle {
     fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool;
 }
 
+unsafe impl<'a> HasRawWindowHandle for &'a dyn PlatformWindow {
+    fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
+        self.raw_window_handle()
+    }
+}
+
+unsafe impl<'a> HasRawDisplayHandle for &'a dyn PlatformWindow {
+    fn raw_display_handle(&self) -> raw_window_handle::RawDisplayHandle {
+        self.raw_display_handle()
+    }
+}
+
+impl<'a> RenderTarget for &'a dyn PlatformWindow {
+    fn content_device_size(&self) -> Size<crate::DevicePixels> {
+        self.content_size()
+            .map(|d| d.to_device_pixels(self.scale_factor()))
+    }
+}
+
 pub trait PlatformDispatcher: Send + Sync {
     fn is_main_thread(&self) -> bool;
     fn run_on_main_thread(&self, task: Runnable);

crates/gpui3/src/platform/mac/window.rs 🔗

@@ -1,9 +1,9 @@
 use crate::{
-    point, px, size, AnyWindowHandle, Bounds, Event, InputHandler, KeyDownEvent, Keystroke,
-    MacScreen, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMovedEvent,
-    MouseUpEvent, NSRectExt, Pixels, Platform, PlatformDispatcher, PlatformScreen, PlatformWindow,
-    Point, Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions,
-    WindowPromptLevel,
+    point, px, size, AnyWindowHandle, Bounds, DevicePixels, Event, InputHandler, KeyDownEvent,
+    Keystroke, MacScreen, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent,
+    MouseMovedEvent, MouseUpEvent, NSRectExt, Pixels, Platform, PlatformDispatcher, PlatformScreen,
+    PlatformWindow, Point, RenderTarget, Size, Timer, WindowAppearance, WindowBounds, WindowKind,
+    WindowOptions, WindowPromptLevel,
 };
 use block::ConcreteBlock;
 use cocoa::{
@@ -617,7 +617,7 @@ impl Drop for MacWindow {
     fn drop(&mut self) {
         let this = self.0.clone();
         let dispatcher = self.0.lock().dispatcher.clone();
-        let _ = crate::spawn_on_main(dispatcher, async move {
+        let _ = crate::spawn_on_main(dispatcher, || async move {
             unsafe {
                 this.lock().native_window.close();
             }
@@ -625,7 +625,7 @@ impl Drop for MacWindow {
     }
 }
 
-unsafe impl HasRawWindowHandle for MacWindow {
+impl PlatformWindow for MacWindow {
     fn raw_window_handle(&self) -> RawWindowHandle {
         let ns_window = self.0.lock().native_window;
         let ns_view = unsafe { ns_window.contentView() };
@@ -634,15 +634,11 @@ unsafe impl HasRawWindowHandle for MacWindow {
         handle.ns_view = ns_view as *mut c_void;
         handle.into()
     }
-}
 
-unsafe impl HasRawDisplayHandle for MacWindow {
     fn raw_display_handle(&self) -> RawDisplayHandle {
         AppKitDisplayHandle::empty().into()
     }
-}
 
-impl PlatformWindow for MacWindow {
     fn bounds(&self) -> WindowBounds {
         self.0.as_ref().lock().bounds()
     }

crates/gpui3/src/renderer.rs 🔗

@@ -1,7 +1,7 @@
+use crate::{DevicePixels, Scene, Size};
+use futures::{future::BoxFuture, FutureExt};
 use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
 
-use super::{Scene, Size};
-
 pub struct Renderer {
     device: wgpu::Device,
     queue: wgpu::Queue,
@@ -12,113 +12,118 @@ pub struct Renderer {
     vertex_count: u32,
 }
 
-pub trait Window: HasRawWindowHandle + HasRawDisplayHandle {
-    fn inner_size(&self) -> Size<u32>;
+pub(crate) trait RenderTarget: HasRawWindowHandle + HasRawDisplayHandle {
+    fn content_device_size(&self) -> Size<DevicePixels>;
 }
 
 impl Renderer {
-    pub async fn new<W>(window: &W) -> Self
+    pub(crate) fn new<'a, W>(window: &'a W) -> BoxFuture<'static, Self>
     where
-        W: Window,
+        W: RenderTarget,
     {
         let instance = wgpu::Instance::new(Default::default());
         let surface = unsafe { instance.create_surface(window).unwrap() };
+        let width = window.content_device_size().width;
+        let height = window.content_device_size().height;
+
+        async move {
+            let adapter = instance
+                .request_adapter(&wgpu::RequestAdapterOptions::default())
+                .await
+                .unwrap();
+
+            let (device, queue) = adapter
+                .request_device(&wgpu::DeviceDescriptor::default(), None)
+                .await
+                .unwrap();
+
+            let surface_config = wgpu::SurfaceConfiguration {
+                usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
+                format: wgpu::TextureFormat::Bgra8UnormSrgb,
+                width: width.into(),
+                height: height.into(),
+
+                // "FIFO" mode renders frames in queue synced with the display's refresh rate.
+                // Avoids screen tearing but may not offer the lowest latency. Ideal when image
+                // quality takes priority over input latency.
+                present_mode: wgpu::PresentMode::Fifo,
+
+                // Use the Premultiplied alpha mode. With premultiplication, the color components
+                // are multiplied by the alpha value before storage or blending, meaning calculations
+                // with colors already factor in the influence of alpha. This typically results
+                // in better performance and avoids a separate multiplication operation during blending.
+                alpha_mode: wgpu::CompositeAlphaMode::PreMultiplied,
+
+                // Specify the color formats for the views the surface can have.
+                // In this case, the format is BGRA (blue, green, red, alpha) with unsigned
+                // normalised integers in the 8-bit range and the color space is sRGB (standard RGB).
+                // sRGB is the standard color space for displaying images and video on digital displays,
+                // as it optimises color accuracy and consistency.
+                view_formats: vec![wgpu::TextureFormat::Bgra8UnormSrgb],
+            };
+
+            surface.configure(&device, &surface_config);
+
+            let vs_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
+                label: Some("Vertex Shader"),
+                source: wgpu::ShaderSource::Wgsl(include_str!("shader.vert.wgsl").into()),
+            });
 
-        let adapter = instance
-            .request_adapter(&wgpu::RequestAdapterOptions::default())
-            .await
-            .unwrap();
-
-        let (device, queue) = adapter
-            .request_device(&wgpu::DeviceDescriptor::default(), None)
-            .await
-            .unwrap();
-
-        let surface_config = wgpu::SurfaceConfiguration {
-            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
-            format: wgpu::TextureFormat::Bgra8UnormSrgb,
-            width: window.inner_size().width,
-            height: window.inner_size().height,
-
-            // "FIFO" mode renders frames in queue synced with the display's refresh rate.
-            // Avoids screen tearing but may not offer the lowest latency. Ideal when image
-            // quality takes priority over input latency.
-            present_mode: wgpu::PresentMode::Fifo,
-
-            // Use the Premultiplied alpha mode. With premultiplication, the color components
-            // are multiplied by the alpha value before storage or blending, meaning calculations
-            // with colors already factor in the influence of alpha. This typically results
-            // in better performance and avoids a separate multiplication operation during blending.
-            alpha_mode: wgpu::CompositeAlphaMode::PreMultiplied,
-
-            // Specify the color formats for the views the surface can have.
-            // In this case, the format is BGRA (blue, green, red, alpha) with unsigned
-            // normalised integers in the 8-bit range and the color space is sRGB (standard RGB).
-            // sRGB is the standard color space for displaying images and video on digital displays,
-            // as it optimises color accuracy and consistency.
-            view_formats: vec![wgpu::TextureFormat::Bgra8UnormSrgb],
-        };
-
-        surface.configure(&device, &surface_config);
-
-        let vs_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
-            label: Some("Vertex Shader"),
-            source: wgpu::ShaderSource::Wgsl(include_str!("shader.vert.wgsl").into()),
-        });
-
-        let fs_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
-            label: Some("Fragment Shader"),
-            source: wgpu::ShaderSource::Wgsl(include_str!("shader.frag.wgsl").into()),
-        });
-
-        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
-            label: Some("Render Pipeline Layout"),
-            bind_group_layouts: &[],
-            push_constant_ranges: &[],
-        });
-
-        let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
-            label: Some("Vertex Buffer"),
-            size: 0,
-            usage: wgpu::BufferUsages::VERTEX,
-            mapped_at_creation: false,
-        });
-
-        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
-            label: Some("Render Pipeline"),
-            layout: Some(&pipeline_layout),
-            vertex: wgpu::VertexState {
-                module: &vs_module,
-                entry_point: "main",
-                buffers: &[],
-            },
-            fragment: Some(wgpu::FragmentState {
-                module: &fs_module,
-                entry_point: "main",
-                targets: &[Some(wgpu::ColorTargetState {
-                    format: surface_config.format,
-                    blend: Some(wgpu::BlendState::REPLACE),
-                    write_mask: wgpu::ColorWrites::ALL,
-                })],
-            }),
-            primitive: wgpu::PrimitiveState {
-                topology: wgpu::PrimitiveTopology::TriangleStrip,
-                ..Default::default()
-            },
-            depth_stencil: None,
-            multisample: wgpu::MultisampleState::default(),
-            multiview: None,
-        });
-
-        Self {
-            device,
-            queue,
-            surface,
-            surface_config,
-            pipeline,
-            vertex_buffer,
-            vertex_count: 0,
+            let fs_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
+                label: Some("Fragment Shader"),
+                source: wgpu::ShaderSource::Wgsl(include_str!("shader.frag.wgsl").into()),
+            });
+
+            let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
+                label: Some("Render Pipeline Layout"),
+                bind_group_layouts: &[],
+                push_constant_ranges: &[],
+            });
+
+            let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
+                label: Some("Vertex Buffer"),
+                size: 0,
+                usage: wgpu::BufferUsages::VERTEX,
+                mapped_at_creation: false,
+            });
+
+            let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
+                label: Some("Render Pipeline"),
+                layout: Some(&pipeline_layout),
+                vertex: wgpu::VertexState {
+                    module: &vs_module,
+                    entry_point: "main",
+                    buffers: &[],
+                },
+                fragment: Some(wgpu::FragmentState {
+                    module: &fs_module,
+                    entry_point: "main",
+                    targets: &[Some(wgpu::ColorTargetState {
+                        format: surface_config.format,
+                        blend: Some(wgpu::BlendState::REPLACE),
+                        write_mask: wgpu::ColorWrites::ALL,
+                    })],
+                }),
+                primitive: wgpu::PrimitiveState {
+                    topology: wgpu::PrimitiveTopology::TriangleStrip,
+                    ..Default::default()
+                },
+                depth_stencil: None,
+                multisample: wgpu::MultisampleState::default(),
+                multiview: None,
+            });
+
+            Self {
+                device,
+                queue,
+                surface,
+                surface_config,
+                pipeline,
+                vertex_buffer,
+                vertex_count: 0,
+            }
         }
+        .boxed()
     }
 
     pub fn render(&mut self, scene: &Scene) {

crates/gpui3/src/window.rs 🔗

@@ -1,6 +1,6 @@
 use crate::{
-    AvailableSpace, MainThreadOnly, Platform, PlatformWindow, Point, Size, Style, TextStyle,
-    TextStyleRefinement, WindowOptions,
+    renderer::Renderer, AvailableSpace, MainThreadOnly, Platform, PlatformWindow, Point, Size,
+    Style, TextStyle, TextStyleRefinement, WindowOptions,
 };
 
 use super::{
@@ -12,6 +12,7 @@ use derive_more::{Deref, DerefMut};
 use refineable::Refineable;
 use std::{
     any::{Any, TypeId},
+    future::Future,
     marker::PhantomData,
     sync::Arc,
 };
@@ -21,6 +22,7 @@ pub struct AnyWindow {}
 pub struct Window {
     handle: AnyWindowHandle,
     platform_window: MainThreadOnly<Box<dyn PlatformWindow>>,
+    renderer: Renderer,
     rem_size: Pixels,
     layout_engine: TaffyLayoutEngine,
     text_style_stack: Vec<TextStyleRefinement>,
@@ -29,17 +31,28 @@ pub struct Window {
 }
 
 impl Window {
-    pub fn new(handle: AnyWindowHandle, options: WindowOptions, platform: &dyn Platform) -> Window {
-        let platform_window = Arc::new(platform.open_window(handle, options));
+    pub fn new(
+        handle: AnyWindowHandle,
+        options: WindowOptions,
+        platform: &dyn Platform,
+    ) -> impl Future<Output = Window> + 'static {
+        let platform_window = platform.open_window(handle, options);
+        let renderer = Renderer::new(&platform_window.as_ref());
         let mouse_position = platform_window.mouse_position();
-        Window {
-            handle,
-            platform_window: MainThreadOnly::new(platform_window, platform.dispatcher()),
-            rem_size: px(16.),
-            layout_engine: TaffyLayoutEngine::new(),
-            text_style_stack: Vec::new(),
-            root_view: None,
-            mouse_position,
+        let platform_window = MainThreadOnly::new(Arc::new(platform_window), platform.dispatcher());
+
+        async move {
+            let renderer = renderer.await;
+            Window {
+                handle,
+                platform_window,
+                renderer,
+                rem_size: px(16.),
+                layout_engine: TaffyLayoutEngine::new(),
+                text_style_stack: Vec::new(),
+                root_view: None,
+                mouse_position,
+            }
         }
     }
 }