Just vibes for now. Not sure it'll go much further.

Nate Butler created

Change summary

crates/gpui/Cargo.toml                            |   4 
crates/gpui/examples/metal_view.rs                | 307 ++++++++++++++
crates/gpui/examples/metal_view_quad.rs           | 355 +++++++++++++++++
crates/gpui/examples/metal_view_simple.rs         | 198 +++++++++
crates/gpui/src/elements/metal_view.rs            | 196 +++++++++
crates/gpui/src/elements/mod.rs                   |   5 
crates/gpui/src/gpui.rs                           |   2 
crates/gpui/src/platform/mac.rs                   |   2 
crates/gpui/src/platform/mac/metal_render_pass.rs | 343 ++++++++++++++++
crates/gpui/src/platform/mac/metal_renderer.rs    | 120 +++++
crates/gpui/src/scene.rs                          |  82 +++
crates/gpui/src/window.rs                         |  24 +
12 files changed, 1,625 insertions(+), 13 deletions(-)

Detailed changes

crates/gpui/Cargo.toml 🔗

@@ -298,3 +298,7 @@ path = "examples/uniform_list.rs"
 [[example]]
 name = "window_shadow"
 path = "examples/window_shadow.rs"
+
+[[example]]
+name = "metal_view"
+path = "examples/metal_view.rs"

crates/gpui/examples/metal_view.rs 🔗

@@ -0,0 +1,307 @@
+use gpui::{Application, *};
+use std::sync::Arc;
+
+#[cfg(target_os = "macos")]
+use metal::{Device, MTLPrimitiveType, RenderCommandEncoderRef, RenderPipelineState, TextureRef};
+
+struct MetalViewExample {
+    #[cfg(target_os = "macos")]
+    pipeline_state: Option<RenderPipelineState>,
+    #[cfg(target_os = "macos")]
+    device: Option<Device>,
+}
+
+impl MetalViewExample {
+    fn new() -> Self {
+        Self {
+            #[cfg(target_os = "macos")]
+            pipeline_state: None,
+            #[cfg(target_os = "macos")]
+            device: None,
+        }
+    }
+
+    #[cfg(target_os = "macos")]
+    fn setup_metal(&mut self) {
+        // Create Metal device
+        let device = Device::system_default().expect("no Metal device");
+
+        // Create shader library from source
+        let shader_source = r#"
+            #include <metal_stdlib>
+            using namespace metal;
+
+            struct VertexOut {
+                float4 position [[position]];
+                float4 color;
+            };
+
+            vertex VertexOut vertex_main(uint vid [[vertex_id]]) {
+                VertexOut out;
+
+                // Create a rectangle using two triangles
+                // Triangle 1: top-left, top-right, bottom-left
+                // Triangle 2: top-right, bottom-right, bottom-left
+                float2 positions[6] = {
+                    float2(-1.0,  1.0), // top-left
+                    float2( 1.0,  1.0), // top-right
+                    float2(-1.0, -1.0), // bottom-left
+                    float2( 1.0,  1.0), // top-right
+                    float2( 1.0, -1.0), // bottom-right
+                    float2(-1.0, -1.0), // bottom-left
+                };
+
+                out.position = float4(positions[vid], 0.0, 1.0);
+                // Create a gradient color based on position
+                out.color = float4(
+                    (positions[vid].x + 1.0) * 0.5,  // Red based on X
+                    (positions[vid].y + 1.0) * 0.5,  // Green based on Y
+                    0.7,                              // Blue constant
+                    1.0                               // Alpha
+                );
+
+                return out;
+            }
+
+            fragment float4 fragment_main(VertexOut in [[stage_in]]) {
+                return in.color;
+            }
+        "#;
+
+        let library = device
+            .new_library_with_source(shader_source, &metal::CompileOptions::new())
+            .expect("Failed to create shader library");
+
+        let vertex_function = library.get_function("vertex_main", None).unwrap();
+        let fragment_function = library.get_function("fragment_main", None).unwrap();
+
+        // Create pipeline state
+        let pipeline_descriptor = metal::RenderPipelineDescriptor::new();
+        pipeline_descriptor.set_vertex_function(Some(&vertex_function));
+        pipeline_descriptor.set_fragment_function(Some(&fragment_function));
+
+        // Configure color attachment
+        let color_attachment = pipeline_descriptor
+            .color_attachments()
+            .object_at(0)
+            .unwrap();
+        color_attachment.set_pixel_format(metal::MTLPixelFormat::BGRA8Unorm);
+
+        // Enable blending to work with GPUI's existing content
+        color_attachment.set_blending_enabled(true);
+        color_attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::SourceAlpha);
+        color_attachment
+            .set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
+        color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
+        color_attachment
+            .set_destination_alpha_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
+
+        let pipeline_state = device
+            .new_render_pipeline_state(&pipeline_descriptor)
+            .expect("Failed to create pipeline state");
+
+        self.device = Some(device);
+        self.pipeline_state = Some(pipeline_state);
+    }
+
+    #[cfg(target_os = "macos")]
+    fn create_render_callback(&self) -> MetalRenderCallback {
+        let pipeline_state = self.pipeline_state.clone().unwrap();
+
+        Arc::new(
+            move |encoder: &RenderCommandEncoderRef,
+                  _target: &TextureRef,
+                  bounds: Bounds<Pixels>,
+                  scale_factor: f32| {
+                // Set the pipeline state
+                encoder.set_render_pipeline_state(&pipeline_state);
+
+                // Set viewport to match element bounds
+                let viewport = metal::MTLViewport {
+                    originX: bounds.origin.x.0 as f64 * scale_factor as f64,
+                    originY: bounds.origin.y.0 as f64 * scale_factor as f64,
+                    width: bounds.size.width.0 as f64 * scale_factor as f64,
+                    height: bounds.size.height.0 as f64 * scale_factor as f64,
+                    znear: 0.0,
+                    zfar: 1.0,
+                };
+                encoder.set_viewport(viewport);
+
+                // Draw the rectangle (6 vertices for 2 triangles)
+                encoder.draw_primitives(MTLPrimitiveType::Triangle, 0, 6);
+            },
+        )
+    }
+}
+
+impl Render for MetalViewExample {
+    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
+        // Initialize Metal on first render if on macOS
+        #[cfg(target_os = "macos")]
+        if self.pipeline_state.is_none() {
+            self.setup_metal();
+        }
+
+        div()
+            .flex()
+            .bg(rgb(0x1e1e1e))
+            .size_full()
+            .justify_center()
+            .items_center()
+            .child(
+                div()
+                    .flex_col()
+                    .gap_4()
+                    .child(
+                        div().flex().justify_center().child(
+                            div()
+                                .child("Metal View Example")
+                                .text_xl()
+                                .text_color(rgb(0xffffff)),
+                        ),
+                    )
+                    .child(
+                        div()
+                            .border_1()
+                            .border_color(rgb(0x444444))
+                            .rounded_md()
+                            .overflow_hidden()
+                            .child(
+                                // The Metal view
+                                #[cfg(target_os = "macos")]
+                                {
+                                    let callback = self.create_render_callback();
+                                    metal_view()
+                                        .render_with_shared(callback)
+                                        .w(px(400.0))
+                                        .h(px(300.0))
+                                        .bg(rgb(0x000000))
+                                },
+                                #[cfg(not(target_os = "macos"))]
+                                {
+                                    // Fallback for non-macOS platforms
+                                    div()
+                                        .w(px(400.0))
+                                        .h(px(300.0))
+                                        .bg(rgb(0x222222))
+                                        .flex()
+                                        .justify_center()
+                                        .items_center()
+                                        .child(
+                                            div()
+                                                .child("Metal rendering is only available on macOS")
+                                                .text_color(rgb(0x888888)),
+                                        )
+                                },
+                            ),
+                    )
+                    .child(
+                        div().flex().justify_center().child(
+                            div()
+                                .child("A gradient rectangle rendered with custom Metal shaders")
+                                .text_sm()
+                                .text_color(rgb(0xaaaaaa)),
+                        ),
+                    ),
+            )
+    }
+}
+
+fn main() {
+    Application::new().run(|cx: &mut App| {
+        let _ = cx.open_window(
+            WindowOptions {
+                window_bounds: Some(WindowBounds::Windowed(Bounds {
+                    origin: Point::new(px(100.0), px(100.0)),
+                    size: Size {
+                        width: px(600.0),
+                        height: px(500.0),
+                    },
+                })),
+                titlebar: Some(TitlebarOptions {
+                    title: Some("Metal View Example".into()),
+                    ..Default::default()
+                }),
+                ..Default::default()
+            },
+            |_window, cx| cx.new(|_cx| MetalViewExample::new()),
+        );
+    });
+}
+
+// Additional example: Using MetalView for more complex rendering
+#[cfg(target_os = "macos")]
+#[allow(dead_code)]
+mod advanced_example {
+    use super::*;
+    use std::sync::Mutex;
+
+    /// Example of a MetalView that renders an animated scene
+    pub struct AnimatedMetalView {
+        device: Device,
+        pipeline_state: RenderPipelineState,
+        frame_count: Arc<Mutex<f32>>,
+    }
+
+    impl AnimatedMetalView {
+        pub fn create_animated_renderer(&self) -> MetalRenderCallback {
+            let pipeline_state = self.pipeline_state.clone();
+            let frame_count = self.frame_count.clone();
+
+            Arc::new(
+                move |encoder: &RenderCommandEncoderRef,
+                      _target: &TextureRef,
+                      bounds: Bounds<Pixels>,
+                      scale_factor: f32| {
+                    // Update animation state
+                    let mut count = frame_count.lock().unwrap();
+                    *count += 0.01;
+                    let time = *count;
+
+                    // Set pipeline and viewport
+                    encoder.set_render_pipeline_state(&pipeline_state);
+
+                    let viewport = metal::MTLViewport {
+                        originX: bounds.origin.x.0 as f64 * scale_factor as f64,
+                        originY: bounds.origin.y.0 as f64 * scale_factor as f64,
+                        width: bounds.size.width.0 as f64 * scale_factor as f64,
+                        height: bounds.size.height.0 as f64 * scale_factor as f64,
+                        znear: 0.0,
+                        zfar: 1.0,
+                    };
+                    encoder.set_viewport(viewport);
+
+                    // Pass time as a uniform
+                    encoder.set_vertex_bytes(
+                        0,
+                        std::mem::size_of::<f32>() as u64,
+                        &time as *const f32 as *const _,
+                    );
+
+                    // Draw animated geometry
+                    encoder.draw_primitives(MTLPrimitiveType::TriangleStrip, 0, 4);
+                },
+            )
+        }
+    }
+}
+
+// Example usage in a component:
+// ```rust
+// struct MyApp {
+//     metal_renderer: Option<MetalRenderCallback>,
+// }
+//
+// impl Render for MyApp {
+//     fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+//         div()
+//             .child(
+//                 metal_view()
+//                     .render_with(|encoder, target, bounds, scale_factor| {
+//                         // Your custom Metal rendering code here
+//                     })
+//                     .size_full()
+//             )
+//     }
+// }
+// ```

