Checkpoint

Antonio Scandurra and Nathan Sobo created

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

Change summary

Cargo.lock                                      |  14 
crates/gpui3/Cargo.toml                         |   2 
crates/gpui3/build.rs                           |  32 
crates/gpui3/src/color.rs                       |   4 
crates/gpui3/src/geometry.rs                    |   4 
crates/gpui3/src/platform/mac/metal_renderer.rs | 402 +++++++++++-------
crates/gpui3/src/platform/mac/shaders.metal     | 189 ++++++++
crates/gpui3/src/scene.rs                       |  60 +-
8 files changed, 516 insertions(+), 191 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -1039,6 +1039,20 @@ name = "bytemuck"
 version = "1.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
+dependencies = [
+ "bytemuck_derive",
+]
+
+[[package]]
+name = "bytemuck_derive"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.29",
+]
 
 [[package]]
 name = "byteorder"

crates/gpui3/Cargo.toml 🔗

@@ -55,7 +55,7 @@ usvg = { version = "0.14", features = [] }
 uuid = { version = "1.1.2", features = ["v4"] }
 waker-fn = "1.1.0"
 slotmap = "1.0.6"
-bytemuck = "1.14.0"
+bytemuck = { version = "1.14.0", features = ["derive"] }
 schemars.workspace = true
 plane-split = "0.18.0"
 

crates/gpui3/build.rs 🔗

@@ -4,6 +4,8 @@ use std::{
     process::{self, Command},
 };
 
