vello/pgpu-render/src/render.rs
Chad Brokaw 532b6ee808 Add C api for glyph rendering
First cut at a public C api that supports glyph rendering on Metal targets.
2022-05-10 03:56:06 -04:00

223 lines
6.8 KiB
Rust

// 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<piet_gpu::Renderer>,
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<PgpuGlyphProvider> {
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<PgpuGlyph> {
let fragment = self.0.get(gid)?;
Some(PgpuGlyph { fragment })
}
pub fn get_color(&mut self, palette_index: u16, gid: u16) -> Option<PgpuGlyph> {
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<Affine>) -> 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())
}
}
}