1use anyhow::{Context, Result};
11use std::collections::HashMap;
12use std::sync::Arc;
13use winit::window::Window;
14
15use crate::scrollbar::Scrollbar;
16use par_term_config::{SeparatorMark, color_u8_to_f32_a};
17use par_term_fonts::font_manager::FontManager;
18
19pub mod atlas;
20pub mod background;
21mod bg_instance_builder;
22pub mod block_chars;
23mod cursor;
24mod font;
25mod instance_buffers;
26mod layout;
27pub(crate) mod pane_render;
28pub mod pipeline;
29pub mod render;
30mod settings;
31mod surface;
32mod text_instance_builder;
33pub mod types;
34pub(crate) use pane_render::PaneRenderViewParams;
36pub use types::{Cell, PaneViewport};
37pub(crate) use types::{BackgroundInstance, GlyphInfo, RowCacheEntry, TextInstance};
39pub(crate) use instance_buffers::{CURSOR_OVERLAY_SLOTS, TEXT_INSTANCES_PER_CELL};
41pub(crate) use cursor::CursorState;
43pub(crate) use font::FontState;
44pub(crate) use layout::GridLayout;
45
46pub(crate) const MACOS_PLATFORM_DPI: f32 = 72.0;
48
49pub(crate) const DEFAULT_PLATFORM_DPI: f32 = 96.0;
51
52pub(crate) const FONT_REFERENCE_DPI: f32 = 72.0;
55
56const SOLID_PIXEL_SIZE: u32 = 2;
59
60pub(crate) const ATLAS_GLYPH_PADDING: u32 = 2;
62
63const SURFACE_FRAME_LATENCY: u32 = 2;
67
68const DEFAULT_GUIDE_OPACITY: f32 = 0.08;
71
72const DEFAULT_SHADOW_ALPHA: f32 = 0.5;
74
75const DEFAULT_SHADOW_OFFSET_PX: f32 = 2.0;
77
78const DEFAULT_SHADOW_BLUR_PX: f32 = 3.0;
80
81pub(crate) struct GpuPipelines {
83 pub(crate) bg_pipeline: wgpu::RenderPipeline,
84 pub(crate) text_pipeline: wgpu::RenderPipeline,
85 pub(crate) bg_image_pipeline: wgpu::RenderPipeline,
86 pub(crate) visual_bell_pipeline: wgpu::RenderPipeline,
88 pub(crate) text_bind_group: wgpu::BindGroup,
89 #[allow(dead_code)] pub(crate) text_bind_group_layout: wgpu::BindGroupLayout,
91 pub(crate) bg_image_bind_group: Option<wgpu::BindGroup>,
92 pub(crate) bg_image_bind_group_layout: wgpu::BindGroupLayout,
93 pub(crate) visual_bell_bind_group: wgpu::BindGroup,
95 pub(crate) opaque_alpha_pipeline: wgpu::RenderPipeline,
97}
98
99pub(crate) struct GpuBuffers {
101 pub(crate) vertex_buffer: wgpu::Buffer,
102 pub(crate) bg_instance_buffer: wgpu::Buffer,
103 pub(crate) text_instance_buffer: wgpu::Buffer,
104 pub(crate) bg_image_uniform_buffer: wgpu::Buffer,
105 pub(crate) visual_bell_uniform_buffer: wgpu::Buffer,
107 pub(crate) max_bg_instances: usize,
109 pub(crate) max_text_instances: usize,
111 pub(crate) actual_bg_instances: usize,
113 pub(crate) actual_text_instances: usize,
115}
116
117pub(crate) struct GlyphAtlas {
119 pub(crate) atlas_texture: wgpu::Texture,
120 #[allow(dead_code)] pub(crate) atlas_view: wgpu::TextureView,
122 pub(crate) glyph_cache: HashMap<u64, GlyphInfo>,
123 pub(crate) lru_head: Option<u64>,
124 pub(crate) lru_tail: Option<u64>,
125 pub(crate) atlas_next_x: u32,
126 pub(crate) atlas_next_y: u32,
127 pub(crate) atlas_row_height: u32,
128 pub(crate) atlas_size: u32,
130 pub(crate) solid_pixel_offset: (u32, u32),
132}
133
134pub(crate) struct BackgroundImageState {
136 pub(crate) bg_image_texture: Option<wgpu::Texture>,
137 pub(crate) bg_image_mode: par_term_config::BackgroundImageMode,
138 pub(crate) bg_image_opacity: f32,
139 pub(crate) bg_image_width: u32,
140 pub(crate) bg_image_height: u32,
141 pub(crate) bg_is_solid_color: bool,
145 pub(crate) solid_bg_color: [f32; 3],
148 pub(crate) pane_bg_cache: HashMap<String, background::PaneBackgroundEntry>,
150 pub(crate) pane_bg_uniform_cache: HashMap<String, background::PaneBgUniformEntry>,
153}
154
155pub(crate) struct SeparatorConfig {
157 pub(crate) enabled: bool,
159 pub(crate) thickness: f32,
161 pub(crate) opacity: f32,
163 pub(crate) exit_color: bool,
165 pub(crate) color: [f32; 3],
167 pub(crate) visible_marks: Vec<SeparatorMark>,
169}
170
171pub struct CellRenderer {
172 pub(crate) device: Arc<wgpu::Device>,
174 pub(crate) queue: Arc<wgpu::Queue>,
175 pub(crate) surface: wgpu::Surface<'static>,
176 pub(crate) config: wgpu::SurfaceConfiguration,
177 pub(crate) supported_present_modes: Vec<wgpu::PresentMode>,
179
180 pub(crate) pipelines: GpuPipelines,
182 pub(crate) buffers: GpuBuffers,
183 pub(crate) atlas: GlyphAtlas,
184 pub(crate) grid: GridLayout,
185 pub(crate) cursor: CursorState,
186 pub(crate) font: FontState,
187 pub(crate) bg_state: BackgroundImageState,
188 pub(crate) separator: SeparatorConfig,
189
190 pub(crate) scale_factor: f32,
192
193 pub(crate) font_manager: FontManager,
195 pub(crate) scrollbar: Scrollbar,
196
197 pub(crate) cells: Vec<Cell>,
199 pub(crate) dirty_rows: Vec<bool>,
200 pub(crate) row_cache: Vec<Option<RowCacheEntry>>,
201
202 pub(crate) visual_bell_intensity: f32,
204 pub(crate) visual_bell_color: [f32; 3],
205 pub(crate) window_opacity: f32,
206 pub(crate) background_color: [f32; 4],
207 pub(crate) is_focused: bool,
209
210 pub(crate) bg_instances: Vec<BackgroundInstance>,
212 pub(crate) text_instances: Vec<TextInstance>,
213
214 pub(crate) scratch_row_bg: Vec<BackgroundInstance>,
216 pub(crate) scratch_row_text: Vec<TextInstance>,
217 pub(crate) scratch_row_cells: Vec<Cell>,
220
221 pub(crate) scale_context: swash::scale::ScaleContext,
226
227 pub(crate) transparency_affects_only_default_background: bool,
231 pub(crate) keep_text_opaque: bool,
233 pub(crate) link_underline_style: par_term_config::LinkUnderlineStyle,
235
236 pub(crate) gutter_indicators: Vec<(usize, [f32; 4])>,
238}
239
240pub struct CellRendererConfig<'a> {
245 pub font_family: Option<&'a str>,
246 pub font_family_bold: Option<&'a str>,
247 pub font_family_italic: Option<&'a str>,
248 pub font_family_bold_italic: Option<&'a str>,
249 pub font_ranges: &'a [par_term_config::FontRange],
250 pub font_size: f32,
251 pub cols: usize,
252 pub rows: usize,
253 pub window_padding: f32,
254 pub line_spacing: f32,
255 pub char_spacing: f32,
256 pub scrollbar_position: &'a str,
257 pub scrollbar_width: f32,
258 pub scrollbar_thumb_color: [f32; 4],
259 pub scrollbar_track_color: [f32; 4],
260 pub enable_text_shaping: bool,
261 pub enable_ligatures: bool,
262 pub enable_kerning: bool,
263 pub font_antialias: bool,
264 pub font_hinting: bool,
265 pub font_thin_strokes: par_term_config::ThinStrokesMode,
266 pub minimum_contrast: f32,
267 pub vsync_mode: par_term_config::VsyncMode,
268 pub power_preference: par_term_config::PowerPreference,
269 pub window_opacity: f32,
270 pub background_color: [u8; 3],
271 pub background_image_path: Option<&'a str>,
272 pub background_image_mode: par_term_config::BackgroundImageMode,
273 pub background_image_opacity: f32,
274}
275
276impl CellRenderer {
277 pub async fn new(window: Arc<Window>, config: CellRendererConfig<'_>) -> Result<Self> {
278 let CellRendererConfig {
279 font_family,
280 font_family_bold,
281 font_family_italic,
282 font_family_bold_italic,
283 font_ranges,
284 font_size,
285 cols,
286 rows,
287 window_padding,
288 line_spacing,
289 char_spacing,
290 scrollbar_position,
291 scrollbar_width,
292 scrollbar_thumb_color,
293 scrollbar_track_color,
294 enable_text_shaping,
295 enable_ligatures,
296 enable_kerning,
297 font_antialias,
298 font_hinting,
299 font_thin_strokes,
300 minimum_contrast,
301 vsync_mode,
302 power_preference,
303 window_opacity,
304 background_color,
305 background_image_path,
306 background_image_mode,
307 background_image_opacity,
308 } = config;
309 #[cfg(target_os = "windows")]
318 let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
319 backends: wgpu::Backends::DX12,
320 ..Default::default()
321 });
322 #[cfg(target_os = "macos")]
323 let instance = wgpu::Instance::default();
324 #[cfg(target_os = "linux")]
325 let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
326 backends: wgpu::Backends::VULKAN | wgpu::Backends::GL,
327 ..Default::default()
328 });
329 let surface = instance.create_surface(window.clone())?;
330 let adapter = instance
331 .request_adapter(&wgpu::RequestAdapterOptions {
332 power_preference: power_preference.to_wgpu(),
333 compatible_surface: Some(&surface),
334 force_fallback_adapter: false,
335 })
336 .await
337 .context("Failed to find wgpu adapter")?;
338
339 let (device, queue) = adapter
340 .request_device(&wgpu::DeviceDescriptor {
341 label: Some("device"),
342 required_features: wgpu::Features::empty(),
343 required_limits: wgpu::Limits::default(),
344 memory_hints: wgpu::MemoryHints::default(),
345 ..Default::default()
346 })
347 .await?;
348
349 let device = Arc::new(device);
350 let queue = Arc::new(queue);
351
352 let size = window.inner_size();
353 let surface_caps = surface.get_capabilities(&adapter);
354 let surface_format = surface_caps
355 .formats
356 .iter()
357 .copied()
358 .find(|f| !f.is_srgb())
359 .unwrap_or(surface_caps.formats[0]);
360
361 let supported_present_modes = surface_caps.present_modes.clone();
363
364 let requested_mode = vsync_mode.to_present_mode();
366 let present_mode = if supported_present_modes.contains(&requested_mode) {
367 requested_mode
368 } else {
369 log::warn!(
371 "Requested present mode {:?} not supported (available: {:?}), falling back",
372 requested_mode,
373 supported_present_modes
374 );
375 if supported_present_modes.contains(&wgpu::PresentMode::Fifo) {
376 wgpu::PresentMode::Fifo
377 } else {
378 supported_present_modes[0]
379 }
380 };
381
382 let alpha_mode = if surface_caps
385 .alpha_modes
386 .contains(&wgpu::CompositeAlphaMode::PreMultiplied)
387 {
388 wgpu::CompositeAlphaMode::PreMultiplied
389 } else if surface_caps
390 .alpha_modes
391 .contains(&wgpu::CompositeAlphaMode::PostMultiplied)
392 {
393 wgpu::CompositeAlphaMode::PostMultiplied
394 } else if surface_caps
395 .alpha_modes
396 .contains(&wgpu::CompositeAlphaMode::Auto)
397 {
398 wgpu::CompositeAlphaMode::Auto
399 } else {
400 surface_caps.alpha_modes[0]
401 };
402 log::info!(
403 "Selected alpha mode: {:?} (available: {:?})",
404 alpha_mode,
405 surface_caps.alpha_modes
406 );
407
408 let config = wgpu::SurfaceConfiguration {
409 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
410 format: surface_format,
411 width: size.width.max(1),
412 height: size.height.max(1),
413 present_mode,
414 alpha_mode,
415 view_formats: vec![],
416 desired_maximum_frame_latency: SURFACE_FRAME_LATENCY,
417 };
418 surface.configure(&device, &config);
419
420 let scale_factor = window.scale_factor() as f32;
421
422 let platform_dpi = if cfg!(target_os = "macos") {
423 MACOS_PLATFORM_DPI
424 } else {
425 DEFAULT_PLATFORM_DPI
426 };
427
428 let base_font_pixels = font_size * platform_dpi / FONT_REFERENCE_DPI;
429 let font_size_pixels = (base_font_pixels * scale_factor).max(1.0);
430
431 let font_manager = FontManager::new(
432 font_family,
433 font_family_bold,
434 font_family_italic,
435 font_family_bold_italic,
436 font_ranges,
437 )?;
438
439 let (font_ascent, font_descent, font_leading, char_advance) = {
441 let primary_font = font_manager
442 .get_font(0)
443 .expect("Primary font at index 0 must exist after FontManager initialization");
444 let metrics = primary_font.metrics(&[]);
445 let scale = font_size_pixels / metrics.units_per_em as f32;
446 let glyph_id = primary_font.charmap().map('m');
447 let advance = primary_font.glyph_metrics(&[]).advance_width(glyph_id) * scale;
448 (
449 metrics.ascent * scale,
450 metrics.descent * scale,
451 metrics.leading * scale,
452 advance,
453 )
454 };
455
456 let natural_line_height = font_ascent + font_descent + font_leading;
457 let cell_height = (natural_line_height * line_spacing).max(1.0).round();
462 let cell_width = (char_advance * char_spacing).max(1.0).round();
463
464 let scrollbar = Scrollbar::new(
465 Arc::clone(&device),
466 surface_format,
467 scrollbar_width,
468 scrollbar_position,
469 scrollbar_thumb_color,
470 scrollbar_track_color,
471 );
472
473 let bg_pipeline = pipeline::create_bg_pipeline(&device, surface_format);
475
476 let (atlas_texture, atlas_view, atlas_sampler, atlas_size) =
477 pipeline::create_atlas(&device);
478 let text_bind_group_layout = pipeline::create_text_bind_group_layout(&device);
479 let text_bind_group = pipeline::create_text_bind_group(
480 &device,
481 &text_bind_group_layout,
482 &atlas_view,
483 &atlas_sampler,
484 );
485 let text_pipeline =
486 pipeline::create_text_pipeline(&device, surface_format, &text_bind_group_layout);
487
488 let bg_image_bind_group_layout = pipeline::create_bg_image_bind_group_layout(&device);
489 let bg_image_pipeline = pipeline::create_bg_image_pipeline(
490 &device,
491 surface_format,
492 &bg_image_bind_group_layout,
493 );
494 let bg_image_uniform_buffer = pipeline::create_bg_image_uniform_buffer(&device);
495
496 let (visual_bell_pipeline, visual_bell_bind_group, _, visual_bell_uniform_buffer) =
497 pipeline::create_visual_bell_pipeline(&device, surface_format);
498
499 let opaque_alpha_pipeline = pipeline::create_opaque_alpha_pipeline(&device, surface_format);
500
501 let vertex_buffer = pipeline::create_vertex_buffer(&device);
502
503 let max_bg_instances = cols * rows + CURSOR_OVERLAY_SLOTS + rows + rows;
506 let max_text_instances = cols * rows * TEXT_INSTANCES_PER_CELL;
507 let (bg_instance_buffer, text_instance_buffer) =
508 pipeline::create_instance_buffers(&device, max_bg_instances, max_text_instances);
509
510 let mut renderer = Self {
511 device,
512 queue,
513 surface,
514 config,
515 supported_present_modes,
516 pipelines: GpuPipelines {
517 bg_pipeline,
518 text_pipeline,
519 bg_image_pipeline,
520 visual_bell_pipeline,
521 text_bind_group,
522 text_bind_group_layout,
523 bg_image_bind_group: None,
524 bg_image_bind_group_layout,
525 visual_bell_bind_group,
526 opaque_alpha_pipeline,
527 },
528 buffers: GpuBuffers {
529 vertex_buffer,
530 bg_instance_buffer,
531 text_instance_buffer,
532 bg_image_uniform_buffer,
533 visual_bell_uniform_buffer,
534 max_bg_instances,
535 max_text_instances,
536 actual_bg_instances: 0,
537 actual_text_instances: 0,
538 },
539 atlas: GlyphAtlas {
540 atlas_texture,
541 atlas_view,
542 glyph_cache: HashMap::new(),
543 lru_head: None,
544 lru_tail: None,
545 atlas_next_x: 0,
546 atlas_next_y: 0,
547 atlas_row_height: 0,
548 atlas_size,
549 solid_pixel_offset: (0, 0),
550 },
551 grid: GridLayout {
552 cols,
553 rows,
554 cell_width,
555 cell_height,
556 window_padding,
557 content_offset_y: 0.0,
558 content_offset_x: 0.0,
559 content_inset_bottom: 0.0,
560 content_inset_right: 0.0,
561 egui_bottom_inset: 0.0,
562 egui_right_inset: 0.0,
563 },
564 cursor: CursorState {
565 pos: (0, 0),
566 opacity: 0.0,
567 style: par_term_emu_core_rust::cursor::CursorStyle::SteadyBlock,
568 color: [1.0, 1.0, 1.0],
569 text_color: None,
570 hidden_for_shader: false,
571 guide_enabled: false,
572 guide_color: [1.0, 1.0, 1.0, DEFAULT_GUIDE_OPACITY],
573 shadow_enabled: false,
574 shadow_color: [0.0, 0.0, 0.0, DEFAULT_SHADOW_ALPHA],
575 shadow_offset: [DEFAULT_SHADOW_OFFSET_PX, DEFAULT_SHADOW_OFFSET_PX],
576 shadow_blur: DEFAULT_SHADOW_BLUR_PX,
577 boost: 0.0,
578 boost_color: [1.0, 1.0, 1.0],
579 unfocused_style: par_term_config::UnfocusedCursorStyle::default(),
580 },
581 font: FontState {
582 base_font_size: font_size,
583 line_spacing,
584 char_spacing,
585 font_ascent,
586 font_descent,
587 font_leading,
588 font_size_pixels,
589 char_advance,
590 enable_text_shaping,
591 enable_ligatures,
592 enable_kerning,
593 font_antialias,
594 font_hinting,
595 font_thin_strokes,
596 minimum_contrast: minimum_contrast.clamp(0.0, 1.0),
597 },
598 bg_state: BackgroundImageState {
599 bg_image_texture: None,
600 bg_image_mode: background_image_mode,
601 bg_image_opacity: background_image_opacity,
602 bg_image_width: 0,
603 bg_image_height: 0,
604 bg_is_solid_color: false,
605 solid_bg_color: [0.0, 0.0, 0.0],
606 pane_bg_cache: HashMap::new(),
607 pane_bg_uniform_cache: HashMap::new(),
608 },
609 separator: SeparatorConfig {
610 enabled: false,
611 thickness: 1.0,
612 opacity: 0.4,
613 exit_color: true,
614 color: [0.5, 0.5, 0.5],
615 visible_marks: Vec::new(),
616 },
617 scale_factor,
618 font_manager,
619 scrollbar,
620 cells: vec![Cell::default(); cols * rows],
621 dirty_rows: vec![true; rows],
622 row_cache: (0..rows).map(|_| None).collect(),
623 is_focused: true,
624 visual_bell_intensity: 0.0,
625 visual_bell_color: [1.0, 1.0, 1.0], window_opacity,
627 background_color: color_u8_to_f32_a(background_color, 1.0),
628 bg_instances: vec![
629 BackgroundInstance {
630 position: [0.0, 0.0],
631 size: [0.0, 0.0],
632 color: [0.0, 0.0, 0.0, 0.0],
633 };
634 max_bg_instances
635 ],
636 text_instances: vec![
637 TextInstance {
638 position: [0.0, 0.0],
639 size: [0.0, 0.0],
640 tex_offset: [0.0, 0.0],
641 tex_size: [0.0, 0.0],
642 color: [0.0, 0.0, 0.0, 0.0],
643 is_colored: 0,
644 };
645 max_text_instances
646 ],
647 transparency_affects_only_default_background: false,
648 keep_text_opaque: true,
649 link_underline_style: par_term_config::LinkUnderlineStyle::default(),
650 gutter_indicators: Vec::new(),
651 scratch_row_bg: Vec::with_capacity(cols),
652 scratch_row_text: Vec::with_capacity(cols * 2),
653 scratch_row_cells: Vec::with_capacity(cols),
654 scale_context: swash::scale::ScaleContext::new(),
655 };
656
657 renderer.upload_solid_pixel();
659
660 log::info!(
661 "CellRenderer::new: background_image_path={:?}",
662 background_image_path
663 );
664 if let Some(path) = background_image_path {
665 if let Err(e) = renderer.load_background_image(path) {
667 log::warn!(
668 "Could not load background image '{}': {} - continuing without background image",
669 path,
670 e
671 );
672 }
673 }
674
675 Ok(renderer)
676 }
677
678 pub(crate) fn upload_solid_pixel(&mut self) {
680 let size = SOLID_PIXEL_SIZE;
681 let white_pixels: Vec<u8> = vec![255; (size * size * 4) as usize];
682
683 self.queue.write_texture(
684 wgpu::TexelCopyTextureInfo {
685 texture: &self.atlas.atlas_texture,
686 mip_level: 0,
687 origin: wgpu::Origin3d {
688 x: self.atlas.atlas_next_x,
689 y: self.atlas.atlas_next_y,
690 z: 0,
691 },
692 aspect: wgpu::TextureAspect::All,
693 },
694 &white_pixels,
695 wgpu::TexelCopyBufferLayout {
696 offset: 0,
697 bytes_per_row: Some(4 * size),
698 rows_per_image: Some(size),
699 },
700 wgpu::Extent3d {
701 width: size,
702 height: size,
703 depth_or_array_layers: 1,
704 },
705 );
706
707 self.atlas.solid_pixel_offset = (self.atlas.atlas_next_x, self.atlas.atlas_next_y);
708 self.atlas.atlas_next_x += size + ATLAS_GLYPH_PADDING;
709 self.atlas.atlas_row_height = self.atlas.atlas_row_height.max(size);
710 }
711
712 pub fn device(&self) -> &wgpu::Device {
713 &self.device
714 }
715 pub fn queue(&self) -> &wgpu::Queue {
716 &self.queue
717 }
718 pub fn surface_format(&self) -> wgpu::TextureFormat {
719 self.config.format
720 }
721 pub fn keep_text_opaque(&self) -> bool {
722 self.keep_text_opaque
723 }
724
725 pub fn update_cells(&mut self, new_cells: &[Cell]) -> bool {
727 let mut changed = false;
728 for row in 0..self.grid.rows {
729 let start = row * self.grid.cols;
730 let end = (row + 1) * self.grid.cols;
731 if start < new_cells.len() && end <= new_cells.len() {
732 let row_slice = &new_cells[start..end];
733 if row_slice != &self.cells[start..end] {
734 self.cells[start..end].clone_from_slice(row_slice);
735 self.dirty_rows[row] = true;
736 changed = true;
737 }
738 }
739 }
740 changed
741 }
742
743 pub fn clear_all_cells(&mut self) {
745 for cell in &mut self.cells {
746 *cell = Cell::default();
747 }
748 for dirty in &mut self.dirty_rows {
749 *dirty = true;
750 }
751 }
752
753 pub fn update_graphics(
754 &mut self,
755 _graphics: &[par_term_emu_core_rust::graphics::TerminalGraphic],
756 _scroll_offset: usize,
757 _scrollback_len: usize,
758 _visible_lines: usize,
759 ) -> Result<()> {
760 Ok(())
761 }
762}