Skip to main content

loam_text/
pass.rs

1use std::cell::RefCell;
2use std::rc::Rc;
3
4use loam_render::device::GpuContext;
5use loam_render::pass::{FrameFormat, FramePass, FrameTarget, PassStage};
6
7use wgpu::{CommandEncoder, Device, Queue};
8
9use crate::TextRenderer;
10
11/// One placement of the published text, in physical pixels.
12#[derive(Clone, Copy, Debug, PartialEq)]
13pub struct TextDraw {
14    pub origin_px: [f32; 2],
15    pub size_px: f32,
16    pub color: [f32; 4],
17}
18
19struct State {
20    font: Vec<u8>,
21    bake_size_px: f32,
22    text: String,
23    draws: Vec<TextDraw>,
24    renderer: Option<TextRenderer>,
25    failure: Option<String>,
26    device: Option<Device>,
27    queue: Option<Queue>,
28}
29
30/// A shared text overlay pass without a depth test.
31#[derive(Clone)]
32pub struct TextPass {
33    name: &'static str,
34    shared: Rc<RefCell<State>>,
35}
36
37impl TextPass {
38    pub fn new(name: &'static str, font: Vec<u8>, bake_size_px: f32) -> Self {
39        Self {
40            name,
41            shared: Rc::new(RefCell::new(State {
42                font,
43                bake_size_px,
44                text: String::new(),
45                draws: Vec::new(),
46                renderer: None,
47                failure: None,
48                device: None,
49                queue: None,
50            })),
51        }
52    }
53
54    /// The record queues `text` once per draw, in order, so a shadow placed first paints under the body.
55    pub fn publish(&self, text: &str, draws: &[TextDraw]) {
56        let mut state = self.shared.borrow_mut();
57        state.text.clear();
58        state.text.push_str(text);
59        state.draws.clear();
60        state.draws.extend_from_slice(draws);
61    }
62
63    /// False when the font never parsed, so the pass draws nothing.
64    pub fn ready(&self) -> bool {
65        self.shared.borrow().renderer.is_some()
66    }
67
68    pub fn failure(&self) -> Option<String> {
69        self.shared.borrow().failure.clone()
70    }
71}
72
73impl FramePass for TextPass {
74    fn name(&self) -> &'static str {
75        self.name
76    }
77
78    fn stage(&self) -> PassStage {
79        PassStage::Overlay
80    }
81
82    fn record(
83        &mut self,
84        encoder: &mut CommandEncoder,
85        target: &FrameTarget<'_>,
86    ) -> anyhow::Result<()> {
87        let mut state = self.shared.borrow_mut();
88        if state.text.is_empty() || state.draws.is_empty() {
89            return Ok(());
90        }
91        let State {
92            text,
93            draws,
94            renderer,
95            device,
96            queue,
97            ..
98        } = &mut *state;
99        let (Some(renderer), Some(device), Some(queue)) =
100            (renderer.as_mut(), device.as_ref(), queue.as_ref())
101        else {
102            return Ok(());
103        };
104        for draw in draws.iter() {
105            renderer.queue(text, draw.origin_px, draw.size_px, draw.color);
106        }
107        renderer.record(
108            device,
109            queue,
110            encoder,
111            target.color,
112            [target.size.0 as f32, target.size.1 as f32],
113        );
114        Ok(())
115    }
116
117    fn attach(&mut self, gpu: &GpuContext, frame: FrameFormat) -> anyhow::Result<()> {
118        let mut state = self.shared.borrow_mut();
119        state.renderer = None;
120        match TextRenderer::new(
121            &gpu.device,
122            &gpu.queue,
123            frame.color,
124            &state.font,
125            state.bake_size_px,
126        ) {
127            Ok(renderer) => {
128                state.renderer = Some(renderer);
129                state.failure = None;
130            }
131            Err(error) => {
132                let failure = format!("{error:#}");
133                tracing::warn!("pass `{}` has no font: {failure}", self.name);
134                state.failure = Some(failure);
135            }
136        }
137        state.device = Some(gpu.device.clone());
138        state.queue = Some(gpu.queue.clone());
139        Ok(())
140    }
141}