use std::collections::HashSet;
use klyff_msdf::MtsdfGpuWriter;
use super::cache::Cache;
use super::size::{AtlasSize, grow_size};
use crate::Rect;
pub struct TextureAtlas {
pub(super) bind_group: wgpu::BindGroup,
pub(super) texture: wgpu::Texture,
pub(super) sampler: wgpu::Sampler,
pub(super) allocators: Vec<etagere::BucketedAtlasAllocator>,
pub(super) cache: Cache<GlyphCacheKey, GlyphCacheValue>,
pub(super) empty_glyphs: HashSet<(fontdb::ID, skrifa::GlyphId)>,
pub(super) gpu_write: MtsdfGpuWriter,
pub(super) current_size: wgpu::Extent3d,
pub(super) max_size: wgpu::Extent3d,
pub(super) ppem: f32,
pub(super) padding_x: usize,
pub(super) padding_y: usize,
pub(super) allow_grow: bool,
pub(super) retain_glyphs_frame_duration: u64,
pub(super) frozen: bool,
pub(super) scale_context: swash::scale::ScaleContext,
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub(super) struct GlyphCacheKey {
pub(super) font: fontdb::ID,
pub(super) font_size_bytes: Option<[u8; 4]>,
pub(super) id: skrifa::GlyphId,
}
#[derive(Debug, Clone, Copy)]
pub(super) struct GlyphCacheValue {
pub(super) region: super::AtlasRegion,
pub(super) em_size: f32,
pub(super) bounds_em: Rect,
pub(super) alloc_id: etagere::AllocId,
}
pub struct TextureAtlasDescriptor {
pub initial_size: AtlasSize,
pub max_size: Option<AtlasSize>,
pub allow_grow: bool,
pub retain_glyphs_frame_duration: u64,
pub pixel_per_em: Option<f32>,
pub glyph_padding: Option<(usize, usize)>,
}
impl Default for TextureAtlasDescriptor {
fn default() -> Self {
Self {
initial_size: AtlasSize::MaxSize { layer_count: 1 },
max_size: None,
allow_grow: true,
retain_glyphs_frame_duration: 0,
pixel_per_em: None,
glyph_padding: None,
}
}
}
impl TextureAtlas {
pub fn new(device: &wgpu::Device, descriptor: TextureAtlasDescriptor) -> Self {
let device_max_size = device.limits().max_texture_dimension_2d;
let device_max_layers = device.limits().max_texture_array_layers;
let size =
atlas_size_to_extent(descriptor.initial_size, device_max_size, device_max_layers);
let allocators = (0..size.depth_or_array_layers)
.map(|_| {
etagere::BucketedAtlasAllocator::new(etagere::size2(
size.width as i32,
size.height as i32,
))
})
.collect();
let usage = texture_usage(descriptor.allow_grow);
let (texture, sampler) = create_texture(device, size, usage);
let bind_group = Self::create_bind_group(device, &texture, &sampler);
let max_size = descriptor
.max_size
.map(|s| atlas_size_to_extent(s, device_max_size, device_max_layers))
.unwrap_or(wgpu::Extent3d {
width: device_max_size,
height: device_max_size,
depth_or_array_layers: device_max_layers,
});
Self {
texture,
sampler,
allocators,
empty_glyphs: Default::default(),
cache: Cache::new(),
current_size: size,
max_size,
ppem: descriptor.pixel_per_em.unwrap_or(super::DEFAULT_PPEM),
padding_x: descriptor
.glyph_padding
.map(|x| x.0)
.unwrap_or(super::DEFAULT_PADDING),
padding_y: descriptor
.glyph_padding
.map(|x| x.1)
.unwrap_or(super::DEFAULT_PADDING),
allow_grow: descriptor.allow_grow,
retain_glyphs_frame_duration: descriptor.retain_glyphs_frame_duration,
frozen: false,
bind_group,
scale_context: swash::scale::ScaleContext::new(),
gpu_write: MtsdfGpuWriter::new(device),
}
}
pub fn bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout {
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("klyff atlas bind group layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
multisampled: false,
view_dimension: wgpu::TextureViewDimension::D2Array,
sample_type: wgpu::TextureSampleType::Float { filterable: true },
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
})
}
pub(super) fn create_bind_group(
device: &wgpu::Device,
texture: &wgpu::Texture,
sampler: &wgpu::Sampler,
) -> wgpu::BindGroup {
let view = texture.create_view(&wgpu::TextureViewDescriptor {
label: Some("klyff atlas texture view"),
dimension: Some(wgpu::TextureViewDimension::D2Array),
..Default::default()
});
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("klyff atlas bind group"),
layout: &Self::bind_group_layout(device),
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(sampler),
},
],
})
}
pub(super) fn grow_size(&mut self, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder) {
profiling::scope!("TextureAtlas::grow_size");
let usage = self.texture.usage();
let old_size = self.current_size;
self.current_size = grow_size(self.current_size, self.max_size);
log::trace!(
"Growing texture size from {old_size:?} to {:?}",
self.current_size
);
let (new_texture, new_sampler) = create_texture(device, self.current_size, usage);
let new_bind_group = Self::create_bind_group(device, &new_texture, &new_sampler);
encoder.copy_texture_to_texture(
self.texture.as_image_copy(),
new_texture.as_image_copy(),
self.texture.size(),
);
self.texture = new_texture;
self.sampler = new_sampler;
self.bind_group = new_bind_group;
for allocator in self.allocators.iter_mut() {
allocator.grow(etagere::size2(
self.current_size.width as i32,
self.current_size.height as i32,
));
}
}
pub fn ppem(&self) -> f32 {
self.ppem
}
pub fn atlas_size(&self) -> (u32, u32, u32) {
(
self.texture.width(),
self.texture.height(),
self.texture.depth_or_array_layers(),
)
}
pub fn bind_group(&self) -> &wgpu::BindGroup {
&self.bind_group
}
pub fn freeze(&mut self) {
self.frozen = true;
}
pub fn unfreeze(&mut self) {
self.frozen = false;
}
pub fn is_frozen(&self) -> bool {
self.frozen
}
pub fn write_glyphs(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
) {
profiling::scope!("TextureAtlas::write_glyphs");
self.gpu_write
.write_glyphs(device, queue, encoder, &self.texture);
}
}
fn atlas_size_to_extent(
size: AtlasSize,
device_max_size: u32,
device_max_layers: u32,
) -> wgpu::Extent3d {
match size {
AtlasSize::SingleLayer { width, height } => wgpu::Extent3d {
width: width.clamp(super::MIN_TEXTURE_SIZE, device_max_size),
height: height.clamp(super::MIN_TEXTURE_SIZE, device_max_size),
depth_or_array_layers: 1,
},
AtlasSize::MaxSize { layer_count } => wgpu::Extent3d {
width: device_max_size,
height: device_max_size,
depth_or_array_layers: layer_count.clamp(1, device_max_layers),
},
}
}
pub(super) fn texture_usage(allow_grow: bool) -> wgpu::TextureUsages {
let mut usage = wgpu::TextureUsages::COPY_DST
| wgpu::TextureUsages::TEXTURE_BINDING
| wgpu::TextureUsages::RENDER_ATTACHMENT;
if allow_grow || cfg!(feature = "serde") {
usage |= wgpu::TextureUsages::COPY_SRC;
}
usage
}
pub(super) fn create_texture(
device: &wgpu::Device,
size: wgpu::Extent3d,
usage: wgpu::TextureUsages,
) -> (wgpu::Texture, wgpu::Sampler) {
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Klyff atlas"),
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8Unorm,
usage,
view_formats: &[wgpu::TextureFormat::Rgba8Unorm],
});
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("Klyff atlas sampler"),
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
..Default::default()
});
(texture, sampler)
}