1use super::cell_renderer::{Cell, CellRenderer};
2use super::graphics_renderer::GraphicsRenderer;
3use crate::custom_shader_renderer::CustomShaderRenderer;
4use anyhow::Result;
5use std::sync::Arc;
6use winit::dpi::PhysicalSize;
7use winit::window::Window;
8
9pub struct Renderer {
11 cell_renderer: CellRenderer,
13
14 graphics_renderer: GraphicsRenderer,
16
17 sixel_graphics: Vec<(u64, isize, usize, usize, usize, f32, usize)>,
20
21 egui_renderer: egui_wgpu::Renderer,
23
24 custom_shader_renderer: Option<CustomShaderRenderer>,
26 custom_shader_path: Option<String>,
28
29 cursor_shader_renderer: Option<CustomShaderRenderer>,
31 cursor_shader_path: Option<String>,
33
34 size: PhysicalSize<u32>,
36
37 dirty: bool,
39
40 #[allow(dead_code)]
42 #[allow(dead_code)]
43 debug_text: Option<String>,
44}
45
46impl Renderer {
47 #[allow(clippy::too_many_arguments)]
49 pub async fn new(
50 window: Arc<Window>,
51 font_family: Option<&str>,
52 font_family_bold: Option<&str>,
53 font_family_italic: Option<&str>,
54 font_family_bold_italic: Option<&str>,
55 font_ranges: &[crate::config::FontRange],
56 font_size: f32,
57 window_padding: f32,
58 line_spacing: f32,
59 char_spacing: f32,
60 scrollbar_position: &str,
61 scrollbar_width: f32,
62 scrollbar_thumb_color: [f32; 4],
63 scrollbar_track_color: [f32; 4],
64 enable_text_shaping: bool,
65 enable_ligatures: bool,
66 enable_kerning: bool,
67 vsync_mode: crate::config::VsyncMode,
68 window_opacity: f32,
69 background_color: [u8; 3],
70 background_image_path: Option<&str>,
71 background_image_enabled: bool,
72 background_image_mode: crate::config::BackgroundImageMode,
73 background_image_opacity: f32,
74 custom_shader_path: Option<&str>,
75 custom_shader_enabled: bool,
76 custom_shader_animation: bool,
77 custom_shader_animation_speed: f32,
78 custom_shader_text_opacity: f32,
79 custom_shader_full_content: bool,
80 cursor_shader_path: Option<&str>,
82 cursor_shader_enabled: bool,
83 cursor_shader_animation: bool,
84 cursor_shader_animation_speed: f32,
85 ) -> Result<Self> {
86 let size = window.inner_size();
87 let scale_factor = window.scale_factor();
88
89 let platform_dpi = if cfg!(target_os = "macos") {
92 72.0
93 } else {
94 96.0
95 };
96
97 let base_font_pixels = font_size * platform_dpi / 72.0;
99 let font_size_pixels = (base_font_pixels * scale_factor as f32).max(1.0);
100
101 let font_manager = crate::font_manager::FontManager::new(
103 font_family,
104 font_family_bold,
105 font_family_italic,
106 font_family_bold_italic,
107 font_ranges,
108 )?;
109
110 let (font_ascent, font_descent, font_leading, char_advance) = {
111 let primary_font = font_manager.get_font(0).unwrap();
112 let metrics = primary_font.metrics(&[]);
113 let scale = font_size_pixels / metrics.units_per_em as f32;
114
115 let glyph_id = primary_font.charmap().map('m');
117 let advance = primary_font.glyph_metrics(&[]).advance_width(glyph_id) * scale;
118
119 (
120 metrics.ascent * scale,
121 metrics.descent * scale,
122 metrics.leading * scale,
123 advance,
124 )
125 };
126
127 let natural_line_height = font_ascent + font_descent + font_leading;
130 let char_height = (natural_line_height * line_spacing).max(1.0);
131
132 let available_width = (size.width as f32 - window_padding * 2.0).max(0.0);
134 let available_height = (size.height as f32 - window_padding * 2.0).max(0.0);
135
136 let char_width = (char_advance * char_spacing).max(1.0); let cols = (available_width / char_width).max(1.0) as usize;
139 let rows = (available_height / char_height).max(1.0) as usize;
140
141 let cell_renderer = CellRenderer::new(
143 window.clone(),
144 font_family,
145 font_family_bold,
146 font_family_italic,
147 font_family_bold_italic,
148 font_ranges,
149 font_size,
150 cols,
151 rows,
152 window_padding,
153 line_spacing,
154 char_spacing,
155 scrollbar_position,
156 scrollbar_width,
157 scrollbar_thumb_color,
158 scrollbar_track_color,
159 enable_text_shaping,
160 enable_ligatures,
161 enable_kerning,
162 vsync_mode,
163 window_opacity,
164 background_color,
165 if background_image_enabled {
166 background_image_path
167 } else {
168 None
169 },
170 background_image_mode,
171 background_image_opacity,
172 )
173 .await?;
174
175 let egui_renderer = egui_wgpu::Renderer::new(
177 cell_renderer.device(),
178 cell_renderer.surface_format(),
179 egui_wgpu::RendererOptions {
180 msaa_samples: 1,
181 depth_stencil_format: None,
182 dithering: false,
183 predictable_texture_filtering: false,
184 },
185 );
186
187 let graphics_renderer = GraphicsRenderer::new(
189 cell_renderer.device(),
190 cell_renderer.surface_format(),
191 cell_renderer.cell_width(),
192 cell_renderer.cell_height(),
193 cell_renderer.window_padding(),
194 )?;
195
196 let (custom_shader_renderer, initial_shader_path) = if custom_shader_enabled {
198 if let Some(shader_path) = custom_shader_path {
199 let path = crate::config::Config::shader_path(shader_path);
200 match CustomShaderRenderer::new(
201 cell_renderer.device(),
202 cell_renderer.queue(),
203 cell_renderer.surface_format(),
204 &path,
205 size.width,
206 size.height,
207 custom_shader_animation,
208 custom_shader_animation_speed,
209 window_opacity,
210 custom_shader_text_opacity,
211 custom_shader_full_content,
212 ) {
213 Ok(mut renderer) => {
214 renderer.update_cell_dimensions(
216 cell_renderer.cell_width(),
217 cell_renderer.cell_height(),
218 window_padding,
219 );
220 log::info!(
221 "Custom shader renderer initialized from: {}",
222 path.display()
223 );
224 (Some(renderer), Some(shader_path.to_string()))
225 }
226 Err(e) => {
227 log::error!("Failed to load custom shader '{}': {}", path.display(), e);
228 (None, None)
229 }
230 }
231 } else {
232 (None, None)
233 }
234 } else {
235 (None, None)
236 };
237
238 let (cursor_shader_renderer, initial_cursor_shader_path) = if cursor_shader_enabled {
240 if let Some(shader_path) = cursor_shader_path {
241 let path = crate::config::Config::shader_path(shader_path);
242 match CustomShaderRenderer::new(
243 cell_renderer.device(),
244 cell_renderer.queue(),
245 cell_renderer.surface_format(),
246 &path,
247 size.width,
248 size.height,
249 cursor_shader_animation,
250 cursor_shader_animation_speed,
251 window_opacity,
252 1.0, true, ) {
255 Ok(mut renderer) => {
256 let cell_w = cell_renderer.cell_width();
258 let cell_h = cell_renderer.cell_height();
259 renderer.update_cell_dimensions(cell_w, cell_h, window_padding);
260 log::info!(
261 "Cursor shader renderer initialized from: {} (cell={}x{}, padding={})",
262 path.display(),
263 cell_w,
264 cell_h,
265 window_padding
266 );
267 (Some(renderer), Some(shader_path.to_string()))
268 }
269 Err(e) => {
270 log::error!("Failed to load cursor shader '{}': {}", path.display(), e);
271 (None, None)
272 }
273 }
274 } else {
275 (None, None)
276 }
277 } else {
278 (None, None)
279 };
280
281 Ok(Self {
282 cell_renderer,
283 graphics_renderer,
284 sixel_graphics: Vec::new(),
285 egui_renderer,
286 custom_shader_renderer,
287 custom_shader_path: initial_shader_path,
288 cursor_shader_renderer,
289 cursor_shader_path: initial_cursor_shader_path,
290 size,
291 dirty: true, debug_text: None,
293 })
294 }
295
296 pub fn resize(&mut self, new_size: PhysicalSize<u32>) -> (usize, usize) {
298 if new_size.width > 0 && new_size.height > 0 {
299 self.size = new_size;
300 self.dirty = true; let result = self.cell_renderer.resize(new_size.width, new_size.height);
302
303 self.graphics_renderer.update_cell_dimensions(
305 self.cell_renderer.cell_width(),
306 self.cell_renderer.cell_height(),
307 self.cell_renderer.window_padding(),
308 );
309
310 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
312 custom_shader.resize(self.cell_renderer.device(), new_size.width, new_size.height);
313 custom_shader.update_cell_dimensions(
315 self.cell_renderer.cell_width(),
316 self.cell_renderer.cell_height(),
317 self.cell_renderer.window_padding(),
318 );
319 }
320
321 if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
323 cursor_shader.resize(self.cell_renderer.device(), new_size.width, new_size.height);
324 cursor_shader.update_cell_dimensions(
326 self.cell_renderer.cell_width(),
327 self.cell_renderer.cell_height(),
328 self.cell_renderer.window_padding(),
329 );
330 }
331
332 return result;
333 }
334
335 self.cell_renderer.grid_size()
336 }
337
338 pub fn handle_scale_factor_change(
340 &mut self,
341 scale_factor: f64,
342 new_size: PhysicalSize<u32>,
343 ) -> (usize, usize) {
344 self.cell_renderer.update_scale_factor(scale_factor);
345 self.resize(new_size)
346 }
347
348 pub fn update_cells(&mut self, cells: &[Cell]) {
350 self.cell_renderer.update_cells(cells);
351 self.dirty = true; }
353
354 pub fn update_cursor(
356 &mut self,
357 position: (usize, usize),
358 opacity: f32,
359 style: par_term_emu_core_rust::cursor::CursorStyle,
360 ) {
361 self.cell_renderer.update_cursor(position, opacity, style);
362 self.dirty = true;
363 }
364
365 pub fn clear_cursor(&mut self) {
367 self.cell_renderer.clear_cursor();
368 self.dirty = true;
369 }
370
371 pub fn update_scrollbar(
378 &mut self,
379 scroll_offset: usize,
380 visible_lines: usize,
381 total_lines: usize,
382 ) {
383 self.cell_renderer
384 .update_scrollbar(scroll_offset, visible_lines, total_lines);
385 self.dirty = true; }
387
388 pub fn set_visual_bell_intensity(&mut self, intensity: f32) {
393 self.cell_renderer.set_visual_bell_intensity(intensity);
394 if intensity > 0.0 {
395 self.dirty = true; }
397 }
398
399 pub fn update_opacity(&mut self, opacity: f32) {
401 self.cell_renderer.update_opacity(opacity);
402
403 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
405 custom_shader.set_opacity(opacity);
406 }
407
408 self.dirty = true;
409 }
410
411 pub fn update_cursor_color(&mut self, color: [u8; 3]) {
413 self.cell_renderer.update_cursor_color(color);
414 self.dirty = true;
415 }
416
417 pub fn update_window_padding(&mut self, padding: f32) -> Option<(usize, usize)> {
420 let result = self.cell_renderer.update_window_padding(padding);
421 self.dirty = true;
422 result
423 }
424
425 pub fn set_background_image_enabled(
427 &mut self,
428 enabled: bool,
429 path: Option<&str>,
430 mode: crate::config::BackgroundImageMode,
431 opacity: f32,
432 ) {
433 let path = if enabled { path } else { None };
434 self.cell_renderer.set_background_image(path, mode, opacity);
435 self.dirty = true;
436 }
437
438 #[allow(dead_code)]
440 pub fn set_custom_shader_animation(&mut self, enabled: bool) {
441 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
442 custom_shader.set_animation_enabled(enabled);
443 self.dirty = true;
444 }
445 }
446
447 pub fn set_shader_mouse_position(&mut self, x: f32, y: f32) {
453 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
454 custom_shader.set_mouse_position(x, y);
455 }
456 }
457
458 pub fn set_shader_mouse_button(&mut self, pressed: bool, x: f32, y: f32) {
465 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
466 custom_shader.set_mouse_button(pressed, x, y);
467 }
468 }
469
470 pub fn update_shader_cursor(
481 &mut self,
482 col: usize,
483 row: usize,
484 opacity: f32,
485 color: [f32; 4],
486 style: par_term_emu_core_rust::cursor::CursorStyle,
487 ) {
488 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
489 custom_shader.update_cursor(col, row, opacity, color, style);
490 }
491 if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
493 cursor_shader.update_cursor(col, row, opacity, color, style);
494 }
495 }
496
497 pub fn update_cursor_shader_config(
505 &mut self,
506 color: [u8; 3],
507 trail_duration: f32,
508 glow_radius: f32,
509 glow_intensity: f32,
510 ) {
511 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
513 custom_shader.update_cursor_shader_config(
514 color,
515 trail_duration,
516 glow_radius,
517 glow_intensity,
518 );
519 }
520 if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
521 cursor_shader.update_cursor_shader_config(
522 color,
523 trail_duration,
524 glow_radius,
525 glow_intensity,
526 );
527 }
528 }
529
530 #[allow(clippy::too_many_arguments)]
542 pub fn set_cursor_shader_enabled(
543 &mut self,
544 enabled: bool,
545 path: Option<&str>,
546 window_opacity: f32,
547 animation_enabled: bool,
548 animation_speed: f32,
549 ) -> Result<(), String> {
550 match (enabled, path) {
551 (true, Some(path)) => {
552 let path_changed = self.cursor_shader_path.as_ref().is_none_or(|p| p != path);
553
554 if let Some(renderer) = &mut self.cursor_shader_renderer
556 && !path_changed
557 {
558 renderer.set_animation_enabled(animation_enabled);
559 renderer.set_animation_speed(animation_speed);
560 renderer.set_opacity(window_opacity);
561 self.dirty = true;
562 return Ok(());
563 }
564
565 let shader_path_full = crate::config::Config::shader_path(path);
566 match CustomShaderRenderer::new(
567 self.cell_renderer.device(),
568 self.cell_renderer.queue(),
569 self.cell_renderer.surface_format(),
570 &shader_path_full,
571 self.size.width,
572 self.size.height,
573 animation_enabled,
574 animation_speed,
575 window_opacity,
576 1.0, true, ) {
579 Ok(mut renderer) => {
580 renderer.update_cell_dimensions(
582 self.cell_renderer.cell_width(),
583 self.cell_renderer.cell_height(),
584 self.cell_renderer.window_padding(),
585 );
586 log::info!(
587 "Cursor shader enabled at runtime: {}",
588 shader_path_full.display()
589 );
590 self.cursor_shader_renderer = Some(renderer);
591 self.cursor_shader_path = Some(path.to_string());
592 self.dirty = true;
593 Ok(())
594 }
595 Err(e) => {
596 let error_msg = format!(
597 "Failed to load cursor shader '{}': {}",
598 shader_path_full.display(),
599 e
600 );
601 log::error!("{}", error_msg);
602 Err(error_msg)
603 }
604 }
605 }
606 _ => {
607 if self.cursor_shader_renderer.is_some() {
608 log::info!("Cursor shader disabled at runtime");
609 }
610 self.cursor_shader_renderer = None;
611 self.cursor_shader_path = None;
612 self.dirty = true;
613 Ok(())
614 }
615 }
616 }
617
618 #[allow(dead_code)]
620 pub fn cursor_shader_path(&self) -> Option<&str> {
621 self.cursor_shader_path.as_deref()
622 }
623
624 pub fn reload_cursor_shader_from_source(&mut self, source: &str) -> Result<()> {
626 if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
627 cursor_shader.reload_from_source(
628 self.cell_renderer.device(),
629 source,
630 "cursor_editor",
631 )?;
632 self.dirty = true;
633 Ok(())
634 } else {
635 Err(anyhow::anyhow!("No cursor shader renderer active"))
636 }
637 }
638
639 pub fn update_scrollbar_appearance(
641 &mut self,
642 width: f32,
643 thumb_color: [f32; 4],
644 track_color: [f32; 4],
645 ) {
646 self.cell_renderer
647 .update_scrollbar_appearance(width, thumb_color, track_color);
648 self.dirty = true;
649 }
650
651 #[allow(dead_code)]
653 pub fn update_scrollbar_position(&mut self, position: &str) {
654 self.cell_renderer.update_scrollbar_position(position);
655 self.dirty = true;
656 }
657
658 pub fn reload_shader_from_source(&mut self, source: &str) -> Result<()> {
669 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
670 custom_shader.reload_from_source(self.cell_renderer.device(), source, "editor")?;
671 self.dirty = true;
672 Ok(())
673 } else {
674 Err(anyhow::anyhow!(
675 "No custom shader is currently loaded. Enable a custom shader first."
676 ))
677 }
678 }
679
680 #[allow(clippy::too_many_arguments)]
686 pub fn set_custom_shader_enabled(
687 &mut self,
688 enabled: bool,
689 shader_path: Option<&str>,
690 window_opacity: f32,
691 text_opacity: f32,
692 animation_enabled: bool,
693 animation_speed: f32,
694 full_content: bool,
695 ) -> Result<(), String> {
696 match (enabled, shader_path) {
697 (true, Some(path)) => {
698 let path_changed = self.custom_shader_path.as_deref() != Some(path);
700
701 if let Some(renderer) = &mut self.custom_shader_renderer {
703 if !path_changed {
704 renderer.set_animation_enabled(animation_enabled);
705 renderer.set_animation_speed(animation_speed);
706 renderer.set_opacity(window_opacity);
707 renderer.set_full_content_mode(full_content);
708 return Ok(());
709 }
710 log::info!("Shader path changed, reloading shader");
712 }
713
714 let shader_path_full = crate::config::Config::shader_path(path);
715 match CustomShaderRenderer::new(
716 self.cell_renderer.device(),
717 self.cell_renderer.queue(),
718 self.cell_renderer.surface_format(),
719 &shader_path_full,
720 self.size.width,
721 self.size.height,
722 animation_enabled,
723 animation_speed,
724 window_opacity,
725 text_opacity,
726 full_content,
727 ) {
728 Ok(mut renderer) => {
729 renderer.update_cell_dimensions(
731 self.cell_renderer.cell_width(),
732 self.cell_renderer.cell_height(),
733 self.cell_renderer.window_padding(),
734 );
735 log::info!(
736 "Custom shader enabled at runtime: {}",
737 shader_path_full.display()
738 );
739 self.custom_shader_renderer = Some(renderer);
740 self.custom_shader_path = Some(path.to_string());
741 self.dirty = true;
742 Ok(())
743 }
744 Err(e) => {
745 let error_msg = format!(
746 "Failed to load shader '{}': {}",
747 shader_path_full.display(),
748 e
749 );
750 log::error!("{}", error_msg);
751 Err(error_msg)
752 }
753 }
754 }
755 _ => {
756 if self.custom_shader_renderer.is_some() {
757 log::info!("Custom shader disabled at runtime");
758 }
759 self.custom_shader_renderer = None;
760 self.custom_shader_path = None;
761 self.dirty = true;
762 Ok(())
763 }
764 }
765 }
766
767 pub fn update_background_image_opacity(&mut self, opacity: f32) {
769 self.cell_renderer.update_background_image_opacity(opacity);
770 self.dirty = true;
771 }
772
773 #[allow(dead_code)]
781 pub fn update_graphics(
782 &mut self,
783 graphics: &[par_term_emu_core_rust::graphics::TerminalGraphic],
784 view_scroll_offset: usize,
785 scrollback_len: usize,
786 visible_rows: usize,
787 ) -> Result<()> {
788 self.sixel_graphics.clear();
790
791 let total_lines = scrollback_len + visible_rows;
796 let view_end = total_lines.saturating_sub(view_scroll_offset);
797 let view_start = view_end.saturating_sub(visible_rows);
798
799 for graphic in graphics {
801 let id = graphic.id;
803 let (col, row) = graphic.position;
804
805 let screen_row: isize = if let Some(sb_row) = graphic.scrollback_row {
807 sb_row as isize - view_start as isize
810 } else {
811 let absolute_row = scrollback_len.saturating_sub(graphic.scroll_offset_rows) + row;
815
816 debug_trace!(
817 "RENDERER",
818 "CALC: scrollback_len={}, row={}, scroll_offset_rows={}, absolute_row={}, view_start={}, screen_row={}",
819 scrollback_len,
820 row,
821 graphic.scroll_offset_rows,
822 absolute_row,
823 view_start,
824 absolute_row as isize - view_start as isize
825 );
826
827 absolute_row as isize - view_start as isize
828 };
829
830 debug_log!(
831 "RENDERER",
832 "Graphics update: id={}, protocol={:?}, pos=({},{}), screen_row={}, scrollback_row={:?}, scroll_offset_rows={}, size={}x{}, view=[{},{})",
833 id,
834 graphic.protocol,
835 col,
836 row,
837 screen_row,
838 graphic.scrollback_row,
839 graphic.scroll_offset_rows,
840 graphic.width,
841 graphic.height,
842 view_start,
843 view_end
844 );
845
846 self.graphics_renderer.get_or_create_texture(
848 self.cell_renderer.device(),
849 self.cell_renderer.queue(),
850 id,
851 &graphic.pixels, graphic.width as u32,
853 graphic.height as u32,
854 )?;
855
856 let width_cells =
859 ((graphic.width as f32 / self.cell_renderer.cell_width()).ceil() as usize).max(1);
860 let height_cells =
861 ((graphic.height as f32 / self.cell_renderer.cell_height()).ceil() as usize).max(1);
862
863 let effective_clip_rows = if screen_row < 0 {
867 (-screen_row) as usize
868 } else {
869 0
870 };
871
872 self.sixel_graphics.push((
873 id,
874 screen_row, col, width_cells,
877 height_cells,
878 1.0, effective_clip_rows, ));
881 }
882
883 if !graphics.is_empty() {
884 self.dirty = true; }
886
887 Ok(())
888 }
889
890 pub fn needs_continuous_render(&self) -> bool {
895 let custom_needs = self
896 .custom_shader_renderer
897 .as_ref()
898 .is_some_and(|r| r.animation_enabled() || r.cursor_needs_animation());
899 let cursor_needs = self
900 .cursor_shader_renderer
901 .as_ref()
902 .is_some_and(|r| r.animation_enabled() || r.cursor_needs_animation());
903 custom_needs || cursor_needs
904 }
905
906 pub fn render(
909 &mut self,
910 egui_data: Option<(egui::FullOutput, &egui::Context)>,
911 force_egui_opaque: bool,
912 show_scrollbar: bool,
913 ) -> Result<bool> {
914 let force_render = self.needs_continuous_render();
916
917 if !self.dirty && egui_data.is_none() && !force_render {
918 return Ok(false);
920 }
921
922 let has_custom_shader = self.custom_shader_renderer.is_some();
924 let has_cursor_shader = self.cursor_shader_renderer.is_some();
925
926 let t1 = std::time::Instant::now();
928 let surface_texture = if has_custom_shader {
929 self.cell_renderer.render_to_texture(
931 self.custom_shader_renderer
932 .as_ref()
933 .unwrap()
934 .intermediate_texture_view(),
935 )?
936 } else if has_cursor_shader {
937 self.cell_renderer.render_to_texture(
939 self.cursor_shader_renderer
940 .as_ref()
941 .unwrap()
942 .intermediate_texture_view(),
943 )?
944 } else {
945 self.cell_renderer.render(show_scrollbar)?
947 };
948 let cell_render_time = t1.elapsed();
949
950 let t_custom = std::time::Instant::now();
952 let custom_shader_time = if let Some(ref mut custom_shader) = self.custom_shader_renderer {
953 if has_cursor_shader {
954 custom_shader.render(
956 self.cell_renderer.device(),
957 self.cell_renderer.queue(),
958 self.cursor_shader_renderer
959 .as_ref()
960 .unwrap()
961 .intermediate_texture_view(),
962 )?;
963 } else {
964 let surface_view = surface_texture
966 .texture
967 .create_view(&wgpu::TextureViewDescriptor::default());
968 custom_shader.render(
969 self.cell_renderer.device(),
970 self.cell_renderer.queue(),
971 &surface_view,
972 )?;
973
974 self.cell_renderer
976 .render_overlays(&surface_texture, show_scrollbar)?;
977 }
978 t_custom.elapsed()
979 } else {
980 std::time::Duration::ZERO
981 };
982
983 let t_cursor = std::time::Instant::now();
985 let cursor_shader_time = if let Some(ref mut cursor_shader) = self.cursor_shader_renderer {
986 log::trace!("Rendering cursor shader");
987 let surface_view = surface_texture
988 .texture
989 .create_view(&wgpu::TextureViewDescriptor::default());
990 cursor_shader.render(
991 self.cell_renderer.device(),
992 self.cell_renderer.queue(),
993 &surface_view,
994 )?;
995
996 self.cell_renderer
998 .render_overlays(&surface_texture, show_scrollbar)?;
999 t_cursor.elapsed()
1000 } else {
1001 std::time::Duration::ZERO
1002 };
1003
1004 let t2 = std::time::Instant::now();
1006 if !self.sixel_graphics.is_empty() {
1007 self.render_sixel_graphics(&surface_texture)?;
1008 }
1009 let sixel_render_time = t2.elapsed();
1010
1011 let t3 = std::time::Instant::now();
1013 if let Some((egui_output, egui_ctx)) = egui_data {
1014 self.render_egui(&surface_texture, egui_output, egui_ctx, force_egui_opaque)?;
1015 }
1016 let egui_render_time = t3.elapsed();
1017
1018 let t4 = std::time::Instant::now();
1020 surface_texture.present();
1021 let present_time = t4.elapsed();
1022
1023 let total = cell_render_time
1025 + custom_shader_time
1026 + cursor_shader_time
1027 + sixel_render_time
1028 + egui_render_time
1029 + present_time;
1030 if present_time.as_millis() > 10 || total.as_millis() > 10 {
1031 log::info!(
1032 "RENDER_BREAKDOWN: CellRender={:.2}ms BgShader={:.2}ms CursorShader={:.2}ms Sixel={:.2}ms Egui={:.2}ms PRESENT={:.2}ms Total={:.2}ms",
1033 cell_render_time.as_secs_f64() * 1000.0,
1034 custom_shader_time.as_secs_f64() * 1000.0,
1035 cursor_shader_time.as_secs_f64() * 1000.0,
1036 sixel_render_time.as_secs_f64() * 1000.0,
1037 egui_render_time.as_secs_f64() * 1000.0,
1038 present_time.as_secs_f64() * 1000.0,
1039 total.as_secs_f64() * 1000.0
1040 );
1041 }
1042
1043 self.dirty = false;
1045
1046 Ok(true)
1047 }
1048
1049 fn render_sixel_graphics(&mut self, surface_texture: &wgpu::SurfaceTexture) -> Result<()> {
1051 use wgpu::TextureViewDescriptor;
1052
1053 let view = surface_texture
1055 .texture
1056 .create_view(&TextureViewDescriptor::default());
1057
1058 let mut encoder =
1060 self.cell_renderer
1061 .device()
1062 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1063 label: Some("sixel encoder"),
1064 });
1065
1066 {
1068 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1069 label: Some("sixel render pass"),
1070 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1071 view: &view,
1072 resolve_target: None,
1073 ops: wgpu::Operations {
1074 load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store,
1076 },
1077 depth_slice: None,
1078 })],
1079 depth_stencil_attachment: None,
1080 timestamp_writes: None,
1081 occlusion_query_set: None,
1082 });
1083
1084 self.graphics_renderer.render(
1086 self.cell_renderer.device(),
1087 self.cell_renderer.queue(),
1088 &mut render_pass,
1089 &self.sixel_graphics,
1090 self.size.width as f32,
1091 self.size.height as f32,
1092 )?;
1093 } self.cell_renderer
1097 .queue()
1098 .submit(std::iter::once(encoder.finish()));
1099
1100 Ok(())
1101 }
1102
1103 fn render_egui(
1105 &mut self,
1106 surface_texture: &wgpu::SurfaceTexture,
1107 egui_output: egui::FullOutput,
1108 egui_ctx: &egui::Context,
1109 force_opaque: bool,
1110 ) -> Result<()> {
1111 use wgpu::TextureViewDescriptor;
1112
1113 let view = surface_texture
1115 .texture
1116 .create_view(&TextureViewDescriptor::default());
1117
1118 let mut encoder =
1120 self.cell_renderer
1121 .device()
1122 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1123 label: Some("egui encoder"),
1124 });
1125
1126 let screen_descriptor = egui_wgpu::ScreenDescriptor {
1128 size_in_pixels: [self.size.width, self.size.height],
1129 pixels_per_point: egui_output.pixels_per_point,
1130 };
1131
1132 for (id, image_delta) in &egui_output.textures_delta.set {
1134 self.egui_renderer.update_texture(
1135 self.cell_renderer.device(),
1136 self.cell_renderer.queue(),
1137 *id,
1138 image_delta,
1139 );
1140 }
1141
1142 let mut paint_jobs = egui_ctx.tessellate(egui_output.shapes, egui_output.pixels_per_point);
1144
1145 if force_opaque {
1147 for job in paint_jobs.iter_mut() {
1148 match &mut job.primitive {
1149 egui::epaint::Primitive::Mesh(mesh) => {
1150 for v in mesh.vertices.iter_mut() {
1151 v.color[3] = 255;
1152 }
1153 }
1154 egui::epaint::Primitive::Callback(_) => {}
1155 }
1156 }
1157 }
1158
1159 self.egui_renderer.update_buffers(
1161 self.cell_renderer.device(),
1162 self.cell_renderer.queue(),
1163 &mut encoder,
1164 &paint_jobs,
1165 &screen_descriptor,
1166 );
1167
1168 {
1170 let render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1171 label: Some("egui render pass"),
1172 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1173 view: &view,
1174 resolve_target: None,
1175 ops: wgpu::Operations {
1176 load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store,
1178 },
1179 depth_slice: None,
1180 })],
1181 depth_stencil_attachment: None,
1182 timestamp_writes: None,
1183 occlusion_query_set: None,
1184 });
1185
1186 let mut render_pass = render_pass.forget_lifetime();
1188
1189 self.egui_renderer
1190 .render(&mut render_pass, &paint_jobs, &screen_descriptor);
1191 } self.cell_renderer
1195 .queue()
1196 .submit(std::iter::once(encoder.finish()));
1197
1198 for id in &egui_output.textures_delta.free {
1200 self.egui_renderer.free_texture(id);
1201 }
1202
1203 Ok(())
1204 }
1205
1206 pub fn size(&self) -> PhysicalSize<u32> {
1208 self.size
1209 }
1210
1211 pub fn grid_size(&self) -> (usize, usize) {
1213 self.cell_renderer.grid_size()
1214 }
1215
1216 pub fn cell_width(&self) -> f32 {
1218 self.cell_renderer.cell_width()
1219 }
1220
1221 pub fn cell_height(&self) -> f32 {
1223 self.cell_renderer.cell_height()
1224 }
1225
1226 pub fn window_padding(&self) -> f32 {
1228 self.cell_renderer.window_padding()
1229 }
1230
1231 pub fn scrollbar_contains_point(&self, x: f32, y: f32) -> bool {
1237 self.cell_renderer.scrollbar_contains_point(x, y)
1238 }
1239
1240 pub fn scrollbar_thumb_bounds(&self) -> Option<(f32, f32)> {
1242 self.cell_renderer.scrollbar_thumb_bounds()
1243 }
1244
1245 pub fn scrollbar_track_contains_x(&self, x: f32) -> bool {
1247 self.cell_renderer.scrollbar_track_contains_x(x)
1248 }
1249
1250 pub fn scrollbar_mouse_y_to_scroll_offset(&self, mouse_y: f32) -> Option<usize> {
1258 self.cell_renderer
1259 .scrollbar_mouse_y_to_scroll_offset(mouse_y)
1260 }
1261
1262 #[allow(dead_code)]
1264 pub fn is_dirty(&self) -> bool {
1265 self.dirty
1266 }
1267
1268 #[allow(dead_code)]
1270 pub fn mark_dirty(&mut self) {
1271 self.dirty = true;
1272 }
1273
1274 #[allow(dead_code)]
1276 pub fn clear_sixel_cache(&mut self) {
1277 self.graphics_renderer.clear_cache();
1278 self.sixel_graphics.clear();
1279 self.dirty = true;
1280 }
1281
1282 #[allow(dead_code)]
1284 pub fn sixel_cache_size(&self) -> usize {
1285 self.graphics_renderer.cache_size()
1286 }
1287
1288 #[allow(dead_code)]
1290 pub fn remove_sixel_texture(&mut self, id: u64) {
1291 self.graphics_renderer.remove_texture(id);
1292 self.sixel_graphics
1293 .retain(|(gid, _, _, _, _, _, _)| *gid != id);
1294 self.dirty = true;
1295 }
1296
1297 #[allow(dead_code)]
1299 #[allow(dead_code)]
1300 pub fn render_debug_overlay(&mut self, text: &str) {
1301 self.debug_text = Some(text.to_string());
1302 self.dirty = true; }
1304
1305 pub fn reconfigure_surface(&mut self) {
1308 self.cell_renderer.reconfigure_surface();
1309 self.dirty = true;
1310 }
1311
1312 pub fn clear_glyph_cache(&mut self) {
1315 self.cell_renderer.clear_glyph_cache();
1316 self.dirty = true;
1317 }
1318}