Merge pull request #188 from dfrg/cpath

Expose path rendering in C API
diff --git a/pgpu-render/pgpu.h b/pgpu-render/pgpu.h
index 0f04499..7d0976e 100644
--- a/pgpu-render/pgpu.h
+++ b/pgpu-render/pgpu.h
@@ -6,6 +6,23 @@
 #include <ostream>
 #include <new>
 
+enum class PgpuBrushKind {
+  Solid = 0,
+};
+
+enum class PgpuFill {
+  NonZero = 0,
+  EvenOdd = 1,
+};
+
+enum class PgpuPathVerb {
+  MoveTo = 0,
+  LineTo = 1,
+  QuadTo = 2,
+  CurveTo = 3,
+  Close = 4,
+};
+
 /// Encoded (possibly color) outline for a glyph.
 struct PgpuGlyph;
 
@@ -24,6 +41,50 @@
 /// Builder for constructing an encoded scene.
 struct PgpuSceneBuilder;
 
+/// Encoded streams and resources describing a vector graphics scene fragment.
+struct PgpuSceneFragment;
+
+/// Affine transformation matrix.
+struct PgpuTransform {
+  float xx;
+  float yx;
+  float xy;
+  float yy;
+  float dx;
+  float dy;
+};
+
+struct PgpuColor {
+  uint8_t r;
+  uint8_t g;
+  uint8_t b;
+  uint8_t a;
+};
+
+union PgpuBrushData {
+  PgpuColor solid;
+};
+
+struct PgpuBrush {
+  PgpuBrushKind kind;
+  PgpuBrushData data;
+};
+
+struct PgpuPoint {
+  float x;
+  float y;
+};
+
+struct PgpuPathElement {
+  PgpuPathVerb verb;
+  PgpuPoint points[3];
+};
+
+struct PgpuPathIter {
+  void *context;
+  bool (*next_element)(void*, PgpuPathElement*);
+};
+
 /// Tag and value for a font variation axis.
 struct PgpuFontVariation {
   /// Tag that specifies the axis.
@@ -98,14 +159,45 @@
 /// Destroys the piet-gpu scene.
 void pgpu_scene_destroy(PgpuScene *scene);
 
+/// Creates a new, empty piet-gpu scene fragment.
+PgpuSceneFragment *pgpu_scene_fragment_new();
+
+/// Destroys the piet-gpu scene fragment.
+void pgpu_scene_fragment_destroy(PgpuSceneFragment *fragment);
+
 /// Creates a new builder for filling a piet-gpu scene. The specified scene
 /// should not be accessed while the builder is live.
-PgpuSceneBuilder *pgpu_scene_builder_new(PgpuScene *scene);
+PgpuSceneBuilder *pgpu_scene_builder_for_scene(PgpuScene *scene);
+
+/// Creates a new builder for filling a piet-gpu scene fragment. The specified
+/// scene fragment should not be accessed while the builder is live.
+PgpuSceneBuilder *pgpu_scene_builder_for_fragment(PgpuSceneFragment *fragment);
 
 /// Adds a glyph with the specified transform to the underlying scene.
 void pgpu_scene_builder_add_glyph(PgpuSceneBuilder *builder,
                                   const PgpuGlyph *glyph,
-                                  const float (*transform)[6]);
+                                  const PgpuTransform *transform);
+
+/// Sets the current absolute transform for the scene builder.
+void pgpu_scene_builder_transform(PgpuSceneBuilder *builder, const PgpuTransform *transform);
+
+/// Fills a path using the specified fill style and brush. If the brush
+/// parameter is nullptr, a solid color white brush will be used. The
+/// brush_transform may be nullptr.
+void pgpu_scene_builder_fill_path(PgpuSceneBuilder *builder,
+                                  PgpuFill fill,
+                                  const PgpuBrush *brush,
+                                  const PgpuTransform *brush_transform,
+                                  PgpuPathIter *path);
+
+/// Appends a scene fragment to the underlying scene or fragment. The
+/// transform parameter represents an absolute transform to apply to
+/// the fragment. If it is nullptr, the fragment will be appended to
+/// the scene with an assumed identity transform regardless of the
+/// current transform state.
+void pgpu_scene_builder_append_fragment(PgpuSceneBuilder *builder,
+                                        const PgpuSceneFragment *fragment,
+                                        const PgpuTransform *transform);
 
 /// Finalizes the scene builder, making the underlying scene ready for
 /// rendering. This takes ownership and consumes the builder.
