@@ -1,7 +1,7 @@
use crate::{
point, size, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, ContentMask, DevicePixels,
- MetalAtlas, MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch, Quad,
- ScaledPixels, Scene, Shadow, Size, Underline,
+ Hsla, MetalAtlas, MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch,
+ Quad, ScaledPixels, Scene, Shadow, Size, Underline,
};
use cocoa::{
base::{NO, YES},
@@ -11,6 +11,7 @@ use cocoa::{
use collections::HashMap;
use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
use objc::{self, msg_send, sel, sel_impl};
+use smallvec::SmallVec;
use std::{ffi::c_void, mem, ptr, sync::Arc};
const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
@@ -19,6 +20,8 @@ const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decisio
pub(crate) struct MetalRenderer {
layer: metal::MetalLayer,
command_queue: CommandQueue,
+ paths_rasterization_pipeline_state: metal::RenderPipelineState,
+ path_sprites_pipeline_state: metal::RenderPipelineState,
shadows_pipeline_state: metal::RenderPipelineState,
quads_pipeline_state: metal::RenderPipelineState,
underlines_pipeline_state: metal::RenderPipelineState,
@@ -31,8 +34,6 @@ pub(crate) struct MetalRenderer {
impl MetalRenderer {
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 {
@@ -42,7 +43,7 @@ impl MetalRenderer {
let layer = metal::MetalLayer::new();
layer.set_device(&device);
- layer.set_pixel_format(PIXEL_FORMAT);
+ layer.set_pixel_format(MTLPixelFormat::BGRA8Unorm);
layer.set_presents_with_transaction(true);
layer.set_opaque(is_opaque);
unsafe {
@@ -86,13 +87,29 @@ impl MetalRenderer {
MTLResourceOptions::StorageModeManaged,
);
+ let paths_rasterization_pipeline_state = build_pipeline_state(
+ &device,
+ &library,
+ "paths_rasterization",
+ "path_rasterization_vertex",
+ "path_rasterization_fragment",
+ MTLPixelFormat::R16Float,
+ );
+ let path_sprites_pipeline_state = build_pipeline_state(
+ &device,
+ &library,
+ "path_sprites",
+ "path_sprite_vertex",
+ "path_sprite_fragment",
+ MTLPixelFormat::BGRA8Unorm,
+ );
let shadows_pipeline_state = build_pipeline_state(
&device,
&library,
"shadows",
"shadow_vertex",
"shadow_fragment",
- PIXEL_FORMAT,
+ MTLPixelFormat::BGRA8Unorm,
);
let quads_pipeline_state = build_pipeline_state(
&device,
@@ -100,7 +117,7 @@ impl MetalRenderer {
"quads",
"quad_vertex",
"quad_fragment",
- PIXEL_FORMAT,
+ MTLPixelFormat::BGRA8Unorm,
);
let underlines_pipeline_state = build_pipeline_state(
&device,
@@ -108,7 +125,7 @@ impl MetalRenderer {
"underlines",
"underline_vertex",
"underline_fragment",
- PIXEL_FORMAT,
+ MTLPixelFormat::BGRA8Unorm,
);
let monochrome_sprites_pipeline_state = build_pipeline_state(
&device,
@@ -116,7 +133,7 @@ impl MetalRenderer {
"monochrome_sprites",
"monochrome_sprite_vertex",
"monochrome_sprite_fragment",
- PIXEL_FORMAT,
+ MTLPixelFormat::BGRA8Unorm,
);
let polychrome_sprites_pipeline_state = build_pipeline_state(
&device,
@@ -124,7 +141,7 @@ impl MetalRenderer {
"polychrome_sprites",
"polychrome_sprite_vertex",
"polychrome_sprite_fragment",
- PIXEL_FORMAT,
+ MTLPixelFormat::BGRA8Unorm,
);
let command_queue = device.new_command_queue();
@@ -133,6 +150,8 @@ impl MetalRenderer {
Self {
layer,
command_queue,
+ paths_rasterization_pipeline_state,
+ path_sprites_pipeline_state,
shadows_pipeline_state,
quads_pipeline_state,
underlines_pipeline_state,
@@ -172,7 +191,7 @@ impl MetalRenderer {
let command_buffer = command_queue.new_command_buffer();
let mut instance_offset = 0;
- // let path_tiles = self.rasterize_paths(scene.paths(), &mut instance_offset, &command_buffer);
+ let path_tiles = self.rasterize_paths(scene.paths(), &mut instance_offset, &command_buffer);
let render_pass_descriptor = metal::RenderPassDescriptor::new();
let color_attachment = render_pass_descriptor
@@ -208,8 +227,14 @@ impl MetalRenderer {
PrimitiveBatch::Quads(quads) => {
self.draw_quads(quads, &mut instance_offset, viewport_size, command_encoder);
}
- PrimitiveBatch::Paths(_paths) => {
- // self.draw_paths(paths, &mut instance_offset, viewport_size, command_encoder);
+ PrimitiveBatch::Paths(paths) => {
+ self.draw_paths(
+ paths,
+ &path_tiles,
+ &mut instance_offset,
+ viewport_size,
+ command_encoder,
+ );
}
PrimitiveBatch::Underlines(underlines) => {
self.draw_underlines(
@@ -258,19 +283,20 @@ impl MetalRenderer {
drawable.present();
}
- #[allow(dead_code)]
fn rasterize_paths(
&mut self,
paths: &[Path<ScaledPixels>],
- _offset: &mut usize,
- _command_buffer: &metal::CommandBufferRef,
+ offset: &mut usize,
+ command_buffer: &metal::CommandBufferRef,
) -> HashMap<PathId, AtlasTile> {
let mut tiles = HashMap::default();
let mut vertices_by_texture_id = HashMap::default();
for path in paths {
+ let clipped_bounds = path.bounds.intersect(&path.content_mask.bounds);
+
let tile = self
.sprite_atlas
- .allocate(path.bounds.size.map(Into::into), AtlasTextureKind::Path);
+ .allocate(clipped_bounds.size.map(Into::into), AtlasTextureKind::Path);
vertices_by_texture_id
.entry(tile.texture_id)
.or_insert(Vec::new())
@@ -279,68 +305,65 @@ impl MetalRenderer {
+ tile.bounds.origin.map(Into::into),
st_position: vertex.st_position,
content_mask: ContentMask {
- bounds: Bounds {
- origin: vertex.xy_position - path.bounds.origin
- + tile.bounds.origin.map(Into::into),
- size: vertex.content_mask.bounds.size,
- },
+ bounds: tile.bounds.map(Into::into),
},
}));
tiles.insert(path.id, tile);
}
- for (_texture_id, _vertices) in vertices_by_texture_id {
- todo!();
- // align_offset(offset);
- // let next_offset = *offset + vertices.len() * mem::size_of::<PathVertex<ScaledPixels>>();
- // assert!(
- // next_offset <= INSTANCE_BUFFER_SIZE,
- // "instance buffer exhausted"
- // );
-
- // let render_pass_descriptor = metal::RenderPassDescriptor::new();
- // let color_attachment = render_pass_descriptor
- // .color_attachments()
- // .object_at(0)
- // .unwrap();
-
- // let texture = self.sprite_atlas.metal_texture(texture_id);
- // color_attachment.set_texture(Some(&texture));
- // color_attachment.set_load_action(metal::MTLLoadAction::Clear);
- // color_attachment.set_store_action(metal::MTLStoreAction::Store);
- // color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., 1.));
- // let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
- // command_encoder.set_render_pipeline_state(&self.path_atlas_pipeline_state);
- // command_encoder.set_vertex_buffer(
- // shaders::GPUIPathAtlasVertexInputIndex_GPUIPathAtlasVertexInputIndexVertices as u64,
- // Some(&self.instances),
- // *offset as u64,
- // );
- // command_encoder.set_vertex_bytes(
- // shaders::GPUIPathAtlasVertexInputIndex_GPUIPathAtlasVertexInputIndexAtlasSize
- // as u64,
- // mem::size_of::<shaders::vector_float2>() as u64,
- // [vec2i(texture.width() as i32, texture.height() as i32).to_float2()].as_ptr()
- // as *const c_void,
- // );
-
- // let buffer_contents = unsafe {
- // (self.instances.contents() as *mut u8).add(*offset) as *mut shaders::GPUIPathVertex
- // };
-
- // for (ix, vertex) in vertices.iter().enumerate() {
- // unsafe {
- // *buffer_contents.add(ix) = *vertex;
- // }
- // }
-
- // command_encoder.draw_primitives(
- // metal::MTLPrimitiveType::Triangle,
- // 0,
- // vertices.len() as u64,
- // );
- // command_encoder.end_encoding();
- // *offset = next_offset;
+ for (texture_id, vertices) in vertices_by_texture_id {
+ align_offset(offset);
+ let next_offset = *offset + vertices.len() * mem::size_of::<PathVertex<ScaledPixels>>();
+ assert!(
+ next_offset <= INSTANCE_BUFFER_SIZE,
+ "instance buffer exhausted"
+ );
+
+ let render_pass_descriptor = metal::RenderPassDescriptor::new();
+ let color_attachment = render_pass_descriptor
+ .color_attachments()
+ .object_at(0)
+ .unwrap();
+
+ let texture = self.sprite_atlas.metal_texture(texture_id);
+ color_attachment.set_texture(Some(&texture));
+ color_attachment.set_load_action(metal::MTLLoadAction::Clear);
+ color_attachment.set_store_action(metal::MTLStoreAction::Store);
+ color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., 1.));
+ let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
+ command_encoder.set_render_pipeline_state(&self.paths_rasterization_pipeline_state);
+ command_encoder.set_vertex_buffer(
+ PathRasterizationInputIndex::Vertices as u64,
+ Some(&self.instances),
+ *offset as u64,
+ );
+ let texture_size = Size {
+ width: DevicePixels::from(texture.width()),
+ height: DevicePixels::from(texture.height()),
+ };
+ command_encoder.set_vertex_bytes(
+ PathRasterizationInputIndex::AtlasTextureSize as u64,
+ mem::size_of_val(&texture_size) as u64,
+ &texture_size as *const Size<DevicePixels> as *const _,
+ );
+
+ let vertices_bytes_len = mem::size_of::<PathVertex<ScaledPixels>>() * vertices.len();
+ let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
+ unsafe {
+ ptr::copy_nonoverlapping(
+ vertices.as_ptr() as *const u8,
+ buffer_contents,
+ vertices_bytes_len,
+ );
+ }
+
+ command_encoder.draw_primitives(
+ metal::MTLPrimitiveType::Triangle,
+ 0,
+ vertices.len() as u64,
+ );
+ command_encoder.end_encoding();
+ *offset = next_offset;
}
tiles
@@ -462,6 +485,112 @@ impl MetalRenderer {
*offset = next_offset;
}
+ fn draw_paths(
+ &mut self,
+ paths: &[Path<ScaledPixels>],
+ tiles_by_path_id: &HashMap<PathId, AtlasTile>,
+ offset: &mut usize,
+ viewport_size: Size<DevicePixels>,
+ command_encoder: &metal::RenderCommandEncoderRef,
+ ) {
+ if paths.is_empty() {
+ return;
+ }
+
+ command_encoder.set_render_pipeline_state(&self.path_sprites_pipeline_state);
+ command_encoder.set_vertex_buffer(
+ SpriteInputIndex::Vertices as u64,
+ Some(&self.unit_vertices),
+ 0,
+ );
+ command_encoder.set_vertex_bytes(
+ SpriteInputIndex::ViewportSize as u64,
+ mem::size_of_val(&viewport_size) as u64,
+ &viewport_size as *const Size<DevicePixels> as *const _,
+ );
+
+ let mut prev_texture_id = None;
+ let mut sprites = SmallVec::<[_; 1]>::new();
+ let mut paths_and_tiles = paths
+ .into_iter()
+ .map(|path| (path, tiles_by_path_id.get(&path.id).unwrap()))
+ .peekable();
+
+ loop {
+ if let Some((path, tile)) = paths_and_tiles.peek() {
+ if prev_texture_id.map_or(true, |texture_id| texture_id == tile.texture_id) {
+ prev_texture_id = Some(tile.texture_id);
+ sprites.push(PathSprite {
+ bounds: Bounds {
+ origin: path.bounds.origin.map(|p| p.floor()),
+ size: tile.bounds.size.map(Into::into),
+ },
+ color: path.color,
+ tile: (*tile).clone(),
+ });
+ paths_and_tiles.next();
+ continue;
+ }
+ }
+
+ if sprites.is_empty() {
+ break;
+ } else {
+ align_offset(offset);
+ let texture_id = prev_texture_id.take().unwrap();
+ let texture: metal::Texture = self.sprite_atlas.metal_texture(texture_id);
+ let texture_size = size(
+ DevicePixels(texture.width() as i32),
+ DevicePixels(texture.height() as i32),
+ );
+
+ command_encoder.set_vertex_buffer(
+ SpriteInputIndex::Sprites as u64,
+ Some(&self.instances),
+ *offset as u64,
+ );
+ command_encoder.set_vertex_bytes(
+ SpriteInputIndex::AtlasTextureSize as u64,
+ mem::size_of_val(&texture_size) as u64,
+ &texture_size as *const Size<DevicePixels> as *const _,
+ );
+ command_encoder.set_fragment_buffer(
+ SpriteInputIndex::Sprites as u64,
+ Some(&self.instances),
+ *offset as u64,
+ );
+ command_encoder
+ .set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture));
+
+ let sprite_bytes_len = mem::size_of::<MonochromeSprite>() * sprites.len();
+ let buffer_contents =
+ unsafe { (self.instances.contents() as *mut u8).add(*offset) };
+ unsafe {
+ ptr::copy_nonoverlapping(
+ sprites.as_ptr() as *const u8,
+ buffer_contents,
+ sprite_bytes_len,
+ );
+ }
+
+ let next_offset = *offset + sprite_bytes_len;
+ assert!(
+ next_offset <= INSTANCE_BUFFER_SIZE,
+ "instance buffer exhausted"
+ );
+
+ command_encoder.draw_primitives_instanced(
+ metal::MTLPrimitiveType::Triangle,
+ 0,
+ 6,
+ sprites.len() as u64,
+ );
+ *offset = next_offset;
+ sprites.clear();
+ }
+ }
+ }
+
fn draw_underlines(
&mut self,
underlines: &[Underline],
@@ -734,3 +863,17 @@ enum SpriteInputIndex {
AtlasTextureSize = 3,
AtlasTexture = 4,
}
+
+#[repr(C)]
+enum PathRasterizationInputIndex {
+ Vertices = 0,
+ AtlasTextureSize = 1,
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+#[repr(C)]
+pub struct PathSprite {
+ pub bounds: Bounds<ScaledPixels>,
+ pub color: Hsla,
+ pub tile: AtlasTile,
+}
@@ -8,7 +8,9 @@ use plane_split::{BspSplitter, Polygon as BspPolygon};
use std::{fmt::Debug, iter::Peekable, mem, slice};
// Exported to metal
-pub type PointF = Point<f32>;
+pub(crate) type PointF = Point<f32>;
+#[allow(non_camel_case_types, unused)]
+pub(crate) type PathVertex_ScaledPixels = PathVertex<ScaledPixels>;
pub type LayerId = u32;
@@ -62,6 +64,12 @@ impl SceneBuilder {
.add(quad.bounds.to_bsp_polygon(z, (PrimitiveKind::Quad, ix)));
}
+ for (ix, path) in self.paths.iter().enumerate() {
+ let z = layer_z_values[path.order as LayerId as usize];
+ self.splitter
+ .add(path.bounds.to_bsp_polygon(z, (PrimitiveKind::Path, ix)));
+ }
+
for (ix, underline) in self.underlines.iter().enumerate() {
let z = layer_z_values[underline.order as LayerId as usize];
self.splitter.add(
@@ -131,6 +139,16 @@ impl SceneBuilder {
}
pub fn insert(&mut self, order: &StackingOrder, primitive: impl Into<Primitive>) {
+ let primitive = primitive.into();
+ let clipped_bounds = primitive
+ .bounds()
+ .intersect(&primitive.content_mask().bounds);
+ if clipped_bounds.size.width <= ScaledPixels(0.)
+ || clipped_bounds.size.height <= ScaledPixels(0.)
+ {
+ return;
+ }
+
let layer_id = if let Some(layer_id) = self.layers_by_order.get(order) {
*layer_id
} else {
@@ -139,7 +157,6 @@ impl SceneBuilder {
next_id
};
- let primitive = primitive.into();
match primitive {
Primitive::Shadow(mut shadow) => {
shadow.order = layer_id;
@@ -240,6 +257,7 @@ impl<'a> Iterator for BatchIterator<'a> {
PrimitiveKind::Shadow,
),
(self.quads_iter.peek().map(|q| q.order), PrimitiveKind::Quad),
+ (self.paths_iter.peek().map(|q| q.order), PrimitiveKind::Path),
(
self.underlines_iter.peek().map(|u| u.order),
PrimitiveKind::Underline,
@@ -382,6 +400,30 @@ pub enum Primitive {
PolychromeSprite(PolychromeSprite),
}
+impl Primitive {
+ pub fn bounds(&self) -> &Bounds<ScaledPixels> {
+ match self {
+ Primitive::Shadow(shadow) => &shadow.bounds,
+ Primitive::Quad(quad) => &quad.bounds,
+ Primitive::Path(path) => &path.bounds,
+ Primitive::Underline(underline) => &underline.bounds,
+ Primitive::MonochromeSprite(sprite) => &sprite.bounds,
+ Primitive::PolychromeSprite(sprite) => &sprite.bounds,
+ }
+ }
+
+ pub fn content_mask(&self) -> &ContentMask<ScaledPixels> {
+ match self {
+ Primitive::Shadow(shadow) => &shadow.content_mask,
+ Primitive::Quad(quad) => &quad.content_mask,
+ Primitive::Path(path) => &path.content_mask,
+ Primitive::Underline(underline) => &underline.content_mask,
+ Primitive::MonochromeSprite(sprite) => &sprite.content_mask,
+ Primitive::PolychromeSprite(sprite) => &sprite.content_mask,
+ }
+ }
+}
+
#[derive(Debug)]
pub(crate) enum PrimitiveBatch<'a> {
Shadows(&'a [Shadow]),
@@ -557,20 +599,29 @@ pub struct Path<P: Clone + Debug> {
pub(crate) id: PathId,
order: u32,
pub(crate) bounds: Bounds<P>,
+ pub(crate) content_mask: ContentMask<P>,
pub(crate) vertices: Vec<PathVertex<P>>,
- start: Option<Point<P>>,
+ pub(crate) color: Hsla,
+ start: Point<P>,
current: Point<P>,
+ contour_count: usize,
}
impl Path<Pixels> {
- pub fn new() -> Self {
+ pub fn new(start: Point<Pixels>) -> Self {
Self {
id: PathId(0),
order: 0,
vertices: Vec::new(),
- start: Default::default(),
- current: Default::default(),
- bounds: Default::default(),
+ start,
+ current: start,
+ bounds: Bounds {
+ origin: start,
+ size: Default::default(),
+ },
+ content_mask: Default::default(),
+ color: Default::default(),
+ contour_count: 0,
}
}
@@ -579,6 +630,7 @@ impl Path<Pixels> {
id: self.id,
order: self.order,
bounds: self.bounds.scale(factor),
+ content_mask: self.content_mask.scale(factor),
vertices: self
.vertices
.iter()
@@ -586,27 +638,36 @@ impl Path<Pixels> {
.collect(),
start: self.start.map(|start| start.scale(factor)),
current: self.current.scale(factor),
+ contour_count: self.contour_count,
+ color: self.color,
}
}
pub fn line_to(&mut self, to: Point<Pixels>) {
- if let Some(start) = self.start {
+ self.contour_count += 1;
+ if self.contour_count > 1 {
self.push_triangle(
- (start, self.current, to),
+ (self.start, self.current, to),
(point(0., 1.), point(0., 1.), point(0., 1.)),
);
- } else {
- self.start = Some(to);
}
self.current = to;
}
pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
- self.line_to(to);
+ self.contour_count += 1;
+ if self.contour_count > 1 {
+ self.push_triangle(
+ (self.start, self.current, to),
+ (point(0., 1.), point(0., 1.), point(0., 1.)),
+ );
+ }
+
self.push_triangle(
(self.current, ctrl, to),
(point(0., 0.), point(0.5, 0.), point(1., 1.)),
);
+ self.current = to;
}
fn push_triangle(
@@ -614,12 +675,6 @@ impl Path<Pixels> {
xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>),
st: (Point<f32>, Point<f32>, Point<f32>),
) {
- if self.vertices.is_empty() {
- self.bounds = Bounds {
- origin: xy.0,
- size: Default::default(),
- };
- }
self.bounds = self
.bounds
.union(&Bounds {