Remove piet API & replace w/ fragments

Removes the dependency on the piet crate and replaces all uses with the scene crate.  Also does some cleanup of the scene API, renaming some types and moving them all to the crate root for better ergonomics.
This commit is contained in:
Chad Brokaw 2022-08-11 15:29:15 -04:00
parent 9dd4559b00
commit 2e8781fbb6
34 changed files with 910 additions and 2389 deletions

93
Cargo.lock generated
View file

@ -589,12 +589,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"
@ -873,33 +867,23 @@ 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",
"clap",
"kurbo 0.8.3",
"ndk 0.3.0",
"ndk-glue 0.3.0",
"ndk-sys",
"piet",
"piet-gpu-hal",
"piet-gpu-types",
"piet-scene",
"png",
"rand",
"raw-window-handle 0.3.4",
"roxmltree",
"swash",
"winit",
]
@ -1219,16 +1203,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.98"
@ -1292,57 +1266,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.1"
@ -1654,15 +1577,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"

View file

@ -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<PgpuTransform> for PgpuAffine {
impl From<PgpuTransform> for piet_scene::Affine {
fn from(xform: PgpuTransform) -> Self {
Self {
xx: xform.xx,
@ -212,8 +210,6 @@ impl From<PgpuTransform> 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<Self::Item> {
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,

View file

@ -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<PgpuGlyph> {
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 {

View file

@ -26,15 +26,18 @@ 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.16.2"
rand = "0.7.3"
roxmltree = "0.13"
winit = "0.26.1"
clap = "2.33"
swash = "0.1.4"
bytemuck = { version = "1.7.2", features = ["derive"] }
kurbo = "0.8.3"
[target.'cfg(target_os = "android")'.dependencies]
ndk = "0.3"

View file

@ -17,10 +17,8 @@ use piet_gpu_hal::{
SubmittedCmdBuf, 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() {
@ -134,14 +132,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();
@ -173,12 +172,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,
);
}

View file

@ -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)?;

View file

@ -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};
@ -59,6 +57,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 surface = instance.surface(&window)?;
let device = instance.device()?;
@ -72,7 +73,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
@ -87,8 +88,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)
}
_ => {}
}
}
@ -107,53 +112,36 @@ 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);
} 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) {
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 {
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();
let swap_image = swapchain.image(image_idx);
@ -193,12 +181,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,
);
}

View file

@ -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<BlendMode> for Blend {
fn from(mode: BlendMode) -> Self {
Self {
mode,
composition_mode: CompositionMode::SrcOver,
}
}
}
impl From<CompositionMode> for Blend {
fn from(mode: CompositionMode) -> Self {
Self {
mode: BlendMode::Normal,
composition_mode: mode,
}
}
}

View file

@ -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<stages::Transform>,
tag_stream: Vec<u8>,
pathseg_stream: Vec<u8>,
linewidth_stream: Vec<f32>,
drawtag_stream: Vec<u32>,
drawdata_stream: Vec<u8>,
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<u8>,
pathseg_stream: Vec<u8>,
drawtag_stream: Vec<u32>,
drawdata_stream: Vec<u8>,
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<Blend>) {
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<Blend>) {
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()
}
}

View file

@ -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
}
}

View file

@ -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<GradientStop>,
}
#[derive(Clone)]
pub struct BakedGradient {
ramp: Vec<u32>,
}
#[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<GradientRamp>,
map: HashMap<GradientRamp, usize>,
}
#[derive(Clone, Hash, PartialEq, Eq)]
struct GradientRamp(Vec<u32>);
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<u32> {
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);
}
}

View file

@ -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::<Result<Vec<_>, _>>()?;
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<T: Copy + Pod>(
&mut self,
scene: &EncodedSceneRef<T>,
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<Image, Error> {
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)

View file

@ -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<Item>,
pub items: Vec<Item>,
}
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
}

View file

