1pub(crate) mod builder;
2pub(crate) mod wgpu_backend;
3
4use std::num::NonZeroU32;
5
6use ratatui::style::Color;
7use wgpu::{
8 Adapter,
9 BindGroup,
10 CommandEncoder,
11 Device,
12 Extent3d,
13 Queue,
14 RenderPipeline,
15 Surface,
16 SurfaceConfiguration,
17 SurfaceTexture,
18 TextureDescriptor,
19 TextureDimension,
20 TextureFormat,
21 TextureUsages,
22 TextureView,
23 TextureViewDescriptor,
24};
25#[cfg(test)]
26use wgpu::{
27 Buffer,
28 BufferDescriptor,
29 BufferUsages,
30 Texture,
31};
32
33use crate::colors::{
34 named::*,
35 Rgb,
36 ANSI_TO_RGB,
37};
38
39pub trait PostProcessor {
41 type UserData;
45
46 fn compile(
50 device: &Device,
51 text_view: &TextureView,
52 surface_config: &SurfaceConfiguration,
53 user_data: Self::UserData,
54 ) -> Self;
55
56 fn resize(
59 &mut self,
60 device: &Device,
61 text_view: &TextureView,
62 surface_config: &SurfaceConfiguration,
63 );
64
65 fn process(
76 &mut self,
77 encoder: &mut CommandEncoder,
78 queue: &Queue,
79 text_view: &TextureView,
80 surface_config: &SurfaceConfiguration,
81 surface_view: &TextureView,
82 );
83
84 fn needs_update(&self) -> bool {
90 false
91 }
92}
93
94pub struct Dimensions {
96 pub width: NonZeroU32,
97 pub height: NonZeroU32,
98}
99
100impl From<(NonZeroU32, NonZeroU32)> for Dimensions {
101 fn from((width, height): (NonZeroU32, NonZeroU32)) -> Self {
102 Self { width, height }
103 }
104}
105
106#[derive(Debug, Default, Clone, Copy)]
109#[non_exhaustive]
110pub enum Viewport {
111 #[default]
113 Full,
114 Shrink { width: u32, height: u32 },
117}
118
119mod private {
120 use wgpu::Surface;
121
122 use crate::backend::RenderTarget;
123 #[cfg(test)]
124 use crate::backend::{
125 HeadlessSurface,
126 HeadlessTarget,
127 };
128
129 pub trait Sealed {}
130
131 pub struct Token;
132
133 impl Sealed for Surface<'_> {}
134 impl Sealed for RenderTarget {}
135
136 #[cfg(test)]
137 impl Sealed for HeadlessTarget {}
138
139 #[cfg(test)]
140 impl Sealed for HeadlessSurface {}
141}
142
143pub trait RenderTexture: private::Sealed + Sized {
145 fn get_view(&self, _token: private::Token) -> &TextureView;
147 fn present(self, _token: private::Token) {}
149}
150
151impl RenderTexture for RenderTarget {
152 fn get_view(&self, _token: private::Token) -> &TextureView {
153 &self.view
154 }
155
156 fn present(self, _token: private::Token) {
157 self.texture.present();
158 }
159}
160
161#[cfg(test)]
162impl RenderTexture for HeadlessTarget {
163 fn get_view(&self, _token: private::Token) -> &TextureView {
164 &self.view
165 }
166}
167
168pub trait RenderSurface<'s>: private::Sealed {
170 type Target: RenderTexture;
171
172 fn wgpu_surface(&self, _token: private::Token) -> Option<&Surface<'s>>;
173
174 fn get_default_config(
175 &self,
176 adapter: &Adapter,
177 width: u32,
178 height: u32,
179 _token: private::Token,
180 ) -> Option<SurfaceConfiguration>;
181
182 fn configure(&mut self, device: &Device, config: &SurfaceConfiguration, _token: private::Token);
183
184 fn get_current_texture(&self, _token: private::Token) -> Option<Self::Target>;
185}
186
187pub struct RenderTarget {
188 texture: SurfaceTexture,
189 view: TextureView,
190}
191
192impl<'s> RenderSurface<'s> for Surface<'s> {
193 type Target = RenderTarget;
194
195 fn wgpu_surface(&self, _token: private::Token) -> Option<&Surface<'s>> {
196 Some(self)
197 }
198
199 fn get_default_config(
200 &self,
201 adapter: &Adapter,
202 width: u32,
203 height: u32,
204 _token: private::Token,
205 ) -> Option<SurfaceConfiguration> {
206 self.get_default_config(adapter, width, height)
207 }
208
209 fn configure(
210 &mut self,
211 device: &Device,
212 config: &SurfaceConfiguration,
213 _token: private::Token,
214 ) {
215 Surface::configure(self, device, config);
216 }
217
218 fn get_current_texture(&self, _token: private::Token) -> Option<Self::Target> {
219 let output = match self.get_current_texture() {
220 Ok(output) => output,
221 Err(err) => {
222 error!("{err}");
223 return None;
224 }
225 };
226
227 let view = output
228 .texture
229 .create_view(&TextureViewDescriptor::default());
230
231 Some(RenderTarget {
232 texture: output,
233 view,
234 })
235 }
236}
237
238#[cfg(test)]
239pub(crate) struct HeadlessTarget {
240 view: TextureView,
241}
242
243#[cfg(test)]
244pub(crate) struct HeadlessSurface {
245 pub(crate) texture: Option<Texture>,
246 pub(crate) buffer: Option<Buffer>,
247 pub(crate) buffer_width: u32,
248 pub(crate) width: u32,
249 pub(crate) height: u32,
250 pub(crate) format: TextureFormat,
251}
252
253#[cfg(test)]
254impl HeadlessSurface {
255 fn new(format: TextureFormat) -> Self {
256 Self {
257 format,
258 ..Default::default()
259 }
260 }
261}
262
263#[cfg(test)]
264impl Default for HeadlessSurface {
265 fn default() -> Self {
266 Self {
267 texture: Default::default(),
268 buffer: Default::default(),
269 buffer_width: Default::default(),
270 width: Default::default(),
271 height: Default::default(),
272 format: TextureFormat::Rgba8Unorm,
273 }
274 }
275}
276
277#[cfg(test)]
278impl RenderSurface<'static> for HeadlessSurface {
279 type Target = HeadlessTarget;
280
281 fn wgpu_surface(&self, _token: private::Token) -> Option<&Surface<'static>> {
282 None
283 }
284
285 fn get_default_config(
286 &self,
287 _adapter: &Adapter,
288 width: u32,
289 height: u32,
290 _token: private::Token,
291 ) -> Option<SurfaceConfiguration> {
292 Some(SurfaceConfiguration {
293 usage: TextureUsages::RENDER_ATTACHMENT,
294 format: self.format,
295 width,
296 height,
297 present_mode: wgpu::PresentMode::Immediate,
298 desired_maximum_frame_latency: 2,
299 alpha_mode: wgpu::CompositeAlphaMode::Auto,
300 view_formats: vec![],
301 })
302 }
303
304 fn configure(
305 &mut self,
306 device: &Device,
307 config: &SurfaceConfiguration,
308 _token: private::Token,
309 ) {
310 self.texture = Some(device.create_texture(&TextureDescriptor {
311 label: None,
312 size: Extent3d {
313 width: config.width,
314 height: config.height,
315 depth_or_array_layers: 1,
316 },
317 mip_level_count: 1,
318 sample_count: 1,
319 dimension: TextureDimension::D2,
320 format: self.format,
321 usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::COPY_SRC,
322 view_formats: &[],
323 }));
324
325 self.buffer_width = config.width * 4;
326 self.buffer = Some(device.create_buffer(&BufferDescriptor {
327 label: None,
328 size: (self.buffer_width * config.height) as u64,
329 usage: BufferUsages::COPY_DST | BufferUsages::MAP_READ,
330 mapped_at_creation: false,
331 }));
332 self.width = config.width;
333 self.height = config.height;
334 }
335
336 fn get_current_texture(&self, _token: private::Token) -> Option<Self::Target> {
337 self.texture.as_ref().map(|t| HeadlessTarget {
338 view: t.create_view(&TextureViewDescriptor::default()),
339 })
340 }
341}
342
343#[repr(C)]
344#[derive(bytemuck::Pod, bytemuck::Zeroable, Debug, Clone, Copy)]
345struct TextBgVertexMember {
346 vertex: [f32; 2],
347 bg_color: u32,
348}
349
350#[repr(C)]
352#[derive(bytemuck::Pod, bytemuck::Zeroable, Debug, Clone, Copy)]
353struct TextVertexMember {
354 vertex: [f32; 2],
355 uv: [f32; 2],
356 fg_color: u32,
357 underline_pos: u32,
358 underline_color: u32,
359}
360
361struct TextCacheBgPipeline {
362 pipeline: RenderPipeline,
363 fs_uniforms: BindGroup,
364}
365
366struct TextCacheFgPipeline {
367 pipeline: RenderPipeline,
368 fs_uniforms: BindGroup,
369 atlas_bindings: BindGroup,
370}
371
372struct WgpuState {
373 text_dest_view: TextureView,
374}
375
376fn c2c(color: ratatui::style::Color, reset: Rgb) -> Rgb {
377 match color {
378 Color::Reset => reset,
379 Color::Black => BLACK,
380 Color::Red => RED,
381 Color::Green => GREEN,
382 Color::Yellow => YELLOW,
383 Color::Blue => BLUE,
384 Color::Magenta => MAGENTA,
385 Color::Cyan => CYAN,
386 Color::Gray => GRAY,
387 Color::DarkGray => DARKGRAY,
388 Color::LightRed => LIGHTRED,
389 Color::LightGreen => LIGHTGREEN,
390 Color::LightYellow => LIGHTYELLOW,
391 Color::LightBlue => LIGHTBLUE,
392 Color::LightMagenta => LIGHTMAGENTA,
393 Color::LightCyan => LIGHTCYAN,
394 Color::White => WHITE,
395 Color::Rgb(r, g, b) => [r, g, b],
396 Color::Indexed(idx) => ANSI_TO_RGB[idx as usize],
397 }
398}
399
400fn build_wgpu_state(device: &Device, drawable_width: u32, drawable_height: u32) -> WgpuState {
401 let text_dest = device.create_texture(&TextureDescriptor {
402 label: Some("Text Compositor Out"),
403 size: Extent3d {
404 width: drawable_width.max(1),
405 height: drawable_height.max(1),
406 depth_or_array_layers: 1,
407 },
408 mip_level_count: 1,
409 sample_count: 1,
410 dimension: TextureDimension::D2,
411 format: TextureFormat::Rgba8Unorm,
412 usage: TextureUsages::TEXTURE_BINDING | TextureUsages::RENDER_ATTACHMENT,
413 view_formats: &[],
414 });
415
416 let text_dest_view = text_dest.create_view(&TextureViewDescriptor::default());
417
418 WgpuState { text_dest_view }
419}