use std::{borrow::Cow, ops::RangeBounds}; use piet::{ kurbo::{Affine, Insets, PathEl, Point, Rect, Shape, Size}, HitTestPosition, TextAttribute, TextStorage, }; use piet::{ Color, Error, FixedGradient, FontFamily, HitTestPoint, ImageFormat, InterpolationMode, IntoBrush, LineMetric, RenderContext, StrokeStyle, Text, TextLayout, TextLayoutBuilder, }; use crate::MAX_BLEND_STACK; use piet_gpu_types::encoder::{Encode, Encoder}; use piet_gpu_types::scene::{ Clip, CubicSeg, Element, FillColor, FillLinGradient, LineSeg, QuadSeg, SetFillMode, SetLineWidth, Transform, }; use crate::gradient::{LinearGradient, RampCache}; use crate::text::Font; pub use crate::text::{PathEncoder, PietGpuText, PietGpuTextLayout, PietGpuTextLayoutBuilder}; pub struct PietGpuImage; pub struct PietGpuRenderContext { encoder: Encoder, elements: Vec, // Will probably need direct accesss to hal Device to create images etc. inner_text: PietGpuText, stroke_width: f32, fill_mode: FillMode, // We're tallying these cpu-side for expedience, but will probably // move this to some kind of readback from element processing. /// The count of elements that make it through to coarse rasterization. path_count: usize, /// The count of path segment elements. pathseg_count: usize, /// The count of transform elements. trans_count: usize, cur_transform: Affine, state_stack: Vec, clip_stack: Vec, ramp_cache: RampCache, } #[derive(Clone)] pub enum PietGpuBrush { Solid(u32), LinGradient(LinearGradient), } #[derive(Default)] struct State { /// The transform relative to the parent state. rel_transform: Affine, /// The transform at the parent state. /// /// This invariant should hold: transform * rel_transform = cur_transform transform: Affine, n_clip: usize, } struct ClipElement { /// Index of BeginClip element in element vec, for bbox fixup. begin_ix: usize, bbox: Option, } #[derive(Clone, Copy, PartialEq)] pub(crate) enum FillMode { // Fill path according to the non-zero winding rule. Nonzero = 0, // Fill stroked path. Stroke = 1, } const TOLERANCE: f64 = 0.25; impl PietGpuRenderContext { pub fn new() -> PietGpuRenderContext { let encoder = Encoder::new(); let elements = Vec::new(); let font = Font::new(); let inner_text = PietGpuText::new(font); let stroke_width = 0.0; PietGpuRenderContext { encoder, elements, inner_text, stroke_width, fill_mode: FillMode::Nonzero, path_count: 0, pathseg_count: 0, trans_count: 0, cur_transform: Affine::default(), state_stack: Vec::new(), clip_stack: Vec::new(), ramp_cache: RampCache::default(), } } pub fn get_scene_buf(&mut self) -> &[u8] { self.elements.encode(&mut self.encoder); self.encoder.buf() } pub fn path_count(&self) -> usize { self.path_count } pub fn pathseg_count(&self) -> usize { self.pathseg_count } pub fn trans_count(&self) -> usize { self.trans_count } pub fn get_ramp_data(&self) -> Vec { self.ramp_cache.get_ramp_data() } pub(crate) fn set_fill_mode(&mut self, fill_mode: FillMode) { if self.fill_mode != fill_mode { self.elements.push(Element::SetFillMode(SetFillMode { fill_mode: fill_mode as u32, })); self.fill_mode = fill_mode; } } } impl RenderContext for PietGpuRenderContext { type Brush = PietGpuBrush; type Image = PietGpuImage; type Text = PietGpuText; type TextLayout = PietGpuTextLayout; fn status(&mut self) -> Result<(), Error> { Ok(()) } fn solid_brush(&mut self, color: Color) -> Self::Brush { // kernel4 expects colors encoded in alpha-premultiplied sRGB: // // [α,sRGB(α⋅R),sRGB(α⋅G),sRGB(α⋅B)] // // See also http://ssp.impulsetrain.com/gamma-premult.html. let (r, g, b, a) = color.as_rgba(); let premul = Color::rgba( to_srgb(from_srgb(r) * a), to_srgb(from_srgb(g) * a), to_srgb(from_srgb(b) * a), a, ); PietGpuBrush::Solid(premul.as_rgba_u32()) } fn gradient(&mut self, gradient: impl Into) -> Result { match gradient.into() { FixedGradient::Linear(lin) => { let lin = self.ramp_cache.add_linear_gradient(&lin); Ok(PietGpuBrush::LinGradient(lin)) } _ => todo!("don't do radial gradients yet"), } } fn clear(&mut self, _color: Color) {} fn stroke(&mut self, shape: impl Shape, brush: &impl IntoBrush, width: f64) { let width_f32 = width as f32; if self.stroke_width != width_f32 { self.elements .push(Element::SetLineWidth(SetLineWidth { width: width_f32 })); self.stroke_width = width_f32; } self.set_fill_mode(FillMode::Stroke); let brush = brush.make_brush(self, || shape.bounding_box()).into_owned(); // Note: the bbox contribution of stroke becomes more complicated with miter joins. self.accumulate_bbox(|| shape.bounding_box() + Insets::uniform(width * 0.5)); let path = shape.path_elements(TOLERANCE); self.encode_path(path, false); self.encode_brush(&brush); } fn stroke_styled( &mut self, _shape: impl Shape, _brush: &impl IntoBrush, _width: f64, _style: &StrokeStyle, ) { } fn fill(&mut self, shape: impl Shape, brush: &impl IntoBrush) { let brush = brush.make_brush(self, || shape.bounding_box()).into_owned(); // Note: we might get a good speedup from using an approximate bounding box. // Perhaps that should be added to kurbo. self.accumulate_bbox(|| shape.bounding_box()); let path = shape.path_elements(TOLERANCE); self.set_fill_mode(FillMode::Nonzero); self.encode_path(path, true); self.encode_brush(&brush); } fn fill_even_odd(&mut self, _shape: impl Shape, _brush: &impl IntoBrush) {} fn clip(&mut self, shape: impl Shape) { self.set_fill_mode(FillMode::Nonzero); let path = shape.path_elements(TOLERANCE); self.encode_path(path, true); let begin_ix = self.elements.len(); self.elements.push(Element::BeginClip(Clip { bbox: Default::default(), })); if self.clip_stack.len() >= MAX_BLEND_STACK { panic!("Maximum clip/blend stack size {} exceeded", MAX_BLEND_STACK); } self.clip_stack.push(ClipElement { bbox: None, begin_ix, }); self.path_count += 1; if let Some(tos) = self.state_stack.last_mut() { tos.n_clip += 1; } } fn text(&mut self) -> &mut Self::Text { &mut self.inner_text } fn draw_text(&mut self, layout: &Self::TextLayout, pos: impl Into) { layout.draw_text(self, pos.into()); } fn save(&mut self) -> Result<(), Error> { self.state_stack.push(State { rel_transform: Affine::default(), transform: self.cur_transform, n_clip: 0, }); Ok(()) } fn restore(&mut self) -> Result<(), Error> { 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.cur_transform = state.transform; for _ in 0..state.n_clip { self.pop_clip(); } Ok(()) } else { Err(Error::StackUnbalance) } } fn finish(&mut self) -> Result<(), Error> { for _ in 0..self.clip_stack.len() { self.pop_clip(); } Ok(()) } fn transform(&mut self, transform: Affine) { self.encode_transform(to_scene_transform(transform)); if let Some(tos) = self.state_stack.last_mut() { tos.rel_transform *= transform; } self.cur_transform *= transform; } fn make_image( &mut self, _width: usize, _height: usize, _buf: &[u8], _format: ImageFormat, ) -> Result { Ok(PietGpuImage) } fn draw_image( &mut self, _image: &Self::Image, _rect: impl Into, _interp: InterpolationMode, ) { } fn draw_image_area( &mut self, _image: &Self::Image, _src_rect: impl Into, _dst_rect: impl Into, _interp: InterpolationMode, ) { } fn blurred_rect(&mut self, _rect: Rect, _blur_radius: f64, _brush: &impl IntoBrush) {} fn current_transform(&self) -> Affine { self.cur_transform } fn with_save(&mut self, f: impl FnOnce(&mut Self) -> Result<(), Error>) -> Result<(), Error> { self.save()?; // Always try to restore the stack, even if `f` errored. f(self).and(self.restore()) } } impl PietGpuRenderContext { fn encode_line_seg(&mut self, seg: LineSeg) { self.elements.push(Element::Line(seg)); self.pathseg_count += 1; } fn encode_quad_seg(&mut self, seg: QuadSeg) { self.elements.push(Element::Quad(seg)); self.pathseg_count += 1; } fn encode_cubic_seg(&mut self, seg: CubicSeg) { self.elements.push(Element::Cubic(seg)); self.pathseg_count += 1; } fn encode_path(&mut self, path: impl Iterator, is_fill: bool) { if is_fill { self.encode_path_inner( path.flat_map(|el| { match el { PathEl::MoveTo(..) => Some(PathEl::ClosePath), _ => None, } .into_iter() .chain(Some(el)) }) .chain(Some(PathEl::ClosePath)), ) } else { self.encode_path_inner(path) } } fn encode_path_inner(&mut self, path: impl Iterator) { let flatten = false; if flatten { let mut start_pt = None; let mut last_pt = None; piet::kurbo::flatten(path, TOLERANCE, |el| { match el { PathEl::MoveTo(p) => { let scene_pt = to_f32_2(p); start_pt = Some(scene_pt); last_pt = Some(scene_pt); } PathEl::LineTo(p) => { let scene_pt = to_f32_2(p); let seg = LineSeg { p0: last_pt.unwrap(), p1: scene_pt, }; self.encode_line_seg(seg); last_pt = Some(scene_pt); } PathEl::ClosePath => { if let (Some(start), Some(last)) = (start_pt.take(), last_pt.take()) { if last != start { let seg = LineSeg { p0: last, p1: start, }; self.encode_line_seg(seg); } } } _ => (), } //println!("{:?}", el); }); } else { let mut start_pt = None; let mut last_pt = None; for el in path { match el { PathEl::MoveTo(p) => { let scene_pt = to_f32_2(p); start_pt = Some(scene_pt); last_pt = Some(scene_pt); } PathEl::LineTo(p) => { let scene_pt = to_f32_2(p); let seg = LineSeg { p0: last_pt.unwrap(), p1: scene_pt, }; self.encode_line_seg(seg); last_pt = Some(scene_pt); } PathEl::QuadTo(p1, p2) => { let scene_p1 = to_f32_2(p1); let scene_p2 = to_f32_2(p2); let seg = QuadSeg { p0: last_pt.unwrap(), p1: scene_p1, p2: scene_p2, }; self.encode_quad_seg(seg); last_pt = Some(scene_p2); } PathEl::CurveTo(p1, p2, p3) => { let scene_p1 = to_f32_2(p1); let scene_p2 = to_f32_2(p2); let scene_p3 = to_f32_2(p3); let seg = CubicSeg { p0: last_pt.unwrap(), p1: scene_p1, p2: scene_p2, p3: scene_p3, }; self.encode_cubic_seg(seg); last_pt = Some(scene_p3); } PathEl::ClosePath => { if let (Some(start), Some(last)) = (start_pt.take(), last_pt.take()) { if last != start { let seg = LineSeg { p0: last, p1: start, }; self.encode_line_seg(seg); } } } } //println!("{:?}", el); } } } fn pop_clip(&mut self) { let tos = self.clip_stack.pop().unwrap(); let bbox = tos.bbox.unwrap_or_default(); let bbox_f32_4 = rect_to_f32_4(bbox); self.elements .push(Element::EndClip(Clip { bbox: bbox_f32_4 })); self.path_count += 1; if let Element::BeginClip(begin_clip) = &mut self.elements[tos.begin_ix] { begin_clip.bbox = bbox_f32_4; } else { unreachable!("expected BeginClip, not found"); } if let Some(bbox) = tos.bbox { self.union_bbox(bbox); } } /// Accumulate a bbox. /// /// The bbox is given lazily as a closure, relative to the current transform. /// It's lazy because we don't need to compute it unless we're inside a clip. fn accumulate_bbox(&mut self, f: impl FnOnce() -> Rect) { if !self.clip_stack.is_empty() { let bbox = f(); let bbox = self.cur_transform.transform_rect_bbox(bbox); self.union_bbox(bbox); } } /// Accumulate an absolute bbox. /// /// The bbox is given already transformed into surface coordinates. fn union_bbox(&mut self, bbox: Rect) { if let Some(tos) = self.clip_stack.last_mut() { tos.bbox = if let Some(old_bbox) = tos.bbox { Some(old_bbox.union(bbox)) } else { Some(bbox) }; } } pub(crate) fn append_path_encoder(&mut self, path: &PathEncoder) { let elements = path.elements(); self.elements.extend(elements.iter().cloned()); self.pathseg_count += elements.len(); } 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; } pub(crate) fn encode_transform(&mut self, transform: Transform) { self.elements.push(Element::Transform(transform)); self.trans_count += 1; } fn encode_brush(&mut self, brush: &PietGpuBrush) { match brush { PietGpuBrush::Solid(rgba_color) => { let fill = FillColor { rgba_color: *rgba_color, }; self.elements.push(Element::FillColor(fill)); self.path_count += 1; } PietGpuBrush::LinGradient(lin) => { let fill_lin = FillLinGradient { index: lin.ramp_id, p0: lin.start, p1: lin.end, }; self.elements.push(Element::FillLinGradient(fill_lin)); self.path_count += 1; } } } } impl IntoBrush for PietGpuBrush { fn make_brush<'b>( &'b self, _piet: &mut PietGpuRenderContext, _bbox: impl FnOnce() -> Rect, ) -> std::borrow::Cow<'b, PietGpuBrush> { Cow::Borrowed(self) } } pub(crate) fn to_f32_2(point: Point) -> [f32; 2] { [point.x as f32, point.y as f32] } fn rect_to_f32_4(rect: Rect) -> [f32; 4] { [ rect.x0 as f32, rect.y0 as f32, rect.x1 as f32, rect.y1 as f32, ] } 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 } else { let a = 0.055; (1. + a) * f64::powf(f, f64::recip(2.4)) - a } } fn from_srgb(f: f64) -> f64 { if f <= 0.04045 { f / 12.92 } else { let a = 0.055; f64::powf((f + a) * f64::recip(1. + a), 2.4) } }