Make transforms stateless

Removes the transform state mutator from SceneBuilder and adds transform parameters to push_layer, fill and stroke methods.
This commit is contained in:
Chad Brokaw 2022-08-16 14:52:04 -04:00
parent cd25528abd
commit 7fe022228a
6 changed files with 127 additions and 171 deletions

View file

@ -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

View file

@ -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();
}
}

View file

@ -122,7 +122,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,
@ -131,6 +131,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);

View file

@ -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<Item = PathElement> + 'a + Clone {
path.elements()
.iter()
@ -440,13 +403,13 @@ fn make_ellipse(cx: f32, cy: f32, rx: f32, ry: f32) -> impl Iterator<Item = Path
(0..elements.len()).map(move |i| elements[i])
}
fn make_diamond(origin: Point) -> impl Iterator<Item = PathElement> + Clone {
fn make_diamond(cx: f32, cy: f32) -> impl Iterator<Item = PathElement> + 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])

View file

@ -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()),

View file

@ -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<PathElement>,
{
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<Affine>,
elements: E,
@ -90,21 +85,14 @@ impl<'a> SceneBuilder<'a> {
E::IntoIter: Clone,
E::Item: Borrow<PathElement>,
{
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<D>,
transform: Affine,
brush: &Brush,
brush_transform: Option<Affine>,
elements: E,
@ -124,21 +113,14 @@ impl<'a> SceneBuilder<'a> {
E::IntoIter: Clone,
E::Item: Borrow<PathElement>,
{
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<Affine>) {
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);