Detailed changes
@@ -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"
@@ -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"
@@ -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");
@@ -4,6 +4,7 @@ mod event;
mod geometry;
mod renderer;
mod runner;
+mod sprite_cache;
mod window;
use crate::platform;
@@ -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::<shaders::GPUISprite>();
+ 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::<shaders::GPUIUniforms>() 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) {
@@ -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;
@@ -0,0 +1,19 @@
+use crate::geometry::vector::Vector2I;
+use etagere::BucketedAtlasAllocator;
+
+struct SpriteCache {
+ atlasses: Vec<etagere::BucketedAtlasAllocator>,
+}
+
+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()
+ }
+}
@@ -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<Layer>,
@@ -12,6 +15,7 @@ pub struct Layer {
clip_bounds: Option<RectF>,
quads: Vec<Quad>,
shadows: Vec<Shadow>,
+ glyphs: Vec<Glyph>,
}
#[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 {
@@ -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<usize>, ColorU)],
- _ctx: &mut PaintContext,
- ) {
- // canvas.set_font_size(self.font_size);
- // let mut colors = colors.iter().peekable();
-
- // 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,
- // );
-
- // 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);
-
- // if glyph_origin.x() + max_glyph_width < viewport_rect.origin().x() {
- // continue;
- // }
-
- // 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),
- // );
- // }
- // }
- // }
+ pub fn paint(&self, bounds: RectF, colors: &[(Range<usize>, ColorU)], ctx: &mut PaintContext) {
+ let mut colors = colors.iter().peekable();
+ let mut color = ColorU::black();
+
+ 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 = 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;
+ }
+
+ while let Some((range, next_color)) = colors.peek() {
+ if glyph.index >= range.end {
+ colors.next();
+ } else {
+ color = *next_color;
+ break;
+ }
+ }
+
+ ctx.scene.push_glyph(scene::Glyph {
+ font_id: run.font_id,
+ font_size: self.font_size,
+ glyph_id: glyph.id,
+ origin: glyph_origin,
+ color,
+ });
+ }
+ }
}
}