crates/gpui/examples/metal_view_quad.rs 🔗

@@ -0,0 +1,355 @@
+use gpui::{prelude::*, *};
+use std::sync::Arc;
+
+#[cfg(target_os = "macos")]
+use metal::{Device, MTLPrimitiveType, RenderCommandEncoderRef, RenderPipelineState, TextureRef};
+
+struct MetalQuadExample {
+    #[cfg(target_os = "macos")]
+    pipeline_state: Option<RenderPipelineState>,
+    #[cfg(target_os = "macos")]
+    device: Option<Device>,
+}
+
+impl MetalQuadExample {
+    fn new() -> Self {
+        Self {
+            #[cfg(target_os = "macos")]
+            pipeline_state: None,
+            #[cfg(target_os = "macos")]
+            device: None,
+        }
+    }
+
+    #[cfg(target_os = "macos")]
+    fn setup_metal(&mut self) {
+        let device = Device::system_default().expect("no Metal device");
+
+        // Shader that properly handles viewport transformation
+        let shader_source = r#"
+            #include <metal_stdlib>
+            using namespace metal;
+
+            struct Uniforms {
+                float2 viewport_size;
+            };
+
+            struct VertexOut {
+                float4 position [[position]];
+                float4 color;
+            };
+
+            vertex VertexOut vertex_main(
+                uint vid [[vertex_id]],
+                constant Uniforms& uniforms [[buffer(0)]]
+            ) {
+                VertexOut out;
+
+                // Define a quad in pixel coordinates (0,0 to viewport_size)
+                float2 positions[6] = {
+                    float2(0.0, 0.0),                                      // top-left
+                    float2(uniforms.viewport_size.x, 0.0),                // top-right
+                    float2(0.0, uniforms.viewport_size.y),                // bottom-left
+                    float2(uniforms.viewport_size.x, 0.0),                // top-right
+                    float2(uniforms.viewport_size.x, uniforms.viewport_size.y), // bottom-right
+                    float2(0.0, uniforms.viewport_size.y),                // bottom-left
+                };
+
+                // Transform from pixel coordinates to normalized device coordinates
+                float2 pos = positions[vid];
+                float2 ndc = (pos / uniforms.viewport_size) * 2.0 - 1.0;
+                ndc.y = -ndc.y; // Flip Y axis to match screen coordinates
+
+                out.position = float4(ndc, 0.0, 1.0);
+
+                // Create a nice gradient
+                float2 uv = pos / uniforms.viewport_size;
+                out.color = float4(
+                    uv.x,           // Red increases left to right
+                    uv.y,           // Green increases top to bottom
+                    1.0 - uv.x,     // Blue decreases left to right
+                    1.0             // Full opacity
+                );
+
+                return out;
+            }
+
+            fragment float4 fragment_main(VertexOut in [[stage_in]]) {
+                return in.color;
+            }
+        "#;
+
+        let library = device
+            .new_library_with_source(shader_source, &metal::CompileOptions::new())
+            .expect("Failed to create shader library");
+
+        let vertex_function = library.get_function("vertex_main", None).unwrap();
+        let fragment_function = library.get_function("fragment_main", None).unwrap();
+
+        // Create pipeline state
+        let pipeline_descriptor = metal::RenderPipelineDescriptor::new();
+        pipeline_descriptor.set_vertex_function(Some(&vertex_function));
+        pipeline_descriptor.set_fragment_function(Some(&fragment_function));
+
+        let color_attachment = pipeline_descriptor
+            .color_attachments()
+            .object_at(0)
+            .unwrap();
+        color_attachment.set_pixel_format(metal::MTLPixelFormat::BGRA8Unorm);
+
+        // Enable blending
+        color_attachment.set_blending_enabled(true);
+        color_attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::SourceAlpha);
+        color_attachment
+            .set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
+        color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
+        color_attachment
+            .set_destination_alpha_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
+
+        let pipeline_state = device
+            .new_render_pipeline_state(&pipeline_descriptor)
+            .expect("Failed to create pipeline state");
+
+        self.device = Some(device);
+        self.pipeline_state = Some(pipeline_state);
+    }
+
+    #[cfg(target_os = "macos")]
+    fn create_render_callback(&self) -> MetalRenderCallback {
+        let pipeline_state = self.pipeline_state.clone().unwrap();
+
+        Arc::new(
+            move |encoder: &RenderCommandEncoderRef,
+                  _target: &TextureRef,
+                  bounds: Bounds<Pixels>,
+                  scale_factor: f32| {
+                // Set the pipeline state
+                encoder.set_render_pipeline_state(&pipeline_state);
+
+                // Set viewport to match element bounds
+                let viewport = metal::MTLViewport {
+                    originX: bounds.origin.x.0 as f64 * scale_factor as f64,
+                    originY: bounds.origin.y.0 as f64 * scale_factor as f64,
+                    width: bounds.size.width.0 as f64 * scale_factor as f64,
+                    height: bounds.size.height.0 as f64 * scale_factor as f64,
+                    znear: 0.0,
+                    zfar: 1.0,
+                };
+                encoder.set_viewport(viewport);
+
+                // Set scissor rectangle to clip to bounds
+                let scissor_rect = metal::MTLScissorRect {
+                    x: (bounds.origin.x.0 * scale_factor) as u64,
+                    y: (bounds.origin.y.0 * scale_factor) as u64,
+                    width: (bounds.size.width.0 * scale_factor) as u64,
+                    height: (bounds.size.height.0 * scale_factor) as u64,
+                };
+                encoder.set_scissor_rect(scissor_rect);
+
+                // Pass viewport size as uniform
+                #[repr(C)]
+                struct Uniforms {
+                    viewport_size: [f32; 2],
+                }
+
+                let uniforms = Uniforms {
+                    viewport_size: [
+                        bounds.size.width.0 * scale_factor,
+                        bounds.size.height.0 * scale_factor,
+                    ],
+                };
+
+                encoder.set_vertex_bytes(
+                    0,
+                    std::mem::size_of::<Uniforms>() as u64,
+                    &uniforms as *const Uniforms as *const _,
+                );
+
+                // Draw the quad
+                encoder.draw_primitives(MTLPrimitiveType::Triangle, 0, 6);
+            },
+        )
+    }
+}
+
+impl Render for MetalQuadExample {
+    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
+        #[cfg(target_os = "macos")]
+        if self.pipeline_state.is_none() {
+            self.setup_metal();
+        }
+
+        div()
+            .flex()
+            .flex_col()
+            .bg(rgb(0x1e1e1e))
+            .size_full()
+            .p_8()
+            .gap_6()
+            .child(
+                div()
+                    .child("Metal Quad Example")
+                    .text_2xl()
+                    .text_color(rgb(0xffffff)),
+            )
+            .child(
+                div()
+                    .child("This example demonstrates proper coordinate handling in MetalView")
+                    .text_color(rgb(0xaaaaaa)),
+            )
+            .child(
+                div()
+                    .flex()
+                    .gap_4()
+                    .child(
+                        div()
+                            .flex_col()
+                            .gap_2()
+                            .flex_1()
+                            .child(
+                                div()
+                                    .child("Small MetalView (200x150)")
+                                    .text_sm()
+                                    .text_color(rgb(0xcccccc)),
+                            )
+                            .child(
+                                div()
+                                    .border_1()
+                                    .border_color(rgb(0x444444))
+                                    .rounded_md()
+                                    .overflow_hidden()
+                                    .child(
+                                        #[cfg(target_os = "macos")]
+                                        {
+                                            let callback = self.create_render_callback();
+                                            metal_view()
+                                                .render_with_shared(callback)
+                                                .w(px(200.0))
+                                                .h(px(150.0))
+                                                .bg(rgb(0x000000))
+                                        },
+                                        #[cfg(not(target_os = "macos"))]
+                                        {
+                                            div()
+                                                .w(px(200.0))
+                                                .h(px(150.0))
+                                                .bg(rgb(0x222222))
+                                                .flex()
+                                                .items_center()
+                                                .justify_center()
+                                                .child(
+                                                    div()
+                                                        .child("Metal (macOS only)")
+                                                        .text_color(rgb(0x666666)),
+                                                )
+                                        },
+                                    ),
+                            ),
+                    )
+                    .child(
+                        div()
+                            .flex_col()
+                            .gap_2()
+                            .flex_1()
+                            .child(
+                                div()
+                                    .child("Large MetalView (400x300)")
+                                    .text_sm()
+                                    .text_color(rgb(0xcccccc)),
+                            )
+                            .child(
+                                div()
+                                    .border_1()
+                                    .border_color(rgb(0x444444))
+                                    .rounded_md()
+                                    .overflow_hidden()
+                                    .child(
+                                        #[cfg(target_os = "macos")]
+                                        {
+                                            let callback = self.create_render_callback();
+                                            metal_view()
+                                                .render_with_shared(callback)
+                                                .w(px(400.0))
+                                                .h(px(300.0))
+                                                .bg(rgb(0x000000))
+                                        },
+                                        #[cfg(not(target_os = "macos"))]
+                                        {
+                                            div()
+                                                .w(px(400.0))
+                                                .h(px(300.0))
+                                                .bg(rgb(0x222222))
+                                                .flex()
+                                                .items_center()
+                                                .justify_center()
+                                                .child(
+                                                    div()
+                                                        .child("Metal (macOS only)")
+                                                        .text_color(rgb(0x666666)),
+                                                )
+                                        },
+                                    ),
+                            ),
+                    ),
+            )
+            .child(
+                div().p_4().bg(rgb(0x2a2a2a)).rounded_md().child(
+                    div()
+                        .flex()
+                        .flex_col()
+                        .gap_2()
+                        .child(
+                            div()
+                                .child("Key Features:")
+                                .text_base()
+                                .font_weight(FontWeight::SEMIBOLD)
+                                .text_color(rgb(0xffffff)),
+                        )
+                        .child(
+                            div()
+                                .child("• Proper coordinate transformation from pixels to NDC")
+                                .text_sm()
+                                .text_color(rgb(0xaaaaaa)),
+                        )
+                        .child(
+                            div()
+                                .child("• Scissor rectangle to clip content to bounds")
+                                .text_sm()
+                                .text_color(rgb(0xaaaaaa)),
+                        )
+                        .child(
+                            div()
+                                .child("• Viewport size passed as uniform to shader")
+                                .text_sm()
+                                .text_color(rgb(0xaaaaaa)),
+                        )
+                        .child(
+                            div()
+                                .child("• Gradient fills entire MetalView bounds")
+                                .text_sm()
+                                .text_color(rgb(0xaaaaaa)),
+                        ),
+                ),
+            )
+    }
+}
+
+fn main() {
+    Application::new().run(|cx: &mut App| {
+        let _ = cx.open_window(
+            WindowOptions {
+                window_bounds: Some(WindowBounds::Windowed(Bounds::centered(
+                    None,
+                    size(px(900.0), px(600.0)),
+                    cx,
+                ))),
+                titlebar: Some(TitlebarOptions {
+                    title: Some("Metal Quad Example".into()),
+                    ..Default::default()
+                }),
+                ..Default::default()
+            },
+            |_window, cx| cx.new(|_cx| MetalQuadExample::new()),
+        );
+    });
+}

