use crate::context::Context;
use tracing::debug;
use super::atlas::*;
use super::ContentType;
use super::*;
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub enum AtlasKind {
#[default]
Mask, Color, }
#[derive(Default)]
pub struct Entry {
allocated: bool,
x: u16,
y: u16,
width: u16,
height: u16,
atlas_kind: AtlasKind,
}
pub struct Atlas {
alloc: AtlasAllocator,
buffer: Vec<u8>,
fresh: bool,
dirty: bool,
channels: usize, }
impl Atlas {
fn new(kind: AtlasKind) -> Self {
let channels = match kind {
AtlasKind::Mask => 1,
AtlasKind::Color => 4, };
Self {
alloc: AtlasAllocator::new(SIZE, SIZE),
buffer: vec![0; SIZE as usize * SIZE as usize * channels],
fresh: true,
dirty: false,
channels,
}
}
}
pub struct ImageCache {
pub entries: Vec<Entry>,
mask_atlas: Atlas,
color_atlas: Atlas,
max_texture_size: u16,
mask_texture: wgpu::Texture,
color_texture: wgpu::Texture,
pub mask_texture_view: wgpu::TextureView,
pub color_texture_view: wgpu::TextureView,
}
#[inline]
pub fn buffer_size(width: u32, height: u32) -> Option<usize> {
(width as usize)
.checked_add(height as usize)?
.checked_add(4)
}
pub const SIZE: u16 = 2048;
impl ImageCache {
pub fn new(context: &Context) -> Self {
let device = &context.device;
let max_texture_size = SIZE;
let mask_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("rich_text mask atlas"),
size: wgpu::Extent3d {
width: SIZE as u32,
height: SIZE as u32,
depth_or_array_layers: 1,
},
view_formats: &[],
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::R8Unorm,
usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING,
mip_level_count: 1,
sample_count: 1,
});
let mask_texture_view =
mask_texture.create_view(&wgpu::TextureViewDescriptor::default());
let color_texture_format = wgpu::TextureFormat::Rgba8Unorm;
let color_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("rich_text color atlas"),
size: wgpu::Extent3d {
width: SIZE as u32,
height: SIZE as u32,
depth_or_array_layers: 1,
},
view_formats: &[],
dimension: wgpu::TextureDimension::D2,
format: color_texture_format,
usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING,
mip_level_count: 1,
sample_count: 1,
});
let color_texture_view =
color_texture.create_view(&wgpu::TextureViewDescriptor::default());
Self {
entries: Vec::new(),
mask_atlas: Atlas::new(AtlasKind::Mask),
color_atlas: Atlas::new(AtlasKind::Color), max_texture_size,
mask_texture,
color_texture,
mask_texture_view,
color_texture_view,
}
}
pub fn allocate(&mut self, request: AddImage) -> Option<ImageId> {
let width = request.width;
let height = request.height;
if width == 0 || height == 0 {
return None;
}
buffer_size(width as u32, height as u32)?;
if !(width <= self.max_texture_size && height <= (self.max_texture_size / 4)) {
return None;
}
let atlas_kind = match request.content_type {
ContentType::Mask => AtlasKind::Mask,
ContentType::Color => AtlasKind::Color,
};
let atlas = match atlas_kind {
AtlasKind::Mask => &mut self.mask_atlas,
AtlasKind::Color => &mut self.color_atlas,
};
let atlas_data = atlas.alloc.allocate(width, height);
if atlas_data.is_none() {
debug!(
"ImageCache allocation failed for {}x{} - {:?} atlas full",
width, height, atlas_kind
);
return None;
}
let (x, y) = atlas_data?;
let entry_index = self.entries.len();
self.entries.push(Entry {
allocated: true,
x,
y,
width,
height,
atlas_kind,
});
if let Some(data) = request.data() {
fill(
FillParams {
x,
y,
width,
_height: height,
target_width: self.max_texture_size,
channels: atlas.channels,
},
data,
&mut atlas.buffer,
);
atlas.dirty = true;
}
ImageId::new(entry_index as u32, request.has_alpha)
}
#[allow(unused)]
pub fn deallocate(&mut self, image: ImageId) -> Option<()> {
let entry = self.entries.get_mut(image.index())?;
if !entry.allocated {
return None;
}
let atlas = match entry.atlas_kind {
AtlasKind::Mask => &mut self.mask_atlas,
AtlasKind::Color => &mut self.color_atlas,
};
atlas.alloc.deallocate(entry.x, entry.y, entry.width);
entry.allocated = false;
Some(())
}
pub fn get(&self, handle: &ImageId) -> Option<ImageLocation> {
if handle.is_empty() {
return None;
}
let entry = self.entries.get(handle.index())?;
if !entry.allocated {
return None;
}
let s = 1. / self.max_texture_size as f32;
Some(ImageLocation {
min: (entry.x as f32 * s, entry.y as f32 * s),
max: (
(entry.x + entry.width) as f32 * s,
(entry.y + entry.height) as f32 * s,
),
})
}
pub fn clear_atlas(&mut self) {
self.entries.clear();
self.mask_atlas = Atlas::new(AtlasKind::Mask);
self.color_atlas = Atlas::new(AtlasKind::Color);
tracing::info!("Dual atlases cleared due to font change");
}
pub fn is_valid(&self, image: ImageId) -> bool {
if image.is_empty() {
return true;
}
if let Some(entry) = self.entries.get(image.index()) {
entry.allocated
} else {
false
}
}
#[inline]
pub fn process_atlases(&mut self, context: &mut Context) {
if self.mask_atlas.dirty {
let texture_size = wgpu::Extent3d {
width: self.max_texture_size as u32,
height: self.max_texture_size as u32,
depth_or_array_layers: 1,
};
context.queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &self.mask_texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
&self.mask_atlas.buffer,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(
self.max_texture_size as u32 * self.mask_atlas.channels as u32,
),
rows_per_image: Some(self.max_texture_size as u32),
},
texture_size,
);
self.mask_atlas.fresh = false;
self.mask_atlas.dirty = false;
}
if self.color_atlas.dirty {
let texture_size = wgpu::Extent3d {
width: self.max_texture_size as u32,
height: self.max_texture_size as u32,
depth_or_array_layers: 1,
};
context.queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &self.color_texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
&self.color_atlas.buffer,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(
self.max_texture_size as u32 * self.color_atlas.channels as u32,
),
rows_per_image: Some(self.max_texture_size as u32),
},
texture_size,
);
self.color_atlas.fresh = false;
self.color_atlas.dirty = false;
}
}
}
struct FillParams {
x: u16,
y: u16,
width: u16,
_height: u16,
target_width: u16,
channels: usize,
}
fn fill(params: FillParams, image: &[u8], target: &mut [u8]) -> Option<()> {
let image_pitch = params.width as usize * params.channels;
let buffer_pitch = params.target_width as usize * params.channels;
let mut offset =
params.y as usize * buffer_pitch + params.x as usize * params.channels;
for row in image.chunks(image_pitch) {
let dest = target.get_mut(offset..offset + image_pitch)?;
dest.copy_from_slice(row);
offset += buffer_pitch;
}
Some(())
}