+use cbindgen::Config;
+
 fn main() {
     generate_dispatch_bindings();
     let header_path = generate_shader_bindings();
@@ -32,21 +34,29 @@ fn generate_dispatch_bindings() {
 fn generate_shader_bindings() -> PathBuf {
     let output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("scene.h");
     let crate_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
+    let mut config = Config::default();
+    config.include_guard = Some("SCENE_H".into());
+    config.language = cbindgen::Language::C;
+    config.export.include.extend([
+        "Bounds".into(),
+        "Corners".into(),
+        "Edges".into(),
+        "Size".into(),
+        "Pixels".into(),
+        "PointF".into(),
+        "Hsla".into(),
+        "Quad".into(),
+        "QuadInputIndex".into(),
+        "QuadUniforms".into(),
+    ]);
+    config.no_includes = true;
+    config.enumeration.prefix_with_name = true;
     cbindgen::Builder::new()
-        .with_language(cbindgen::Language::C)
-        .with_include_guard("SCENE_H")
         .with_src(crate_dir.join("src/scene.rs"))
         .with_src(crate_dir.join("src/geometry.rs"))
         .with_src(crate_dir.join("src/color.rs"))
-        .with_no_includes()
-        .include_item("Quad")
-        .include_item("Bounds")
-        .include_item("Corners")
-        .include_item("Edges")
-        .include_item("Size")
-        .include_item("Pixels")
-        .include_item("Point")
-        .include_item("Hsla")
+        .with_src(crate_dir.join("src/platform/mac/metal_renderer.rs"))
+        .with_config(config)
         .generate()
         .expect("Unable to generate bindings")
         .write_to_file(&output_path);

crates/gpui3/src/color.rs 🔗

@@ -118,7 +118,7 @@ impl TryFrom<&'_ str> for Rgba {
     }
 }
 
-#[derive(Default, Copy, Clone, Debug, PartialEq)]
+#[derive(Default, Copy, Clone, Debug, PartialEq, Zeroable, Pod)]
 #[repr(C)]
 pub struct Hsla {
     pub h: f32,
@@ -128,8 +128,6 @@ pub struct Hsla {
 }
 
 impl Eq for Hsla {}
-unsafe impl Zeroable for Hsla {}
-unsafe impl Pod for Hsla {}
 
 pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
     Hsla {

crates/gpui3/src/geometry.rs 🔗

@@ -72,7 +72,6 @@ impl<T: Clone + Debug> Clone for Point<T> {
 }
 
 unsafe impl<T: Clone + Debug + Zeroable + Pod> Zeroable for Point<T> {}
-
 unsafe impl<T: Clone + Debug + Zeroable + Pod> Pod for Point<T> {}
 
 #[derive(Refineable, Default, Clone, Copy, Debug, PartialEq)]
@@ -83,6 +82,9 @@ pub struct Size<T: Clone + Debug> {
     pub height: T,
 }
 
+unsafe impl<T: Clone + Debug + Zeroable + Pod> Zeroable for Size<T> {}
+unsafe impl<T: Clone + Debug + Zeroable + Pod> Pod for Size<T> {}
+
 pub fn size<T: Clone + Debug>(width: T, height: T) -> Size<T> {
     Size { width, height }
 }

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

@@ -1,153 +1,249 @@
-// use cocoa::{
-//     base::{NO, YES},
-//     foundation::NSUInteger,
-//     quartzcore::AutoresizingMask,
-// };
-// use core_foundation::base::TCFType;
-// use foreign_types::ForeignTypeRef;
-// use log::warn;
-// use media::core_video::{self, CVMetalTextureCache};
-// use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
-// use objc::{self, msg_send, sel, sel_impl};
-// use shaders::ToFloat2 as _;
-// use std::{collections::HashMap, ffi::c_void, iter::Peekable, mem, ptr, sync::Arc, vec};
-
-// use crate::{Quad, Scene};
-
-// const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
-// const BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decision. There's probably a more optimal value.
-
-// pub struct Renderer {
-//     layer: metal::MetalLayer,
-//     command_queue: CommandQueue,
-//     quad_pipeline_state: metal::RenderPipelineState,
-//     buffer: metal::Buffer,
-// }
-
-// impl Renderer {
-//     pub fn new(is_opaque: bool, fonts: Arc<dyn platform::FontSystem>) -> Self {
-//         const PIXEL_FORMAT: MTLPixelFormat = MTLPixelFormat::BGRA8Unorm;
-
-//         let device: metal::Device = if let Some(device) = metal::Device::system_default() {
-//             device
-//         } else {
-//             log::error!("unable to access a compatible graphics device");
-//             std::process::exit(1);
-//         };
-
-//         let layer = metal::MetalLayer::new();
-//         layer.set_device(&device);
-//         layer.set_pixel_format(PIXEL_FORMAT);
-//         layer.set_presents_with_transaction(true);
-//         layer.set_opaque(is_opaque);
-//         unsafe {
-//             let _: () = msg_send![&*layer, setAllowsNextDrawableTimeout: NO];
-//             let _: () = msg_send![&*layer, setNeedsDisplayOnBoundsChange: YES];
-//             let _: () = msg_send![
-//                 &*layer,
-//                 setAutoresizingMask: AutoresizingMask::WIDTH_SIZABLE
-//                     | AutoresizingMask::HEIGHT_SIZABLE
-//             ];
-//         }
-
-//         let library = device
-//             .new_library_with_data(SHADERS_METALLIB)
-//             .expect("error building metal library");
-
-//         let buffer = device.new_buffer(BUFFER_SIZE as u64, MTLResourceOptions::StorageModeManaged);
-
-//         let quad_pipeline_state = build_pipeline_state(
-//             &device,
-//             &library,
-//             "quad",
-//             "quad_vertex",
-//             "quad_fragment",
-//             PIXEL_FORMAT,
-//         );
-
-//         Self {
-//             layer,
-//             command_queue: device.new_command_queue(),
-//             quad_pipeline_state,
-//             buffer,
-//         }
-//     }
-
-//     pub fn draw(&mut self, scene: &Scene) {
-//         draw_quads(scene);
-//     }
-
-//     fn draw_quads(
-//         &mut self,
-//         quads: &[Quad],
-//         scale_factor: f32,
-//         offset: &mut usize,
-//         drawable_size: Vector2F,
-//         command_encoder: &metal::RenderCommandEncoderRef,
-//     ) {
-//         if quads.is_empty() {
-//             return;
-//         }
-//         align_offset(offset);
-//         let next_offset = *offset + quads.len() * mem::size_of::<shaders::GPUIQuad>();
-//         assert!(
-//             next_offset <= INSTANCE_BUFFER_SIZE,
-//             "instance buffer exhausted"
-//         );
-
-//         command_encoder.set_render_pipeline_state(&self.quad_pipeline_state);
-//         command_encoder.set_vertex_buffer(
-//             shaders::GPUIQuadInputIndex_GPUIQuadInputIndexVertices as u64,
-//             Some(&self.unit_vertices),
-//             0,
-//         );
-//         command_encoder.set_vertex_buffer(
-//             shaders::GPUIQuadInputIndex_GPUIQuadInputIndexQuads as u64,
-//             Some(&self.instances),
-//             *offset as u64,
-//         );
-//         command_encoder.set_vertex_bytes(
-//             shaders::GPUIQuadInputIndex_GPUIQuadInputIndexUniforms as u64,
-//             mem::size_of::<shaders::GPUIUniforms>() as u64,
-//             [shaders::GPUIUniforms {
-//                 viewport_size: drawable_size.to_float2(),
-//             }]
-//             .as_ptr() as *const c_void,
-//         );
-
-//         let buffer_contents = unsafe {
-//             (self.instances.contents() as *mut u8).add(*offset) as *mut shaders::GPUIQuad
-//         };
-//         for (ix, quad) in quads.iter().enumerate() {
-//             let bounds = quad.bounds * scale_factor;
-//             let shader_quad = shaders::GPUIQuad {
-//                 origin: bounds.origin().round().to_float2(),
-//                 size: bounds.size().round().to_float2(),
-//                 background_color: quad
-//                     .background
-//                     .unwrap_or_else(Color::transparent_black)
-//                     .to_uchar4(),
-//                 border_top: quad.border.top * scale_factor,
-//                 border_right: quad.border.right * scale_factor,
-//                 border_bottom: quad.border.bottom * scale_factor,
-//                 border_left: quad.border.left * scale_factor,
-//                 border_color: quad.border.color.to_uchar4(),
-//                 corner_radius_top_left: quad.corner_radii.top_left * scale_factor,
-//                 corner_radius_top_right: quad.corner_radii.top_right * scale_factor,
-//                 corner_radius_bottom_right: quad.corner_radii.bottom_right * scale_factor,
-//                 corner_radius_bottom_left: quad.corner_radii.bottom_left * scale_factor,
-//             };
-//             unsafe {
-//                 *(buffer_contents.add(ix)) = shader_quad;
-//             }
-//         }
-
-//         command_encoder.draw_primitives_instanced(
-//             metal::MTLPrimitiveType::Triangle,
-//             0,
-//             6,
-//             quads.len() as u64,
-//         );
-//         *offset = next_offset;
-//     }
-// }
+use crate::{point, size, Pixels, PointF, Quad, Scene, Size};
+use bytemuck::{Pod, Zeroable};
+use cocoa::{
+    base::{NO, YES},
+    foundation::NSUInteger,
+    quartzcore::AutoresizingMask,
+};
+use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
+use objc::{self, msg_send, sel, sel_impl};
+use std::{ffi::c_void, mem, ptr};
+
+const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
+const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decision. There's probably a more optimal value.
+
+pub struct Renderer {
+    layer: metal::MetalLayer,
+    command_queue: CommandQueue,
+    quad_pipeline_state: metal::RenderPipelineState,
+    unit_vertices: metal::Buffer,
+    instances: metal::Buffer,
+}
+
+impl Renderer {
+    pub fn new(is_opaque: bool) -> Self {
+        const PIXEL_FORMAT: MTLPixelFormat = MTLPixelFormat::BGRA8Unorm;
+
+        let device: metal::Device = if let Some(device) = metal::Device::system_default() {
+            device
+        } else {
+            log::error!("unable to access a compatible graphics device");
+            std::process::exit(1);
+        };
+
+        let layer = metal::MetalLayer::new();
+        layer.set_device(&device);
+        layer.set_pixel_format(PIXEL_FORMAT);
+        layer.set_presents_with_transaction(true);
+        layer.set_opaque(is_opaque);
+        unsafe {
+            let _: () = msg_send![&*layer, setAllowsNextDrawableTimeout: NO];
+            let _: () = msg_send![&*layer, setNeedsDisplayOnBoundsChange: YES];
+            let _: () = msg_send![
+                &*layer,
+                setAutoresizingMask: AutoresizingMask::WIDTH_SIZABLE
+                    | AutoresizingMask::HEIGHT_SIZABLE
+            ];
+        }
+
+        let library = device
+            .new_library_with_data(SHADERS_METALLIB)
+            .expect("error building metal library");
+
+        let unit_vertices = [point(1., 1.), point(1., 0.), point(0., 0.), point(0., 1.)];
+        let unit_vertices = device.new_buffer_with_data(
+            unit_vertices.as_ptr() as *const c_void,
+            (unit_vertices.len() * mem::size_of::<PointF>()) as u64,
+            MTLResourceOptions::StorageModeManaged,
+        );
+        let instances = device.new_buffer(
+            INSTANCE_BUFFER_SIZE as u64,
+            MTLResourceOptions::StorageModeManaged,
+        );
+
+        let quad_pipeline_state = build_pipeline_state(
+            &device,
+            &library,
+            "quad",
+            "quad_vertex",
+            "quad_fragment",
+            PIXEL_FORMAT,
+        );
+
+        Self {
+            layer,
+            command_queue: device.new_command_queue(),
+            quad_pipeline_state,
+            unit_vertices,
+            instances,
+        }
+    }
+
+    pub fn draw(&mut self, scene: &Scene, scale_factor: f32) {
+        let layer = self.layer.clone();
+        let viewport_size = layer.drawable_size();
+        let viewport_size: Size<Pixels> =
+            size(viewport_size.width.into(), viewport_size.height.into());
+        let drawable = if let Some(drawable) = layer.next_drawable() {
+            drawable
+        } else {
+            log::error!(
+                "failed to retrieve next drawable, drawable size: {:?}",
+                viewport_size
+            );
+            return;
+        };
+        let command_queue = self.command_queue.clone();
+        let command_buffer = command_queue.new_command_buffer();
+        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::Clear);
+        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);
+
+        command_encoder.set_viewport(metal::MTLViewport {
+            originX: 0.0,
+            originY: 0.0,
+            width: viewport_size.width.into(),
+            height: viewport_size.height.into(),
+            znear: 0.0,
+            zfar: 1.0,
+        });
+
+        let mut buffer_offset = 0;
+        self.draw_quads(
+            &scene.opaque_primitives().quads,
+            &mut buffer_offset,
+            scale_factor,
+            viewport_size,
+            scene.max_order(),
+            command_encoder,
+        );
+
+        self.instances.did_modify_range(NSRange {
+            location: 0,
+            length: buffer_offset as NSUInteger,
+        });
+        command_buffer.commit();
+        command_buffer.wait_until_completed();
+        drawable.present();
+    }
+
+    fn draw_quads(
+        &mut self,
+        quads: &[Quad],
+        offset: &mut usize,
+        scale_factor: f32,
+        viewport_size: Size<Pixels>,
+        max_order: u32,
+        command_encoder: &metal::RenderCommandEncoderRef,
+    ) {
+        if quads.is_empty() {
+            return;
+        }
+        align_offset(offset);
+
+        command_encoder.set_render_pipeline_state(&self.quad_pipeline_state);
+        command_encoder.set_vertex_buffer(
+            QuadInputIndex::Vertices as u64,
+            Some(&self.unit_vertices),
+            0,
+        );
+        command_encoder.set_vertex_buffer(
+            QuadInputIndex::Quads as u64,
+            Some(&self.instances),
+            *offset as u64,
+        );
+        let quad_uniforms = QuadUniforms {
+            viewport_size,
+            scale_factor,
+            max_order,
+        };
+        let quad_uniform_bytes = bytemuck::bytes_of(&quad_uniforms);
+        command_encoder.set_vertex_bytes(
+            QuadInputIndex::Uniforms as u64,
+            quad_uniform_bytes.len() as u64,
+            quad_uniform_bytes.as_ptr() as *const c_void,
+        );
+
+        let quad_bytes = bytemuck::cast_slice(quads);
+        let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
+        unsafe {
+            ptr::copy_nonoverlapping(quad_bytes.as_ptr(), buffer_contents, quad_bytes.len());
+        }
+
+        let next_offset = *offset + quad_bytes.len();
+        assert!(
+            next_offset <= INSTANCE_BUFFER_SIZE,
+            "instance buffer exhausted"
+        );
+
+        command_encoder.draw_primitives_instanced(
+            metal::MTLPrimitiveType::TriangleStrip,
+            0,
+            4,
+            quads.len() as u64,
+        );
+        *offset = next_offset;
+    }
+}
+
+fn build_pipeline_state(
+    device: &metal::DeviceRef,
+    library: &metal::LibraryRef,
+    label: &str,
+    vertex_fn_name: &str,
+    fragment_fn_name: &str,
+    pixel_format: metal::MTLPixelFormat,
+) -> metal::RenderPipelineState {
+    let vertex_fn = library
+        .get_function(vertex_fn_name, None)
+        .expect("error locating vertex function");
+    let fragment_fn = library
+        .get_function(fragment_fn_name, None)
+        .expect("error locating fragment function");
+
+    let descriptor = metal::RenderPipelineDescriptor::new();
+    descriptor.set_label(label);
+    descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
+    descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
+    let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
+    color_attachment.set_pixel_format(pixel_format);
+    color_attachment.set_blending_enabled(true);
+    color_attachment.set_rgb_blend_operation(metal::MTLBlendOperation::Add);
+    color_attachment.set_alpha_blend_operation(metal::MTLBlendOperation::Add);
+    color_attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::SourceAlpha);
+    color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
+    color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
+    color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One);
+
+    device
+        .new_render_pipeline_state(&descriptor)
+        .expect("could not create render pipeline state")
+}
+
+// Align to multiples of 256 make Metal happy.
+fn align_offset(offset: &mut usize) {
+    *offset = ((*offset + 255) / 256) * 256;
+}
+
+#[repr(C)]
+enum QuadInputIndex {
+    Vertices,
+    Quads,
+    Uniforms,
+}
+
+#[derive(Debug, Clone, Copy, Zeroable, Pod)]
+#[repr(C)]
+pub(crate) struct QuadUniforms {
+    viewport_size: Size<Pixels>,
+    scale_factor: f32,
+    max_order: u32,
+}

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