crates/gpui/examples/metal_view_simple.rs 🔗

@@ -0,0 +1,198 @@
+use gpui::{prelude::*, *};
+
+struct MetalViewSimpleExample {
+    show_metal_view: bool,
+}
+
+impl MetalViewSimpleExample {
+    fn new() -> Self {
+        Self {
+            show_metal_view: true,
+        }
+    }
+}
+
+impl Render for MetalViewSimpleExample {
+    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+        div()
+            .flex()
+            .flex_col()
+            .bg(rgb(0x1e1e1e))
+            .size_full()
+            .p_8()
+            .gap_4()
+            .child(
+                div()
+                    .child("MetalView Simple Example")
+                    .text_2xl()
+                    .text_color(rgb(0xffffff)),
+            )
+            .child(
+                div()
+                    .flex()
+                    .gap_2()
+                    .items_center()
+                    .child(
+                        div()
+                            .id("toggle-button")
+                            .px_3()
+                            .py_1()
+                            .bg(rgb(0x3b82f6))
+                            .hover(|style| style.bg(rgb(0x2563eb)))
+                            .rounded_md()
+                            .cursor_pointer()
+                            .on_click(cx.listener(|this, _event, _window, cx| {
+                                this.show_metal_view = !this.show_metal_view;
+                                cx.notify();
+                            }))
+                            .child(div().child("Toggle MetalView").text_color(rgb(0xffffff))),
+                    )
+                    .child(
+                        div()
+                            .child(format!(
+                                "MetalView is: {}",
+                                if self.show_metal_view {
+                                    "visible"
+                                } else {
+                                    "hidden"
+                                }
+                            ))
+                            .text_color(rgb(0xaaaaaa)),
+                    ),
+            )
+            .child(
+                div()
+                    .flex()
+                    .flex_col()
+                    .gap_4()
+                    .p_4()
+                    .bg(rgb(0x2a2a2a))
+                    .rounded_md()
+                    .child(
+                        div()
+                            .child("Container with MetalView")
+                            .text_lg()
+                            .text_color(rgb(0xffffff)),
+                    )
+                    .when(self.show_metal_view, |parent| {
+                        parent.child(
+                            div()
+                                .border_2()
+                                .border_color(rgb(0x444444))
+                                .rounded_md()
+                                .overflow_hidden()
+                                .child(
+                                    #[cfg(target_os = "macos")]
+                                    {
+                                        metal_view()
+                                            .w_full()
+                                            .h(px(200.0))
+                                            .bg(rgb(0x1a1a1a))
+                                            .render_with(
+                                                |_encoder, _target, _bounds, _scale_factor| {
+                                                    // This callback would contain custom Metal rendering code
+                                                    // For now, it's just a placeholder
+                                                },
+                                            )
+                                    },
+                                    #[cfg(not(target_os = "macos"))]
+                                    {
+                                        div()
+                                            .w_full()
+                                            .h(px(200.0))
+                                            .bg(rgb(0x1a1a1a))
+                                            .flex()
+                                            .items_center()
+                                            .justify_center()
+                                            .child(
+                                                div()
+                                                    .child("MetalView (macOS only)")
+                                                    .text_color(rgb(0x666666)),
+                                            )
+                                    },
+                                ),
+                        )
+                    })
+                    .child(
+                        div()
+                            .flex()
+                            .gap_4()
+                            .child(
+                                div().flex_1().p_3().bg(rgb(0x333333)).rounded_md().child(
+                                    div()
+                                        .child("Regular GPUI content")
+                                        .text_sm()
+                                        .text_color(rgb(0xcccccc)),
+                                ),
+                            )
+                            .child(
+                                div().flex_1().p_3().bg(rgb(0x333333)).rounded_md().child(
+                                    div()
+                                        .child("Can be mixed with MetalView")
+                                        .text_sm()
+                                        .text_color(rgb(0xcccccc)),
+                                ),
+                            ),
+                    ),
+            )
+            .child(
+                div().mt_4().p_4().bg(rgb(0x2a2a2a)).rounded_md().child(
+                    div()
+                        .flex()
+                        .flex_col()
+                        .gap_2()
+                        .child(
+                            div()
+                                .child("Notes:")
+                                .text_base()
+                                .font_weight(FontWeight::SEMIBOLD)
+                                .text_color(rgb(0xffffff)),
+                        )
+                        .child(
+                            div()
+                                .child("• MetalView integrates with GPUI's layout system")
+                                .text_sm()
+                                .text_color(rgb(0xaaaaaa)),
+                        )
+                        .child(
+                            div()
+                                .child("• It can be styled with the same methods as other elements")
+                                .text_sm()
+                                .text_color(rgb(0xaaaaaa)),
+                        )
+                        .child(
+                            div()
+                                .child("• On macOS, it would render custom Metal content")
+                                .text_sm()
+                                .text_color(rgb(0xaaaaaa)),
+                        )
+                        .child(
+                            div()
+                                .child("• On other platforms, a fallback can be provided")
+                                .text_sm()
+                                .text_color(rgb(0xaaaaaa)),
+                        ),
+                ),
+            )
+    }
+}
+
+fn main() {
+    Application::new().run(|cx: &mut App| {
+        let _ = cx.open_window(
+            WindowOptions {
+                window_bounds: Some(WindowBounds::Windowed(Bounds::centered(
+                    None,
+                    size(px(800.0), px(600.0)),
+                    cx,
+                ))),
+                titlebar: Some(TitlebarOptions {
+                    title: Some("MetalView Simple Example".into()),
+                    ..Default::default()
+                }),
+                ..Default::default()
+            },
+            |_window, cx| cx.new(|_cx| MetalViewSimpleExample::new()),
+        );
+    });
+}

