From f53d00e6bcbeb08efa2f1c58074a1d782cd6ce67 Mon Sep 17 00:00:00 2001 From: Raph Levien Date: Thu, 19 Nov 2020 11:53:59 -0800 Subject: [PATCH] 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. --- piet-gpu-types/src/annotated.rs | 5 + piet-gpu-types/src/ptcl.rs | 10 ++ piet-gpu-types/src/scene.rs | 15 ++- piet-gpu/shader/annotated.h | 53 +++++++++++ piet-gpu/shader/backdrop.spv | Bin 7256 -> 7256 bytes piet-gpu/shader/binning.spv | Bin 10152 -> 10152 bytes piet-gpu/shader/coarse.spv | Bin 37332 -> 37332 bytes piet-gpu/shader/kernel4.comp | 20 ++++ piet-gpu/shader/kernel4.spv | Bin 26192 -> 30140 bytes piet-gpu/shader/path_coarse.spv | Bin 28876 -> 28876 bytes piet-gpu/shader/ptcl.h | 86 ++++++++++++++++- piet-gpu/shader/scene.h | 57 ++++++++++++ piet-gpu/shader/tile_alloc.spv | Bin 8816 -> 8816 bytes piet-gpu/src/render_ctx.rs | 158 +++++++++++++++++++++++++++----- 14 files changed, 376 insertions(+), 28 deletions(-) diff --git a/piet-gpu-types/src/annotated.rs b/piet-gpu-types/src/annotated.rs index 681a7ec..04f2111 100644 --- a/piet-gpu-types/src/annotated.rs +++ b/piet-gpu-types/src/annotated.rs @@ -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), } } } diff --git a/piet-gpu-types/src/ptcl.rs b/piet-gpu-types/src/ptcl.rs index 95dcdc6..d05218b 100644 --- a/piet-gpu-types/src/ptcl.rs +++ b/piet-gpu-types/src/ptcl.rs @@ -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), diff --git a/piet-gpu-types/src/scene.rs b/piet-gpu-types/src/scene.rs index 1359c1b..7e2fb43 100644 --- a/piet-gpu-types/src/scene.rs +++ b/piet-gpu-types/src/scene.rs @@ -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), } } } diff --git a/piet-gpu/shader/annotated.h b/piet-gpu/shader/annotated.h index 847ca06..986eff2 100644 --- a/piet-gpu/shader/annotated.h +++ b/piet-gpu/shader/annotated.h @@ -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); +} + diff --git a/piet-gpu/shader/backdrop.spv b/piet-gpu/shader/backdrop.spv index e2093d9a02e92895c45174a57d2701d2e434dd20..7f0852dadb86d116244ff9b7433f8e417daef0f5 100644 GIT binary patch delta 18 Zcmca%al?X>nMs+Qfq{{MYa^$>3;-$^1Bn0t delta 18 Zcmca%al?X>nMs+Qfq{{MV55yj8J(_!DuSqDU4#L zZx_!^5lcjtWkRNaJ?Q*DokGuyyv5Io9fBfC*70f?7%3y!E*`@DHbjPSD&Sj^6M7AJ zzD&vr{Hn4OHwfTXae8oh)e$(oFYppFcY!r%`b^<{)SIZ4P&)>H03c3+8S#;SFFl?cjpvtjPo&`>kORUD>{7SzXLCz^_Hx2273H_ zI<3yMpaG|jqqn>Sr>D0Jkah)4pDCP?np@UTy9R#-vc`F-^_Dl_F>35-{cX9C?ip?M in7Xqacx@cLMHjrI-qQIwGN#=E`Fr)eckCNJWVv6JXHXyj delta 554 zcmX|8IZne+5FOiW?yxNiM1dlRDCiIkViBnj4T>c=K$?I_FbRuU3`szuGDbqbgMuO_ zAQ1%>5*MJcI0C1j;JwJ7;`=T0=Xu7?x@OijZOfb)6)|M&)t+(KO>!uL_HqE<%j5c~o&V{m$J=qMro19%Fpw`83&(BtRQ zX%W+cGdK~B-trurp58J*+66Ryrf^1TZdpU^3j77g8t0|fTV8`VP-9O#*m5P^Hd^X2 i^(NZzS~z-(4tQI=r&E(;OuGR__)~h`TlV!HGV2es&rm!7 diff --git a/piet-gpu/shader/kernel4.comp b/piet-gpu/shader/kernel4.comp index d21aa87..919d120 100644 --- a/piet-gpu/shader/kernel4.comp +++ b/piet-gpu/shader/kernel4.comp @@ -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; diff --git a/piet-gpu/shader/kernel4.spv b/piet-gpu/shader/kernel4.spv index d159fe54ca5caf49dd92dbdf5f5f6c3317aed27e..0d3858159e8435dce93ded58eaa62665d5a970c1 100644 GIT binary patch literal 30140 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 diff --git a/piet-gpu/shader/path_coarse.spv b/piet-gpu/shader/path_coarse.spv index db5bc57af80015b065c9a69d0e04de0844737f37..f82a031295caaf30b5e4075547981235bfe9d90c 100644 GIT binary patch delta 20 bcmX@}knzkzMowlXWp)MzMh32poQDelL> 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); diff --git a/piet-gpu/shader/scene.h b/piet-gpu/shader/scene.h index 6823fe6..0a4a2ce 100644 --- a/piet-gpu/shader/scene.h +++ b/piet-gpu/shader/scene.h @@ -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)); +} + diff --git a/piet-gpu/shader/tile_alloc.spv b/piet-gpu/shader/tile_alloc.spv index af52665184d64b41d3f1e8b3f9174a3ea38b1c49..449f4d6874ff4de6782271dbd4f09d1d23ddadf4 100644 GIT binary patch delta 18 Zcmez1^1+3ZnMs+Qfq{{MYa?g65&$m#1MdI; delta 18 Zcmez1^1+3ZnMs+Qfq{{MV, + clip_stack: Vec, } #[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, + /// 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, 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) { let brush = brush.make_brush(self, || shape.bounding_box()).into_owned(); - 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; - } - _ => (), + 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); + 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) {} - 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) {} 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) {} 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 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], + } +}