diff --git a/pgpu-render/src/lib.rs b/pgpu-render/src/lib.rs
index c96b03f..7763e58 100644
--- a/pgpu-render/src/lib.rs
+++ b/pgpu-render/src/lib.rs
@@ -26,6 +26,9 @@
 
 mod render;
 
+use piet_scene::brush::{Brush, Color};
+use piet_scene::path::Element;
+use piet_scene::scene::Fill;
 use render::*;
 use std::ffi::c_void;
 use std::mem::transmute;
@@ -98,13 +101,135 @@
     Box::from_raw(scene);
 }
 
+/// Creates a new, empty piet-gpu scene fragment.
+#[no_mangle]
+pub unsafe extern "C" fn pgpu_scene_fragment_new() -> *mut PgpuSceneFragment {
+    Box::into_raw(Box::new(PgpuSceneFragment::new()))
+}
+
+/// Destroys the piet-gpu scene fragment.
+#[no_mangle]
+pub unsafe extern "C" fn pgpu_scene_fragment_destroy(fragment: *mut PgpuSceneFragment) {
+    Box::from_raw(fragment);
+}
+
+#[derive(Copy, Clone, PartialEq, Debug)]
+#[repr(C)]
+pub enum PgpuPathVerb {
+    MoveTo = 0,
+    LineTo = 1,
+    QuadTo = 2,
+    CurveTo = 3,
+    Close = 4,
+}
+
+#[derive(Copy, Clone, Default, Debug)]
+#[repr(C)]
+pub struct PgpuPoint {
+    pub x: f32,
+    pub y: f32,
+}
+
+/// Rectangle defined by minimum and maximum points.
+#[derive(Copy, Clone, Default)]
+#[repr(C)]
+pub struct PgpuRect {
+    pub x0: f32,
+    pub y0: f32,
+    pub x1: f32,
+    pub y1: f32,
+}
+
+#[derive(Copy, Clone, Debug)]
+#[repr(C)]
+pub struct PgpuPathElement {
+    pub verb: PgpuPathVerb,
+    pub points: [PgpuPoint; 3],
+}
+
+#[derive(Clone)]
+#[repr(C)]
+pub struct PgpuPathIter {
+    pub context: *mut c_void,
+    pub next_element: extern "C" fn(*mut c_void, *mut PgpuPathElement) -> bool,
+}
+
+#[derive(Copy, Clone, PartialEq, Debug)]
+#[repr(C)]
+pub enum PgpuFill {
+    NonZero = 0,
+    EvenOdd = 1,
+}
+
+#[derive(Copy, Clone, PartialEq, Debug)]
+#[repr(C)]
+pub enum PgpuBrushKind {
+    Solid = 0,
+}
+
+#[derive(Copy, Clone, Debug)]
+#[repr(C)]
+pub struct PgpuColor {
+    pub r: u8,
+    pub g: u8,
+    pub b: u8,
+    pub a: u8,
+}
+
+#[repr(C)]
+pub union PgpuBrushData {
+    pub solid: PgpuColor,
+}
+
+#[repr(C)]
+pub struct PgpuBrush {
+    pub kind: PgpuBrushKind,
+    pub data: PgpuBrushData,
+}
+
+/// Affine transformation matrix.
+#[derive(Copy, Clone, Debug)]
+#[repr(C)]
+pub struct PgpuTransform {
+    pub xx: f32,
+    pub yx: f32,
+    pub xy: f32,
+    pub yy: f32,
+    pub dx: f32,
+    pub dy: f32,
+}
+
+impl From<PgpuTransform> for PgpuAffine {
+    fn from(xform: PgpuTransform) -> Self {
+        Self {
+            xx: xform.xx,
+            yx: xform.yx,
+            xy: xform.xy,
+            yy: xform.yy,
+            dx: xform.dx,
+            dy: xform.dy,
+        }
+    }
+}
+
+pub type PgpuAffine = piet_scene::geometry::Affine;
+
 /// Creates a new builder for filling a piet-gpu scene. The specified scene
 /// should not be accessed while the builder is live.
 #[no_mangle]