crates/gpui/src/elements/metal_view.rs 🔗

@@ -0,0 +1,196 @@
+use crate::{
+    App, Bounds, Element, ElementId, GlobalElementId, InspectorElementId, IntoElement, LayoutId,
+    Pixels, Style, StyleRefinement, Styled, Window,
+};
+use refineable::Refineable;
+use std::sync::Arc;
+
+#[cfg(target_os = "macos")]
+use metal::{RenderCommandEncoderRef, TextureRef};
+
+/// A callback for custom Metal rendering.
+///
+/// The callback receives:
+/// - command_encoder: The Metal command encoder to issue draw calls
+/// - target_texture: The texture to render into
+/// - bounds: The bounds of the element in pixels
+/// - scale_factor: The window's scale factor
+#[cfg(target_os = "macos")]
+pub type MetalRenderCallback =
+    Arc<dyn Fn(&RenderCommandEncoderRef, &TextureRef, Bounds<Pixels>, f32) + Send + Sync + 'static>;
+
+/// A view that allows custom Metal rendering.
+pub struct MetalView {
+    #[cfg(target_os = "macos")]
+    render_callback: Option<MetalRenderCallback>,
+    style: StyleRefinement,
+}
+
+/// Create a new Metal view element.
+pub fn metal_view() -> MetalView {
+    MetalView {
+        #[cfg(target_os = "macos")]
+        render_callback: None,
+        style: Default::default(),
+    }
+}
+
+impl MetalView {
+    /// Set the Metal render callback.
+    #[cfg(target_os = "macos")]
+    pub fn render_with<F>(mut self, callback: F) -> Self
+    where
+        F: Fn(&RenderCommandEncoderRef, &TextureRef, Bounds<Pixels>, f32) + Send + Sync + 'static,
+    {
+        self.render_callback = Some(Arc::new(callback));
+        self
+    }
+
+    /// Set the Metal render callback using a shared callback.
+    #[cfg(target_os = "macos")]
+    pub fn render_with_shared(mut self, callback: MetalRenderCallback) -> Self {
+        self.render_callback = Some(callback);
+        self
+    }
+}
+
+impl Element for MetalView {
+    type RequestLayoutState = ();
+    type PrepaintState = ();
+
+    fn id(&self) -> Option<ElementId> {
+        None
+    }
+
+    fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
+        None
+    }
+
+    fn request_layout(
+        &mut self,
+        _global_id: Option<&GlobalElementId>,
+        _inspector_id: Option<&InspectorElementId>,
+        window: &mut Window,
+        cx: &mut App,
+    ) -> (LayoutId, Self::RequestLayoutState) {
+        let mut style = Style::default();
+        style.refine(&self.style);
+        let layout_id = window.request_layout(style, [], cx);
+        (layout_id, ())
+    }
+
+    fn prepaint(
+        &mut self,
+        _global_id: Option<&GlobalElementId>,
+        _inspector_id: Option<&InspectorElementId>,
+        _bounds: Bounds<Pixels>,
+        _request_layout: &mut Self::RequestLayoutState,
+        _window: &mut Window,
+        _cx: &mut App,
+    ) -> Self::PrepaintState {
+    }
+
+    fn paint(
+        &mut self,
+        _global_id: Option<&GlobalElementId>,
+        _inspector_id: Option<&InspectorElementId>,
+        bounds: Bounds<Pixels>,
+        _: &mut Self::RequestLayoutState,
+        _: &mut Self::PrepaintState,
+        window: &mut Window,
+        _: &mut App,
+    ) {
+        #[cfg(target_os = "macos")]
+        if let Some(render_callback) = &self.render_callback {
+            // TODO: This is a placeholder. In a real implementation, we would need to:
+            // 1. Register this Metal view with the window's rendering system
+            // 2. Ensure the callback is invoked during the Metal rendering pass
+            // 3. Handle proper clipping and transformation matrices
+            //
+            // For now, we'll store the callback and bounds in the window's custom render queue
+            window.paint_metal_view(bounds, render_callback.clone());
+        }
+    }
+}
+
+impl IntoElement for MetalView {
+    type Element = Self;
+
+    fn into_element(self) -> Self::Element {
+        self
+    }
+}
+
+impl Styled for MetalView {
+    fn style(&mut self) -> &mut StyleRefinement {
+        &mut self.style
+    }
+}
+
+/// Extension trait for MetalView to provide platform-agnostic API
+pub trait MetalViewExt {
+    /// Set a placeholder render function for non-macOS platforms
+    fn render_placeholder<F>(self, callback: F) -> Self
+    where
+        F: Fn(Bounds<Pixels>) + Send + Sync + 'static;
+}
+
+impl MetalViewExt for MetalView {
+    fn render_placeholder<F>(self, _callback: F) -> Self
+    where
+        F: Fn(Bounds<Pixels>) + Send + Sync + 'static,
+    {
+        // On non-macOS platforms, this could render a placeholder
+        // or use a different rendering backend
+        self
+    }
+}
+
+#[cfg(target_os = "macos")]
+/// Helper functions for creating common Metal render callbacks
+pub mod helpers {
+    use super::*;
+    use metal::*;
+
+    /// Helper to create a simple colored rectangle Metal renderer
+    pub fn solid_color_renderer(r: f32, g: f32, b: f32, a: f32) -> MetalRenderCallback {
+        Arc::new(move |encoder, _texture, bounds, _scale_factor| {
+            // This is a simplified example. In practice, you would:
+            // 1. Create or reuse a render pipeline state
+            // 2. Set up vertex data for the bounds
+            // 3. Issue draw calls
+            // 4. Handle proper coordinate transformation
+
+            // For now, this is just a placeholder to show the API design
+            let _ = (encoder, bounds, r, g, b, a);
+        })
+    }
+
+    /// Helper to create a Metal renderer that draws a textured quad
+    pub fn textured_quad_renderer(texture: Texture) -> MetalRenderCallback {
+        Arc::new(move |encoder, _target, bounds, _scale_factor| {
+            // Similar to above, this would set up a textured quad rendering
+            let _ = (encoder, &texture, bounds);
+        })
+    }
+}
+
+// Example usage:
+// ```rust
+// use gpui::elements::{metal_view, MetalViewExt};
+//
+// #[cfg(target_os = "macos")]
+// let view = metal_view()
+//     .render_with(|encoder, target, bounds, scale_factor| {
+//         // Custom Metal rendering code here
+//         // You have full access to Metal command encoder
+//     })
+//     .size_full();
+//
+// #[cfg(not(target_os = "macos"))]
+// let view = metal_view()
+//     .render_placeholder(|bounds| {
+//         // Fallback rendering for non-macOS platforms
+//     })
+//     .size_full();
+// ```

