Re-introduce screen-sharing in zed2 (#3456)

Antonio Scandurra created

Release Notes:

- N/A

Change summary

crates/call2/src/call2.rs                            |  21 -
crates/call2/src/shared_screen.rs                    |  84 +------
crates/collab2/src/tests/test_server.rs              |   2 
crates/collab_ui2/src/collab_panel/contact_finder.rs |   2 
crates/editor2/src/test.rs                           |   2 
crates/gpui2/build.rs                                |   2 
crates/gpui2/src/elements/img.rs                     | 108 +++++++---
crates/gpui2/src/geometry.rs                         |  18 +
crates/gpui2/src/platform/mac/metal_renderer.rs      | 138 +++++++++++++
crates/gpui2/src/platform/mac/shaders.metal          |  52 +++++
crates/gpui2/src/scene.rs                            |  66 ++++++
crates/gpui2/src/window.rs                           |  22 ++
crates/theme2/src/styles/stories/players.rs          |  92 +++++----
crates/ui2/src/components/avatar.rs                  |   5 
crates/workspace2/src/workspace2.rs                  |  24 +
15 files changed, 466 insertions(+), 172 deletions(-)

Detailed changes

crates/call2/src/call2.rs 🔗

@@ -15,7 +15,7 @@ use collections::HashSet;
 use futures::{channel::oneshot, future::Shared, Future, FutureExt};
 use gpui::{
     AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, PromptLevel,
-    Subscription, Task, View, ViewContext, VisualContext, WeakModel, WeakView, WindowHandle,
+    Subscription, Task, View, ViewContext, VisualContext, WeakModel, WindowHandle,
 };
 pub use participant::ParticipantLocation;
 use postage::watch;
@@ -557,24 +557,17 @@ pub fn report_call_event_for_channel(
 
 pub struct Call {
     active_call: Option<(Model<ActiveCall>, Vec<Subscription>)>,
-    parent_workspace: WeakView<Workspace>,
 }
 
 impl Call {
-    pub fn new(
-        parent_workspace: WeakView<Workspace>,
-        cx: &mut ViewContext<'_, Workspace>,
-    ) -> Box<dyn CallHandler> {
+    pub fn new(cx: &mut ViewContext<'_, Workspace>) -> Box<dyn CallHandler> {
         let mut active_call = None;
         if cx.has_global::<Model<ActiveCall>>() {
             let call = cx.global::<Model<ActiveCall>>().clone();
             let subscriptions = vec![cx.subscribe(&call, Self::on_active_call_event)];
             active_call = Some((call, subscriptions));
         }
-        Box::new(Self {
-            active_call,
-            parent_workspace,
-        })
+        Box::new(Self { active_call })
     }
     fn on_active_call_event(
         workspace: &mut Workspace,
@@ -597,6 +590,7 @@ impl CallHandler for Call {
     fn peer_state(
         &mut self,
         leader_id: PeerId,
+        project: &Model<Project>,
         cx: &mut ViewContext<Workspace>,
     ) -> Option<(bool, bool)> {
         let (call, _) = self.active_call.as_ref()?;
@@ -608,12 +602,7 @@ impl CallHandler for Call {
         match participant.location {
             ParticipantLocation::SharedProject { project_id } => {
                 leader_in_this_app = true;
-                leader_in_this_project = Some(project_id)
-                    == self
-                        .parent_workspace
-                        .update(cx, |this, cx| this.project().read(cx).remote_id())
-                        .log_err()
-                        .flatten();
+                leader_in_this_project = Some(project_id) == project.read(cx).remote_id();
             }
             ParticipantLocation::UnsharedProject => {
                 leader_in_this_app = true;

crates/call2/src/shared_screen.rs 🔗

@@ -3,10 +3,12 @@ use anyhow::Result;
 use client::{proto::PeerId, User};
 use futures::StreamExt;
 use gpui::{
-    div, AppContext, Div, Element, EventEmitter, FocusHandle, FocusableView, ParentElement, Render,
-    SharedString, Task, View, ViewContext, VisualContext, WindowContext,
+    div, img, AppContext, Div, Element, EventEmitter, FocusHandle, Focusable, FocusableView,
+    InteractiveElement, ParentElement, Render, SharedString, Styled, Task, View, ViewContext,
+    VisualContext, WindowContext,
 };
 use std::sync::{Arc, Weak};
+use ui::{h_stack, Icon, IconElement};
 use workspace::{item::Item, ItemNavHistory, WorkspaceId};
 
 pub enum Event {
@@ -16,8 +18,6 @@ pub enum Event {
 pub struct SharedScreen {
     track: Weak<RemoteVideoTrack>,
     frame: Option<Frame>,
-    // temporary addition just to render something interactive.
-    current_frame_id: usize,
     pub peer_id: PeerId,
     user: Arc<User>,
     nav_history: Option<ItemNavHistory>,
@@ -51,7 +51,6 @@ impl SharedScreen {
                 Ok(())
             }),
             focus: cx.focus_handle(),
-            current_frame_id: 0,
         }
     }
 }
@@ -65,50 +64,16 @@ impl FocusableView for SharedScreen {
     }
 }
 impl Render for SharedScreen {
-    type Element = Div;
+    type Element = Focusable<Div>;
+
     fn render(&mut self, _: &mut ViewContext<Self>) -> Self::Element {
-        let frame = self.frame.clone();
-        let frame_id = self.current_frame_id;
-        self.current_frame_id = self.current_frame_id.wrapping_add(1);
-        div().children(frame.map(|_| {
-            ui::Label::new(frame_id.to_string()).color(ui::Color::Error)
-            // img().data(Arc::new(ImageData::new(image::ImageBuffer::new(
-            //     frame.width() as u32,
-            //     frame.height() as u32,
-            // ))))
-        }))
+        div().track_focus(&self.focus).size_full().children(
+            self.frame
+                .as_ref()
+                .map(|frame| img(frame.image()).size_full()),
+        )
     }
 }
-// impl View for SharedScreen {
-//     fn ui_name() -> &'static str {
-//         "SharedScreen"
-//     }
-
-//     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-//         enum Focus {}
-
-//         let frame = self.frame.clone();
-//         MouseEventHandler::new::<Focus, _>(0, cx, |_, cx| {
-//             Canvas::new(move |bounds, _, _, cx| {
-//                 if let Some(frame) = frame.clone() {
-//                     let size = constrain_size_preserving_aspect_ratio(
-//                         bounds.size(),
-//                         vec2f(frame.width() as f32, frame.height() as f32),
-//                     );
-//                     let origin = bounds.origin() + (bounds.size() / 2.) - size / 2.;
-//                     cx.scene().push_surface(gpui::platform::mac::Surface {
-//                         bounds: RectF::new(origin, size),
-//                         image_buffer: frame.image(),
-//                     });
-//                 }
-//             })
-//             .contained()
-//             .with_style(theme::current(cx).shared_screen)
-//         })
-//         .on_down(MouseButton::Left, |_, _, cx| cx.focus_parent())
-//         .into_any()
-//     }
-// }
 
 impl Item for SharedScreen {
     fn tab_tooltip_text(&self, _: &AppContext) -> Option<SharedString> {
@@ -121,25 +86,14 @@ impl Item for SharedScreen {
     }
 
     fn tab_content(&self, _: Option<usize>, _: &WindowContext<'_>) -> gpui::AnyElement {
-        div().child("Shared screen").into_any()
-        // Flex::row()
-        //     .with_child(
-        //         Svg::new("icons/desktop.svg")
-        //             .with_color(style.label.text.color)
-        //             .constrained()
-        //             .with_width(style.type_icon_width)
-        //             .aligned()
-        //             .contained()
-        //             .with_margin_right(style.spacing),
-        //     )
-        //     .with_child(
-        //         Label::new(
-        //             format!("{}'s screen", self.user.github_login),
-        //             style.label.clone(),
-        //         )
-        //         .aligned(),
-        //     )
-        //     .into_any()
+        h_stack()
+            .gap_1()
+            .child(IconElement::new(Icon::Screen))
+            .child(SharedString::from(format!(
+                "{}'s screen",
+                self.user.github_login
+            )))
+            .into_any()
     }
 
     fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {

crates/collab2/src/tests/test_server.rs 🔗

@@ -221,7 +221,7 @@ impl TestServer {
             fs: fs.clone(),
             build_window_options: |_, _, _| Default::default(),
             node_runtime: FakeNodeRuntime::new(),
-            call_factory: |_, _| Box::new(workspace::TestCallHandler),
+            call_factory: |_| Box::new(workspace::TestCallHandler),
         });
 
         cx.update(|cx| {

crates/collab_ui2/src/collab_panel/contact_finder.rs 🔗

@@ -185,7 +185,7 @@ impl PickerDelegate for ContactFinderDelegate {
             div()
                 .flex_1()
                 .justify_between()
-                .children(user.avatar.clone().map(|avatar| img().data(avatar)))
+                .children(user.avatar.clone().map(|avatar| img(avatar)))
                 .child(Label::new(user.github_login.clone()))
                 .children(icon_path.map(|icon_path| svg().path(icon_path))),
         )

crates/editor2/src/test.rs 🔗

@@ -27,7 +27,7 @@ pub fn marked_display_snapshot(
     let (unmarked_text, markers) = marked_text_offsets(text);
 
     let font = cx.text_style().font();
-    let font_size: Pixels = 14.into();
+    let font_size: Pixels = 14usize.into();
 
     let buffer = MultiBuffer::build_simple(&unmarked_text, cx);
     let display_map = cx.build_model(|cx| DisplayMap::new(buffer, font, font_size, None, 1, 1, cx));

crates/gpui2/build.rs 🔗

@@ -65,6 +65,8 @@ fn generate_shader_bindings() -> PathBuf {
         "MonochromeSprite".into(),
         "PolychromeSprite".into(),
         "PathSprite".into(),
+        "SurfaceInputIndex".into(),
+        "SurfaceBounds".into(),
     ]);
     config.no_includes = true;
     config.enumeration.prefix_with_name = true;

crates/gpui2/src/elements/img.rs 🔗

@@ -1,10 +1,12 @@
 use std::sync::Arc;
 
 use crate::{
-    Bounds, Element, ImageData, InteractiveElement, InteractiveElementState, Interactivity,
-    IntoElement, LayoutId, Pixels, SharedString, StyleRefinement, Styled, WindowContext,
+    point, size, Bounds, DevicePixels, Element, ImageData, InteractiveElement,
+    InteractiveElementState, Interactivity, IntoElement, LayoutId, Pixels, SharedString, Size,
+    StyleRefinement, Styled, WindowContext,
 };
 use futures::FutureExt;
+use media::core_video::CVImageBuffer;
 use util::ResultExt;
 
 #[derive(Clone, Debug)]
@@ -12,6 +14,7 @@ pub enum ImageSource {
     /// Image content will be loaded from provided URI at render time.
     Uri(SharedString),
     Data(Arc<ImageData>),
+    Surface(CVImageBuffer),
 }
 
 impl From<SharedString> for ImageSource {
@@ -20,40 +23,45 @@ impl From<SharedString> for ImageSource {
     }
 }
 
+impl From<&'static str> for ImageSource {
+    fn from(uri: &'static str) -> Self {
+        Self::Uri(uri.into())
+    }
+}
+
+impl From<String> for ImageSource {
+    fn from(uri: String) -> Self {
+        Self::Uri(uri.into())
+    }
+}
+
 impl From<Arc<ImageData>> for ImageSource {
     fn from(value: Arc<ImageData>) -> Self {
         Self::Data(value)
     }
 }
 
+impl From<CVImageBuffer> for ImageSource {
+    fn from(value: CVImageBuffer) -> Self {
+        Self::Surface(value)
+    }
+}
+
 pub struct Img {
     interactivity: Interactivity,
-    source: Option<ImageSource>,
+    source: ImageSource,
     grayscale: bool,
 }
 
-pub fn img() -> Img {
+pub fn img(source: impl Into<ImageSource>) -> Img {
     Img {
         interactivity: Interactivity::default(),
-        source: None,
+        source: source.into(),
         grayscale: false,
     }
 }
 
 impl Img {
-    pub fn uri(mut self, uri: impl Into<SharedString>) -> Self {
-        self.source = Some(ImageSource::from(uri.into()));
-        self
-    }
-    pub fn data(mut self, data: Arc<ImageData>) -> Self {
-        self.source = Some(ImageSource::from(data));
-        self
-    }
-
-    pub fn source(mut self, source: impl Into<ImageSource>) -> Self {
-        self.source = Some(source.into());
-        self
-    }
     pub fn grayscale(mut self, grayscale: bool) -> Self {
         self.grayscale = grayscale;
         self
@@ -68,9 +76,8 @@ impl Element for Img {
         element_state: Option<Self::State>,
         cx: &mut WindowContext,
     ) -> (LayoutId, Self::State) {
-        self.interactivity.layout(element_state, cx, |style, cx| {
-            cx.request_layout(&style, None)
-        })
+        self.interactivity
+            .layout(element_state, cx, |style, cx| cx.request_layout(&style, []))
     }
 
     fn paint(
@@ -85,10 +92,9 @@ impl Element for Img {
             element_state,
             cx,
             |style, _scroll_offset, cx| {
-                let corner_radii = style.corner_radii;
-
-                if let Some(source) = self.source {
-                    let image = match source {
+                let corner_radii = style.corner_radii.to_pixels(bounds.size, cx.rem_size());
+                cx.with_z_index(1, |cx| {
+                    match self.source {
                         ImageSource::Uri(uri) => {
                             let image_future = cx.image_cache.get(uri.clone());
                             if let Some(data) = image_future
@@ -96,7 +102,9 @@ impl Element for Img {
                                 .now_or_never()
                                 .and_then(|result| result.ok())
                             {
-                                data
+                                let new_bounds = preserve_aspect_ratio(bounds, data.size());
+                                cx.paint_image(new_bounds, corner_radii, data, self.grayscale)
+                                    .log_err();
                             } else {
                                 cx.spawn(|mut cx| async move {
                                     if image_future.await.ok().is_some() {
@@ -104,17 +112,23 @@ impl Element for Img {
                                     }
                                 })
                                 .detach();
-                                return;
                             }
                         }
-                        ImageSource::Data(image) => image,
+
+                        ImageSource::Data(data) => {
+                            let new_bounds = preserve_aspect_ratio(bounds, data.size());
+                            cx.paint_image(new_bounds, corner_radii, data, self.grayscale)
+                                .log_err();
+                        }
+
+                        ImageSource::Surface(surface) => {
+                            let size = size(surface.width().into(), surface.height().into());
+                            let new_bounds = preserve_aspect_ratio(bounds, size);
+                            // TODO: Add support for corner_radii and grayscale.
+                            cx.paint_surface(new_bounds, surface);
+                        }
                     };
-                    let corner_radii = corner_radii.to_pixels(bounds.size, cx.rem_size());
-                    cx.with_z_index(1, |cx| {
-                        cx.paint_image(bounds, corner_radii, image, self.grayscale)
-                            .log_err()
-                    });
-                }
+                });
             },
         )
     }
@@ -143,3 +157,29 @@ impl InteractiveElement for Img {
         &mut self.interactivity
     }
 }
+
+fn preserve_aspect_ratio(bounds: Bounds<Pixels>, image_size: Size<DevicePixels>) -> Bounds<Pixels> {
+    let image_size = image_size.map(|dimension| Pixels::from(u32::from(dimension)));
+    let image_ratio = image_size.width / image_size.height;
+    let bounds_ratio = bounds.size.width / bounds.size.height;
+
+    let new_size = if bounds_ratio > image_ratio {
+        size(
+            image_size.width * (bounds.size.height / image_size.height),
+            bounds.size.height,
+        )
+    } else {
+        size(
+            bounds.size.width,
+            image_size.height * (bounds.size.width / image_size.width),
+        )
+    };
+
+    Bounds {
+        origin: point(
+            bounds.origin.x + (bounds.size.width - new_size.width) / 2.0,
+            bounds.origin.y + (bounds.size.height - new_size.height) / 2.0,
+        ),
+        size: new_size,
+    }
+}

crates/gpui2/src/geometry.rs 🔗

@@ -905,6 +905,12 @@ impl From<Pixels> for usize {
     }
 }
 
+impl From<usize> for Pixels {
+    fn from(pixels: usize) -> Self {
+        Pixels(pixels as f32)
+    }
+}
+
 #[derive(
     Add, AddAssign, Clone, Copy, Default, Div, Eq, Hash, Ord, PartialEq, PartialOrd, Sub, SubAssign,
 )]
@@ -959,6 +965,18 @@ impl From<u64> for DevicePixels {
     }
 }
 
+impl From<DevicePixels> for usize {
+    fn from(device_pixels: DevicePixels) -> Self {
+        device_pixels.0 as usize
+    }
+}
+
+impl From<usize> for DevicePixels {
+    fn from(device_pixels: usize) -> Self {
+        DevicePixels(device_pixels as i32)
+    }
+}
+
 #[derive(Clone, Copy, Default, Add, AddAssign, Sub, SubAssign, Div, PartialEq, PartialOrd)]
 #[repr(transparent)]
 pub struct ScaledPixels(pub(crate) f32);

crates/gpui2/src/platform/mac/metal_renderer.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{
     point, size, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, ContentMask, DevicePixels,
     Hsla, MetalAtlas, MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch,
-    Quad, ScaledPixels, Scene, Shadow, Size, Underline,
+    Quad, ScaledPixels, Scene, Shadow, Size, Surface, Underline,
 };
 use cocoa::{
     base::{NO, YES},
@@ -9,6 +9,9 @@ use cocoa::{
     quartzcore::AutoresizingMask,
 };
 use collections::HashMap;
+use core_foundation::base::TCFType;
+use foreign_types::ForeignType;
+use media::core_video::CVMetalTextureCache;
 use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
 use objc::{self, msg_send, sel, sel_impl};
 use smallvec::SmallVec;
@@ -27,9 +30,11 @@ pub(crate) struct MetalRenderer {
     underlines_pipeline_state: metal::RenderPipelineState,
     monochrome_sprites_pipeline_state: metal::RenderPipelineState,
     polychrome_sprites_pipeline_state: metal::RenderPipelineState,
+    surfaces_pipeline_state: metal::RenderPipelineState,
     unit_vertices: metal::Buffer,
     instances: metal::Buffer,
     sprite_atlas: Arc<MetalAtlas>,
+    core_video_texture_cache: CVMetalTextureCache,
 }
 
 impl MetalRenderer {
@@ -143,6 +148,14 @@ impl MetalRenderer {
             "polychrome_sprite_fragment",
             MTLPixelFormat::BGRA8Unorm,
         );
+        let surfaces_pipeline_state = build_pipeline_state(
+            &device,
+            &library,
+            "surfaces",
+            "surface_vertex",
+            "surface_fragment",
+            MTLPixelFormat::BGRA8Unorm,
+        );
 
         let command_queue = device.new_command_queue();
         let sprite_atlas = Arc::new(MetalAtlas::new(device.clone()));
@@ -157,9 +170,11 @@ impl MetalRenderer {
             underlines_pipeline_state,
             monochrome_sprites_pipeline_state,
             polychrome_sprites_pipeline_state,
+            surfaces_pipeline_state,
             unit_vertices,
             instances,
             sprite_atlas,
+            core_video_texture_cache: CVMetalTextureCache::new(device.as_ptr()).unwrap(),
         }
     }
 
@@ -268,6 +283,14 @@ impl MetalRenderer {
                         command_encoder,
                     );
                 }
+                PrimitiveBatch::Surfaces(surfaces) => {
+                    self.draw_surfaces(
+                        surfaces,
+                        &mut instance_offset,
+                        viewport_size,
+                        command_encoder,
+                    );
+                }
             }
         }
 
@@ -793,6 +816,102 @@ impl MetalRenderer {
         );
         *offset = next_offset;
     }
+
+    fn draw_surfaces(
+        &mut self,
+        surfaces: &[Surface],
+        offset: &mut usize,
+        viewport_size: Size<DevicePixels>,
+        command_encoder: &metal::RenderCommandEncoderRef,
+    ) {
+        command_encoder.set_render_pipeline_state(&self.surfaces_pipeline_state);
+        command_encoder.set_vertex_buffer(
+            SurfaceInputIndex::Vertices as u64,
+            Some(&self.unit_vertices),
+            0,
+        );
+        command_encoder.set_vertex_bytes(
+            SurfaceInputIndex::ViewportSize as u64,
+            mem::size_of_val(&viewport_size) as u64,
+            &viewport_size as *const Size<DevicePixels> as *const _,
+        );
+
+        for surface in surfaces {
+            let texture_size = size(
+                DevicePixels::from(surface.image_buffer.width() as i32),
+                DevicePixels::from(surface.image_buffer.height() as i32),
+            );
+
+            assert_eq!(
+                surface.image_buffer.pixel_format_type(),
+                media::core_video::kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
+            );
+
+            let y_texture = self
+                .core_video_texture_cache
+                .create_texture_from_image(
+                    surface.image_buffer.as_concrete_TypeRef(),
+                    ptr::null(),
+                    MTLPixelFormat::R8Unorm,
+                    surface.image_buffer.plane_width(0),
+                    surface.image_buffer.plane_height(0),
+                    0,
+                )
+                .unwrap();
+            let cb_cr_texture = self
+                .core_video_texture_cache
+                .create_texture_from_image(
+                    surface.image_buffer.as_concrete_TypeRef(),
+                    ptr::null(),
+                    MTLPixelFormat::RG8Unorm,
+                    surface.image_buffer.plane_width(1),
+                    surface.image_buffer.plane_height(1),
+                    1,
+                )
+                .unwrap();
+
+            align_offset(offset);
+            let next_offset = *offset + mem::size_of::<Surface>();
+            assert!(
+                next_offset <= INSTANCE_BUFFER_SIZE,
+                "instance buffer exhausted"
+            );
+
+            command_encoder.set_vertex_buffer(
+                SurfaceInputIndex::Surfaces as u64,
+                Some(&self.instances),
+                *offset as u64,
+            );
+            command_encoder.set_vertex_bytes(
+                SurfaceInputIndex::TextureSize as u64,
+                mem::size_of_val(&texture_size) as u64,
+                &texture_size as *const Size<DevicePixels> as *const _,
+            );
+            command_encoder.set_fragment_texture(
+                SurfaceInputIndex::YTexture as u64,
+                Some(y_texture.as_texture_ref()),
+            );
+            command_encoder.set_fragment_texture(
+                SurfaceInputIndex::CbCrTexture as u64,
+                Some(cb_cr_texture.as_texture_ref()),
+            );
+
+            unsafe {
+                let buffer_contents =
+                    (self.instances.contents() as *mut u8).add(*offset) as *mut SurfaceBounds;
+                ptr::write(
+                    buffer_contents,
+                    SurfaceBounds {
+                        bounds: surface.bounds,
+                        content_mask: surface.content_mask.clone(),
+                    },
+                );
+            }
+
+            command_encoder.draw_primitives(metal::MTLPrimitiveType::Triangle, 0, 6);
+            *offset = next_offset;
+        }
+    }
 }
 
 fn build_pipeline_state(
@@ -898,6 +1017,16 @@ enum SpriteInputIndex {
     AtlasTexture = 4,
 }
 
+#[repr(C)]
+enum SurfaceInputIndex {
+    Vertices = 0,
+    Surfaces = 1,
+    ViewportSize = 2,
+    TextureSize = 3,
+    YTexture = 4,
+    CbCrTexture = 5,
+}
+
 #[repr(C)]
 enum PathRasterizationInputIndex {
     Vertices = 0,
@@ -911,3 +1040,10 @@ pub struct PathSprite {
     pub color: Hsla,
     pub tile: AtlasTile,
 }
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+#[repr(C)]
+pub struct SurfaceBounds {
+    pub bounds: Bounds<ScaledPixels>,
+    pub content_mask: ContentMask<ScaledPixels>,
+}

crates/gpui2/src/platform/mac/shaders.metal 🔗

@@ -469,6 +469,58 @@ fragment float4 path_sprite_fragment(
   return color;
 }
 
+struct SurfaceVertexOutput {
+  float4 position [[position]];
+  float2 texture_position;
+  float clip_distance [[clip_distance]][4];
+};
+
+struct SurfaceFragmentInput {
+  float4 position [[position]];
+  float2 texture_position;
+};
+
+vertex SurfaceVertexOutput surface_vertex(
+    uint unit_vertex_id [[vertex_id]], uint surface_id [[instance_id]],
+    constant float2 *unit_vertices [[buffer(SurfaceInputIndex_Vertices)]],
+    constant SurfaceBounds *surfaces [[buffer(SurfaceInputIndex_Surfaces)]],
+    constant Size_DevicePixels *viewport_size
+    [[buffer(SurfaceInputIndex_ViewportSize)]],
+    constant Size_DevicePixels *texture_size
+    [[buffer(SurfaceInputIndex_TextureSize)]]) {
+  float2 unit_vertex = unit_vertices[unit_vertex_id];
+  SurfaceBounds surface = surfaces[surface_id];
+  float4 device_position =
+      to_device_position(unit_vertex, surface.bounds, viewport_size);
+  float4 clip_distance = distance_from_clip_rect(unit_vertex, surface.bounds,
+                                                 surface.content_mask.bounds);
+  // We are going to copy the whole texture, so the texture position corresponds
+  // to the current vertex of the unit triangle.
+  float2 texture_position = unit_vertex;
+  return SurfaceVertexOutput{
+      device_position,
+      texture_position,
+      {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
+}
+
+fragment float4 surface_fragment(SurfaceFragmentInput input [[stage_in]],
+                                 texture2d<float> y_texture
+                                 [[texture(SurfaceInputIndex_YTexture)]],
+                                 texture2d<float> cb_cr_texture
+                                 [[texture(SurfaceInputIndex_CbCrTexture)]]) {
+  constexpr sampler texture_sampler(mag_filter::linear, min_filter::linear);
+  const float4x4 ycbcrToRGBTransform =
+      float4x4(float4(+1.0000f, +1.0000f, +1.0000f, +0.0000f),
+               float4(+0.0000f, -0.3441f, +1.7720f, +0.0000f),
+               float4(+1.4020f, -0.7141f, +0.0000f, +0.0000f),
+               float4(-0.7010f, +0.5291f, -0.8860f, +1.0000f));
+  float4 ycbcr = float4(
+      y_texture.sample(texture_sampler, input.texture_position).r,
+      cb_cr_texture.sample(texture_sampler, input.texture_position).rg, 1.0);
+
+  return ycbcrToRGBTransform * ycbcr;
+}
+
 float4 hsla_to_rgba(Hsla hsla) {
   float h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range
   float s = hsla.s;

crates/gpui2/src/scene.rs 🔗

@@ -25,6 +25,7 @@ pub(crate) struct SceneBuilder {
     underlines: Vec<Underline>,
     monochrome_sprites: Vec<MonochromeSprite>,
     polychrome_sprites: Vec<PolychromeSprite>,
+    surfaces: Vec<Surface>,
 }
 
 impl Default for SceneBuilder {
@@ -38,6 +39,7 @@ impl Default for SceneBuilder {
             underlines: Vec::new(),
             monochrome_sprites: Vec::new(),
             polychrome_sprites: Vec::new(),
+            surfaces: Vec::new(),
         }
     }
 }
@@ -120,6 +122,7 @@ impl SceneBuilder {
                 (PrimitiveKind::PolychromeSprite, ix) => {
                     self.polychrome_sprites[ix].order = draw_order as DrawOrder
                 }
+                (PrimitiveKind::Surface, ix) => self.surfaces[ix].order = draw_order as DrawOrder,
             }
         }
 
@@ -129,6 +132,7 @@ impl SceneBuilder {
         self.underlines.sort_unstable();
         self.monochrome_sprites.sort_unstable();
         self.polychrome_sprites.sort_unstable();
+        self.surfaces.sort_unstable();
 
         Scene {
             shadows: mem::take(&mut self.shadows),
@@ -137,6 +141,7 @@ impl SceneBuilder {
             underlines: mem::take(&mut self.underlines),
             monochrome_sprites: mem::take(&mut self.monochrome_sprites),
             polychrome_sprites: mem::take(&mut self.polychrome_sprites),
+            surfaces: mem::take(&mut self.surfaces),
         }
     }
 
@@ -185,6 +190,10 @@ impl SceneBuilder {
                 sprite.order = layer_id;
                 self.polychrome_sprites.push(sprite);
             }
+            Primitive::Surface(mut surface) => {
+                surface.order = layer_id;
+                self.surfaces.push(surface);
+            }
         }
     }
 }
@@ -196,6 +205,7 @@ pub(crate) struct Scene {
     pub underlines: Vec<Underline>,
     pub monochrome_sprites: Vec<MonochromeSprite>,
     pub polychrome_sprites: Vec<PolychromeSprite>,
+    pub surfaces: Vec<Surface>,
 }
 
 impl Scene {
@@ -224,6 +234,9 @@ impl Scene {
             polychrome_sprites: &self.polychrome_sprites,
             polychrome_sprites_start: 0,
             polychrome_sprites_iter: self.polychrome_sprites.iter().peekable(),
+            surfaces: &self.surfaces,
+            surfaces_start: 0,
+            surfaces_iter: self.surfaces.iter().peekable(),
         }
     }
 }
@@ -247,6 +260,9 @@ struct BatchIterator<'a> {
     polychrome_sprites: &'a [PolychromeSprite],
     polychrome_sprites_start: usize,
     polychrome_sprites_iter: Peekable<slice::Iter<'a, PolychromeSprite>>,
+    surfaces: &'a [Surface],
+    surfaces_start: usize,
+    surfaces_iter: Peekable<slice::Iter<'a, Surface>>,
 }
 
 impl<'a> Iterator for BatchIterator<'a> {
@@ -272,6 +288,10 @@ impl<'a> Iterator for BatchIterator<'a> {
                 self.polychrome_sprites_iter.peek().map(|s| s.order),
                 PrimitiveKind::PolychromeSprite,
             ),
+            (
+                self.surfaces_iter.peek().map(|s| s.order),
+                PrimitiveKind::Surface,
+            ),
         ];
         orders_and_kinds.sort_by_key(|(order, kind)| (order.unwrap_or(u32::MAX), *kind));
 
@@ -378,6 +398,21 @@ impl<'a> Iterator for BatchIterator<'a> {
                     sprites: &self.polychrome_sprites[sprites_start..sprites_end],
                 })
             }
+            PrimitiveKind::Surface => {
+                let surfaces_start = self.surfaces_start;
+                let mut surfaces_end = surfaces_start;
+                while self
+                    .surfaces_iter
+                    .next_if(|surface| surface.order <= max_order)
+                    .is_some()
+                {
+                    surfaces_end += 1;
+                }
+                self.surfaces_start = surfaces_end;
+                Some(PrimitiveBatch::Surfaces(
+                    &self.surfaces[surfaces_start..surfaces_end],
+                ))
+            }
         }
     }
 }
@@ -391,6 +426,7 @@ pub enum PrimitiveKind {
     Underline,
     MonochromeSprite,
     PolychromeSprite,
+    Surface,
 }
 
 pub enum Primitive {
@@ -400,6 +436,7 @@ pub enum Primitive {
     Underline(Underline),
     MonochromeSprite(MonochromeSprite),
     PolychromeSprite(PolychromeSprite),
+    Surface(Surface),
 }
 
 impl Primitive {
@@ -411,6 +448,7 @@ impl Primitive {
             Primitive::Underline(underline) => &underline.bounds,
             Primitive::MonochromeSprite(sprite) => &sprite.bounds,
             Primitive::PolychromeSprite(sprite) => &sprite.bounds,
+            Primitive::Surface(surface) => &surface.bounds,
         }
     }
 
@@ -422,6 +460,7 @@ impl Primitive {
             Primitive::Underline(underline) => &underline.content_mask,
             Primitive::MonochromeSprite(sprite) => &sprite.content_mask,
             Primitive::PolychromeSprite(sprite) => &sprite.content_mask,
+            Primitive::Surface(surface) => &surface.content_mask,
         }
     }
 }
@@ -440,6 +479,7 @@ pub(crate) enum PrimitiveBatch<'a> {
         texture_id: AtlasTextureId,
         sprites: &'a [PolychromeSprite],
     },