-pub unsafe extern "C" fn pgpu_scene_builder_new(
+pub unsafe extern "C" fn pgpu_scene_builder_for_scene(
     scene: *mut PgpuScene,
 ) -> *mut PgpuSceneBuilder<'static> {
-    Box::into_raw(Box::new((*scene).build()))
+    Box::into_raw(Box::new((*scene).builder()))
+}
+
+/// Creates a new builder for filling a piet-gpu scene fragment. The specified
+/// scene fragment should not be accessed while the builder is live.
+#[no_mangle]
+pub unsafe extern "C" fn pgpu_scene_builder_for_fragment(
+    fragment: *mut PgpuSceneFragment,
+) -> *mut PgpuSceneBuilder<'static> {
+    Box::into_raw(Box::new((*fragment).builder()))
 }
 
 /// Adds a glyph with the specified transform to the underlying scene.
@@ -112,10 +237,103 @@
 pub unsafe extern "C" fn pgpu_scene_builder_add_glyph(
     builder: *mut PgpuSceneBuilder<'static>,
     glyph: *const PgpuGlyph,
-    transform: &[f32; 6],
+    transform: *const PgpuTransform,
 ) {
-    let transform = piet_scene::geometry::Affine::new(transform);
-    (*builder).add_glyph(&*glyph, &transform);
+    (*builder).add_glyph(&*glyph, &(*transform).into());
+}
+
+impl Iterator for PgpuPathIter {
+    type Item = piet_scene::path::Element;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let mut el = PgpuPathElement {
+            verb: PgpuPathVerb::MoveTo,
+            points: [PgpuPoint::default(); 3],
+        };
+        if (self.next_element)(self.context, &mut el as _) {
+            let p = &el.points;
+            Some(match el.verb {
+                PgpuPathVerb::MoveTo => Element::MoveTo((p[0].x, p[0].y).into()),
+                PgpuPathVerb::LineTo => Element::LineTo((p[0].x, p[0].y).into()),
+                PgpuPathVerb::QuadTo => {
+                    Element::QuadTo((p[0].x, p[0].y).into(), (p[1].x, p[1].y).into())
+                }
+                PgpuPathVerb::CurveTo => Element::CurveTo(
+                    (p[0].x, p[0].y).into(),
+                    (p[1].x, p[1].y).into(),
+                    (p[2].x, p[2].y).into(),
+                ),
+                PgpuPathVerb::Close => Element::Close,
+            })
+        } else {
+            None
+        }
+    }
+}
+
+/// Sets the current absolute transform for the scene builder.
+#[no_mangle]
+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())
+    }
+}
+
+/// Fills a path using the specified fill style and brush. If the brush
+/// parameter is nullptr, a solid color white brush will be used. The
+/// brush_transform may be nullptr.
+#[no_mangle]
+pub unsafe extern "C" fn pgpu_scene_builder_fill_path(
+    builder: *mut PgpuSceneBuilder<'static>,
+    fill: PgpuFill,
+    brush: *const PgpuBrush,
+    brush_transform: *const PgpuTransform,
+    path: *mut PgpuPathIter,
+) {
+    let fill = match fill {
+        PgpuFill::NonZero => Fill::NonZero,
+        PgpuFill::EvenOdd => Fill::EvenOdd,
+    };
+    let brush = if brush.is_null() {
+        Brush::Solid(Color::rgb8(255, 255, 255))
+    } else {
+        match (*brush).kind {
+            PgpuBrushKind::Solid => {
+                let color = &(*brush).data.solid;
+                Brush::Solid(Color::rgba8(color.r, color.g, color.b, color.a))
+            }
+        }
+    };
+    let brush_transform = if brush_transform.is_null() {
+        None
+    } else {
+        Some((*brush_transform).into())
+    };
+    (*builder)
+        .0
+        .fill(fill, &brush, brush_transform, (*path).clone());
+}
+
+/// Appends a scene fragment to the underlying scene or fragment. The
+/// transform parameter represents an absolute transform to apply to
+/// the fragment. If it is nullptr, the fragment will be appended to
+/// the scene with an assumed identity transform regardless of the
+/// current transform state.
+#[no_mangle]
+pub unsafe extern "C" fn pgpu_scene_builder_append_fragment(
+    builder: *mut PgpuSceneBuilder<'static>,
+    fragment: *const PgpuSceneFragment,
+    transform: *const PgpuTransform,
+) {
+    let transform = if transform.is_null() {
+        None
+    } else {
+        Some((*transform).into())
+    };
+    (*builder).0.append(&(*fragment).0, transform);
 }
 
 /// Finalizes the scene builder, making the underlying scene ready for