crates/gpui/src/elements/mod.rs 🔗

@@ -6,6 +6,9 @@ mod div;
 mod image_cache;
 mod img;
 mod list;
+/// Metal-based custom rendering for macOS
+#[cfg(target_os = "macos")]
+pub mod metal_view;
 mod surface;
 mod svg;
 mod text;
@@ -19,6 +22,8 @@ pub use div::*;
 pub use image_cache::*;
 pub use img::*;
 pub use list::*;
+#[cfg(target_os = "macos")]
+pub use metal_view::*;
 pub use surface::*;
 pub use svg::*;
 pub use text::*;

crates/gpui/src/gpui.rs 🔗

@@ -129,6 +129,8 @@ pub use assets::*;
 pub use color::*;
 pub use ctor::ctor;
 pub use element::*;
+#[cfg(target_os = "macos")]
+pub use elements::metal_view::MetalRenderCallback;
 pub use elements::*;
 pub use executor::*;
 pub use geometry::*;

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

@@ -9,6 +9,8 @@ mod screen_capture;
 
 #[cfg(not(feature = "macos-blade"))]
 mod metal_atlas;
+// #[cfg(not(feature = "macos-blade"))]
+// mod metal_render_pass;
 #[cfg(not(feature = "macos-blade"))]
 pub mod metal_renderer;
 

crates/gpui/src/platform/mac/metal_render_pass.rs 🔗

