diff --git a/Cargo.lock b/Cargo.lock index c0c4033..e07e12e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -697,12 +697,6 @@ dependencies = [ "libc", ] -[[package]] -name = "matches" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" - [[package]] name = "memchr" version = "2.5.0" @@ -1009,34 +1003,32 @@ dependencies = [ "piet-scene", ] -[[package]] -name = "piet" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f00543608fb5ee6063f5ff1259246ae23073c1a5e413e643d0469da3d4b7b4de" -dependencies = [ - "kurbo 0.7.1", - "unic-bidi", -] - [[package]] name = "piet-gpu" version = "0.1.0" dependencies = [ "bytemuck", +<<<<<<< HEAD "clap 3.2.22", "ndk 0.3.0", "ndk-glue 0.3.0", "ndk-sys 0.2.2", "piet", +======= + "clap", + "kurbo 0.8.3", + "ndk 0.3.0", + "ndk-glue 0.3.0", + "ndk-sys", +>>>>>>> 2e8781f (Remove piet API & replace w/ fragments) "piet-gpu-hal", "piet-gpu-types", + "piet-scene", "png", "rand 0.8.5", "raw-window-handle 0.3.4", "raw-window-handle 0.5.0", "roxmltree", - "swash", "winit", ] @@ -1448,16 +1440,6 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" -[[package]] -name = "swash" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1bdb2004b76b5f5f3afe722d70b1aea160d2362cb7e40716a7106197ee7f82d" -dependencies = [ - "yazi", - "zeno", -] - [[package]] name = "syn" version = "1.0.100" @@ -1561,57 +1543,6 @@ dependencies = [ "serde", ] -[[package]] -name = "unic-bidi" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1356b759fb6a82050666f11dce4b6fe3571781f1449f3ef78074e408d468ec09" -dependencies = [ - "matches", - "unic-ucd-bidi", -] - -[[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-ucd-bidi" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1d568b51222484e1f8209ce48caa6b430bf352962b877d592c29ab31fb53d8c" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", -] - [[package]] name = "unicode-ident" version = "1.0.4" @@ -1938,15 +1869,3 @@ name = "xmlparser" version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "114ba2b24d2167ef6d67d7d04c8cc86522b87f490025f39f0303b7db5bf5e3d8" - -[[package]] -name = "yazi" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03b3e19c937b5b9bd8e52b1c88f30cce5c0d33d676cf174866175bb794ff658" - -[[package]] -name = "zeno" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c110ba09c9b3a43edd4803d570df0da2414fed6e822e22b976a4e3ef50860701" diff --git a/pgpu-render/src/lib.rs b/pgpu-render/src/lib.rs index 7763e58..8d137ea 100644 --- a/pgpu-render/src/lib.rs +++ b/pgpu-render/src/lib.rs @@ -26,9 +26,7 @@ mod render; -use piet_scene::brush::{Brush, Color}; -use piet_scene::path::Element; -use piet_scene::scene::Fill; +use piet_scene::{Brush, Color, Fill, PathElement}; use render::*; use std::ffi::c_void; use std::mem::transmute; @@ -199,7 +197,7 @@ pub struct PgpuTransform { pub dy: f32, } -impl From for PgpuAffine { +impl From for piet_scene::Affine { fn from(xform: PgpuTransform) -> Self { Self { xx: xform.xx, @@ -212,8 +210,6 @@ impl From for PgpuAffine { } } -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] @@ -243,7 +239,7 @@ pub unsafe extern "C" fn pgpu_scene_builder_add_glyph( } impl Iterator for PgpuPathIter { - type Item = piet_scene::path::Element; + type Item = PathElement; fn next(&mut self) -> Option { let mut el = PgpuPathElement { @@ -253,17 +249,17 @@ impl Iterator for PgpuPathIter { 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::MoveTo => PathElement::MoveTo((p[0].x, p[0].y).into()), + PgpuPathVerb::LineTo => PathElement::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()) + PathElement::QuadTo((p[0].x, p[0].y).into(), (p[1].x, p[1].y).into()) } - PgpuPathVerb::CurveTo => Element::CurveTo( + PgpuPathVerb::CurveTo => PathElement::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, + PgpuPathVerb::Close => PathElement::Close, }) } else { None @@ -445,7 +441,7 @@ pub unsafe extern "C" fn pgpu_glyph_bbox( glyph: *const PgpuGlyph, transform: &[f32; 6], ) -> PgpuRect { - let transform = piet_scene::geometry::Affine::new(transform); + let transform = piet_scene::Affine::new(transform); let rect = (*glyph).bbox(Some(transform)); PgpuRect { x0: rect.min.x, diff --git a/pgpu-render/src/render.rs b/pgpu-render/src/render.rs index 16b112a..bbea802 100644 --- a/pgpu-render/src/render.rs +++ b/pgpu-render/src/render.rs @@ -14,13 +14,11 @@ // // Also licensed under MIT license, at your choice. -use piet_gpu::{EncodedSceneRef, PixelFormat, RenderConfig}; +use piet_gpu::{PixelFormat, RenderConfig}; use piet_gpu_hal::{QueryPool, Session}; -use piet_scene::geometry::{Affine, Rect}; use piet_scene::glyph::pinot::{types::Tag, FontDataRef}; use piet_scene::glyph::{GlyphContext, GlyphProvider}; -use piet_scene::resource::ResourceContext; -use piet_scene::scene::{Fragment, Scene}; +use piet_scene::{Affine, Rect, ResourceContext, Scene, SceneFragment}; /// State and resources for rendering a scene. pub struct PgpuRenderer { @@ -120,47 +118,31 @@ impl PgpuScene { pub fn builder(&mut self) -> PgpuSceneBuilder { self.rcx.advance(); - PgpuSceneBuilder(piet_scene::scene::build_scene( + PgpuSceneBuilder(piet_scene::SceneBuilder::for_scene( &mut self.scene, &mut self.rcx, )) } - - fn encoded_scene<'a>(&'a self) -> EncodedSceneRef<'a, piet_scene::geometry::Affine> { - let d = self.scene.data(); - EncodedSceneRef { - transform_stream: &d.transform_stream, - tag_stream: &d.tag_stream, - pathseg_stream: &d.pathseg_stream, - linewidth_stream: &d.linewidth_stream, - drawtag_stream: &d.drawtag_stream, - drawdata_stream: &d.drawdata_stream, - n_path: d.n_path, - n_pathseg: d.n_pathseg, - n_clip: d.n_clip, - ramp_data: self.rcx.ramp_data(), - } - } } /// Encoded streams and resources describing a vector graphics scene fragment. -pub struct PgpuSceneFragment(pub Fragment); +pub struct PgpuSceneFragment(pub SceneFragment); impl PgpuSceneFragment { pub fn new() -> Self { - Self(Fragment::default()) + Self(SceneFragment::default()) } pub fn builder(&mut self) -> PgpuSceneBuilder { - PgpuSceneBuilder(piet_scene::scene::build_fragment(&mut self.0)) + PgpuSceneBuilder(piet_scene::SceneBuilder::for_fragment(&mut self.0)) } } /// Builder for constructing an encoded scene. -pub struct PgpuSceneBuilder<'a>(pub piet_scene::scene::Builder<'a>); +pub struct PgpuSceneBuilder<'a>(pub piet_scene::SceneBuilder<'a>); impl<'a> PgpuSceneBuilder<'a> { - pub fn add_glyph(&mut self, glyph: &PgpuGlyph, transform: &piet_scene::geometry::Affine) { + pub fn add_glyph(&mut self, glyph: &PgpuGlyph, transform: &piet_scene::Affine) { self.0.append(&glyph.fragment, Some(*transform)); } @@ -216,7 +198,7 @@ pub struct PgpuGlyphProvider<'a>(GlyphProvider<'a>); impl<'a> PgpuGlyphProvider<'a> { pub fn get(&mut self, gid: u16) -> Option { - let fragment = self.0.get(gid)?; + let fragment = self.0.get(gid, None)?; Some(PgpuGlyph { fragment }) } @@ -228,7 +210,7 @@ impl<'a> PgpuGlyphProvider<'a> { /// Encoded (possibly color) outline for a glyph. pub struct PgpuGlyph { - fragment: Fragment, + fragment: SceneFragment, } impl PgpuGlyph { diff --git a/piet-gpu/Cargo.toml b/piet-gpu/Cargo.toml index 1b33cba..7b09e59 100644 --- a/piet-gpu/Cargo.toml +++ b/piet-gpu/Cargo.toml @@ -26,16 +26,19 @@ path = "../piet-gpu-hal" [dependencies.piet-gpu-types] path = "../piet-gpu-types" +[dependencies.piet-scene] +path = "../piet-scene" +features = ["kurbo"] + [dependencies] -piet = "0.2.0" png = "0.17.6" rand = "0.8.5" roxmltree = "0.13" winit = {version = "0.27.3", default-features = false, features = ["x11", "wayland", "wayland-dlopen"]} raw-window-handle = "0.5" clap = "3.2.22" -swash = "0.1.4" bytemuck = { version = "1.7.2", features = ["derive"] } +kurbo = "0.8.3" [target.'cfg(target_os = "android")'.dependencies] ndk = "0.3" diff --git a/piet-gpu/bin/android.rs b/piet-gpu/bin/android.rs index bfdf4ad..6f91e36 100644 --- a/piet-gpu/bin/android.rs +++ b/piet-gpu/bin/android.rs @@ -16,10 +16,8 @@ use piet_gpu_hal::{ Error, ImageLayout, Instance, InstanceFlags, Semaphore, Session, Surface, Swapchain, }; -use piet::kurbo::Point; -use piet::{RenderContext, Text, TextAttribute, TextLayoutBuilder}; - -use piet_gpu::{test_scenes, PietGpuRenderContext, RenderDriver, Renderer}; +use piet_gpu::{samples, RenderDriver, Renderer, SimpleText}; +use piet_scene::{ResourceContext, Scene, SceneBuilder}; #[cfg_attr(target_os = "android", ndk_glue::main(backtrace = "on"))] fn main() { @@ -115,14 +113,15 @@ impl GfxState { info_string = stats.short_summary(); println!("{}", info_string); } - let mut ctx = PietGpuRenderContext::new(); - test_scenes::render_anim_frame(&mut ctx, self.current_frame); - //test_scenes::render_tiger(&mut ctx); - render_info_string(&mut ctx, &info_string); - if let Err(e) = self - .render_driver - .upload_render_ctx(&self.session, &mut ctx) - { + let mut text = SimpleText::new(); + let mut scene = Scene::default(); + let mut rcx = ResourceContext::default(); + let mut builder = SceneBuilder::for_scene(&mut scene, &mut rcx); + samples::render_anim_frame(&mut builder, self.current_frame); + //samples::render_tiger(&mut builder, false); + render_info(&mut text, &mut builder, &info_string); + builder.finish(); + if let Err(e) = self.render_driver.upload_scene(&self.session, &scene, &rcx) { println!("error in uploading: {}", e); } let (image_idx, acquisition_semaphore) = self.swapchain.next().unwrap(); @@ -154,12 +153,13 @@ impl GfxState { } } -fn render_info_string(rc: &mut impl RenderContext, info: &str) { - let layout = rc - .text() - .new_text_layout(info.to_string()) - .default_attribute(TextAttribute::FontSize(60.0)) - .build() - .unwrap(); - rc.draw_text(&layout, Point::new(110.0, 120.0)); +fn render_info(simple_text: &mut SimpleText, sb: &mut SceneBuilder, info: &str) { + simple_text.add( + sb, + None, + 60.0, + None, + piet_scene::Affine::translate(110.0, 120.0), + info, + ); } diff --git a/piet-gpu/bin/cli.rs b/piet-gpu/bin/cli.rs index 81572de..892f193 100644 --- a/piet-gpu/bin/cli.rs +++ b/piet-gpu/bin/cli.rs @@ -6,7 +6,8 @@ use clap::{App, Arg}; use piet_gpu_hal::{BufferUsage, Error, Instance, InstanceFlags, Session}; -use piet_gpu::{test_scenes, PicoSvg, PietGpuRenderContext, RenderDriver, Renderer}; +use piet_gpu::{samples, PicoSvg, RenderDriver, Renderer}; +use piet_scene::{ResourceContext, Scene, SceneBuilder}; const WIDTH: usize = 2048; const HEIGHT: usize = 1536; @@ -227,11 +228,13 @@ fn main() -> Result<(), Error> { ) .get_matches(); let instance = Instance::new(InstanceFlags::default())?; + let mut scene = Scene::default(); + let mut rcx = ResourceContext::default(); unsafe { let device = instance.device()?; let session = Session::new(device); - - let mut ctx = PietGpuRenderContext::new(); + rcx.advance(); + let mut builder = SceneBuilder::for_scene(&mut scene, &mut rcx); if let Some(input) = matches.value_of("INPUT") { let mut scale = matches .value_of("scale") @@ -244,16 +247,17 @@ fn main() -> Result<(), Error> { let start = std::time::Instant::now(); let svg = PicoSvg::load(&xml_str, scale).unwrap(); println!("parsing time: {:?}", start.elapsed()); - test_scenes::render_svg(&mut ctx, &svg); + samples::render_svg(&mut builder, &svg, true); } else { //test_scenes::render_scene(&mut ctx); - test_scenes::render_blend_grid(&mut ctx); + samples::render_blend_grid(&mut builder); } + builder.finish(); let renderer = Renderer::new(&session, WIDTH, HEIGHT, 1)?; let mut render_driver = RenderDriver::new(&session, 1, renderer); let start = std::time::Instant::now(); - render_driver.upload_render_ctx(&session, &mut ctx)?; + render_driver.upload_scene(&session, &scene, &rcx)?; let image_usage = BufferUsage::MAP_READ | BufferUsage::COPY_DST; let image_buf = session.create_buffer((WIDTH * HEIGHT * 4) as u64, image_usage)?; diff --git a/piet-gpu/bin/winit.rs b/piet-gpu/bin/winit.rs index c87ebba..f120ad8 100644 --- a/piet-gpu/bin/winit.rs +++ b/piet-gpu/bin/winit.rs @@ -1,8 +1,6 @@ -use piet::kurbo::Point; -use piet::{RenderContext, Text, TextAttribute, TextLayoutBuilder}; +use piet_gpu::{samples, PicoSvg, RenderDriver, Renderer, SimpleText}; use piet_gpu_hal::{Error, ImageLayout, Instance, InstanceFlags, Session}; - -use piet_gpu::{test_scenes, PicoSvg, PietGpuRenderContext, RenderDriver, Renderer}; +use piet_scene::{ResourceContext, Scene, SceneBuilder}; use clap::{App, Arg}; @@ -61,6 +59,9 @@ fn main() -> Result<(), Error> { let instance = Instance::new(InstanceFlags::default())?; let mut info_string = "info".to_string(); + let mut scene = Scene::default(); + let mut rcx = ResourceContext::default(); + let mut simple_text = piet_gpu::SimpleText::new(); unsafe { let display_handle = window.raw_display_handle(); let window_handle = window.raw_window_handle(); @@ -76,7 +77,7 @@ fn main() -> Result<(), Error> { let renderer = Renderer::new(&session, WIDTH, HEIGHT, NUM_FRAMES)?; let mut render_driver = RenderDriver::new(&session, NUM_FRAMES, renderer); - let mut mode = 0usize; + let mut sample_index = 0usize; event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Poll; // `ControlFlow::Wait` if only re-render on event @@ -91,8 +92,12 @@ fn main() -> Result<(), Error> { WindowEvent::KeyboardInput { input, .. } => { if input.state == ElementState::Pressed { match input.virtual_keycode { - Some(VirtualKeyCode::Left) => mode = mode.wrapping_sub(1), - Some(VirtualKeyCode::Right) => mode = mode.wrapping_add(1), + Some(VirtualKeyCode::Left) => { + sample_index = sample_index.saturating_sub(1) + } + Some(VirtualKeyCode::Right) => { + sample_index = sample_index.saturating_add(1) + } _ => {} } } @@ -111,52 +116,35 @@ fn main() -> Result<(), Error> { info_string = stats.short_summary(); } - let mut ctx = PietGpuRenderContext::new(); - let test_blend = false; if let Some(svg) = &svg { - test_scenes::render_svg(&mut ctx, svg); - } else if test_blend { - use piet_gpu::{Blend, BlendMode::*, CompositionMode::*}; - let blends = [ - Blend::new(Normal, SrcOver), - Blend::new(Multiply, SrcOver), - Blend::new(Screen, SrcOver), - Blend::new(Overlay, SrcOver), - Blend::new(Darken, SrcOver), - Blend::new(Lighten, SrcOver), - Blend::new(ColorDodge, SrcOver), - Blend::new(ColorBurn, SrcOver), - Blend::new(HardLight, SrcOver), - Blend::new(SoftLight, SrcOver), - Blend::new(Difference, SrcOver), - Blend::new(Exclusion, SrcOver), - Blend::new(Hue, SrcOver), - Blend::new(Saturation, SrcOver), - Blend::new(Color, SrcOver), - Blend::new(Luminosity, SrcOver), - Blend::new(Normal, Clear), - Blend::new(Normal, Copy), - Blend::new(Normal, Dest), - Blend::new(Normal, SrcOver), - Blend::new(Normal, DestOver), - Blend::new(Normal, SrcIn), - Blend::new(Normal, DestIn), - Blend::new(Normal, SrcOut), - Blend::new(Normal, DestOut), - Blend::new(Normal, SrcAtop), - Blend::new(Normal, DestAtop), - Blend::new(Normal, Xor), - Blend::new(Normal, Plus), - ]; - let blend = blends[mode % blends.len()]; - test_scenes::render_blend_test(&mut ctx, current_frame, blend); - info_string = format!("{:?}", blend); + rcx.advance(); + let mut builder = SceneBuilder::for_scene(&mut scene, &mut rcx); + samples::render_svg(&mut builder, svg, false); + render_info(&mut simple_text, &mut builder, &info_string); + builder.finish(); + if let Err(e) = render_driver.upload_scene(&session, &scene, &rcx) { + println!("error in uploading: {}", e); + } } else { - test_scenes::render_anim_frame(&mut ctx, current_frame); - } - render_info_string(&mut ctx, &info_string); - if let Err(e) = render_driver.upload_render_ctx(&session, &mut ctx) { - println!("error in uploading: {}", e); + rcx.advance(); + let mut builder = SceneBuilder::for_scene(&mut scene, &mut rcx); + + const N_SAMPLES: usize = 4; + match sample_index % N_SAMPLES { + 0 => samples::render_anim_frame( + &mut builder, + &mut simple_text, + current_frame, + ), + 1 => samples::render_blend_grid(&mut builder), + 2 => samples::render_tiger(&mut builder, false), + _ => samples::render_scene(&mut builder), + } + render_info(&mut simple_text, &mut builder, &info_string); + builder.finish(); + if let Err(e) = render_driver.upload_scene(&session, &scene, &rcx) { + println!("error in uploading: {}", e); + } } let (image_idx, acquisition_semaphore) = swapchain.next().unwrap(); @@ -197,12 +185,13 @@ fn main() -> Result<(), Error> { } } -fn render_info_string(rc: &mut impl RenderContext, info: &str) { - let layout = rc - .text() - .new_text_layout(info.to_string()) - .default_attribute(TextAttribute::FontSize(40.0)) - .build() - .unwrap(); - rc.draw_text(&layout, Point::new(110.0, 50.0)); +fn render_info(simple_text: &mut SimpleText, sb: &mut SceneBuilder, info: &str) { + simple_text.add( + sb, + None, + 40.0, + None, + piet_scene::Affine::translate(110.0, 50.0), + info, + ); } diff --git a/piet-gpu/src/blend.rs b/piet-gpu/src/blend.rs deleted file mode 100644 index f0ca002..0000000 --- a/piet-gpu/src/blend.rs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2022 The piet-gpu authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// Also licensed under MIT license, at your choice. - -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -#[repr(C)] -pub enum BlendMode { - Normal = 0, - Multiply = 1, - Screen = 2, - Overlay = 3, - Darken = 4, - Lighten = 5, - ColorDodge = 6, - ColorBurn = 7, - HardLight = 8, - SoftLight = 9, - Difference = 10, - Exclusion = 11, - Hue = 12, - Saturation = 13, - Color = 14, - Luminosity = 15, - // Clip is the same as normal, but doesn't always push a blend group. - Clip = 128, -} - -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -#[repr(C)] -pub enum CompositionMode { - Clear = 0, - Copy = 1, - Dest = 2, - SrcOver = 3, - DestOver = 4, - SrcIn = 5, - DestIn = 6, - SrcOut = 7, - DestOut = 8, - SrcAtop = 9, - DestAtop = 10, - Xor = 11, - Plus = 12, - PlusLighter = 13, -} - -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub struct Blend { - pub mode: BlendMode, - pub composition_mode: CompositionMode, -} - -impl Blend { - pub fn new(mode: BlendMode, composition_mode: CompositionMode) -> Self { - Self { - mode, - composition_mode, - } - } - - pub(crate) fn pack(&self) -> u32 { - (self.mode as u32) << 8 | self.composition_mode as u32 - } -} - -impl Default for Blend { - fn default() -> Self { - Self { - mode: BlendMode::Clip, - composition_mode: CompositionMode::SrcOver, - } - } -} - -impl From for Blend { - fn from(mode: BlendMode) -> Self { - Self { - mode, - composition_mode: CompositionMode::SrcOver, - } - } -} - -impl From for Blend { - fn from(mode: CompositionMode) -> Self { - Self { - mode: BlendMode::Normal, - composition_mode: mode, - } - } -} diff --git a/piet-gpu/src/encoder.rs b/piet-gpu/src/encoder.rs deleted file mode 100644 index 2d7c23a..0000000 --- a/piet-gpu/src/encoder.rs +++ /dev/null @@ -1,301 +0,0 @@ -// Copyright 2021 The piet-gpu authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// Also licensed under MIT license, at your choice. - -//! Low-level scene encoding. - -use crate::{Blend, SceneStats, DRAWTAG_SIZE, TRANSFORM_SIZE}; -use bytemuck::{Pod, Zeroable}; -use piet_gpu_hal::BufWrite; - -use crate::stages::{self, PathEncoder, Transform, DRAW_PART_SIZE, PATHSEG_PART_SIZE}; - -pub struct Encoder { - transform_stream: Vec, - tag_stream: Vec, - pathseg_stream: Vec, - linewidth_stream: Vec, - drawtag_stream: Vec, - drawdata_stream: Vec, - n_path: u32, - n_pathseg: u32, - n_clip: u32, -} - -#[derive(Copy, Clone, Debug)] -pub struct EncodedSceneRef<'a, T: Copy + Pod> { - pub transform_stream: &'a [T], - pub tag_stream: &'a [u8], - pub pathseg_stream: &'a [u8], - pub linewidth_stream: &'a [f32], - pub drawtag_stream: &'a [u32], - pub drawdata_stream: &'a [u8], - pub n_path: u32, - pub n_pathseg: u32, - pub n_clip: u32, - pub ramp_data: &'a [u32], -} - -impl<'a, T: Copy + Pod> EncodedSceneRef<'a, T> { - pub(crate) fn stats(&self) -> SceneStats { - SceneStats { - n_drawobj: self.drawtag_stream.len(), - drawdata_len: self.drawdata_stream.len(), - n_transform: self.transform_stream.len(), - linewidth_len: std::mem::size_of_val(self.linewidth_stream), - pathseg_len: self.pathseg_stream.len(), - n_pathtag: self.tag_stream.len(), - - n_path: self.n_path, - n_pathseg: self.n_pathseg, - n_clip: self.n_clip, - } - } - - pub fn write_scene(&self, buf: &mut BufWrite) { - buf.extend_slice(&self.drawtag_stream); - let n_drawobj = self.drawtag_stream.len(); - buf.fill_zero(padding(n_drawobj, DRAW_PART_SIZE as usize) * DRAWTAG_SIZE); - buf.extend_slice(&self.drawdata_stream); - buf.extend_slice(&self.transform_stream); - buf.extend_slice(&self.linewidth_stream); - buf.extend_slice(&self.tag_stream); - let n_pathtag = self.tag_stream.len(); - buf.fill_zero(padding(n_pathtag, PATHSEG_PART_SIZE as usize)); - buf.extend_slice(&self.pathseg_stream); - } -} - -/// A scene fragment encoding a glyph. -/// -/// This is a reduced version of the full encoder. -#[derive(Default)] -pub struct GlyphEncoder { - tag_stream: Vec, - pathseg_stream: Vec, - drawtag_stream: Vec, - drawdata_stream: Vec, - n_path: u32, - n_pathseg: u32, -} - -// Tags for draw objects. See shader/drawtag.h for the authoritative source. -const DRAWTAG_FILLCOLOR: u32 = 0x44; -const DRAWTAG_FILLLINGRADIENT: u32 = 0x114; -const DRAWTAG_FILLRADGRADIENT: u32 = 0x2dc; -const DRAWTAG_BEGINCLIP: u32 = 0x05; -const DRAWTAG_ENDCLIP: u32 = 0x25; - -#[repr(C)] -#[derive(Clone, Copy, Debug, Default, Zeroable, Pod)] -pub struct FillColor { - rgba_color: u32, -} - -#[repr(C)] -#[derive(Clone, Copy, Debug, Default, Zeroable, Pod)] -pub struct FillLinGradient { - index: u32, - p0: [f32; 2], - p1: [f32; 2], -} - -#[repr(C)] -#[derive(Clone, Copy, Debug, Default, Zeroable, Pod)] -pub struct FillRadGradient { - index: u32, - p0: [f32; 2], - p1: [f32; 2], - r0: f32, - r1: f32, -} - -#[allow(unused)] -#[repr(C)] -#[derive(Clone, Copy, Debug, Default, Zeroable, Pod)] -pub struct FillImage { - index: u32, - // [i16; 2] - offset: u32, -} - -#[repr(C)] -#[derive(Clone, Copy, Debug, Default, Zeroable, Pod)] -pub struct Clip { - blend: u32, -} - -impl Encoder { - pub fn new() -> Encoder { - Encoder { - transform_stream: vec![Transform::IDENTITY], - tag_stream: Vec::new(), - pathseg_stream: Vec::new(), - linewidth_stream: vec![-1.0], - drawtag_stream: Vec::new(), - drawdata_stream: Vec::new(), - n_path: 0, - n_pathseg: 0, - n_clip: 0, - } - } - - pub fn path_encoder(&mut self) -> PathEncoder { - PathEncoder::new(&mut self.tag_stream, &mut self.pathseg_stream) - } - - pub fn finish_path(&mut self, n_pathseg: u32) { - self.n_path += 1; - self.n_pathseg += n_pathseg; - } - - pub fn transform(&mut self, transform: Transform) { - self.tag_stream.push(0x20); - self.transform_stream.push(transform); - } - - // Swap the last two tags in the tag stream; used for transformed - // gradients. - pub fn swap_last_tags(&mut self) { - let len = self.tag_stream.len(); - self.tag_stream.swap(len - 1, len - 2); - } - - // -1.0 means "fill" - pub fn linewidth(&mut self, linewidth: f32) { - self.tag_stream.push(0x40); - self.linewidth_stream.push(linewidth); - } - - /// Encode a fill color draw object. - /// - /// This should be encoded after a path. - pub fn fill_color(&mut self, rgba_color: u32) { - self.drawtag_stream.push(DRAWTAG_FILLCOLOR); - let element = FillColor { rgba_color }; - self.drawdata_stream.extend(bytemuck::bytes_of(&element)); - } - - /// Encode a fill linear gradient draw object. - /// - /// This should be encoded after a path. - pub fn fill_lin_gradient(&mut self, index: u32, p0: [f32; 2], p1: [f32; 2]) { - self.drawtag_stream.push(DRAWTAG_FILLLINGRADIENT); - let element = FillLinGradient { index, p0, p1 }; - self.drawdata_stream.extend(bytemuck::bytes_of(&element)); - } - - /// Encode a fill radial gradient draw object. - /// - /// This should be encoded after a path. - pub fn fill_rad_gradient(&mut self, index: u32, p0: [f32; 2], p1: [f32; 2], r0: f32, r1: f32) { - self.drawtag_stream.push(DRAWTAG_FILLRADGRADIENT); - let element = FillRadGradient { - index, - p0, - p1, - r0, - r1, - }; - self.drawdata_stream.extend(bytemuck::bytes_of(&element)); - } - - /// Start a clip. - pub fn begin_clip(&mut self, blend: Option) { - self.drawtag_stream.push(DRAWTAG_BEGINCLIP); - let element = Clip { - blend: blend.unwrap_or(Blend::default()).pack(), - }; - self.drawdata_stream.extend(bytemuck::bytes_of(&element)); - self.n_clip += 1; - } - - pub fn end_clip(&mut self, blend: Option) { - self.drawtag_stream.push(DRAWTAG_ENDCLIP); - let element = Clip { - blend: blend.unwrap_or(Blend::default()).pack(), - }; - self.drawdata_stream.extend(bytemuck::bytes_of(&element)); - // This is a dummy path, and will go away with the new clip impl. - self.tag_stream.push(0x10); - self.n_path += 1; - self.n_clip += 1; - } - - pub fn write_scene(&self, buf: &mut BufWrite) { - buf.extend_slice(&self.drawtag_stream); - let n_drawobj = self.drawtag_stream.len(); - buf.fill_zero(padding(n_drawobj, DRAW_PART_SIZE as usize) * DRAWTAG_SIZE); - buf.extend_slice(&self.drawdata_stream); - buf.extend_slice(&self.transform_stream); - buf.extend_slice(&self.linewidth_stream); - buf.extend_slice(&self.tag_stream); - let n_pathtag = self.tag_stream.len(); - buf.fill_zero(padding(n_pathtag, PATHSEG_PART_SIZE as usize)); - buf.extend_slice(&self.pathseg_stream); - } - - pub(crate) fn stats(&self) -> SceneStats { - SceneStats { - n_drawobj: self.drawtag_stream.len(), - drawdata_len: self.drawdata_stream.len(), - n_transform: self.transform_stream.len(), - linewidth_len: std::mem::size_of_val(&*self.linewidth_stream), - n_pathtag: self.tag_stream.len(), - pathseg_len: self.pathseg_stream.len(), - - n_path: self.n_path, - n_pathseg: self.n_pathseg, - n_clip: self.n_clip, - } - } - - pub(crate) fn encode_glyph(&mut self, glyph: &GlyphEncoder) { - self.tag_stream.extend(&glyph.tag_stream); - self.pathseg_stream.extend(&glyph.pathseg_stream); - self.drawtag_stream.extend(&glyph.drawtag_stream); - self.drawdata_stream.extend(&glyph.drawdata_stream); - self.n_path += glyph.n_path; - self.n_pathseg += glyph.n_pathseg; - } -} - -fn padding(x: usize, align: usize) -> usize { - x.wrapping_neg() & (align - 1) -} - -impl GlyphEncoder { - pub(crate) fn path_encoder(&mut self) -> PathEncoder { - PathEncoder::new(&mut self.tag_stream, &mut self.pathseg_stream) - } - - pub(crate) fn finish_path(&mut self, n_pathseg: u32) { - self.n_path += 1; - self.n_pathseg += n_pathseg; - } - - /// Encode a fill color draw object. - /// - /// This should be encoded after a path. - pub(crate) fn fill_color(&mut self, rgba_color: u32) { - self.drawtag_stream.push(DRAWTAG_FILLCOLOR); - let element = FillColor { rgba_color }; - self.drawdata_stream.extend(bytemuck::bytes_of(&element)); - } - - pub(crate) fn is_color(&self) -> bool { - !self.drawtag_stream.is_empty() - } -} diff --git a/piet-gpu/src/glyph_render.rs b/piet-gpu/src/glyph_render.rs deleted file mode 100644 index 8f4c626..0000000 --- a/piet-gpu/src/glyph_render.rs +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2022 The piet-gpu authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// Also licensed under MIT license, at your choice. - -//! An experimental API for glyph rendering. - -use piet::{kurbo::Affine, RenderContext}; -use swash::{scale::ScaleContext, CacheKey, FontDataRef}; - -use crate::{encoder::GlyphEncoder, PietGpuRenderContext}; - -pub struct GlyphRenderer { - pub render_ctx: PietGpuRenderContext, - scale_context: ScaleContext, -} - -#[repr(transparent)] -pub struct FontId(CacheKey); - -impl GlyphRenderer { - pub fn new() -> GlyphRenderer { - let render_ctx = PietGpuRenderContext::new(); - let scale_context = ScaleContext::new(); - GlyphRenderer { - render_ctx, - scale_context, - } - } - - pub unsafe fn add_glyph( - &mut self, - font_data: &[u8], - font_id: u64, - glyph_id: u16, - transform: [f32; 6], - ) { - // This transmute is dodgy because the definition in swash isn't repr(transparent). - // I think the best solution is to have a from_u64 method, but we'll work that out - // later. - let font_id = FontId(std::mem::transmute(font_id)); - let encoder = self.make_glyph(font_data, font_id, glyph_id); - const DEFAULT_UPEM: u16 = 2048; - let affine = Affine::new([ - transform[0] as f64, - transform[1] as f64, - transform[2] as f64, - transform[3] as f64, - transform[4] as f64, - transform[5] as f64, - ]) * Affine::scale(1.0 / DEFAULT_UPEM as f64); - self.render_ctx.transform(affine); - self.render_ctx.encode_glyph(&encoder); - // TODO: don't fill glyph if RGBA - self.render_ctx.fill_glyph(0xff_ff_ff_ff); - self.render_ctx.transform(affine.inverse()); - } - - pub fn reset(&mut self) { - self.render_ctx = PietGpuRenderContext::new(); - } - - fn make_glyph(&mut self, font_data: &[u8], font_id: FontId, glyph_id: u16) -> GlyphEncoder { - let mut encoder = GlyphEncoder::default(); - let font_data = FontDataRef::new(font_data).expect("invalid font"); - let mut font_ref = font_data.get(0).expect("invalid font index"); - font_ref.key = font_id.0; - let mut scaler = self.scale_context.builder(font_ref).size(2048.).build(); - if let Some(outline) = scaler.scale_outline(glyph_id) { - crate::text::append_outline(&mut encoder, outline.verbs(), outline.points()); - } else { - println!("failed to scale"); - } - encoder - } -} diff --git a/piet-gpu/src/gradient.rs b/piet-gpu/src/gradient.rs deleted file mode 100644 index 443eaec..0000000 --- a/piet-gpu/src/gradient.rs +++ /dev/null @@ -1,249 +0,0 @@ -// Copyright 2021 The piet-gpu authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// Also licensed under MIT license, at your choice. - -//! Implementation of gradients. - -use std::collections::hash_map::{Entry, HashMap}; - -use piet::kurbo::Point; -use piet::{Color, FixedLinearGradient, FixedRadialGradient, GradientStop}; - -/// Radial gradient compatible with COLRv1 spec -#[derive(Debug, Clone)] -pub struct Colrv1RadialGradient { - /// The center of the iner circle. - pub center0: Point, - /// The offset of the origin relative to the center. - pub center1: Point, - /// The radius of the inner circle. - pub radius0: f64, - /// The radius of the outer circle. - pub radius1: f64, - /// The stops. - pub stops: Vec, -} - -#[derive(Clone)] -pub struct BakedGradient { - ramp: Vec, -} - -#[derive(Clone)] -pub struct LinearGradient { - pub(crate) start: [f32; 2], - pub(crate) end: [f32; 2], - pub(crate) ramp_id: u32, -} - -#[derive(Clone)] -pub struct RadialGradient { - pub(crate) start: [f32; 2], - pub(crate) end: [f32; 2], - pub(crate) r0: f32, - pub(crate) r1: f32, - pub(crate) ramp_id: u32, -} - -#[derive(Default)] -pub struct RampCache { - ramps: Vec, - map: HashMap, -} - -#[derive(Clone, Hash, PartialEq, Eq)] -struct GradientRamp(Vec); - -pub const N_SAMPLES: usize = 512; -// TODO: make this dynamic -pub const N_GRADIENTS: usize = 256; - -#[derive(Clone, Copy)] -struct PremulRgba([f64; 4]); - -impl PremulRgba { - fn from_color(c: &Color) -> PremulRgba { - let rgba = c.as_rgba(); - let a = rgba.3; - // TODO: sRGB nonlinearity? This is complicated. - PremulRgba([rgba.0 * a, rgba.1 * a, rgba.2 * a, a]) - } - - fn to_u32(&self) -> u32 { - let z = self.0; - let r = (z[0].max(0.0).min(1.0) * 255.0).round() as u32; - let g = (z[1].max(0.0).min(1.0) * 255.0).round() as u32; - let b = (z[2].max(0.0).min(1.0) * 255.0).round() as u32; - let a = (z[3].max(0.0).min(1.0) * 255.0).round() as u32; - r | (g << 8) | (b << 16) | (a << 24) - } - - fn lerp(&self, other: PremulRgba, t: f64) -> PremulRgba { - fn l(a: f64, b: f64, t: f64) -> f64 { - a * (1.0 - t) + b * t - } - let a = self.0; - let b = other.0; - PremulRgba([ - l(a[0], b[0], t), - l(a[1], b[1], t), - l(a[2], b[2], t), - l(a[3], b[3], t), - ]) - } -} - -impl GradientRamp { - fn from_stops(stops: &[GradientStop]) -> GradientRamp { - let mut last_u = 0.0; - let mut last_c = PremulRgba::from_color(&stops[0].color); - let mut this_u = last_u; - let mut this_c = last_c; - let mut j = 0; - let v = (0..N_SAMPLES) - .map(|i| { - let u = (i as f64) / (N_SAMPLES - 1) as f64; - while u > this_u { - last_u = this_u; - last_c = this_c; - if let Some(s) = stops.get(j + 1) { - this_u = s.pos as f64; - this_c = PremulRgba::from_color(&s.color); - j += 1; - } else { - break; - } - } - let du = this_u - last_u; - let c = if du < 1e-9 { - this_c - } else { - last_c.lerp(this_c, (u - last_u) / du) - }; - c.to_u32() - }) - .collect(); - GradientRamp(v) - } - - /// For debugging/development. - pub(crate) fn dump(&self) { - for val in &self.0 { - println!("{:x}", val); - } - } -} - -impl RampCache { - /// Add a gradient ramp to the cache. - /// - /// Currently there is no eviction, so if the gradient is animating, there may - /// be resource leaks. In order to support lifetime management, the signature - /// should probably change so it returns a ref-counted handle, so that eviction - /// is deferred until the last handle is dropped. - /// - /// This function is pretty expensive, but the result is lightweight. - fn add_ramp(&mut self, ramp: &[GradientStop]) -> usize { - let ramp = GradientRamp::from_stops(ramp); - match self.map.entry(ramp) { - Entry::Occupied(o) => *o.get(), - Entry::Vacant(v) => { - let idx = self.ramps.len(); - self.ramps.push(v.key().clone()); - v.insert(idx); - idx - } - } - } - - pub fn add_linear_gradient(&mut self, lin: &FixedLinearGradient) -> LinearGradient { - let ramp_id = self.add_ramp(&lin.stops); - LinearGradient { - ramp_id: ramp_id as u32, - start: crate::render_ctx::to_f32_2(lin.start), - end: crate::render_ctx::to_f32_2(lin.end), - } - } - - pub fn add_radial_gradient(&mut self, rad: &FixedRadialGradient) -> RadialGradient { - let ramp_id = self.add_ramp(&rad.stops); - RadialGradient { - ramp_id: ramp_id as u32, - start: crate::render_ctx::to_f32_2(rad.center + rad.origin_offset), - end: crate::render_ctx::to_f32_2(rad.center), - r0: 0.0, - r1: rad.radius as f32, - } - } - - pub fn add_radial_gradient_colrv1(&mut self, rad: &Colrv1RadialGradient) -> RadialGradient { - let ramp_id = self.add_ramp(&rad.stops); - RadialGradient { - ramp_id: ramp_id as u32, - start: crate::render_ctx::to_f32_2(rad.center0), - end: crate::render_ctx::to_f32_2(rad.center1), - r0: rad.radius0 as f32, - r1: rad.radius1 as f32, - } - } - - /// Dump the contents of a gradient. This is for debugging. - #[allow(unused)] - pub(crate) fn dump_gradient(&self, lin: &LinearGradient) { - println!("id = {}", lin.ramp_id); - self.ramps[lin.ramp_id as usize].dump(); - } - - /// Get the ramp data. - /// - /// This concatenates all the ramps; we'll want a more sophisticated approach to - /// incremental update. - pub fn get_ramp_data(&self) -> Vec { - let mut result = Vec::with_capacity(N_SAMPLES * self.ramps.len()); - for ramp in &self.ramps { - result.extend(&ramp.0); - } - result - } -} - -#[cfg(test)] -mod test { - use super::RampCache; - use piet::kurbo::Point; - use piet::{Color, FixedLinearGradient, GradientStop}; - - #[test] - fn simple_ramp() { - let stops = vec![ - GradientStop { - color: Color::WHITE, - pos: 0.0, - }, - GradientStop { - color: Color::BLACK, - pos: 1.0, - }, - ]; - let mut cache = RampCache::default(); - let lin = FixedLinearGradient { - start: Point::new(0.0, 0.0), - end: Point::new(0.0, 1.0), - stops, - }; - let our_lin = cache.add_linear_gradient(&lin); - cache.dump_gradient(&our_lin); - } -} diff --git a/piet-gpu/src/lib.rs b/piet-gpu/src/lib.rs index 71ce7d4..a3d86b2 100644 --- a/piet-gpu/src/lib.rs +++ b/piet-gpu/src/lib.rs @@ -1,31 +1,24 @@ -mod blend; -mod encoder; -pub mod glyph_render; -mod gradient; mod pico_svg; -mod render_ctx; mod render_driver; +pub mod samples; +mod simple_text; pub mod stages; -pub mod test_scenes; -mod text; + +pub use piet_scene as scene; use bytemuck::{Pod, Zeroable}; use std::convert::TryInto; -pub use blend::{Blend, BlendMode, CompositionMode}; -pub use encoder::EncodedSceneRef; -pub use gradient::Colrv1RadialGradient; -pub use render_ctx::PietGpuRenderContext; pub use render_driver::RenderDriver; - -use piet::kurbo::Vec2; -use piet::{ImageFormat, RenderContext}; +pub use simple_text::SimpleText; use piet_gpu_hal::{ - include_shader, BindType, Buffer, BufferUsage, CmdBuf, ComputePassDescriptor, DescriptorSet, - Error, Image, ImageLayout, Pipeline, QueryPool, Session, + include_shader, BindType, BufWrite, Buffer, BufferUsage, CmdBuf, ComputePassDescriptor, + DescriptorSet, Error, Image, ImageLayout, Pipeline, QueryPool, Session, }; +use piet_scene::{ResourceContext, Scene}; + pub use pico_svg::PicoSvg; use stages::{ClipBinding, ElementBinding, ElementCode, DRAW_PART_SIZE, PATHSEG_PART_SIZE}; @@ -36,6 +29,10 @@ const TILE_H: usize = 16; const PTCL_INITIAL_ALLOC: usize = 1024; +const N_GRADIENT_SAMPLES: usize = 512; +// TODO: make this dynamic +const N_GRADIENTS: usize = 256; + #[allow(unused)] fn dump_scene(buf: &[u8]) { for i in 0..(buf.len() / 4) { @@ -333,8 +330,8 @@ impl Renderer { .collect::, _>>()?; let bg_image = Self::make_test_bg_image(&session); - const GRADIENT_BUF_SIZE: usize = - crate::gradient::N_GRADIENTS * crate::gradient::N_SAMPLES * 4; + const GRADIENT_BUF_SIZE: usize = N_GRADIENTS * N_GRADIENT_SAMPLES * 4; + let gradient_bufs = (0..n_bufs) .map(|_| { session @@ -409,59 +406,29 @@ impl Renderer { }) } - /// Convert the scene in the render context to GPU resources. - /// - /// At present, this requires that any command buffer submission has completed. - /// A future evolution will handle staging of the next frame's scene while the - /// rendering of the current frame is in flight. - pub fn upload_render_ctx( + pub fn upload_scene( &mut self, - render_ctx: &mut PietGpuRenderContext, + scene: &Scene, + rcx: &ResourceContext, buf_ix: usize, ) -> Result<(), Error> { - self.scene_stats = render_ctx.stats(); + self.scene_stats = SceneStats::from_scene(scene); unsafe { self.upload_config(buf_ix)?; { let mut mapped_scene = self.scene_bufs[buf_ix].map_write(..)?; - render_ctx.write_scene(&mut mapped_scene); + write_scene(scene, &mut mapped_scene); } // Upload gradient data. - let ramp_data = render_ctx.get_ramp_data(); + let ramp_data = rcx.ramp_data(); if !ramp_data.is_empty() { assert!( self.gradient_bufs[buf_ix].size() as usize >= std::mem::size_of_val(&*ramp_data) ); - self.gradient_bufs[buf_ix].write(&ramp_data)?; - } - } - Ok(()) - } - - pub fn upload_scene( - &mut self, - scene: &EncodedSceneRef, - buf_ix: usize, - ) -> Result<(), Error> { - self.scene_stats = scene.stats(); - - unsafe { - self.upload_config(buf_ix)?; - { - let mut mapped_scene = self.scene_bufs[buf_ix].map_write(..)?; - scene.write_scene(&mut mapped_scene); - } - - // Upload gradient data. - if !scene.ramp_data.is_empty() { - assert!( - self.gradient_bufs[buf_ix].size() as usize - >= std::mem::size_of_val(&*scene.ramp_data) - ); - self.gradient_bufs[buf_ix].write(scene.ramp_data)?; + self.gradient_bufs[buf_ix].write(ramp_data)?; } } Ok(()) @@ -643,12 +610,8 @@ impl Renderer { width: usize, height: usize, buf: &[u8], - format: ImageFormat, ) -> Result { unsafe { - if format != ImageFormat::RgbaPremul { - return Err("unsupported image format".into()); - } let buffer = session.create_buffer_init(&buf, BufferUsage::COPY_SRC)?; const RGBA: piet_gpu_hal::ImageFormat = piet_gpu_hal::ImageFormat::Rgba8; let image = session.create_image2d(width.try_into()?, height.try_into()?, RGBA)?; @@ -682,18 +645,14 @@ impl Renderer { buf[(y * WIDTH + x) * 4 + 2] = b; } } - Self::make_image(session, WIDTH, HEIGHT, &buf, ImageFormat::RgbaPremul).unwrap() + Self::make_image(session, WIDTH, HEIGHT, &buf).unwrap() } fn make_gradient_image(session: &Session) -> Image { unsafe { const RGBA: piet_gpu_hal::ImageFormat = piet_gpu_hal::ImageFormat::Rgba8; session - .create_image2d( - gradient::N_SAMPLES as u32, - gradient::N_GRADIENTS as u32, - RGBA, - ) + .create_image2d(N_GRADIENT_SAMPLES as u32, N_GRADIENTS as u32, RGBA) .unwrap() } } @@ -792,6 +751,21 @@ const DRAWTAG_SIZE: usize = 4; const ANNOTATED_SIZE: usize = 40; impl SceneStats { + pub fn from_scene(scene: &piet_scene::Scene) -> Self { + let data = scene.data(); + Self { + n_drawobj: data.drawtag_stream.len(), + drawdata_len: data.drawdata_stream.len(), + n_transform: data.transform_stream.len(), + linewidth_len: std::mem::size_of_val(&*data.linewidth_stream), + pathseg_len: data.pathseg_stream.len(), + n_pathtag: data.tag_stream.len(), + n_path: data.n_path, + n_pathseg: data.n_pathseg, + n_clip: data.n_clip, + } + } + pub(crate) fn scene_size(&self) -> usize { align_up(self.n_drawobj, DRAW_PART_SIZE as usize) * DRAWTAG_SIZE + self.drawdata_len @@ -896,6 +870,24 @@ impl SceneStats { } } +fn write_scene(scene: &Scene, buf: &mut BufWrite) { + let data = scene.data(); + buf.extend_slice(&data.drawtag_stream); + let n_drawobj = data.drawtag_stream.len(); + buf.fill_zero(padding(n_drawobj, DRAW_PART_SIZE as usize) * DRAWTAG_SIZE); + buf.extend_slice(&data.drawdata_stream); + buf.extend_slice(&data.transform_stream); + buf.extend_slice(&data.linewidth_stream); + buf.extend_slice(&data.tag_stream); + let n_pathtag = data.tag_stream.len(); + buf.fill_zero(padding(n_pathtag, PATHSEG_PART_SIZE as usize)); + buf.extend_slice(&data.pathseg_stream); +} + +fn padding(x: usize, align: usize) -> usize { + x.wrapping_neg() & (align - 1) +} + fn align_up(x: usize, align: usize) -> usize { debug_assert!(align.is_power_of_two()); (x + align - 1) & !(align - 1) diff --git a/piet-gpu/src/pico_svg.rs b/piet-gpu/src/pico_svg.rs index 140c42d..eebe3ec 100644 --- a/piet-gpu/src/pico_svg.rs +++ b/piet-gpu/src/pico_svg.rs @@ -4,12 +4,12 @@ use std::str::FromStr; use roxmltree::{Document, Node}; -use piet::kurbo::{Affine, BezPath}; +use kurbo::{Affine, BezPath}; -use piet::{Color, RenderContext}; +use piet_scene::Color; pub struct PicoSvg { - items: Vec, + pub items: Vec, } pub enum Item { @@ -18,14 +18,14 @@ pub enum Item { } pub struct StrokeItem { - width: f64, - color: Color, - path: BezPath, + pub width: f64, + pub color: Color, + pub path: BezPath, } pub struct FillItem { - color: Color, - path: BezPath, + pub color: Color, + pub path: BezPath, } struct Parser<'a> { @@ -44,20 +44,6 @@ impl PicoSvg { } Ok(PicoSvg { items }) } - - pub fn render(&self, rc: &mut impl RenderContext) { - for item in &self.items { - match item { - Item::Fill(fill_item) => { - rc.fill(&fill_item.path, &fill_item.color); - //rc.stroke(&fill_item.path, &fill_item.color, 1.0); - } - Item::Stroke(stroke_item) => { - rc.stroke(&stroke_item.path, &stroke_item.color, stroke_item.width); - } - } - } - } } impl<'a> Parser<'a> { @@ -119,7 +105,14 @@ fn parse_color(color: &str) -> Color { if color.len() == 4 { hex = (hex >> 8) * 0x110000 + ((hex >> 4) & 0xf) * 0x1100 + (hex & 0xf) * 0x11; } - Color::from_rgba32_u32((hex << 8) + 0xff) + let rgba = (hex << 8) + 0xff; + let (r, g, b, a) = ( + (rgba >> 24 & 255) as u8, + ((rgba >> 16) & 255) as u8, + ((rgba >> 8) & 255) as u8, + (rgba & 255) as u8, + ); + Color::rgba8(r, g, b, a) } else if color.starts_with("rgb(") { let mut iter = color[4..color.len() - 1].split(','); let r = u8::from_str(iter.next().unwrap()).unwrap(); @@ -127,19 +120,20 @@ fn parse_color(color: &str) -> Color { let b = u8::from_str(iter.next().unwrap()).unwrap(); Color::rgb8(r, g, b) } else { - Color::from_rgba32_u32(0xff00ff80) + Color::rgba8(255, 0, 255, 0x80) } } -fn modify_opacity(color: Color, attr_name: &str, node: Node) -> Color { +fn modify_opacity(mut color: Color, attr_name: &str, node: Node) -> Color { if let Some(opacity) = node.attribute(attr_name) { let alpha = if opacity.ends_with("%") { let pctg = opacity[..opacity.len() - 1].parse().unwrap_or(100.0); pctg * 0.01 } else { opacity.parse().unwrap_or(1.0) - }; - color.with_alpha(alpha) + } as f64; + color.a = (alpha.min(1.0).max(0.0) * 255.0).round() as u8; + color } else { color } diff --git a/piet-gpu/src/render_ctx.rs b/piet-gpu/src/render_ctx.rs deleted file mode 100644 index a283507..0000000 --- a/piet-gpu/src/render_ctx.rs +++ /dev/null @@ -1,452 +0,0 @@ -// This should match the value in kernel4.comp for correct rendering. -const DO_SRGB_CONVERSION: bool = false; - -use std::borrow::Cow; - -use crate::encoder::GlyphEncoder; -use crate::stages::Transform; -use piet::kurbo::{Affine, PathEl, Point, Rect, Shape}; -use piet::{ - Color, Error, FixedGradient, ImageFormat, InterpolationMode, IntoBrush, RenderContext, - StrokeStyle, -}; - -use piet_gpu_hal::BufWrite; -use piet_gpu_types::encoder::{Encode, Encoder}; -use piet_gpu_types::scene::Element; - -use crate::gradient::{Colrv1RadialGradient, LinearGradient, RadialGradient, RampCache}; -use crate::text::Font; -pub use crate::text::{PietGpuText, PietGpuTextLayout, PietGpuTextLayoutBuilder}; -use crate::{Blend, SceneStats}; - -pub struct PietGpuImage; - -pub struct PietGpuRenderContext { - encoder: Encoder, - elements: Vec, - // Will probably need direct accesss to hal Device to create images etc. - inner_text: PietGpuText, - 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, - /// The count of transform elements. - trans_count: usize, - - cur_transform: Affine, - state_stack: Vec, - clip_stack: Vec, - - ramp_cache: RampCache, - - // Fields for new element processing pipeline below - // TODO: delete old encoder, rename - new_encoder: crate::encoder::Encoder, -} - -#[derive(Clone)] -pub enum PietGpuBrush { - Solid(u32), - LinGradient(LinearGradient), - RadGradient(RadialGradient), -} - -#[derive(Default)] -struct State { - /// The transform at the parent state. - transform: Affine, - n_clip: usize, -} - -struct ClipElement { - blend: Option, -} - -const TOLERANCE: f64 = 0.25; - -impl PietGpuRenderContext { - pub fn new() -> PietGpuRenderContext { - let encoder = Encoder::new(); - let elements = Vec::new(); - let font = Font::new(); - let inner_text = PietGpuText::new(font); - let stroke_width = -1.0; - PietGpuRenderContext { - encoder, - elements, - inner_text, - stroke_width, - path_count: 0, - pathseg_count: 0, - trans_count: 0, - cur_transform: Affine::default(), - state_stack: Vec::new(), - clip_stack: Vec::new(), - ramp_cache: RampCache::default(), - new_encoder: crate::encoder::Encoder::new(), - } - } - - pub(crate) fn stats(&self) -> SceneStats { - self.new_encoder.stats() - } - - pub fn write_scene(&self, buf: &mut BufWrite) { - self.new_encoder.write_scene(buf); - } - - // TODO: delete - pub fn get_scene_buf(&mut self) -> &[u8] { - const ALIGN: usize = 128; - let padded_size = (self.elements.len() + (ALIGN - 1)) & ALIGN.wrapping_neg(); - self.elements.resize(padded_size, Element::Nop()); - self.elements.encode(&mut self.encoder); - self.encoder.buf() - } - - pub fn path_count(&self) -> usize { - self.path_count - } - - pub fn pathseg_count(&self) -> usize { - self.pathseg_count - } - - pub fn trans_count(&self) -> usize { - self.trans_count - } - - pub fn get_ramp_data(&self) -> Vec { - self.ramp_cache.get_ramp_data() - } -} - -impl RenderContext for PietGpuRenderContext { - type Brush = PietGpuBrush; - type Image = PietGpuImage; - type Text = PietGpuText; - type TextLayout = PietGpuTextLayout; - - fn status(&mut self) -> Result<(), Error> { - Ok(()) - } - - fn solid_brush(&mut self, color: Color) -> Self::Brush { - // kernel4 expects colors encoded in alpha-premultiplied sRGB: - // - // [α,sRGB(α⋅R),sRGB(α⋅G),sRGB(α⋅B)] - // - // See also http://ssp.impulsetrain.com/gamma-premult.html. - let (r, g, b, a) = color.as_rgba(); - let premul = Color::rgba( - to_srgb(from_srgb(r) * a), - to_srgb(from_srgb(g) * a), - to_srgb(from_srgb(b) * a), - a, - ); - PietGpuBrush::Solid(premul.as_rgba_u32()) - } - - fn gradient(&mut self, gradient: impl Into) -> Result { - match gradient.into() { - FixedGradient::Linear(lin) => { - let lin = self.ramp_cache.add_linear_gradient(&lin); - Ok(PietGpuBrush::LinGradient(lin)) - } - FixedGradient::Radial(rad) => { - let rad = self.ramp_cache.add_radial_gradient(&rad); - Ok(PietGpuBrush::RadGradient(rad)) - } - } - } - - fn clear(&mut self, _color: Color) {} - - fn stroke(&mut self, shape: impl Shape, brush: &impl IntoBrush, width: f64) { - self.encode_linewidth(width.abs() as f32); - let brush = brush.make_brush(self, || shape.bounding_box()).into_owned(); - let path = shape.path_elements(TOLERANCE); - self.encode_path(path, false); - self.encode_brush(&brush); - } - - fn stroke_styled( - &mut self, - _shape: impl Shape, - _brush: &impl IntoBrush, - _width: f64, - _style: &StrokeStyle, - ) { - } - - 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_linewidth(-1.0); - self.encode_path(path, true); - self.encode_brush(&brush); - } - - fn fill_even_odd(&mut self, _shape: impl Shape, _brush: &impl IntoBrush) {} - - fn clip(&mut self, shape: impl Shape) { - self.encode_linewidth(-1.0); - let path = shape.path_elements(TOLERANCE); - self.encode_path(path, true); - self.new_encoder.begin_clip(None); - self.clip_stack.push(ClipElement { blend: None }); - if let Some(tos) = self.state_stack.last_mut() { - tos.n_clip += 1; - } - } - - fn text(&mut self) -> &mut Self::Text { - &mut self.inner_text - } - - fn draw_text(&mut self, layout: &Self::TextLayout, pos: impl Into) { - self.encode_linewidth(-1.0); - layout.draw_text(self, pos.into()); - } - - fn save(&mut self) -> Result<(), Error> { - self.state_stack.push(State { - transform: self.cur_transform, - n_clip: 0, - }); - Ok(()) - } - - fn restore(&mut self) -> Result<(), Error> { - if let Some(state) = self.state_stack.pop() { - self.encode_transform(Transform::from_kurbo(state.transform)); - self.cur_transform = state.transform; - for _ in 0..state.n_clip { - self.pop_clip(); - } - Ok(()) - } else { - Err(Error::StackUnbalance) - } - } - - fn finish(&mut self) -> Result<(), Error> { - for _ in 0..self.clip_stack.len() { - self.pop_clip(); - } - Ok(()) - } - - fn transform(&mut self, transform: Affine) { - self.cur_transform *= transform; - self.encode_transform(Transform::from_kurbo(self.cur_transform)); - } - - fn make_image( - &mut self, - _width: usize, - _height: usize, - _buf: &[u8], - _format: ImageFormat, - ) -> Result { - Ok(PietGpuImage) - } - - fn draw_image( - &mut self, - _image: &Self::Image, - _rect: impl Into, - _interp: InterpolationMode, - ) { - } - - fn draw_image_area( - &mut self, - _image: &Self::Image, - _src_rect: impl Into, - _dst_rect: impl Into, - _interp: InterpolationMode, - ) { - } - - fn blurred_rect(&mut self, _rect: Rect, _blur_radius: f64, _brush: &impl IntoBrush) {} - - fn current_transform(&self) -> Affine { - 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()) - } -} - -impl PietGpuRenderContext { - pub fn blend(&mut self, shape: impl Shape, blend: Blend) { - self.encode_linewidth(-1.0); - let path = shape.path_elements(TOLERANCE); - self.encode_path(path, true); - self.new_encoder.begin_clip(Some(blend)); - self.clip_stack.push(ClipElement { blend: Some(blend) }); - if let Some(tos) = self.state_stack.last_mut() { - tos.n_clip += 1; - } - } - - pub fn radial_gradient_colrv1(&mut self, rad: &Colrv1RadialGradient) -> PietGpuBrush { - PietGpuBrush::RadGradient(self.ramp_cache.add_radial_gradient_colrv1(rad)) - } - - pub fn fill_transform(&mut self, shape: impl Shape, brush: &PietGpuBrush, transform: Affine) { - let path = shape.path_elements(TOLERANCE); - self.encode_linewidth(-1.0); - self.encode_path(path, true); - self.encode_transform(Transform::from_kurbo(transform)); - self.new_encoder.swap_last_tags(); - self.encode_brush(&brush); - self.encode_transform(Transform::from_kurbo(transform.inverse())); - } - - fn encode_path(&mut self, path: impl Iterator, is_fill: bool) { - if is_fill { - self.encode_path_inner( - path.flat_map(|el| { - match el { - PathEl::MoveTo(..) => Some(PathEl::ClosePath), - _ => None, - } - .into_iter() - .chain(Some(el)) - }) - .chain(Some(PathEl::ClosePath)), - ) - } else { - self.encode_path_inner(path) - } - } - - fn encode_path_inner(&mut self, path: impl Iterator) { - let mut pe = self.new_encoder.path_encoder(); - for el in path { - match el { - PathEl::MoveTo(p) => { - let p = to_f32_2(p); - pe.move_to(p[0], p[1]); - } - PathEl::LineTo(p) => { - let p = to_f32_2(p); - pe.line_to(p[0], p[1]); - } - PathEl::QuadTo(p1, p2) => { - let p1 = to_f32_2(p1); - let p2 = to_f32_2(p2); - pe.quad_to(p1[0], p1[1], p2[0], p2[1]); - } - PathEl::CurveTo(p1, p2, p3) => { - let p1 = to_f32_2(p1); - let p2 = to_f32_2(p2); - let p3 = to_f32_2(p3); - pe.cubic_to(p1[0], p1[1], p2[0], p2[1], p3[0], p3[1]); - } - PathEl::ClosePath => pe.close_path(), - } - } - pe.path(); - let n_pathseg = pe.n_pathseg(); - self.new_encoder.finish_path(n_pathseg); - } - - fn pop_clip(&mut self) { - let tos = self.clip_stack.pop().unwrap(); - self.new_encoder.end_clip(tos.blend); - } - - pub(crate) fn encode_glyph(&mut self, glyph: &GlyphEncoder) { - self.new_encoder.encode_glyph(glyph); - } - - pub(crate) fn fill_glyph(&mut self, rgba_color: u32) { - self.new_encoder.fill_color(rgba_color); - } - - pub(crate) fn encode_transform(&mut self, transform: Transform) { - self.new_encoder.transform(transform); - } - - fn encode_linewidth(&mut self, linewidth: f32) { - if self.stroke_width != linewidth { - self.new_encoder.linewidth(linewidth); - self.stroke_width = linewidth; - } - } - - fn encode_brush(&mut self, brush: &PietGpuBrush) { - match brush { - PietGpuBrush::Solid(rgba_color) => { - self.new_encoder.fill_color(*rgba_color); - } - PietGpuBrush::LinGradient(lin) => { - self.new_encoder - .fill_lin_gradient(lin.ramp_id, lin.start, lin.end); - } - PietGpuBrush::RadGradient(rad) => { - self.new_encoder - .fill_rad_gradient(rad.ramp_id, rad.start, rad.end, rad.r0, rad.r1); - } - } - } -} - -impl IntoBrush for PietGpuBrush { - fn make_brush<'b>( - &'b self, - _piet: &mut PietGpuRenderContext, - _bbox: impl FnOnce() -> Rect, - ) -> std::borrow::Cow<'b, PietGpuBrush> { - Cow::Borrowed(self) - } -} - -pub(crate) 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_srgb(f: f64) -> f64 { - if DO_SRGB_CONVERSION { - if f <= 0.0031308 { - f * 12.92 - } else { - let a = 0.055; - (1. + a) * f64::powf(f, f64::recip(2.4)) - a - } - } else { - f - } -} - -fn from_srgb(f: f64) -> f64 { - if DO_SRGB_CONVERSION { - if f <= 0.04045 { - f / 12.92 - } else { - let a = 0.055; - f64::powf((f + a) * f64::recip(1. + a), 2.4) - } - } else { - f - } -} diff --git a/piet-gpu/src/render_driver.rs b/piet-gpu/src/render_driver.rs index 98dff0c..afeec5e 100644 --- a/piet-gpu/src/render_driver.rs +++ b/piet-gpu/src/render_driver.rs @@ -14,10 +14,10 @@ // // Also licensed under MIT license, at your choice. -use bytemuck::Pod; use piet_gpu_hal::{CmdBuf, Error, Image, QueryPool, Semaphore, Session, SubmittedCmdBuf}; +use piet_scene::{ResourceContext, Scene}; -use crate::{EncodedSceneRef, MemoryHeader, PietGpuRenderContext, Renderer, SceneStats}; +use crate::{MemoryHeader, Renderer, SceneStats}; /// Additional logic for sequencing rendering operations, specifically /// for handling failure and reallocation. @@ -86,24 +86,15 @@ impl RenderDriver { } } - pub fn upload_render_ctx( + pub fn upload_scene( &mut self, session: &Session, - render_ctx: &mut PietGpuRenderContext, + scene: &Scene, + rcx: &ResourceContext, ) -> Result<(), Error> { - let stats = render_ctx.stats(); + let stats = SceneStats::from_scene(scene); self.ensure_scene_buffers(session, &stats)?; - self.renderer.upload_render_ctx(render_ctx, self.buf_ix) - } - - pub fn upload_scene( - &mut self, - session: &Session, - scene: &EncodedSceneRef, - ) -> Result<(), Error> { - let stats = scene.stats(); - self.ensure_scene_buffers(session, &stats)?; - self.renderer.upload_scene(scene, self.buf_ix) + self.renderer.upload_scene(scene, rcx, self.buf_ix) } fn ensure_scene_buffers(&mut self, session: &Session, stats: &SceneStats) -> Result<(), Error> { diff --git a/piet-gpu/src/samples.rs b/piet-gpu/src/samples.rs new file mode 100644 index 0000000..b3b4b8b --- /dev/null +++ b/piet-gpu/src/samples.rs @@ -0,0 +1,368 @@ +use crate::PicoSvg; +use kurbo::BezPath; +use piet_scene::*; + +use crate::SimpleText; + +#[allow(unused)] +const N_CIRCLES: usize = 0; + +#[allow(unused)] +pub fn render_svg(sb: &mut SceneBuilder, svg: &PicoSvg, print_stats: bool) { + use crate::pico_svg::*; + let start = std::time::Instant::now(); + for item in &svg.items { + match item { + Item::Fill(fill) => { + sb.fill( + Fill::NonZero, + &fill.color.into(), + None, + convert_bez_path(&fill.path), + ); + } + Item::Stroke(stroke) => { + sb.stroke( + &simple_stroke(stroke.width as f32), + &stroke.color.into(), + None, + convert_bez_path(&stroke.path), + ); + } + } + } + if print_stats { + println!("flattening and encoding time: {:?}", start.elapsed()); + } +} + +#[allow(unused)] +pub fn render_tiger(sb: &mut SceneBuilder, print_stats: bool) { + use super::pico_svg::*; + let xml_str = std::str::from_utf8(include_bytes!("../Ghostscript_Tiger.svg")).unwrap(); + let start = std::time::Instant::now(); + let svg = PicoSvg::load(xml_str, 8.0).unwrap(); + if print_stats { + println!("parsing time: {:?}", start.elapsed()); + } + render_svg(sb, &svg, print_stats); +} + +pub fn render_scene(sb: &mut SceneBuilder) { + render_cardioid(sb); + render_clip_test(sb); + render_alpha_test(sb); + //render_tiger(sb, false); +} + +#[allow(unused)] +fn render_cardioid(sb: &mut SceneBuilder) { + let n = 601; + let dth = std::f32::consts::PI * 2.0 / (n as f32); + let center = Point::new(1024.0, 768.0); + let r = 750.0; + let mut path = vec![]; + for i in 1..n { + let mut p0 = center; + let a0 = i as f32 * dth; + p0.x += a0.cos() * r; + p0.y += a0.sin() * r; + let mut p1 = center; + let a1 = ((i * 2) % n) as f32 * dth; + p1.x += a1.cos() * r; + p1.y += a1.sin() * r; + path.push(PathElement::MoveTo(p0)); + path.push(PathElement::LineTo(p1)); + } + sb.stroke( + &simple_stroke(2.0), + &Brush::Solid(Color::rgb8(0, 0, 0)), + None, + &path, + ); +} + +#[allow(unused)] +fn render_clip_test(sb: &mut SceneBuilder) { + const N: usize = 16; + const X0: f32 = 50.0; + const Y0: f32 = 450.0; + // Note: if it gets much larger, it will exceed the 1MB scratch buffer. + // But this is a pretty demanding test. + const X1: f32 = 550.0; + const Y1: f32 = 950.0; + let step = 1.0 / ((N + 1) as f32); + for i in 0..N { + let t = ((i + 1) as f32) * step; + let path = &[ + PathElement::MoveTo((X0, Y0).into()), + PathElement::LineTo((X1, Y0).into()), + PathElement::LineTo((X1, Y0 + t * (Y1 - Y0)).into()), + PathElement::LineTo((X1 + t * (X0 - X1), Y1).into()), + PathElement::LineTo((X0, Y1).into()), + PathElement::Close, + ]; + sb.push_layer(Mix::Clip.into(), path); + } + let rect = Rect { + min: Point::new(X0, Y0), + max: Point::new(X1, Y1), + }; + sb.fill( + Fill::NonZero, + &Brush::Solid(Color::rgb8(0, 0, 0)), + None, + rect.elements(), + ); + for _ in 0..N { + sb.pop_layer(); + } +} + +#[allow(unused)] +fn render_alpha_test(sb: &mut SceneBuilder) { + // Alpha compositing tests. + sb.fill( + Fill::NonZero, + &Color::rgb8(255, 0, 0).into(), + None, + make_diamond(Point::new(1024.0, 100.0)), + ); + sb.fill( + Fill::NonZero, + &Color::rgba8(0, 255, 0, 0x80).into(), + None, + make_diamond(Point::new(1024.0, 125.0)), + ); + sb.push_layer(Mix::Clip.into(), make_diamond(Point::new(1024.0, 150.0))); + sb.fill( + Fill::NonZero, + &Color::rgba8(0, 0, 255, 0x80).into(), + None, + make_diamond(Point::new(1024.0, 175.0)), + ); + sb.pop_layer(); +} + +#[allow(unused)] +pub fn render_blend_grid(sb: &mut SceneBuilder) { + const BLEND_MODES: &[Mix] = &[ + Mix::Normal, + Mix::Multiply, + Mix::Darken, + Mix::Screen, + Mix::Lighten, + Mix::Overlay, + Mix::ColorDodge, + Mix::ColorBurn, + Mix::HardLight, + Mix::SoftLight, + Mix::Difference, + Mix::Exclusion, + Mix::Hue, + Mix::Saturation, + Mix::Color, + Mix::Luminosity, + ]; + for (ix, &blend) in BLEND_MODES.iter().enumerate() { + let i = ix % 4; + let j = ix / 4; + let transform = Affine::translate(i as f32 * 225., j as f32 * 225.); + 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 { + 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(transform); + sb.push_layer(blend, 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, + &linear, + None, + make_ellipse(100., 100., 90., 90.), + ); + sb.pop_layer(); + } + sb.pop_layer(); +} + +#[allow(unused)] +pub fn render_anim_frame(sb: &mut SceneBuilder, text: &mut SimpleText, i: usize) { + sb.fill( + Fill::NonZero, + &Brush::Solid(Color::rgb8(128, 128, 128)), + None, + Rect::from_origin_size(Point::new(0.0, 0.0), 1000.0, 1000.0).elements(), + ); + let text_size = 60.0 + 40.0 * (0.01 * i as f32).sin(); + let s = "\u{1f600}hello piet-gpu text!"; + text.add( + sb, + None, + text_size, + None, + Affine::translate(110.0, 600.0), + s, + ); + text.add( + sb, + None, + text_size, + None, + 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; + p1.x += 400.0 * th.cos(); + p1.y += 400.0 * th.sin(); + sb.stroke( + &simple_stroke(5.0), + &Brush::Solid(Color::rgb8(128, 0, 0)), + None, + &[PathElement::MoveTo(center), PathElement::LineTo(p1)], + ); +} + +fn convert_bez_path<'a>(path: &'a BezPath) -> impl Iterator + 'a + Clone { + path.elements() + .iter() + .map(|el| PathElement::from_kurbo(*el)) +} + +fn make_ellipse(cx: f32, cy: f32, rx: f32, ry: f32) -> impl Iterator + Clone { + let a = 0.551915024494; + let arx = a * rx; + let ary = a * ry; + let elements = [ + PathElement::MoveTo(Point::new(cx + rx, cy)), + PathElement::CurveTo( + Point::new(cx + rx, cy + ary), + Point::new(cx + arx, cy + ry), + Point::new(cx, cy + ry), + ), + PathElement::CurveTo( + Point::new(cx - arx, cy + ry), + Point::new(cx - rx, cy + ary), + Point::new(cx - rx, cy), + ), + PathElement::CurveTo( + Point::new(cx - rx, cy - ary), + Point::new(cx - arx, cy - ry), + Point::new(cx, cy - ry), + ), + PathElement::CurveTo( + Point::new(cx + arx, cy - ry), + Point::new(cx + rx, cy - ary), + Point::new(cx + rx, cy), + ), + PathElement::Close, + ]; + (0..elements.len()).map(move |i| elements[i]) +} + +fn make_diamond(origin: Point) -> impl Iterator + 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::Close, + ]; + (0..elements.len()).map(move |i| elements[i]) +} + +fn simple_stroke(width: f32) -> Stroke<[f32; 0]> { + Stroke { + width, + join: Join::Round, + miter_limit: 1.4, + start_cap: Cap::Round, + end_cap: Cap::Round, + dash_pattern: [], + dash_offset: 0.0, + scale: true, + } +} diff --git a/piet-gpu/src/simple_text.rs b/piet-gpu/src/simple_text.rs new file mode 100644 index 0000000..0068b70 --- /dev/null +++ b/piet-gpu/src/simple_text.rs @@ -0,0 +1,82 @@ +// Copyright 2022 The piet-gpu authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Also licensed under MIT license, at your choice. + +use piet_scene::glyph::{pinot, pinot::TableProvider, GlyphContext}; +use piet_scene::{Affine, Brush, SceneBuilder}; + +pub use pinot::FontRef; + +// This is very much a hack to get things working. +// On Windows, can set this to "c:\\Windows\\Fonts\\seguiemj.ttf" to get color emoji +const FONT_DATA: &[u8] = include_bytes!("../third-party/Roboto-Regular.ttf"); + +pub struct SimpleText { + gcx: GlyphContext, +} + +impl SimpleText { + pub fn new() -> Self { + Self { + gcx: GlyphContext::new(), + } + } + + pub fn add( + &mut self, + builder: &mut SceneBuilder, + font: Option<&FontRef>, + size: f32, + brush: Option<&Brush>, + transform: Affine, + text: &str, + ) { + let font = font.unwrap_or(&FontRef { + data: FONT_DATA, + offset: 0, + }); + if let Some(cmap) = font.cmap() { + if let Some(hmtx) = font.hmtx() { + let upem = font.head().map(|head| head.units_per_em()).unwrap_or(1000) as f32; + let scale = size / upem; + let vars: [(pinot::types::Tag, f32); 0] = []; + let mut provider = self.gcx.new_provider(font, None, size, false, vars); + let hmetrics = hmtx.hmetrics(); + let default_advance = hmetrics + .get(hmetrics.len().saturating_sub(1)) + .map(|h| h.advance_width) + .unwrap_or(0); + let mut pen_x = 0f32; + for ch in text.chars() { + let gid = cmap.map(ch as u32).unwrap_or(0); + let advance = hmetrics + .get(gid as usize) + .map(|h| h.advance_width) + .unwrap_or(default_advance) as f32 + * scale; + if let Some(glyph) = provider.get(gid, brush) { + if !glyph.is_empty() { + let xform = transform + * Affine::translate(pen_x, 0.0) + * Affine::scale(1.0, -1.0); + builder.append(&glyph, Some(xform)); + } + } + pen_x += advance; + } + } + } + } +} diff --git a/piet-gpu/src/stages/transform.rs b/piet-gpu/src/stages/transform.rs index 0e0a3f6..8e237ba 100644 --- a/piet-gpu/src/stages/transform.rs +++ b/piet-gpu/src/stages/transform.rs @@ -18,8 +18,6 @@ use bytemuck::{Pod, Zeroable}; -use piet::kurbo::Affine; - /// An affine transform. // This is equivalent to the version in piet-gpu-types, but the bytemuck // representation will likely be faster. @@ -35,23 +33,4 @@ impl Transform { mat: [1.0, 0.0, 0.0, 1.0], translate: [0.0, 0.0], }; - - pub fn from_kurbo(a: Affine) -> Transform { - let c = a.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], - } - } - - pub fn to_kurbo(self) -> Affine { - Affine::new([ - self.mat[0] as f64, - self.mat[1] as f64, - self.mat[2] as f64, - self.mat[3] as f64, - self.translate[0] as f64, - self.translate[1] as f64, - ]) - } } diff --git a/piet-gpu/src/test_scenes.rs b/piet-gpu/src/test_scenes.rs deleted file mode 100644 index 94956ad..0000000 --- a/piet-gpu/src/test_scenes.rs +++ /dev/null @@ -1,332 +0,0 @@ -//! Various synthetic scenes for exercising the renderer. - -use rand::{Rng, RngCore}; - -use crate::{Blend, BlendMode, Colrv1RadialGradient, PietGpuRenderContext}; -use piet::kurbo::{Affine, BezPath, Circle, Line, Point, Rect, Shape}; -use piet::{ - Color, GradientStop, LinearGradient, Text, TextAttribute, TextLayoutBuilder, UnitPoint, -}; - -use crate::{PicoSvg, RenderContext, Vec2}; - -const N_CIRCLES: usize = 0; - -pub fn render_blend_test(rc: &mut PietGpuRenderContext, i: usize, blend: Blend) { - rc.fill(Rect::new(400., 400., 800., 800.), &Color::rgb8(0, 0, 200)); - rc.save().unwrap(); - rc.blend(Rect::new(0., 0., 1000., 1000.), blend); - rc.transform(Affine::translate(Vec2::new(600., 600.)) * Affine::rotate(0.01 * i as f64)); - rc.fill(Rect::new(0., 0., 400., 400.), &Color::rgba8(255, 0, 0, 255)); - rc.restore().unwrap(); -} - -pub fn render_svg(rc: &mut impl RenderContext, svg: &PicoSvg) { - let start = std::time::Instant::now(); - svg.render(rc); - println!("flattening and encoding time: {:?}", start.elapsed()); -} - -pub fn render_scene(rc: &mut PietGpuRenderContext) { - const WIDTH: usize = 2048; - const HEIGHT: usize = 1536; - let mut rng = rand::thread_rng(); - for _ in 0..N_CIRCLES { - let color = Color::from_rgba32_u32(rng.next_u32()); - let center = Point::new( - rng.gen_range(0.0..WIDTH as f64), - rng.gen_range(0.0..HEIGHT as f64), - ); - let radius = rng.gen_range(0.0..50.0); - let circle = Circle::new(center, radius); - rc.fill(circle, &color); - } - let _ = rc.save(); - let mut path = BezPath::new(); - path.move_to((200.0, 150.0)); - path.line_to((100.0, 200.0)); - path.line_to((150.0, 250.0)); - path.close_path(); - rc.clip(path); - - let mut path = BezPath::new(); - path.move_to((100.0, 150.0)); - path.line_to((200.0, 200.0)); - path.line_to((150.0, 250.0)); - path.close_path(); - rc.fill(path, &Color::rgb8(128, 0, 128)); - let _ = rc.restore(); - rc.stroke( - piet::kurbo::Line::new((100.0, 100.0), (200.0, 150.0)), - &Color::WHITE, - 5.0, - ); - //render_cardioid(rc); - render_clip_test(rc); - render_alpha_test(rc); - render_gradient_test(rc); - render_text_test(rc); - //render_tiger(rc); -} - -#[allow(unused)] -fn render_cardioid(rc: &mut impl RenderContext) { - let n = 601; - let dth = std::f64::consts::PI * 2.0 / (n as f64); - let center = Point::new(1024.0, 768.0); - let r = 750.0; - let mut path = BezPath::new(); - for i in 1..n { - let p0 = center + Vec2::from_angle(i as f64 * dth) * r; - let p1 = center + Vec2::from_angle(((i * 2) % n) as f64 * dth) * r; - //rc.fill(&Circle::new(p0, 8.0), &Color::WHITE); - path.move_to(p0); - path.line_to(p1); - //rc.stroke(Line::new(p0, p1), &Color::BLACK, 2.0); - } - rc.stroke(&path, &Color::BLACK, 2.0); -} - -#[allow(unused)] -fn render_clip_test(rc: &mut impl RenderContext) { - const N: usize = 16; - const X0: f64 = 50.0; - const Y0: f64 = 450.0; - // Note: if it gets much larger, it will exceed the 1MB scratch buffer. - // But this is a pretty demanding test. - const X1: f64 = 550.0; - const Y1: f64 = 950.0; - let step = 1.0 / ((N + 1) as f64); - for i in 0..N { - let t = ((i + 1) as f64) * step; - rc.save(); - let mut path = BezPath::new(); - path.move_to((X0, Y0)); - path.line_to((X1, Y0)); - path.line_to((X1, Y0 + t * (Y1 - Y0))); - path.line_to((X1 + t * (X0 - X1), Y1)); - path.line_to((X0, Y1)); - path.close_path(); - rc.clip(path); - } - let rect = piet::kurbo::Rect::new(X0, Y0, X1, Y1); - rc.fill(rect, &Color::BLACK); - for _ in 0..N { - rc.restore(); - } -} - -#[allow(unused)] -fn render_alpha_test(rc: &mut impl RenderContext) { - // Alpha compositing tests. - rc.fill( - diamond(Point::new(1024.0, 100.0)), - &Color::Rgba32(0xff0000ff), - ); - rc.fill( - diamond(Point::new(1024.0, 125.0)), - &Color::Rgba32(0x00ff0080), - ); - rc.save(); - rc.clip(diamond(Point::new(1024.0, 150.0))); - rc.fill( - diamond(Point::new(1024.0, 175.0)), - &Color::Rgba32(0x0000ff80), - ); - rc.restore(); -} - -#[allow(unused)] -fn render_gradient_test(rc: &mut PietGpuRenderContext) { - let stops = vec![ - GradientStop { - color: Color::rgb8(0, 255, 0), - pos: 0.0, - }, - GradientStop { - color: Color::BLACK, - pos: 1.0, - }, - ]; - let rad = Colrv1RadialGradient { - center0: Point::new(200.0, 200.0), - center1: Point::new(250.0, 200.0), - radius0: 50.0, - radius1: 100.0, - stops, - }; - let brush = rc.radial_gradient_colrv1(&rad); - //let brush = FixedGradient::Radial(rad); - //let brush = Color::rgb8(0, 128, 0); - let transform = Affine::new([1.0, 0.0, 0.0, 0.5, 0.0, 100.0]); - rc.fill_transform(Rect::new(100.0, 100.0, 300.0, 300.0), &brush, transform); -} - -fn diamond(origin: Point) -> impl Shape { - let mut path = BezPath::new(); - const SIZE: f64 = 50.0; - path.move_to((origin.x, origin.y - SIZE)); - path.line_to((origin.x + SIZE, origin.y)); - path.line_to((origin.x, origin.y + SIZE)); - path.line_to((origin.x - SIZE, origin.y)); - path.close_path(); - return path; -} - -#[allow(unused)] -fn render_text_test(rc: &mut impl RenderContext) { - rc.save(); - //rc.transform(Affine::new([0.2, 0.0, 0.0, -0.2, 200.0, 800.0])); - let layout = rc - .text() - .new_text_layout("\u{1f600}hello piet-gpu text!") - .default_attribute(TextAttribute::FontSize(100.0)) - .build() - .unwrap(); - rc.draw_text(&layout, Point::new(110.0, 600.0)); - rc.draw_text(&layout, Point::new(110.0, 700.0)); - rc.restore(); -} - -#[allow(unused)] -fn render_tiger(rc: &mut impl RenderContext) { - let xml_str = std::str::from_utf8(include_bytes!("../Ghostscript_Tiger.svg")).unwrap(); - let start = std::time::Instant::now(); - let svg = PicoSvg::load(xml_str, 8.0).unwrap(); - println!("parsing time: {:?}", start.elapsed()); - - let start = std::time::Instant::now(); - svg.render(rc); - println!("flattening and encoding time: {:?}", start.elapsed()); -} - -pub fn render_blend_square(rc: &mut PietGpuRenderContext, blend: Blend) { - // Inspired by https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode - let rect = Rect::new(0., 0., 200., 200.); - let stops = vec![ - GradientStop { - color: Color::BLACK, - pos: 0.0, - }, - GradientStop { - color: Color::WHITE, - pos: 1.0, - }, - ]; - let linear = LinearGradient::new(UnitPoint::LEFT, UnitPoint::RIGHT, stops); - rc.fill(rect, &linear); - const GRADIENTS: &[(f64, f64, Color)] = &[ - (150., 0., Color::rgb8(255, 240, 64)), - (175., 100., Color::rgb8(255, 96, 240)), - (125., 200., Color::rgb8(64, 192, 255)), - ]; - for (x, y, c) in GRADIENTS { - let stops = vec![ - GradientStop { - color: c.clone(), - pos: 0.0, - }, - GradientStop { - color: Color::rgba8(0, 0, 0, 0), - pos: 1.0, - }, - ]; - let rad = Colrv1RadialGradient { - center0: Point::new(*x, *y), - center1: Point::new(*x, *y), - radius0: 0.0, - radius1: 100.0, - stops, - }; - let brush = rc.radial_gradient_colrv1(&rad); - rc.fill(Rect::new(0., 0., 200., 200.), &brush); - } - const COLORS: &[Color] = &[ - Color::rgb8(255, 0, 0), - Color::rgb8(0, 255, 0), - Color::rgb8(0, 0, 255), - ]; - let _ = rc.with_save(|rc| { - // Isolation (this can be removed for non-isolated version) - rc.blend(rect, BlendMode::Normal.into()); - for (i, c) in COLORS.iter().enumerate() { - let stops = vec![ - GradientStop { - color: Color::WHITE, - pos: 0.0, - }, - GradientStop { - color: c.clone(), - pos: 1.0, - }, - ]; - // squash the ellipse - let a = Affine::translate((100., 100.)) - * Affine::rotate(std::f64::consts::FRAC_PI_3 * (i * 2 + 1) as f64) - * Affine::scale_non_uniform(1.0, 0.357) - * Affine::translate((-100., -100.)); - let linear = LinearGradient::new(UnitPoint::TOP, UnitPoint::BOTTOM, stops); - let _ = rc.with_save(|rc| { - rc.blend(rect, blend); - rc.transform(a); - rc.fill(Circle::new((100., 100.), 90.), &linear); - Ok(()) - }); - } - Ok(()) - }); -} - -pub fn render_blend_grid(rc: &mut PietGpuRenderContext) { - const BLEND_MODES: &[BlendMode] = &[ - BlendMode::Normal, - BlendMode::Multiply, - BlendMode::Darken, - BlendMode::Screen, - BlendMode::Lighten, - BlendMode::Overlay, - BlendMode::ColorDodge, - BlendMode::ColorBurn, - BlendMode::HardLight, - BlendMode::SoftLight, - BlendMode::Difference, - BlendMode::Exclusion, - BlendMode::Hue, - BlendMode::Saturation, - BlendMode::Color, - BlendMode::Luminosity, - ]; - for (ix, &blend) in BLEND_MODES.iter().enumerate() { - let _ = rc.with_save(|rc| { - let i = ix % 4; - let j = ix / 4; - rc.transform(Affine::translate((i as f64 * 225., j as f64 * 225.))); - render_blend_square(rc, blend.into()); - Ok(()) - }); - } -} - -pub fn render_anim_frame(rc: &mut impl RenderContext, i: usize) { - rc.fill( - Rect::new(0.0, 0.0, 1000.0, 1000.0), - &Color::rgb8(128, 128, 128), - ); - let text_size = 60.0 + 40.0 * (0.01 * i as f64).sin(); - rc.save().unwrap(); - //rc.transform(Affine::new([0.2, 0.0, 0.0, -0.2, 200.0, 800.0])); - let layout = rc - .text() - .new_text_layout("\u{1f600}hello piet-gpu text!") - .default_attribute(TextAttribute::FontSize(text_size)) - .build() - .unwrap(); - rc.draw_text(&layout, Point::new(110.0, 600.0)); - rc.draw_text(&layout, Point::new(110.0, 700.0)); - rc.restore().unwrap(); - let th = (std::f64::consts::PI / 180.0) * (i as f64); - let center = Point::new(500.0, 500.0); - let p1 = center + 400.0 * Vec2::from_angle(th); - let line = Line::new(center, p1); - rc.stroke(line, &Color::rgb8(128, 0, 0), 5.0); -} diff --git a/piet-gpu/src/text.rs b/piet-gpu/src/text.rs deleted file mode 100644 index da8b86e..0000000 --- a/piet-gpu/src/text.rs +++ /dev/null @@ -1,271 +0,0 @@ -use std::ops::RangeBounds; - -use swash::scale::{ScaleContext, Scaler}; -use swash::zeno::{Vector, Verb}; -use swash::{FontRef, GlyphId}; - -use piet::kurbo::{Point, Rect, Size}; -use piet::{ - Error, FontFamily, HitTestPoint, HitTestPosition, LineMetric, RenderContext, Text, - TextAttribute, TextLayout, TextLayoutBuilder, TextStorage, -}; - -use crate::encoder::GlyphEncoder; -use crate::render_ctx; -use crate::stages::Transform; -use crate::PietGpuRenderContext; - -// This is very much a hack to get things working. -// On Windows, can set this to "c:\\Windows\\Fonts\\seguiemj.ttf" to get color emoji -const FONT_DATA: &[u8] = include_bytes!("../third-party/Roboto-Regular.ttf"); - -#[derive(Clone)] -pub struct Font { - // Storing the font_ref is ok for static font data, but the better way to do - // this is to store the CacheKey. - font_ref: FontRef<'static>, -} - -#[derive(Clone)] -pub struct PietGpuText { - font: Font, -} - -#[derive(Clone)] -pub struct PietGpuTextLayout { - font: Font, - size: f64, - glyphs: Vec, -} - -pub struct PietGpuTextLayoutBuilder { - font: Font, - text: String, - size: f64, -} - -#[derive(Clone, Debug)] -struct Glyph { - glyph_id: GlyphId, - x: f32, - y: f32, -} - -struct TextRenderCtx<'a> { - scaler: Scaler<'a>, -} - -impl PietGpuText { - pub(crate) fn new(font: Font) -> PietGpuText { - PietGpuText { font } - } -} - -impl Text for PietGpuText { - type TextLayout = PietGpuTextLayout; - type TextLayoutBuilder = PietGpuTextLayoutBuilder; - - fn load_font(&mut self, _data: &[u8]) -> Result { - Ok(FontFamily::default()) - } - - fn new_text_layout(&mut self, text: impl TextStorage) -> Self::TextLayoutBuilder { - PietGpuTextLayoutBuilder::new(&self.font, &text.as_str()) - } - - fn font_family(&mut self, _family_name: &str) -> Option { - Some(FontFamily::default()) - } -} - -impl TextLayout for PietGpuTextLayout { - fn size(&self) -> Size { - Size::ZERO - } - - fn image_bounds(&self) -> Rect { - Rect::ZERO - } - - fn line_text(&self, _line_number: usize) -> Option<&str> { - None - } - - fn line_metric(&self, _line_number: usize) -> Option { - None - } - - fn line_count(&self) -> usize { - 0 - } - - fn hit_test_point(&self, _point: Point) -> HitTestPoint { - HitTestPoint::default() - } - - fn hit_test_text_position(&self, _text_position: usize) -> HitTestPosition { - HitTestPosition::default() - } - - fn text(&self) -> &str { - "" - } -} - -impl Font { - pub fn new() -> Font { - let font_ref = FontRef::from_index(FONT_DATA, 0).expect("error parsing font"); - Font { font_ref } - } - - fn make_path<'a>(&self, glyph_id: GlyphId, tc: &mut TextRenderCtx<'a>) -> GlyphEncoder { - let mut encoder = GlyphEncoder::default(); - if tc.scaler.has_color_outlines() { - if let Some(outline) = tc.scaler.scale_color_outline(glyph_id) { - // TODO: be more sophisticated choosing a palette - let palette = self.font_ref.color_palettes().next().unwrap(); - let mut i = 0; - while let Some(layer) = outline.get(i) { - if let Some(color_ix) = layer.color_index() { - let color = palette.get(color_ix); - append_outline(&mut encoder, layer.verbs(), layer.points()); - encoder.fill_color(*bytemuck::from_bytes(&color)); - } - i += 1; - } - return encoder; - } - } - if let Some(outline) = tc.scaler.scale_outline(glyph_id) { - append_outline(&mut encoder, outline.verbs(), outline.points()); - } - encoder - } -} - -impl PietGpuTextLayout { - pub(crate) fn make_layout(font: &Font, text: &str, size: f64) -> PietGpuTextLayout { - let mut glyphs = Vec::new(); - let mut x = 0.0; - let y = 0.0; - for c in text.chars() { - let glyph_id = font.font_ref.charmap().map(c); - let glyph = Glyph { glyph_id, x, y }; - glyphs.push(glyph); - let adv = font.font_ref.glyph_metrics(&[]).advance_width(glyph_id); - x += adv; - } - PietGpuTextLayout { - glyphs, - font: font.clone(), - size, - } - } - - pub(crate) fn draw_text(&self, ctx: &mut PietGpuRenderContext, pos: Point) { - let mut scale_ctx = ScaleContext::new(); - let scaler = scale_ctx.builder(self.font.font_ref).size(2048.).build(); - let mut tc = TextRenderCtx { scaler }; - // Should we use ppem from font, or let swash scale? - const DEFAULT_UPEM: u16 = 2048; - let scale = self.size as f32 / DEFAULT_UPEM as f32; - ctx.save().unwrap(); - // TODO: handle y offsets also - for glyph in &self.glyphs { - let tpos = render_ctx::to_f32_2(pos); - let transform = Transform { - mat: [scale, 0.0, 0.0, -scale], - translate: [tpos[0] + scale * glyph.x, tpos[1]], - }; - //println!("{:?}, {:?}", transform.mat, transform.translate); - ctx.encode_transform(transform); - let glyph = self.font.make_path(glyph.glyph_id, &mut tc); - ctx.encode_glyph(&glyph); - if !glyph.is_color() { - ctx.fill_glyph(0xff_ff_ff_ff); - } - } - ctx.restore().unwrap(); - } -} - -impl PietGpuTextLayoutBuilder { - pub(crate) fn new(font: &Font, text: &str) -> PietGpuTextLayoutBuilder { - PietGpuTextLayoutBuilder { - font: font.clone(), - text: text.to_owned(), - size: 12.0, - } - } -} - -impl TextLayoutBuilder for PietGpuTextLayoutBuilder { - type Out = PietGpuTextLayout; - - fn max_width(self, _width: f64) -> Self { - self - } - - fn alignment(self, _alignment: piet::TextAlignment) -> Self { - self - } - - fn default_attribute(mut self, attribute: impl Into) -> Self { - let attribute = attribute.into(); - match attribute { - TextAttribute::FontSize(size) => self.size = size, - _ => (), - } - self - } - - fn range_attribute( - self, - _range: impl RangeBounds, - _attribute: impl Into, - ) -> Self { - self - } - - fn build(self) -> Result { - Ok(PietGpuTextLayout::make_layout( - &self.font, &self.text, self.size, - )) - } -} - -pub(crate) fn append_outline(encoder: &mut GlyphEncoder, verbs: &[Verb], points: &[Vector]) { - let mut path_encoder = encoder.path_encoder(); - let mut i = 0; - for verb in verbs { - match verb { - Verb::MoveTo => { - let p = points[i]; - path_encoder.move_to(p.x, p.y); - i += 1; - } - Verb::LineTo => { - let p = points[i]; - path_encoder.line_to(p.x, p.y); - i += 1; - } - Verb::QuadTo => { - let p1 = points[i]; - let p2 = points[i + 1]; - path_encoder.quad_to(p1.x, p1.y, p2.x, p2.y); - i += 2; - } - Verb::CurveTo => { - let p1 = points[i]; - let p2 = points[i + 1]; - let p3 = points[i + 2]; - path_encoder.cubic_to(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); - i += 3; - } - Verb::Close => path_encoder.close_path(), - } - } - path_encoder.path(); - let n_pathseg = path_encoder.n_pathseg(); - encoder.finish_path(n_pathseg); -} diff --git a/piet-scene/src/brush/color.rs b/piet-scene/src/brush/color.rs index a377888..59ffae5 100644 --- a/piet-scene/src/brush/color.rs +++ b/piet-scene/src/brush/color.rs @@ -14,6 +14,7 @@ // // Also licensed under MIT license, at your choice. +/// 32-bit RGBA color. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] pub struct Color { pub r: u8, diff --git a/piet-scene/src/brush/gradient.rs b/piet-scene/src/brush/gradient.rs index 161604b..a010ad5 100644 --- a/piet-scene/src/brush/gradient.rs +++ b/piet-scene/src/brush/gradient.rs @@ -15,17 +15,19 @@ // Also licensed under MIT license, at your choice. use super::color::Color; +use super::ExtendMode; use crate::geometry::Point; use smallvec::SmallVec; use std::hash::{Hash, Hasher}; +/// Offset and color of a transition point in a gradient. #[derive(Copy, Clone, PartialOrd, Default, Debug)] -pub struct Stop { +pub struct GradientStop { pub offset: f32, pub color: Color, } -impl Hash for Stop { +impl Hash for GradientStop { fn hash(&self, state: &mut H) { self.offset.to_bits().hash(state); self.color.hash(state); @@ -33,46 +35,46 @@ impl Hash for Stop { } // Override PartialEq to use to_bits for the offset to match with the Hash impl -impl std::cmp::PartialEq for Stop { +impl std::cmp::PartialEq for GradientStop { fn eq(&self, other: &Self) -> bool { self.offset.to_bits() == other.offset.to_bits() && self.color == other.color } } -impl std::cmp::Eq for Stop {} +impl std::cmp::Eq for GradientStop {} -pub type StopVec = SmallVec<[Stop; 4]>; - -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum Extend { - Pad, - Repeat, - Reflect, -} +/// Collection of gradient stops. +pub type GradientStops = SmallVec<[GradientStop; 4]>; +/// Definition of a gradient that transitions between two or more colors along +/// a line. #[derive(Clone, Debug)] pub struct LinearGradient { pub start: Point, pub end: Point, - pub stops: StopVec, - pub extend: Extend, + pub stops: GradientStops, + pub extend: ExtendMode, } +/// Definition of a gradient that transitions between two or more colors that +/// radiate from an origin. #[derive(Clone, Debug)] pub struct RadialGradient { pub center0: Point, pub radius0: f32, pub center1: Point, pub radius1: f32, - pub stops: StopVec, - pub extend: Extend, + pub stops: GradientStops, + pub extend: ExtendMode, } +/// Definition gradient that transitions between two or more colors that rotate +/// around a center point. #[derive(Clone, Debug)] pub struct SweepGradient { pub center: Point, pub start_angle: f32, pub end_angle: f32, - pub stops: StopVec, - pub extend: Extend, + pub stops: GradientStops, + pub extend: ExtendMode, } diff --git a/piet-scene/src/brush/image.rs b/piet-scene/src/brush/image.rs index 07157e7..737ca94 100644 --- a/piet-scene/src/brush/image.rs +++ b/piet-scene/src/brush/image.rs @@ -18,53 +18,35 @@ use std::result::Result; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub enum Format { - A8, - Rgba8, -} - -impl Format { - pub fn data_size(self, width: u32, height: u32) -> Option { - (width as usize) - .checked_mul(height as usize) - .and_then(|size| { - size.checked_mul(match self { - Self::A8 => 1, - Self::Rgba8 => 4, - }) - }) - } -} - +/// Image data resource. #[derive(Clone, Debug)] pub struct Image(Arc); #[derive(Clone, Debug)] struct Inner { id: u64, - format: Format, width: u32, height: u32, - data: Vec, + data: Arc<[u8]>, } impl Image { pub fn new( - format: Format, width: u32, height: u32, - mut data: Vec, - ) -> Result { - let data_size = format.data_size(width, height).ok_or(DataSizeError)?; + data: impl Into>, + ) -> Result { + let data_size = width + .checked_mul(height) + .and_then(|x| x.checked_mul(4)) + .ok_or(ImageDataSizeError)? as usize; + let data = data.into(); if data.len() < data_size { - return Err(DataSizeError); + return Err(ImageDataSizeError); } - data.truncate(data_size); static ID: AtomicU64 = AtomicU64::new(1); Ok(Self(Arc::new(Inner { id: ID.fetch_add(1, Ordering::Relaxed), - format, width, height, data, @@ -75,10 +57,6 @@ impl Image { self.0.id } - pub fn format(&self) -> Format { - self.0.format - } - pub fn width(&self) -> u32 { self.0.width } @@ -92,5 +70,7 @@ impl Image { } } +/// Error returned when image data size is not sufficient for the specified +/// dimensions. #[derive(Clone, Debug)] -pub struct DataSizeError; +pub struct ImageDataSizeError; diff --git a/piet-scene/src/brush/mod.rs b/piet-scene/src/brush/mod.rs index 9cde1fb..1d7a674 100644 --- a/piet-scene/src/brush/mod.rs +++ b/piet-scene/src/brush/mod.rs @@ -22,8 +22,7 @@ pub use color::Color; pub use gradient::*; pub use image::*; -use crate::resource::PersistentBrush; - +/// Describes the content of a filled or stroked shape. #[derive(Clone, Debug)] pub enum Brush { Solid(Color), @@ -31,5 +30,31 @@ pub enum Brush { RadialGradient(RadialGradient), SweepGradient(SweepGradient), Image(Image), - Persistent(PersistentBrush), +} + +/// Defines how a brush is extended when the content does not +/// completely fill a shape. +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum ExtendMode { + Pad, + Repeat, + Reflect, +} + +impl From for Brush { + fn from(c: Color) -> Self { + Self::Solid(c) + } +} + +impl From for Brush { + fn from(g: LinearGradient) -> Self { + Self::LinearGradient(g) + } +} + +impl From for Brush { + fn from(g: RadialGradient) -> Self { + Self::RadialGradient(g) + } } diff --git a/piet-scene/src/geometry.rs b/piet-scene/src/geometry.rs index 2df7f83..dc76fe8 100644 --- a/piet-scene/src/geometry.rs +++ b/piet-scene/src/geometry.rs @@ -201,6 +201,14 @@ impl Rect { } } + /// Creates a new rectangle from an origin point and dimensions. + pub fn from_origin_size(origin: Point, width: f32, height: f32) -> Self { + Self { + min: origin, + max: Point::new(origin.x + width, origin.y + height), + } + } + /// Returns the width of the rectangle. pub fn width(&self) -> f32 { self.max.x - self.min.x diff --git a/piet-scene/src/glyph/mod.rs b/piet-scene/src/glyph.rs similarity index 85% rename from piet-scene/src/glyph/mod.rs rename to piet-scene/src/glyph.rs index f6ebf14..8eb0627 100644 --- a/piet-scene/src/glyph/mod.rs +++ b/piet-scene/src/glyph.rs @@ -14,12 +14,14 @@ // // Also licensed under MIT license, at your choice. +//! Support for glyph rendering. + pub use moscato::pinot; use crate::brush::{Brush, Color}; use crate::geometry::Affine; -use crate::path::Element; -use crate::scene::{build_fragment, Fill, Fragment}; +use crate::path::PathElement; +use crate::scene::{Fill, SceneBuilder, SceneFragment}; use moscato::{Context, Scaler}; use pinot::{types::Tag, FontRef}; @@ -81,28 +83,29 @@ pub struct GlyphProvider<'a> { impl<'a> GlyphProvider<'a> { /// Returns a scene fragment containing the commands to render the /// specified glyph. - pub fn get(&mut self, gid: u16) -> Option { + pub fn get(&mut self, gid: u16, brush: Option<&Brush>) -> Option { let glyph = self.scaler.glyph(gid)?; let path = glyph.path(0)?; - let mut fragment = Fragment::default(); - let mut builder = build_fragment(&mut fragment); + let mut fragment = SceneFragment::default(); + let mut builder = SceneBuilder::for_fragment(&mut fragment); builder.fill( Fill::NonZero, - &Brush::Solid(Color::rgb8(255, 255, 255)), + brush.unwrap_or(&Brush::Solid(Color::rgb8(255, 255, 255))), None, convert_path(path.elements()), ); + builder.finish(); Some(fragment) } /// Returns a scene fragment containing the commands and resources to /// render the specified color glyph. - pub fn get_color(&mut self, palette_index: u16, gid: u16) -> Option { + pub fn get_color(&mut self, palette_index: u16, gid: u16) -> Option { use crate::geometry::*; use moscato::Command; let glyph = self.scaler.color_glyph(palette_index, gid)?; - let mut fragment = Fragment::default(); - let mut builder = build_fragment(&mut fragment); + let mut fragment = SceneFragment::default(); + let mut builder = SceneBuilder::for_fragment(&mut fragment); let mut xform_stack: SmallVec<[Affine; 8]> = SmallVec::new(); for command in glyph.commands() { match command { @@ -171,59 +174,64 @@ impl<'a> GlyphProvider<'a> { ); } } - Command::Fill(brush, brush_xform) => { + Command::Fill(_brush, _brush_xform) => { // TODO: this needs to compute a bounding box for // the parent clips } } } + builder.finish(); Some(fragment) } } fn convert_path( path: impl Iterator + Clone, -) -> impl Iterator + Clone { +) -> impl Iterator + Clone { use crate::geometry::Point; path.map(|el| match el { - moscato::Element::MoveTo(p0) => Element::MoveTo(Point::new(p0.x, p0.y)), - moscato::Element::LineTo(p0) => Element::LineTo(Point::new(p0.x, p0.y)), + moscato::Element::MoveTo(p0) => PathElement::MoveTo(Point::new(p0.x, p0.y)), + moscato::Element::LineTo(p0) => PathElement::LineTo(Point::new(p0.x, p0.y)), moscato::Element::QuadTo(p0, p1) => { - Element::QuadTo(Point::new(p0.x, p0.y), Point::new(p1.x, p1.y)) + PathElement::QuadTo(Point::new(p0.x, p0.y), Point::new(p1.x, p1.y)) } - moscato::Element::CurveTo(p0, p1, p2) => Element::CurveTo( + moscato::Element::CurveTo(p0, p1, p2) => PathElement::CurveTo( Point::new(p0.x, p0.y), Point::new(p1.x, p1.y), Point::new(p2.x, p2.y), ), - moscato::Element::Close => Element::Close, + moscato::Element::Close => PathElement::Close, }) } fn convert_transformed_path( path: impl Iterator + Clone, xform: &Affine, -) -> impl Iterator + Clone { +) -> impl Iterator + Clone { use crate::geometry::Point; let xform = *xform; path.map(move |el| match el { - moscato::Element::MoveTo(p0) => Element::MoveTo(Point::new(p0.x, p0.y).transform(&xform)), - moscato::Element::LineTo(p0) => Element::LineTo(Point::new(p0.x, p0.y).transform(&xform)), - moscato::Element::QuadTo(p0, p1) => Element::QuadTo( + moscato::Element::MoveTo(p0) => { + PathElement::MoveTo(Point::new(p0.x, p0.y).transform(&xform)) + } + moscato::Element::LineTo(p0) => { + PathElement::LineTo(Point::new(p0.x, p0.y).transform(&xform)) + } + moscato::Element::QuadTo(p0, p1) => PathElement::QuadTo( Point::new(p0.x, p0.y).transform(&xform), Point::new(p1.x, p1.y).transform(&xform), ), - moscato::Element::CurveTo(p0, p1, p2) => Element::CurveTo( + moscato::Element::CurveTo(p0, p1, p2) => PathElement::CurveTo( Point::new(p0.x, p0.y).transform(&xform), Point::new(p1.x, p1.y).transform(&xform), Point::new(p2.x, p2.y).transform(&xform), ), - moscato::Element::Close => Element::Close, + moscato::Element::Close => PathElement::Close, }) } -fn convert_blend(mode: moscato::CompositeMode) -> crate::scene::Blend { - use crate::scene::{Blend, Compose, Mix}; +fn convert_blend(mode: moscato::CompositeMode) -> crate::scene::BlendMode { + use crate::scene::{BlendMode, Compose, Mix}; use moscato::CompositeMode; let mut mix = Mix::Normal; let mut compose = Compose::SrcOver; @@ -257,7 +265,7 @@ fn convert_blend(mode: moscato::CompositeMode) -> crate::scene::Blend { CompositeMode::HslColor => mix = Mix::Color, CompositeMode::HslLuminosity => mix = Mix::Luminosity, } - Blend { mix, compose } + BlendMode { mix, compose } } fn convert_transform(xform: &moscato::Transform) -> crate::geometry::Affine { @@ -298,11 +306,11 @@ fn convert_brush(brush: &moscato::Brush) -> crate::brush::Brush { } } -fn convert_stops(stops: &[moscato::ColorStop]) -> crate::brush::StopVec { - use crate::brush::Stop; +fn convert_stops(stops: &[moscato::ColorStop]) -> crate::brush::GradientStops { + use crate::brush::GradientStop; stops .iter() - .map(|stop| Stop { + .map(|stop| GradientStop { offset: stop.offset, color: Color { r: stop.color.r, @@ -314,8 +322,8 @@ fn convert_stops(stops: &[moscato::ColorStop]) -> crate::brush::StopVec { .collect() } -fn convert_extend(extend: moscato::ExtendMode) -> crate::brush::Extend { - use crate::brush::Extend::*; +fn convert_extend(extend: moscato::ExtendMode) -> crate::brush::ExtendMode { + use crate::brush::ExtendMode::*; match extend { moscato::ExtendMode::Pad => Pad, moscato::ExtendMode::Repeat => Repeat, diff --git a/piet-scene/src/lib.rs b/piet-scene/src/lib.rs index 8f436b2..4e0729b 100644 --- a/piet-scene/src/lib.rs +++ b/piet-scene/src/lib.rs @@ -14,19 +14,26 @@ // // Also licensed under MIT license, at your choice. -pub mod brush; -pub mod geometry; +mod brush; +mod geometry; +mod path; +mod resource; +mod scene; + pub mod glyph; -pub mod path; -pub mod resource; -pub mod scene; + +pub use brush::*; +pub use geometry::*; +pub use path::*; +pub use resource::*; +pub use scene::*; /// Implement conversions to and from Kurbo types when the `kurbo` feature is /// enabled. #[cfg(feature = "kurbo")] mod kurbo_conv { use super::geometry::{Affine, Point, Rect}; - use super::path::Element; + use super::path::PathElement; impl Point { /// Creates a new point from the equivalent kurbo type. @@ -90,26 +97,27 @@ mod kurbo_conv { } } - impl Element { + impl PathElement { /// Creates a new path element from the equivalent kurbo type. pub fn from_kurbo(el: kurbo::PathEl) -> Self { use kurbo::PathEl::*; - use Point::from_kurbo; - match e { - MoveTo(p0) => Self::MoveTo(from_kurbo(p0)), - LineTo(p0) => Self::LineTo(from_kurbo(p0)), - QuadTo(p0, p1) => Self::QuadTo(from_kurbo(p0), from_kurbo(p1)), - CurveTo(p0, p1, p2) => { - Self::CurveTo(from_kurbo(p0), from_kurbo(p1), from_kurbo(p2)) - } + match el { + MoveTo(p0) => Self::MoveTo(Point::from_kurbo(p0)), + LineTo(p0) => Self::LineTo(Point::from_kurbo(p0)), + QuadTo(p0, p1) => Self::QuadTo(Point::from_kurbo(p0), Point::from_kurbo(p1)), + CurveTo(p0, p1, p2) => Self::CurveTo( + Point::from_kurbo(p0), + Point::from_kurbo(p1), + Point::from_kurbo(p2), + ), ClosePath => Self::Close, } } } - impl From for kurbo::PathEl { - fn from(e: Element) -> Self { - use Element::*; + impl From for kurbo::PathEl { + fn from(e: PathElement) -> Self { + use PathElement::*; match e { MoveTo(p0) => Self::MoveTo(p0.into()), LineTo(p0) => Self::LineTo(p0.into()), diff --git a/piet-scene/src/path.rs b/piet-scene/src/path.rs index 3ad0ac1..bccf656 100644 --- a/piet-scene/src/path.rs +++ b/piet-scene/src/path.rs @@ -18,7 +18,7 @@ use super::geometry::{Point, Rect}; /// Action of a path element. #[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub enum Verb { +pub enum PathVerb { MoveTo, LineTo, QuadTo, @@ -28,7 +28,7 @@ pub enum Verb { /// Element of a path represented by a verb and its associated points. #[derive(Copy, Clone, PartialEq, Debug)] -pub enum Element { +pub enum PathElement { MoveTo(Point), LineTo(Point), QuadTo(Point, Point), @@ -36,27 +36,27 @@ pub enum Element { Close, } -impl Element { +impl PathElement { /// Returns the verb that describes the action of the path element. - pub fn verb(&self) -> Verb { + pub fn verb(&self) -> PathVerb { match self { - Self::MoveTo(..) => Verb::MoveTo, - Self::LineTo(..) => Verb::LineTo, - Self::QuadTo(..) => Verb::QuadTo, - Self::CurveTo(..) => Verb::CurveTo, - Self::Close => Verb::Close, + Self::MoveTo(..) => PathVerb::MoveTo, + Self::LineTo(..) => PathVerb::LineTo, + Self::QuadTo(..) => PathVerb::QuadTo, + Self::CurveTo(..) => PathVerb::CurveTo, + Self::Close => PathVerb::Close, } } } impl Rect { - pub fn elements(&self) -> impl Iterator + Clone { + pub fn elements(&self) -> impl Iterator + Clone { let elements = [ - Element::MoveTo((self.min.x, self.min.y).into()), - Element::LineTo((self.max.x, self.min.y).into()), - Element::LineTo((self.max.x, self.max.y).into()), - Element::LineTo((self.min.x, self.max.y).into()), - Element::Close, + PathElement::MoveTo((self.min.x, self.min.y).into()), + PathElement::LineTo((self.max.x, self.min.y).into()), + PathElement::LineTo((self.max.x, self.max.y).into()), + PathElement::LineTo((self.min.x, self.max.y).into()), + PathElement::Close, ]; (0..5).map(move |i| elements[i]) } diff --git a/piet-scene/src/resource/gradient.rs b/piet-scene/src/resource/gradient.rs index 39b7f40..8a23216 100644 --- a/piet-scene/src/resource/gradient.rs +++ b/piet-scene/src/resource/gradient.rs @@ -1,4 +1,4 @@ -use crate::brush::{Color, Stop, StopVec}; +use crate::brush::{Color, GradientStop, GradientStops}; use std::collections::HashMap; const N_SAMPLES: usize = 512; @@ -7,15 +7,11 @@ const RETAINED_COUNT: usize = 64; #[derive(Default)] pub struct RampCache { epoch: u64, - map: HashMap, + map: HashMap, data: Vec, } impl RampCache { - pub fn new() -> Self { - Self::default() - } - pub fn advance(&mut self) { self.epoch += 1; if self.map.len() > RETAINED_COUNT { @@ -31,7 +27,7 @@ impl RampCache { self.data.clear(); } - pub fn add(&mut self, stops: &[Stop]) -> u32 { + pub fn add(&mut self, stops: &[GradientStop]) -> u32 { if let Some(entry) = self.map.get_mut(stops) { entry.1 = self.epoch; entry.0 @@ -73,7 +69,7 @@ impl RampCache { } } -fn make_ramp<'a>(stops: &'a [Stop]) -> impl Iterator + 'a { +fn make_ramp<'a>(stops: &'a [GradientStop]) -> impl Iterator + 'a { let mut last_u = 0.0; let mut last_c = ColorF64::from_color(stops[0].color); let mut this_u = last_u; diff --git a/piet-scene/src/resource/mod.rs b/piet-scene/src/resource/mod.rs index a1ea58b..2e6b2d6 100644 --- a/piet-scene/src/resource/mod.rs +++ b/piet-scene/src/resource/mod.rs @@ -1,14 +1,12 @@ mod gradient; -use crate::brush::{Brush, Stop}; +use crate::brush::GradientStop; use gradient::RampCache; -use std::collections::HashMap; /// Context for caching resources across rendering operations. #[derive(Default)] pub struct ResourceContext { ramps: RampCache, - persistent_map: HashMap, } impl ResourceContext { @@ -22,35 +20,13 @@ impl ResourceContext { pub fn clear(&mut self) { self.ramps.clear(); - self.persistent_map.clear(); } - pub fn add_ramp(&mut self, stops: &[Stop]) -> u32 { + pub fn add_ramp(&mut self, stops: &[GradientStop]) -> u32 { self.ramps.add(stops) } - pub fn create_brush(&mut self, brush: &Brush) -> PersistentBrush { - match brush { - Brush::Persistent(dup) => return *dup, - _ => {} - } - PersistentBrush { kind: 0, id: 0 } - } - - pub fn destroy_brush(&mut self, brush: PersistentBrush) {} - pub fn ramp_data(&self) -> &[u32] { &self.ramps.data() } } - -/// Handle for a brush that is managed by the resource context. -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] -pub struct PersistentBrush { - kind: u8, - id: u64, -} - -struct PersistentBrushData { - brush: Brush, -} diff --git a/piet-scene/src/scene/blend.rs b/piet-scene/src/scene/blend.rs index d6aa080..43ecfa7 100644 --- a/piet-scene/src/scene/blend.rs +++ b/piet-scene/src/scene/blend.rs @@ -60,12 +60,12 @@ pub enum Compose { /// Blend mode consisting of mixing and composition functions. #[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub struct Blend { +pub struct BlendMode { pub mix: Mix, pub compose: Compose, } -impl Blend { +impl BlendMode { pub fn new(mix: Mix, compose: Compose) -> Self { Self { mix, compose } } @@ -75,7 +75,7 @@ impl Blend { } } -impl Default for Blend { +impl Default for BlendMode { fn default() -> Self { Self { mix: Mix::Clip, @@ -84,7 +84,7 @@ impl Default for Blend { } } -impl From for Blend { +impl From for BlendMode { fn from(mix: Mix) -> Self { Self { mix, @@ -93,7 +93,7 @@ impl From for Blend { } } -impl From for Blend { +impl From for BlendMode { fn from(compose: Compose) -> Self { Self { mix: Mix::Normal, diff --git a/piet-scene/src/scene/builder.rs b/piet-scene/src/scene/builder.rs index 5394f88..37befac 100644 --- a/piet-scene/src/scene/builder.rs +++ b/piet-scene/src/scene/builder.rs @@ -15,37 +15,39 @@ // Also licensed under MIT license, at your choice. use super::style::{Fill, Stroke}; -use super::{Affine, Blend, Element, Fragment, FragmentResources, ResourcePatch, Scene, SceneData}; +use super::{ + Affine, BlendMode, FragmentResources, PathElement, ResourcePatch, Scene, SceneData, + SceneFragment, +}; use crate::brush::*; use crate::resource::ResourceContext; use bytemuck::{Pod, Zeroable}; use core::borrow::Borrow; - -const MAX_BLEND_STACK: usize = 256; - -/// Creates a new builder for filling a scene. Any current content in the scene -/// will be cleared. -pub fn build_scene<'a>(scene: &'a mut Scene, rcx: &'a mut ResourceContext) -> Builder<'a> { - Builder::new(&mut scene.data, ResourceData::Scene(rcx)) -} - -/// Creates a new builder for filling a scene fragment. Any current content in -/// the fragment will be cleared. -pub fn build_fragment<'a>(fragment: &'a mut Fragment) -> Builder<'a> { - Builder::new( - &mut fragment.data, - ResourceData::Fragment(&mut fragment.resources), - ) -} +use smallvec::SmallVec; /// Builder for constructing a scene or scene fragment. -pub struct Builder<'a> { +pub struct SceneBuilder<'a> { scene: &'a mut SceneData, resources: ResourceData<'a>, - layers: Vec, + layers: SmallVec<[BlendMode; 8]>, } -impl<'a> Builder<'a> { +impl<'a> SceneBuilder<'a> { + /// Creates a new builder for filling a scene. Any current content in the scene + /// will be cleared. + pub fn for_scene(scene: &'a mut Scene, rcx: &'a mut ResourceContext) -> Self { + Self::new(&mut scene.data, ResourceData::Scene(rcx)) + } + + /// Creates a new builder for filling a scene fragment. Any current content in + /// the fragment will be cleared. + pub fn for_fragment(fragment: &'a mut SceneFragment) -> Self { + Self::new( + &mut fragment.data, + ResourceData::Fragment(&mut fragment.resources), + ) + } + /// Creates a new builder for constructing a scene. fn new(scene: &'a mut SceneData, mut resources: ResourceData<'a>) -> Self { let is_fragment = match resources { @@ -57,30 +59,29 @@ impl<'a> Builder<'a> { Self { scene, resources, - layers: vec![], + layers: Default::default(), } } /// Sets the current transformation. pub fn transform(&mut self, transform: Affine) { - self.encode_transform(transform); + 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: Blend, elements: E) + pub fn push_layer<'s, E>(&mut self, blend: BlendMode, elements: E) where E: IntoIterator, E::IntoIter: Clone, - E::Item: Borrow, + E::Item: Borrow, { self.linewidth(-1.0); let elements = elements.into_iter(); self.encode_path(elements, true); self.begin_clip(Some(blend)); - if self.layers.len() >= MAX_BLEND_STACK { - panic!("Maximum clip/blend stack size {} exceeded", MAX_BLEND_STACK); - } self.layers.push(blend); } @@ -94,32 +95,33 @@ impl<'a> Builder<'a> { /// Fills a shape using the specified style and brush. pub fn fill<'s, E>( &mut self, - style: Fill, + _style: Fill, brush: &Brush, brush_transform: Option, elements: E, ) where E: IntoIterator, E::IntoIter: Clone, - E::Item: Borrow, + E::Item: Borrow, { self.linewidth(-1.0); let elements = elements.into_iter(); - 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); + 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); + } } else { - self.encode_transform(brush_transform); - self.swap_last_tags(); self.encode_brush(brush); - self.encode_transform(Affine::IDENTITY); } - } else { - self.encode_brush(brush); } } @@ -134,37 +136,38 @@ impl<'a> Builder<'a> { D: Borrow<[f32]>, E: IntoIterator, E::IntoIter: Clone, - E::Item: Borrow, + E::Item: Borrow, { self.linewidth(style.width); let elements = elements.into_iter(); - 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); + 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); + } } else { - self.encode_transform(brush_transform); - self.swap_last_tags(); self.encode_brush(brush); - self.encode_transform(Affine::IDENTITY); } - } else { - self.encode_brush(brush); } } /// Appends a fragment to the scene. - pub fn append(&mut self, fragment: &Fragment, transform: Option) { + pub fn append(&mut self, fragment: &SceneFragment, transform: Option) { let drawdata_base = self.scene.drawdata_stream.len(); 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.encode_transform(transform); + self.transform(transform); } else if cur_transform != Some(Affine::IDENTITY) { self.encode_transform(Affine::IDENTITY); } @@ -204,7 +207,7 @@ impl<'a> Builder<'a> { } // Prevent fragments from affecting transform state. Should we allow this? if let Some(transform) = cur_transform { - self.encode_transform(transform); + self.transform(transform); } } @@ -216,11 +219,11 @@ impl<'a> Builder<'a> { } } -impl<'a> Builder<'a> { - fn encode_path(&mut self, elements: E, is_fill: bool) +impl<'a> SceneBuilder<'a> { + fn encode_path(&mut self, elements: E, is_fill: bool) -> bool where E: Iterator, - E::Item: Borrow, + E::Item: Borrow, { if is_fill { self.encode_path_inner( @@ -228,34 +231,39 @@ impl<'a> Builder<'a> { .map(|el| *el.borrow()) .flat_map(|el| { match el { - Element::MoveTo(..) => Some(Element::Close), + PathElement::MoveTo(..) => Some(PathElement::Close), _ => None, } .into_iter() .chain(Some(el)) }) - .chain(Some(Element::Close)), + .chain(Some(PathElement::Close)), ) } else { self.encode_path_inner(elements.map(|el| *el.borrow())) } } - fn encode_path_inner(&mut self, elements: impl Iterator) { + fn encode_path_inner(&mut self, elements: impl Iterator) -> bool { let mut b = PathBuilder::new(&mut self.scene.tag_stream, &mut self.scene.pathseg_stream); + let mut has_els = false; for el in elements { match el { - Element::MoveTo(p0) => b.move_to(p0.x, p0.y), - Element::LineTo(p0) => b.line_to(p0.x, p0.y), - Element::QuadTo(p0, p1) => b.quad_to(p0.x, p0.y, p1.x, p1.y), - Element::CurveTo(p0, p1, p2) => b.cubic_to(p0.x, p0.y, p1.x, p1.y, p2.x, p2.y), - Element::Close => b.close_path(), + PathElement::MoveTo(p0) => b.move_to(p0.x, p0.y), + PathElement::LineTo(p0) => b.line_to(p0.x, p0.y), + PathElement::QuadTo(p0, p1) => b.quad_to(p0.x, p0.y, p1.x, p1.y), + PathElement::CurveTo(p0, p1, p2) => b.cubic_to(p0.x, p0.y, p1.x, p1.y, p2.x, p2.y), + PathElement::Close => b.close_path(), } + has_els = true; } - b.path(); - let n_pathseg = b.n_pathseg(); - self.scene.n_path += 1; - self.scene.n_pathseg += n_pathseg; + if has_els { + b.path(); + let n_pathseg = b.n_pathseg(); + self.scene.n_path += 1; + self.scene.n_pathseg += n_pathseg; + } + has_els } fn encode_transform(&mut self, transform: Affine) { @@ -272,8 +280,10 @@ impl<'a> Builder<'a> { // -1.0 means "fill" fn linewidth(&mut self, linewidth: f32) { - self.scene.tag_stream.push(0x40); - self.scene.linewidth_stream.push(linewidth); + if self.scene.linewidth_stream.last() != Some(&linewidth) { + self.scene.tag_stream.push(0x40); + self.scene.linewidth_stream.push(linewidth); + } } fn encode_brush(&mut self, brush: &Brush) { @@ -311,11 +321,10 @@ impl<'a> Builder<'a> { } Brush::SweepGradient(_gradient) => todo!("sweep gradients aren't done yet!"), Brush::Image(_image) => todo!("images aren't done yet!"), - Brush::Persistent(_) => todo!("persistent brushes aren't done yet!"), } } - fn add_ramp(&mut self, stops: &[Stop]) -> u32 { + fn add_ramp(&mut self, stops: &[GradientStop]) -> u32 { match &mut self.resources { ResourceData::Scene(res) => res.add_ramp(stops), ResourceData::Fragment(res) => { @@ -332,10 +341,10 @@ impl<'a> Builder<'a> { } /// Start a clip. - fn begin_clip(&mut self, blend: Option) { + fn begin_clip(&mut self, blend: Option) { self.scene.drawtag_stream.push(DRAWTAG_BEGINCLIP); let element = Clip { - blend: blend.unwrap_or(Blend::default()).pack(), + blend: blend.unwrap_or(BlendMode::default()).pack(), }; self.scene .drawdata_stream @@ -343,10 +352,10 @@ impl<'a> Builder<'a> { self.scene.n_clip += 1; } - fn end_clip(&mut self, blend: Option) { + fn end_clip(&mut self, blend: Option) { self.scene.drawtag_stream.push(DRAWTAG_ENDCLIP); let element = Clip { - blend: blend.unwrap_or(Blend::default()).pack(), + blend: blend.unwrap_or(BlendMode::default()).pack(), }; self.scene .drawdata_stream @@ -357,7 +366,6 @@ impl<'a> Builder<'a> { self.scene.n_clip += 1; } } - enum ResourceData<'a> { Fragment(&'a mut FragmentResources), Scene(&'a mut ResourceContext), diff --git a/piet-scene/src/scene/mod.rs b/piet-scene/src/scene/mod.rs index 5f0e77f..33abe57 100644 --- a/piet-scene/src/scene/mod.rs +++ b/piet-scene/src/scene/mod.rs @@ -18,13 +18,13 @@ mod blend; mod builder; mod style; -pub use blend::{Blend, Compose, Mix}; -pub use builder::{build_fragment, build_scene, Builder}; +pub use blend::{BlendMode, Compose, Mix}; +pub use builder::SceneBuilder; pub use style::*; use super::brush::*; use super::geometry::{Affine, Point}; -use super::path::Element; +use super::path::PathElement; use core::ops::Range; @@ -43,6 +43,10 @@ pub struct SceneData { } impl SceneData { + fn is_empty(&self) -> bool { + self.pathseg_stream.is_empty() + } + fn reset(&mut self, is_fragment: bool) { self.transform_stream.clear(); self.tag_stream.clear(); @@ -97,23 +101,32 @@ impl Scene { /// Encoded definition of a scene fragment and associated resources. #[derive(Default)] -pub struct Fragment { +pub struct SceneFragment { data: SceneData, resources: FragmentResources, } -impl Fragment { +impl SceneFragment { + /// Returns true if the fragment does not contain any paths. + pub fn is_empty(&self) -> bool { + self.data.is_empty() + } + /// Returns the underlying stream of points that defined all encoded path /// segments. pub fn points(&self) -> &[Point] { - bytemuck::cast_slice(&self.data.pathseg_stream) + if self.is_empty() { + &[] + } else { + bytemuck::cast_slice(&self.data.pathseg_stream) + } } } #[derive(Default)] struct FragmentResources { patches: Vec, - stops: Vec, + stops: Vec, } enum ResourcePatch {