@@ -220,16 +438,6 @@
     Box::from_raw(provider);
 }
 
-/// Rectangle defined by minimum and maximum points.
-#[derive(Copy, Clone, Default)]
-#[repr(C)]
-pub struct PgpuRect {
-    pub x0: f32,
-    pub y0: f32,
-    pub x1: f32,
-    pub y1: f32,
-}
-
 /// Computes the bounding box for the glyph after applying the specified
 /// transform.
 #[no_mangle]
diff --git a/pgpu-render/src/render.rs b/pgpu-render/src/render.rs
index 1227f19..16b112a 100644
--- a/pgpu-render/src/render.rs
+++ b/pgpu-render/src/render.rs
@@ -61,7 +61,7 @@
         cmdbuf: &metal::CommandBufferRef,
         target: &metal::TextureRef,
     ) -> u32 {
-        let is_color = target.pixel_format() != metal::MTLPixelFormat::A8Unorm;
+        let is_color = target.pixel_format() != metal::MTLPixelFormat::R8Unorm;
         let width = target.width() as u32;
         let height = target.height() as u32;
         if self.pgpu_renderer.is_none()
@@ -93,6 +93,7 @@
                 renderer.record(&mut cmd_buf, &self.query_pool, 0);
                 // TODO later: we can bind the destination image and avoid the copy.
                 cmd_buf.blit_image(&renderer.image_dev, &dst_image);
+                cmd_buf.flush();
             }
         }
         0
@@ -117,7 +118,7 @@
         }
     }
 
-    pub fn build(&mut self) -> PgpuSceneBuilder {
+    pub fn builder(&mut self) -> PgpuSceneBuilder {
         self.rcx.advance();
         PgpuSceneBuilder(piet_scene::scene::build_scene(
             &mut self.scene,
@@ -142,8 +143,21 @@
     }
 }
 
+/// Encoded streams and resources describing a vector graphics scene fragment.
+pub struct PgpuSceneFragment(pub Fragment);
+
+impl PgpuSceneFragment {
+    pub fn new() -> Self {
+        Self(Fragment::default())
+    }
+
+    pub fn builder(&mut self) -> PgpuSceneBuilder {
+        PgpuSceneBuilder(piet_scene::scene::build_fragment(&mut self.0))
+    }
+}
+
 /// Builder for constructing an encoded scene.
-pub struct PgpuSceneBuilder<'a>(piet_scene::scene::Builder<'a>);
+pub struct PgpuSceneBuilder<'a>(pub piet_scene::scene::Builder<'a>);
 
 impl<'a> PgpuSceneBuilder<'a> {
     pub fn add_glyph(&mut self, glyph: &PgpuGlyph, transform: &piet_scene::geometry::Affine) {
diff --git a/piet-gpu-hal/src/backend.rs b/piet-gpu-hal/src/backend.rs
index d3a1f09..b948ebc 100644
--- a/piet-gpu-hal/src/backend.rs
+++ b/piet-gpu-hal/src/backend.rs
@@ -205,6 +205,9 @@
     /// State: ready -> finished
     unsafe fn finish(&mut self);
 
+    /// Commits any open command encoder.
+    unsafe fn flush(&mut self);
+
     /// Return true if the command buffer is suitable for reuse.
     unsafe fn reset(&mut self) -> bool;
 
diff --git a/piet-gpu-hal/src/dx12.rs b/piet-gpu-hal/src/dx12.rs
index 01afcfd..8d6820b 100644
--- a/piet-gpu-hal/src/dx12.rs
+++ b/piet-gpu-hal/src/dx12.rs
@@ -631,6 +631,8 @@
         self.needs_reset = true;
     }
 
+    unsafe fn flush(&mut self) {}
+
     unsafe fn reset(&mut self) -> bool {
         self.allocator.reset().is_ok() && self.c.reset(&self.allocator, None).is_ok()
     }
diff --git a/piet-gpu-hal/src/hub.rs b/piet-gpu-hal/src/hub.rs
index bbb52c1..1d51459 100644
--- a/piet-gpu-hal/src/hub.rs
+++ b/piet-gpu-hal/src/hub.rs
@@ -509,6 +509,11 @@
         self.cmd_buf().finish();
     }
 
