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