diff --git a/piet-gpu/src/encoder.rs b/piet-gpu/src/encoder.rs index 12e9db4..0314003 100644 --- a/piet-gpu/src/encoder.rs +++ b/piet-gpu/src/encoder.rs @@ -31,6 +31,18 @@ pub struct Encoder { n_pathseg: u32, } +/// A scene fragment encoding a glyph. +/// +/// This is a reduced version of the full encoder. +#[derive(Default)] +pub struct GlyphEncoder { + tag_stream: Vec, + pathseg_stream: Vec, + drawobj_stream: Vec, + n_path: u32, + n_pathseg: u32, +} + // Currently same as Element, but may change - should become packed. const DRAWOBJ_SIZE: usize = 36; const TRANSFORM_SIZE: usize = 24; @@ -187,6 +199,14 @@ impl Encoder { pub(crate) fn n_transform(&self) -> usize { self.transform_stream.len() } + + pub(crate) fn encode_glyph(&mut self, glyph: &GlyphEncoder) { + self.tag_stream.extend(&glyph.tag_stream); + self.pathseg_stream.extend(&glyph.pathseg_stream); + self.drawobj_stream.extend(&glyph.drawobj_stream); + self.n_path += glyph.n_path; + self.n_pathseg += glyph.n_pathseg; + } } fn align_up(x: usize, align: usize) -> usize { @@ -197,3 +217,30 @@ fn align_up(x: usize, align: usize) -> usize { fn padding(x: usize, align: usize) -> usize { x.wrapping_neg() & (align - 1) } + +impl GlyphEncoder { + pub(crate) fn path_encoder(&mut self) -> PathEncoder { + PathEncoder::new(&mut self.tag_stream, &mut self.pathseg_stream) + } + + pub(crate) fn finish_path(&mut self, n_pathseg: u32) { + self.n_path += 1; + self.n_pathseg += n_pathseg; + } + + /// Encode a fill color draw object. + /// + /// This should be encoded after a path. + pub(crate) fn fill_color(&mut self, rgba_color: u32) { + let element = FillColor { + tag: ELEMENT_FILLCOLOR, + rgba_color, + ..Default::default() + }; + self.drawobj_stream.extend(bytemuck::bytes_of(&element)); + } + + pub(crate) fn is_color(&self) -> bool { + !self.drawobj_stream.is_empty() + } +} diff --git a/piet-gpu/src/render_ctx.rs b/piet-gpu/src/render_ctx.rs index 5b10fec..d13a888 100644 --- a/piet-gpu/src/render_ctx.rs +++ b/piet-gpu/src/render_ctx.rs @@ -1,6 +1,7 @@ use std::borrow::Cow; -use crate::stages::Config; +use crate::encoder::GlyphEncoder; +use crate::stages::{Config, Transform}; use crate::MAX_BLEND_STACK; use piet::kurbo::{Affine, Insets, PathEl, Point, Rect, Shape}; use piet::{ @@ -10,11 +11,11 @@ use piet::{ use piet_gpu_hal::BufWrite; use piet_gpu_types::encoder::{Encode, Encoder}; -use piet_gpu_types::scene::{Clip, Element, FillColor, FillLinGradient, SetFillMode, Transform}; +use piet_gpu_types::scene::{Clip, Element, FillLinGradient, SetFillMode}; use crate::gradient::{LinearGradient, RampCache}; use crate::text::Font; -pub use crate::text::{PathEncoder, PietGpuText, PietGpuTextLayout, PietGpuTextLayoutBuilder}; +pub use crate::text::{PietGpuText, PietGpuTextLayout, PietGpuTextLayoutBuilder}; pub struct PietGpuImage; @@ -262,6 +263,7 @@ impl RenderContext for PietGpuRenderContext { } fn draw_text(&mut self, layout: &Self::TextLayout, pos: impl Into) { + self.encode_linewidth(-1.0); layout.draw_text(self, pos.into()); } @@ -278,7 +280,7 @@ impl RenderContext for PietGpuRenderContext { if let Some(state) = self.state_stack.pop() { if state.rel_transform != Affine::default() { let a_inv = state.rel_transform.inverse(); - self.encode_transform(to_scene_transform(a_inv)); + self.encode_transform(Transform::from_kurbo(a_inv)); } self.cur_transform = state.transform; for _ in 0..state.n_clip { @@ -298,7 +300,7 @@ impl RenderContext for PietGpuRenderContext { } fn transform(&mut self, transform: Affine) { - self.encode_transform(to_scene_transform(transform)); + self.encode_transform(Transform::from_kurbo(transform)); if let Some(tos) = self.state_stack.last_mut() { tos.rel_transform *= transform; } @@ -437,26 +439,16 @@ impl PietGpuRenderContext { } } - pub(crate) fn append_path_encoder(&mut self, path: &PathEncoder) { - let elements = path.elements(); - self.elements.extend(elements.iter().cloned()); - self.pathseg_count += path.n_segs(); + pub(crate) fn encode_glyph(&mut self, glyph: &GlyphEncoder) { + self.new_encoder.encode_glyph(glyph); } pub(crate) fn fill_glyph(&mut self, rgba_color: u32) { - let fill = FillColor { rgba_color }; - self.elements.push(Element::FillColor(fill)); - self.path_count += 1; - } - - /// Bump the path count when rendering a color emoji. - pub(crate) fn bump_n_paths(&mut self, n_paths: usize) { - self.path_count += n_paths; + self.new_encoder.fill_color(rgba_color); } pub(crate) fn encode_transform(&mut self, transform: Transform) { - self.elements.push(Element::Transform(transform)); - self.trans_count += 1; + self.new_encoder.transform(transform); } fn encode_linewidth(&mut self, linewidth: f32) { @@ -507,14 +499,6 @@ fn rect_to_f32_4(rect: Rect) -> [f32; 4] { ] } -fn to_scene_transform(transform: Affine) -> Transform { - let c = transform.as_coeffs(); - Transform { - mat: [c[0] as f32, c[1] as f32, c[2] as f32, c[3] as f32], - translate: [c[4] as f32, c[5] as f32], - } -} - fn to_srgb(f: f64) -> f64 { if f <= 0.0031308 { f * 12.92 diff --git a/piet-gpu/src/text.rs b/piet-gpu/src/text.rs index 39f2922..d25320b 100644 --- a/piet-gpu/src/text.rs +++ b/piet-gpu/src/text.rs @@ -10,10 +10,10 @@ use piet::{ TextLayoutBuilder, TextStorage, }; -use piet_gpu_types::scene::{CubicSeg, Element, FillColor, LineSeg, QuadSeg, Transform}; - +use crate::encoder::GlyphEncoder; use crate::render_ctx::{self, FillMode}; use crate::PietGpuRenderContext; +use crate::stages::Transform; // This is very much a hack to get things working. // On Windows, can set this to "c:\\Windows\\Fonts\\seguiemj.ttf" to get color emoji @@ -51,13 +51,6 @@ struct Glyph { y: f32, } -#[derive(Default)] -pub struct PathEncoder { - elements: Vec, - n_segs: usize, - // If this is zero, then it's a text glyph and should be followed by a fill - n_colr_layers: usize, -} struct TextRenderCtx<'a> { scaler: Scaler<'a>, @@ -126,8 +119,8 @@ impl Font { Font { font_ref } } - fn make_path<'a>(&self, glyph_id: GlyphId, tc: &mut TextRenderCtx<'a>) -> PathEncoder { - let mut encoder = PathEncoder::default(); + fn make_path<'a>(&self, glyph_id: GlyphId, tc: &mut TextRenderCtx<'a>) -> GlyphEncoder { + let mut encoder = GlyphEncoder::default(); if tc.scaler.has_color_outlines() { if let Some(outline) = tc.scaler.scale_color_outline(glyph_id) { // TODO: be more sophisticated choosing a palette @@ -136,8 +129,8 @@ impl Font { while let Some(layer) = outline.get(i) { if let Some(color_ix) = layer.color_index() { let color = palette.get(color_ix); - encoder.append_outline(layer.verbs(), layer.points()); - encoder.append_solid_fill(color); + append_outline(&mut encoder, layer.verbs(), layer.points()); + encoder.fill_color(*bytemuck::from_bytes(&color)); } i += 1; } @@ -145,7 +138,7 @@ impl Font { } } if let Some(outline) = tc.scaler.scale_outline(glyph_id) { - encoder.append_outline(outline.verbs(), outline.points()); + append_outline(&mut encoder, outline.verbs(), outline.points()); } encoder } @@ -212,12 +205,10 @@ impl PietGpuTextLayout { last_x = glyph.x; //println!("{:?}, {:?}", transform.mat, transform.translate); ctx.encode_transform(transform); - let path = self.font.make_path(glyph.glyph_id, &mut tc); - ctx.append_path_encoder(&path); - if path.n_colr_layers == 0 { + let glyph = self.font.make_path(glyph.glyph_id, &mut tc); + ctx.encode_glyph(&glyph); + if !glyph.is_color() { ctx.fill_glyph(0xff_ff_ff_ff); - } else { - ctx.bump_n_paths(path.n_colr_layers); } } if let Some(transform) = inv_transform { @@ -271,79 +262,38 @@ impl TextLayoutBuilder for PietGpuTextLayoutBuilder { } } -impl PathEncoder { - pub(crate) fn elements(&self) -> &[Element] { - &self.elements - } - - pub(crate) fn n_segs(&self) -> usize { - self.n_segs - } - - fn append_outline(&mut self, verbs: &[Verb], points: &[Vector]) { - let elements = &mut self.elements; - let old_len = elements.len(); - let mut i = 0; - let mut start_pt = [0.0f32; 2]; - let mut last_pt = [0.0f32; 2]; - for verb in verbs { - match verb { - Verb::MoveTo => { - start_pt = convert_swash_point(points[i]); - last_pt = start_pt; - i += 1; - } - Verb::LineTo => { - let p1 = convert_swash_point(points[i]); - elements.push(Element::Line(LineSeg { p0: last_pt, p1 })); - last_pt = p1; - i += 1; - } - Verb::QuadTo => { - let p1 = convert_swash_point(points[i]); - let p2 = convert_swash_point(points[i + 1]); - elements.push(Element::Quad(QuadSeg { - p0: last_pt, - p1, - p2, - })); - last_pt = p2; - i += 2; - } - Verb::CurveTo => { - let p1 = convert_swash_point(points[i]); - let p2 = convert_swash_point(points[i + 1]); - let p3 = convert_swash_point(points[i + 2]); - elements.push(Element::Cubic(CubicSeg { - p0: last_pt, - p1, - p2, - p3, - })); - last_pt = p3; - i += 3; - } - Verb::Close => { - if start_pt != last_pt { - elements.push(Element::Line(LineSeg { - p0: last_pt, - p1: start_pt, - })); - } - } +fn append_outline(encoder: &mut GlyphEncoder, verbs: &[Verb], points: &[Vector]) { + let mut path_encoder = encoder.path_encoder(); + let mut i = 0; + for verb in verbs { + match verb { + Verb::MoveTo => { + let p = points[i]; + path_encoder.move_to(p.x, p.y); + i += 1; } + Verb::LineTo => { + let p = points[i]; + path_encoder.line_to(p.x, p.y); + i += 1; + } + Verb::QuadTo => { + let p1 = points[i]; + let p2 = points[i + 1]; + path_encoder.quad_to(p1.x, p1.y, p2.x, p2.y); + i += 2; + } + Verb::CurveTo => { + let p1 = points[i]; + let p2 = points[i + 1]; + let p3 = points[i + 2]; + path_encoder.cubic_to(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); + i += 3; + } + Verb::Close => path_encoder.close_path(), } - self.n_segs += elements.len() - old_len; - } - - fn append_solid_fill(&mut self, color: [u8; 4]) { - let rgba_color = u32::from_be_bytes(color); - self.elements - .push(Element::FillColor(FillColor { rgba_color })); - self.n_colr_layers += 1; } -} - -fn convert_swash_point(v: Vector) -> [f32; 2] { - [v.x, v.y] + path_encoder.path(); + let n_pathseg = path_encoder.n_pathseg(); + encoder.finish_path(n_pathseg); }