@@ -0,0 +1,343 @@
+use crate::{DevicePixels, PaintMetalView, PrimitiveBatch, ScaledPixels, Scene, Size};
+use metal::{
+    CommandBufferRef, CommandQueue, Device, MTLLoadAction, MTLStoreAction, RenderCommandEncoderRef,
+};
+
+/// Represents a single render command in the rendering pipeline
+#[derive(Debug)]
+pub enum RenderCommand<'a> {
+    /// Begin a new render pass with the specified configuration
+    BeginRenderPass { descriptor: RenderPassDescriptor },
+    /// Draw a batch of GPUI primitives
+    DrawPrimitives {
+        batch: PrimitiveBatch<'a>,
+        viewport_size: Size<DevicePixels>,
+    },
+    /// Execute custom Metal rendering
+    ExecuteMetalCallback {
+        metal_view: &'a PaintMetalView,
+        viewport_size: Size<DevicePixels>,
+    },
+    /// End the current render pass
+    EndRenderPass,
+}
+
+/// Configuration for a render pass
+#[derive(Clone, Debug)]
+pub struct RenderPassDescriptor {
+    pub texture: metal::Texture,
+    pub load_action: MTLLoadAction,
+    pub store_action: MTLStoreAction,
+    pub clear_color: metal::MTLClearColor,
+    pub viewport: metal::MTLViewport,
+}
+
+/// State that needs to be preserved across render pass breaks
+#[derive(Clone, Debug)]
+pub struct RenderState {
+    pub viewport: metal::MTLViewport,
+    pub blend_mode: Option<BlendMode>,
+    // Add other state that needs to be preserved
+}
+
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum BlendMode {
+    Normal,
+    Multiply,
+    Screen,
+    // Add other blend modes as needed
+}
+
+/// Context provided to Metal render callbacks
+pub struct MetalRenderContext<'a> {
+    pub command_buffer: &'a CommandBufferRef,
+    pub drawable_texture: &'a metal::TextureRef,
+    pub viewport_size: Size<DevicePixels>,
+    pub device: &'a Device,
+    pub bounds: crate::Bounds<ScaledPixels>,
+    pub scale_factor: f32,
+}
+
+/// Manages the rendering pipeline with support for render pass breaks
+pub struct RenderPassManager {
+    device: Device,
+    command_queue: CommandQueue,
+    current_state: RenderState,
+}
+
+impl RenderPassManager {
+    pub fn new(device: Device, command_queue: CommandQueue) -> Self {
+        Self {
+            device,
+            command_queue,
+            current_state: RenderState {
+                viewport: metal::MTLViewport {
+                    originX: 0.0,
+                    originY: 0.0,
+                    width: 0.0,
+                    height: 0.0,
+                    znear: 0.0,
+                    zfar: 1.0,
+                },
+                blend_mode: None,
+            },
+        }
+    }
+
+    /// Convert a scene into a list of render commands
+    pub fn build_render_commands<'a>(
+        &self,
+        scene: &'a Scene,
+        drawable_texture: &metal::TextureRef,
+        viewport_size: Size<DevicePixels>,
+        is_opaque: bool,
+    ) -> Vec<RenderCommand<'a>> {
+        let mut commands = Vec::new();
+
+        // Initial render pass configuration
+        let alpha = if is_opaque { 1.0 } else { 0.0 };
+        let descriptor = RenderPassDescriptor {
+            texture: drawable_texture.to_owned(),
+            load_action: MTLLoadAction::Clear,
+            store_action: MTLStoreAction::Store,
+            clear_color: metal::MTLClearColor::new(0.0, 0.0, 0.0, alpha),
+            viewport: metal::MTLViewport {
+                originX: 0.0,
+                originY: 0.0,
+                width: i32::from(viewport_size.width) as f64,
+                height: i32::from(viewport_size.height) as f64,
+                znear: 0.0,
+                zfar: 1.0,
+            },
+        };
+
+        commands.push(RenderCommand::BeginRenderPass { descriptor });
+
+        // Process batches, inserting render pass breaks for MetalViews
+        let mut in_render_pass = true;
+
+        for batch in scene.batches() {
+            match batch {
+                #[cfg(target_os = "macos")]
+                PrimitiveBatch::MetalViews(metal_views) => {
+                    // End current render pass
+                    if in_render_pass {
+                        commands.push(RenderCommand::EndRenderPass);
+                        in_render_pass = false;
+                    }
+
+                    // Add commands for each MetalView
+                    for metal_view in metal_views {
+                        commands.push(RenderCommand::ExecuteMetalCallback {
+                            metal_view,
+                            viewport_size,
+                        });
+                    }
+                }
+                _ => {
+                    // Ensure we're in a render pass
+                    if !in_render_pass {
+                        let descriptor = RenderPassDescriptor {
+                            texture: drawable_texture.to_owned(),
+                            load_action: MTLLoadAction::Load, // Load existing content
+                            store_action: MTLStoreAction::Store,
+                            clear_color: metal::MTLClearColor::new(0.0, 0.0, 0.0, 0.0),
+                            viewport: self.current_state.viewport,
+                        };
+                        commands.push(RenderCommand::BeginRenderPass { descriptor });
+                        in_render_pass = true;
+                    }
+
+                    // Add primitive drawing command
+                    commands.push(RenderCommand::DrawPrimitives {
+                        batch,
+                        viewport_size,
+                    });
+                }
+            }
+        }
+
+        // Ensure we end the final render pass
+        if in_render_pass {
+            commands.push(RenderCommand::EndRenderPass);
+        }
+
+        commands
+    }
+
+    /// Execute a list of render commands
+    pub fn execute_commands<F>(
+        &mut self,
+        commands: &[RenderCommand],
+        command_buffer: &CommandBufferRef,
+        drawable_texture: &metal::TextureRef,
+        mut draw_primitives: F,
+    ) -> Result<(), anyhow::Error>
+    where
+        F: FnMut(
+            PrimitiveBatch,
+            &RenderCommandEncoderRef,
+            Size<DevicePixels>,
+        ) -> Result<(), anyhow::Error>,
+    {
+        let mut current_encoder: Option<metal::RenderCommandEncoder> = None;
+
+        for command in commands {
+            match command {
+                RenderCommand::BeginRenderPass { descriptor } => {
+                    // End any existing encoder
+                    if let Some(encoder) = current_encoder.take() {
+                        encoder.end_encoding();
+                    }
+
+                    // Create new render pass
+                    let render_pass_descriptor = metal::RenderPassDescriptor::new();
+                    let color_attachment = render_pass_descriptor
+                        .color_attachments()
+                        .object_at(0)
+                        .unwrap();
+
+                    color_attachment.set_texture(Some(&descriptor.texture));
+                    color_attachment.set_load_action(descriptor.load_action);
+                    color_attachment.set_store_action(descriptor.store_action);
+                    color_attachment.set_clear_color(descriptor.clear_color);
+
+                    let encoder =
+                        command_buffer.new_render_command_encoder(&render_pass_descriptor);
+                    encoder.set_viewport(descriptor.viewport);
+                    self.current_state.viewport = descriptor.viewport;
+
+                    current_encoder = Some(encoder);
+                }
+
+                RenderCommand::DrawPrimitives {
+                    batch,
+                    viewport_size,
+                } => {
+                    if let Some(ref encoder) = current_encoder {
+                        draw_primitives(*batch, encoder, *viewport_size)?;
+                    }
+                }
+
+                RenderCommand::ExecuteMetalCallback {
+                    metal_view,
+                    viewport_size,
+                } => {
+                    // End current encoder if any
+                    if let Some(encoder) = current_encoder.take() {
+                        encoder.end_encoding();
+                    }
+
+                    // Create context for the callback
+                    let context = MetalRenderContext {
+                        command_buffer,
+                        drawable_texture,
+                        viewport_size: *viewport_size,
+                        device: &self.device,
+                        bounds: metal_view.bounds.clone(),
+                        scale_factor: 2.0, // TODO: Get actual scale factor
+                    };
+
+                    // Create a new render command encoder for the callback
+                    let render_pass_descriptor = metal::RenderPassDescriptor::new();
+                    let color_attachment = render_pass_descriptor
+                        .color_attachments()
+                        .object_at(0)
+                        .unwrap();
+
+                    color_attachment.set_texture(Some(drawable_texture));
+                    color_attachment.set_load_action(MTLLoadAction::Load);
+                    color_attachment.set_store_action(MTLStoreAction::Store);
+
+                    let encoder =
+                        command_buffer.new_render_command_encoder(&render_pass_descriptor);
+
+                    // Invoke the callback
+                    (metal_view.render_callback)(
+                        &encoder,
+                        drawable_texture,
+                        context.bounds.into(),
+                        context.scale_factor,
+                    );
+
+                    encoder.end_encoding();
+                }
+
+                RenderCommand::EndRenderPass => {
+                    if let Some(encoder) = current_encoder.take() {
+                        encoder.end_encoding();
+                    }
+                }
+            }
+        }
+
+        // Ensure any remaining encoder is ended
+        if let Some(encoder) = current_encoder {
+            encoder.end_encoding();
+        }
+
+        Ok(())
+    }
+
+    /// Save the current render state
+    pub fn save_state(&self) -> RenderState {
+        self.current_state.clone()
+    }
+
+    /// Restore a previously saved render state
+    pub fn restore_state(&mut self, state: RenderState) {
+        self.current_state = state;
+    }
+}
+
+/// Builder for constructing render command lists
+pub struct RenderCommandBuilder<'a> {
+    commands: Vec<RenderCommand<'a>>,
+}
+
+impl<'a> RenderCommandBuilder<'a> {
+    pub fn new() -> Self {
+        Self {
+            commands: Vec::new(),
+        }
+    }
+
+    pub fn begin_render_pass(mut self, descriptor: RenderPassDescriptor) -> Self {
+        self.commands
+            .push(RenderCommand::BeginRenderPass { descriptor });
+        self
+    }
+
+    pub fn draw_primitives(
+        mut self,
+        batch: PrimitiveBatch<'a>,
+        viewport_size: Size<DevicePixels>,
+    ) -> Self {
+        self.commands.push(RenderCommand::DrawPrimitives {
+            batch,
+            viewport_size,
+        });
+        self
+    }
+
+    pub fn execute_metal_callback(
+        mut self,
+        metal_view: &'a PaintMetalView,
+        viewport_size: Size<DevicePixels>,
+    ) -> Self {
+        self.commands.push(RenderCommand::ExecuteMetalCallback {
+            metal_view,
+            viewport_size,
+        });
+        self
+    }
+
+    pub fn end_render_pass(mut self) -> Self {
+        self.commands.push(RenderCommand::EndRenderPass);
+        self
+    }
+
+    pub fn build(self) -> Vec<RenderCommand<'a>> {
+        self.commands
+    }
+}

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

