Detailed changes
@@ -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"
@@ -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"
@@ -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);
@@ -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 {
@@ -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 }
}
@@ -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,
+}
@@ -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));
+// }
@@ -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 {