From d07fda8ef8f368085b16097fd8fb62dc64bf83ef Mon Sep 17 00:00:00 2001 From: Chad Brokaw Date: Tue, 16 Aug 2022 14:52:04 -0400 Subject: [PATCH] Make transforms stateless Removes the transform state mutator from SceneBuilder and adds transform parameters to push_layer, fill and stroke methods. --- pgpu-render/src/lib.rs | 16 +-- pgpu-render/src/render.rs | 27 +++-- piet-gpu/bin/winit.rs | 3 +- piet-gpu/src/samples.rs | 177 +++++++++++++------------------- piet-scene/src/glyph.rs | 14 ++- piet-scene/src/scene/builder.rs | 61 ++++------- 6 files changed, 127 insertions(+), 171 deletions(-) diff --git a/pgpu-render/src/lib.rs b/pgpu-render/src/lib.rs index 8d137ea..43ff438 100644 --- a/pgpu-render/src/lib.rs +++ b/pgpu-render/src/lib.rs @@ -273,8 +273,8 @@ pub unsafe extern "C" fn pgpu_scene_builder_transform( builder: *mut PgpuSceneBuilder<'static>, transform: *const PgpuTransform, ) { - if !transform.is_null() { - (*builder).0.transform((*transform).into()) + if let Some(transform) = transform.as_ref() { + (*builder).transform = (*transform).into(); } } @@ -308,9 +308,13 @@ pub unsafe extern "C" fn pgpu_scene_builder_fill_path( } else { Some((*brush_transform).into()) }; - (*builder) - .0 - .fill(fill, &brush, brush_transform, (*path).clone()); + (*builder).builder.fill( + fill, + (*builder).transform, + &brush, + brush_transform, + (*path).clone(), + ); } /// Appends a scene fragment to the underlying scene or fragment. The @@ -329,7 +333,7 @@ pub unsafe extern "C" fn pgpu_scene_builder_append_fragment( } else { Some((*transform).into()) }; - (*builder).0.append(&(*fragment).0, transform); + (*builder).builder.append(&(*fragment).0, transform); } /// Finalizes the scene builder, making the underlying scene ready for diff --git a/pgpu-render/src/render.rs b/pgpu-render/src/render.rs index 591fdeb..4cf9563 100644 --- a/pgpu-render/src/render.rs +++ b/pgpu-render/src/render.rs @@ -103,19 +103,18 @@ impl PgpuRenderer { } /// Encoded streams and resources describing a vector graphics scene. -pub struct PgpuScene { - scene: Scene, -} +pub struct PgpuScene(pub Scene); impl PgpuScene { pub fn new() -> Self { - Self { - scene: Scene::default(), - } + Self(Scene::default()) } pub fn builder(&mut self) -> PgpuSceneBuilder { - PgpuSceneBuilder(piet_scene::SceneBuilder::for_scene(&mut self.scene)) + PgpuSceneBuilder { + builder: piet_scene::SceneBuilder::for_scene(&mut self.0), + transform: Affine::IDENTITY, + } } } @@ -128,20 +127,26 @@ impl PgpuSceneFragment { } pub fn builder(&mut self) -> PgpuSceneBuilder { - PgpuSceneBuilder(piet_scene::SceneBuilder::for_fragment(&mut self.0)) + PgpuSceneBuilder { + builder: piet_scene::SceneBuilder::for_fragment(&mut self.0), + transform: Affine::IDENTITY, + } } } /// Builder for constructing an encoded scene. -pub struct PgpuSceneBuilder<'a>(pub piet_scene::SceneBuilder<'a>); +pub struct PgpuSceneBuilder<'a> { + pub builder: piet_scene::SceneBuilder<'a>, + pub transform: Affine, +} impl<'a> PgpuSceneBuilder<'a> { pub fn add_glyph(&mut self, glyph: &PgpuGlyph, transform: &piet_scene::Affine) { - self.0.append(&glyph.fragment, Some(*transform)); + self.builder.append(&glyph.fragment, Some(*transform)); } pub fn finish(self) { - self.0.finish(); + self.builder.finish(); } } diff --git a/piet-gpu/bin/winit.rs b/piet-gpu/bin/winit.rs index 6a4df36..c97651d 100644 --- a/piet-gpu/bin/winit.rs +++ b/piet-gpu/bin/winit.rs @@ -126,7 +126,7 @@ fn main() -> Result<(), Error> { } else { let mut builder = SceneBuilder::for_scene(&mut scene); - const N_SAMPLES: usize = 4; + const N_SAMPLES: usize = 5; match sample_index % N_SAMPLES { 0 => samples::render_anim_frame( &mut builder, @@ -135,6 +135,7 @@ fn main() -> Result<(), Error> { ), 1 => samples::render_blend_grid(&mut builder), 2 => samples::render_tiger(&mut builder, false), + 3 => samples::render_brush_transform(&mut builder, current_frame), _ => samples::render_scene(&mut builder), } render_info(&mut simple_text, &mut builder, &info_string); diff --git a/piet-gpu/src/samples.rs b/piet-gpu/src/samples.rs index a04f95b..51e49cd 100644 --- a/piet-gpu/src/samples.rs +++ b/piet-gpu/src/samples.rs @@ -16,6 +16,7 @@ pub fn render_svg(sb: &mut SceneBuilder, svg: &PicoSvg, print_stats: bool) { Item::Fill(fill) => { sb.fill( Fill::NonZero, + Affine::IDENTITY, &fill.color.into(), None, convert_bez_path(&fill.path), @@ -24,6 +25,7 @@ pub fn render_svg(sb: &mut SceneBuilder, svg: &PicoSvg, print_stats: bool) { Item::Stroke(stroke) => { sb.stroke( &simple_stroke(stroke.width as f32), + Affine::IDENTITY, &stroke.color.into(), None, convert_bez_path(&stroke.path), @@ -76,6 +78,7 @@ fn render_cardioid(sb: &mut SceneBuilder) { } sb.stroke( &simple_stroke(2.0), + Affine::IDENTITY, &Brush::Solid(Color::rgb8(0, 0, 0)), None, &path, @@ -102,7 +105,7 @@ fn render_clip_test(sb: &mut SceneBuilder) { PathElement::LineTo((X0, Y1).into()), PathElement::Close, ]; - sb.push_layer(Mix::Clip.into(), path); + sb.push_layer(Mix::Clip.into(), Affine::IDENTITY, path); } let rect = Rect { min: Point::new(X0, Y0), @@ -110,6 +113,7 @@ fn render_clip_test(sb: &mut SceneBuilder) { }; sb.fill( Fill::NonZero, + Affine::IDENTITY, &Brush::Solid(Color::rgb8(0, 0, 0)), None, rect.elements(), @@ -124,22 +128,29 @@ fn render_alpha_test(sb: &mut SceneBuilder) { // Alpha compositing tests. sb.fill( Fill::NonZero, + Affine::IDENTITY, &Color::rgb8(255, 0, 0).into(), None, - make_diamond(Point::new(1024.0, 100.0)), + make_diamond(1024.0, 100.0), ); sb.fill( Fill::NonZero, + Affine::IDENTITY, &Color::rgba8(0, 255, 0, 0x80).into(), None, - make_diamond(Point::new(1024.0, 125.0)), + make_diamond(1024.0, 125.0), + ); + sb.push_layer( + Mix::Clip.into(), + Affine::IDENTITY, + make_diamond(1024.0, 150.0), ); - sb.push_layer(Mix::Clip.into(), make_diamond(Point::new(1024.0, 150.0))); sb.fill( Fill::NonZero, + Affine::IDENTITY, &Color::rgba8(0, 0, 255, 0x80).into(), None, - make_diamond(Point::new(1024.0, 175.0)), + make_diamond(1024.0, 175.0), ); sb.pop_layer(); } @@ -170,15 +181,12 @@ pub fn render_blend_grid(sb: &mut SceneBuilder) { let transform = Affine::translate(i as f32 * 225., j as f32 * 225.); let square = blend_square(blend.into()); sb.append(&square, Some(transform)); - // sb.append(&square, Some(transform)); - // render_blend_square(sb, blend.into(), transform); } } #[allow(unused)] fn render_blend_square(sb: &mut SceneBuilder, blend: BlendMode, transform: Affine) { // Inspired by https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode - sb.transform(transform); let rect = Rect::from_origin_size(Point::new(0., 0.), 200., 200.); let stops = &[ GradientStop { @@ -196,7 +204,7 @@ fn render_blend_square(sb: &mut SceneBuilder, blend: BlendMode, transform: Affin stops: stops.into(), extend: ExtendMode::Pad, }); - sb.fill(Fill::NonZero, &linear, None, rect.elements()); + sb.fill(Fill::NonZero, transform, &linear, None, rect.elements()); const GRADIENTS: &[(f32, f32, Color)] = &[ (150., 0., Color::rgb8(64, 240, 255)), (175., 100., Color::rgb8(240, 96, 255)), @@ -223,14 +231,14 @@ fn render_blend_square(sb: &mut SceneBuilder, blend: BlendMode, transform: Affin stops: stops.into(), extend: ExtendMode::Pad, }); - sb.fill(Fill::NonZero, &rad, None, rect.elements()); + sb.fill(Fill::NonZero, transform, &rad, None, rect.elements()); } const COLORS: &[Color] = &[ Color::rgb8(0, 0, 255), Color::rgb8(0, 255, 0), Color::rgb8(255, 0, 0), ]; - sb.push_layer(Mix::Normal.into(), rect.elements()); + sb.push_layer(Mix::Normal.into(), transform, rect.elements()); for (i, c) in COLORS.iter().enumerate() { let stops = &[ GradientStop { @@ -248,17 +256,16 @@ fn render_blend_square(sb: &mut SceneBuilder, blend: BlendMode, transform: Affin stops: stops.into(), extend: ExtendMode::Pad, }); - sb.transform(transform); - sb.push_layer(blend, rect.elements()); + sb.push_layer(blend, transform, rect.elements()); // squash the ellipse let a = transform * Affine::translate(100., 100.) * Affine::rotate(std::f32::consts::FRAC_PI_3 * (i * 2 + 1) as f32) * Affine::scale(1.0, 0.357) * Affine::translate(-100., -100.); - sb.transform(a); sb.fill( Fill::NonZero, + a, &linear, None, make_ellipse(100., 100., 90., 90.), @@ -272,93 +279,7 @@ fn render_blend_square(sb: &mut SceneBuilder, blend: BlendMode, transform: Affin fn blend_square(blend: BlendMode) -> SceneFragment { let mut fragment = SceneFragment::default(); let mut sb = SceneBuilder::for_fragment(&mut fragment); - // Inspired by https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode - let rect = Rect::from_origin_size(Point::new(0., 0.), 200., 200.); - let stops = &[ - GradientStop { - color: Color::rgb8(0, 0, 0), - offset: 0.0, - }, - GradientStop { - color: Color::rgb8(255, 255, 255), - offset: 1.0, - }, - ][..]; - let linear = Brush::LinearGradient(LinearGradient { - start: Point::new(0.0, 0.0), - end: Point::new(200.0, 0.0), - stops: stops.into(), - extend: ExtendMode::Pad, - }); - sb.fill(Fill::NonZero, &linear, None, rect.elements()); - const GRADIENTS: &[(f32, f32, Color)] = &[ - (150., 0., Color::rgb8(64, 240, 255)), - (175., 100., Color::rgb8(240, 96, 255)), - (125., 200., Color::rgb8(255, 192, 64)), - ]; - for (x, y, c) in GRADIENTS { - let mut color2 = c.clone(); - color2.a = 0; - let stops = &[ - GradientStop { - color: c.clone(), - offset: 0.0, - }, - GradientStop { - color: color2, - offset: 1.0, - }, - ][..]; - let rad = Brush::RadialGradient(RadialGradient { - center0: Point::new(*x, *y), - center1: Point::new(*x, *y), - radius0: 0.0, - radius1: 100.0, - stops: stops.into(), - extend: ExtendMode::Pad, - }); - sb.fill(Fill::NonZero, &rad, None, rect.elements()); - } - const COLORS: &[Color] = &[ - Color::rgb8(0, 0, 255), - Color::rgb8(0, 255, 0), - Color::rgb8(255, 0, 0), - ]; - sb.push_layer(Mix::Normal.into(), rect.elements()); - for (i, c) in COLORS.iter().enumerate() { - let stops = &[ - GradientStop { - color: Color::rgb8(255, 255, 255), - offset: 0.0, - }, - GradientStop { - color: c.clone(), - offset: 1.0, - }, - ][..]; - let linear = Brush::LinearGradient(LinearGradient { - start: Point::new(0.0, 0.0), - end: Point::new(0.0, 200.0), - stops: stops.into(), - extend: ExtendMode::Pad, - }); - sb.transform(Affine::IDENTITY); - sb.push_layer(blend, rect.elements()); - // squash the ellipse - let a = Affine::translate(100., 100.) - * Affine::rotate(std::f32::consts::FRAC_PI_3 * (i * 2 + 1) as f32) - * Affine::scale(1.0, 0.357) - * Affine::translate(-100., -100.); - sb.transform(a); - sb.fill( - Fill::NonZero, - &linear, - None, - make_ellipse(100., 100., 90., 90.), - ); - sb.pop_layer(); - } - sb.pop_layer(); + render_blend_square(&mut sb, blend, Affine::IDENTITY); sb.finish(); fragment } @@ -367,6 +288,7 @@ fn blend_square(blend: BlendMode) -> SceneFragment { pub fn render_anim_frame(sb: &mut SceneBuilder, text: &mut SimpleText, i: usize) { sb.fill( Fill::NonZero, + Affine::IDENTITY, &Brush::Solid(Color::rgb8(128, 128, 128)), None, Rect::from_origin_size(Point::new(0.0, 0.0), 1000.0, 1000.0).elements(), @@ -389,7 +311,6 @@ pub fn render_anim_frame(sb: &mut SceneBuilder, text: &mut SimpleText, i: usize) Affine::translate(110.0, 700.0), s, ); - sb.transform(Affine::IDENTITY); let th = (std::f32::consts::PI / 180.0) * (i as f32); let center = Point::new(500.0, 500.0); let mut p1 = center; @@ -397,12 +318,54 @@ pub fn render_anim_frame(sb: &mut SceneBuilder, text: &mut SimpleText, i: usize) p1.y += 400.0 * th.sin(); sb.stroke( &simple_stroke(5.0), + Affine::IDENTITY, &Brush::Solid(Color::rgb8(128, 0, 0)), None, &[PathElement::MoveTo(center), PathElement::LineTo(p1)], ); } +#[allow(unused)] +pub fn render_brush_transform(sb: &mut SceneBuilder, i: usize) { + let th = (std::f32::consts::PI / 180.0) * (i as f32); + let stops = &[ + GradientStop { + color: Color::rgb8(255, 0, 0), + offset: 0.0, + }, + GradientStop { + color: Color::rgb8(0, 255, 0), + offset: 0.5, + }, + GradientStop { + color: Color::rgb8(0, 0, 255), + offset: 1.0, + }, + ][..]; + let linear = LinearGradient { + start: Point::new(0.0, 0.0), + end: Point::new(0.0, 200.0), + stops: stops.into(), + extend: ExtendMode::Pad, + } + .into(); + sb.fill( + Fill::NonZero, + Affine::translate(200.0, 200.0), + &linear, + Some(Affine::rotate(th).around_center(200.0, 100.0)), + Rect::from_origin_size(Point::default(), 400.0, 200.0).elements(), + ); + sb.stroke( + &simple_stroke(40.0), + Affine::translate(800.0, 200.0), + &linear, + Some(Affine::rotate(th).around_center(200.0, 100.0)), + Rect::from_origin_size(Point::default(), 400.0, 200.0).elements(), + ); + +} + fn convert_bez_path<'a>(path: &'a BezPath) -> impl Iterator + 'a + Clone { path.elements() .iter() @@ -440,13 +403,13 @@ fn make_ellipse(cx: f32, cy: f32, rx: f32, ry: f32) -> impl Iterator impl Iterator + Clone { +fn make_diamond(cx: f32, cy: f32) -> impl Iterator + Clone { const SIZE: f32 = 50.0; let elements = [ - PathElement::MoveTo(Point::new(origin.x, origin.y - SIZE)), - PathElement::LineTo(Point::new(origin.x + SIZE, origin.y)), - PathElement::LineTo(Point::new(origin.x, origin.y + SIZE)), - PathElement::LineTo(Point::new(origin.x - SIZE, origin.y)), + PathElement::MoveTo(Point::new(cx, cy - SIZE)), + PathElement::LineTo(Point::new(cx + SIZE, cy)), + PathElement::LineTo(Point::new(cx, cy + SIZE)), + PathElement::LineTo(Point::new(cx - SIZE, cy)), PathElement::Close, ]; (0..elements.len()).map(move |i| elements[i]) diff --git a/piet-scene/src/glyph.rs b/piet-scene/src/glyph.rs index 8eb0627..6f752b7 100644 --- a/piet-scene/src/glyph.rs +++ b/piet-scene/src/glyph.rs @@ -90,6 +90,7 @@ impl<'a> GlyphProvider<'a> { let mut builder = SceneBuilder::for_fragment(&mut fragment); builder.fill( Fill::NonZero, + Affine::IDENTITY, brush.unwrap_or(&Brush::Solid(Color::rgb8(255, 255, 255))), None, convert_path(path.elements()), @@ -125,10 +126,15 @@ impl<'a> GlyphProvider<'a> { if let Some(xform) = xform_stack.last() { builder.push_layer( Default::default(), + Affine::IDENTITY, convert_transformed_path(path.elements(), xform), ); } else { - builder.push_layer(Default::default(), convert_path(path.elements())); + builder.push_layer( + Default::default(), + Affine::IDENTITY, + convert_path(path.elements()), + ); } } Command::PopClip => builder.pop_layer(), @@ -140,7 +146,7 @@ impl<'a> GlyphProvider<'a> { if let Some(xform) = xform_stack.last() { rect = rect.transform(xform); } - builder.push_layer(Default::default(), rect.elements()); + builder.push_layer(Default::default(), Affine::IDENTITY, rect.elements()); } Command::PopLayer => builder.pop_layer(), Command::BeginBlend(bounds, mode) => { @@ -151,7 +157,7 @@ impl<'a> GlyphProvider<'a> { if let Some(xform) = xform_stack.last() { rect = rect.transform(xform); } - builder.push_layer(convert_blend(*mode), rect.elements()) + builder.push_layer(convert_blend(*mode), Affine::IDENTITY, rect.elements()) } Command::EndBlend => builder.pop_layer(), Command::SimpleFill(path_index, brush, brush_xform) => { @@ -161,6 +167,7 @@ impl<'a> GlyphProvider<'a> { if let Some(xform) = xform_stack.last() { builder.fill( Fill::NonZero, + Affine::IDENTITY, &brush, brush_xform.map(|x| x * *xform), convert_transformed_path(path.elements(), xform), @@ -168,6 +175,7 @@ impl<'a> GlyphProvider<'a> { } else { builder.fill( Fill::NonZero, + Affine::IDENTITY, &brush, brush_xform, convert_path(path.elements()), diff --git a/piet-scene/src/scene/builder.rs b/piet-scene/src/scene/builder.rs index d7c8b03..56f9d7f 100644 --- a/piet-scene/src/scene/builder.rs +++ b/piet-scene/src/scene/builder.rs @@ -49,21 +49,15 @@ impl<'a> SceneBuilder<'a> { } } - /// Sets the current transformation. - pub fn transform(&mut self, transform: Affine) { - if self.scene.transform_stream.last() != Some(&transform) { - self.encode_transform(transform); - } - } - /// Pushes a new layer bound by the specifed shape and composed with /// previous layers using the specified blend mode. - pub fn push_layer<'s, E>(&mut self, blend: BlendMode, elements: E) + pub fn push_layer<'s, E>(&mut self, blend: BlendMode, transform: Affine, elements: E) where E: IntoIterator, E::IntoIter: Clone, E::Item: Borrow, { + self.maybe_encode_transform(transform); self.linewidth(-1.0); let elements = elements.into_iter(); self.encode_path(elements, true); @@ -82,6 +76,7 @@ impl<'a> SceneBuilder<'a> { pub fn fill<'s, E>( &mut self, _style: Fill, + transform: Affine, brush: &Brush, brush_transform: Option, elements: E, @@ -90,21 +85,14 @@ impl<'a> SceneBuilder<'a> { E::IntoIter: Clone, E::Item: Borrow, { + self.maybe_encode_transform(transform); self.linewidth(-1.0); let elements = elements.into_iter(); if self.encode_path(elements, true) { if let Some(brush_transform) = brush_transform { - if let Some(last_transform) = self.scene.transform_stream.last().copied() { - self.encode_transform(brush_transform * last_transform); - self.swap_last_tags(); - self.encode_brush(brush); - self.encode_transform(last_transform); - } else { - self.encode_transform(brush_transform); - self.swap_last_tags(); - self.encode_brush(brush); - self.encode_transform(Affine::IDENTITY); - } + self.encode_transform(transform * brush_transform); + self.swap_last_tags(); + self.encode_brush(brush); } else { self.encode_brush(brush); } @@ -115,6 +103,7 @@ impl<'a> SceneBuilder<'a> { pub fn stroke<'s, D, E>( &mut self, style: &Stroke, + transform: Affine, brush: &Brush, brush_transform: Option, elements: E, @@ -124,21 +113,14 @@ impl<'a> SceneBuilder<'a> { E::IntoIter: Clone, E::Item: Borrow, { + self.maybe_encode_transform(transform); self.linewidth(style.width); let elements = elements.into_iter(); if self.encode_path(elements, false) { if let Some(brush_transform) = brush_transform { - if let Some(last_transform) = self.scene.transform_stream.last().copied() { - self.encode_transform(brush_transform * last_transform); - self.swap_last_tags(); - self.encode_brush(brush); - self.encode_transform(last_transform); - } else { - self.encode_transform(brush_transform); - self.swap_last_tags(); - self.encode_brush(brush); - self.encode_transform(Affine::IDENTITY); - } + self.encode_transform(transform * brush_transform); + self.swap_last_tags(); + self.encode_brush(brush); } else { self.encode_brush(brush); } @@ -147,20 +129,7 @@ impl<'a> SceneBuilder<'a> { /// Appends a fragment to the scene. pub fn append(&mut self, fragment: &SceneFragment, transform: Option) { - let mut cur_transform = self.scene.transform_stream.last().copied(); - if let Some(transform) = transform { - if cur_transform.is_none() { - cur_transform = Some(Affine::IDENTITY); - } - self.transform(transform); - } else if cur_transform != Some(Affine::IDENTITY) { - self.encode_transform(Affine::IDENTITY); - } self.scene.append(&fragment.data, &transform); - // Prevent fragments from affecting transform state. Should we allow this? - if let Some(transform) = cur_transform { - self.transform(transform); - } } /// Completes construction and finalizes the underlying scene. @@ -218,6 +187,12 @@ impl<'a> SceneBuilder<'a> { has_els } + fn maybe_encode_transform(&mut self, transform: Affine) { + if self.scene.transform_stream.last() != Some(&transform) { + self.encode_transform(transform); + } + } + fn encode_transform(&mut self, transform: Affine) { self.scene.tag_stream.push(0x20); self.scene.transform_stream.push(transform);