+    /// Commits any open command encoder.
+    pub unsafe fn flush(&mut self) {
+        self.cmd_buf().flush();
+    }
+
     /// Begin a compute pass.
     pub unsafe fn begin_compute_pass(&mut self, desc: &ComputePassDescriptor) -> ComputePass {
         self.cmd_buf().begin_compute_pass(desc);
diff --git a/piet-gpu-hal/src/metal.rs b/piet-gpu-hal/src/metal.rs
index 891a9be..7471d19 100644
--- a/piet-gpu-hal/src/metal.rs
+++ b/piet-gpu-hal/src/metal.rs
@@ -590,6 +590,10 @@
         self.flush_encoder();
     }
 
+    unsafe fn flush(&mut self) {
+        self.flush_encoder();
+    }
+
     unsafe fn reset(&mut self) -> bool {
         false
     }
diff --git a/piet-gpu-hal/src/mux.rs b/piet-gpu-hal/src/mux.rs
index cab97e6..c4149d1 100644
--- a/piet-gpu-hal/src/mux.rs
+++ b/piet-gpu-hal/src/mux.rs
@@ -675,6 +675,14 @@
         }
     }
 
+    pub unsafe fn flush(&mut self) {
+        mux_match! { self;
+            CmdBuf::Vk(c) => c.flush(),
+            CmdBuf::Dx12(c) => c.flush(),
+            CmdBuf::Mtl(c) => c.flush(),
+        }
+    }
+
     pub unsafe fn finish(&mut self) {
         mux_match! { self;
             CmdBuf::Vk(c) => c.finish(),
diff --git a/piet-gpu-hal/src/vulkan.rs b/piet-gpu-hal/src/vulkan.rs
index a2e2371..6a790ef 100644
--- a/piet-gpu-hal/src/vulkan.rs
+++ b/piet-gpu-hal/src/vulkan.rs
@@ -967,6 +967,8 @@
         self.device.device.end_command_buffer(self.cmd_buf).unwrap();
     }
 
+    unsafe fn flush(&mut self) {}
+
     unsafe fn reset(&mut self) -> bool {
         true
     }
diff --git a/piet-gpu/src/lib.rs b/piet-gpu/src/lib.rs
index e0415d4..71ce7d4 100644
--- a/piet-gpu/src/lib.rs
+++ b/piet-gpu/src/lib.rs
@@ -243,7 +243,10 @@
                     .unwrap()
             })
             .collect();
-        let memory_buf_dev = session.create_buffer(16 * 1024 * 1024, usage_mem_dev)?;
+        let target_dependent_size =
+            (width / TILE_W) as u64 * (height / TILE_H) as u64 * PTCL_INITIAL_ALLOC as u64;
+        let memory_buf_dev =
+            session.create_buffer(target_dependent_size + 8 * 1024 * 1024, usage_mem_dev)?;
         let memory_buf_readback =
             session.create_buffer(std::mem::size_of::<MemoryHeader>() as u64, usage_readback)?;
         let blend_buf = session.create_buffer(16 * 1024 * 1024, usage_blend)?;