@@ -1 +1,190 @@
+#include <metal_stdlib>
+#include <simd/simd.h>
 
+using namespace metal;
+
+float4 hsla_to_rgba(Hsla hsla);
+float4 to_device_position(float2 pixel_position, uint order, uint max_order, float2 viewport_size);
+
+struct QuadVertexOutput {
+    float4 position [[position]];
+    uint quad_id;
+};
+
+vertex QuadVertexOutput quad_vertex(
+    uint unit_vertex_id [[vertex_id]],
+    uint quad_id [[instance_id]],
+    constant float2 *unit_vertices [[buffer(QuadInputIndex_Vertices)]],
+    constant Quad *quads [[buffer(QuadInputIndex_Quads)]],
+    constant QuadUniforms *uniforms [[buffer(QuadInputIndex_Uniforms)]]
+) {
+    float2 unit_vertex = unit_vertices[unit_vertex_id];
+    Quad quad = quads[quad_id];
+    float2 position_2d = unit_vertex * float2(quad.bounds.size.width, quad.bounds.size.height) + float2(quad.bounds.origin.x, quad.bounds.origin.y);
+    float2 viewport_size = float2(uniforms->viewport_size.width, uniforms->viewport_size.height);
+    float4 device_position = to_device_position(position_2d, quad.order, uniforms->max_order, viewport_size);
+    return QuadVertexOutput { device_position, quad_id };
+}
+
+fragment float4 quad_fragment(QuadVertexOutput input [[stage_in]], constant Quad *quads [[buffer(QuadInputIndex_Quads)]]) {
+    Quad quad = quads[input.quad_id];
+    float2 half_size = float2( quad.bounds.size.width, quad.bounds.size.height ) / 2.;
+    float2 center = float2( quad.bounds.origin.x, quad.bounds.origin.y ) + half_size;
+    float2 center_to_point = input.position.xy - center;
+    float corner_radius;
+    if (center_to_point.x < 0.) {
+        if (center_to_point.y < 0.) {
+            corner_radius = quad.corner_radii.top_left;
+        } else {
+            corner_radius = quad.corner_radii.bottom_left;
+        }
+    } else {
+        if (center_to_point.y < 0.) {
+            corner_radius = quad.corner_radii.top_right;
+        } else {
+            corner_radius = quad.corner_radii.bottom_right;
+        }
+    }
+
+    float2 rounded_edge_to_point = fabs(center_to_point) - half_size + corner_radius;
+    float distance = length(max(0., rounded_edge_to_point)) + min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) - corner_radius;
+
+    float vertical_border = center_to_point.x <= 0. ? quad.border_widths.left : quad.border_widths.right;
+    float horizontal_border = center_to_point.y <= 0. ? quad.border_widths.top : quad.border_widths.bottom;
+    float2 inset_size = half_size - corner_radius - float2(vertical_border, horizontal_border);
+    float2 point_to_inset_corner = fabs(center_to_point) - inset_size;
+    float border_width;
+    if (point_to_inset_corner.x < 0. && point_to_inset_corner.y < 0.) {
+        border_width = 0.;
+    } else if (point_to_inset_corner.y > point_to_inset_corner.x) {
+        border_width = horizontal_border;
+    } else {
+        border_width = vertical_border;
+    }
+
+    float4 color;
+    if (border_width == 0.) {
+        color = float4(quad.background.h, quad.background.s, quad.background.l, quad.background.a);
+    } else {
+        float inset_distance = distance + border_width;
+
+        // Decrease border's opacity as we move inside the background.
+        quad.border_color.a *= 1. - saturate(0.5 - inset_distance);
+
+        // Alpha-blend the border and the background.
+        float output_alpha = quad.border_color.a + quad.background.a * (1. - quad.border_color.a);
+        float3 premultiplied_border_rgb = float3(quad.border_color.h, quad.border_color.s, quad.border_color.l) * quad.border_color.a;
+        float3 premultiplied_background_rgb = float3(quad.background.h, quad.background.s, quad.background.l) * quad.background.a;
+        float3 premultiplied_output_rgb = premultiplied_border_rgb + premultiplied_background_rgb * (1. - quad.border_color.a);
+        color = float4(premultiplied_output_rgb.x, premultiplied_output_rgb.y, premultiplied_output_rgb.z, output_alpha);
+    }
+
+    return color;
+}
+
+float4 hsla_to_rgba(Hsla hsla) {
+    float h = hsla.h;
+    float s = hsla.s;
+    float l = hsla.l;
+    float a = hsla.a;
+
+    float c = (1.0 - fabs(2.0*l - 1.0)) * s;
+    float x = c * (1.0 - fabs(fmod(h, 2.0) - 1.0));
+    float m = l - c/2.0;
+
+    float r = 0.0;
+    float g = 0.0;
+    float b = 0.0;
+
+    if (h >= 0.0 && h < 1.0) {
+        r = c;
+        g = x;
+        b = 0.0;
+    } else if (h >= 1.0 && h < 2.0) {
+        r = x;
+        g = c;
+        b = 0.0;
+    } else if (h >= 2.0 && h < 3.0) {
+        r = 0.0;
+        g = c;
+        b = x;
+    } else if (h >= 3.0 && h < 4.0) {
+        r = 0.0;
+        g = x;
+        b = c;
+    } else if (h >= 4.0 && h < 5.0) {
+        r = x;
+        g = 0.0;
+        b = c;
+    } else {
+        r = c;
+        g = 0.0;
+        b = x;
+    }
+
+    float4 rgba;
+    rgba.x = (r + m);
+    rgba.y = (g + m);
+    rgba.z = (b + m);
+    rgba.w = a;
+    return rgba;
+}
+
+float4 to_device_position(float2 pixel_position, uint order, uint max_order, float2 viewport_size) {
+    return float4(pixel_position / viewport_size * float2(2., -2.) + float2(-1., 1.), (1. - order / max_order), 1.);
+}
+
+// fragment float4 quad_fragment(QuadVertexOutput input [[stage_in]]) {
+//     float2 half_size = input.size / 2.;
+//     float2 center = input.origin + half_size;
+//     float2 center_to_point = input.position.xy - center;
+//     float corner_radius;
+//     if (center_to_point.x < 0.) {
+//         if (center_to_point.y < 0.) {
+//             corner_radius = input.corner_radius_top_left;
+//         } else {
+//             corner_radius = input.corner_radius_bottom_left;
+//         }
+//     } else {
+//         if (center_to_point.y < 0.) {
+//             corner_radius = input.corner_radius_top_right;
+//         } else {
+//             corner_radius = input.corner_radius_bottom_right;
+//         }
+//     }
+
+//     float2 rounded_edge_to_point = fabs(center_to_point) - half_size + corner_radius;
+//     float distance = length(max(0., rounded_edge_to_point)) + min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) - corner_radius;
+
+//     float vertical_border = center_to_point.x <= 0. ? input.border_left : input.border_right;
+//     float horizontal_border = center_to_point.y <= 0. ? input.border_top : input.border_bottom;
+//     float2 inset_size = half_size - corner_radius - float2(vertical_border, horizontal_border);
+//     float2 point_to_inset_corner = fabs(center_to_point) - inset_size;
+//     float border_width;
+//     if (point_to_inset_corner.x < 0. && point_to_inset_corner.y < 0.) {
+//         border_width = 0.;
+//     } else if (point_to_inset_corner.y > point_to_inset_corner.x) {
+//         border_width = horizontal_border;
+//     } else {
+//         border_width = vertical_border;
+//     }
+
+//     float4 color;
+//     if (border_width == 0.) {
+//         color = input.background_color;
+//     } else {
+//         float inset_distance = distance + border_width;
+
+//         // Decrease border's opacity as we move inside the background.
+//         input.border_color.a *= 1. - saturate(0.5 - inset_distance);
+
+//         // Alpha-blend the border and the background.
+//         float output_alpha = input.border_color.a + input.background_color.a * (1. - input.border_color.a);
+//         float3 premultiplied_border_rgb = input.border_color.rgb * input.border_color.a;
+//         float3 premultiplied_background_rgb = input.background_color.rgb * input.background_color.a;
+//         float3 premultiplied_output_rgb = premultiplied_border_rgb + premultiplied_background_rgb * (1. - input.border_color.a);
+//         color = float4(premultiplied_output_rgb / output_alpha, output_alpha);
+//     }
+
+//     return color * float4(1., 1., 1., saturate(0.5 - distance));
+// }

