mirror of
https://github.com/italicsjenga/vello.git
synced 2025-01-10 20:51:29 +11:00
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:
parent
9dd4559b00
commit
2e8781fbb6
93
Cargo.lock
generated
93
Cargo.lock
generated
|
@ -589,12 +589,6 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "matches"
|
|
||||||
version = "0.1.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
|
@ -873,33 +867,23 @@ dependencies = [
|
||||||
"piet-scene",
|
"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]]
|
[[package]]
|
||||||
name = "piet-gpu"
|
name = "piet-gpu"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"clap",
|
"clap",
|
||||||
|
"kurbo 0.8.3",
|
||||||
"ndk 0.3.0",
|
"ndk 0.3.0",
|
||||||
"ndk-glue 0.3.0",
|
"ndk-glue 0.3.0",
|
||||||
"ndk-sys",
|
"ndk-sys",
|
||||||
"piet",
|
|
||||||
"piet-gpu-hal",
|
"piet-gpu-hal",
|
||||||
"piet-gpu-types",
|
"piet-gpu-types",
|
||||||
|
"piet-scene",
|
||||||
"png",
|
"png",
|
||||||
"rand",
|
"rand",
|
||||||
"raw-window-handle 0.3.4",
|
"raw-window-handle 0.3.4",
|
||||||
"roxmltree",
|
"roxmltree",
|
||||||
"swash",
|
|
||||||
"winit",
|
"winit",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1219,16 +1203,6 @@ version = "0.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
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]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.98"
|
version = "1.0.98"
|
||||||
|
@ -1292,57 +1266,6 @@ dependencies = [
|
||||||
"serde",
|
"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]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
@ -1654,15 +1577,3 @@ name = "xmlparser"
|
||||||
version = "0.13.3"
|
version = "0.13.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "114ba2b24d2167ef6d67d7d04c8cc86522b87f490025f39f0303b7db5bf5e3d8"
|
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"
|
|
||||||
|
|
|
@ -26,9 +26,7 @@
|
||||||
|
|
||||||
mod render;
|
mod render;
|
||||||
|
|
||||||
use piet_scene::brush::{Brush, Color};
|
use piet_scene::{Brush, Color, Fill, PathElement};
|
||||||
use piet_scene::path::Element;
|
|
||||||
use piet_scene::scene::Fill;
|
|
||||||
use render::*;
|
use render::*;
|
||||||
use std::ffi::c_void;
|
use std::ffi::c_void;
|
||||||
use std::mem::transmute;
|
use std::mem::transmute;
|
||||||
|
@ -199,7 +197,7 @@ pub struct PgpuTransform {
|
||||||
pub dy: f32,
|
pub dy: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<PgpuTransform> for PgpuAffine {
|
impl From<PgpuTransform> for piet_scene::Affine {
|
||||||
fn from(xform: PgpuTransform) -> Self {
|
fn from(xform: PgpuTransform) -> Self {
|
||||||
Self {
|
Self {
|
||||||
xx: xform.xx,
|
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
|
/// Creates a new builder for filling a piet-gpu scene. The specified scene
|
||||||
/// should not be accessed while the builder is live.
|
/// should not be accessed while the builder is live.
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
|
@ -243,7 +239,7 @@ pub unsafe extern "C" fn pgpu_scene_builder_add_glyph(
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Iterator for PgpuPathIter {
|
impl Iterator for PgpuPathIter {
|
||||||
type Item = piet_scene::path::Element;
|
type Item = PathElement;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
let mut el = PgpuPathElement {
|
let mut el = PgpuPathElement {
|
||||||
|
@ -253,17 +249,17 @@ impl Iterator for PgpuPathIter {
|
||||||
if (self.next_element)(self.context, &mut el as _) {
|
if (self.next_element)(self.context, &mut el as _) {
|
||||||
let p = &el.points;
|
let p = &el.points;
|
||||||
Some(match el.verb {
|
Some(match el.verb {
|
||||||
PgpuPathVerb::MoveTo => Element::MoveTo((p[0].x, p[0].y).into()),
|
PgpuPathVerb::MoveTo => PathElement::MoveTo((p[0].x, p[0].y).into()),
|
||||||
PgpuPathVerb::LineTo => Element::LineTo((p[0].x, p[0].y).into()),
|
PgpuPathVerb::LineTo => PathElement::LineTo((p[0].x, p[0].y).into()),
|
||||||
PgpuPathVerb::QuadTo => {
|
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[0].x, p[0].y).into(),
|
||||||
(p[1].x, p[1].y).into(),
|
(p[1].x, p[1].y).into(),
|
||||||
(p[2].x, p[2].y).into(),
|
(p[2].x, p[2].y).into(),
|
||||||
),
|
),
|
||||||
PgpuPathVerb::Close => Element::Close,
|
PgpuPathVerb::Close => PathElement::Close,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -445,7 +441,7 @@ pub unsafe extern "C" fn pgpu_glyph_bbox(
|
||||||
glyph: *const PgpuGlyph,
|
glyph: *const PgpuGlyph,
|
||||||
transform: &[f32; 6],
|
transform: &[f32; 6],
|
||||||
) -> PgpuRect {
|
) -> PgpuRect {
|
||||||
let transform = piet_scene::geometry::Affine::new(transform);
|
let transform = piet_scene::Affine::new(transform);
|
||||||
let rect = (*glyph).bbox(Some(transform));
|
let rect = (*glyph).bbox(Some(transform));
|
||||||
PgpuRect {
|
PgpuRect {
|
||||||
x0: rect.min.x,
|
x0: rect.min.x,
|
||||||
|
|
|
@ -14,13 +14,11 @@
|
||||||
//
|
//
|
||||||
// Also licensed under MIT license, at your choice.
|
// 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_gpu_hal::{QueryPool, Session};
|
||||||
use piet_scene::geometry::{Affine, Rect};
|
|
||||||
use piet_scene::glyph::pinot::{types::Tag, FontDataRef};
|
use piet_scene::glyph::pinot::{types::Tag, FontDataRef};
|
||||||
use piet_scene::glyph::{GlyphContext, GlyphProvider};
|
use piet_scene::glyph::{GlyphContext, GlyphProvider};
|
||||||
use piet_scene::resource::ResourceContext;
|
use piet_scene::{Affine, Rect, ResourceContext, Scene, SceneFragment};
|
||||||
use piet_scene::scene::{Fragment, Scene};
|
|
||||||
|
|
||||||
/// State and resources for rendering a scene.
|
/// State and resources for rendering a scene.
|
||||||
pub struct PgpuRenderer {
|
pub struct PgpuRenderer {
|
||||||
|
@ -120,47 +118,31 @@ impl PgpuScene {
|
||||||
|
|
||||||
pub fn builder(&mut self) -> PgpuSceneBuilder {
|
pub fn builder(&mut self) -> PgpuSceneBuilder {
|
||||||
self.rcx.advance();
|
self.rcx.advance();
|
||||||
PgpuSceneBuilder(piet_scene::scene::build_scene(
|
PgpuSceneBuilder(piet_scene::SceneBuilder::for_scene(
|
||||||
&mut self.scene,
|
&mut self.scene,
|
||||||
&mut self.rcx,
|
&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.
|
/// Encoded streams and resources describing a vector graphics scene fragment.
|
||||||
pub struct PgpuSceneFragment(pub Fragment);
|
pub struct PgpuSceneFragment(pub SceneFragment);
|
||||||
|
|
||||||
impl PgpuSceneFragment {
|
impl PgpuSceneFragment {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self(Fragment::default())
|
Self(SceneFragment::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn builder(&mut self) -> PgpuSceneBuilder {
|
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.
|
/// 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> {
|
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));
|
self.0.append(&glyph.fragment, Some(*transform));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,7 +198,7 @@ pub struct PgpuGlyphProvider<'a>(GlyphProvider<'a>);
|
||||||
|
|
||||||
impl<'a> PgpuGlyphProvider<'a> {
|
impl<'a> PgpuGlyphProvider<'a> {
|
||||||
pub fn get(&mut self, gid: u16) -> Option<PgpuGlyph> {
|
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 })
|
Some(PgpuGlyph { fragment })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -228,7 +210,7 @@ impl<'a> PgpuGlyphProvider<'a> {
|
||||||
|
|
||||||
/// Encoded (possibly color) outline for a glyph.
|
/// Encoded (possibly color) outline for a glyph.
|
||||||
pub struct PgpuGlyph {
|
pub struct PgpuGlyph {
|
||||||
fragment: Fragment,
|
fragment: SceneFragment,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PgpuGlyph {
|
impl PgpuGlyph {
|
||||||
|
|
|
@ -26,15 +26,18 @@ path = "../piet-gpu-hal"
|
||||||
[dependencies.piet-gpu-types]
|
[dependencies.piet-gpu-types]
|
||||||
path = "../piet-gpu-types"
|
path = "../piet-gpu-types"
|
||||||
|
|
||||||
|
[dependencies.piet-scene]
|
||||||
|
path = "../piet-scene"
|
||||||
|
features = ["kurbo"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
piet = "0.2.0"
|
|
||||||
png = "0.16.2"
|
png = "0.16.2"
|
||||||
rand = "0.7.3"
|
rand = "0.7.3"
|
||||||
roxmltree = "0.13"
|
roxmltree = "0.13"
|
||||||
winit = "0.26.1"
|
winit = "0.26.1"
|
||||||
clap = "2.33"
|
clap = "2.33"
|
||||||
swash = "0.1.4"
|
|
||||||
bytemuck = { version = "1.7.2", features = ["derive"] }
|
bytemuck = { version = "1.7.2", features = ["derive"] }
|
||||||
|
kurbo = "0.8.3"
|
||||||
|
|
||||||
[target.'cfg(target_os = "android")'.dependencies]
|
[target.'cfg(target_os = "android")'.dependencies]
|
||||||
ndk = "0.3"
|
ndk = "0.3"
|
||||||
|
|
|
@ -17,10 +17,8 @@ use piet_gpu_hal::{
|
||||||
SubmittedCmdBuf, Surface, Swapchain,
|
SubmittedCmdBuf, Surface, Swapchain,
|
||||||
};
|
};
|
||||||
|
|
||||||
use piet::kurbo::Point;
|
use piet_gpu::{samples, RenderDriver, Renderer, SimpleText};
|
||||||
use piet::{RenderContext, Text, TextAttribute, TextLayoutBuilder};
|
use piet_scene::{ResourceContext, Scene, SceneBuilder};
|
||||||
|
|
||||||
use piet_gpu::{test_scenes, PietGpuRenderContext, RenderDriver, Renderer};
|
|
||||||
|
|
||||||
#[cfg_attr(target_os = "android", ndk_glue::main(backtrace = "on"))]
|
#[cfg_attr(target_os = "android", ndk_glue::main(backtrace = "on"))]
|
||||||
fn main() {
|
fn main() {
|
||||||
|
@ -134,14 +132,15 @@ impl GfxState {
|
||||||
info_string = stats.short_summary();
|
info_string = stats.short_summary();
|
||||||
println!("{}", info_string);
|
println!("{}", info_string);
|
||||||
}
|
}
|
||||||
let mut ctx = PietGpuRenderContext::new();
|
let mut text = SimpleText::new();
|
||||||
test_scenes::render_anim_frame(&mut ctx, self.current_frame);
|
let mut scene = Scene::default();
|
||||||
//test_scenes::render_tiger(&mut ctx);
|
let mut rcx = ResourceContext::default();
|
||||||
render_info_string(&mut ctx, &info_string);
|
let mut builder = SceneBuilder::for_scene(&mut scene, &mut rcx);
|
||||||
if let Err(e) = self
|
samples::render_anim_frame(&mut builder, self.current_frame);
|
||||||
.render_driver
|
//samples::render_tiger(&mut builder, false);
|
||||||
.upload_render_ctx(&self.session, &mut ctx)
|
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);
|
println!("error in uploading: {}", e);
|
||||||
}
|
}
|
||||||
let (image_idx, acquisition_semaphore) = self.swapchain.next().unwrap();
|
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) {
|
fn render_info(simple_text: &mut SimpleText, sb: &mut SceneBuilder, info: &str) {
|
||||||
let layout = rc
|
simple_text.add(
|
||||||
.text()
|
sb,
|
||||||
.new_text_layout(info.to_string())
|
None,
|
||||||
.default_attribute(TextAttribute::FontSize(60.0))
|
60.0,
|
||||||
.build()
|
None,
|
||||||
.unwrap();
|
piet_scene::Affine::translate(110.0, 120.0),
|
||||||
rc.draw_text(&layout, Point::new(110.0, 120.0));
|
info,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,8 @@ use clap::{App, Arg};
|
||||||
|
|
||||||
use piet_gpu_hal::{BufferUsage, Error, Instance, InstanceFlags, Session};
|
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 WIDTH: usize = 2048;
|
||||||
const HEIGHT: usize = 1536;
|
const HEIGHT: usize = 1536;
|
||||||
|
@ -227,11 +228,13 @@ fn main() -> Result<(), Error> {
|
||||||
)
|
)
|
||||||
.get_matches();
|
.get_matches();
|
||||||
let instance = Instance::new(InstanceFlags::default())?;
|
let instance = Instance::new(InstanceFlags::default())?;
|
||||||
|
let mut scene = Scene::default();
|
||||||
|
let mut rcx = ResourceContext::default();
|
||||||
unsafe {
|
unsafe {
|
||||||
let device = instance.device()?;
|
let device = instance.device()?;
|
||||||
let session = Session::new(device);
|
let session = Session::new(device);
|
||||||
|
rcx.advance();
|
||||||
let mut ctx = PietGpuRenderContext::new();
|
let mut builder = SceneBuilder::for_scene(&mut scene, &mut rcx);
|
||||||
if let Some(input) = matches.value_of("INPUT") {
|
if let Some(input) = matches.value_of("INPUT") {
|
||||||
let mut scale = matches
|
let mut scale = matches
|
||||||
.value_of("scale")
|
.value_of("scale")
|
||||||
|
@ -244,16 +247,17 @@ fn main() -> Result<(), Error> {
|
||||||
let start = std::time::Instant::now();
|
let start = std::time::Instant::now();
|
||||||
let svg = PicoSvg::load(&xml_str, scale).unwrap();
|
let svg = PicoSvg::load(&xml_str, scale).unwrap();
|
||||||
println!("parsing time: {:?}", start.elapsed());
|
println!("parsing time: {:?}", start.elapsed());
|
||||||
test_scenes::render_svg(&mut ctx, &svg);
|
samples::render_svg(&mut builder, &svg, true);
|
||||||
} else {
|
} else {
|
||||||
//test_scenes::render_scene(&mut ctx);
|
//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 renderer = Renderer::new(&session, WIDTH, HEIGHT, 1)?;
|
||||||
let mut render_driver = RenderDriver::new(&session, 1, renderer);
|
let mut render_driver = RenderDriver::new(&session, 1, renderer);
|
||||||
let start = std::time::Instant::now();
|
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_usage = BufferUsage::MAP_READ | BufferUsage::COPY_DST;
|
||||||
let image_buf = session.create_buffer((WIDTH * HEIGHT * 4) as u64, image_usage)?;
|
let image_buf = session.create_buffer((WIDTH * HEIGHT * 4) as u64, image_usage)?;
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
use piet::kurbo::Point;
|
use piet_gpu::{samples, PicoSvg, RenderDriver, Renderer, SimpleText};
|
||||||
use piet::{RenderContext, Text, TextAttribute, TextLayoutBuilder};
|
|
||||||
use piet_gpu_hal::{Error, ImageLayout, Instance, InstanceFlags, Session};
|
use piet_gpu_hal::{Error, ImageLayout, Instance, InstanceFlags, Session};
|
||||||
|
use piet_scene::{ResourceContext, Scene, SceneBuilder};
|
||||||
use piet_gpu::{test_scenes, PicoSvg, PietGpuRenderContext, RenderDriver, Renderer};
|
|
||||||
|
|
||||||
use clap::{App, Arg};
|
use clap::{App, Arg};
|
||||||
|
|
||||||
|
@ -59,6 +57,9 @@ fn main() -> Result<(), Error> {
|
||||||
|
|
||||||
let instance = Instance::new(InstanceFlags::default())?;
|
let instance = Instance::new(InstanceFlags::default())?;
|
||||||
let mut info_string = "info".to_string();
|
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 {
|
unsafe {
|
||||||
let surface = instance.surface(&window)?;
|
let surface = instance.surface(&window)?;
|
||||||
let device = instance.device()?;
|
let device = instance.device()?;
|
||||||
|
@ -72,7 +73,7 @@ fn main() -> Result<(), Error> {
|
||||||
|
|
||||||
let renderer = Renderer::new(&session, WIDTH, HEIGHT, NUM_FRAMES)?;
|
let renderer = Renderer::new(&session, WIDTH, HEIGHT, NUM_FRAMES)?;
|
||||||
let mut render_driver = RenderDriver::new(&session, NUM_FRAMES, renderer);
|
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| {
|
event_loop.run(move |event, _, control_flow| {
|
||||||
*control_flow = ControlFlow::Poll; // `ControlFlow::Wait` if only re-render on event
|
*control_flow = ControlFlow::Poll; // `ControlFlow::Wait` if only re-render on event
|
||||||
|
@ -87,8 +88,12 @@ fn main() -> Result<(), Error> {
|
||||||
WindowEvent::KeyboardInput { input, .. } => {
|
WindowEvent::KeyboardInput { input, .. } => {
|
||||||
if input.state == ElementState::Pressed {
|
if input.state == ElementState::Pressed {
|
||||||
match input.virtual_keycode {
|
match input.virtual_keycode {
|
||||||
Some(VirtualKeyCode::Left) => mode = mode.wrapping_sub(1),
|
Some(VirtualKeyCode::Left) => {
|
||||||
Some(VirtualKeyCode::Right) => mode = mode.wrapping_add(1),
|
sample_index = sample_index.saturating_sub(1)
|
||||||
|
}
|
||||||
|
Some(VirtualKeyCode::Right) => {
|
||||||
|
sample_index = sample_index.saturating_add(1)
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,52 +112,35 @@ fn main() -> Result<(), Error> {
|
||||||
info_string = stats.short_summary();
|
info_string = stats.short_summary();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut ctx = PietGpuRenderContext::new();
|
|
||||||
let test_blend = false;
|
|
||||||
if let Some(svg) = &svg {
|
if let Some(svg) = &svg {
|
||||||
test_scenes::render_svg(&mut ctx, svg);
|
rcx.advance();
|
||||||
} else if test_blend {
|
let mut builder = SceneBuilder::for_scene(&mut scene, &mut rcx);
|
||||||
use piet_gpu::{Blend, BlendMode::*, CompositionMode::*};
|
samples::render_svg(&mut builder, svg, false);
|
||||||
let blends = [
|
render_info(&mut simple_text, &mut builder, &info_string);
|
||||||
Blend::new(Normal, SrcOver),
|
builder.finish();
|
||||||
Blend::new(Multiply, SrcOver),
|
if let Err(e) = render_driver.upload_scene(&session, &scene, &rcx) {
|
||||||
Blend::new(Screen, SrcOver),
|
println!("error in uploading: {}", e);
|
||||||
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 {
|
} else {
|
||||||
test_scenes::render_anim_frame(&mut ctx, current_frame);
|
rcx.advance();
|
||||||
}
|
let mut builder = SceneBuilder::for_scene(&mut scene, &mut rcx);
|
||||||
render_info_string(&mut ctx, &info_string);
|
|
||||||
if let Err(e) = render_driver.upload_render_ctx(&session, &mut ctx) {
|
const N_SAMPLES: usize = 4;
|
||||||
println!("error in uploading: {}", e);
|
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 (image_idx, acquisition_semaphore) = swapchain.next().unwrap();
|
||||||
|
@ -193,12 +181,13 @@ fn main() -> Result<(), Error> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_info_string(rc: &mut impl RenderContext, info: &str) {
|
fn render_info(simple_text: &mut SimpleText, sb: &mut SceneBuilder, info: &str) {
|
||||||
let layout = rc
|
simple_text.add(
|
||||||
.text()
|
sb,
|
||||||
.new_text_layout(info.to_string())
|
None,
|
||||||
.default_attribute(TextAttribute::FontSize(40.0))
|
40.0,
|
||||||
.build()
|
None,
|
||||||
.unwrap();
|
piet_scene::Affine::translate(110.0, 50.0),
|
||||||
rc.draw_text(&layout, Point::new(110.0, 50.0));
|
info,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,31 +1,24 @@
|
||||||
mod blend;
|
|
||||||
mod encoder;
|
|
||||||
pub mod glyph_render;
|
|
||||||
mod gradient;
|
|
||||||
mod pico_svg;
|
mod pico_svg;
|
||||||
mod render_ctx;
|
|
||||||
mod render_driver;
|
mod render_driver;
|
||||||
|
pub mod samples;
|
||||||
|
mod simple_text;
|
||||||
pub mod stages;
|
pub mod stages;
|
||||||
pub mod test_scenes;
|
|
||||||
mod text;
|
pub use piet_scene as scene;
|
||||||
|
|
||||||
use bytemuck::{Pod, Zeroable};
|
use bytemuck::{Pod, Zeroable};
|
||||||
use std::convert::TryInto;
|
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;
|
pub use render_driver::RenderDriver;
|
||||||
|
pub use simple_text::SimpleText;
|
||||||
use piet::kurbo::Vec2;
|
|
||||||
use piet::{ImageFormat, RenderContext};
|
|
||||||
|
|
||||||
use piet_gpu_hal::{
|
use piet_gpu_hal::{
|
||||||
include_shader, BindType, Buffer, BufferUsage, CmdBuf, ComputePassDescriptor, DescriptorSet,
|
include_shader, BindType, BufWrite, Buffer, BufferUsage, CmdBuf, ComputePassDescriptor,
|
||||||
Error, Image, ImageLayout, Pipeline, QueryPool, Session,
|
DescriptorSet, Error, Image, ImageLayout, Pipeline, QueryPool, Session,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use piet_scene::{ResourceContext, Scene};
|
||||||
|
|
||||||
pub use pico_svg::PicoSvg;
|
pub use pico_svg::PicoSvg;
|
||||||
use stages::{ClipBinding, ElementBinding, ElementCode, DRAW_PART_SIZE, PATHSEG_PART_SIZE};
|
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 PTCL_INITIAL_ALLOC: usize = 1024;
|
||||||
|
|
||||||
|
const N_GRADIENT_SAMPLES: usize = 512;
|
||||||
|
// TODO: make this dynamic
|
||||||
|
const N_GRADIENTS: usize = 256;
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
fn dump_scene(buf: &[u8]) {
|
fn dump_scene(buf: &[u8]) {
|
||||||
for i in 0..(buf.len() / 4) {
|
for i in 0..(buf.len() / 4) {
|
||||||
|
@ -333,8 +330,8 @@ impl Renderer {
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
let bg_image = Self::make_test_bg_image(&session);
|
let bg_image = Self::make_test_bg_image(&session);
|
||||||
|
|
||||||
const GRADIENT_BUF_SIZE: usize =
|
const GRADIENT_BUF_SIZE: usize = N_GRADIENTS * N_GRADIENT_SAMPLES * 4;
|
||||||
crate::gradient::N_GRADIENTS * crate::gradient::N_SAMPLES * 4;
|
|
||||||
let gradient_bufs = (0..n_bufs)
|
let gradient_bufs = (0..n_bufs)
|
||||||
.map(|_| {
|
.map(|_| {
|
||||||
session
|
session
|
||||||
|
@ -409,59 +406,29 @@ impl Renderer {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert the scene in the render context to GPU resources.
|
pub fn upload_scene(
|
||||||
///
|
|
||||||
/// 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(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
render_ctx: &mut PietGpuRenderContext,
|
scene: &Scene,
|
||||||
|
rcx: &ResourceContext,
|
||||||
buf_ix: usize,
|
buf_ix: usize,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
self.scene_stats = render_ctx.stats();
|
self.scene_stats = SceneStats::from_scene(scene);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
self.upload_config(buf_ix)?;
|
self.upload_config(buf_ix)?;
|
||||||
{
|
{
|
||||||
let mut mapped_scene = self.scene_bufs[buf_ix].map_write(..)?;
|
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.
|
// Upload gradient data.
|
||||||
let ramp_data = render_ctx.get_ramp_data();
|
let ramp_data = rcx.ramp_data();
|
||||||
if !ramp_data.is_empty() {
|
if !ramp_data.is_empty() {
|
||||||
assert!(
|
assert!(
|
||||||
self.gradient_bufs[buf_ix].size() as usize
|
self.gradient_bufs[buf_ix].size() as usize
|
||||||
>= std::mem::size_of_val(&*ramp_data)
|
>= std::mem::size_of_val(&*ramp_data)
|
||||||
);
|
);
|
||||||
self.gradient_bufs[buf_ix].write(&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)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -643,12 +610,8 @@ impl Renderer {
|
||||||
width: usize,
|
width: usize,
|
||||||
height: usize,
|
height: usize,
|
||||||
buf: &[u8],
|
buf: &[u8],
|
||||||
format: ImageFormat,
|
|
||||||
) -> Result<Image, Error> {
|
) -> Result<Image, Error> {
|
||||||
unsafe {
|
unsafe {
|
||||||
if format != ImageFormat::RgbaPremul {
|
|
||||||
return Err("unsupported image format".into());
|
|
||||||
}
|
|
||||||
let buffer = session.create_buffer_init(&buf, BufferUsage::COPY_SRC)?;
|
let buffer = session.create_buffer_init(&buf, BufferUsage::COPY_SRC)?;
|
||||||
const RGBA: piet_gpu_hal::ImageFormat = piet_gpu_hal::ImageFormat::Rgba8;
|
const RGBA: piet_gpu_hal::ImageFormat = piet_gpu_hal::ImageFormat::Rgba8;
|
||||||
let image = session.create_image2d(width.try_into()?, height.try_into()?, RGBA)?;
|
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;
|
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 {
|
fn make_gradient_image(session: &Session) -> Image {
|
||||||
unsafe {
|
unsafe {
|
||||||
const RGBA: piet_gpu_hal::ImageFormat = piet_gpu_hal::ImageFormat::Rgba8;
|
const RGBA: piet_gpu_hal::ImageFormat = piet_gpu_hal::ImageFormat::Rgba8;
|
||||||
session
|
session
|
||||||
.create_image2d(
|
.create_image2d(N_GRADIENT_SAMPLES as u32, N_GRADIENTS as u32, RGBA)
|
||||||
gradient::N_SAMPLES as u32,
|
|
||||||
gradient::N_GRADIENTS as u32,
|
|
||||||
RGBA,
|
|
||||||
)
|
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -792,6 +751,21 @@ const DRAWTAG_SIZE: usize = 4;
|
||||||
const ANNOTATED_SIZE: usize = 40;
|
const ANNOTATED_SIZE: usize = 40;
|
||||||
|
|
||||||
impl SceneStats {
|
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 {
|
pub(crate) fn scene_size(&self) -> usize {
|
||||||
align_up(self.n_drawobj, DRAW_PART_SIZE as usize) * DRAWTAG_SIZE
|
align_up(self.n_drawobj, DRAW_PART_SIZE as usize) * DRAWTAG_SIZE
|
||||||
+ self.drawdata_len
|
+ 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 {
|
fn align_up(x: usize, align: usize) -> usize {
|
||||||
debug_assert!(align.is_power_of_two());
|
debug_assert!(align.is_power_of_two());
|
||||||
(x + align - 1) & !(align - 1)
|
(x + align - 1) & !(align - 1)
|
||||||
|
|
|
@ -4,12 +4,12 @@ use std::str::FromStr;
|
||||||
|
|
||||||
use roxmltree::{Document, Node};
|
use roxmltree::{Document, Node};
|
||||||
|
|
||||||
use piet::kurbo::{Affine, BezPath};
|
use kurbo::{Affine, BezPath};
|
||||||
|
|
||||||
use piet::{Color, RenderContext};
|
use piet_scene::Color;
|
||||||
|
|
||||||
pub struct PicoSvg {
|
pub struct PicoSvg {
|
||||||
items: Vec<Item>,
|
pub items: Vec<Item>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Item {
|
pub enum Item {
|
||||||
|
@ -18,14 +18,14 @@ pub enum Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StrokeItem {
|
pub struct StrokeItem {
|
||||||
width: f64,
|
pub width: f64,
|
||||||
color: Color,
|
pub color: Color,
|
||||||
path: BezPath,
|
pub path: BezPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FillItem {
|
pub struct FillItem {
|
||||||
color: Color,
|
pub color: Color,
|
||||||
path: BezPath,
|
pub path: BezPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Parser<'a> {
|
struct Parser<'a> {
|
||||||
|
@ -44,20 +44,6 @@ impl PicoSvg {
|
||||||
}
|
}
|
||||||
Ok(PicoSvg { items })
|
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> {
|
impl<'a> Parser<'a> {
|
||||||
|
@ -119,7 +105,14 @@ fn parse_color(color: &str) -> Color {
|
||||||
if color.len() == 4 {
|
if color.len() == 4 {
|
||||||
hex = (hex >> 8) * 0x110000 + ((hex >> 4) & 0xf) * 0x1100 + (hex & 0xf) * 0x11;
|
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(") {
|
} else if color.starts_with("rgb(") {
|
||||||
let mut iter = color[4..color.len() - 1].split(',');
|
let mut iter = color[4..color.len() - 1].split(',');
|
||||||
let r = u8::from_str(iter.next().unwrap()).unwrap();
|
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();
|
let b = u8::from_str(iter.next().unwrap()).unwrap();
|
||||||
Color::rgb8(r, g, b)
|
Color::rgb8(r, g, b)
|
||||||
} else {
|
} 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) {
|
if let Some(opacity) = node.attribute(attr_name) {
|
||||||
let alpha = if opacity.ends_with("%") {
|
let alpha = if opacity.ends_with("%") {
|
||||||
let pctg = opacity[..opacity.len() - 1].parse().unwrap_or(100.0);
|
let pctg = opacity[..opacity.len() - 1].parse().unwrap_or(100.0);
|
||||||
pctg * 0.01
|
pctg * 0.01
|
||||||
} else {
|
} else {
|
||||||
opacity.parse().unwrap_or(1.0)
|
opacity.parse().unwrap_or(1.0)
|
||||||
};
|
} as f64;
|
||||||
color.with_alpha(alpha)
|
color.a = (alpha.min(1.0).max(0.0) * 255.0).round() as u8;
|
||||||
|
color
|
||||||
} else {
|
} else {
|
||||||
color
|
color
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -14,10 +14,10 @@
|
||||||
//
|
//
|
||||||
// Also licensed under MIT license, at your choice.
|
// Also licensed under MIT license, at your choice.
|
||||||
|
|
||||||
use bytemuck::Pod;
|
|
||||||
use piet_gpu_hal::{CmdBuf, Error, Image, QueryPool, Semaphore, Session, SubmittedCmdBuf};
|
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
|
/// Additional logic for sequencing rendering operations, specifically
|
||||||
/// for handling failure and reallocation.
|
/// for handling failure and reallocation.
|
||||||
|
@ -86,24 +86,15 @@ impl RenderDriver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn upload_render_ctx(
|
pub fn upload_scene(
|
||||||
&mut self,
|
&mut self,
|
||||||
session: &Session,
|
session: &Session,
|
||||||
render_ctx: &mut PietGpuRenderContext,
|
scene: &Scene,
|
||||||
|
rcx: &ResourceContext,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let stats = render_ctx.stats();
|
let stats = SceneStats::from_scene(scene);
|
||||||
self.ensure_scene_buffers(session, &stats)?;
|
self.ensure_scene_buffers(session, &stats)?;
|
||||||
self.renderer.upload_render_ctx(render_ctx, self.buf_ix)
|
self.renderer.upload_scene(scene, rcx, 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ensure_scene_buffers(&mut self, session: &Session, stats: &SceneStats) -> Result<(), Error> {
|
fn ensure_scene_buffers(&mut self, session: &Session, stats: &SceneStats) -> Result<(), Error> {
|
||||||
|
|
368
piet-gpu/src/samples.rs
Normal file
368
piet-gpu/src/samples.rs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
82
piet-gpu/src/simple_text.rs
Normal file
82
piet-gpu/src/simple_text.rs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,8 +18,6 @@
|
||||||
|
|
||||||
use bytemuck::{Pod, Zeroable};
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
|
||||||
use piet::kurbo::Affine;
|
|
||||||
|
|
||||||
/// An affine transform.
|
/// An affine transform.
|
||||||
// This is equivalent to the version in piet-gpu-types, but the bytemuck
|
// This is equivalent to the version in piet-gpu-types, but the bytemuck
|
||||||
// representation will likely be faster.
|
// representation will likely be faster.
|
||||||
|
@ -35,23 +33,4 @@ impl Transform {
|
||||||
mat: [1.0, 0.0, 0.0, 1.0],
|
mat: [1.0, 0.0, 0.0, 1.0],
|
||||||
translate: [0.0, 0.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,
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -14,6 +14,7 @@
|
||||||
//
|
//
|
||||||
// Also licensed under MIT license, at your choice.
|
// Also licensed under MIT license, at your choice.
|
||||||
|
|
||||||
|
/// 32-bit RGBA color.
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
||||||
pub struct Color {
|
pub struct Color {
|
||||||
pub r: u8,
|
pub r: u8,
|
||||||
|
|
|
@ -15,17 +15,19 @@
|
||||||
// Also licensed under MIT license, at your choice.
|
// Also licensed under MIT license, at your choice.
|
||||||
|
|
||||||
use super::color::Color;
|
use super::color::Color;
|
||||||
|
use super::ExtendMode;
|
||||||
use crate::geometry::Point;
|
use crate::geometry::Point;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
|
/// Offset and color of a transition point in a gradient.
|
||||||
#[derive(Copy, Clone, PartialOrd, Default, Debug)]
|
#[derive(Copy, Clone, PartialOrd, Default, Debug)]
|
||||||
pub struct Stop {
|
pub struct GradientStop {
|
||||||
pub offset: f32,
|
pub offset: f32,
|
||||||
pub color: Color,
|
pub color: Color,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for Stop {
|
impl Hash for GradientStop {
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
self.offset.to_bits().hash(state);
|
self.offset.to_bits().hash(state);
|
||||||
self.color.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
|
// 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 {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.offset.to_bits() == other.offset.to_bits() && self.color == other.color
|
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]>;
|
/// Collection of gradient stops.
|
||||||
|
pub type GradientStops = SmallVec<[GradientStop; 4]>;
|
||||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
|
||||||
pub enum Extend {
|
|
||||||
Pad,
|
|
||||||
Repeat,
|
|
||||||
Reflect,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// Definition of a gradient that transitions between two or more colors along
|
||||||
|
/// a line.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct LinearGradient {
|
pub struct LinearGradient {
|
||||||
pub start: Point,
|
pub start: Point,
|
||||||
pub end: Point,
|
pub end: Point,
|
||||||
pub stops: StopVec,
|
pub stops: GradientStops,
|
||||||
pub extend: Extend,
|
pub extend: ExtendMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Definition of a gradient that transitions between two or more colors that
|
||||||
|
/// radiate from an origin.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct RadialGradient {
|
pub struct RadialGradient {
|
||||||
pub center0: Point,
|
pub center0: Point,
|
||||||
pub radius0: f32,
|
pub radius0: f32,
|
||||||
pub center1: Point,
|
pub center1: Point,
|
||||||
pub radius1: f32,
|
pub radius1: f32,
|
||||||
pub stops: StopVec,
|
pub stops: GradientStops,
|
||||||
pub extend: Extend,
|
pub extend: ExtendMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Definition gradient that transitions between two or more colors that rotate
|
||||||
|
/// around a center point.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct SweepGradient {
|
pub struct SweepGradient {
|
||||||
pub center: Point,
|
pub center: Point,
|
||||||
pub start_angle: f32,
|
pub start_angle: f32,
|
||||||
pub end_angle: f32,
|
pub end_angle: f32,
|
||||||
pub stops: StopVec,
|
pub stops: GradientStops,
|
||||||
pub extend: Extend,
|
pub extend: ExtendMode,
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,53 +18,35 @@ use std::result::Result;
|
||||||
use std::sync::atomic::{AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
/// Image data resource.
|
||||||
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,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Image(Arc<Inner>);
|
pub struct Image(Arc<Inner>);
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct Inner {
|
struct Inner {
|
||||||
id: u64,
|
id: u64,
|
||||||
format: Format,
|
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
data: Vec<u8>,
|
data: Arc<[u8]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Image {
|
impl Image {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
format: Format,
|
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
mut data: Vec<u8>,
|
data: impl Into<Arc<[u8]>>,
|
||||||
) -> Result<Self, DataSizeError> {
|
) -> Result<Self, ImageDataSizeError> {
|
||||||
let data_size = format.data_size(width, height).ok_or(DataSizeError)?;
|
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 {
|
if data.len() < data_size {
|
||||||
return Err(DataSizeError);
|
return Err(ImageDataSizeError);
|
||||||
}
|
}
|
||||||
data.truncate(data_size);
|
|
||||||
static ID: AtomicU64 = AtomicU64::new(1);
|
static ID: AtomicU64 = AtomicU64::new(1);
|
||||||
Ok(Self(Arc::new(Inner {
|
Ok(Self(Arc::new(Inner {
|
||||||
id: ID.fetch_add(1, Ordering::Relaxed),
|
id: ID.fetch_add(1, Ordering::Relaxed),
|
||||||
format,
|
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
data,
|
data,
|
||||||
|
@ -75,10 +57,6 @@ impl Image {
|
||||||
self.0.id
|
self.0.id
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn format(&self) -> Format {
|
|
||||||
self.0.format
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn width(&self) -> u32 {
|
pub fn width(&self) -> u32 {
|
||||||
self.0.width
|
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)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct DataSizeError;
|
pub struct ImageDataSizeError;
|
||||||
|
|
|
@ -22,8 +22,7 @@ pub use color::Color;
|
||||||
pub use gradient::*;
|
pub use gradient::*;
|
||||||
pub use image::*;
|
pub use image::*;
|
||||||
|
|
||||||
use crate::resource::PersistentBrush;
|
/// Describes the content of a filled or stroked shape.
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Brush {
|
pub enum Brush {
|
||||||
Solid(Color),
|
Solid(Color),
|
||||||
|
@ -31,5 +30,31 @@ pub enum Brush {
|
||||||
RadialGradient(RadialGradient),
|
RadialGradient(RadialGradient),
|
||||||
SweepGradient(SweepGradient),
|
SweepGradient(SweepGradient),
|
||||||
Image(Image),
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
/// Returns the width of the rectangle.
|
||||||
pub fn width(&self) -> f32 {
|
pub fn width(&self) -> f32 {
|
||||||
self.max.x - self.min.x
|
self.max.x - self.min.x
|
||||||
|
|
|
@ -14,12 +14,14 @@
|
||||||
//
|
//
|
||||||
// Also licensed under MIT license, at your choice.
|
// Also licensed under MIT license, at your choice.
|
||||||
|
|
||||||
|
//! Support for glyph rendering.
|
||||||
|
|
||||||
pub use moscato::pinot;
|
pub use moscato::pinot;
|
||||||
|
|
||||||
use crate::brush::{Brush, Color};
|
use crate::brush::{Brush, Color};
|
||||||
use crate::geometry::Affine;
|
use crate::geometry::Affine;
|
||||||
use crate::path::Element;
|
use crate::path::PathElement;
|
||||||
use crate::scene::{build_fragment, Fill, Fragment};
|
use crate::scene::{Fill, SceneBuilder, SceneFragment};
|
||||||
|
|
||||||
use moscato::{Context, Scaler};
|
use moscato::{Context, Scaler};
|
||||||
use pinot::{types::Tag, FontRef};
|
use pinot::{types::Tag, FontRef};
|
||||||
|
@ -81,28 +83,29 @@ pub struct GlyphProvider<'a> {
|
||||||
impl<'a> GlyphProvider<'a> {
|
impl<'a> GlyphProvider<'a> {
|
||||||
/// Returns a scene fragment containing the commands to render the
|
/// Returns a scene fragment containing the commands to render the
|
||||||
/// specified glyph.
|
/// 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 glyph = self.scaler.glyph(gid)?;
|
||||||
let path = glyph.path(0)?;
|
let path = glyph.path(0)?;
|
||||||
let mut fragment = Fragment::default();
|
let mut fragment = SceneFragment::default();
|
||||||
let mut builder = build_fragment(&mut fragment);
|
let mut builder = SceneBuilder::for_fragment(&mut fragment);
|
||||||
builder.fill(
|
builder.fill(
|
||||||
Fill::NonZero,
|
Fill::NonZero,
|
||||||
&Brush::Solid(Color::rgb8(255, 255, 255)),
|
brush.unwrap_or(&Brush::Solid(Color::rgb8(255, 255, 255))),
|
||||||
None,
|
None,
|
||||||
convert_path(path.elements()),
|
convert_path(path.elements()),
|
||||||
);
|
);
|
||||||
|
builder.finish();
|
||||||
Some(fragment)
|
Some(fragment)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a scene fragment containing the commands and resources to
|
/// Returns a scene fragment containing the commands and resources to
|
||||||
/// render the specified color glyph.
|
/// 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 crate::geometry::*;
|
||||||
use moscato::Command;
|
use moscato::Command;
|
||||||
let glyph = self.scaler.color_glyph(palette_index, gid)?;
|
let glyph = self.scaler.color_glyph(palette_index, gid)?;
|
||||||
let mut fragment = Fragment::default();
|
let mut fragment = SceneFragment::default();
|
||||||
let mut builder = build_fragment(&mut fragment);
|
let mut builder = SceneBuilder::for_fragment(&mut fragment);
|
||||||
let mut xform_stack: SmallVec<[Affine; 8]> = SmallVec::new();
|
let mut xform_stack: SmallVec<[Affine; 8]> = SmallVec::new();
|
||||||
for command in glyph.commands() {
|
for command in glyph.commands() {
|
||||||
match command {
|
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
|
// TODO: this needs to compute a bounding box for
|
||||||
// the parent clips
|
// the parent clips
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
builder.finish();
|
||||||
Some(fragment)
|
Some(fragment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_path(
|
fn convert_path(
|
||||||
path: impl Iterator<Item = moscato::Element> + Clone,
|
path: impl Iterator<Item = moscato::Element> + Clone,
|
||||||
) -> impl Iterator<Item = Element> + Clone {
|
) -> impl Iterator<Item = PathElement> + Clone {
|
||||||
use crate::geometry::Point;
|
use crate::geometry::Point;
|
||||||
path.map(|el| match el {
|
path.map(|el| match el {
|
||||||
moscato::Element::MoveTo(p0) => Element::MoveTo(Point::new(p0.x, p0.y)),
|
moscato::Element::MoveTo(p0) => PathElement::MoveTo(Point::new(p0.x, p0.y)),
|
||||||
moscato::Element::LineTo(p0) => Element::LineTo(Point::new(p0.x, p0.y)),
|
moscato::Element::LineTo(p0) => PathElement::LineTo(Point::new(p0.x, p0.y)),
|
||||||
moscato::Element::QuadTo(p0, p1) => {
|
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(p0.x, p0.y),
|
||||||
Point::new(p1.x, p1.y),
|
Point::new(p1.x, p1.y),
|
||||||
Point::new(p2.x, p2.y),
|
Point::new(p2.x, p2.y),
|
||||||
),
|
),
|
||||||
moscato::Element::Close => Element::Close,
|
moscato::Element::Close => PathElement::Close,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_transformed_path(
|
fn convert_transformed_path(
|
||||||
path: impl Iterator<Item = moscato::Element> + Clone,
|
path: impl Iterator<Item = moscato::Element> + Clone,
|
||||||
xform: &Affine,
|
xform: &Affine,
|
||||||
) -> impl Iterator<Item = Element> + Clone {
|
) -> impl Iterator<Item = PathElement> + Clone {
|
||||||
use crate::geometry::Point;
|
use crate::geometry::Point;
|
||||||
let xform = *xform;
|
let xform = *xform;
|
||||||
path.map(move |el| match el {
|
path.map(move |el| match el {
|
||||||
moscato::Element::MoveTo(p0) => Element::MoveTo(Point::new(p0.x, p0.y).transform(&xform)),
|
moscato::Element::MoveTo(p0) => {
|
||||||
moscato::Element::LineTo(p0) => Element::LineTo(Point::new(p0.x, p0.y).transform(&xform)),
|
PathElement::MoveTo(Point::new(p0.x, p0.y).transform(&xform))
|
||||||
moscato::Element::QuadTo(p0, p1) => Element::QuadTo(
|
}
|
||||||
|
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(p0.x, p0.y).transform(&xform),
|
||||||
Point::new(p1.x, p1.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(p0.x, p0.y).transform(&xform),
|
||||||
Point::new(p1.x, p1.y).transform(&xform),
|
Point::new(p1.x, p1.y).transform(&xform),
|
||||||
Point::new(p2.x, p2.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 {
|
fn convert_blend(mode: moscato::CompositeMode) -> crate::scene::BlendMode {
|
||||||
use crate::scene::{Blend, Compose, Mix};
|
use crate::scene::{BlendMode, Compose, Mix};
|
||||||
use moscato::CompositeMode;
|
use moscato::CompositeMode;
|
||||||
let mut mix = Mix::Normal;
|
let mut mix = Mix::Normal;
|
||||||
let mut compose = Compose::SrcOver;
|
let mut compose = Compose::SrcOver;
|
||||||
|
@ -257,7 +265,7 @@ fn convert_blend(mode: moscato::CompositeMode) -> crate::scene::Blend {
|
||||||
CompositeMode::HslColor => mix = Mix::Color,
|
CompositeMode::HslColor => mix = Mix::Color,
|
||||||
CompositeMode::HslLuminosity => mix = Mix::Luminosity,
|
CompositeMode::HslLuminosity => mix = Mix::Luminosity,
|
||||||
}
|
}
|
||||||
Blend { mix, compose }
|
BlendMode { mix, compose }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_transform(xform: &moscato::Transform) -> crate::geometry::Affine {
|
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 {
|
fn convert_stops(stops: &[moscato::ColorStop]) -> crate::brush::GradientStops {
|
||||||
use crate::brush::Stop;
|
use crate::brush::GradientStop;
|
||||||
stops
|
stops
|
||||||
.iter()
|
.iter()
|
||||||
.map(|stop| Stop {
|
.map(|stop| GradientStop {
|
||||||
offset: stop.offset,
|
offset: stop.offset,
|
||||||
color: Color {
|
color: Color {
|
||||||
r: stop.color.r,
|
r: stop.color.r,
|
||||||
|
@ -314,8 +322,8 @@ fn convert_stops(stops: &[moscato::ColorStop]) -> crate::brush::StopVec {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_extend(extend: moscato::ExtendMode) -> crate::brush::Extend {
|
fn convert_extend(extend: moscato::ExtendMode) -> crate::brush::ExtendMode {
|
||||||
use crate::brush::Extend::*;
|
use crate::brush::ExtendMode::*;
|
||||||
match extend {
|
match extend {
|
||||||
moscato::ExtendMode::Pad => Pad,
|
moscato::ExtendMode::Pad => Pad,
|
||||||
moscato::ExtendMode::Repeat => Repeat,
|
moscato::ExtendMode::Repeat => Repeat,
|
|
@ -14,19 +14,26 @@
|
||||||
//
|
//
|
||||||
// Also licensed under MIT license, at your choice.
|
// Also licensed under MIT license, at your choice.
|
||||||
|
|
||||||
pub mod brush;
|
mod brush;
|
||||||
pub mod geometry;
|
mod geometry;
|
||||||
|
mod path;
|
||||||
|
mod resource;
|
||||||
|
mod scene;
|
||||||
|
|
||||||
pub mod glyph;
|
pub mod glyph;
|
||||||
pub mod path;
|
|
||||||
pub mod resource;
|
pub use brush::*;
|
||||||
pub mod scene;
|
pub use geometry::*;
|
||||||
|
pub use path::*;
|
||||||
|
pub use resource::*;
|
||||||
|
pub use scene::*;
|
||||||
|
|
||||||
/// Implement conversions to and from Kurbo types when the `kurbo` feature is
|
/// Implement conversions to and from Kurbo types when the `kurbo` feature is
|
||||||
/// enabled.
|
/// enabled.
|
||||||
#[cfg(feature = "kurbo")]
|
#[cfg(feature = "kurbo")]
|
||||||
mod kurbo_conv {
|
mod kurbo_conv {
|
||||||
use super::geometry::{Affine, Point, Rect};
|
use super::geometry::{Affine, Point, Rect};
|
||||||
use super::path::Element;
|
use super::path::PathElement;
|
||||||
|
|
||||||
impl Point {
|
impl Point {
|
||||||
/// Creates a new point from the equivalent kurbo type.
|
/// 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.
|
/// Creates a new path element from the equivalent kurbo type.
|
||||||
pub fn from_kurbo(el: kurbo::PathEl) -> Self {
|
pub fn from_kurbo(el: kurbo::PathEl) -> Self {
|
||||||
use kurbo::PathEl::*;
|
use kurbo::PathEl::*;
|
||||||
use Point::from_kurbo;
|
match el {
|
||||||
match e {
|
MoveTo(p0) => Self::MoveTo(Point::from_kurbo(p0)),
|
||||||
MoveTo(p0) => Self::MoveTo(from_kurbo(p0)),
|
LineTo(p0) => Self::LineTo(Point::from_kurbo(p0)),
|
||||||
LineTo(p0) => Self::LineTo(from_kurbo(p0)),
|
QuadTo(p0, p1) => Self::QuadTo(Point::from_kurbo(p0), Point::from_kurbo(p1)),
|
||||||
QuadTo(p0, p1) => Self::QuadTo(from_kurbo(p0), from_kurbo(p1)),
|
CurveTo(p0, p1, p2) => Self::CurveTo(
|
||||||
CurveTo(p0, p1, p2) => {
|
Point::from_kurbo(p0),
|
||||||
Self::CurveTo(from_kurbo(p0), from_kurbo(p1), from_kurbo(p2))
|
Point::from_kurbo(p1),
|
||||||
}
|
Point::from_kurbo(p2),
|
||||||
|
),
|
||||||
ClosePath => Self::Close,
|
ClosePath => Self::Close,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Element> for kurbo::PathEl {
|
impl From<PathElement> for kurbo::PathEl {
|
||||||
fn from(e: Element) -> Self {
|
fn from(e: PathElement) -> Self {
|
||||||
use Element::*;
|
use PathElement::*;
|
||||||
match e {
|
match e {
|
||||||
MoveTo(p0) => Self::MoveTo(p0.into()),
|
MoveTo(p0) => Self::MoveTo(p0.into()),
|
||||||
LineTo(p0) => Self::LineTo(p0.into()),
|
LineTo(p0) => Self::LineTo(p0.into()),
|
||||||
|
|
|
@ -18,7 +18,7 @@ use super::geometry::{Point, Rect};
|
||||||
|
|
||||||
/// Action of a path element.
|
/// Action of a path element.
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
pub enum Verb {
|
pub enum PathVerb {
|
||||||
MoveTo,
|
MoveTo,
|
||||||
LineTo,
|
LineTo,
|
||||||
QuadTo,
|
QuadTo,
|
||||||
|
@ -28,7 +28,7 @@ pub enum Verb {
|
||||||
|
|
||||||
/// Element of a path represented by a verb and its associated points.
|
/// Element of a path represented by a verb and its associated points.
|
||||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||||
pub enum Element {
|
pub enum PathElement {
|
||||||
MoveTo(Point),
|
MoveTo(Point),
|
||||||
LineTo(Point),
|
LineTo(Point),
|
||||||
QuadTo(Point, Point),
|
QuadTo(Point, Point),
|
||||||
|
@ -36,27 +36,27 @@ pub enum Element {
|
||||||
Close,
|
Close,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Element {
|
impl PathElement {
|
||||||
/// Returns the verb that describes the action of the path element.
|
/// Returns the verb that describes the action of the path element.
|
||||||
pub fn verb(&self) -> Verb {
|
pub fn verb(&self) -> PathVerb {
|
||||||
match self {
|
match self {
|
||||||
Self::MoveTo(..) => Verb::MoveTo,
|
Self::MoveTo(..) => PathVerb::MoveTo,
|
||||||
Self::LineTo(..) => Verb::LineTo,
|
Self::LineTo(..) => PathVerb::LineTo,
|
||||||
Self::QuadTo(..) => Verb::QuadTo,
|
Self::QuadTo(..) => PathVerb::QuadTo,
|
||||||
Self::CurveTo(..) => Verb::CurveTo,
|
Self::CurveTo(..) => PathVerb::CurveTo,
|
||||||
Self::Close => Verb::Close,
|
Self::Close => PathVerb::Close,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Rect {
|
impl Rect {
|
||||||
pub fn elements(&self) -> impl Iterator<Item = Element> + Clone {
|
pub fn elements(&self) -> impl Iterator<Item = PathElement> + Clone {
|
||||||
let elements = [
|
let elements = [
|
||||||
Element::MoveTo((self.min.x, self.min.y).into()),
|
PathElement::MoveTo((self.min.x, self.min.y).into()),
|
||||||
Element::LineTo((self.max.x, self.min.y).into()),
|
PathElement::LineTo((self.max.x, self.min.y).into()),
|
||||||
Element::LineTo((self.max.x, self.max.y).into()),
|
PathElement::LineTo((self.max.x, self.max.y).into()),
|
||||||
Element::LineTo((self.min.x, self.max.y).into()),
|
PathElement::LineTo((self.min.x, self.max.y).into()),
|
||||||
Element::Close,
|
PathElement::Close,
|
||||||
];
|
];
|
||||||
(0..5).map(move |i| elements[i])
|
(0..5).map(move |i| elements[i])
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::brush::{Color, Stop, StopVec};
|
use crate::brush::{Color, GradientStop, GradientStops};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
const N_SAMPLES: usize = 512;
|
const N_SAMPLES: usize = 512;
|
||||||
|
@ -7,15 +7,11 @@ const RETAINED_COUNT: usize = 64;
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct RampCache {
|
pub struct RampCache {
|
||||||
epoch: u64,
|
epoch: u64,
|
||||||
map: HashMap<StopVec, (u32, u64)>,
|
map: HashMap<GradientStops, (u32, u64)>,
|
||||||
data: Vec<u32>,
|
data: Vec<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RampCache {
|
impl RampCache {
|
||||||
pub fn new() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn advance(&mut self) {
|
pub fn advance(&mut self) {
|
||||||
self.epoch += 1;
|
self.epoch += 1;
|
||||||
if self.map.len() > RETAINED_COUNT {
|
if self.map.len() > RETAINED_COUNT {
|
||||||
|
@ -31,7 +27,7 @@ impl RampCache {
|
||||||
self.data.clear();
|
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) {
|
if let Some(entry) = self.map.get_mut(stops) {
|
||||||
entry.1 = self.epoch;
|
entry.1 = self.epoch;
|
||||||
entry.0
|
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_u = 0.0;
|
||||||
let mut last_c = ColorF64::from_color(stops[0].color);
|
let mut last_c = ColorF64::from_color(stops[0].color);
|
||||||
let mut this_u = last_u;
|
let mut this_u = last_u;
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
mod gradient;
|
mod gradient;
|
||||||
|
|
||||||
use crate::brush::{Brush, Stop};
|
use crate::brush::GradientStop;
|
||||||
use gradient::RampCache;
|
use gradient::RampCache;
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
/// Context for caching resources across rendering operations.
|
/// Context for caching resources across rendering operations.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ResourceContext {
|
pub struct ResourceContext {
|
||||||
ramps: RampCache,
|
ramps: RampCache,
|
||||||
persistent_map: HashMap<u64, PersistentBrushData>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResourceContext {
|
impl ResourceContext {
|
||||||
|
@ -22,35 +20,13 @@ impl ResourceContext {
|
||||||
|
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
self.ramps.clear();
|
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)
|
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] {
|
pub fn ramp_data(&self) -> &[u32] {
|
||||||
&self.ramps.data()
|
&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,
|
|
||||||
}
|
|
||||||
|
|
|
@ -60,12 +60,12 @@ pub enum Compose {
|
||||||
|
|
||||||
/// Blend mode consisting of mixing and composition functions.
|
/// Blend mode consisting of mixing and composition functions.
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
pub struct Blend {
|
pub struct BlendMode {
|
||||||
pub mix: Mix,
|
pub mix: Mix,
|
||||||
pub compose: Compose,
|
pub compose: Compose,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Blend {
|
impl BlendMode {
|
||||||
pub fn new(mix: Mix, compose: Compose) -> Self {
|
pub fn new(mix: Mix, compose: Compose) -> Self {
|
||||||
Self { mix, compose }
|
Self { mix, compose }
|
||||||
}
|
}
|
||||||
|
@ -75,7 +75,7 @@ impl Blend {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Blend {
|
impl Default for BlendMode {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
mix: Mix::Clip,
|
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 {
|
fn from(mix: Mix) -> Self {
|
||||||
Self {
|
Self {
|
||||||
mix,
|
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 {
|
fn from(compose: Compose) -> Self {
|
||||||
Self {
|
Self {
|
||||||
mix: Mix::Normal,
|
mix: Mix::Normal,
|
||||||
|
|
|
@ -15,37 +15,39 @@
|
||||||
// Also licensed under MIT license, at your choice.
|
// Also licensed under MIT license, at your choice.
|
||||||
|
|
||||||
use super::style::{Fill, Stroke};
|
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::brush::*;
|
||||||
use crate::resource::ResourceContext;
|
use crate::resource::ResourceContext;
|
||||||
use bytemuck::{Pod, Zeroable};
|
use bytemuck::{Pod, Zeroable};
|
||||||
use core::borrow::Borrow;
|
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new builder for filling a scene fragment. Any current content in
|
|
||||||
/// the fragment will be cleared.
|
|
||||||
pub fn build_fragment<'a>(fragment: &'a mut Fragment) -> Builder<'a> {
|
|
||||||
Builder::new(
|
|
||||||
&mut fragment.data,
|
|
||||||
ResourceData::Fragment(&mut fragment.resources),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Builder for constructing a scene or scene fragment.
|
/// Builder for constructing a scene or scene fragment.
|
||||||
pub struct Builder<'a> {
|
pub struct SceneBuilder<'a> {
|
||||||
scene: &'a mut SceneData,
|
scene: &'a mut SceneData,
|
||||||
resources: ResourceData<'a>,
|
resources: ResourceData<'a>,
|
||||||
layers: Vec<Blend>,
|
layers: SmallVec<[BlendMode; 8]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Builder<'a> {
|
impl<'a> SceneBuilder<'a> {
|
||||||
|
/// Creates a new builder for filling a scene. Any current content in the scene
|
||||||
|
/// will be cleared.
|
||||||
|
pub fn for_scene(scene: &'a mut Scene, rcx: &'a mut ResourceContext) -> Self {
|
||||||
|
Self::new(&mut scene.data, ResourceData::Scene(rcx))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new builder for filling a scene fragment. Any current content in
|
||||||
|
/// the fragment will be cleared.
|
||||||
|
pub fn for_fragment(fragment: &'a mut SceneFragment) -> Self {
|
||||||
|
Self::new(
|
||||||
|
&mut fragment.data,
|
||||||
|
ResourceData::Fragment(&mut fragment.resources),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a new builder for constructing a scene.
|
/// Creates a new builder for constructing a scene.
|
||||||
fn new(scene: &'a mut SceneData, mut resources: ResourceData<'a>) -> Self {
|
fn new(scene: &'a mut SceneData, mut resources: ResourceData<'a>) -> Self {
|
||||||
let is_fragment = match resources {
|
let is_fragment = match resources {
|
||||||
|
@ -57,30 +59,29 @@ impl<'a> Builder<'a> {
|
||||||
Self {
|
Self {
|
||||||
scene,
|
scene,
|
||||||
resources,
|
resources,
|
||||||
layers: vec![],
|
layers: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the current transformation.
|
/// Sets the current transformation.
|
||||||
pub fn transform(&mut self, transform: Affine) {
|
pub fn transform(&mut self, transform: Affine) {
|
||||||
self.encode_transform(transform);
|
if self.scene.transform_stream.last() != Some(&transform) {
|
||||||
|
self.encode_transform(transform);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pushes a new layer bound by the specifed shape and composed with
|
/// Pushes a new layer bound by the specifed shape and composed with
|
||||||
/// previous layers using the specified blend mode.
|
/// 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
|
where
|
||||||
E: IntoIterator,
|
E: IntoIterator,
|
||||||
E::IntoIter: Clone,
|
E::IntoIter: Clone,
|
||||||
E::Item: Borrow<Element>,
|
E::Item: Borrow<PathElement>,
|
||||||
{
|
{
|
||||||
self.linewidth(-1.0);
|
self.linewidth(-1.0);
|
||||||
let elements = elements.into_iter();
|
let elements = elements.into_iter();
|
||||||
self.encode_path(elements, true);
|
self.encode_path(elements, true);
|
||||||
self.begin_clip(Some(blend));
|
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);
|
self.layers.push(blend);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,32 +95,33 @@ impl<'a> Builder<'a> {
|
||||||
/// Fills a shape using the specified style and brush.
|
/// Fills a shape using the specified style and brush.
|
||||||
pub fn fill<'s, E>(
|
pub fn fill<'s, E>(
|
||||||
&mut self,
|
&mut self,
|
||||||
style: Fill,
|
_style: Fill,
|
||||||
brush: &Brush,
|
brush: &Brush,
|
||||||
brush_transform: Option<Affine>,
|
brush_transform: Option<Affine>,
|
||||||
elements: E,
|
elements: E,
|
||||||
) where
|
) where
|
||||||
E: IntoIterator,
|
E: IntoIterator,
|
||||||
E::IntoIter: Clone,
|
E::IntoIter: Clone,
|
||||||
E::Item: Borrow<Element>,
|
E::Item: Borrow<PathElement>,
|
||||||
{
|
{
|
||||||
self.linewidth(-1.0);
|
self.linewidth(-1.0);
|
||||||
let elements = elements.into_iter();
|
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(brush_transform) = brush_transform {
|
||||||
if let Some(last_transform) = self.scene.transform_stream.last().copied() {
|
if let Some(last_transform) = self.scene.transform_stream.last().copied() {
|
||||||
self.encode_transform(brush_transform * last_transform);
|
self.encode_transform(brush_transform * last_transform);
|
||||||
self.swap_last_tags();
|
self.swap_last_tags();
|
||||||
self.encode_brush(brush);
|
self.encode_brush(brush);
|
||||||
self.encode_transform(last_transform);
|
self.encode_transform(last_transform);
|
||||||
|
} else {
|
||||||
|
self.encode_transform(brush_transform);
|
||||||
|
self.swap_last_tags();
|
||||||
|
self.encode_brush(brush);
|
||||||
|
self.encode_transform(Affine::IDENTITY);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
self.encode_transform(brush_transform);
|
|
||||||
self.swap_last_tags();
|
|
||||||
self.encode_brush(brush);
|
self.encode_brush(brush);
|
||||||
self.encode_transform(Affine::IDENTITY);
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
self.encode_brush(brush);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,37 +136,38 @@ impl<'a> Builder<'a> {
|
||||||
D: Borrow<[f32]>,
|
D: Borrow<[f32]>,
|
||||||
E: IntoIterator,
|
E: IntoIterator,
|
||||||
E::IntoIter: Clone,
|
E::IntoIter: Clone,
|
||||||
E::Item: Borrow<Element>,
|
E::Item: Borrow<PathElement>,
|
||||||
{
|
{
|
||||||
self.linewidth(style.width);
|
self.linewidth(style.width);
|
||||||
let elements = elements.into_iter();
|
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(brush_transform) = brush_transform {
|
||||||
if let Some(last_transform) = self.scene.transform_stream.last().copied() {
|
if let Some(last_transform) = self.scene.transform_stream.last().copied() {
|
||||||
self.encode_transform(brush_transform * last_transform);
|
self.encode_transform(brush_transform * last_transform);
|
||||||
self.swap_last_tags();
|
self.swap_last_tags();
|
||||||
self.encode_brush(brush);
|
self.encode_brush(brush);
|
||||||
self.encode_transform(last_transform);
|
self.encode_transform(last_transform);
|
||||||
|
} else {
|
||||||
|
self.encode_transform(brush_transform);
|
||||||
|
self.swap_last_tags();
|
||||||
|
self.encode_brush(brush);
|
||||||
|
self.encode_transform(Affine::IDENTITY);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
self.encode_transform(brush_transform);
|
|
||||||
self.swap_last_tags();
|
|
||||||
self.encode_brush(brush);
|
self.encode_brush(brush);
|
||||||
self.encode_transform(Affine::IDENTITY);
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
self.encode_brush(brush);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Appends a fragment to the scene.
|
/// 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 drawdata_base = self.scene.drawdata_stream.len();
|
||||||
let mut cur_transform = self.scene.transform_stream.last().copied();
|
let mut cur_transform = self.scene.transform_stream.last().copied();
|
||||||
if let Some(transform) = transform {
|
if let Some(transform) = transform {
|
||||||
if cur_transform.is_none() {
|
if cur_transform.is_none() {
|
||||||
cur_transform = Some(Affine::IDENTITY);
|
cur_transform = Some(Affine::IDENTITY);
|
||||||
}
|
}
|
||||||
self.encode_transform(transform);
|
self.transform(transform);
|
||||||
} else if cur_transform != Some(Affine::IDENTITY) {
|
} else if cur_transform != Some(Affine::IDENTITY) {
|
||||||
self.encode_transform(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?
|
// Prevent fragments from affecting transform state. Should we allow this?
|
||||||
if let Some(transform) = cur_transform {
|
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> {
|
impl<'a> SceneBuilder<'a> {
|
||||||
fn encode_path<E>(&mut self, elements: E, is_fill: bool)
|
fn encode_path<E>(&mut self, elements: E, is_fill: bool) -> bool
|
||||||
where
|
where
|
||||||
E: Iterator,
|
E: Iterator,
|
||||||
E::Item: Borrow<Element>,
|
E::Item: Borrow<PathElement>,
|
||||||
{
|
{
|
||||||
if is_fill {
|
if is_fill {
|
||||||
self.encode_path_inner(
|
self.encode_path_inner(
|
||||||
|
@ -228,34 +231,39 @@ impl<'a> Builder<'a> {
|
||||||
.map(|el| *el.borrow())
|
.map(|el| *el.borrow())
|
||||||
.flat_map(|el| {
|
.flat_map(|el| {
|
||||||
match el {
|
match el {
|
||||||
Element::MoveTo(..) => Some(Element::Close),
|
PathElement::MoveTo(..) => Some(PathElement::Close),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(Some(el))
|
.chain(Some(el))
|
||||||
})
|
})
|
||||||
.chain(Some(Element::Close)),
|
.chain(Some(PathElement::Close)),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
self.encode_path_inner(elements.map(|el| *el.borrow()))
|
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 b = PathBuilder::new(&mut self.scene.tag_stream, &mut self.scene.pathseg_stream);
|
||||||
|
let mut has_els = false;
|
||||||
for el in elements {
|
for el in elements {
|
||||||
match el {
|
match el {
|
||||||
Element::MoveTo(p0) => b.move_to(p0.x, p0.y),
|
PathElement::MoveTo(p0) => b.move_to(p0.x, p0.y),
|
||||||
Element::LineTo(p0) => b.line_to(p0.x, p0.y),
|
PathElement::LineTo(p0) => b.line_to(p0.x, p0.y),
|
||||||
Element::QuadTo(p0, p1) => b.quad_to(p0.x, p0.y, p1.x, p1.y),
|
PathElement::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),
|
PathElement::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::Close => b.close_path(),
|
||||||
}
|
}
|
||||||
|
has_els = true;
|
||||||
}
|
}
|
||||||
b.path();
|
if has_els {
|
||||||
let n_pathseg = b.n_pathseg();
|
b.path();
|
||||||
self.scene.n_path += 1;
|
let n_pathseg = b.n_pathseg();
|
||||||
self.scene.n_pathseg += n_pathseg;
|
self.scene.n_path += 1;
|
||||||
|
self.scene.n_pathseg += n_pathseg;
|
||||||
|
}
|
||||||
|
has_els
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode_transform(&mut self, transform: Affine) {
|
fn encode_transform(&mut self, transform: Affine) {
|
||||||
|
@ -272,8 +280,10 @@ impl<'a> Builder<'a> {
|
||||||
|
|
||||||
// -1.0 means "fill"
|
// -1.0 means "fill"
|
||||||
fn linewidth(&mut self, linewidth: f32) {
|
fn linewidth(&mut self, linewidth: f32) {
|
||||||
self.scene.tag_stream.push(0x40);
|
if self.scene.linewidth_stream.last() != Some(&linewidth) {
|
||||||
self.scene.linewidth_stream.push(linewidth);
|
self.scene.tag_stream.push(0x40);
|
||||||
|
self.scene.linewidth_stream.push(linewidth);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode_brush(&mut self, brush: &Brush) {
|
fn encode_brush(&mut self, brush: &Brush) {
|
||||||
|
@ -311,11 +321,10 @@ impl<'a> Builder<'a> {
|
||||||
}
|
}
|
||||||
Brush::SweepGradient(_gradient) => todo!("sweep gradients aren't done yet!"),
|
Brush::SweepGradient(_gradient) => todo!("sweep gradients aren't done yet!"),
|
||||||
Brush::Image(_image) => todo!("images 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 {
|
match &mut self.resources {
|
||||||
ResourceData::Scene(res) => res.add_ramp(stops),
|
ResourceData::Scene(res) => res.add_ramp(stops),
|
||||||
ResourceData::Fragment(res) => {
|
ResourceData::Fragment(res) => {
|
||||||
|
@ -332,10 +341,10 @@ impl<'a> Builder<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start a clip.
|
/// 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);
|
self.scene.drawtag_stream.push(DRAWTAG_BEGINCLIP);
|
||||||
let element = Clip {
|
let element = Clip {
|
||||||
blend: blend.unwrap_or(Blend::default()).pack(),
|
blend: blend.unwrap_or(BlendMode::default()).pack(),
|
||||||
};
|
};
|
||||||
self.scene
|
self.scene
|
||||||
.drawdata_stream
|
.drawdata_stream
|
||||||
|
@ -343,10 +352,10 @@ impl<'a> Builder<'a> {
|
||||||
self.scene.n_clip += 1;
|
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);
|
self.scene.drawtag_stream.push(DRAWTAG_ENDCLIP);
|
||||||
let element = Clip {
|
let element = Clip {
|
||||||
blend: blend.unwrap_or(Blend::default()).pack(),
|
blend: blend.unwrap_or(BlendMode::default()).pack(),
|
||||||
};
|
};
|
||||||
self.scene
|
self.scene
|
||||||
.drawdata_stream
|
.drawdata_stream
|
||||||
|
@ -357,7 +366,6 @@ impl<'a> Builder<'a> {
|
||||||
self.scene.n_clip += 1;
|
self.scene.n_clip += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ResourceData<'a> {
|
enum ResourceData<'a> {
|
||||||
Fragment(&'a mut FragmentResources),
|
Fragment(&'a mut FragmentResources),
|
||||||
Scene(&'a mut ResourceContext),
|
Scene(&'a mut ResourceContext),
|
||||||
|
|
|
@ -18,13 +18,13 @@ mod blend;
|
||||||
mod builder;
|
mod builder;
|
||||||
mod style;
|
mod style;
|
||||||
|
|
||||||
pub use blend::{Blend, Compose, Mix};
|
pub use blend::{BlendMode, Compose, Mix};
|
||||||
pub use builder::{build_fragment, build_scene, Builder};
|
pub use builder::SceneBuilder;
|
||||||
pub use style::*;
|
pub use style::*;
|
||||||
|
|
||||||
use super::brush::*;
|
use super::brush::*;
|
||||||
use super::geometry::{Affine, Point};
|
use super::geometry::{Affine, Point};
|
||||||
use super::path::Element;
|
use super::path::PathElement;
|
||||||
|
|
||||||
use core::ops::Range;
|
use core::ops::Range;
|
||||||
|
|
||||||
|
@ -43,6 +43,10 @@ pub struct SceneData {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SceneData {
|
impl SceneData {
|
||||||
|
fn is_empty(&self) -> bool {
|
||||||
|
self.pathseg_stream.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
fn reset(&mut self, is_fragment: bool) {
|
fn reset(&mut self, is_fragment: bool) {
|
||||||
self.transform_stream.clear();
|
self.transform_stream.clear();
|
||||||
self.tag_stream.clear();
|
self.tag_stream.clear();
|
||||||
|
@ -97,23 +101,32 @@ impl Scene {
|
||||||
|
|
||||||
/// Encoded definition of a scene fragment and associated resources.
|
/// Encoded definition of a scene fragment and associated resources.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Fragment {
|
pub struct SceneFragment {
|
||||||
data: SceneData,
|
data: SceneData,
|
||||||
resources: FragmentResources,
|
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
|
/// Returns the underlying stream of points that defined all encoded path
|
||||||
/// segments.
|
/// segments.
|
||||||
pub fn points(&self) -> &[Point] {
|
pub fn points(&self) -> &[Point] {
|
||||||
bytemuck::cast_slice(&self.data.pathseg_stream)
|
if self.is_empty() {
|
||||||
|
&[]
|
||||||
|
} else {
|
||||||
|
bytemuck::cast_slice(&self.data.pathseg_stream)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct FragmentResources {
|
struct FragmentResources {
|
||||||
patches: Vec<ResourcePatch>,
|
patches: Vec<ResourcePatch>,
|
||||||
stops: Vec<Stop>,
|
stops: Vec<GradientStop>,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ResourcePatch {
|
enum ResourcePatch {
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
//! Tests for the piet-gpu draw object stage.
|
//! Tests for the piet-gpu draw object stage.
|
||||||
|
|
||||||
use piet_gpu_hal::{BufWrite, BufferUsage};
|
use piet_gpu_hal::{BufWrite, BufferUsage};
|
||||||
use rand::{seq::SliceRandom, Rng};
|
use rand::seq::SliceRandom;
|
||||||
|
|
||||||
use crate::{Config, Runner, TestResult};
|
use crate::{Config, Runner, TestResult};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue