// 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()) } } }