crates/gpui3/src/scene.rs 🔗

@@ -1,12 +1,18 @@
+use std::cmp;
+
 use super::{Bounds, Hsla, Pixels, Point};
 use crate::{Corners, Edges, FontId, GlyphId};
 use bytemuck::{Pod, Zeroable};
 use plane_split::BspSplitter;
 
+// Exported to metal
+pub type PointF = Point<f32>;
+
 pub struct Scene {
     opaque_primitives: PrimitiveBatch,
     transparent_primitives: slotmap::SlotMap<slotmap::DefaultKey, Primitive>,
     splitter: BspSplitter<slotmap::DefaultKey>,
+    max_order: u32,
 }
 
 impl Scene {
@@ -15,14 +21,17 @@ impl Scene {
             opaque_primitives: PrimitiveBatch::default(),
             transparent_primitives: slotmap::SlotMap::new(),
             splitter: BspSplitter::new(),
+            max_order: 0,
         }
     }
 
     pub fn insert(&mut self, primitive: impl Into<Primitive>, is_transparent: bool) {
+        let primitive = primitive.into();
+        self.max_order = cmp::max(self.max_order, primitive.order());
         if is_transparent {
-            self.transparent_primitives.insert(primitive.into());
+            self.transparent_primitives.insert(primitive);
         } else {
-            match primitive.into() {
+            match primitive {
                 Primitive::Quad(quad) => self.opaque_primitives.quads.push(quad),
                 Primitive::Glyph(glyph) => self.opaque_primitives.glyphs.push(glyph),
                 Primitive::Underline(underline) => {
@@ -35,6 +44,10 @@ impl Scene {
     pub fn opaque_primitives(&self) -> &PrimitiveBatch {
         &self.opaque_primitives
     }
+
+    pub fn max_order(&self) -> u32 {
+        self.max_order
+    }
 }
 
 #[derive(Clone, Debug)]
@@ -45,6 +58,14 @@ pub enum Primitive {
 }
 
 impl Primitive {
+    pub fn order(&self) -> u32 {
+        match self {
+            Primitive::Quad(quad) => quad.order,
+            Primitive::Glyph(glyph) => glyph.order,
+            Primitive::Underline(underline) => underline.order,
+        }
+    }
+
     pub fn is_transparent(&self) -> bool {
         match self {
             Primitive::Quad(quad) => {
@@ -63,10 +84,10 @@ pub struct PrimitiveBatch {
     pub underlines: Vec<Underline>,
 }
 
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, Copy, Zeroable, Pod)]
 #[repr(C)]
 pub struct Quad {
-    pub order: f32,
+    pub order: u32,
     pub bounds: Bounds<Pixels>,
     pub clip_bounds: Bounds<Pixels>,
     pub clip_corner_radii: Corners<Pixels>,
@@ -92,29 +113,16 @@ impl Quad {
     }
 }
 
-unsafe impl Zeroable for Quad {}
-
-unsafe impl Pod for Quad {}
-
 impl From<Quad> for Primitive {
     fn from(quad: Quad) -> Self {
         Primitive::Quad(quad)
     }
 }
 
-#[derive(Debug, Clone, Copy)]
-#[repr(C)]
-pub(crate) struct QuadUniforms {
-    viewport_size: [f32; 2],
-}
-
-unsafe impl Zeroable for QuadUniforms {}
-
-unsafe impl Pod for QuadUniforms {}
-
 #[derive(Debug, Clone, Copy)]
 #[repr(C)]
 pub struct RenderedGlyph {
+    pub order: u32,
     pub font_id: FontId,
     pub font_size: f32,
     pub id: GlyphId,
@@ -128,19 +136,27 @@ impl From<RenderedGlyph> for Primitive {
     }
 }
 
-#[derive(Copy, Clone, Default, Debug)]
+#[derive(Copy, Clone, Default, Debug, Zeroable, Pod)]
 #[repr(C)]
 pub struct Underline {
+    pub order: u32,
     pub origin: Point<Pixels>,
     pub width: Pixels,
     pub thickness: Pixels,
     pub color: Hsla,
-    pub squiggly: bool,
+    pub style: LineStyle,
 }
 
-unsafe impl Zeroable for Underline {}
+#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
+#[repr(C)]
+pub enum LineStyle {
+    #[default]
+    Solid = 0,
+    Squiggly = 1,
+}
 
-unsafe impl Pod for Underline {}
+unsafe impl Zeroable for LineStyle {}
+unsafe impl Pod for LineStyle {}
 
 impl From<Underline> for Primitive {
     fn from(underline: Underline) -> Self {