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