Checkpoint

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
Antonio Scandurra
2023-09-22 14:40:39 -06:00
co-authored by Nathan Sobo
parent d1791a999d
commit 3dc1e917bf
8 changed files with 203 additions and 132 deletions
+16 -8
View File
@@ -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>(
+12 -5
View File
@@ -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),
);
+16
View File
@@ -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 {
+7 -5
View File
@@ -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
})
}
}
+27 -3
View File
@@ -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);
+7 -11
View File
@@ -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()
}
+93 -88
View File
@@ -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;
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions::default())
.await
.unwrap();
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 (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,
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,
// "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,
// 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],
};
// 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);
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 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 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 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 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,
});
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,
Self {
device,
queue,
surface,
surface_config,
pipeline,
vertex_buffer,
vertex_count: 0,
}
}
.boxed()
}
pub fn render(&mut self, scene: &Scene) {
+25 -12
View File
@@ -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,
}
}
}
}