From 532b6ee808b223081dc6f356a6084179fbef22ef Mon Sep 17 00:00:00 2001 From: Chad Brokaw Date: Tue, 10 May 2022 03:56:06 -0400 Subject: [PATCH] Add C api for glyph rendering First cut at a public C api that supports glyph rendering on Metal targets. --- Cargo.lock | 132 ++++++++++++++++++++ Cargo.toml | 1 + pgpu-render/Cargo.toml | 21 ++++ pgpu-render/build.rs | 29 +++++ pgpu-render/pgpu.h | 140 ++++++++++++++++++++++ pgpu-render/src/lib.rs | 233 ++++++++++++++++++++++++++++++++++++ pgpu-render/src/render.rs | 222 ++++++++++++++++++++++++++++++++++ piet-scene/src/geometry.rs | 4 +- piet-scene/src/glyph/mod.rs | 74 ++++++++++-- piet-scene/src/scene/mod.rs | 9 +- 10 files changed, 853 insertions(+), 12 deletions(-) create mode 100644 pgpu-render/Cargo.toml create mode 100644 pgpu-render/build.rs create mode 100644 pgpu-render/pgpu.h create mode 100644 pgpu-render/src/lib.rs create mode 100644 pgpu-render/src/render.rs diff --git a/Cargo.lock b/Cargo.lock index cb6b76a..938c91f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -127,6 +127,25 @@ dependencies = [ "nix 0.18.0", ] +[[package]] +name = "cbindgen" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51e3973b165dc0f435831a9e426de67e894de532754ff7a3f307c03ee5dec7dc" +dependencies = [ + "clap", + "heck", + "indexmap", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn", + "tempfile", + "toml", +] + [[package]] name = "cc" version = "1.0.73" @@ -452,6 +471,15 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + [[package]] name = "fnv" version = "1.0.7" @@ -501,6 +529,21 @@ version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "hermit-abi" version = "0.1.19" @@ -516,6 +559,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "indexmap" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" +dependencies = [ + "autocfg", + "hashbrown", +] + [[package]] name = "instant" version = "0.1.12" @@ -525,6 +578,12 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "itoa" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" + [[package]] name = "jni-sys" version = "0.3.0" @@ -876,6 +935,19 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +[[package]] +name = "pgpu-render" +version = "0.1.0" +dependencies = [ + "cbindgen", + "cocoa", + "metal", + "objc", + "piet-gpu", + "piet-gpu-hal", + "piet-scene", +] + [[package]] name = "piet" version = "0.2.0" @@ -1131,6 +1203,15 @@ dependencies = [ "thiserror", ] +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + [[package]] name = "roxmltree" version = "0.13.1" @@ -1150,6 +1231,12 @@ dependencies = [ "owned_ttf_parser", ] +[[package]] +name = "ryu" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" + [[package]] name = "same-file" version = "1.0.6" @@ -1176,6 +1263,31 @@ name = "serde" version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +dependencies = [ + "itoa", + "ryu", + "serde", +] [[package]] name = "smallvec" @@ -1235,6 +1347,20 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if 1.0.0", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + [[package]] name = "textwrap" version = "0.11.0" @@ -1330,6 +1456,12 @@ dependencies = [ "unic-common", ] +[[package]] +name = "unicode-segmentation" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" + [[package]] name = "unicode-width" version = "0.1.9" diff --git a/Cargo.toml b/Cargo.toml index b94b82b..88d9611 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ + "pgpu-render", "piet-gpu", "piet-gpu-derive", "piet-gpu-hal", diff --git a/pgpu-render/Cargo.toml b/pgpu-render/Cargo.toml new file mode 100644 index 0000000..8581d82 --- /dev/null +++ b/pgpu-render/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "pgpu-render" +version = "0.1.0" +description = "C interface for glyph rendering using piet-gpu." +license = "MIT/Apache-2.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +piet-gpu = { path = "../piet-gpu" } +piet-gpu-hal = { path = "../piet-gpu-hal" } +piet-scene = { path = "../piet-scene" } + +metal = "0.22" +cocoa = "0.24.0" +objc = "0.2.5" + +[build-dependencies] +cbindgen = "0.20.0" diff --git a/pgpu-render/build.rs b/pgpu-render/build.rs new file mode 100644 index 0000000..182c72c --- /dev/null +++ b/pgpu-render/build.rs @@ -0,0 +1,29 @@ +// 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. + +extern crate cbindgen; + +use std::env; + +fn main() { + let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + + cbindgen::Builder::new() + .with_crate(crate_dir) + .generate() + .expect("Unable to generate bindings") + .write_to_file("pgpu.h"); +} diff --git a/pgpu-render/pgpu.h b/pgpu-render/pgpu.h new file mode 100644 index 0000000..7f93625 --- /dev/null +++ b/pgpu-render/pgpu.h @@ -0,0 +1,140 @@ +#include +#include +#include +#include +#include + +/// Encoded (possibly color) outline for a glyph. +struct PgpuGlyph; + +/// Context for loading and scaling glyphs. +struct PgpuGlyphContext; + +/// Context for loading a scaling glyphs from a specific font. +struct PgpuGlyphProvider; + +/// State and resources for rendering a scene. +struct PgpuRenderer; + +/// Encoded streams and resources describing a vector graphics scene. +struct PgpuScene; + +/// Builder for constructing an encoded scene. +struct PgpuSceneBuilder; + +/// Tag and value for a font variation axis. +struct PgpuFontVariation { + /// Tag that specifies the axis. + uint32_t tag; + /// Requested setting for the axis. + float value; +}; + +/// Description of a font. +struct PgpuFontDesc { + /// Pointer to the context of the font file. + const uint8_t *data; + /// Size of the font file data in bytes. + uintptr_t data_len; + /// Index of the requested font in the font file. + uint32_t index; + /// Unique identifier for the font. + uint64_t unique_id; + /// Requested size in pixels per em unit. Set to 0.0 for + /// unscaled outlines. + float ppem; + /// Pointer to array of font variation settings. + const PgpuFontVariation *variations; + /// Number of font variation settings. + uintptr_t variations_len; +}; + +/// Rectangle defined by minimum and maximum points. +struct PgpuRect { + float x0; + float y0; + float x1; + float y1; +}; + +extern "C" { + +/// Creates a new piet-gpu renderer for the specified Metal device and +/// command queue. +/// +/// device: MTLDevice* +/// queue: MTLCommandQueue* +PgpuRenderer *pgpu_renderer_new(void *device, void *queue); + +/// Renders a prepared scene into a texture target. Commands for rendering are +/// recorded into the specified command buffer. Returns an id representing +/// resources that may have been allocated during this process. After the +/// command buffer has been retired, call `pgpu_renderer_release` with this id +/// to drop any associated resources. +/// +/// target: MTLTexture* +/// cmdbuf: MTLCommandBuffer* +uint32_t pgpu_renderer_render(PgpuRenderer *renderer, + const PgpuScene *scene, + void *target, + void *cmdbuf); + +/// Releases the internal resources associated with the specified id from a +/// previous render operation. +void pgpu_renderer_release(PgpuRenderer *renderer, uint32_t id); + +/// Destroys the piet-gpu renderer. +void pgpu_renderer_destroy(PgpuRenderer *renderer); + +/// Creates a new, empty piet-gpu scene. +PgpuScene *pgpu_scene_new(); + +/// Destroys the piet-gpu scene. +void pgpu_scene_destroy(PgpuScene *scene); + +/// Creates a new builder for filling a piet-gpu scene. The specified scene +/// should not be accessed while the builder is live. +PgpuSceneBuilder *pgpu_scene_builder_new(PgpuScene *scene); + +/// Adds a glyph with the specified transform to the underlying scene. +void pgpu_scene_builder_add_glyph(PgpuSceneBuilder *builder, + const PgpuGlyph *glyph, + const float (*transform)[6]); + +/// Finalizes the scene builder, making the underlying scene ready for +/// rendering. This takes ownership and consumes the builder. +void pgpu_scene_builder_finish(PgpuSceneBuilder *builder); + +/// Creates a new context for loading glyph outlines. +PgpuGlyphContext *pgpu_glyph_context_new(); + +/// Destroys the glyph context. +void pgpu_glyph_context_destroy(PgpuGlyphContext *gcx); + +/// Creates a new glyph provider for the specified glyph context and font +/// descriptor. May return nullptr if the font data is invalid. Only one glyph +/// provider may be live for a glyph context. +PgpuGlyphProvider *pgpu_glyph_provider_new(PgpuGlyphContext *gcx, const PgpuFontDesc *font); + +/// Returns an encoded outline for the specified glyph provider and glyph id. +/// May return nullptr if the requested glyph is not available. +const PgpuGlyph *pgpu_glyph_provider_get(PgpuGlyphProvider *provider, uint16_t gid); + +/// Returns an encoded color outline for the specified glyph provider, color +/// palette index and glyph id. May return nullptr if the requested glyph is +/// not available. +PgpuGlyph *pgpu_glyph_provider_get_color(PgpuGlyphProvider *provider, + uint16_t palette_index, + uint16_t gid); + +/// Destroys the glyph provider. +void pgpu_glyph_provider_destroy(PgpuGlyphProvider *provider); + +/// Computes the bounding box for the glyph after applying the specified +/// transform. +PgpuRect pgpu_glyph_bbox(const PgpuGlyph *glyph, const float (*transform)[6]); + +/// Destroys the glyph. +void pgpu_glyph_destroy(PgpuGlyph *glyph); + +} // extern "C" diff --git a/pgpu-render/src/lib.rs b/pgpu-render/src/lib.rs new file mode 100644 index 0000000..2036f57 --- /dev/null +++ b/pgpu-render/src/lib.rs @@ -0,0 +1,233 @@ +// 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. + +mod render; + +use render::*; +use std::ffi::c_void; +use std::mem::transmute; + +/// Creates a new piet-gpu renderer for the specified Metal device and +/// command queue. +/// +/// device: MTLDevice* +/// queue: MTLCommandQueue* +#[no_mangle] +pub unsafe extern "C" fn pgpu_renderer_new( + device: *mut c_void, + queue: *mut c_void, +) -> *mut PgpuRenderer { + let device: &metal::DeviceRef = transmute(device); + let queue: &metal::CommandQueueRef = transmute(queue); + Box::into_raw(Box::new(PgpuRenderer::new(device, queue))) +} + +/// Renders a prepared scene into a texture target. Commands for rendering are +/// recorded into the specified command buffer. Returns an id representing +/// resources that may have been allocated during this process. After the +/// command buffer has been retired, call `pgpu_renderer_release` with this id +/// to drop any associated resources. +/// +/// target: MTLTexture* +/// cmdbuf: MTLCommandBuffer* +#[no_mangle] +pub unsafe extern "C" fn pgpu_renderer_render( + renderer: *mut PgpuRenderer, + scene: *const PgpuScene, + target: *mut c_void, + cmdbuf: *mut c_void, +) -> u32 { + let cmdbuf: &metal::CommandBufferRef = transmute(cmdbuf); + let target: &metal::TextureRef = transmute(target); + (*renderer).render(&*scene, cmdbuf, target) +} + +/// Releases the internal resources associated with the specified id from a +/// previous render operation. +#[no_mangle] +pub unsafe extern "C" fn pgpu_renderer_release(renderer: *mut PgpuRenderer, id: u32) { + (*renderer).release(id); +} + +/// Destroys the piet-gpu renderer. +#[no_mangle] +pub unsafe extern "C" fn pgpu_renderer_destroy(renderer: *mut PgpuRenderer) { + Box::from_raw(renderer); +} + +/// Creates a new, empty piet-gpu scene. +#[no_mangle] +pub unsafe extern "C" fn pgpu_scene_new() -> *mut PgpuScene { + Box::into_raw(Box::new(PgpuScene::new())) +} + +/// Destroys the piet-gpu scene. +#[no_mangle] +pub unsafe extern "C" fn pgpu_scene_destroy(scene: *mut PgpuScene) { + Box::from_raw(scene); +} + +/// Creates a new builder for filling a piet-gpu scene. The specified scene +/// should not be accessed while the builder is live. +#[no_mangle] +pub unsafe extern "C" fn pgpu_scene_builder_new( + scene: *mut PgpuScene, +) -> *mut PgpuSceneBuilder<'static> { + Box::into_raw(Box::new((*scene).build())) +} + +/// Adds a glyph with the specified transform to the underlying scene. +#[no_mangle] +pub unsafe extern "C" fn pgpu_scene_builder_add_glyph( + builder: *mut PgpuSceneBuilder<'static>, + glyph: *const PgpuGlyph, + transform: &[f32; 6], +) { + let transform = piet_scene::geometry::Affine::new(transform); + (*builder).add_glyph(&*glyph, &transform); +} + +/// Finalizes the scene builder, making the underlying scene ready for +/// rendering. This takes ownership and consumes the builder. +#[no_mangle] +pub unsafe extern "C" fn pgpu_scene_builder_finish(builder: *mut PgpuSceneBuilder<'static>) { + let builder = Box::from_raw(builder); + builder.finish(); +} + +/// Creates a new context for loading glyph outlines. +#[no_mangle] +pub unsafe extern "C" fn pgpu_glyph_context_new() -> *mut PgpuGlyphContext { + Box::into_raw(Box::new(PgpuGlyphContext::new())) +} + +/// Destroys the glyph context. +#[no_mangle] +pub unsafe extern "C" fn pgpu_glyph_context_destroy(gcx: *mut PgpuGlyphContext) { + Box::from_raw(gcx); +} + +/// Description of a font. +#[derive(Copy, Clone)] +#[repr(C)] +pub struct PgpuFontDesc { + /// Pointer to the context of the font file. + data: *const u8, + /// Size of the font file data in bytes. + data_len: usize, + /// Index of the requested font in the font file. + index: u32, + /// Unique identifier for the font. + unique_id: u64, + /// Requested size in pixels per em unit. Set to 0.0 for + /// unscaled outlines. + ppem: f32, + /// Pointer to array of font variation settings. + variations: *const PgpuFontVariation, + /// Number of font variation settings. + variations_len: usize, +} + +/// Creates a new glyph provider for the specified glyph context and font +/// descriptor. May return nullptr if the font data is invalid. Only one glyph +/// provider may be live for a glyph context. +#[no_mangle] +pub unsafe extern "C" fn pgpu_glyph_provider_new( + gcx: *mut PgpuGlyphContext, + font: *const PgpuFontDesc, +) -> *mut PgpuGlyphProvider<'static> { + let font = &*font; + let font_data = std::slice::from_raw_parts(font.data, font.data_len); + let variations = std::slice::from_raw_parts(font.variations, font.variations_len); + if let Some(provider) = (*gcx).new_provider( + font_data, + font.index, + font.unique_id, + font.ppem, + false, + variations, + ) { + Box::into_raw(Box::new(provider)) + } else { + std::ptr::null_mut() + } +} + +/// Returns an encoded outline for the specified glyph provider and glyph id. +/// May return nullptr if the requested glyph is not available. +#[no_mangle] +pub unsafe extern "C" fn pgpu_glyph_provider_get( + provider: *mut PgpuGlyphProvider, + gid: u16, +) -> *const PgpuGlyph { + if let Some(glyph) = (*provider).get(gid) { + Box::into_raw(Box::new(glyph)) + } else { + std::ptr::null() + } +} + +/// Returns an encoded color outline for the specified glyph provider, color +/// palette index and glyph id. May return nullptr if the requested glyph is +/// not available. +#[no_mangle] +pub unsafe extern "C" fn pgpu_glyph_provider_get_color( + provider: *mut PgpuGlyphProvider, + palette_index: u16, + gid: u16, +) -> *mut PgpuGlyph { + if let Some(glyph) = (*provider).get_color(palette_index, gid) { + Box::into_raw(Box::new(glyph)) + } else { + std::ptr::null_mut() + } +} + +/// Destroys the glyph provider. +#[no_mangle] +pub unsafe extern "C" fn pgpu_glyph_provider_destroy(provider: *mut PgpuGlyphProvider) { + Box::from_raw(provider); +} + +/// Rectangle defined by minimum and maximum points. +#[derive(Copy, Clone, Default)] +#[repr(C)] +pub struct PgpuRect { + pub x0: f32, + pub y0: f32, + pub x1: f32, + pub y1: f32, +} + +/// Computes the bounding box for the glyph after applying the specified +/// transform. +#[no_mangle] +pub unsafe extern "C" fn pgpu_glyph_bbox(glyph: *const PgpuGlyph, transform: &[f32; 6]) -> PgpuRect { + let transform = piet_scene::geometry::Affine::new(transform); + let rect = (*glyph).bbox(Some(transform)); + PgpuRect { + x0: rect.min.x, + y0: rect.min.y, + x1: rect.max.x, + y1: rect.max.y, + } +} + +/// Destroys the glyph. +#[no_mangle] +pub unsafe extern "C" fn pgpu_glyph_destroy(glyph: *mut PgpuGlyph) { + Box::from_raw(glyph); +} diff --git a/pgpu-render/src/render.rs b/pgpu-render/src/render.rs new file mode 100644 index 0000000..361ef42 --- /dev/null +++ b/pgpu-render/src/render.rs @@ -0,0 +1,222 @@ +// 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_gpu::{EncodedSceneRef, PixelFormat, RenderConfig}; +use piet_gpu_hal::{QueryPool, Session}; +use piet_scene::glyph::pinot::{types::Tag, FontDataRef}; +use piet_scene::geometry::{Affine, Rect}; +use piet_scene::glyph::{GlyphContext, GlyphProvider}; +use piet_scene::resource::ResourceContext; +use piet_scene::scene::{Fragment, Scene}; + +/// State and resources for rendering a scene. +pub struct PgpuRenderer { + session: Session, + pgpu_renderer: Option, + query_pool: QueryPool, + width: u32, + height: u32, + is_color: bool, +} + +impl PgpuRenderer { + pub fn new(device: &metal::DeviceRef, queue: &metal::CommandQueueRef) -> Self { + let piet_device = piet_gpu_hal::Device::new_from_raw_mtl(device, &queue); + let session = Session::new(piet_device); + let query_pool = session.create_query_pool(12).unwrap(); + Self { + session, + pgpu_renderer: None, + query_pool, + width: 0, + height: 0, + is_color: false, + } + } + + pub fn render( + &mut self, + scene: &PgpuScene, + cmdbuf: &metal::CommandBufferRef, + target: &metal::TextureRef, + ) -> u32 { + let is_color = target.pixel_format() != metal::MTLPixelFormat::A8Unorm; + let width = target.width() as u32; + let height = target.height() as u32; + if self.pgpu_renderer.is_none() + || self.width != width + || self.height != height + || self.is_color != is_color + { + self.width = width; + self.height = height; + self.is_color = is_color; + let format = if is_color { + PixelFormat::Rgba8 + } else { + PixelFormat::A8 + }; + let config = RenderConfig::new(width as usize, height as usize).pixel_format(format); + unsafe { + self.pgpu_renderer = + piet_gpu::Renderer::new_from_config(&self.session, config, 1).ok(); + } + } + unsafe { + let mut cmd_buf = self.session.cmd_buf_from_raw_mtl(cmdbuf); + let dst_image = self + .session + .image_from_raw_mtl(target, self.width, self.height); + if let Some(renderer) = &mut self.pgpu_renderer { + renderer.upload_scene(&scene.encoded_scene(), 0).unwrap(); + renderer.record(&mut cmd_buf, &self.query_pool, 0); + // TODO later: we can bind the destination image and avoid the copy. + cmd_buf.blit_image(&renderer.image_dev, &dst_image); + } + } + 0 + } + + pub fn release(&mut self, _id: u32) { + // TODO: worry about freeing resources / managing overlapping submits + } +} + +/// Encoded streams and resources describing a vector graphics scene. +pub struct PgpuScene { + scene: Scene, + rcx: ResourceContext, +} + +impl PgpuScene { + pub fn new() -> Self { + Self { + scene: Scene::default(), + rcx: ResourceContext::new(), + } + } + + pub fn build(&mut self) -> PgpuSceneBuilder { + self.rcx.advance(); + PgpuSceneBuilder(piet_scene::scene::build_scene( + &mut self.scene, + &mut self.rcx, + )) + } + + fn encoded_scene<'a>(&'a self) -> EncodedSceneRef<'a, piet_scene::geometry::Affine> { + let d = self.scene.data(); + EncodedSceneRef { + transform_stream: &d.transform_stream, + tag_stream: &d.tag_stream, + pathseg_stream: &d.pathseg_stream, + linewidth_stream: &d.linewidth_stream, + drawtag_stream: &d.drawtag_stream, + drawdata_stream: &d.drawdata_stream, + n_path: d.n_path, + n_pathseg: d.n_pathseg, + n_clip: d.n_clip, + ramp_data: self.rcx.ramp_data(), + } + } +} + +/// Builder for constructing an encoded scene. +pub struct PgpuSceneBuilder<'a>(piet_scene::scene::Builder<'a>); + +impl<'a> PgpuSceneBuilder<'a> { + pub fn add_glyph(&mut self, glyph: &PgpuGlyph, transform: &piet_scene::geometry::Affine) { + self.0.push_transform(*transform); + self.0.append(&glyph.fragment); + self.0.pop_transform(); + } + + pub fn finish(self) { + self.0.finish(); + } +} + +/// Tag and value for a font variation axis. +#[derive(Copy, Clone)] +#[repr(C)] +pub struct PgpuFontVariation { + /// Tag that specifies the axis. + pub tag: u32, + /// Requested setting for the axis. + pub value: f32, +} + +/// Context for loading and scaling glyphs. +pub struct PgpuGlyphContext(GlyphContext); + +impl PgpuGlyphContext { + pub fn new() -> Self { + Self(GlyphContext::new()) + } + + pub fn new_provider<'a>( + &'a mut self, + font_data: &'a [u8], + font_index: u32, + font_id: u64, + ppem: f32, + hint: bool, + variations: &[PgpuFontVariation], + ) -> Option { + let font = FontDataRef::new(font_data).and_then(|f| f.get(font_index))?; + Some(PgpuGlyphProvider( + self.0.new_provider( + &font, + Some(font_id), + ppem, + hint, + variations + .iter() + .map(|variation| (Tag(variation.tag), variation.value)), + ), + )) + } +} + +/// Context for loading a scaling glyphs from a specific font. +pub struct PgpuGlyphProvider<'a>(GlyphProvider<'a>); + +impl<'a> PgpuGlyphProvider<'a> { + pub fn get(&mut self, gid: u16) -> Option { + let fragment = self.0.get(gid)?; + Some(PgpuGlyph { fragment }) + } + + pub fn get_color(&mut self, palette_index: u16, gid: u16) -> Option { + let fragment = self.0.get_color(palette_index, gid)?; + Some(PgpuGlyph { fragment }) + } +} + +/// Encoded (possibly color) outline for a glyph. +pub struct PgpuGlyph { + fragment: Fragment, +} + +impl PgpuGlyph { + pub fn bbox(&self, transform: Option) -> Rect { + if let Some(transform) = &transform { + Rect::from_points(self.fragment.points().iter().map(|p| p.transform(transform))) + } else { + Rect::from_points(self.fragment.points()) + } + } +} diff --git a/piet-scene/src/geometry.rs b/piet-scene/src/geometry.rs index fbc8765..d844013 100644 --- a/piet-scene/src/geometry.rs +++ b/piet-scene/src/geometry.rs @@ -19,7 +19,7 @@ use core::borrow::Borrow; use core::hash::{Hash, Hasher}; /// Two dimensional point. -#[derive(Copy, Clone, PartialEq, PartialOrd, Default, Debug)] +#[derive(Copy, Clone, PartialEq, PartialOrd, Default, Debug, Pod, Zeroable)] #[repr(C)] pub struct Point { pub x: f32, @@ -168,7 +168,7 @@ impl std::ops::Mul for Affine { } /// Axis-aligned rectangle represented as minimum and maximum points. -#[derive(Copy, Clone, Default, Debug)] +#[derive(Copy, Clone, Default, Debug, Pod, Zeroable)] #[repr(C)] pub struct Rect { pub min: Point, diff --git a/piet-scene/src/glyph/mod.rs b/piet-scene/src/glyph/mod.rs index 0e13211..24e458b 100644 --- a/piet-scene/src/glyph/mod.rs +++ b/piet-scene/src/glyph/mod.rs @@ -17,11 +17,15 @@ pub use pinot; use crate::brush::{Brush, Color}; +use crate::geometry::Affine; use crate::path::Element; use crate::scene::{build_fragment, Fill, Fragment}; + use moscato::{Context, Scaler}; use pinot::{types::Tag, FontRef}; +use smallvec::SmallVec; + /// General context for creating scene fragments for glyph outlines. pub struct GlyphContext { ctx: Context, @@ -99,40 +103,70 @@ impl<'a> GlyphProvider<'a> { let glyph = self.scaler.color_glyph(palette_index, gid)?; let mut fragment = Fragment::default(); let mut builder = build_fragment(&mut fragment); + let mut xform_stack: SmallVec<[Affine; 8]> = SmallVec::new(); for command in glyph.commands() { match command { Command::PushTransform(xform) => { - builder.push_transform(convert_transform(xform)); + xform_stack.push(convert_transform(xform)); } - Command::PopTransform => builder.pop_transform(), + Command::PopTransform => { xform_stack.pop(); }, Command::PushClip(path_index) => { let path = glyph.path(*path_index)?; - builder.push_layer(Default::default(), convert_path(path.elements())); + if let Some(xform) = xform_stack.last() { + builder.push_layer( + Default::default(), + convert_transformed_path(path.elements(), xform), + ); + } else { + builder.push_layer(Default::default(), convert_path(path.elements())); + } } Command::PopClip => builder.pop_layer(), Command::PushLayer(bounds) => { - let rect = Rect { + let mut rect = Rect { min: Point::new(bounds.min.x, bounds.min.y), max: Point::new(bounds.max.x, bounds.max.y), }; + if let Some(xform) = xform_stack.last() { + rect.min = rect.min.transform(xform); + rect.max = rect.max.transform(xform); + } builder.push_layer(Default::default(), rect.elements()); } Command::PopLayer => builder.pop_layer(), Command::BeginBlend(bounds, mode) => { - let rect = Rect { + let mut rect = Rect { min: Point::new(bounds.min.x, bounds.min.y), max: Point::new(bounds.max.x, bounds.max.y), }; + if let Some(xform) = xform_stack.last() { + rect.min = rect.min.transform(xform); + rect.max = rect.max.transform(xform); + } builder.push_layer(convert_blend(*mode), rect.elements()) } Command::EndBlend => builder.pop_layer(), - Command::SimpleFill(path_index, brush, xform) => { + Command::SimpleFill(path_index, brush, brush_xform) => { let path = glyph.path(*path_index)?; let brush = convert_brush(brush); - let xform = xform.map(|xform| convert_transform(&xform)); - builder.fill(Fill::NonZero, &brush, xform, convert_path(path.elements())); + let brush_xform = brush_xform.map(|xform| convert_transform(&xform)); + if let Some(xform) = xform_stack.last() { + builder.fill( + Fill::NonZero, + &brush, + brush_xform, + convert_transformed_path(path.elements(), xform), + ); + } else { + builder.fill( + Fill::NonZero, + &brush, + brush_xform, + convert_path(path.elements()), + ); + } } - Command::Fill(brush, xform) => { + Command::Fill(brush, brush_xform) => { // TODO: this needs to compute a bounding box for // the parent clips } @@ -161,6 +195,28 @@ fn convert_path( }) } +fn convert_transformed_path( + path: impl Iterator + Clone, + xform: &Affine, +) -> impl Iterator + Clone { + use crate::geometry::Point; + let xform = *xform; + path.map(move |el| match el { + moscato::Element::MoveTo(p0) => Element::MoveTo(Point::new(p0.x, p0.y).transform(&xform)), + moscato::Element::LineTo(p0) => Element::LineTo(Point::new(p0.x, p0.y).transform(&xform)), + moscato::Element::QuadTo(p0, p1) => Element::QuadTo( + Point::new(p0.x, p0.y).transform(&xform), + Point::new(p1.x, p1.y).transform(&xform), + ), + moscato::Element::CurveTo(p0, p1, p2) => Element::CurveTo( + Point::new(p0.x, p0.y).transform(&xform), + Point::new(p1.x, p1.y).transform(&xform), + Point::new(p2.x, p2.y).transform(&xform), + ), + moscato::Element::Close => Element::Close, + }) +} + fn convert_blend(mode: moscato::CompositeMode) -> crate::scene::Blend { use crate::scene::{Blend, Compose, Mix}; use moscato::CompositeMode; diff --git a/piet-scene/src/scene/mod.rs b/piet-scene/src/scene/mod.rs index ba0b069..9f7be2f 100644 --- a/piet-scene/src/scene/mod.rs +++ b/piet-scene/src/scene/mod.rs @@ -23,8 +23,9 @@ pub use builder::{build_fragment, build_scene, Builder}; pub use style::*; use super::brush::*; -use super::geometry::{Affine, Rect}; +use super::geometry::{Affine, Point, Rect}; use super::path::Element; + use core::ops::Range; #[derive(Default)] @@ -94,6 +95,12 @@ pub struct Fragment { resources: FragmentResources, } +impl Fragment { + pub fn points(&self) -> &[Point] { + bytemuck::cast_slice(&self.data.pathseg_stream) + } +} + #[derive(Default)] struct FragmentResources { patches: Vec,