@ -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<Element>,
// 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<State>,
clip_stack: Vec<ClipElement>,
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<Blend>,
}
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<u32> {
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<FixedGradient>) -> Result<Self::Brush, Error> {
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<Self>, 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<Self>,
_width: f64,
_style: &StrokeStyle,
) {
}
fn fill(&mut self, shape: impl Shape, brush: &impl IntoBrush<Self>) {
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<Self>) {}
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<Point>) {
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<Self::Image, Error> {
Ok(PietGpuImage)
}
fn draw_image(
&mut self,
_image: &Self::Image,
_rect: impl Into<Rect>,
_interp: InterpolationMode,
) {
}
fn draw_image_area(
&mut self,
_image: &Self::Image,
_src_rect: impl Into<Rect>,
_dst_rect: impl Into<Rect>,
_interp: InterpolationMode,
) {
}
fn blurred_rect(&mut self, _rect: Rect, _blur_radius: f64, _brush: &impl IntoBrush<Self>) {}
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<Item = PathEl>, 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<Item = PathEl>) {
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<PietGpuRenderContext> 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
}
}

View file

@ -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<T: Copy + Pod>(
&mut self,
session: &Session,
scene: &EncodedSceneRef<T>,
) -> 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> {

368
piet-gpu/src/samples.rs Normal file
View file

@ -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<Item = PathElement> + 'a + Clone {
path.elements()
.iter()
.map(|el| PathElement::from_kurbo(*el))
}
fn make_ellipse(cx: f32, cy: f32, rx: f32, ry: f32) -> impl Iterator<Item = PathElement> + 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<Item = PathElement> + 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,
}
}

View file

@ -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;
}
}
}
}
}

View file

@ -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,
])
}
}

View file

@ -1,332 +0,0 @@
//! Various synthetic scenes for exercising the renderer.
use rand::{Rng, RngCore};
use crate::{Blend, BlendMode, Colrv1RadialGradient, CompositionMode, 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);
}

View file

