1use crate::{
2 Cache, CacheKey, FontSystem, GlyphDetails, GpuCacheStatus, SwashCache, text_render::ContentType,
3};
4use etagere::{Allocation, BucketedAtlasAllocator, size2};
5use lru::LruCache;
6use rustc_hash::FxHasher;
7use std::{collections::HashSet, hash::BuildHasherDefault};
8use wgpu::{
9 BindGroup, DepthStencilState, Device, Extent3d, MultisampleState, Origin3d, Queue,
10 RenderPipeline, TexelCopyBufferLayout, TexelCopyTextureInfo, Texture, TextureAspect,
11 TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, TextureView,
12 TextureViewDescriptor,
13};
14
15type Hasher = BuildHasherDefault<FxHasher>;
16
17#[allow(dead_code)]
18pub(crate) struct InnerAtlas {
19 pub kind: Kind,
20 pub texture: Texture,
21 pub texture_view: TextureView,
22 pub packer: BucketedAtlasAllocator,
23 pub size: u32,
24 pub glyph_cache: LruCache<CacheKey, GlyphDetails, Hasher>,
25 pub glyphs_in_use: HashSet<CacheKey, Hasher>,
26 pub max_texture_dimension_2d: u32,
27}
28
29impl InnerAtlas {
30 const INITIAL_SIZE: u32 = 256;
31
32 fn new(device: &Device, _queue: &Queue, kind: Kind) -> Self {
33 let max_texture_dimension_2d = device.limits().max_texture_dimension_2d;
34 let size = Self::INITIAL_SIZE.min(max_texture_dimension_2d);
35
36 let packer = BucketedAtlasAllocator::new(size2(size as i32, size as i32));
37
38 let texture = device.create_texture(&TextureDescriptor {
40 label: Some("glyphon atlas"),
41 size: Extent3d {
42 width: size,
43 height: size,
44 depth_or_array_layers: 1,
45 },
46 mip_level_count: 1,
47 sample_count: 1,
48 dimension: TextureDimension::D2,
49 format: kind.texture_format(),
50 usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
51 view_formats: &[],
52 });
53
54 let texture_view = texture.create_view(&TextureViewDescriptor::default());
55
56 let glyph_cache = LruCache::unbounded_with_hasher(Hasher::default());
57 let glyphs_in_use = HashSet::with_hasher(Hasher::default());
58
59 Self {
60 kind,
61 texture,
62 texture_view,
63 packer,
64 size,
65 glyph_cache,
66 glyphs_in_use,
67 max_texture_dimension_2d,
68 }
69 }
70
71 pub(crate) fn try_allocate(&mut self, width: usize, height: usize) -> Option<Allocation> {
72 let size = size2(width as i32, height as i32);
73
74 loop {
75 let allocation = self.packer.allocate(size);
76
77 if allocation.is_some() {
78 return allocation;
79 }
80
81 let (mut key, mut value) = self.glyph_cache.peek_lru()?;
83
84 while value.atlas_id.is_none() {
86 if self.glyphs_in_use.contains(key) {
88 return None;
89 }
90
91 let _ = self.glyph_cache.pop_lru();
92
93 (key, value) = self.glyph_cache.peek_lru()?;
94 }
95
96 if self.glyphs_in_use.contains(key) {
98 return None;
99 }
100
101 let (_, value) = self.glyph_cache.pop_lru().unwrap();
102 self.packer.deallocate(value.atlas_id.unwrap());
103 }
104 }
105
106 pub fn num_channels(&self) -> usize {
107 self.kind.num_channels()
108 }
109
110 pub(crate) fn grow(
111 &mut self,
112 device: &wgpu::Device,
113 queue: &wgpu::Queue,
114 font_system: &mut FontSystem,
115 cache: &mut SwashCache,
116 ) -> bool {
117 if self.size >= self.max_texture_dimension_2d {
118 return false;
119 }
120
121 const GROWTH_FACTOR: u32 = 2;
124 let new_size = (self.size * GROWTH_FACTOR).min(self.max_texture_dimension_2d);
125
126 self.packer.grow(size2(new_size as i32, new_size as i32));
127
128 self.texture = device.create_texture(&TextureDescriptor {
130 label: Some("glyphon atlas"),
131 size: Extent3d {
132 width: new_size,
133 height: new_size,
134 depth_or_array_layers: 1,
135 },
136 mip_level_count: 1,
137 sample_count: 1,
138 dimension: TextureDimension::D2,
139 format: self.kind.texture_format(),
140 usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
141 view_formats: &[],
142 });
143
144 for (&cache_key, glyph) in &self.glyph_cache {
146 let (x, y) = match glyph.gpu_cache {
147 GpuCacheStatus::InAtlas { x, y, .. } => (x, y),
148 GpuCacheStatus::SkipRasterization => continue,
149 };
150
151 let image = cache.get_image_uncached(font_system, cache_key).unwrap();
152
153 let width = image.placement.width as usize;
154 let height = image.placement.height as usize;
155
156 queue.write_texture(
157 TexelCopyTextureInfo {
158 texture: &self.texture,
159 mip_level: 0,
160 origin: Origin3d {
161 x: x as u32,
162 y: y as u32,
163 z: 0,
164 },
165 aspect: TextureAspect::All,
166 },
167 &image.data,
168 TexelCopyBufferLayout {
169 offset: 0,
170 bytes_per_row: Some(width as u32 * self.kind.num_channels() as u32),
171 rows_per_image: None,
172 },
173 Extent3d {
174 width: width as u32,
175 height: height as u32,
176 depth_or_array_layers: 1,
177 },
178 );
179 }
180
181 self.texture_view = self.texture.create_view(&TextureViewDescriptor::default());
182 self.size = new_size;
183
184 true
185 }
186
187 fn trim(&mut self) {
188 self.glyphs_in_use.clear();
189 }
190}
191
192#[derive(Debug, Clone, Copy, PartialEq, Eq)]
193pub(crate) enum Kind {
194 Mask,
195 Color { srgb: bool },
196}
197
198impl Kind {
199 fn num_channels(self) -> usize {
200 match self {
201 Kind::Mask => 1,
202 Kind::Color { .. } => 4,
203 }
204 }
205
206 fn texture_format(self) -> wgpu::TextureFormat {
207 match self {
208 Kind::Mask => TextureFormat::R8Unorm,
209 Kind::Color { srgb } => {
210 if srgb {
211 TextureFormat::Rgba8UnormSrgb
212 } else {
213 TextureFormat::Rgba8Unorm
214 }
215 }
216 }
217 }
218}
219
220#[derive(Debug, Clone, Copy, PartialEq, Eq)]
222pub enum ColorMode {
223 Accurate,
228
229 Web,
241}
242
243pub struct TextAtlas {
245 cache: Cache,
246 pub(crate) bind_group: BindGroup,
247 pub(crate) color_atlas: InnerAtlas,
248 pub(crate) mask_atlas: InnerAtlas,
249 pub(crate) format: TextureFormat,
250 pub(crate) color_mode: ColorMode,
251}
252
253impl TextAtlas {
254 pub fn new(device: &Device, queue: &Queue, cache: &Cache, format: TextureFormat) -> Self {
256 Self::with_color_mode(device, queue, cache, format, ColorMode::Accurate)
257 }
258
259 pub fn with_color_mode(
261 device: &Device,
262 queue: &Queue,
263 cache: &Cache,
264 format: TextureFormat,
265 color_mode: ColorMode,
266 ) -> Self {
267 let color_atlas = InnerAtlas::new(
268 device,
269 queue,
270 Kind::Color {
271 srgb: match color_mode {
272 ColorMode::Accurate => true,
273 ColorMode::Web => false,
274 },
275 },
276 );
277 let mask_atlas = InnerAtlas::new(device, queue, Kind::Mask);
278
279 let bind_group = cache.create_atlas_bind_group(
280 device,
281 &color_atlas.texture_view,
282 &mask_atlas.texture_view,
283 );
284
285 Self {
286 cache: cache.clone(),
287 bind_group,
288 color_atlas,
289 mask_atlas,
290 format,
291 color_mode,
292 }
293 }
294
295 pub fn trim(&mut self) {
296 self.mask_atlas.trim();
297 self.color_atlas.trim();
298 }
299
300 pub(crate) fn grow(
301 &mut self,
302 device: &wgpu::Device,
303 queue: &wgpu::Queue,
304 font_system: &mut FontSystem,
305 cache: &mut SwashCache,
306 content_type: ContentType,
307 ) -> bool {
308 let did_grow = match content_type {
309 ContentType::Mask => self.mask_atlas.grow(device, queue, font_system, cache),
310 ContentType::Color => self.color_atlas.grow(device, queue, font_system, cache),
311 };
312
313 if did_grow {
314 self.rebind(device);
315 }
316
317 did_grow
318 }
319
320 pub(crate) fn inner_for_content_mut(&mut self, content_type: ContentType) -> &mut InnerAtlas {
321 match content_type {
322 ContentType::Color => &mut self.color_atlas,
323 ContentType::Mask => &mut self.mask_atlas,
324 }
325 }
326
327 pub(crate) fn get_or_create_pipeline(
328 &self,
329 device: &Device,
330 multisample: MultisampleState,
331 depth_stencil: Option<DepthStencilState>,
332 ) -> RenderPipeline {
333 self.cache
334 .get_or_create_pipeline(device, self.format, multisample, depth_stencil)
335 }
336
337 fn rebind(&mut self, device: &wgpu::Device) {
338 self.bind_group = self.cache.create_atlas_bind_group(
339 device,
340 &self.color_atlas.texture_view,
341 &self.mask_atlas.texture_view,
342 );
343 }
344}