1use anyhow::{Context, Result};
2use std::collections::HashMap;
3use std::sync::Arc;
4use wgpu::util::DeviceExt;
5use winit::window::Window;
6
7use crate::font_manager::FontManager;
8use crate::scrollbar::Scrollbar;
9use crate::text_shaper::ShapingOptions;
10
11#[repr(C)]
13#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
14struct Vertex {
15 position: [f32; 2],
16 tex_coords: [f32; 2],
17}
18
19#[repr(C)]
21#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
22struct BackgroundInstance {
23 position: [f32; 2],
24 size: [f32; 2],
25 color: [f32; 4],
26}
27
28#[repr(C)]
30#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
31struct TextInstance {
32 position: [f32; 2],
33 size: [f32; 2],
34 tex_offset: [f32; 2],
35 tex_size: [f32; 2],
36 color: [f32; 4],
37 is_colored: u32, }
39
40#[derive(Clone, Debug, PartialEq)]
42pub struct Cell {
43 pub grapheme: String,
44 pub fg_color: [u8; 4],
45 pub bg_color: [u8; 4],
46 pub bold: bool,
47 pub italic: bool,
48 pub underline: bool,
49 pub strikethrough: bool,
50 pub hyperlink_id: Option<u32>,
51 pub wide_char: bool,
52 pub wide_char_spacer: bool,
53}
54
55impl Default for Cell {
56 fn default() -> Self {
57 Self {
58 grapheme: " ".to_string(),
59 fg_color: [255, 255, 255, 255],
60 bg_color: [0, 0, 0, 0],
61 bold: false,
62 italic: false,
63 underline: false,
64 strikethrough: false,
65 hyperlink_id: None,
66 wide_char: false,
67 wide_char_spacer: false,
68 }
69 }
70}
71
72#[derive(Clone, Debug)]
74struct GlyphInfo {
75 #[allow(dead_code)]
76 key: u64,
77 x: u32,
78 y: u32,
79 width: u32,
80 height: u32,
81 #[allow(dead_code)]
82 bearing_x: f32,
83 #[allow(dead_code)]
84 bearing_y: f32,
85 is_colored: bool,
86 prev: Option<u64>,
87 next: Option<u64>,
88}
89
90struct RowCacheEntry {}
92
93pub struct CellRenderer {
94 device: Arc<wgpu::Device>,
95 queue: Arc<wgpu::Queue>,
96 surface: wgpu::Surface<'static>,
97 config: wgpu::SurfaceConfiguration,
98
99 bg_pipeline: wgpu::RenderPipeline,
101 text_pipeline: wgpu::RenderPipeline,
102 bg_image_pipeline: wgpu::RenderPipeline,
103 #[allow(dead_code)]
104 visual_bell_pipeline: wgpu::RenderPipeline,
105
106 vertex_buffer: wgpu::Buffer,
108 bg_instance_buffer: wgpu::Buffer,
109 text_instance_buffer: wgpu::Buffer,
110 bg_image_uniform_buffer: wgpu::Buffer,
111 #[allow(dead_code)]
112 visual_bell_uniform_buffer: wgpu::Buffer,
113
114 text_bind_group: wgpu::BindGroup,
116 #[allow(dead_code)]
117 text_bind_group_layout: wgpu::BindGroupLayout,
118 bg_image_bind_group: Option<wgpu::BindGroup>,
119 bg_image_bind_group_layout: wgpu::BindGroupLayout,
120 #[allow(dead_code)]
121 visual_bell_bind_group: wgpu::BindGroup,
122
123 atlas_texture: wgpu::Texture,
125 #[allow(dead_code)]
126 atlas_view: wgpu::TextureView,
127 glyph_cache: HashMap<u64, GlyphInfo>,
128 lru_head: Option<u64>,
129 lru_tail: Option<u64>,
130 atlas_next_x: u32,
131 atlas_next_y: u32,
132 atlas_row_height: u32,
133
134 cols: usize,
136 rows: usize,
137 cell_width: f32,
138 cell_height: f32,
139 window_padding: f32,
140 #[allow(dead_code)]
141 scale_factor: f32,
142
143 font_manager: FontManager,
145 scrollbar: Scrollbar,
146
147 cells: Vec<Cell>,
149 dirty_rows: Vec<bool>,
150 row_cache: Vec<Option<RowCacheEntry>>,
151 cursor_pos: (usize, usize),
152 cursor_opacity: f32,
153 cursor_style: par_term_emu_core_rust::cursor::CursorStyle,
154 cursor_overlay: Option<BackgroundInstance>,
156 cursor_color: [f32; 3],
158 visual_bell_intensity: f32,
159 window_opacity: f32,
160 background_color: [f32; 4],
161
162 font_ascent: f32,
164 font_descent: f32,
165 font_leading: f32,
166 font_size_pixels: f32,
167
168 bg_image_texture: Option<wgpu::Texture>,
170 bg_image_mode: crate::config::BackgroundImageMode,
171 bg_image_opacity: f32,
172
173 max_bg_instances: usize,
175 max_text_instances: usize,
176
177 bg_instances: Vec<BackgroundInstance>,
179 text_instances: Vec<TextInstance>,
180
181 #[allow(dead_code)]
183 enable_text_shaping: bool,
184 enable_ligatures: bool,
185 enable_kerning: bool,
186}
187
188impl CellRenderer {
189 #[allow(clippy::too_many_arguments)]
190 pub async fn new(
191 window: Arc<Window>,
192 font_family: Option<&str>,
193 font_family_bold: Option<&str>,
194 font_family_italic: Option<&str>,
195 font_family_bold_italic: Option<&str>,
196 font_ranges: &[crate::config::FontRange],
197 font_size: f32,
198 cols: usize,
199 rows: usize,
200 window_padding: f32,
201 line_spacing: f32,
202 char_spacing: f32,
203 scrollbar_position: &str,
204 scrollbar_width: f32,
205 scrollbar_thumb_color: [f32; 4],
206 scrollbar_track_color: [f32; 4],
207 enable_text_shaping: bool,
208 enable_ligatures: bool,
209 enable_kerning: bool,
210 vsync_mode: crate::config::VsyncMode,
211 window_opacity: f32,
212 background_color: [u8; 3],
213 background_image_path: Option<&str>,
214 background_image_mode: crate::config::BackgroundImageMode,
215 background_image_opacity: f32,
216 ) -> Result<Self> {
217 let instance = wgpu::Instance::default();
218 let surface = instance.create_surface(window.clone())?;
219 let adapter = instance
220 .request_adapter(&wgpu::RequestAdapterOptions {
221 power_preference: wgpu::PowerPreference::HighPerformance,
222 compatible_surface: Some(&surface),
223 force_fallback_adapter: false,
224 })
225 .await
226 .context("Failed to find wgpu adapter")?;
227
228 let (device, queue) = adapter
229 .request_device(&wgpu::DeviceDescriptor {
230 label: Some("device"),
231 required_features: wgpu::Features::empty(),
232 required_limits: wgpu::Limits::default(),
233 memory_hints: wgpu::MemoryHints::default(),
234 ..Default::default()
235 })
236 .await?;
237
238 let device = Arc::new(device);
239 let queue = Arc::new(queue);
240
241 let size = window.inner_size();
242 let surface_caps = surface.get_capabilities(&adapter);
243 let surface_format = surface_caps
244 .formats
245 .iter()
246 .copied()
247 .find(|f| !f.is_srgb())
248 .unwrap_or(surface_caps.formats[0]);
249
250 let config = wgpu::SurfaceConfiguration {
251 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
252 format: surface_format,
253 width: size.width.max(1),
254 height: size.height.max(1),
255 present_mode: vsync_mode.to_present_mode(),
256 alpha_mode: surface_caps.alpha_modes[0],
257 view_formats: vec![],
258 desired_maximum_frame_latency: 2,
259 };
260 surface.configure(&device, &config);
261
262 let scale_factor = window.scale_factor() as f32;
263
264 let platform_dpi = if cfg!(target_os = "macos") {
267 72.0
268 } else {
269 96.0
270 };
271
272 let base_font_pixels = font_size * platform_dpi / 72.0;
273 let font_size_pixels = (base_font_pixels * scale_factor).max(1.0);
274
275 let font_manager = FontManager::new(
276 font_family,
277 font_family_bold,
278 font_family_italic,
279 font_family_bold_italic,
280 font_ranges,
281 )?;
282
283 let (font_ascent, font_descent, font_leading, char_advance) = {
285 let primary_font = font_manager.get_font(0).unwrap();
286 let metrics = primary_font.metrics(&[]);
287 let scale = font_size_pixels / metrics.units_per_em as f32;
288
289 let glyph_id = primary_font.charmap().map('m');
291 let advance = primary_font.glyph_metrics(&[]).advance_width(glyph_id) * scale;
292
293 (
294 metrics.ascent * scale,
295 metrics.descent * scale,
296 metrics.leading * scale,
297 advance,
298 )
299 };
300
301 let natural_line_height = font_ascent + font_descent + font_leading;
304 let cell_height = (natural_line_height * line_spacing).max(1.0);
305 let cell_width = (char_advance * char_spacing).max(1.0);
306
307 let scrollbar = Scrollbar::new(
308 &device,
309 surface_format,
310 scrollbar_width,
311 scrollbar_position,
312 scrollbar_thumb_color,
313 scrollbar_track_color,
314 );
315
316 let bg_shader = device.create_shader_module(wgpu::include_wgsl!("shaders/cell_bg.wgsl"));
318 let text_shader =
319 device.create_shader_module(wgpu::include_wgsl!("shaders/cell_text.wgsl"));
320 let bg_image_shader =
321 device.create_shader_module(wgpu::include_wgsl!("shaders/background_image.wgsl"));
322
323 let vertices = [
325 Vertex {
326 position: [0.0, 0.0],
327 tex_coords: [0.0, 0.0],
328 },
329 Vertex {
330 position: [1.0, 0.0],
331 tex_coords: [1.0, 0.0],
332 },
333 Vertex {
334 position: [0.0, 1.0],
335 tex_coords: [0.0, 1.0],
336 },
337 Vertex {
338 position: [1.0, 1.0],
339 tex_coords: [1.0, 1.0],
340 },
341 ];
342 let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
343 label: Some("vertex buffer"),
344 contents: bytemuck::cast_slice(&vertices),
345 usage: wgpu::BufferUsages::VERTEX,
346 });
347
348 let bg_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
350 label: Some("bg pipeline layout"),
351 bind_group_layouts: &[],
352 push_constant_ranges: &[],
353 });
354
355 let bg_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
356 label: Some("bg pipeline"),
357 layout: Some(&bg_pipeline_layout),
358 vertex: wgpu::VertexState {
359 module: &bg_shader,
360 entry_point: Some("vs_main"),
361 compilation_options: Default::default(),
362 buffers: &[
363 wgpu::VertexBufferLayout {
364 array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
365 step_mode: wgpu::VertexStepMode::Vertex,
366 attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2],
367 },
368 wgpu::VertexBufferLayout {
369 array_stride: std::mem::size_of::<BackgroundInstance>() as wgpu::BufferAddress,
370 step_mode: wgpu::VertexStepMode::Instance,
371 attributes: &wgpu::vertex_attr_array![2 => Float32x2, 3 => Float32x2, 4 => Float32x4],
372 },
373 ],
374 },
375 fragment: Some(wgpu::FragmentState {
376 module: &bg_shader,
377 entry_point: Some("fs_main"),
378 compilation_options: Default::default(),
379 targets: &[Some(wgpu::ColorTargetState {
380 format: surface_format,
381 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
382 write_mask: wgpu::ColorWrites::ALL,
383 })],
384 }),
385 primitive: wgpu::PrimitiveState {
386 topology: wgpu::PrimitiveTopology::TriangleStrip,
387 ..Default::default()
388 },
389 depth_stencil: None,
390 multisample: wgpu::MultisampleState::default(),
391 multiview: None,
392 cache: None,
393 });
394
395 let atlas_size = 2048;
397 let atlas_texture = device.create_texture(&wgpu::TextureDescriptor {
398 label: Some("atlas texture"),
399 size: wgpu::Extent3d {
400 width: atlas_size,
401 height: atlas_size,
402 depth_or_array_layers: 1,
403 },
404 mip_level_count: 1,
405 sample_count: 1,
406 dimension: wgpu::TextureDimension::D2,
407 format: wgpu::TextureFormat::Rgba8Unorm,
408 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
409 view_formats: &[],
410 });
411 let atlas_view = atlas_texture.create_view(&wgpu::TextureViewDescriptor::default());
412 let atlas_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
413 address_mode_u: wgpu::AddressMode::ClampToEdge,
414 address_mode_v: wgpu::AddressMode::ClampToEdge,
415 mag_filter: wgpu::FilterMode::Linear,
416 min_filter: wgpu::FilterMode::Linear,
417 ..Default::default()
418 });
419
420 let text_bind_group_layout =
421 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
422 label: Some("text bind group layout"),
423 entries: &[
424 wgpu::BindGroupLayoutEntry {
425 binding: 0,
426 visibility: wgpu::ShaderStages::FRAGMENT,
427 ty: wgpu::BindingType::Texture {
428 sample_type: wgpu::TextureSampleType::Float { filterable: true },
429 view_dimension: wgpu::TextureViewDimension::D2,
430 multisampled: false,
431 },
432 count: None,
433 },
434 wgpu::BindGroupLayoutEntry {
435 binding: 1,
436 visibility: wgpu::ShaderStages::FRAGMENT,
437 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
438 count: None,
439 },
440 ],
441 });
442
443 let text_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
444 label: Some("text bind group"),
445 layout: &text_bind_group_layout,
446 entries: &[
447 wgpu::BindGroupEntry {
448 binding: 0,
449 resource: wgpu::BindingResource::TextureView(&atlas_view),
450 },
451 wgpu::BindGroupEntry {
452 binding: 1,
453 resource: wgpu::BindingResource::Sampler(&atlas_sampler),
454 },
455 ],
456 });
457
458 let text_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
459 label: Some("text pipeline layout"),
460 bind_group_layouts: &[&text_bind_group_layout],
461 push_constant_ranges: &[],
462 });
463
464 let text_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
465 label: Some("text pipeline"),
466 layout: Some(&text_pipeline_layout),
467 vertex: wgpu::VertexState {
468 module: &text_shader,
469 entry_point: Some("vs_main"),
470 compilation_options: Default::default(),
471 buffers: &[
472 wgpu::VertexBufferLayout {
473 array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
474 step_mode: wgpu::VertexStepMode::Vertex,
475 attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2],
476 },
477 wgpu::VertexBufferLayout {
478 array_stride: std::mem::size_of::<TextInstance>() as wgpu::BufferAddress,
479 step_mode: wgpu::VertexStepMode::Instance,
480 attributes: &wgpu::vertex_attr_array![
481 2 => Float32x2,
482 3 => Float32x2,
483 4 => Float32x2,
484 5 => Float32x2,
485 6 => Float32x4,
486 7 => Uint32
487 ],
488 },
489 ],
490 },
491 fragment: Some(wgpu::FragmentState {
492 module: &text_shader,
493 entry_point: Some("fs_main"),
494 compilation_options: Default::default(),
495 targets: &[Some(wgpu::ColorTargetState {
496 format: surface_format,
497 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
498 write_mask: wgpu::ColorWrites::ALL,
499 })],
500 }),
501 primitive: wgpu::PrimitiveState {
502 topology: wgpu::PrimitiveTopology::TriangleStrip,
503 ..Default::default()
504 },
505 depth_stencil: None,
506 multisample: wgpu::MultisampleState::default(),
507 multiview: None,
508 cache: None,
509 });
510
511 let bg_image_bind_group_layout =
513 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
514 label: Some("bg image bind group layout"),
515 entries: &[
516 wgpu::BindGroupLayoutEntry {
517 binding: 0,
518 visibility: wgpu::ShaderStages::FRAGMENT,
519 ty: wgpu::BindingType::Texture {
520 sample_type: wgpu::TextureSampleType::Float { filterable: true },
521 view_dimension: wgpu::TextureViewDimension::D2,
522 multisampled: false,
523 },
524 count: None,
525 },
526 wgpu::BindGroupLayoutEntry {
527 binding: 1,
528 visibility: wgpu::ShaderStages::FRAGMENT,
529 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
530 count: None,
531 },
532 wgpu::BindGroupLayoutEntry {
533 binding: 2,
534 visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
535 ty: wgpu::BindingType::Buffer {
536 ty: wgpu::BufferBindingType::Uniform,
537 has_dynamic_offset: false,
538 min_binding_size: None,
539 },
540 count: None,
541 },
542 ],
543 });
544
545 let bg_image_pipeline_layout =
546 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
547 label: Some("bg image pipeline layout"),
548 bind_group_layouts: &[&bg_image_bind_group_layout],
549 push_constant_ranges: &[],
550 });
551
552 let bg_image_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
553 label: Some("bg image pipeline"),
554 layout: Some(&bg_image_pipeline_layout),
555 vertex: wgpu::VertexState {
556 module: &bg_image_shader,
557 entry_point: Some("vs_main"),
558 compilation_options: Default::default(),
559 buffers: &[wgpu::VertexBufferLayout {
560 array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
561 step_mode: wgpu::VertexStepMode::Vertex,
562 attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2],
563 }],
564 },
565 fragment: Some(wgpu::FragmentState {
566 module: &bg_image_shader,
567 entry_point: Some("fs_main"),
568 compilation_options: Default::default(),
569 targets: &[Some(wgpu::ColorTargetState {
570 format: surface_format,
571 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
572 write_mask: wgpu::ColorWrites::ALL,
573 })],
574 }),
575 primitive: wgpu::PrimitiveState {
576 topology: wgpu::PrimitiveTopology::TriangleStrip,
577 ..Default::default()
578 },
579 depth_stencil: None,
580 multisample: wgpu::MultisampleState::default(),
581 multiview: None,
582 cache: None,
583 });
584
585 let bg_image_uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
586 label: Some("bg image uniform buffer"),
587 size: 64, usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
589 mapped_at_creation: false,
590 });
591
592 let visual_bell_shader =
594 device.create_shader_module(wgpu::include_wgsl!("shaders/cell_bg.wgsl"));
595 let visual_bell_bind_group_layout =
596 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
597 label: Some("visual bell bind group layout"),
598 entries: &[wgpu::BindGroupLayoutEntry {
599 binding: 0,
600 visibility: wgpu::ShaderStages::FRAGMENT,
601 ty: wgpu::BindingType::Buffer {
602 ty: wgpu::BufferBindingType::Uniform,
603 has_dynamic_offset: false,
604 min_binding_size: None,
605 },
606 count: None,
607 }],
608 });
609
610 let visual_bell_pipeline_layout =
611 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
612 label: Some("visual bell pipeline layout"),
613 bind_group_layouts: &[&visual_bell_bind_group_layout],
614 push_constant_ranges: &[],
615 });
616
617 let visual_bell_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
618 label: Some("visual bell pipeline"),
619 layout: Some(&visual_bell_pipeline_layout),
620 vertex: wgpu::VertexState {
621 module: &visual_bell_shader,
622 entry_point: Some("vs_main"),
623 compilation_options: Default::default(),
624 buffers: &[
625 wgpu::VertexBufferLayout {
626 array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
627 step_mode: wgpu::VertexStepMode::Vertex,
628 attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2],
629 },
630 wgpu::VertexBufferLayout {
631 array_stride: std::mem::size_of::<BackgroundInstance>() as wgpu::BufferAddress,
632 step_mode: wgpu::VertexStepMode::Instance,
633 attributes: &wgpu::vertex_attr_array![2 => Float32x2, 3 => Float32x2, 4 => Float32x4],
634 },
635 ],
636 },
637 fragment: Some(wgpu::FragmentState {
638 module: &visual_bell_shader,
639 entry_point: Some("fs_main"),
640 compilation_options: Default::default(),
641 targets: &[Some(wgpu::ColorTargetState {
642 format: surface_format,
643 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
644 write_mask: wgpu::ColorWrites::ALL,
645 })],
646 }),
647 primitive: wgpu::PrimitiveState {
648 topology: wgpu::PrimitiveTopology::TriangleStrip,
649 ..Default::default()
650 },
651 depth_stencil: None,
652 multisample: wgpu::MultisampleState::default(),
653 multiview: None,
654 cache: None,
655 });
656
657 let visual_bell_uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
658 label: Some("visual bell uniform buffer"),
659 size: 64,
660 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
661 mapped_at_creation: false,
662 });
663
664 let visual_bell_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
665 label: Some("visual bell bind group"),
666 layout: &visual_bell_bind_group_layout,
667 entries: &[wgpu::BindGroupEntry {
668 binding: 0,
669 resource: visual_bell_uniform_buffer.as_entire_binding(),
670 }],
671 });
672
673 let max_bg_instances = cols * rows + 1;
675 let max_text_instances = cols * rows * 2; let bg_instance_buffer = device.create_buffer(&wgpu::BufferDescriptor {
677 label: Some("bg instance buffer"),
678 size: (max_bg_instances * std::mem::size_of::<BackgroundInstance>()) as u64,
679 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
680 mapped_at_creation: false,
681 });
682 let text_instance_buffer = device.create_buffer(&wgpu::BufferDescriptor {
683 label: Some("text instance buffer"),
684 size: (max_text_instances * std::mem::size_of::<TextInstance>()) as u64,
685 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
686 mapped_at_creation: false,
687 });
688
689 let mut renderer = Self {
690 device,
691 queue,
692 surface,
693 config,
694 bg_pipeline,
695 text_pipeline,
696 bg_image_pipeline,
697 visual_bell_pipeline,
698 vertex_buffer,
699 bg_instance_buffer,
700 text_instance_buffer,
701 bg_image_uniform_buffer,
702 visual_bell_uniform_buffer,
703 text_bind_group,
704 text_bind_group_layout,
705 bg_image_bind_group: None,
706 bg_image_bind_group_layout,
707 visual_bell_bind_group,
708 atlas_texture,
709 atlas_view,
710 glyph_cache: HashMap::new(),
711 lru_head: None,
712 lru_tail: None,
713 atlas_next_x: 0,
714 atlas_next_y: 0,
715 atlas_row_height: 0,
716 cols,
717 rows,
718 cell_width,
719 cell_height,
720 window_padding,
721 scale_factor,
722 font_manager,
723 scrollbar,
724 cells: vec![Cell::default(); cols * rows],
725 dirty_rows: vec![true; rows],
726 row_cache: (0..rows).map(|_| None).collect(),
727 cursor_pos: (0, 0),
728 cursor_opacity: 0.0,
729 cursor_style: par_term_emu_core_rust::cursor::CursorStyle::SteadyBlock,
730 cursor_overlay: None,
731 cursor_color: [1.0, 1.0, 1.0], visual_bell_intensity: 0.0,
733 window_opacity,
734 background_color: [
735 background_color[0] as f32 / 255.0,
736 background_color[1] as f32 / 255.0,
737 background_color[2] as f32 / 255.0,
738 1.0,
739 ],
740 font_ascent,
741 font_descent,
742 font_leading,
743 font_size_pixels,
744 bg_image_texture: None,
745 bg_image_mode: background_image_mode,
746 bg_image_opacity: background_image_opacity,
747 max_bg_instances,
748 max_text_instances,
749 bg_instances: vec![
750 BackgroundInstance {
751 position: [0.0, 0.0],
752 size: [0.0, 0.0],
753 color: [0.0, 0.0, 0.0, 0.0],
754 };
755 max_bg_instances
756 ],
757 text_instances: vec![
758 TextInstance {
759 position: [0.0, 0.0],
760 size: [0.0, 0.0],
761 tex_offset: [0.0, 0.0],
762 tex_size: [0.0, 0.0],
763 color: [0.0, 0.0, 0.0, 0.0],
764 is_colored: 0,
765 };
766 max_text_instances
767 ],
768 enable_text_shaping,
769 enable_ligatures,
770 enable_kerning,
771 };
772
773 if let Some(path) = background_image_path {
774 renderer.load_background_image(path)?;
775 }
776
777 Ok(renderer)
778 }
779
780 pub fn device(&self) -> &wgpu::Device {
781 &self.device
782 }
783 pub fn queue(&self) -> &wgpu::Queue {
784 &self.queue
785 }
786 pub fn surface_format(&self) -> wgpu::TextureFormat {
787 self.config.format
788 }
789 pub fn cell_width(&self) -> f32 {
790 self.cell_width
791 }
792 pub fn cell_height(&self) -> f32 {
793 self.cell_height
794 }
795 pub fn window_padding(&self) -> f32 {
796 self.window_padding
797 }
798 pub fn grid_size(&self) -> (usize, usize) {
799 (self.cols, self.rows)
800 }
801
802 pub fn resize(&mut self, width: u32, height: u32) -> (usize, usize) {
803 if width == 0 || height == 0 {
804 return (self.cols, self.rows);
805 }
806 self.config.width = width;
807 self.config.height = height;
808 self.surface.configure(&self.device, &self.config);
809
810 let available_width = (width as f32 - self.window_padding * 2.0).max(0.0);
811 let available_height = (height as f32 - self.window_padding * 2.0).max(0.0);
812 let new_cols = (available_width / self.cell_width).max(1.0) as usize;
813 let new_rows = (available_height / self.cell_height).max(1.0) as usize;
814
815 if new_cols != self.cols || new_rows != self.rows {
816 self.cols = new_cols;
817 self.rows = new_rows;
818 self.cells = vec![Cell::default(); self.cols * self.rows];
819 self.dirty_rows = vec![true; self.rows];
820 self.row_cache = (0..self.rows).map(|_| None).collect();
821 self.recreate_instance_buffers();
822 }
823
824 self.update_bg_image_uniforms();
825 (self.cols, self.rows)
826 }
827
828 fn recreate_instance_buffers(&mut self) {
829 self.max_bg_instances = self.cols * self.rows + 1; self.max_text_instances = self.cols * self.rows * 2;
831 self.bg_instance_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
832 label: Some("bg instance buffer"),
833 size: (self.max_bg_instances * std::mem::size_of::<BackgroundInstance>()) as u64,
834 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
835 mapped_at_creation: false,
836 });
837 self.text_instance_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
838 label: Some("text instance buffer"),
839 size: (self.max_text_instances * std::mem::size_of::<TextInstance>()) as u64,
840 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
841 mapped_at_creation: false,
842 });
843
844 self.bg_instances = vec![
845 BackgroundInstance {
846 position: [0.0, 0.0],
847 size: [0.0, 0.0],
848 color: [0.0, 0.0, 0.0, 0.0],
849 };
850 self.max_bg_instances
851 ];
852 self.text_instances = vec![
853 TextInstance {
854 position: [0.0, 0.0],
855 size: [0.0, 0.0],
856 tex_offset: [0.0, 0.0],
857 tex_size: [0.0, 0.0],
858 color: [0.0, 0.0, 0.0, 0.0],
859 is_colored: 0,
860 };
861 self.max_text_instances
862 ];
863 }
864
865 pub fn update_cells(&mut self, new_cells: &[Cell]) {
866 for row in 0..self.rows {
867 let start = row * self.cols;
868 let end = (row + 1) * self.cols;
869 if start < new_cells.len() && end <= new_cells.len() {
870 let row_slice = &new_cells[start..end];
871 if row_slice != &self.cells[start..end] {
872 self.cells[start..end].clone_from_slice(row_slice);
873 self.dirty_rows[row] = true;
874 }
875 }
876 }
877 }
878
879 pub fn update_cursor(
880 &mut self,
881 pos: (usize, usize),
882 opacity: f32,
883 style: par_term_emu_core_rust::cursor::CursorStyle,
884 ) {
885 if self.cursor_pos != pos || self.cursor_opacity != opacity || self.cursor_style != style {
886 self.dirty_rows[self.cursor_pos.1.min(self.rows - 1)] = true;
887 self.cursor_pos = pos;
888 self.cursor_opacity = opacity;
889 self.cursor_style = style;
890 self.dirty_rows[self.cursor_pos.1.min(self.rows - 1)] = true;
891
892 use par_term_emu_core_rust::cursor::CursorStyle;
894 self.cursor_overlay = if opacity > 0.0 {
895 let col = pos.0;
896 let row = pos.1;
897 let x0 = (self.window_padding + col as f32 * self.cell_width).round();
898 let x1 = (self.window_padding + (col + 1) as f32 * self.cell_width).round();
899 let y0 = (self.window_padding + row as f32 * self.cell_height).round();
900 let y1 = (self.window_padding + (row + 1) as f32 * self.cell_height).round();
901
902 match style {
903 CursorStyle::SteadyBlock | CursorStyle::BlinkingBlock => None,
905 CursorStyle::SteadyBar | CursorStyle::BlinkingBar => {
907 Some(BackgroundInstance {
908 position: [
909 x0 / self.config.width as f32 * 2.0 - 1.0,
910 1.0 - (y0 / self.config.height as f32 * 2.0),
911 ],
912 size: [
913 2.0 / self.config.width as f32 * 2.0,
914 (y1 - y0) / self.config.height as f32 * 2.0,
915 ],
916 color: [self.cursor_color[0], self.cursor_color[1], self.cursor_color[2], opacity],
917 })
918 }
919 CursorStyle::SteadyUnderline | CursorStyle::BlinkingUnderline => {
921 Some(BackgroundInstance {
922 position: [
923 x0 / self.config.width as f32 * 2.0 - 1.0,
924 1.0 - ((y1 - 2.0) / self.config.height as f32 * 2.0),
925 ],
926 size: [
927 (x1 - x0) / self.config.width as f32 * 2.0,
928 2.0 / self.config.height as f32 * 2.0,
929 ],
930 color: [self.cursor_color[0], self.cursor_color[1], self.cursor_color[2], opacity],
931 })
932 }
933 }
934 } else {
935 None
936 };
937 }
938 }
939
940 pub fn clear_cursor(&mut self) {
941 self.update_cursor(self.cursor_pos, 0.0, self.cursor_style);
942 }
943
944 pub fn update_cursor_color(&mut self, color: [u8; 3]) {
946 self.cursor_color = [
947 color[0] as f32 / 255.0,
948 color[1] as f32 / 255.0,
949 color[2] as f32 / 255.0,
950 ];
951 self.dirty_rows[self.cursor_pos.1.min(self.rows - 1)] = true;
953 }
954
955 pub fn update_scrollbar(
956 &mut self,
957 scroll_offset: usize,
958 visible_lines: usize,
959 total_lines: usize,
960 ) {
961 self.scrollbar.update(
962 &self.queue,
963 scroll_offset,
964 visible_lines,
965 total_lines,
966 self.config.width,
967 self.config.height,
968 );
969 }
970
971 pub fn set_visual_bell_intensity(&mut self, intensity: f32) {
972 self.visual_bell_intensity = intensity;
973 }
974
975 pub fn update_opacity(&mut self, opacity: f32) {
976 self.window_opacity = opacity;
977 self.update_bg_image_uniforms();
978 }
979
980 pub fn update_scale_factor(&mut self, scale_factor: f64) {
981 self.scale_factor = scale_factor as f32;
982 }
983
984 pub fn update_window_padding(&mut self, padding: f32) -> Option<(usize, usize)> {
985 if (self.window_padding - padding).abs() > f32::EPSILON {
986 self.window_padding = padding;
987 let size = (self.config.width, self.config.height);
988 return Some(self.resize(size.0, size.1));
989 }
990 None
991 }
992
993 pub fn set_background_image(
994 &mut self,
995 path: Option<&str>,
996 mode: crate::config::BackgroundImageMode,
997 opacity: f32,
998 ) {
999 self.bg_image_mode = mode;
1000 self.bg_image_opacity = opacity;
1001 if let Some(p) = path {
1002 let _ = self.load_background_image(p);
1003 } else {
1004 self.bg_image_texture = None;
1005 self.bg_image_bind_group = None;
1006 }
1007 self.update_bg_image_uniforms();
1008 }
1009
1010 pub fn update_background_image_opacity(&mut self, opacity: f32) {
1011 self.bg_image_opacity = opacity;
1012 self.update_bg_image_uniforms();
1013 }
1014
1015 pub fn update_scrollbar_appearance(
1016 &mut self,
1017 width: f32,
1018 thumb_color: [f32; 4],
1019 track_color: [f32; 4],
1020 ) {
1021 self.scrollbar
1022 .update_appearance(width, thumb_color, track_color);
1023 }
1024
1025 pub fn update_scrollbar_position(&mut self, position: &str) {
1026 self.scrollbar.update_position(position);
1027 }
1028
1029 pub fn scrollbar_contains_point(&self, x: f32, y: f32) -> bool {
1030 self.scrollbar.contains_point(x, y)
1031 }
1032
1033 pub fn scrollbar_thumb_bounds(&self) -> Option<(f32, f32)> {
1034 self.scrollbar.thumb_bounds()
1035 }
1036
1037 pub fn scrollbar_track_contains_x(&self, x: f32) -> bool {
1038 self.scrollbar.track_contains_x(x)
1039 }
1040
1041 pub fn scrollbar_mouse_y_to_scroll_offset(&self, mouse_y: f32) -> Option<usize> {
1042 self.scrollbar.mouse_y_to_scroll_offset(mouse_y)
1043 }
1044
1045 pub fn reconfigure_surface(&mut self) {
1046 self.surface.configure(&self.device, &self.config);
1047 }
1048
1049 pub fn clear_glyph_cache(&mut self) {
1050 self.glyph_cache.clear();
1051 self.lru_head = None;
1052 self.lru_tail = None;
1053 self.atlas_next_x = 0;
1054 self.atlas_next_y = 0;
1055 self.atlas_row_height = 0;
1056 self.dirty_rows.fill(true);
1057 }
1058
1059 fn lru_remove(&mut self, key: u64) {
1060 let info = self.glyph_cache.get(&key).unwrap();
1061 let prev = info.prev;
1062 let next = info.next;
1063
1064 if let Some(p) = prev {
1065 self.glyph_cache.get_mut(&p).unwrap().next = next;
1066 } else {
1067 self.lru_head = next;
1068 }
1069
1070 if let Some(n) = next {
1071 self.glyph_cache.get_mut(&n).unwrap().prev = prev;
1072 } else {
1073 self.lru_tail = prev;
1074 }
1075 }
1076
1077 fn lru_push_front(&mut self, key: u64) {
1078 let next = self.lru_head;
1079 if let Some(n) = next {
1080 self.glyph_cache.get_mut(&n).unwrap().prev = Some(key);
1081 } else {
1082 self.lru_tail = Some(key);
1083 }
1084
1085 let info = self.glyph_cache.get_mut(&key).unwrap();
1086 info.prev = None;
1087 info.next = next;
1088 self.lru_head = Some(key);
1089 }
1090
1091 fn load_background_image(&mut self, path: &str) -> Result<()> {
1092 let img = image::open(path)?.to_rgba8();
1093 let (width, height) = img.dimensions();
1094 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
1095 label: Some("bg image"),
1096 size: wgpu::Extent3d {
1097 width,
1098 height,
1099 depth_or_array_layers: 1,
1100 },
1101 mip_level_count: 1,
1102 sample_count: 1,
1103 dimension: wgpu::TextureDimension::D2,
1104 format: wgpu::TextureFormat::Rgba8UnormSrgb,
1105 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1106 view_formats: &[],
1107 });
1108 self.queue.write_texture(
1109 wgpu::TexelCopyTextureInfo {
1110 texture: &texture,
1111 mip_level: 0,
1112 origin: wgpu::Origin3d::ZERO,
1113 aspect: wgpu::TextureAspect::All,
1114 },
1115 &img,
1116 wgpu::TexelCopyBufferLayout {
1117 offset: 0,
1118 bytes_per_row: Some(4 * width),
1119 rows_per_image: Some(height),
1120 },
1121 wgpu::Extent3d {
1122 width,
1123 height,
1124 depth_or_array_layers: 1,
1125 },
1126 );
1127
1128 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
1129 let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
1130 mag_filter: wgpu::FilterMode::Linear,
1131 min_filter: wgpu::FilterMode::Linear,
1132 ..Default::default()
1133 });
1134
1135 self.bg_image_bind_group =
1136 Some(self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1137 label: Some("bg image bind group"),
1138 layout: &self.bg_image_bind_group_layout,
1139 entries: &[
1140 wgpu::BindGroupEntry {
1141 binding: 0,
1142 resource: wgpu::BindingResource::TextureView(&view),
1143 },
1144 wgpu::BindGroupEntry {
1145 binding: 1,
1146 resource: wgpu::BindingResource::Sampler(&sampler),
1147 },
1148 wgpu::BindGroupEntry {
1149 binding: 2,
1150 resource: self.bg_image_uniform_buffer.as_entire_binding(),
1151 },
1152 ],
1153 }));
1154 self.bg_image_texture = Some(texture);
1155 self.update_bg_image_uniforms();
1156 Ok(())
1157 }
1158
1159 fn update_bg_image_uniforms(&mut self) {
1160 let mut data = [0.0f32; 16];
1161 data[0] = self.bg_image_opacity;
1162 data[1] = self.window_opacity;
1163 data[2] = self.bg_image_mode as u32 as f32;
1164 data[3] = self.config.width as f32;
1165 data[4] = self.config.height as f32;
1166 self.queue.write_buffer(
1167 &self.bg_image_uniform_buffer,
1168 0,
1169 bytemuck::cast_slice(&data),
1170 );
1171 }
1172
1173 pub fn render(&mut self, show_scrollbar: bool) -> Result<wgpu::SurfaceTexture> {
1174 let output = self.surface.get_current_texture()?;
1175 let view = output
1176 .texture
1177 .create_view(&wgpu::TextureViewDescriptor::default());
1178 self.build_instance_buffers()?;
1179
1180 let mut encoder = self
1181 .device
1182 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1183 label: Some("render encoder"),
1184 });
1185
1186 {
1187 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1188 label: Some("render pass"),
1189 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1190 view: &view,
1191 resolve_target: None,
1192 ops: wgpu::Operations {
1193 load: wgpu::LoadOp::Clear(wgpu::Color {
1194 r: self.background_color[0] as f64,
1195 g: self.background_color[1] as f64,
1196 b: self.background_color[2] as f64,
1197 a: self.window_opacity as f64,
1198 }),
1199 store: wgpu::StoreOp::Store,
1200 },
1201 depth_slice: None,
1202 })],
1203 depth_stencil_attachment: None,
1204 timestamp_writes: None,
1205 occlusion_query_set: None,
1206 });
1207
1208 if let Some(ref bg_bind_group) = self.bg_image_bind_group {
1209 render_pass.set_pipeline(&self.bg_image_pipeline);
1210 render_pass.set_bind_group(0, bg_bind_group, &[]);
1211 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1212 render_pass.draw(0..4, 0..1);
1213 }
1214
1215 render_pass.set_pipeline(&self.bg_pipeline);
1216 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1217 render_pass.set_vertex_buffer(1, self.bg_instance_buffer.slice(..));
1218 render_pass.draw(0..4, 0..self.max_bg_instances as u32);
1219
1220 render_pass.set_pipeline(&self.text_pipeline);
1221 render_pass.set_bind_group(0, &self.text_bind_group, &[]);
1222 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1223 render_pass.set_vertex_buffer(1, self.text_instance_buffer.slice(..));
1224 render_pass.draw(0..4, 0..self.max_text_instances as u32);
1225
1226 if show_scrollbar {
1227 self.scrollbar.render(&mut render_pass);
1228 }
1229 }
1230
1231 self.queue.submit(std::iter::once(encoder.finish()));
1232 Ok(output)
1233 }
1234
1235 pub fn render_to_texture(
1236 &mut self,
1237 target_view: &wgpu::TextureView,
1238 ) -> Result<wgpu::SurfaceTexture> {
1239 let output = self.surface.get_current_texture()?;
1240 self.build_instance_buffers()?;
1241
1242 let mut encoder = self
1243 .device
1244 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1245 label: Some("render to texture encoder"),
1246 });
1247
1248 {
1249 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1250 label: Some("render pass"),
1251 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1252 view: target_view,
1253 resolve_target: None,
1254 ops: wgpu::Operations {
1255 load: wgpu::LoadOp::Clear(wgpu::Color {
1256 r: 0.0,
1257 g: 0.0,
1258 b: 0.0,
1259 a: 0.0,
1260 }),
1261 store: wgpu::StoreOp::Store,
1262 },
1263 depth_slice: None,
1264 })],
1265 depth_stencil_attachment: None,
1266 timestamp_writes: None,
1267 occlusion_query_set: None,
1268 });
1269
1270 if let Some(ref bg_bind_group) = self.bg_image_bind_group {
1271 render_pass.set_pipeline(&self.bg_image_pipeline);
1272 render_pass.set_bind_group(0, bg_bind_group, &[]);
1273 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1274 render_pass.draw(0..4, 0..1);
1275 }
1276
1277 render_pass.set_pipeline(&self.bg_pipeline);
1278 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1279 render_pass.set_vertex_buffer(1, self.bg_instance_buffer.slice(..));
1280 render_pass.draw(0..4, 0..self.max_bg_instances as u32);
1281
1282 render_pass.set_pipeline(&self.text_pipeline);
1283 render_pass.set_bind_group(0, &self.text_bind_group, &[]);
1284 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1285 render_pass.set_vertex_buffer(1, self.text_instance_buffer.slice(..));
1286 render_pass.draw(0..4, 0..self.max_text_instances as u32);
1287 }
1288
1289 self.queue.submit(std::iter::once(encoder.finish()));
1290 Ok(output)
1291 }
1292
1293 pub fn render_overlays(
1294 &mut self,
1295 surface_texture: &wgpu::SurfaceTexture,
1296 show_scrollbar: bool,
1297 ) -> Result<()> {
1298 let view = surface_texture
1299 .texture
1300 .create_view(&wgpu::TextureViewDescriptor::default());
1301 let mut encoder = self
1302 .device
1303 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1304 label: Some("overlay encoder"),
1305 });
1306
1307 {
1308 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1309 label: Some("overlay pass"),
1310 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1311 view: &view,
1312 resolve_target: None,
1313 ops: wgpu::Operations {
1314 load: wgpu::LoadOp::Load,
1315 store: wgpu::StoreOp::Store,
1316 },
1317 depth_slice: None,
1318 })],
1319 depth_stencil_attachment: None,
1320 timestamp_writes: None,
1321 occlusion_query_set: None,
1322 });
1323
1324 if show_scrollbar {
1325 self.scrollbar.render(&mut render_pass);
1326 }
1327
1328 if self.visual_bell_intensity > 0.0 {
1329 }
1331 }
1332
1333 self.queue.submit(std::iter::once(encoder.finish()));
1334 Ok(())
1335 }
1336
1337 fn build_instance_buffers(&mut self) -> Result<()> {
1338 let _shaping_options = ShapingOptions {
1339 enable_ligatures: self.enable_ligatures,
1340 enable_kerning: self.enable_kerning,
1341 ..Default::default()
1342 };
1343
1344 for row in 0..self.rows {
1345 if self.dirty_rows[row] || self.row_cache[row].is_none() {
1346 let start = row * self.cols;
1347 let end = (row + 1) * self.cols;
1348 let row_cells = &self.cells[start..end];
1349
1350 let mut row_bg = Vec::with_capacity(self.cols);
1351 let mut row_text = Vec::with_capacity(self.cols);
1352
1353 for (col, cell) in row_cells.iter().enumerate() {
1355 let is_default_bg =
1356 (cell.bg_color[0] as f32 / 255.0 - self.background_color[0]).abs() < 0.001
1357 && (cell.bg_color[1] as f32 / 255.0 - self.background_color[1]).abs()
1358 < 0.001
1359 && (cell.bg_color[2] as f32 / 255.0 - self.background_color[2]).abs()
1360 < 0.001;
1361
1362 let has_cursor = self.cursor_opacity > 0.0
1363 && self.cursor_pos.1 == row
1364 && self.cursor_pos.0 == col;
1365
1366 if is_default_bg && !has_cursor {
1367 row_bg.push(BackgroundInstance {
1368 position: [0.0, 0.0],
1369 size: [0.0, 0.0],
1370 color: [0.0, 0.0, 0.0, 0.0],
1371 });
1372 continue;
1373 }
1374
1375 let bg_color = [
1376 cell.bg_color[0] as f32 / 255.0,
1377 cell.bg_color[1] as f32 / 255.0,
1378 cell.bg_color[2] as f32 / 255.0,
1379 cell.bg_color[3] as f32 / 255.0,
1380 ];
1381
1382 let x0 = (self.window_padding + col as f32 * self.cell_width).round();
1383 let x1 = (self.window_padding + (col + 1) as f32 * self.cell_width).round();
1384 let y0 = (self.window_padding + row as f32 * self.cell_height).round();
1385 let y1 = (self.window_padding + (row + 1) as f32 * self.cell_height).round();
1386
1387 let mut final_bg_color = bg_color;
1390 if has_cursor && self.cursor_opacity > 0.0 {
1391 use par_term_emu_core_rust::cursor::CursorStyle;
1392 match self.cursor_style {
1393 CursorStyle::SteadyBlock | CursorStyle::BlinkingBlock => {
1395 for (bg, &cursor) in
1396 final_bg_color.iter_mut().take(3).zip(&self.cursor_color)
1397 {
1398 *bg = *bg * (1.0 - self.cursor_opacity)
1399 + cursor * self.cursor_opacity;
1400 }
1401 final_bg_color[3] = final_bg_color[3].max(self.cursor_opacity);
1402 }
1403 _ => {}
1405 }
1406 }
1407
1408 row_bg.push(BackgroundInstance {
1410 position: [
1411 x0 / self.config.width as f32 * 2.0 - 1.0,
1412 1.0 - (y0 / self.config.height as f32 * 2.0),
1413 ],
1414 size: [
1415 (x1 - x0) / self.config.width as f32 * 2.0,
1416 (y1 - y0) / self.config.height as f32 * 2.0,
1417 ],
1418 color: final_bg_color,
1419 });
1420 }
1421
1422 let mut x_offset = 0.0;
1424 let cell_data: Vec<(String, bool, bool, [u8; 4], bool, bool)> = row_cells
1425 .iter()
1426 .map(|c| {
1427 (
1428 c.grapheme.clone(),
1429 c.bold,
1430 c.italic,
1431 c.fg_color,
1432 c.wide_char_spacer,
1433 c.wide_char,
1434 )
1435 })
1436 .collect();
1437
1438 let natural_line_height = self.font_ascent + self.font_descent + self.font_leading;
1440 let vertical_padding = (self.cell_height - natural_line_height).max(0.0) / 2.0;
1441 let baseline_y_unrounded = self.window_padding
1442 + (row as f32 * self.cell_height)
1443 + vertical_padding
1444 + self.font_ascent;
1445
1446 for (grapheme, bold, italic, fg_color, is_spacer, is_wide) in cell_data {
1447 if is_spacer || grapheme == " " {
1448 x_offset += self.cell_width;
1449 continue;
1450 }
1451
1452 let chars: Vec<char> = grapheme.chars().collect();
1453 #[allow(clippy::collapsible_if)]
1454 if let Some(ch) = chars.first() {
1455 if let Some((font_idx, glyph_id)) =
1456 self.font_manager.find_glyph(*ch, bold, italic)
1457 {
1458 let cache_key = ((font_idx as u64) << 32) | (glyph_id as u64);
1459 let info = if self.glyph_cache.contains_key(&cache_key) {
1460 self.lru_remove(cache_key);
1462 self.lru_push_front(cache_key);
1463 self.glyph_cache.get(&cache_key).unwrap().clone()
1464 } else if let Some(raster) = self.rasterize_glyph(font_idx, glyph_id) {
1465 let info = self.upload_glyph(cache_key, &raster);
1466 self.glyph_cache.insert(cache_key, info.clone());
1467 self.lru_push_front(cache_key);
1468 info
1469 } else {
1470 x_offset += self.cell_width;
1471 continue;
1472 };
1473
1474 let char_w = if is_wide {
1475 self.cell_width * 2.0
1476 } else {
1477 self.cell_width
1478 };
1479 let x0 = (self.window_padding + x_offset).round();
1480 let x1 = (self.window_padding + x_offset + char_w).round();
1481 let y0 = (self.window_padding + row as f32 * self.cell_height).round();
1482 let y1 =
1483 (self.window_padding + (row + 1) as f32 * self.cell_height).round();
1484
1485 let cell_w = x1 - x0;
1486 let cell_h = y1 - y0;
1487
1488 let scale_x = cell_w / char_w;
1489 let scale_y = cell_h / self.cell_height;
1490
1491 let baseline_offset = baseline_y_unrounded
1493 - (self.window_padding + row as f32 * self.cell_height);
1494 let mut glyph_left = x0 + (info.bearing_x * scale_x).round();
1495 let mut glyph_top =
1496 y0 + ((baseline_offset - info.bearing_y) * scale_y).round();
1497
1498 let mut render_w = info.width as f32 * scale_x;
1499 let mut render_h = info.height as f32 * scale_y;
1500
1501 let char_code = *ch as u32;
1504 let is_block_char = (0x2500..=0x259F).contains(&char_code)
1505 || (0xE0A0..=0xE0D4).contains(&char_code)
1506 || (0x25A0..=0x25FF).contains(&char_code); if is_block_char {
1509 if (glyph_left - x0).abs() < 3.0 {
1511 let right = glyph_left + render_w;
1512 glyph_left = x0;
1513 render_w = (right - x0).max(render_w);
1514 }
1515 if (x1 - (glyph_left + render_w)).abs() < 3.0 {
1516 render_w = x1 - glyph_left;
1517 }
1518
1519 if (glyph_top - y0).abs() < 3.0 {
1521 let bottom = glyph_top + render_h;
1522 glyph_top = y0;
1523 render_h = (bottom - y0).max(render_h);
1524 }
1525 if (y1 - (glyph_top + render_h)).abs() < 3.0 {
1526 render_h = y1 - glyph_top;
1527 }
1528
1529 let cx = (x0 + x1) / 2.0;
1531 let cy = (y0 + y1) / 2.0;
1532
1533 if (glyph_top + render_h - cy).abs() < 2.0 {
1535 render_h = cy - glyph_top;
1536 } else if (glyph_top - cy).abs() < 2.0 {
1537 let bottom = glyph_top + render_h;
1538 glyph_top = cy;
1539 render_h = bottom - cy;
1540 }
1541
1542 if (glyph_left + render_w - cx).abs() < 2.0 {
1544 render_w = cx - glyph_left;
1545 } else if (glyph_left - cx).abs() < 2.0 {
1546 let right = glyph_left + render_w;
1547 glyph_left = cx;
1548 render_w = right - cx;
1549 }
1550 }
1551
1552 row_text.push(TextInstance {
1553 position: [
1554 glyph_left / self.config.width as f32 * 2.0 - 1.0,
1555 1.0 - (glyph_top / self.config.height as f32 * 2.0),
1556 ],
1557 size: [
1558 render_w / self.config.width as f32 * 2.0,
1559 render_h / self.config.height as f32 * 2.0,
1560 ],
1561 tex_offset: [info.x as f32 / 2048.0, info.y as f32 / 2048.0],
1562 tex_size: [info.width as f32 / 2048.0, info.height as f32 / 2048.0],
1563 color: [
1564 fg_color[0] as f32 / 255.0,
1565 fg_color[1] as f32 / 255.0,
1566 fg_color[2] as f32 / 255.0,
1567 fg_color[3] as f32 / 255.0,
1568 ],
1569 is_colored: if info.is_colored { 1 } else { 0 },
1570 });
1571 }
1572 }
1573 x_offset += self.cell_width;
1574 }
1575
1576 let bg_start = row * self.cols;
1578 self.bg_instances[bg_start..bg_start + self.cols].copy_from_slice(&row_bg);
1579
1580 let text_start = row * self.cols * 2;
1581 for i in 0..(self.cols * 2) {
1583 self.text_instances[text_start + i].size = [0.0, 0.0];
1584 }
1585 let text_count = row_text.len().min(self.cols * 2);
1587 self.text_instances[text_start..text_start + text_count]
1588 .copy_from_slice(&row_text[..text_count]);
1589
1590 self.queue.write_buffer(
1592 &self.bg_instance_buffer,
1593 (bg_start * std::mem::size_of::<BackgroundInstance>()) as u64,
1594 bytemuck::cast_slice(&row_bg),
1595 );
1596 self.queue.write_buffer(
1597 &self.text_instance_buffer,
1598 (text_start * std::mem::size_of::<TextInstance>()) as u64,
1599 bytemuck::cast_slice(
1600 &self.text_instances[text_start..text_start + self.cols * 2],
1601 ),
1602 );
1603
1604 self.row_cache[row] = Some(RowCacheEntry {});
1605 self.dirty_rows[row] = false;
1606 }
1607 }
1608
1609 let cursor_overlay_index = self.cols * self.rows;
1611 let cursor_overlay_instance = self.cursor_overlay.unwrap_or(BackgroundInstance {
1612 position: [0.0, 0.0],
1613 size: [0.0, 0.0],
1614 color: [0.0, 0.0, 0.0, 0.0],
1615 });
1616 self.bg_instances[cursor_overlay_index] = cursor_overlay_instance;
1617 self.queue.write_buffer(
1618 &self.bg_instance_buffer,
1619 (cursor_overlay_index * std::mem::size_of::<BackgroundInstance>()) as u64,
1620 bytemuck::cast_slice(&[cursor_overlay_instance]),
1621 );
1622
1623 Ok(())
1624 }
1625
1626 fn rasterize_glyph(&self, font_idx: usize, glyph_id: u16) -> Option<RasterizedGlyph> {
1627 let font = self.font_manager.get_font(font_idx)?;
1628 use swash::scale::image::Content;
1630 use swash::scale::{Render, ScaleContext};
1631 let mut context = ScaleContext::new();
1632 let mut scaler = context
1633 .builder(*font)
1634 .size(self.font_size_pixels)
1635 .hint(true)
1636 .build();
1637 let image = Render::new(&[
1638 swash::scale::Source::ColorOutline(0),
1639 swash::scale::Source::ColorBitmap(swash::scale::StrikeWith::BestFit),
1640 swash::scale::Source::Outline,
1641 ])
1642 .render(&mut scaler, glyph_id)?;
1643
1644 let mut pixels = Vec::with_capacity(image.data.len() * 4);
1645 let is_colored = match image.content {
1646 Content::Color => {
1647 pixels.extend_from_slice(&image.data);
1648 true
1649 }
1650 Content::Mask => {
1651 for &mask in &image.data {
1652 pixels.push(255);
1653 pixels.push(255);
1654 pixels.push(255);
1655 pixels.push(mask);
1656 }
1657 false
1658 }
1659 _ => return None,
1660 };
1661
1662 Some(RasterizedGlyph {
1663 width: image.placement.width,
1664 height: image.placement.height,
1665 bearing_x: image.placement.left as f32,
1666 bearing_y: image.placement.top as f32,
1667 pixels,
1668 is_colored,
1669 })
1670 }
1671
1672 fn upload_glyph(&mut self, _key: u64, raster: &RasterizedGlyph) -> GlyphInfo {
1673 let padding = 2;
1674 if self.atlas_next_x + raster.width + padding > 2048 {
1675 self.atlas_next_x = 0;
1676 self.atlas_next_y += self.atlas_row_height + padding;
1677 self.atlas_row_height = 0;
1678 }
1679
1680 if self.atlas_next_y + raster.height + padding > 2048 {
1681 self.clear_glyph_cache();
1682 }
1683
1684 let info = GlyphInfo {
1685 key: _key,
1686 x: self.atlas_next_x,
1687 y: self.atlas_next_y,
1688 width: raster.width,
1689 height: raster.height,
1690 bearing_x: raster.bearing_x,
1691 bearing_y: raster.bearing_y,
1692 is_colored: raster.is_colored,
1693 prev: None,
1694 next: None,
1695 };
1696
1697 self.queue.write_texture(
1698 wgpu::TexelCopyTextureInfo {
1699 texture: &self.atlas_texture,
1700 mip_level: 0,
1701 origin: wgpu::Origin3d {
1702 x: info.x,
1703 y: info.y,
1704 z: 0,
1705 },
1706 aspect: wgpu::TextureAspect::All,
1707 },
1708 &raster.pixels,
1709 wgpu::TexelCopyBufferLayout {
1710 offset: 0,
1711 bytes_per_row: Some(4 * raster.width),
1712 rows_per_image: Some(raster.height),
1713 },
1714 wgpu::Extent3d {
1715 width: raster.width,
1716 height: raster.height,
1717 depth_or_array_layers: 1,
1718 },
1719 );
1720
1721 self.atlas_next_x += raster.width + padding;
1722 self.atlas_row_height = self.atlas_row_height.max(raster.height);
1723
1724 info
1725 }
1726
1727 #[allow(dead_code)]
1728 pub fn update_graphics(
1729 &mut self,
1730 _graphics: &[par_term_emu_core_rust::graphics::TerminalGraphic],
1731 _scroll_offset: usize,
1732 _scrollback_len: usize,
1733 _visible_lines: usize,
1734 ) -> Result<()> {
1735 Ok(())
1736 }
1737
1738 #[allow(dead_code)]
1739 pub fn update_background_image_opacity_only(&mut self, opacity: f32) {
1740 self.bg_image_opacity = opacity;
1741 self.update_bg_image_uniforms();
1742 }
1743}
1744
1745struct RasterizedGlyph {
1746 width: u32,
1747 height: u32,
1748 #[allow(dead_code)]
1749 bearing_x: f32,
1750 #[allow(dead_code)]
1751 bearing_y: f32,
1752 pixels: Vec<u8>,
1753 is_colored: bool,
1754}