+    Surfaces(&'a [Surface]),
 }
 
 #[derive(Default, Debug, Clone, Eq, PartialEq)]
@@ -593,6 +633,32 @@ impl From<PolychromeSprite> for Primitive {
     }
 }
 
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct Surface {
+    pub order: u32,
+    pub bounds: Bounds<ScaledPixels>,
+    pub content_mask: ContentMask<ScaledPixels>,
+    pub image_buffer: media::core_video::CVImageBuffer,
+}
+
+impl Ord for Surface {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        self.order.cmp(&other.order)
+    }
+}
+
+impl PartialOrd for Surface {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl From<Surface> for Primitive {
+    fn from(surface: Surface) -> Self {
+        Primitive::Surface(surface)
+    }
+}
+
 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
 pub(crate) struct PathId(pub(crate) usize);
 

crates/gpui2/src/window.rs 🔗

@@ -8,8 +8,8 @@ use crate::{
     MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler,
     PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams,
     RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size,
-    Style, SubscriberSet, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View,
-    VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
+    Style, SubscriberSet, Subscription, Surface, TaffyLayoutEngine, Task, Underline,
+    UnderlineStyle, View, VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
 };
 use anyhow::{anyhow, Context as _, Result};
 use collections::HashMap;
@@ -18,6 +18,7 @@ use futures::{
     channel::{mpsc, oneshot},
     StreamExt,
 };
+use media::core_video::CVImageBuffer;
 use parking_lot::RwLock;
 use slotmap::SlotMap;
 use smallvec::SmallVec;
@@ -1116,6 +1117,23 @@ impl<'a> WindowContext<'a> {
         Ok(())
     }
 
+    /// Paint a surface into the scene for the current frame at the current z-index.
+    pub fn paint_surface(&mut self, bounds: Bounds<Pixels>, image_buffer: CVImageBuffer) {
+        let scale_factor = self.scale_factor();
+        let bounds = bounds.scale(scale_factor);
+        let content_mask = self.content_mask().scale(scale_factor);
+        let window = &mut *self.window;
+        window.current_frame.scene_builder.insert(
+            &window.current_frame.z_index_stack,
+            Surface {
+                order: 0,
+                bounds,
+                content_mask,
+                image_buffer,
+            },
+        );
+    }
+
     /// Draw pixels to the display for this window based on the contents of its scene.
     pub(crate) fn draw(&mut self) {
         let root_view = self.window.root_view.take().unwrap();

crates/theme2/src/styles/stories/players.rs 🔗

@@ -55,9 +55,8 @@ impl Render for PlayerStory {
                             .border_2()
                             .border_color(player.cursor)
                             .child(
-                                img()
+                                img("https://avatars.githubusercontent.com/u/1714999?v=4")
                                     .rounded_full()
-                                    .uri("https://avatars.githubusercontent.com/u/1714999?v=4")
                                     .size_6()
                                     .bg(gpui::red()),
                             )
@@ -67,51 +66,62 @@ impl Render for PlayerStory {
                 .child(div().flex().gap_1().children(
                     cx.theme().players().0.clone().iter_mut().map(|player| {
                         div()
-                                .my_1()
-                                .rounded_xl()
-                                .flex()
-                                .items_center()
-                                .h_8()
-                                .py_0p5()
-                                .px_1p5()
-                                .bg(player.background)
-                                .child(
-                                div().relative().neg_mx_1().rounded_full().z_index(3)
+                            .my_1()
+                            .rounded_xl()
+                            .flex()
+                            .items_center()
+                            .h_8()
+                            .py_0p5()
+                            .px_1p5()
+                            .bg(player.background)
+                            .child(
+                                div()
+                                    .relative()
+                                    .neg_mx_1()
+                                    .rounded_full()
+                                    .z_index(3)
                                     .border_2()
                                     .border_color(player.background)
                                     .size(px(28.))
                                     .child(
-                                    img()
-                                        .rounded_full()
-                                        .uri("https://avatars.githubusercontent.com/u/1714999?v=4")
-                                        .size(px(24.))
-                                        .bg(gpui::red()),
-                                ),
-                            ).child(
-                            div().relative().neg_mx_1().rounded_full().z_index(2)
-                                .border_2()
-                                .border_color(player.background)
-                                .size(px(28.))
-                                .child(
-                                img()
+                                        img("https://avatars.githubusercontent.com/u/1714999?v=4")
+                                            .rounded_full()
+                                            .size(px(24.))
+                                            .bg(gpui::red()),
+                                    ),
+                            )
+                            .child(
+                                div()
+                                    .relative()
+                                    .neg_mx_1()
                                     .rounded_full()
-                                    .uri("https://avatars.githubusercontent.com/u/1714999?v=4")
-                                    .size(px(24.))
-                                    .bg(gpui::red()),
-                            ),
-                        ).child(
-                        div().relative().neg_mx_1().rounded_full().z_index(1)
-                            .border_2()
-                            .border_color(player.background)
-                            .size(px(28.))
+                                    .z_index(2)
+                                    .border_2()
+                                    .border_color(player.background)
+                                    .size(px(28.))
+                                    .child(
+                                        img("https://avatars.githubusercontent.com/u/1714999?v=4")
+                                            .rounded_full()
+                                            .size(px(24.))
+                                            .bg(gpui::red()),
+                                    ),
+                            )
                             .child(
-                            img()
-                                .rounded_full()
-                                .uri("https://avatars.githubusercontent.com/u/1714999?v=4")
-                                .size(px(24.))
-                                .bg(gpui::red()),
-                        ),
-                    )
+                                div()
+                                    .relative()
+                                    .neg_mx_1()
+                                    .rounded_full()
+                                    .z_index(1)
+                                    .border_2()
+                                    .border_color(player.background)
+                                    .size(px(28.))
+                                    .child(
+                                        img("https://avatars.githubusercontent.com/u/1714999?v=4")
+                                            .rounded_full()
+                                            .size(px(24.))
+                                            .bg(gpui::red()),
+                                    ),
+                            )
                     }),
                 ))
                 .child(Story::label("Player Selections"))

crates/ui2/src/components/avatar.rs 🔗

@@ -21,7 +21,7 @@ impl RenderOnce for Avatar {
     type Rendered = Div;
 
     fn render(self, cx: &mut WindowContext) -> Self::Rendered {
-        let mut img = img();
+        let mut img = img(self.src);
 
         if self.shape == Shape::Circle {
             img = img.rounded_full();
@@ -34,8 +34,7 @@ impl RenderOnce for Avatar {
         div()
             .size(size)
             .child(
-                img.source(self.src.clone())
-                    .size(size)
+                img.size(size)
                     // todo!(Pull the avatar fallback background from the theme.)
                     .bg(gpui::red()),
             )

crates/workspace2/src/workspace2.rs 🔗

@@ -326,7 +326,12 @@ pub struct TestCallHandler;
 
 #[cfg(any(test, feature = "test-support"))]
 impl CallHandler for TestCallHandler {
-    fn peer_state(&mut self, id: PeerId, cx: &mut ViewContext<Workspace>) -> Option<(bool, bool)> {
+    fn peer_state(
+        &mut self,
+        id: PeerId,
+        project: &Model<Project>,
+        cx: &mut ViewContext<Workspace>,
+    ) -> Option<(bool, bool)> {
         None
     }
 
@@ -409,7 +414,7 @@ impl AppState {
             workspace_store,
             node_runtime: FakeNodeRuntime::new(),
             build_window_options: |_, _, _| Default::default(),
-            call_factory: |_, _| Box::new(TestCallHandler),
+            call_factory: |_| Box::new(TestCallHandler),
         })
     }
 }
@@ -468,7 +473,12 @@ pub enum Event {
 
 #[async_trait(?Send)]
 pub trait CallHandler {
-    fn peer_state(&mut self, id: PeerId, cx: &mut ViewContext<Workspace>) -> Option<(bool, bool)>;
+    fn peer_state(
+        &mut self,
+        id: PeerId,
+        project: &Model<Project>,
+        cx: &mut ViewContext<Workspace>,
+    ) -> Option<(bool, bool)>;
     fn shared_screen_for_peer(
         &self,
         peer_id: PeerId,
@@ -546,7 +556,7 @@ struct FollowerState {
 
 enum WorkspaceBounds {}
 
-type CallFactory = fn(WeakView<Workspace>, &mut ViewContext<Workspace>) -> Box<dyn CallHandler>;
+type CallFactory = fn(&mut ViewContext<Workspace>) -> Box<dyn CallHandler>;
 impl Workspace {
     pub fn new(
         workspace_id: WorkspaceId,
@@ -760,7 +770,7 @@ impl Workspace {
             last_leaders_by_pane: Default::default(),
             window_edited: false,
 
-            call_handler: (app_state.call_factory)(weak_handle.clone(), cx),
+            call_handler: (app_state.call_factory)(cx),
             database_id: workspace_id,
             app_state,
             _observe_current_user,
@@ -2884,7 +2894,7 @@ impl Workspace {
         cx.notify();
 
         let (leader_in_this_project, leader_in_this_app) =
-            self.call_handler.peer_state(leader_id, cx)?;
+            self.call_handler.peer_state(leader_id, &self.project, cx)?;
         let mut items_to_activate = Vec::new();
         for (pane, state) in &self.follower_states {
             if state.leader_id != leader_id {
@@ -3385,7 +3395,7 @@ impl Workspace {
             fs: project.read(cx).fs().clone(),
             build_window_options: |_, _, _| Default::default(),
             node_runtime: FakeNodeRuntime::new(),
-            call_factory: |_, _| Box::new(TestCallHandler),
+            call_factory: |_| Box::new(TestCallHandler),
         });
         let workspace = Self::new(0, project, app_state, cx);
         workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));