@@ -2,7 +2,7 @@ use super::metal_atlas::MetalAtlas;
 use crate::{
     AtlasTextureId, AtlasTextureKind, AtlasTile, Background, Bounds, ContentMask, DevicePixels,
     MonochromeSprite, PaintSurface, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch,
-    Quad, ScaledPixels, Scene, Shadow, Size, Surface, Underline, point, size,
+    Quad, ScaledPixels, Scene, Shadow, Size, Surface, Underline, point, px, size,
 };
 use anyhow::{Context as _, Result};
 use block::ConcreteBlock;
@@ -18,7 +18,7 @@ use core_video::{
     pixel_buffer::kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
 };
 use foreign_types::{ForeignType, ForeignTypeRef};
-use metal::{CAMetalLayer, CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
+use metal::{CAMetalLayer, MTLPixelFormat, MTLResourceOptions, NSRange};
 use objc::{self, msg_send, sel, sel_impl};
 use parking_lot::Mutex;
 use smallvec::SmallVec;
@@ -97,7 +97,7 @@ pub(crate) struct MetalRenderer {
     device: metal::Device,
     layer: metal::MetalLayer,
     presents_with_transaction: bool,
-    command_queue: CommandQueue,
+    command_queue: metal::CommandQueue,
     paths_rasterization_pipeline_state: metal::RenderPipelineState,
     path_sprites_pipeline_state: metal::RenderPipelineState,
     shadows_pipeline_state: metal::RenderPipelineState,
@@ -385,6 +385,7 @@ impl MetalRenderer {
             )
             .with_context(|| format!("rasterizing {} paths", scene.paths().len()))?;
 
+        // Create initial render pass
         let render_pass_descriptor = metal::RenderPassDescriptor::new();
         let color_attachment = render_pass_descriptor
             .color_attachments()
@@ -396,7 +397,7 @@ impl MetalRenderer {
         color_attachment.set_store_action(metal::MTLStoreAction::Store);
         let alpha = if self.layer.is_opaque() { 1. } else { 0. };
         color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., alpha));
-        let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
+        let mut command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
 
         command_encoder.set_viewport(metal::MTLViewport {
             originX: 0.0,
@@ -407,21 +408,53 @@ impl MetalRenderer {
             zfar: 1.0,
         });
 
+        let mut needs_new_encoder = false;
+
+        // Helper to create a continuation render encoder
+        let create_continuation_encoder = || {
+            let render_pass_descriptor = metal::RenderPassDescriptor::new();
+            let color_attachment = render_pass_descriptor
+                .color_attachments()
+                .object_at(0)
+                .unwrap();
+
+            color_attachment.set_texture(Some(drawable.texture()));
+            color_attachment.set_load_action(metal::MTLLoadAction::Load);
+            color_attachment.set_store_action(metal::MTLStoreAction::Store);
+
+            let encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
+            encoder.set_viewport(metal::MTLViewport {
+                originX: 0.0,
+                originY: 0.0,
+                width: i32::from(viewport_size.width) as f64,
+                height: i32::from(viewport_size.height) as f64,
+                znear: 0.0,
+                zfar: 1.0,
+            });
+            encoder
+        };
+
         for batch in scene.batches() {
+            // Create a new encoder if needed
+            if needs_new_encoder {
+                command_encoder = create_continuation_encoder();
+                needs_new_encoder = false;
+            }
+
             let ok = match batch {
                 PrimitiveBatch::Shadows(shadows) => self.draw_shadows(
                     shadows,
                     instance_buffer,
                     &mut instance_offset,
                     viewport_size,
-                    command_encoder,
+                    &command_encoder,
                 ),
                 PrimitiveBatch::Quads(quads) => self.draw_quads(
                     quads,
                     instance_buffer,
                     &mut instance_offset,
                     viewport_size,
-                    command_encoder,
+                    &command_encoder,
                 ),
                 PrimitiveBatch::Paths(paths) => self.draw_paths(
                     paths,
@@ -429,14 +462,14 @@ impl MetalRenderer {
                     instance_buffer,
                     &mut instance_offset,
                     viewport_size,
-                    command_encoder,
+                    &command_encoder,
                 ),
                 PrimitiveBatch::Underlines(underlines) => self.draw_underlines(
                     underlines,
                     instance_buffer,
                     &mut instance_offset,
                     viewport_size,
-                    command_encoder,
+                    &command_encoder,
                 ),
                 PrimitiveBatch::MonochromeSprites {
                     texture_id,
@@ -447,7 +480,7 @@ impl MetalRenderer {
                     instance_buffer,
                     &mut instance_offset,
                     viewport_size,
-                    command_encoder,
+                    &command_encoder,
                 ),
                 PrimitiveBatch::PolychromeSprites {
                     texture_id,
@@ -458,15 +491,72 @@ impl MetalRenderer {
                     instance_buffer,
                     &mut instance_offset,
                     viewport_size,
-                    command_encoder,
+                    &command_encoder,
                 ),
                 PrimitiveBatch::Surfaces(surfaces) => self.draw_surfaces(
                     surfaces,
                     instance_buffer,
                     &mut instance_offset,
                     viewport_size,
-                    command_encoder,
+                    &command_encoder,
                 ),
+                #[cfg(target_os = "macos")]
+                PrimitiveBatch::MetalViews(metal_views) => {
+                    // End current render pass
+                    command_encoder.end_encoding();
+
+                    // Process each MetalView
+                    for metal_view in metal_views {
+                        // Create a render encoder for the callback
+                        let render_pass_descriptor = metal::RenderPassDescriptor::new();
+                        let color_attachment = render_pass_descriptor
+                            .color_attachments()
+                            .object_at(0)
+                            .unwrap();
+
+                        color_attachment.set_texture(Some(drawable.texture()));
+                        color_attachment.set_load_action(metal::MTLLoadAction::Load);
+                        color_attachment.set_store_action(metal::MTLStoreAction::Store);
+
+                        let callback_encoder =
+                            command_buffer.new_render_command_encoder(render_pass_descriptor);
+                        callback_encoder.set_viewport(metal::MTLViewport {
+                            originX: 0.0,
+                            originY: 0.0,
+                            width: i32::from(viewport_size.width) as f64,
+                            height: i32::from(viewport_size.height) as f64,
+                            znear: 0.0,
+                            zfar: 1.0,
+                        });
+
+                        // Invoke the Metal rendering callback
+                        let scale_factor = self.layer.contents_scale() as f32;
+                        // Convert bounds from ScaledPixels to Pixels
+                        let bounds = Bounds {
+                            origin: point(
+                                px(metal_view.bounds.origin.x.0 / scale_factor),
+                                px(metal_view.bounds.origin.y.0 / scale_factor),
+                            ),
+                            size: size(
+                                px(metal_view.bounds.size.width.0 / scale_factor),
+                                px(metal_view.bounds.size.height.0 / scale_factor),
+                            ),
+                        };
+
+                        (metal_view.render_callback)(
+                            &callback_encoder,
+                            drawable.texture(),
+                            bounds,
+                            scale_factor,
+                        );
+
+                        callback_encoder.end_encoding();
+                    }
+
+                    // Mark that we'll need a new encoder for subsequent primitives
+                    needs_new_encoder = true;
+                    true
+                }
             };
 
             if !ok {
@@ -484,7 +574,10 @@ impl MetalRenderer {
             }
         }
 
-        command_encoder.end_encoding();
+        // End the encoder if we haven't already
+        if !needs_new_encoder {
+            command_encoder.end_encoding();
+        }
 
         instance_buffer.metal_buffer.did_modify_range(NSRange {
             location: 0,
@@ -1134,6 +1227,9 @@ impl MetalRenderer {
         }
         true
     }
+
+    // Note: draw_metal_views is no longer needed as we handle MetalViews
+    // directly in draw_primitives with proper render pass management
 }
 
 fn build_pipeline_state(

crates/gpui/src/scene.rs 🔗

@@ -27,6 +27,8 @@ pub(crate) struct Scene {
     pub(crate) monochrome_sprites: Vec<MonochromeSprite>,
     pub(crate) polychrome_sprites: Vec<PolychromeSprite>,
     pub(crate) surfaces: Vec<PaintSurface>,
+    #[cfg(target_os = "macos")]
+    pub(crate) metal_views: Vec<PaintMetalView>,
 }
 
 impl Scene {
@@ -115,6 +117,11 @@ impl Scene {
                 surface.order = order;
                 self.surfaces.push(surface.clone());
             }
+            #[cfg(target_os = "macos")]
+            Primitive::MetalView(metal_view) => {
+                metal_view.order = order;
+                self.metal_views.push(metal_view.clone());
+            }
         }
         self.paint_operations
             .push(PaintOperation::Primitive(primitive));
@@ -140,6 +147,8 @@ impl Scene {
         self.polychrome_sprites
             .sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id));
         self.surfaces.sort_by_key(|surface| surface.order);
+        #[cfg(target_os = "macos")]
+        self.metal_views.sort_by_key(|metal_view| metal_view.order);
     }
 
     #[cfg_attr(
@@ -172,6 +181,12 @@ impl Scene {
             surfaces: &self.surfaces,
             surfaces_start: 0,
             surfaces_iter: self.surfaces.iter().peekable(),
+            #[cfg(target_os = "macos")]
+            metal_views: &self.metal_views,
+            #[cfg(target_os = "macos")]
+            metal_views_start: 0,
+            #[cfg(target_os = "macos")]
+            metal_views_iter: self.metal_views.iter().peekable(),
         }
     }
 }
@@ -193,6 +208,8 @@ pub(crate) enum PrimitiveKind {
     MonochromeSprite,
     PolychromeSprite,
     Surface,
+    #[cfg(target_os = "macos")]
+    MetalView,
 }
 
 pub(crate) enum PaintOperation {
@@ -210,6 +227,8 @@ pub(crate) enum Primitive {
     MonochromeSprite(MonochromeSprite),
     PolychromeSprite(PolychromeSprite),
     Surface(PaintSurface),
+    #[cfg(target_os = "macos")]
+    MetalView(PaintMetalView),
 }
 
 impl Primitive {
@@ -222,6 +241,8 @@ impl Primitive {
             Primitive::MonochromeSprite(sprite) => &sprite.bounds,
             Primitive::PolychromeSprite(sprite) => &sprite.bounds,
             Primitive::Surface(surface) => &surface.bounds,
+            #[cfg(target_os = "macos")]
+            Primitive::MetalView(metal_view) => &metal_view.bounds,
         }
     }
 
@@ -234,6 +255,8 @@ impl Primitive {
             Primitive::MonochromeSprite(sprite) => &sprite.content_mask,
             Primitive::PolychromeSprite(sprite) => &sprite.content_mask,
             Primitive::Surface(surface) => &surface.content_mask,
+            #[cfg(target_os = "macos")]
+            Primitive::MetalView(metal_view) => &metal_view.content_mask,
         }
     }
 }
@@ -267,13 +290,19 @@ struct BatchIterator<'a> {
     surfaces: &'a [PaintSurface],
     surfaces_start: usize,
     surfaces_iter: Peekable<slice::Iter<'a, PaintSurface>>,
+    #[cfg(target_os = "macos")]
+    metal_views: &'a [PaintMetalView],
+    #[cfg(target_os = "macos")]
+    metal_views_start: usize,
+    #[cfg(target_os = "macos")]
+    metal_views_iter: Peekable<slice::Iter<'a, PaintMetalView>>,
 }
 
 impl<'a> Iterator for BatchIterator<'a> {
     type Item = PrimitiveBatch<'a>;
 
     fn next(&mut self) -> Option<Self::Item> {
-        let mut orders_and_kinds = [
+        let mut orders_and_kinds = vec![
             (
                 self.shadows_iter.peek().map(|s| s.order),
                 PrimitiveKind::Shadow,
@@ -297,6 +326,12 @@ impl<'a> Iterator for BatchIterator<'a> {
                 PrimitiveKind::Surface,
             ),
         ];
+
+        #[cfg(target_os = "macos")]
+        orders_and_kinds.push((
+            self.metal_views_iter.peek().map(|m| m.order),
+            PrimitiveKind::MetalView,
+        ));
         orders_and_kinds.sort_by_key(|(order, kind)| (order.unwrap_or(u32::MAX), *kind));
 
         let first = orders_and_kinds[0];
@@ -426,6 +461,23 @@ impl<'a> Iterator for BatchIterator<'a> {
                     &self.surfaces[surfaces_start..surfaces_end],
                 ))
             }
+            #[cfg(target_os = "macos")]
+            PrimitiveKind::MetalView => {
+                let metal_views_start = self.metal_views_start;
+                let mut metal_views_end = metal_views_start + 1;
+                self.metal_views_iter.next();
+                while self
+                    .metal_views_iter
+                    .next_if(|metal_view| (metal_view.order, batch_kind) < max_order_and_kind)
+                    .is_some()
+                {
+                    metal_views_end += 1;
+                }
+                self.metal_views_start = metal_views_end;
+                Some(PrimitiveBatch::MetalViews(
+                    &self.metal_views[metal_views_start..metal_views_end],
+                ))
+            }
         }
     }
 }
