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 size: PhysicalSize<u32>,
31
32 dirty: bool,
34
35 #[allow(dead_code)]
37 #[allow(dead_code)]
38 debug_text: Option<String>,
39}
40
41impl Renderer {
42 #[allow(clippy::too_many_arguments)]
44 pub async fn new(
45 window: Arc<Window>,
46 font_family: Option<&str>,
47 font_family_bold: Option<&str>,
48 font_family_italic: Option<&str>,
49 font_family_bold_italic: Option<&str>,
50 font_ranges: &[crate::config::FontRange],
51 font_size: f32,
52 window_padding: f32,
53 line_spacing: f32,
54 char_spacing: f32,
55 scrollbar_position: &str,
56 scrollbar_width: f32,
57 scrollbar_thumb_color: [f32; 4],
58 scrollbar_track_color: [f32; 4],
59 enable_text_shaping: bool,
60 enable_ligatures: bool,
61 enable_kerning: bool,
62 vsync_mode: crate::config::VsyncMode,
63 window_opacity: f32,
64 background_color: [u8; 3],
65 background_image_path: Option<&str>,
66 background_image_enabled: bool,
67 background_image_mode: crate::config::BackgroundImageMode,
68 background_image_opacity: f32,
69 custom_shader_path: Option<&str>,
70 custom_shader_enabled: bool,
71 custom_shader_animation: bool,
72 custom_shader_animation_speed: f32,
73 custom_shader_text_opacity: f32,
74 custom_shader_full_content: bool,
75 ) -> Result<Self> {
76 let size = window.inner_size();
77 let scale_factor = window.scale_factor();
78
79 let platform_dpi = if cfg!(target_os = "macos") {
82 72.0
83 } else {
84 96.0
85 };
86
87 let base_font_pixels = font_size * platform_dpi / 72.0;
89 let font_size_pixels = (base_font_pixels * scale_factor as f32).max(1.0);
90
91 let font_manager = crate::font_manager::FontManager::new(
93 font_family,
94 font_family_bold,
95 font_family_italic,
96 font_family_bold_italic,
97 font_ranges,
98 )?;
99
100 let (font_ascent, font_descent, font_leading, char_advance) = {
101 let primary_font = font_manager.get_font(0).unwrap();
102 let metrics = primary_font.metrics(&[]);
103 let scale = font_size_pixels / metrics.units_per_em as f32;
104
105 let glyph_id = primary_font.charmap().map('m');
107 let advance = primary_font.glyph_metrics(&[]).advance_width(glyph_id) * scale;
108
109 (
110 metrics.ascent * scale,
111 metrics.descent * scale,
112 metrics.leading * scale,
113 advance,
114 )
115 };
116
117 let natural_line_height = font_ascent + font_descent + font_leading;
120 let char_height = (natural_line_height * line_spacing).max(1.0);
121
122 let available_width = (size.width as f32 - window_padding * 2.0).max(0.0);
124 let available_height = (size.height as f32 - window_padding * 2.0).max(0.0);
125
126 let char_width = (char_advance * char_spacing).max(1.0); let cols = (available_width / char_width).max(1.0) as usize;
129 let rows = (available_height / char_height).max(1.0) as usize;
130
131 let cell_renderer = CellRenderer::new(
133 window.clone(),
134 font_family,
135 font_family_bold,
136 font_family_italic,
137 font_family_bold_italic,
138 font_ranges,
139 font_size,
140 cols,
141 rows,
142 window_padding,
143 line_spacing,
144 char_spacing,
145 scrollbar_position,
146 scrollbar_width,
147 scrollbar_thumb_color,
148 scrollbar_track_color,
149 enable_text_shaping,
150 enable_ligatures,
151 enable_kerning,
152 vsync_mode,
153 window_opacity,
154 background_color,
155 if background_image_enabled {
156 background_image_path
157 } else {
158 None
159 },
160 background_image_mode,
161 background_image_opacity,
162 )
163 .await?;
164
165 let egui_renderer = egui_wgpu::Renderer::new(
167 cell_renderer.device(),
168 cell_renderer.surface_format(),
169 egui_wgpu::RendererOptions {
170 msaa_samples: 1,
171 depth_stencil_format: None,
172 dithering: false,
173 predictable_texture_filtering: false,
174 },
175 );
176
177 let graphics_renderer = GraphicsRenderer::new(
179 cell_renderer.device(),
180 cell_renderer.surface_format(),
181 cell_renderer.cell_width(),
182 cell_renderer.cell_height(),
183 cell_renderer.window_padding(),
184 )?;
185
186 let (custom_shader_renderer, initial_shader_path) = if custom_shader_enabled {
188 if let Some(shader_path) = custom_shader_path {
189 let path = crate::config::Config::shader_path(shader_path);
190 match CustomShaderRenderer::new(
191 cell_renderer.device(),
192 cell_renderer.queue(),
193 cell_renderer.surface_format(),
194 &path,
195 size.width,
196 size.height,
197 custom_shader_animation,
198 custom_shader_animation_speed,
199 window_opacity,
200 custom_shader_text_opacity,
201 custom_shader_full_content,
202 ) {
203 Ok(renderer) => {
204 log::info!(
205 "Custom shader renderer initialized from: {}",
206 path.display()
207 );
208 (Some(renderer), Some(shader_path.to_string()))
209 }
210 Err(e) => {
211 log::error!("Failed to load custom shader '{}': {}", path.display(), e);
212 (None, None)
213 }
214 }
215 } else {
216 (None, None)
217 }
218 } else {
219 (None, None)
220 };
221
222 Ok(Self {
223 cell_renderer,
224 graphics_renderer,
225 sixel_graphics: Vec::new(),
226 egui_renderer,
227 custom_shader_renderer,
228 custom_shader_path: initial_shader_path,
229 size,
230 dirty: true, debug_text: None,
232 })
233 }
234
235 pub fn resize(&mut self, new_size: PhysicalSize<u32>) -> (usize, usize) {
237 if new_size.width > 0 && new_size.height > 0 {
238 self.size = new_size;
239 self.dirty = true; let result = self.cell_renderer.resize(new_size.width, new_size.height);
241
242 self.graphics_renderer.update_cell_dimensions(
244 self.cell_renderer.cell_width(),
245 self.cell_renderer.cell_height(),
246 self.cell_renderer.window_padding(),
247 );
248
249 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
251 custom_shader.resize(self.cell_renderer.device(), new_size.width, new_size.height);
252 }
253
254 return result;
255 }
256
257 self.cell_renderer.grid_size()
258 }
259
260 pub fn handle_scale_factor_change(
262 &mut self,
263 scale_factor: f64,
264 new_size: PhysicalSize<u32>,
265 ) -> (usize, usize) {
266 self.cell_renderer.update_scale_factor(scale_factor);
267 self.resize(new_size)
268 }
269
270 pub fn update_cells(&mut self, cells: &[Cell]) {
272 self.cell_renderer.update_cells(cells);
273 self.dirty = true; }
275
276 pub fn update_cursor(
278 &mut self,
279 position: (usize, usize),
280 opacity: f32,
281 style: par_term_emu_core_rust::cursor::CursorStyle,
282 ) {
283 self.cell_renderer.update_cursor(position, opacity, style);
284 self.dirty = true;
285 }
286
287 pub fn clear_cursor(&mut self) {
289 self.cell_renderer.clear_cursor();
290 self.dirty = true;
291 }
292
293 pub fn update_scrollbar(
300 &mut self,
301 scroll_offset: usize,
302 visible_lines: usize,
303 total_lines: usize,
304 ) {
305 self.cell_renderer
306 .update_scrollbar(scroll_offset, visible_lines, total_lines);
307 self.dirty = true; }
309
310 pub fn set_visual_bell_intensity(&mut self, intensity: f32) {
315 self.cell_renderer.set_visual_bell_intensity(intensity);
316 if intensity > 0.0 {
317 self.dirty = true; }
319 }
320
321 pub fn update_opacity(&mut self, opacity: f32) {
323 self.cell_renderer.update_opacity(opacity);
324
325 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
327 custom_shader.set_opacity(opacity);
328 }
329
330 self.dirty = true;
331 }
332
333 pub fn update_window_padding(&mut self, padding: f32) -> Option<(usize, usize)> {
336 let result = self.cell_renderer.update_window_padding(padding);
337 self.dirty = true;
338 result
339 }
340
341 pub fn set_background_image_enabled(
343 &mut self,
344 enabled: bool,
345 path: Option<&str>,
346 mode: crate::config::BackgroundImageMode,
347 opacity: f32,
348 ) {
349 let path = if enabled { path } else { None };
350 self.cell_renderer.set_background_image(path, mode, opacity);
351 self.dirty = true;
352 }
353
354 #[allow(dead_code)]
356 pub fn set_custom_shader_animation(&mut self, enabled: bool) {
357 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
358 custom_shader.set_animation_enabled(enabled);
359 self.dirty = true;
360 }
361 }
362
363 pub fn set_shader_mouse_position(&mut self, x: f32, y: f32) {
369 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
370 custom_shader.set_mouse_position(x, y);
371 }
372 }
373
374 pub fn set_shader_mouse_button(&mut self, pressed: bool, x: f32, y: f32) {
381 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
382 custom_shader.set_mouse_button(pressed, x, y);
383 }
384 }
385
386 pub fn update_scrollbar_appearance(
388 &mut self,
389 width: f32,
390 thumb_color: [f32; 4],
391 track_color: [f32; 4],
392 ) {
393 self.cell_renderer
394 .update_scrollbar_appearance(width, thumb_color, track_color);
395 self.dirty = true;
396 }
397
398 #[allow(dead_code)]
400 pub fn update_scrollbar_position(&mut self, position: &str) {
401 self.cell_renderer.update_scrollbar_position(position);
402 self.dirty = true;
403 }
404
405 pub fn reload_shader_from_source(&mut self, source: &str) -> Result<()> {
416 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
417 custom_shader.reload_from_source(self.cell_renderer.device(), source, "editor")?;
418 self.dirty = true;
419 Ok(())
420 } else {
421 Err(anyhow::anyhow!(
422 "No custom shader is currently loaded. Enable a custom shader first."
423 ))
424 }
425 }
426
427 #[allow(clippy::too_many_arguments)]
433 pub fn set_custom_shader_enabled(
434 &mut self,
435 enabled: bool,
436 shader_path: Option<&str>,
437 window_opacity: f32,
438 text_opacity: f32,
439 animation_enabled: bool,
440 animation_speed: f32,
441 full_content: bool,
442 ) -> Result<(), String> {
443 match (enabled, shader_path) {
444 (true, Some(path)) => {
445 let path_changed = self.custom_shader_path.as_deref() != Some(path);
447
448 if let Some(renderer) = &mut self.custom_shader_renderer {
450 if !path_changed {
451 renderer.set_animation_enabled(animation_enabled);
452 renderer.set_animation_speed(animation_speed);
453 renderer.set_opacity(window_opacity);
454 renderer.set_full_content_mode(full_content);
455 return Ok(());
456 }
457 log::info!("Shader path changed, reloading shader");
459 }
460
461 let shader_path_full = crate::config::Config::shader_path(path);
462 match CustomShaderRenderer::new(
463 self.cell_renderer.device(),
464 self.cell_renderer.queue(),
465 self.cell_renderer.surface_format(),
466 &shader_path_full,
467 self.size.width,
468 self.size.height,
469 animation_enabled,
470 animation_speed,
471 window_opacity,
472 text_opacity,
473 full_content,
474 ) {
475 Ok(renderer) => {
476 log::info!(
477 "Custom shader enabled at runtime: {}",
478 shader_path_full.display()
479 );
480 self.custom_shader_renderer = Some(renderer);
481 self.custom_shader_path = Some(path.to_string());
482 self.dirty = true;
483 Ok(())
484 }
485 Err(e) => {
486 let error_msg = format!(
487 "Failed to load shader '{}': {}",
488 shader_path_full.display(),
489 e
490 );
491 log::error!("{}", error_msg);
492 Err(error_msg)
493 }
494 }
495 }
496 _ => {
497 if self.custom_shader_renderer.is_some() {
498 log::info!("Custom shader disabled at runtime");
499 }
500 self.custom_shader_renderer = None;
501 self.custom_shader_path = None;
502 self.dirty = true;
503 Ok(())
504 }
505 }
506 }
507
508 pub fn update_background_image_opacity(&mut self, opacity: f32) {
510 self.cell_renderer.update_background_image_opacity(opacity);
511 self.dirty = true;
512 }
513
514 #[allow(dead_code)]
522 pub fn update_graphics(
523 &mut self,
524 graphics: &[par_term_emu_core_rust::graphics::TerminalGraphic],
525 view_scroll_offset: usize,
526 scrollback_len: usize,
527 visible_rows: usize,
528 ) -> Result<()> {
529 self.sixel_graphics.clear();
531
532 let total_lines = scrollback_len + visible_rows;
537 let view_end = total_lines.saturating_sub(view_scroll_offset);
538 let view_start = view_end.saturating_sub(visible_rows);
539
540 for graphic in graphics {
542 let id = graphic.id;
544 let (col, row) = graphic.position;
545
546 let screen_row: isize = if let Some(sb_row) = graphic.scrollback_row {
548 sb_row as isize - view_start as isize
551 } else {
552 let absolute_row = scrollback_len.saturating_sub(graphic.scroll_offset_rows) + row;
556
557 debug_trace!(
558 "RENDERER",
559 "CALC: scrollback_len={}, row={}, scroll_offset_rows={}, absolute_row={}, view_start={}, screen_row={}",
560 scrollback_len,
561 row,
562 graphic.scroll_offset_rows,
563 absolute_row,
564 view_start,
565 absolute_row as isize - view_start as isize
566 );
567
568 absolute_row as isize - view_start as isize
569 };
570
571 debug_log!(
572 "RENDERER",
573 "Graphics update: id={}, protocol={:?}, pos=({},{}), screen_row={}, scrollback_row={:?}, scroll_offset_rows={}, size={}x{}, view=[{},{})",
574 id,
575 graphic.protocol,
576 col,
577 row,
578 screen_row,
579 graphic.scrollback_row,
580 graphic.scroll_offset_rows,
581 graphic.width,
582 graphic.height,
583 view_start,
584 view_end
585 );
586
587 self.graphics_renderer.get_or_create_texture(
589 self.cell_renderer.device(),
590 self.cell_renderer.queue(),
591 id,
592 &graphic.pixels, graphic.width as u32,
594 graphic.height as u32,
595 )?;
596
597 let width_cells =
600 ((graphic.width as f32 / self.cell_renderer.cell_width()).ceil() as usize).max(1);
601 let height_cells =
602 ((graphic.height as f32 / self.cell_renderer.cell_height()).ceil() as usize).max(1);
603
604 let effective_clip_rows = if screen_row < 0 {
608 (-screen_row) as usize
609 } else {
610 0
611 };
612
613 self.sixel_graphics.push((
614 id,
615 screen_row, col, width_cells,
618 height_cells,
619 1.0, effective_clip_rows, ));
622 }
623
624 if !graphics.is_empty() {
625 self.dirty = true; }
627
628 Ok(())
629 }
630
631 pub fn needs_continuous_render(&self) -> bool {
633 self.custom_shader_renderer
634 .as_ref()
635 .is_some_and(|r| r.animation_enabled())
636 }
637
638 pub fn render(
641 &mut self,
642 egui_data: Option<(egui::FullOutput, &egui::Context)>,
643 force_egui_opaque: bool,
644 show_scrollbar: bool,
645 ) -> Result<bool> {
646 let force_render = self.needs_continuous_render();
648
649 if !self.dirty && egui_data.is_none() && !force_render {
650 return Ok(false);
652 }
653
654 let has_custom_shader = self.custom_shader_renderer.is_some();
656
657 let t1 = std::time::Instant::now();
659 let surface_texture = if has_custom_shader {
660 self.cell_renderer.render_to_texture(
662 self.custom_shader_renderer
663 .as_ref()
664 .unwrap()
665 .intermediate_texture_view(),
666 )?
667 } else {
668 self.cell_renderer.render(show_scrollbar)?
670 };
671 let cell_render_time = t1.elapsed();
672
673 let t_custom = std::time::Instant::now();
675 let custom_shader_time = if let Some(ref mut custom_shader) = self.custom_shader_renderer {
676 let surface_view = surface_texture
678 .texture
679 .create_view(&wgpu::TextureViewDescriptor::default());
680 custom_shader.render(
681 self.cell_renderer.device(),
682 self.cell_renderer.queue(),
683 &surface_view,
684 )?;
685
686 self.cell_renderer
688 .render_overlays(&surface_texture, show_scrollbar)?;
689 t_custom.elapsed()
690 } else {
691 std::time::Duration::ZERO
692 };
693
694 let t2 = std::time::Instant::now();
696 if !self.sixel_graphics.is_empty() {
697 self.render_sixel_graphics(&surface_texture)?;
698 }
699 let sixel_render_time = t2.elapsed();
700
701 let t3 = std::time::Instant::now();
703 if let Some((egui_output, egui_ctx)) = egui_data {
704 self.render_egui(&surface_texture, egui_output, egui_ctx, force_egui_opaque)?;
705 }
706 let egui_render_time = t3.elapsed();
707
708 let t4 = std::time::Instant::now();
710 surface_texture.present();
711 let present_time = t4.elapsed();
712
713 let total = cell_render_time
715 + custom_shader_time
716 + sixel_render_time
717 + egui_render_time
718 + present_time;
719 if present_time.as_millis() > 10 || total.as_millis() > 10 {
720 log::info!(
721 "RENDER_BREAKDOWN: CellRender={:.2}ms CustomShader={:.2}ms Sixel={:.2}ms Egui={:.2}ms PRESENT={:.2}ms Total={:.2}ms",
722 cell_render_time.as_secs_f64() * 1000.0,
723 custom_shader_time.as_secs_f64() * 1000.0,
724 sixel_render_time.as_secs_f64() * 1000.0,
725 egui_render_time.as_secs_f64() * 1000.0,
726 present_time.as_secs_f64() * 1000.0,
727 total.as_secs_f64() * 1000.0
728 );
729 }
730
731 self.dirty = false;
733
734 Ok(true)
735 }
736
737 fn render_sixel_graphics(&mut self, surface_texture: &wgpu::SurfaceTexture) -> Result<()> {
739 use wgpu::TextureViewDescriptor;
740
741 let view = surface_texture
743 .texture
744 .create_view(&TextureViewDescriptor::default());
745
746 let mut encoder =
748 self.cell_renderer
749 .device()
750 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
751 label: Some("sixel encoder"),
752 });
753
754 {
756 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
757 label: Some("sixel render pass"),
758 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
759 view: &view,
760 resolve_target: None,
761 ops: wgpu::Operations {
762 load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store,
764 },
765 depth_slice: None,
766 })],
767 depth_stencil_attachment: None,
768 timestamp_writes: None,
769 occlusion_query_set: None,
770 });
771
772 self.graphics_renderer.render(
774 self.cell_renderer.device(),
775 self.cell_renderer.queue(),
776 &mut render_pass,
777 &self.sixel_graphics,
778 self.size.width as f32,
779 self.size.height as f32,
780 )?;
781 } self.cell_renderer
785 .queue()
786 .submit(std::iter::once(encoder.finish()));
787
788 Ok(())
789 }
790
791 fn render_egui(
793 &mut self,
794 surface_texture: &wgpu::SurfaceTexture,
795 egui_output: egui::FullOutput,
796 egui_ctx: &egui::Context,
797 force_opaque: bool,
798 ) -> Result<()> {
799 use wgpu::TextureViewDescriptor;
800
801 let view = surface_texture
803 .texture
804 .create_view(&TextureViewDescriptor::default());
805
806 let mut encoder =
808 self.cell_renderer
809 .device()
810 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
811 label: Some("egui encoder"),
812 });
813
814 let screen_descriptor = egui_wgpu::ScreenDescriptor {
816 size_in_pixels: [self.size.width, self.size.height],
817 pixels_per_point: egui_output.pixels_per_point,
818 };
819
820 for (id, image_delta) in &egui_output.textures_delta.set {
822 self.egui_renderer.update_texture(
823 self.cell_renderer.device(),
824 self.cell_renderer.queue(),
825 *id,
826 image_delta,
827 );
828 }
829
830 let mut paint_jobs = egui_ctx.tessellate(egui_output.shapes, egui_output.pixels_per_point);
832
833 if force_opaque {
835 for job in paint_jobs.iter_mut() {
836 match &mut job.primitive {
837 egui::epaint::Primitive::Mesh(mesh) => {
838 for v in mesh.vertices.iter_mut() {
839 v.color[3] = 255;
840 }
841 }
842 egui::epaint::Primitive::Callback(_) => {}
843 }
844 }
845 }
846
847 self.egui_renderer.update_buffers(
849 self.cell_renderer.device(),
850 self.cell_renderer.queue(),
851 &mut encoder,
852 &paint_jobs,
853 &screen_descriptor,
854 );
855
856 {
858 let render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
859 label: Some("egui render pass"),
860 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
861 view: &view,
862 resolve_target: None,
863 ops: wgpu::Operations {
864 load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store,
866 },
867 depth_slice: None,
868 })],
869 depth_stencil_attachment: None,
870 timestamp_writes: None,
871 occlusion_query_set: None,
872 });
873
874 let mut render_pass = render_pass.forget_lifetime();
876
877 self.egui_renderer
878 .render(&mut render_pass, &paint_jobs, &screen_descriptor);
879 } self.cell_renderer
883 .queue()
884 .submit(std::iter::once(encoder.finish()));
885
886 for id in &egui_output.textures_delta.free {
888 self.egui_renderer.free_texture(id);
889 }
890
891 Ok(())
892 }
893
894 pub fn size(&self) -> PhysicalSize<u32> {
896 self.size
897 }
898
899 pub fn grid_size(&self) -> (usize, usize) {
901 self.cell_renderer.grid_size()
902 }
903
904 pub fn cell_width(&self) -> f32 {
906 self.cell_renderer.cell_width()
907 }
908
909 pub fn cell_height(&self) -> f32 {
911 self.cell_renderer.cell_height()
912 }
913
914 pub fn window_padding(&self) -> f32 {
916 self.cell_renderer.window_padding()
917 }
918
919 pub fn scrollbar_contains_point(&self, x: f32, y: f32) -> bool {
925 self.cell_renderer.scrollbar_contains_point(x, y)
926 }
927
928 pub fn scrollbar_thumb_bounds(&self) -> Option<(f32, f32)> {
930 self.cell_renderer.scrollbar_thumb_bounds()
931 }
932
933 pub fn scrollbar_track_contains_x(&self, x: f32) -> bool {
935 self.cell_renderer.scrollbar_track_contains_x(x)
936 }
937
938 pub fn scrollbar_mouse_y_to_scroll_offset(&self, mouse_y: f32) -> Option<usize> {
946 self.cell_renderer
947 .scrollbar_mouse_y_to_scroll_offset(mouse_y)
948 }
949
950 #[allow(dead_code)]
952 pub fn is_dirty(&self) -> bool {
953 self.dirty
954 }
955
956 #[allow(dead_code)]
958 pub fn mark_dirty(&mut self) {
959 self.dirty = true;
960 }
961
962 #[allow(dead_code)]
964 pub fn clear_sixel_cache(&mut self) {
965 self.graphics_renderer.clear_cache();
966 self.sixel_graphics.clear();
967 self.dirty = true;
968 }
969
970 #[allow(dead_code)]
972 pub fn sixel_cache_size(&self) -> usize {
973 self.graphics_renderer.cache_size()
974 }
975
976 #[allow(dead_code)]
978 pub fn remove_sixel_texture(&mut self, id: u64) {
979 self.graphics_renderer.remove_texture(id);
980 self.sixel_graphics
981 .retain(|(gid, _, _, _, _, _, _)| *gid != id);
982 self.dirty = true;
983 }
984
985 #[allow(dead_code)]
987 #[allow(dead_code)]
988 pub fn render_debug_overlay(&mut self, text: &str) {
989 self.debug_text = Some(text.to_string());
990 self.dirty = true; }
992
993 pub fn reconfigure_surface(&mut self) {
996 self.cell_renderer.reconfigure_surface();
997 self.dirty = true;
998 }
999
1000 pub fn clear_glyph_cache(&mut self) {
1003 self.cell_renderer.clear_glyph_cache();
1004 self.dirty = true;
1005 }
1006}