Add transforms and state stack

Actually handle transforms in RenderCtx (was implemented in renderer but
not actually plumbed through). This also requires maintaining a state
stack, which will also be required for clipping.

This PR also starts work on encoding clipping, including tracking
bounding boxes.

WIP, none of this is tested yet.
This commit is contained in:
Raph Levien 2020-11-19 11:53:59 -08:00
parent 2fd6297d9d
commit f53d00e6bc
14 changed files with 376 additions and 28 deletions

View file

@ -18,12 +18,17 @@ piet_gpu! {
// That's expected to be uncommon, so we could special-case it.
linewidth: f32,
}
struct AnnoClip {
bbox: [f32; 4],
}
enum Annotated {
Nop,
Stroke(AnnoStroke),
Fill(AnnoFill),
FillMask(AnnoFillMask),
FillMaskInv(AnnoFillMask),
BeginClip(AnnoClip),
EndClip(AnnoClip),
}
}
}

View file

@ -30,6 +30,14 @@ piet_gpu! {
backdrop: i32,
mask: f32,
}
struct CmdBeginClip {
tile_ref: u32,
backdrop: i32,
}
struct CmdEndClip {
// This will be 1.0 for clips, but we can imagine blend groups.
alpha: f32,
}
struct CmdSolid {
rgba_color: u32,
}
@ -46,6 +54,8 @@ piet_gpu! {
Fill(CmdFill),
FillMask(CmdFillMask),
FillMaskInv(CmdFillMask),
BeginClip(CmdBeginClip),
EndClip(CmdEndClip),
Stroke(CmdStroke),
Solid(CmdSolid),
SolidMask(CmdSolidMask),

View file

@ -1,6 +1,8 @@
use piet_gpu_derive::piet_gpu;
pub use self::scene::{CubicSeg, Element, Fill, LineSeg, QuadSeg, SetLineWidth, Stroke, Transform};
pub use self::scene::{
BeginClip, CubicSeg, Element, EndClip, Fill, LineSeg, QuadSeg, SetLineWidth, Stroke, Transform,
};
piet_gpu! {
#[rust_encode]
@ -36,6 +38,15 @@ piet_gpu! {
mat: [f32; 4],
translate: [f32; 2],
}
struct BeginClip {
bbox: [f32; 4],
// TODO: add alpha?
}
struct EndClip {
// The delta between the BeginClip and EndClip element indices.
// It is stored as a delta to facilitate binary string concatenation.
delta: u32,
}
enum Element {
Nop,
// Another approach to encoding would be to use a single
@ -55,6 +66,8 @@ piet_gpu! {
Transform(Transform),
FillMask(FillMask),
FillMaskInv(FillMask),
BeginClip(BeginClip),
EndClip(EndClip),
}
}
}

View file

@ -12,6 +12,10 @@ struct AnnoStrokeRef {
uint offset;
};
struct AnnoClipRef {
uint offset;
};
struct AnnotatedRef {
uint offset;
};
@ -50,11 +54,23 @@ AnnoStrokeRef AnnoStroke_index(AnnoStrokeRef ref, uint index) {
return AnnoStrokeRef(ref.offset + index * AnnoStroke_size);
}
struct AnnoClip {
vec4 bbox;
};
#define AnnoClip_size 16
AnnoClipRef AnnoClip_index(AnnoClipRef ref, uint index) {
return AnnoClipRef(ref.offset + index * AnnoClip_size);
}
#define Annotated_Nop 0
#define Annotated_Stroke 1
#define Annotated_Fill 2
#define Annotated_FillMask 3
#define Annotated_FillMaskInv 4
#define Annotated_BeginClip 5
#define Annotated_EndClip 6
#define Annotated_size 28
AnnotatedRef Annotated_index(AnnotatedRef ref, uint index) {
@ -130,6 +146,25 @@ void AnnoStroke_write(AnnoStrokeRef ref, AnnoStroke s) {
annotated[ix + 5] = floatBitsToUint(s.linewidth);
}
AnnoClip AnnoClip_read(AnnoClipRef ref) {
uint ix = ref.offset >> 2;
uint raw0 = annotated[ix + 0];
uint raw1 = annotated[ix + 1];
uint raw2 = annotated[ix + 2];
uint raw3 = annotated[ix + 3];
AnnoClip s;
s.bbox = vec4(uintBitsToFloat(raw0), uintBitsToFloat(raw1), uintBitsToFloat(raw2), uintBitsToFloat(raw3));
return s;
}
void AnnoClip_write(AnnoClipRef ref, AnnoClip s) {
uint ix = ref.offset >> 2;
annotated[ix + 0] = floatBitsToUint(s.bbox.x);
annotated[ix + 1] = floatBitsToUint(s.bbox.y);
annotated[ix + 2] = floatBitsToUint(s.bbox.z);
annotated[ix + 3] = floatBitsToUint(s.bbox.w);
}
uint Annotated_tag(AnnotatedRef ref) {
return annotated[ref.offset >> 2];
}
@ -150,6 +185,14 @@ AnnoFillMask Annotated_FillMaskInv_read(AnnotatedRef ref) {
return AnnoFillMask_read(AnnoFillMaskRef(ref.offset + 4));
}
AnnoClip Annotated_BeginClip_read(AnnotatedRef ref) {
return AnnoClip_read(AnnoClipRef(ref.offset + 4));
}
AnnoClip Annotated_EndClip_read(AnnotatedRef ref) {
return AnnoClip_read(AnnoClipRef(ref.offset + 4));
}
void Annotated_Nop_write(AnnotatedRef ref) {
annotated[ref.offset >> 2] = Annotated_Nop;
}
@ -174,3 +217,13 @@ void Annotated_FillMaskInv_write(AnnotatedRef ref, AnnoFillMask s) {
AnnoFillMask_write(AnnoFillMaskRef(ref.offset + 4), s);
}
void Annotated_BeginClip_write(AnnotatedRef ref, AnnoClip s) {
annotated[ref.offset >> 2] = Annotated_BeginClip;
AnnoClip_write(AnnoClipRef(ref.offset + 4), s);
}
void Annotated_EndClip_write(AnnotatedRef ref, AnnoClip s) {
annotated[ref.offset >> 2] = Annotated_EndClip;
AnnoClip_write(AnnoClipRef(ref.offset + 4), s);
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -27,6 +27,8 @@ layout(rgba8, set = 0, binding = 2) uniform writeonly image2D image;
#include "ptcl.h"
#include "tile.h"
#define BLEND_STACK_SIZE 4
// Calculate coverage based on backdrop + coverage of each line segment
float[CHUNK] computeArea(vec2 xy, int backdrop, uint tile_ref) {
// Probably better to store as float, but conversion is no doubt cheap.
@ -69,6 +71,8 @@ void main() {
vec2 xy = vec2(xy_uint);
vec3 rgb[CHUNK];
float mask[CHUNK];
uint blend_stack[BLEND_STACK_SIZE][CHUNK];
uint blend_sp = 0;
for (uint i = 0; i < CHUNK; i++) {
rgb[i] = vec3(0.5);
mask[i] = 1.0;
@ -137,6 +141,22 @@ void main() {
mask[k] = mix(mask[k], fill_mask.mask, 1.0 - area[k]);
}
break;
case Cmd_BeginClip:
CmdBeginClip begin_clip = Cmd_BeginClip_read(cmd_ref);
area = computeArea(xy, begin_clip.backdrop, begin_clip.tile_ref);
for (uint k = 0; k < CHUNK; k++) {
blend_stack[blend_sp][k] = packUnorm4x8(vec4(rgb[k], clamp(abs(area[k]), 0.0, 1.0)));
}
blend_sp++;
break;
case Cmd_EndClip:
CmdEndClip end_clip = Cmd_EndClip_read(cmd_ref);
blend_sp--;
for (uint k = 0; k < CHUNK; k++) {
vec4 rgba = unpackUnorm4x8(blend_stack[blend_sp][k]);
rgb[k] = mix(rgb[k], rgba.rgb, end_clip.alpha * rgba.a);
}
break;
case Cmd_Solid:
CmdSolid solid = Cmd_Solid_read(cmd_ref);
fg_rgba = unpackUnorm4x8(solid.rgba_color).wzyx;

Binary file not shown.

Binary file not shown.

View file

@ -20,6 +20,14 @@ struct CmdFillMaskRef {
uint offset;
};
struct CmdBeginClipRef {
uint offset;
};
struct CmdEndClipRef {
uint offset;
};
struct CmdSolidRef {
uint offset;
};
@ -103,6 +111,27 @@ CmdFillMaskRef CmdFillMask_index(CmdFillMaskRef ref, uint index) {
return CmdFillMaskRef(ref.offset + index * CmdFillMask_size);
}
struct CmdBeginClip {
uint tile_ref;
int backdrop;
};
#define CmdBeginClip_size 8
CmdBeginClipRef CmdBeginClip_index(CmdBeginClipRef ref, uint index) {
return CmdBeginClipRef(ref.offset + index * CmdBeginClip_size);
}
struct CmdEndClip {
float alpha;
};
#define CmdEndClip_size 4
CmdEndClipRef CmdEndClip_index(CmdEndClipRef ref, uint index) {
return CmdEndClipRef(ref.offset + index * CmdEndClip_size);
}
struct CmdSolid {
uint rgba_color;
};
@ -139,10 +168,12 @@ CmdJumpRef CmdJump_index(CmdJumpRef ref, uint index) {
#define Cmd_Fill 3
#define Cmd_FillMask 4
#define Cmd_FillMaskInv 5
#define Cmd_Stroke 6
#define Cmd_Solid 7
#define Cmd_SolidMask 8
#define Cmd_Jump 9
#define Cmd_BeginClip 6
#define Cmd_EndClip 7
#define Cmd_Stroke 8
#define Cmd_Solid 9
#define Cmd_SolidMask 10
#define Cmd_Jump 11
#define Cmd_size 20
CmdRef Cmd_index(CmdRef ref, uint index) {
@ -271,6 +302,35 @@ void CmdFillMask_write(CmdFillMaskRef ref, CmdFillMask s) {
ptcl[ix + 2] = floatBitsToUint(s.mask);
}
CmdBeginClip CmdBeginClip_read(CmdBeginClipRef ref) {
uint ix = ref.offset >> 2;
uint raw0 = ptcl[ix + 0];
uint raw1 = ptcl[ix + 1];
CmdBeginClip s;
s.tile_ref = raw0;
s.backdrop = int(raw1);
return s;
}
void CmdBeginClip_write(CmdBeginClipRef ref, CmdBeginClip s) {
uint ix = ref.offset >> 2;
ptcl[ix + 0] = s.tile_ref;
ptcl[ix + 1] = uint(s.backdrop);
}
CmdEndClip CmdEndClip_read(CmdEndClipRef ref) {
uint ix = ref.offset >> 2;
uint raw0 = ptcl[ix + 0];
CmdEndClip s;
s.alpha = uintBitsToFloat(raw0);
return s;
}
void CmdEndClip_write(CmdEndClipRef ref, CmdEndClip s) {
uint ix = ref.offset >> 2;
ptcl[ix + 0] = floatBitsToUint(s.alpha);
}
CmdSolid CmdSolid_read(CmdSolidRef ref) {
uint ix = ref.offset >> 2;
uint raw0 = ptcl[ix + 0];
@ -334,6 +394,14 @@ CmdFillMask Cmd_FillMaskInv_read(CmdRef ref) {
return CmdFillMask_read(CmdFillMaskRef(ref.offset + 4));
}
CmdBeginClip Cmd_BeginClip_read(CmdRef ref) {
return CmdBeginClip_read(CmdBeginClipRef(ref.offset + 4));
}
CmdEndClip Cmd_EndClip_read(CmdRef ref) {
return CmdEndClip_read(CmdEndClipRef(ref.offset + 4));
}
CmdStroke Cmd_Stroke_read(CmdRef ref) {
return CmdStroke_read(CmdStrokeRef(ref.offset + 4));
}
@ -379,6 +447,16 @@ void Cmd_FillMaskInv_write(CmdRef ref, CmdFillMask s) {
CmdFillMask_write(CmdFillMaskRef(ref.offset + 4), s);
}
void Cmd_BeginClip_write(CmdRef ref, CmdBeginClip s) {
ptcl[ref.offset >> 2] = Cmd_BeginClip;
CmdBeginClip_write(CmdBeginClipRef(ref.offset + 4), s);
}
void Cmd_EndClip_write(CmdRef ref, CmdEndClip s) {
ptcl[ref.offset >> 2] = Cmd_EndClip;
CmdEndClip_write(CmdEndClipRef(ref.offset + 4), s);
}
void Cmd_Stroke_write(CmdRef ref, CmdStroke s) {
ptcl[ref.offset >> 2] = Cmd_Stroke;
CmdStroke_write(CmdStrokeRef(ref.offset + 4), s);

View file

@ -32,6 +32,14 @@ struct TransformRef {
uint offset;
};
struct BeginClipRef {
uint offset;
};
struct EndClipRef {
uint offset;
};
struct ElementRef {
uint offset;
};
@ -123,6 +131,26 @@ TransformRef Transform_index(TransformRef ref, uint index) {
return TransformRef(ref.offset + index * Transform_size);
}
struct BeginClip {
vec4 bbox;
};
#define BeginClip_size 16
BeginClipRef BeginClip_index(BeginClipRef ref, uint index) {
return BeginClipRef(ref.offset + index * BeginClip_size);
}
struct EndClip {
uint clip_size;
};
#define EndClip_size 4
EndClipRef EndClip_index(EndClipRef ref, uint index) {
return EndClipRef(ref.offset + index * EndClip_size);
}
#define Element_Nop 0
#define Element_StrokeLine 1
#define Element_FillLine 2
@ -136,6 +164,8 @@ TransformRef Transform_index(TransformRef ref, uint index) {
#define Element_Transform 10
#define Element_FillMask 11
#define Element_FillMaskInv 12
#define Element_BeginClip 13
#define Element_EndClip 14
#define Element_size 36
ElementRef Element_index(ElementRef ref, uint index) {
@ -233,6 +263,25 @@ Transform Transform_read(TransformRef ref) {
return s;
}
BeginClip BeginClip_read(BeginClipRef ref) {
uint ix = ref.offset >> 2;
uint raw0 = scene[ix + 0];
uint raw1 = scene[ix + 1];
uint raw2 = scene[ix + 2];
uint raw3 = scene[ix + 3];
BeginClip s;
s.bbox = vec4(uintBitsToFloat(raw0), uintBitsToFloat(raw1), uintBitsToFloat(raw2), uintBitsToFloat(raw3));
return s;
}
EndClip EndClip_read(EndClipRef ref) {
uint ix = ref.offset >> 2;
uint raw0 = scene[ix + 0];
EndClip s;
s.clip_size = raw0;
return s;
}
uint Element_tag(ElementRef ref) {
return scene[ref.offset >> 2];
}
@ -285,3 +334,11 @@ FillMask Element_FillMaskInv_read(ElementRef ref) {
return FillMask_read(FillMaskRef(ref.offset + 4));
}
BeginClip Element_BeginClip_read(ElementRef ref) {
return BeginClip_read(BeginClipRef(ref.offset + 4));
}
EndClip Element_EndClip_read(ElementRef ref) {
return EndClip_read(EndClipRef(ref.offset + 4));
}

Binary file not shown.

View file

@ -1,12 +1,13 @@
use std::{borrow::Cow, ops::RangeBounds};
use std::{borrow::Cow, convert::TryInto, ops::RangeBounds};
use piet_gpu_types::encoder::{Encode, Encoder};
use piet_gpu_types::scene::{CubicSeg, Element, Fill, LineSeg, QuadSeg, SetLineWidth, Stroke};
use piet_gpu_types::scene::{
BeginClip, CubicSeg, Element, EndClip, Fill, LineSeg, QuadSeg, SetLineWidth, Stroke, Transform,
};
use piet::{
kurbo::Size,
kurbo::{Affine, PathEl, Point, Rect, Shape},
kurbo::{Affine, Insets, PathEl, Point, Rect, Shape, Size},
HitTestPosition, TextAttribute, TextStorage,
};
@ -33,8 +34,14 @@ pub struct PietGpuRenderContext {
stroke_width: f32,
// 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,
cur_transform: Affine,
state_stack: Vec<State>,
clip_stack: Vec<ClipElement>,
}
#[derive(Clone)]
@ -43,6 +50,21 @@ pub enum PietGpuBrush {
Gradient,
}
#[derive(Default)]
struct State {
/// The transform relative to the parent state.
transform: Affine,
n_clip: usize,
}
struct ClipElement {
/// Index of BeginClip element in element vec, for bbox fixup.
begin_ix: usize,
bbox: Option<Rect>,
/// The transform relative to the next clip element on the stack.
transform: Affine,
}
const TOLERANCE: f64 = 0.25;
impl PietGpuRenderContext {
@ -58,6 +80,9 @@ impl PietGpuRenderContext {
stroke_width,
path_count: 0,
pathseg_count: 0,
cur_transform: Affine::default(),
state_stack: Vec::new(),
clip_stack: Vec::new(),
}
}
@ -96,17 +121,19 @@ impl RenderContext for PietGpuRenderContext {
fn clear(&mut self, _color: Color) {}
fn stroke(&mut self, shape: impl Shape, brush: &impl IntoBrush<Self>, width: f64) {
let width = width as f32;
if self.stroke_width != width {
let width_f32 = width as f32;
if self.stroke_width != width_f32 {
self.elements
.push(Element::SetLineWidth(SetLineWidth { width }));
self.stroke_width = width;
.push(Element::SetLineWidth(SetLineWidth { width: width_f32 }));
self.stroke_width = width_f32;
}
let brush = brush.make_brush(self, || shape.bounding_box()).into_owned();
let path = shape.path_elements(TOLERANCE);
self.encode_path(path, false);
match brush {
PietGpuBrush::Solid(rgba_color) => {
// 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);
let stroke = Stroke { rgba_color };
self.elements.push(Element::Stroke(stroke));
self.path_count += 1;
@ -126,21 +153,34 @@ impl RenderContext for PietGpuRenderContext {
fn fill(&mut self, shape: impl Shape, brush: &impl IntoBrush<Self>) {
let brush = brush.make_brush(self, || shape.bounding_box()).into_owned();
if let PietGpuBrush::Solid(rgba_color) = brush {
// 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.encode_path(path, true);
match brush {
PietGpuBrush::Solid(rgba_color) => {
let fill = Fill { rgba_color };
self.elements.push(Element::Fill(fill));
self.path_count += 1;
}
_ => (),
}
}
fn fill_even_odd(&mut self, _shape: impl Shape, _brush: &impl IntoBrush<Self>) {}
fn clip(&mut self, _shape: impl Shape) {}
fn clip(&mut self, shape: impl Shape) {
let begin_ix = self.elements.len();
let path = shape.path_elements(TOLERANCE);
self.encode_path(path, true);
self.elements.push(Element::BeginClip(BeginClip {
bbox: Default::default(),
}));
self.clip_stack.push(ClipElement {
bbox: None,
begin_ix,
transform: Affine::default(),
});
self.path_count += 1;
}
fn text(&mut self) -> &mut Self::Text {
&mut self.inner_text
@ -149,15 +189,42 @@ impl RenderContext for PietGpuRenderContext {
fn draw_text(&mut self, _layout: &Self::TextLayout, _pos: impl Into<Point>) {}
fn save(&mut self) -> Result<(), Error> {
self.state_stack.push(Default::default());
Ok(())
}
fn restore(&mut self) -> Result<(), Error> {
Ok(())
if let Some(state) = self.state_stack.pop() {
if state.transform != Affine::default() {
let a_inv = state.transform.inverse();
self.elements
.push(Element::Transform(to_scene_transform(a_inv)));
self.cur_transform *= a_inv;
}
for _ in 0..state.n_clip {
self.pop_clip();
}
Ok(())
} else {
Err(Error::StackUnbalance)
}
}
fn finish(&mut self) -> Result<(), Error> {
Ok(())
}
fn transform(&mut self, _transform: Affine) {}
fn transform(&mut self, transform: Affine) {
self.elements
.push(Element::Transform(to_scene_transform(transform)));
if let Some(tos) = self.state_stack.last_mut() {
tos.transform *= transform;
}
if let Some(tos) = self.clip_stack.last_mut() {
tos.transform *= transform;
}
self.cur_transform *= transform;
}
fn make_image(
&mut self,
@ -189,7 +256,13 @@ impl RenderContext for PietGpuRenderContext {
fn blurred_rect(&mut self, _rect: Rect, _blur_radius: f64, _brush: &impl IntoBrush<Self>) {}
fn current_transform(&self) -> Affine {
Default::default()
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())
}
}
@ -316,6 +389,33 @@ impl PietGpuRenderContext {
}
}
}
fn pop_clip(&mut self) {
let tos = self.clip_stack.pop().unwrap();
let delta = (self.elements.len() - tos.begin_ix).try_into().unwrap();
self.elements.push(Element::EndClip(EndClip { delta }));
self.path_count += 1;
if let Some(bbox) = tos.bbox {
if let Element::BeginClip(begin_clip) = &mut self.elements[tos.begin_ix] {
begin_clip.bbox = rect_to_f32_4(bbox);
} else {
unreachable!("expected BeginClip, not found");
}
self.accumulate_bbox(|| bbox);
}
}
fn accumulate_bbox(&mut self, f: impl FnOnce() -> Rect) {
if let Some(tos) = self.clip_stack.last_mut() {
let bbox = f();
let bbox = tos.transform.transform_rect_bbox(bbox);
tos.bbox = if let Some(old_bbox) = tos.bbox {
Some(old_bbox.union(bbox))
} else {
Some(bbox)
};
}
}
}
impl Text for PietGpuText {
@ -410,3 +510,15 @@ impl IntoBrush<PietGpuRenderContext> for PietGpuBrush {
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],
}
}