From 5a127e09a5ddc5a5175d1c453263aa6d2a6773d9 Mon Sep 17 00:00:00 2001 From: Chad Brokaw Date: Mon, 8 Aug 2022 01:03:27 -0400 Subject: [PATCH] Expose path rendering in C API * capi: Add PathIter type and support for encoding fills * capi: Minimal support for brushes (solid color only) * Add flush method to command buffers * Better initial heuristic for memory buffer size based on target dimensions --- pgpu-render/pgpu.h | 96 ++++++++++++++- pgpu-render/src/lib.rs | 238 +++++++++++++++++++++++++++++++++--- pgpu-render/src/render.rs | 20 ++- piet-gpu-hal/src/backend.rs | 3 + piet-gpu-hal/src/dx12.rs | 2 + piet-gpu-hal/src/hub.rs | 5 + piet-gpu-hal/src/metal.rs | 4 + piet-gpu-hal/src/mux.rs | 8 ++ piet-gpu-hal/src/vulkan.rs | 2 + piet-gpu/src/lib.rs | 5 +- 10 files changed, 362 insertions(+), 21 deletions(-) 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 #include +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 @@ struct PgpuScene; /// 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 @@ PgpuScene *pgpu_scene_new(); /// 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 @@ pub unsafe extern "C" fn pgpu_scene_destroy(scene: *mut PgpuScene) { 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 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_new( 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 { + 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 @@ pub unsafe extern "C" fn pgpu_glyph_provider_destroy(provider: *mut PgpuGlyphPro 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 @@ impl PgpuRenderer { 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 @@ impl PgpuRenderer { 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 @@ impl PgpuScene { } } - 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 @@ impl PgpuScene { } } +/// 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 @@ pub trait CmdBuf { /// 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 @@ impl crate::backend::CmdBuf for CmdBuf { 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 @@ impl CmdBuf { 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 @@ impl crate::backend::CmdBuf for CmdBuf { 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 @@ impl CmdBuf { } } + 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 @@ impl crate::backend::CmdBuf for CmdBuf { 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 @@ impl Renderer { .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::() as u64, usage_readback)?; let blend_buf = session.create_buffer(16 * 1024 * 1024, usage_blend)?;