diff --git a/Cargo.lock b/Cargo.lock index c5b09849fe..41ebb69519 100644 --- a/Cargo.lock +++ b/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" diff --git a/crates/gpui3/Cargo.toml b/crates/gpui3/Cargo.toml index 38eba26bb8..2c567f2047 100644 --- a/crates/gpui3/Cargo.toml +++ b/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" diff --git a/crates/gpui3/build.rs b/crates/gpui3/build.rs index 1dbff6f051..af63512b8a 100644 --- a/crates/gpui3/build.rs +++ b/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); diff --git a/crates/gpui3/src/color.rs b/crates/gpui3/src/color.rs index cf2a070d48..fa28b14ee4 100644 --- a/crates/gpui3/src/color.rs +++ b/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 { diff --git a/crates/gpui3/src/geometry.rs b/crates/gpui3/src/geometry.rs index ae2b3aff15..6a79f14f19 100644 --- a/crates/gpui3/src/geometry.rs +++ b/crates/gpui3/src/geometry.rs @@ -72,7 +72,6 @@ impl Clone for Point { } unsafe impl Zeroable for Point {} - unsafe impl Pod for Point {} #[derive(Refineable, Default, Clone, Copy, Debug, PartialEq)] @@ -83,6 +82,9 @@ pub struct Size { pub height: T, } +unsafe impl Zeroable for Size {} +unsafe impl Pod for Size {} + pub fn size(width: T, height: T) -> Size { Size { width, height } } diff --git a/crates/gpui3/src/platform/mac/metal_renderer.rs b/crates/gpui3/src/platform/mac/metal_renderer.rs index 3a23281fe9..cae902998e 100644 --- a/crates/gpui3/src/platform/mac/metal_renderer.rs +++ b/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::{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}; -// use crate::{Quad, Scene}; +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. -// 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, + unit_vertices: metal::Buffer, + instances: metal::Buffer, +} -// 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) -> Self { + const PIXEL_FORMAT: MTLPixelFormat = MTLPixelFormat::BGRA8Unorm; -// impl Renderer { -// pub fn new(is_opaque: bool, fonts: Arc) -> 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 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 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 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::()) as u64, + MTLResourceOptions::StorageModeManaged, + ); + let instances = device.new_buffer( + INSTANCE_BUFFER_SIZE as u64, + MTLResourceOptions::StorageModeManaged, + ); -// 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, + ); -// 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, + } + } -// Self { -// layer, -// command_queue: device.new_command_queue(), -// quad_pipeline_state, -// buffer, -// } -// } + 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 = + 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); -// pub fn draw(&mut self, scene: &Scene) { -// draw_quads(scene); -// } + 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, + }); -// 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::(); -// assert!( -// next_offset <= INSTANCE_BUFFER_SIZE, -// "instance buffer exhausted" -// ); + let mut buffer_offset = 0; + self.draw_quads( + &scene.opaque_primitives().quads, + &mut buffer_offset, + scale_factor, + viewport_size, + scene.max_order(), + command_encoder, + ); -// 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::() as u64, -// [shaders::GPUIUniforms { -// viewport_size: drawable_size.to_float2(), -// }] -// .as_ptr() as *const c_void, -// ); + self.instances.did_modify_range(NSRange { + location: 0, + length: buffer_offset as NSUInteger, + }); + command_buffer.commit(); + command_buffer.wait_until_completed(); + drawable.present(); + } -// 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; -// } -// } + fn draw_quads( + &mut self, + quads: &[Quad], + offset: &mut usize, + scale_factor: f32, + viewport_size: Size, + max_order: u32, + command_encoder: &metal::RenderCommandEncoderRef, + ) { + if quads.is_empty() { + return; + } + align_offset(offset); -// command_encoder.draw_primitives_instanced( -// metal::MTLPrimitiveType::Triangle, -// 0, -// 6, -// quads.len() as u64, -// ); -// *offset = next_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, + scale_factor: f32, + max_order: u32, +} diff --git a/crates/gpui3/src/platform/mac/shaders.metal b/crates/gpui3/src/platform/mac/shaders.metal index 8b13789179..e06e0ebf4c 100644 --- a/crates/gpui3/src/platform/mac/shaders.metal +++ b/crates/gpui3/src/platform/mac/shaders.metal @@ -1 +1,190 @@ +#include +#include +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)); +// } diff --git a/crates/gpui3/src/scene.rs b/crates/gpui3/src/scene.rs index b08b17c744..1844b94937 100644 --- a/crates/gpui3/src/scene.rs +++ b/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; + pub struct Scene { opaque_primitives: PrimitiveBatch, transparent_primitives: slotmap::SlotMap, splitter: BspSplitter, + 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, 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, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Zeroable, Pod)] #[repr(C)] pub struct Quad { - pub order: f32, + pub order: u32, pub bounds: Bounds, pub clip_bounds: Bounds, pub clip_corner_radii: Corners, @@ -92,29 +113,16 @@ impl Quad { } } -unsafe impl Zeroable for Quad {} - -unsafe impl Pod for Quad {} - impl From 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 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, 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 for Primitive { fn from(underline: Underline) -> Self {