diff --git a/Cargo.lock b/Cargo.lock index 74769a3d6b..be9df0b8d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -549,6 +549,25 @@ dependencies = [ "termcolor", ] +[[package]] +name = "etagere" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520d7de540904fd09b11c03a47d50a7ce4ff37d1aa763f454fa60d9088ef8356" +dependencies = [ + "euclid", + "svg_fmt", +] + +[[package]] +name = "euclid" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51e5bac4ec41ece6346fd867815a57a221abdf48f4eb931b033789b5b4b6fc70" +dependencies = [ + "num-traits", +] + [[package]] name = "event-listener" version = "2.5.1" @@ -745,6 +764,7 @@ dependencies = [ "core-graphics", "core-text", "ctor", + "etagere", "font-kit", "foreign-types", "log", @@ -1483,6 +1503,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +[[package]] +name = "svg_fmt" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fb1df15f412ee2e9dfc1c504260fa695c1c3f10fe9f4a6ee2d2184d7d6450e2" + [[package]] name = "syn" version = "1.0.60" diff --git a/gpui/Cargo.toml b/gpui/Cargo.toml index e58f493d6f..6cd0e889e2 100644 --- a/gpui/Cargo.toml +++ b/gpui/Cargo.toml @@ -7,6 +7,7 @@ version = "0.1.0" [dependencies] async-task = {git = "https://github.com/zedit-io/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e"} ctor = "0.1" +etagere = "0.2" num_cpus = "1.13" ordered-float = "2.1.1" parking_lot = "0.11.1" diff --git a/gpui/build.rs b/gpui/build.rs index 14326c48ce..030145f2dd 100644 --- a/gpui/build.rs +++ b/gpui/build.rs @@ -89,6 +89,8 @@ fn generate_shader_bindings() { .whitelist_type("GPUIQuad") .whitelist_type("GPUIShadowInputIndex") .whitelist_type("GPUIShadow") + .whitelist_type("GPUISpriteInputIndex") + .whitelist_type("GPUISprite") .parse_callbacks(Box::new(bindgen::CargoCallbacks)) .generate() .expect("unable to generate bindings"); diff --git a/gpui/src/platform/mac/mod.rs b/gpui/src/platform/mac/mod.rs index 148355d34c..0ddd8f80c7 100644 --- a/gpui/src/platform/mac/mod.rs +++ b/gpui/src/platform/mac/mod.rs @@ -4,6 +4,7 @@ mod event; mod geometry; mod renderer; mod runner; +mod sprite_cache; mod window; use crate::platform; diff --git a/gpui/src/platform/mac/renderer.rs b/gpui/src/platform/mac/renderer.rs index ecd01f24f4..a741f5fb86 100644 --- a/gpui/src/platform/mac/renderer.rs +++ b/gpui/src/platform/mac/renderer.rs @@ -1,4 +1,4 @@ -use std::{ffi::c_void, mem}; +use std::{collections::HashMap, ffi::c_void, mem}; use self::shaders::ToUchar4; @@ -15,8 +15,10 @@ const INSTANCE_BUFFER_SIZE: usize = 1024 * 1024; // This is an arbitrary decisio pub struct Renderer { quad_pipeline_state: metal::RenderPipelineState, shadow_pipeline_state: metal::RenderPipelineState, + sprite_pipeline_state: metal::RenderPipelineState, unit_vertices: metal::Buffer, instances: metal::Buffer, + sprite_cache: SpriteCache, } impl Renderer { @@ -60,6 +62,14 @@ impl Renderer { "shadow_fragment", pixel_format, )?, + sprite_pipeline_state: build_pipeline_state( + device, + &library, + "sprite", + "sprite_vertex", + "sprite_fragment", + pixel_format, + )?, unit_vertices, instances, }) @@ -79,6 +89,7 @@ impl Renderer { for layer in scene.layers() { self.render_shadows(scene, layer, &mut offset, ctx); self.render_quads(scene, layer, &mut offset, ctx); + self.render_sprites(scene, layer, &mut offset, ctx); } } @@ -234,6 +245,71 @@ impl Renderer { layer.quads().len() as u64, ); } + + fn render_sprites( + &mut self, + scene: &Scene, + layer: &Layer, + offset: &mut usize, + ctx: &RenderContext, + ) { + if layer.glyphs().is_empty() { + return; + } + + align_offset(offset); + let next_offset = *offset + layer.glyphs().len() * mem::size_of::(); + assert!( + next_offset <= INSTANCE_BUFFER_SIZE, + "instance buffer exhausted" + ); + + let mut sprites = HashMap::new(); + for glyph in layer.glyphs() { + let (atlas, bounds) = + self.sprite_cache + .rasterize_glyph(glyph.font_id, glyph.font_size, glyph.glyph_id); + sprites + .entry(atlas) + .or_insert_with(Vec::new) + .push(shaders::GPUISprite { + origin: glyph.origin.to_float2(), + size: bounds.size().to_float2(), + atlas_origin: bounds.origin().to_float2(), + color: glyph.color.to_uchar4(), + }); + } + + ctx.command_encoder + .set_render_pipeline_state(&self.sprite_pipeline_state); + ctx.command_encoder.set_vertex_buffer( + shaders::GPUISpriteInputIndex_GPUISpriteInputIndexVertices as u64, + Some(&self.unit_vertices), + 0, + ); + ctx.command_encoder.set_vertex_buffer( + shaders::GPUISpriteInputIndex_GPUISpriteInputIndexSprites as u64, + Some(&self.instances), + *offset as u64, + ); + ctx.command_encoder.set_vertex_bytes( + shaders::GPUISpriteInputIndex_GPUISpriteInputIndexUniforms as u64, + mem::size_of::() as u64, + [shaders::GPUIUniforms { + viewport_size: ctx.drawable_size.to_float2(), + }] + .as_ptr() as *const c_void, + ); + + let buffer_contents = unsafe { + (self.instances.contents() as *mut u8).offset(*offset as isize) + as *mut shaders::GPUISprite + }; + + for glyph in layer.glyphs() { + let sprite = self.sprite_cache.rasterize_glyph(); + } + } } fn align_offset(offset: &mut usize) { diff --git a/gpui/src/platform/mac/shaders/shaders.h b/gpui/src/platform/mac/shaders/shaders.h index 47500bd748..50f6d52f1d 100644 --- a/gpui/src/platform/mac/shaders/shaders.h +++ b/gpui/src/platform/mac/shaders/shaders.h @@ -35,3 +35,16 @@ typedef struct { float sigma; vector_uchar4 color; } GPUIShadow; + +typedef enum { + GPUISpriteInputIndexVertices = 0, + GPUISpriteInputIndexSprites = 1, + GPUISpriteInputIndexUniforms = 2, +} GPUISpriteInputIndex; + +typedef struct { + vector_float2 origin; + vector_float2 size; + vector_float2 atlas_origin; + vector_uchar4 color; +} GPUISprite; diff --git a/gpui/src/platform/mac/sprite_cache.rs b/gpui/src/platform/mac/sprite_cache.rs new file mode 100644 index 0000000000..8745aae4ee --- /dev/null +++ b/gpui/src/platform/mac/sprite_cache.rs @@ -0,0 +1,19 @@ +use crate::geometry::vector::Vector2I; +use etagere::BucketedAtlasAllocator; + +struct SpriteCache { + atlasses: Vec, +} + +impl SpriteCache { + fn new(size: Vector2I) -> Self { + let size = etagere::Size::new(size.x(), size.y()); + Self { + atlasses: vec![BucketedAtlasAllocator::new(size)], + } + } + + fn render_glyph(&mut self) { + self.atlasses.last().unwrap() + } +} diff --git a/gpui/src/scene.rs b/gpui/src/scene.rs index 5bdd0edf80..3edc373055 100644 --- a/gpui/src/scene.rs +++ b/gpui/src/scene.rs @@ -1,6 +1,9 @@ -use core::f32; +use crate::{ + color::ColorU, + fonts::{FontId, GlyphId}, + geometry::{rect::RectF, vector::Vector2F}, +}; -use crate::{color::ColorU, geometry::rect::RectF}; pub struct Scene { scale_factor: f32, layers: Vec, @@ -12,6 +15,7 @@ pub struct Layer { clip_bounds: Option, quads: Vec, shadows: Vec, + glyphs: Vec, } #[derive(Default, Debug)] @@ -30,6 +34,15 @@ pub struct Shadow { pub color: ColorU, } +#[derive(Debug)] +pub struct Glyph { + pub font_id: FontId, + pub font_size: f32, + pub glyph_id: GlyphId, + pub origin: Vector2F, + pub color: ColorU, +} + #[derive(Clone, Copy, Default, Debug)] pub struct Border { pub width: f32, @@ -76,6 +89,10 @@ impl Scene { self.active_layer().push_shadow(shadow) } + pub fn push_glyph(&mut self, glyph: Glyph) { + self.active_layer().push_glyph(glyph) + } + fn active_layer(&mut self) -> &mut Layer { &mut self.layers[*self.active_layer_stack.last().unwrap()] } @@ -97,6 +114,14 @@ impl Layer { pub fn shadows(&self) -> &[Shadow] { self.shadows.as_slice() } + + fn push_glyph(&mut self, glyph: Glyph) { + self.glyphs.push(glyph); + } + + pub fn glyphs(&self) -> &[Glyph] { + self.glyphs.as_slice() + } } impl Border { diff --git a/gpui/src/text_layout.rs b/gpui/src/text_layout.rs index 546d6f465d..1a75ca527f 100644 --- a/gpui/src/text_layout.rs +++ b/gpui/src/text_layout.rs @@ -2,7 +2,7 @@ use crate::{ color::ColorU, fonts::{FontCache, FontId, GlyphId}, geometry::rect::RectF, - PaintContext, + scene, PaintContext, }; use core_foundation::{ attributed_string::CFMutableAttributedString, @@ -186,71 +186,54 @@ impl Line { } } - pub fn paint( - &self, - _bounds: RectF, - _colors: &[(Range, ColorU)], - _ctx: &mut PaintContext, - ) { - // canvas.set_font_size(self.font_size); - // let mut colors = colors.iter().peekable(); + pub fn paint(&self, bounds: RectF, colors: &[(Range, ColorU)], ctx: &mut PaintContext) { + let mut colors = colors.iter().peekable(); + let mut color = ColorU::black(); - // for run in &self.runs { - // let bounding_box = font_cache.bounding_box(run.font_id, self.font_size); - // let ascent = font_cache.scale_metric( - // font_cache.metric(run.font_id, |m| m.ascent), - // run.font_id, - // self.font_size, - // ); - // let descent = font_cache.scale_metric( - // font_cache.metric(run.font_id, |m| m.descent), - // run.font_id, - // self.font_size, - // ); + for run in &self.runs { + let bounding_box = ctx.font_cache.bounding_box(run.font_id, self.font_size); + let ascent = ctx.font_cache.scale_metric( + ctx.font_cache.metric(run.font_id, |m| m.ascent), + run.font_id, + self.font_size, + ); + let descent = ctx.font_cache.scale_metric( + ctx.font_cache.metric(run.font_id, |m| m.descent), + run.font_id, + self.font_size, + ); - // let max_glyph_width = bounding_box.x(); - // let font = font_cache.font(run.font_id); - // let font_name = font_cache.font_name(run.font_id); - // let is_emoji = font_cache.is_emoji(run.font_id); - // for glyph in &run.glyphs { - // let glyph_origin = origin + glyph.position - vec2f(0.0, descent); + let max_glyph_width = bounding_box.x(); + let font = ctx.font_cache.font(run.font_id); + let font_name = ctx.font_cache.font_name(run.font_id); + let is_emoji = ctx.font_cache.is_emoji(run.font_id); + for glyph in &run.glyphs { + let glyph_origin = bounds.origin() + glyph.position; + if glyph_origin.x() + max_glyph_width < bounds.origin().x() { + continue; + } + if glyph_origin.x() > bounds.upper_right().x() { + break; + } - // if glyph_origin.x() + max_glyph_width < viewport_rect.origin().x() { - // continue; - // } + while let Some((range, next_color)) = colors.peek() { + if glyph.index >= range.end { + colors.next(); + } else { + color = *next_color; + break; + } + } - // if glyph_origin.x() > viewport_rect.upper_right().x() { - // break; - // } - - // while let Some((range, color)) = colors.peek() { - // if glyph.index >= range.end { - // colors.next(); - // } else { - // if glyph.index == range.start { - // canvas.set_fill_style(FillStyle::Color(*color)); - // } - // break; - // } - // } - - // if is_emoji { - // match font_cache.render_emoji(glyph.id, self.font_size) { - // Ok(image) => { - // canvas.draw_image(image, RectF::new(glyph_origin, bounding_box)); - // } - // Err(error) => log::error!("rasterizing emoji: {}", error), - // } - // } else { - // canvas.fill_glyph( - // &font, - // &font_name, - // glyph.id, - // glyph_origin + vec2f(0.0, ascent), - // ); - // } - // } - // } + ctx.scene.push_glyph(scene::Glyph { + font_id: run.font_id, + font_size: self.font_size, + glyph_id: glyph.id, + origin: glyph_origin, + color, + }); + } + } } }