@@ -452,6 +504,8 @@ pub(crate) enum PrimitiveBatch<'a> {
         sprites: &'a [PolychromeSprite],
     },
     Surfaces(&'a [PaintSurface]),
+    #[cfg(target_os = "macos")]
+    MetalViews(&'a [PaintMetalView]),
 }
 
 #[derive(Default, Debug, Clone)]
@@ -668,12 +722,38 @@ pub(crate) struct PaintSurface {
     pub image_buffer: core_video::pixel_buffer::CVPixelBuffer,
 }
 
+#[cfg(target_os = "macos")]
+#[derive(Clone)]
+pub(crate) struct PaintMetalView {
+    pub order: DrawOrder,
+    pub bounds: Bounds<ScaledPixels>,
+    pub content_mask: ContentMask<ScaledPixels>,
+    pub render_callback: crate::MetalRenderCallback,
+}
+
+impl Debug for PaintMetalView {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("PaintMetalView")
+            .field("order", &self.order)
+            .field("bounds", &self.bounds)
+            .field("content_mask", &self.content_mask)
+            .finish()
+    }
+}
+
 impl From<PaintSurface> for Primitive {
     fn from(surface: PaintSurface) -> Self {
         Primitive::Surface(surface)
     }
 }
 
+#[cfg(target_os = "macos")]
+impl From<PaintMetalView> for Primitive {
+    fn from(metal_view: PaintMetalView) -> Self {
+        Primitive::MetalView(metal_view)
+    }
+}
+
 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
 pub(crate) struct PathId(pub(crate) usize);
 

crates/gpui/src/window.rs 🔗

@@ -2946,6 +2946,30 @@ impl Window {
         });
     }
 
+    /// Paint a custom Metal view.
+    ///
+    /// This method should only be called as part of the paint phase of element drawing.
+    #[cfg(target_os = "macos")]
+    pub fn paint_metal_view(
+        &mut self,
+        bounds: Bounds<Pixels>,
+        render_callback: crate::MetalRenderCallback,
+    ) {
+        use crate::PaintMetalView;
+
+        self.invalidator.debug_assert_paint();
+
+        let scale_factor = self.scale_factor();
+        let bounds = bounds.scale(scale_factor);
+        let content_mask = self.content_mask().scale(scale_factor);
+        self.next_frame.scene.insert_primitive(PaintMetalView {
+            order: 0,
+            bounds,
+            content_mask,
+            render_callback,
+        });
+    }
+
     /// Removes an image from the sprite atlas.
     pub fn drop_image(&mut self, data: Arc<RenderImage>) -> Result<()> {
         for frame_index in 0..data.frame_count() {