use crate::render::{ATLAS_IMAGE_ID_BASE, DEFAULT_GLYPH_ATLAS_SIZE};
use crate::{
Image, ImageSource, PaintType, Pixmap, RenderContext, RenderMode, RenderSettings, Resources,
color, kurbo, peniko,
};
use alloc::boxed::Box;
use alloc::sync::Arc;
use alloc::vec::Vec;
use color::palette::css::BLACK;
use core::fmt::Debug;
use core::ops::RangeInclusive;
use glifo::atlas::{
AtlasConfig, AtlasSlot, GlyphAtlas, GlyphCacheConfig, ImageCache, PendingClearRect,
};
use glifo::{AtlasCacher, DrawSink, GlyphRunBackend};
use glifo::{Glyph, renderer};
use kurbo::{Affine, BezPath, Rect};
use peniko::BlendMode;
use peniko::color::{AlphaColor, Srgb};
use vello_common::fearless_simd::Level;
use vello_common::paint::ImageId;
fn atlas_page_image_id(page_index: u32) -> ImageId {
ImageId::new(ATLAS_IMAGE_ID_BASE + page_index)
}
#[derive(Debug)]
pub(crate) struct GlyphAtlasResources {
pub(crate) glyph_atlas: GlyphAtlas,
pub(crate) image_cache: ImageCache,
pub(crate) glyph_renderer: Box<RenderContext>,
pub(crate) pixmaps: Vec<Arc<Pixmap>>,
page_width: u16,
page_height: u16,
}
impl GlyphAtlasResources {
pub(crate) fn with_config(
page_width: u16,
page_height: u16,
level: Level,
render_mode: RenderMode,
eviction_config: GlyphCacheConfig,
) -> Self {
Self {
glyph_atlas: GlyphAtlas::with_config(eviction_config),
image_cache: ImageCache::new_with_config(AtlasConfig::default()),
glyph_renderer: Box::new(RenderContext::new_with(
page_width,
page_height,
RenderSettings {
level,
num_threads: 0,
render_mode,
},
)),
pixmaps: Vec::new(),
page_width,
page_height,
}
}
pub(crate) fn maintain(&mut self) {
self.glyph_atlas.maintain(&mut self.image_cache);
}
}
fn ensure_page(
pixmaps: &mut Vec<Arc<Pixmap>>,
page_width: u16,
page_height: u16,
page_index: usize,
) {
while pixmaps.len() <= page_index {
pixmaps.push(Arc::new(Pixmap::new(page_width, page_height)));
}
}
impl Resources {
pub(crate) fn prepare_glyph_cache(&mut self) {
if self.glyph_resources.is_some() {
self.sync_glyph_cache();
}
}
pub(crate) fn maintain_glyph_cache(&mut self) {
self.glyph_prep_cache.maintain();
if let Some(glyph_resources) = self.glyph_resources.as_mut() {
glyph_resources.maintain();
let page_count = glyph_resources.pixmaps.len();
for page_index in 0..page_count {
self.image_registry.destroy_atlas_page(page_index as u32);
}
self.clear_evicted_glyph_atlas_regions();
}
}
fn ensure_glyph_resources(&mut self, level: Level, render_mode: RenderMode) {
if self.glyph_resources.is_none() {
self.glyph_resources = Some(GlyphAtlasResources::with_config(
DEFAULT_GLYPH_ATLAS_SIZE,
DEFAULT_GLYPH_ATLAS_SIZE,
level,
render_mode,
GlyphCacheConfig::default(),
));
}
}
fn sync_glyph_cache(&mut self) {
let glyph_resources = self
.glyph_resources
.as_mut()
.expect("glyph atlas resources must exist before syncing");
for upload in glyph_resources.glyph_atlas.drain_pending_uploads() {
let page_index = upload.atlas_slot.page_index as usize;
ensure_page(
&mut glyph_resources.pixmaps,
glyph_resources.page_width,
glyph_resources.page_height,
page_index,
);
let pixmap = Arc::get_mut(&mut glyph_resources.pixmaps[page_index])
.expect("atlas pixmap should be uniquely owned during bitmap upload");
copy_pixmap_to_atlas(
&upload.pixmap,
pixmap,
upload.atlas_slot.x,
upload.atlas_slot.y,
upload.atlas_slot.width,
upload.atlas_slot.height,
);
}
let glyph_renderer = glyph_resources.glyph_renderer.as_mut();
glyph_resources
.glyph_atlas
.replay_pending_atlas_commands(|recorder| {
let page_index = recorder.page_index as usize;
ensure_page(
&mut glyph_resources.pixmaps,
glyph_resources.page_width,
glyph_resources.page_height,
page_index,
);
let page = Arc::get_mut(&mut glyph_resources.pixmaps[page_index])
.expect("atlas page pixmap must be uniquely owned during replay");
glyph_renderer.reset();
renderer::replay_atlas_commands(&mut recorder.commands, glyph_renderer);
glyph_renderer.flush();
glyph_renderer.composite_to_pixmap_at_offset(&Self::default(), page, 0, 0);
});
for (page_index, pixmap) in glyph_resources.pixmaps.iter().enumerate() {
self.image_registry
.register_atlas_page(page_index as u32, Arc::clone(pixmap));
}
}
fn clear_evicted_glyph_atlas_regions(&mut self) {
let glyph_resources = self
.glyph_resources
.as_mut()
.expect("glyph atlas resources must exist before clearing");
for clear in glyph_resources.glyph_atlas.drain_pending_clear_rects() {
let pixmap = Arc::get_mut(&mut glyph_resources.pixmaps[clear.page_index as usize])
.expect("atlas pixmap should be uniquely owned during region clearing");
clear_pixmap_region(pixmap, clear);
}
}
}
#[doc(hidden)]
#[derive(Debug)]
pub struct CpuGlyphRunBackend<'a> {
pub ctx: &'a mut RenderContext,
pub resources: &'a mut Resources,
pub atlas_cache_enabled: bool,
}
impl<'a> CpuGlyphRunBackend<'a> {
fn render_glyphs<Glyphs>(
self,
run: glifo::GlyphRun<'a>,
glyphs: Glyphs,
render: impl FnOnce(&mut glifo::GlyphRunRenderer<'a, 'a, Glyphs>, &mut RenderContext),
) where
Glyphs: Iterator<Item = Glyph> + Clone,
{
let atlas_cacher = if self.atlas_cache_enabled {
self.resources.ensure_glyph_resources(
self.ctx.render_settings.level,
self.ctx.render_settings.render_mode,
);
let glyph_resources = self
.resources
.glyph_resources
.as_mut()
.expect("glyph atlas resources must exist after initialization");
AtlasCacher::Enabled(
&mut glyph_resources.glyph_atlas,
&mut glyph_resources.image_cache,
)
} else {
AtlasCacher::Disabled
};
let mut glyph_run = run.build(
glyphs,
self.resources.glyph_prep_cache.as_mut(),
atlas_cacher,
);
render(&mut glyph_run, self.ctx);
}
}
impl<'a> GlyphRunBackend<'a> for CpuGlyphRunBackend<'a> {
fn atlas_cache(mut self, enabled: bool) -> Self {
self.atlas_cache_enabled = enabled;
self
}
fn fill_glyphs<Glyphs>(self, run: glifo::GlyphRun<'a>, glyphs: Glyphs)
where
Glyphs: Iterator<Item = Glyph> + Clone,
{
self.render_glyphs(run, glyphs, |glyph_run, ctx| glyph_run.fill_glyphs(ctx));
}
fn stroke_glyphs<Glyphs>(self, run: glifo::GlyphRun<'a>, glyphs: Glyphs)
where
Glyphs: Iterator<Item = Glyph> + Clone,
{
self.render_glyphs(run, glyphs, |glyph_run, ctx| {
let stroke_adjustment = glyph_run.stroke_adjustment();
let original_width = ctx.stroke().width;
ctx.stroke_mut().width *= stroke_adjustment;
glyph_run.stroke_glyphs(ctx);
ctx.stroke_mut().width = original_width;
});
}
fn render_decoration<Glyphs>(
self,
run: glifo::GlyphRun<'a>,
glyphs: Glyphs,
x_range: RangeInclusive<f32>,
baseline_y: f32,
offset: f32,
size: f32,
buffer: f32,
) where
Glyphs: Iterator<Item = Glyph> + Clone,
{
self.render_glyphs(run, glyphs, |glyph_run, ctx| {
glyph_run.render_decoration(x_range, baseline_y, offset, size, buffer, ctx);
});
}
}
pub type GlyphRunBuilder<'a> = glifo::GlyphRunBuilder<'a, CpuGlyphRunBackend<'a>>;
fn clear_pixmap_region(dst: &mut Pixmap, rect: PendingClearRect) {
let dst_stride = dst.width() as usize;
let dst_data = dst.data_as_u8_slice_mut();
let clear_width = rect.width as usize;
let clear_height = rect.height as usize;
for y in 0..clear_height {
let row_start = ((rect.y as usize + y) * dst_stride + rect.x as usize) * 4;
let row_end = row_start + clear_width * 4;
dst_data[row_start..row_end].fill(0);
}
}
fn copy_pixmap_to_atlas(
src: &Pixmap,
dst: &mut Pixmap,
dst_x: u16,
dst_y: u16,
width: u16,
height: u16,
) {
let copy_width = width as usize;
let copy_height = height as usize;
let src_stride = src.width() as usize;
let dst_stride = dst.width() as usize;
let src_data = src.data_as_u8_slice();
let dst_data = dst.data_as_u8_slice_mut();
for y in 0..copy_height {
let src_row_start = y * src_stride * 4;
let src_row_end = src_row_start + copy_width * 4;
let dst_row_start = ((dst_y as usize + y) * dst_stride + dst_x as usize) * 4;
let dst_row_end = dst_row_start + copy_width * 4;
dst_data[dst_row_start..dst_row_end].copy_from_slice(&src_data[src_row_start..src_row_end]);
}
}
impl DrawSink for RenderContext {
#[inline]
fn set_transform(&mut self, t: Affine) {
Self::set_transform(self, t);
}
#[inline]
fn set_paint(&mut self, paint: glifo::AtlasPaint) {
Self::set_paint(self, paint);
}
#[inline]
fn set_paint_transform(&mut self, t: Affine) {
Self::set_paint_transform(self, t);
}
#[inline]
fn fill_path(&mut self, path: &BezPath) {
Self::fill_path(self, path);
}
#[inline]
fn fill_rect(&mut self, rect: &Rect) {
Self::fill_rect(self, rect);
}
#[inline]
fn push_clip_layer(&mut self, clip: &BezPath) {
Self::push_clip_layer(self, clip);
}
#[inline]
fn push_clip_path(&mut self, clip: &BezPath) {
Self::push_clip_path(self, clip);
}
#[inline]
fn push_blend_layer(&mut self, blend_mode: BlendMode) {
Self::push_blend_layer(self, blend_mode);
}
#[inline]
fn pop_layer(&mut self) {
Self::pop_layer(self);
}
#[inline]
fn pop_clip_path(&mut self) {
Self::pop_clip_path(self);
}
#[inline]
fn width(&self) -> u16 {
Self::width(self)
}
#[inline]
fn height(&self) -> u16 {
Self::height(self)
}
}
impl glifo::GlyphRenderer for RenderContext {
type SavedState = vello_common::render_state::RenderState;
#[inline]
fn save_state(&mut self) -> Self::SavedState {
self.save_current_state()
}
#[inline]
fn restore_state(&mut self, state: Self::SavedState) {
Self::restore_state(self, state);
}
#[inline]
fn stroke_path(&mut self, path: &BezPath) {
Self::stroke_path(self, path);
}
#[inline]
fn set_paint_image(&mut self, image: Image) {
self.set_paint(image);
}
#[inline]
fn set_tint(&mut self, tint: Option<vello_common::paint::Tint>) {
Self::set_tint(self, tint);
}
#[inline]
fn get_context_color(&self) -> AlphaColor<Srgb> {
let paint = self.paint().clone();
match paint {
PaintType::Solid(s) => s,
_ => BLACK,
}
}
#[inline]
fn atlas_image_source(&self, atlas_slot: &AtlasSlot) -> ImageSource {
ImageSource::opaque_id(atlas_page_image_id(atlas_slot.page_index))
}
#[inline]
fn atlas_paint_transform(&self, atlas_slot: &AtlasSlot) -> Affine {
Affine::translate((-(atlas_slot.x as f64), -(atlas_slot.y as f64)))
}
}
#[cfg(debug_assertions)]
#[allow(
dead_code,
unreachable_pub,
clippy::trivially_copy_pass_by_ref,
reason = "debug-only utilities called manually during development"
)]
mod debug {
use core::sync::atomic::{AtomicUsize, Ordering};
use crate::RenderContext;
use crate::kurbo::{Affine, Rect};
use crate::peniko;
use glifo::atlas::RasterMetrics;
static COLOR_INDEX: AtomicUsize = AtomicUsize::new(0);
const COLORS: [peniko::Color; 12] = [
peniko::Color::new([1.0, 0.0, 0.0, 0.5]), peniko::Color::new([0.0, 1.0, 0.0, 0.5]), peniko::Color::new([0.0, 0.0, 1.0, 0.5]), peniko::Color::new([1.0, 1.0, 0.0, 0.5]), peniko::Color::new([1.0, 0.0, 1.0, 0.5]), peniko::Color::new([0.0, 1.0, 1.0, 0.5]), peniko::Color::new([1.0, 0.5, 0.0, 0.5]), peniko::Color::new([0.5, 0.0, 1.0, 0.5]), peniko::Color::new([0.0, 1.0, 0.5, 0.5]), peniko::Color::new([1.0, 0.5, 0.5, 0.5]), peniko::Color::new([0.5, 1.0, 0.5, 0.5]), peniko::Color::new([0.5, 0.5, 1.0, 0.5]), ];
pub fn fill_glyph_bounds(renderer: &mut RenderContext, raster_metrics: &RasterMetrics) {
let idx = COLOR_INDEX.fetch_add(1, Ordering::Relaxed) % COLORS.len();
renderer.set_transform(Affine::IDENTITY);
renderer.set_paint(COLORS[idx]);
renderer.fill_rect(&Rect::new(
0.0,
0.0,
raster_metrics.width as f64,
raster_metrics.height as f64,
));
}
}