@ -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<Glyph>,
}
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<FontFamily, Error> {
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<FontFamily> {
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<LineMetric> {
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<TextAttribute>) -> Self {
let attribute = attribute.into();
match attribute {
TextAttribute::FontSize(size) => self.size = size,
_ => (),
}
self
}
fn range_attribute(
self,
_range: impl RangeBounds<usize>,
_attribute: impl Into<TextAttribute>,
) -> Self {
self
}
fn build(self) -> Result<Self::Out, Error> {
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);
}

View file

@ -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,

View file

@ -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<H: Hasher>(&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,
}

View file

@ -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<usize> {
(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<Inner>);
#[derive(Clone, Debug)]
struct Inner {
id: u64,
format: Format,
width: u32,
height: u32,
data: Vec<u8>,
data: Arc<[u8]>,
}
impl Image {
pub fn new(
format: Format,
width: u32,
height: u32,
mut data: Vec<u8>,
) -> Result<Self, DataSizeError> {
let data_size = format.data_size(width, height).ok_or(DataSizeError)?;
data: impl Into<Arc<[u8]>>,
) -> Result<Self, ImageDataSizeError> {
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;

View file

@ -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<Color> for Brush {
fn from(c: Color) -> Self {
Self::Solid(c)
}
}
impl From<LinearGradient> for Brush {
fn from(g: LinearGradient) -> Self {
Self::LinearGradient(g)
}
}
impl From<RadialGradient> for Brush {
fn from(g: RadialGradient) -> Self {
Self::RadialGradient(g)
}
}

View file

@ -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

View file

@ -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<Fragment> {
pub fn get(&mut self, gid: u16, brush: Option<&Brush>) -> Option<SceneFragment> {
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<Fragment> {
pub fn get_color(&mut self, palette_index: u16, gid: u16) -> Option<SceneFragment> {
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<Item = moscato::Element> + Clone,
) -> impl Iterator<Item = Element> + Clone {
) -> impl Iterator<Item = PathElement> + 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<Item = moscato::Element> + Clone,
xform: &Affine,
) -> impl Iterator<Item = Element> + Clone {
) -> impl Iterator<Item = PathElement> + 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,

View file

@ -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<Element> for kurbo::PathEl {
fn from(e: Element) -> Self {
use Element::*;
impl From<PathElement> for kurbo::PathEl {
fn from(e: PathElement) -> Self {
use PathElement::*;
match e {
MoveTo(p0) => Self::MoveTo(p0.into()),
LineTo(p0) => Self::LineTo(p0.into()),

View file

@ -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<Item = Element> + Clone {
pub fn elements(&self) -> impl Iterator<Item = PathElement> + 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])
}

View file

@ -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<StopVec, (u32, u64)>,
map: HashMap<GradientStops, (u32, u64)>,
data: Vec<u32>,
}
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<Item = u32> + 'a {
fn make_ramp<'a>(stops: &'a [GradientStop]) -> impl Iterator<Item = u32> + 'a {
let mut last_u = 0.0;
let mut last_c = ColorF64::from_color(stops[0].color);
let mut this_u = last_u;

View file

@ -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<u64, PersistentBrushData>,
}
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,
}

View file

@ -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<Mix> for Blend {
impl From<Mix> for BlendMode {
fn from(mix: Mix) -> Self {
Self {
mix,
@ -93,7 +93,7 @@ impl From<Mix> for Blend {
}
}
impl From<Compose> for Blend {
impl From<Compose> for BlendMode {
fn from(compose: Compose) -> Self {
Self {
mix: Mix::Normal,

View file

@ -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;
use smallvec::SmallVec;
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))
/// Builder for constructing a scene or scene fragment.
pub struct SceneBuilder<'a> {
scene: &'a mut SceneData,
resources: ResourceData<'a>,
layers: SmallVec<[BlendMode; 8]>,
}
/// 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(
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),
)
}
}
/// Builder for constructing a scene or scene fragment.
pub struct Builder<'a> {
scene: &'a mut SceneData,
resources: ResourceData<'a>,
layers: Vec<Blend>,
}
impl<'a> Builder<'a> {
/// 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) {
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<Element>,
E::Item: Borrow<PathElement>,
{
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,18 +95,18 @@ 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<Affine>,
elements: E,
) where
E: IntoIterator,
E::IntoIter: Clone,
E::Item: Borrow<Element>,
E::Item: Borrow<PathElement>,
{
self.linewidth(-1.0);
let elements = elements.into_iter();
self.encode_path(elements, true);
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);
@ -122,6 +123,7 @@ impl<'a> Builder<'a> {
self.encode_brush(brush);
}
}
}
/// Strokes a shape using the specified style and brush.
pub fn stroke<'s, D, E>(
@ -134,11 +136,11 @@ impl<'a> Builder<'a> {
D: Borrow<[f32]>,
E: IntoIterator,
E::IntoIter: Clone,
E::Item: Borrow<Element>,
E::Item: Borrow<PathElement>,
{
self.linewidth(style.width);
let elements = elements.into_iter();
self.encode_path(elements, false);
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);
@ -155,16 +157,17 @@ impl<'a> Builder<'a> {
self.encode_brush(brush);
}
}
}
/// Appends a fragment to the scene.
pub fn append(&mut self, fragment: &Fragment, transform: Option<Affine>) {
pub fn append(&mut self, fragment: &SceneFragment, transform: Option<Affine>) {
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<E>(&mut self, elements: E, is_fill: bool)
impl<'a> SceneBuilder<'a> {
fn encode_path<E>(&mut self, elements: E, is_fill: bool) -> bool
where
E: Iterator,
E::Item: Borrow<Element>,
E::Item: Borrow<PathElement>,
{
if is_fill {
self.encode_path_inner(
@ -228,35 +231,40 @@ 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<Item = Element>) {
fn encode_path_inner(&mut self, elements: impl Iterator<Item = PathElement>) -> 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;
}
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) {
self.scene.tag_stream.push(0x20);
@ -272,9 +280,11 @@ impl<'a> Builder<'a> {
// -1.0 means "fill"
fn linewidth(&mut self, linewidth: f32) {
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) {
match 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<Blend>) {
fn begin_clip(&mut self, blend: Option<BlendMode>) {
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<Blend>) {
fn end_clip(&mut self, blend: Option<BlendMode>) {
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),

View file

@ -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] {
if self.is_empty() {
&[]
} else {
bytemuck::cast_slice(&self.data.pathseg_stream)
}
}
}
#[derive(Default)]
struct FragmentResources {
patches: Vec<ResourcePatch>,
stops: Vec<Stop>,
stops: Vec<GradientStop>,
}
enum ResourcePatch {

View file

@ -17,7 +17,7 @@
//! Tests for the piet-gpu draw object stage.
use piet_gpu_hal::{BufWrite, BufferUsage};
use rand::{seq::SliceRandom, Rng};
use rand::seq::SliceRandom;
use crate::{Config, Runner, TestResult};