iced_wgpu/
text.rs

1use crate::core::alignment;
2use crate::core::text::Alignment;
3use crate::core::{Rectangle, Size, Transformation};
4use crate::graphics::cache;
5use crate::graphics::color;
6use crate::graphics::text::cache::{self as text_cache, Cache as BufferCache};
7use crate::graphics::text::{Editor, Paragraph, font_system, to_color};
8
9use rustc_hash::FxHashMap;
10use std::collections::hash_map;
11use std::sync::atomic::{self, AtomicU64};
12use std::sync::{self, Arc, RwLock};
13
14pub use crate::graphics::Text;
15
16const COLOR_MODE: cryoglyph::ColorMode = if color::GAMMA_CORRECTION {
17    cryoglyph::ColorMode::Accurate
18} else {
19    cryoglyph::ColorMode::Web
20};
21
22pub type Batch = Vec<Item>;
23
24#[derive(Debug)]
25pub enum Item {
26    Group {
27        transformation: Transformation,
28        text: Vec<Text>,
29    },
30    Cached {
31        transformation: Transformation,
32        cache: Cache,
33    },
34}
35
36#[derive(Debug, Clone)]
37pub struct Cache {
38    id: Id,
39    group: cache::Group,
40    text: Arc<[Text]>,
41    version: usize,
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
45pub struct Id(u64);
46
47impl Cache {
48    pub fn new(group: cache::Group, text: Vec<Text>) -> Option<Self> {
49        static NEXT_ID: AtomicU64 = AtomicU64::new(0);
50
51        if text.is_empty() {
52            return None;
53        }
54
55        Some(Self {
56            id: Id(NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed)),
57            group,
58            text: Arc::from(text),
59            version: 0,
60        })
61    }
62
63    pub fn update(&mut self, text: Vec<Text>) {
64        if self.text.is_empty() && text.is_empty() {
65            return;
66        }
67
68        self.text = Arc::from(text);
69        self.version += 1;
70    }
71}
72
73struct Upload {
74    renderer: cryoglyph::TextRenderer,
75    buffer_cache: BufferCache,
76    transformation: Transformation,
77    version: usize,
78    group_version: usize,
79    text: sync::Weak<[Text]>,
80    _atlas: sync::Weak<()>,
81}
82
83#[derive(Default)]
84pub struct Storage {
85    groups: FxHashMap<cache::Group, Group>,
86    uploads: FxHashMap<Id, Upload>,
87}
88
89struct Group {
90    atlas: cryoglyph::TextAtlas,
91    version: usize,
92    should_trim: bool,
93    handle: Arc<()>, // Keeps track of active uploads
94}
95
96impl Storage {
97    fn get(&self, cache: &Cache) -> Option<(&cryoglyph::TextAtlas, &Upload)> {
98        if cache.text.is_empty() {
99            return None;
100        }
101
102        self.groups
103            .get(&cache.group)
104            .map(|group| &group.atlas)
105            .zip(self.uploads.get(&cache.id))
106    }
107
108    fn prepare(
109        &mut self,
110        device: &wgpu::Device,
111        queue: &wgpu::Queue,
112        viewport: &cryoglyph::Viewport,
113        encoder: &mut wgpu::CommandEncoder,
114        format: wgpu::TextureFormat,
115        state: &cryoglyph::Cache,
116        cache: &Cache,
117        new_transformation: Transformation,
118        bounds: Rectangle,
119    ) {
120        let group_count = self.groups.len();
121
122        let group = self.groups.entry(cache.group).or_insert_with(|| {
123            log::debug!(
124                "New text atlas: {:?} (total: {})",
125                cache.group,
126                group_count + 1
127            );
128
129            Group {
130                atlas: cryoglyph::TextAtlas::with_color_mode(
131                    device, queue, state, format, COLOR_MODE,
132                ),
133                version: 0,
134                should_trim: false,
135                handle: Arc::new(()),
136            }
137        });
138
139        match self.uploads.entry(cache.id) {
140            hash_map::Entry::Occupied(entry) => {
141                let upload = entry.into_mut();
142
143                if upload.version != cache.version
144                    || upload.group_version != group.version
145                    || upload.transformation != new_transformation
146                {
147                    if !cache.text.is_empty() {
148                        let _ = prepare(
149                            device,
150                            queue,
151                            viewport,
152                            encoder,
153                            &mut upload.renderer,
154                            &mut group.atlas,
155                            &mut upload.buffer_cache,
156                            &cache.text,
157                            bounds,
158                            new_transformation,
159                        );
160                    }
161
162                    // Only trim if glyphs have changed
163                    group.should_trim =
164                        group.should_trim || upload.version != cache.version;
165
166                    upload.text = Arc::downgrade(&cache.text);
167                    upload.version = cache.version;
168                    upload.group_version = group.version;
169                    upload.transformation = new_transformation;
170
171                    upload.buffer_cache.trim();
172                }
173            }
174            hash_map::Entry::Vacant(entry) => {
175                let mut renderer = cryoglyph::TextRenderer::new(
176                    &mut group.atlas,
177                    device,
178                    wgpu::MultisampleState::default(),
179                    None,
180                );
181
182                let mut buffer_cache = BufferCache::new();
183
184                if !cache.text.is_empty() {
185                    let _ = prepare(
186                        device,
187                        queue,
188                        viewport,
189                        encoder,
190                        &mut renderer,
191                        &mut group.atlas,
192                        &mut buffer_cache,
193                        &cache.text,
194                        bounds,
195                        new_transformation,
196                    );
197                }
198
199                let _ = entry.insert(Upload {
200                    renderer,
201                    buffer_cache,
202                    transformation: new_transformation,
203                    version: 0,
204                    group_version: group.version,
205                    text: Arc::downgrade(&cache.text),
206                    _atlas: Arc::downgrade(&group.handle),
207                });
208
209                group.should_trim = cache.group.is_singleton();
210
211                log::debug!(
212                    "New text upload: {} (total: {})",
213                    cache.id.0,
214                    self.uploads.len()
215                );
216            }
217        }
218    }
219
220    pub fn trim(&mut self) {
221        self.uploads
222            .retain(|_id, upload| upload.text.strong_count() > 0);
223
224        self.groups.retain(|id, group| {
225            let active_uploads = Arc::weak_count(&group.handle);
226
227            if active_uploads == 0 {
228                log::debug!("Dropping text atlas: {id:?}");
229
230                return false;
231            }
232
233            if group.should_trim {
234                log::trace!("Trimming text atlas: {id:?}");
235
236                group.atlas.trim();
237                group.should_trim = false;
238
239                // We only need to worry about glyph fighting
240                // when the atlas may be shared by multiple
241                // uploads.
242                if !id.is_singleton() {
243                    log::debug!(
244                        "Invalidating text atlas: {id:?} \
245                        (uploads: {active_uploads})"
246                    );
247
248                    group.version += 1;
249                }
250            }
251
252            true
253        });
254    }
255}
256
257pub struct Viewport(cryoglyph::Viewport);
258
259impl Viewport {
260    pub fn update(&mut self, queue: &wgpu::Queue, resolution: Size<u32>) {
261        self.0.update(
262            queue,
263            cryoglyph::Resolution {
264                width: resolution.width,
265                height: resolution.height,
266            },
267        );
268    }
269}
270
271#[derive(Clone)]
272#[allow(missing_debug_implementations)]
273pub struct Pipeline {
274    format: wgpu::TextureFormat,
275    cache: cryoglyph::Cache,
276    atlas: Arc<RwLock<cryoglyph::TextAtlas>>,
277}
278
279impl Pipeline {
280    pub fn new(
281        device: &wgpu::Device,
282        queue: &wgpu::Queue,
283        format: wgpu::TextureFormat,
284    ) -> Self {
285        let cache = cryoglyph::Cache::new(device);
286        let atlas = cryoglyph::TextAtlas::with_color_mode(
287            device, queue, &cache, format, COLOR_MODE,
288        );
289
290        Pipeline {
291            format,
292            cache,
293            atlas: Arc::new(RwLock::new(atlas)),
294        }
295    }
296
297    pub fn create_viewport(&self, device: &wgpu::Device) -> Viewport {
298        Viewport(cryoglyph::Viewport::new(device, &self.cache))
299    }
300
301    pub fn trim(&self) {
302        self.atlas.write().expect("Write text atlas").trim();
303    }
304}
305
306#[derive(Default)]
307pub struct State {
308    renderers: Vec<cryoglyph::TextRenderer>,
309    prepare_layer: usize,
310    cache: BufferCache,
311    storage: Storage,
312}
313
314impl State {
315    pub fn new() -> Self {
316        Self::default()
317    }
318
319    pub fn prepare(
320        &mut self,
321        pipeline: &Pipeline,
322        device: &wgpu::Device,
323        queue: &wgpu::Queue,
324        viewport: &Viewport,
325        encoder: &mut wgpu::CommandEncoder,
326        batch: &Batch,
327        layer_bounds: Rectangle,
328        layer_transformation: Transformation,
329    ) {
330        let mut atlas = pipeline.atlas.write().expect("Write to text atlas");
331
332        for item in batch {
333            match item {
334                Item::Group {
335                    transformation,
336                    text,
337                } => {
338                    if self.renderers.len() <= self.prepare_layer {
339                        self.renderers.push(cryoglyph::TextRenderer::new(
340                            &mut atlas,
341                            device,
342                            wgpu::MultisampleState::default(),
343                            None,
344                        ));
345                    }
346
347                    let renderer = &mut self.renderers[self.prepare_layer];
348                    let result = prepare(
349                        device,
350                        queue,
351                        &viewport.0,
352                        encoder,
353                        renderer,
354                        &mut atlas,
355                        &mut self.cache,
356                        text,
357                        layer_bounds * layer_transformation,
358                        layer_transformation * *transformation,
359                    );
360
361                    match result {
362                        Ok(()) => {
363                            self.prepare_layer += 1;
364                        }
365                        Err(cryoglyph::PrepareError::AtlasFull) => {
366                            // If the atlas cannot grow, then all bets are off.
367                            // Instead of panicking, we will just pray that the result
368                            // will be somewhat readable...
369                        }
370                    }
371                }
372                Item::Cached {
373                    transformation,
374                    cache,
375                } => {
376                    self.storage.prepare(
377                        device,
378                        queue,
379                        &viewport.0,
380                        encoder,
381                        pipeline.format,
382                        &pipeline.cache,
383                        cache,
384                        layer_transformation * *transformation,
385                        layer_bounds * layer_transformation,
386                    );
387                }
388            }
389        }
390    }
391
392    pub fn render<'a>(
393        &'a self,
394        pipeline: &'a Pipeline,
395        viewport: &'a Viewport,
396        start: usize,
397        batch: &'a Batch,
398        bounds: Rectangle<u32>,
399        render_pass: &mut wgpu::RenderPass<'a>,
400    ) -> usize {
401        let atlas = pipeline.atlas.read().expect("Read text atlas");
402        let mut layer_count = 0;
403
404        render_pass.set_scissor_rect(
405            bounds.x,
406            bounds.y,
407            bounds.width,
408            bounds.height,
409        );
410
411        for item in batch {
412            match item {
413                Item::Group { .. } => {
414                    let renderer = &self.renderers[start + layer_count];
415
416                    renderer
417                        .render(&atlas, &viewport.0, render_pass)
418                        .expect("Render text");
419
420                    layer_count += 1;
421                }
422                Item::Cached { cache, .. } => {
423                    if let Some((atlas, upload)) = self.storage.get(cache) {
424                        upload
425                            .renderer
426                            .render(atlas, &viewport.0, render_pass)
427                            .expect("Render cached text");
428                    }
429                }
430            }
431        }
432
433        layer_count
434    }
435
436    pub fn trim(&mut self) {
437        self.cache.trim();
438        self.storage.trim();
439
440        self.prepare_layer = 0;
441    }
442}
443
444fn prepare(
445    device: &wgpu::Device,
446    queue: &wgpu::Queue,
447    viewport: &cryoglyph::Viewport,
448    encoder: &mut wgpu::CommandEncoder,
449    renderer: &mut cryoglyph::TextRenderer,
450    atlas: &mut cryoglyph::TextAtlas,
451    buffer_cache: &mut BufferCache,
452    sections: &[Text],
453    layer_bounds: Rectangle,
454    layer_transformation: Transformation,
455) -> Result<(), cryoglyph::PrepareError> {
456    let mut font_system = font_system().write().expect("Write font system");
457    let font_system = font_system.raw();
458
459    enum Allocation {
460        Paragraph(Paragraph),
461        Editor(Editor),
462        Cache(text_cache::KeyHash),
463        Raw(Arc<cryoglyph::Buffer>),
464    }
465
466    let allocations: Vec<_> = sections
467        .iter()
468        .map(|section| match section {
469            Text::Paragraph { paragraph, .. } => {
470                paragraph.upgrade().map(Allocation::Paragraph)
471            }
472            Text::Editor { editor, .. } => {
473                editor.upgrade().map(Allocation::Editor)
474            }
475            Text::Cached {
476                content,
477                bounds,
478                size,
479                line_height,
480                font,
481                shaping,
482                align_x,
483                ..
484            } => {
485                let (key, _) = buffer_cache.allocate(
486                    font_system,
487                    text_cache::Key {
488                        content,
489                        size: f32::from(*size),
490                        line_height: f32::from(*line_height),
491                        font: *font,
492                        align_x: *align_x,
493                        bounds: Size {
494                            width: bounds.width,
495                            height: bounds.height,
496                        },
497                        shaping: *shaping,
498                    },
499                );
500
501                Some(Allocation::Cache(key))
502            }
503            Text::Raw { raw, .. } => raw.buffer.upgrade().map(Allocation::Raw),
504        })
505        .collect();
506
507    let text_areas = sections.iter().zip(allocations.iter()).filter_map(
508        |(section, allocation)| {
509            let (buffer, position, color, clip_bounds, transformation) =
510                match section {
511                    Text::Paragraph {
512                        position,
513                        color,
514                        clip_bounds,
515                        transformation,
516                        ..
517                    } => {
518                        let Some(Allocation::Paragraph(paragraph)) = allocation
519                        else {
520                            return None;
521                        };
522
523                        (
524                            paragraph.buffer(),
525                            *position,
526                            *color,
527                            *clip_bounds,
528                            *transformation,
529                        )
530                    }
531                    Text::Editor {
532                        position,
533                        color,
534                        clip_bounds,
535                        transformation,
536                        ..
537                    } => {
538                        let Some(Allocation::Editor(editor)) = allocation
539                        else {
540                            return None;
541                        };
542
543                        (
544                            editor.buffer(),
545                            *position,
546                            *color,
547                            *clip_bounds,
548                            *transformation,
549                        )
550                    }
551                    Text::Cached {
552                        bounds,
553                        align_x,
554                        align_y,
555                        color,
556                        clip_bounds,
557                        ..
558                    } => {
559                        let Some(Allocation::Cache(key)) = allocation else {
560                            return None;
561                        };
562
563                        let entry =
564                            buffer_cache.get(key).expect("Get cached buffer");
565
566                        let mut position = bounds.position();
567
568                        position.x = match align_x {
569                            Alignment::Default
570                            | Alignment::Left
571                            | Alignment::Justified => position.x,
572                            Alignment::Center => {
573                                position.x - entry.min_bounds.width / 2.0
574                            }
575                            Alignment::Right => {
576                                position.x - entry.min_bounds.width
577                            }
578                        };
579
580                        position.y = match align_y {
581                            alignment::Vertical::Top => position.y,
582                            alignment::Vertical::Center => {
583                                position.y - entry.min_bounds.height / 2.0
584                            }
585                            alignment::Vertical::Bottom => {
586                                position.y - entry.min_bounds.height
587                            }
588                        };
589
590                        (
591                            &entry.buffer,
592                            position,
593                            *color,
594                            *clip_bounds,
595                            Transformation::IDENTITY,
596                        )
597                    }
598                    Text::Raw {
599                        raw,
600                        transformation,
601                    } => {
602                        let Some(Allocation::Raw(buffer)) = allocation else {
603                            return None;
604                        };
605
606                        (
607                            buffer.as_ref(),
608                            raw.position,
609                            raw.color,
610                            raw.clip_bounds,
611                            *transformation,
612                        )
613                    }
614                };
615
616            let position = position * transformation * layer_transformation;
617
618            let clip_bounds = layer_bounds.intersection(
619                &(clip_bounds * transformation * layer_transformation),
620            )?;
621
622            Some(cryoglyph::TextArea {
623                buffer,
624                left: position.x,
625                top: position.y,
626                scale: transformation.scale_factor()
627                    * layer_transformation.scale_factor(),
628                bounds: cryoglyph::TextBounds {
629                    left: clip_bounds.x as i32,
630                    top: clip_bounds.y as i32,
631                    right: (clip_bounds.x + clip_bounds.width) as i32,
632                    bottom: (clip_bounds.y + clip_bounds.height) as i32,
633                },
634                default_color: to_color(color),
635            })
636        },
637    );
638
639    renderer.prepare(
640        device,
641        queue,
642        encoder,
643        font_system,
644        atlas,
645        viewport,
646        text_areas,
647        &mut cryoglyph::SwashCache::new(),
648    )
649}