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#[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#[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 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 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}