1use crate::cell_renderer::PaneViewport;
11use anyhow::Result;
12
13use super::{
14 DividerRenderInfo, PaneDividerSettings, PaneRenderInfo, PaneTitleInfo, Renderer, SeparatorMark,
15 fill_visible_separator_marks,
16};
17
18pub struct SplitPanesRenderParams<'a> {
22 pub panes: &'a [PaneRenderInfo<'a>],
23 pub dividers: &'a [DividerRenderInfo],
24 pub pane_titles: &'a [PaneTitleInfo],
25 pub focused_viewport: Option<&'a PaneViewport>,
26 pub divider_settings: &'a PaneDividerSettings,
27 pub egui_data: Option<(egui::FullOutput, &'a egui::Context)>,
28 pub force_egui_opaque: bool,
29}
30
31impl Renderer {
32 pub fn render_split_panes(&mut self, params: SplitPanesRenderParams<'_>) -> Result<bool> {
54 let SplitPanesRenderParams {
55 panes,
56 dividers,
57 pane_titles,
58 focused_viewport,
59 divider_settings,
60 egui_data,
61 force_egui_opaque,
62 } = params;
63 let force_render = self.needs_continuous_render();
65 if !self.dirty && !force_render && egui_data.is_none() {
66 return Ok(false);
67 }
68
69 let has_custom_shader = self.custom_shader_renderer.is_some();
70 let use_cursor_shader =
72 self.cursor_shader_renderer.is_some() && !self.cursor_shader_disabled_for_alt_screen;
73
74 for pane in panes.iter() {
76 if let Some(ref bg) = pane.background
77 && let Some(ref path) = bg.image_path
78 && let Err(e) = self.cell_renderer.load_pane_background(path)
79 {
80 log::error!("Failed to load pane background '{}': {}", path, e);
81 }
82 }
83
84 let surface_texture = self.cell_renderer.surface.get_current_texture()?;
86 let surface_view = surface_texture
87 .texture
88 .create_view(&wgpu::TextureViewDescriptor::default());
89
90 let cursor_intermediate: Option<wgpu::TextureView> = if use_cursor_shader {
93 Some(
94 self.cursor_shader_renderer
95 .as_ref()
96 .expect("cursor_shader_renderer must be Some when use_cursor_shader is true")
97 .intermediate_texture_view()
98 .clone(),
99 )
100 } else {
101 None
102 };
103 let content_view = cursor_intermediate.as_ref().unwrap_or(&surface_view);
105
106 let opacity = self.cell_renderer.window_opacity as f64;
109 let clear_color = if self.cell_renderer.pipelines.bg_image_bind_group.is_some() {
110 wgpu::Color::TRANSPARENT
111 } else if use_cursor_shader {
112 wgpu::Color {
114 r: self.cell_renderer.background_color[0] as f64,
115 g: self.cell_renderer.background_color[1] as f64,
116 b: self.cell_renderer.background_color[2] as f64,
117 a: 1.0,
118 }
119 } else {
120 wgpu::Color {
121 r: self.cell_renderer.background_color[0] as f64 * opacity,
122 g: self.cell_renderer.background_color[1] as f64 * opacity,
123 b: self.cell_renderer.background_color[2] as f64 * opacity,
124 a: opacity,
125 }
126 };
127
128 let full_content_mode = self
130 .custom_shader_renderer
131 .as_ref()
132 .is_some_and(|s| s.full_content_mode());
133
134 if full_content_mode {
139 let custom_shader = self
140 .custom_shader_renderer
141 .as_mut()
142 .expect("custom_shader_renderer must be Some when full_content_mode is true");
143 custom_shader.clear_intermediate_texture(
144 self.cell_renderer.device(),
145 self.cell_renderer.queue(),
146 );
147 let intermediate_view = custom_shader.intermediate_texture_view().clone();
148
149 for pane in panes.iter() {
151 if pane.viewport.focused && pane.show_scrollbar {
152 let total_lines = pane.scrollback_len + pane.grid_size.1;
153 let new_state = (
154 pane.scroll_offset,
155 pane.grid_size.1,
156 total_lines,
157 pane.marks.len(),
158 self.cell_renderer.config.width,
159 self.cell_renderer.config.height,
160 pane.viewport.x.to_bits(),
161 pane.viewport.y.to_bits(),
162 pane.viewport.width.to_bits(),
163 pane.viewport.height.to_bits(),
164 );
165 if new_state != self.last_scrollbar_state {
166 self.last_scrollbar_state = new_state;
167 self.cell_renderer.update_scrollbar_for_pane(
168 pane.scroll_offset,
169 pane.grid_size.1,
170 total_lines,
171 &pane.marks,
172 &pane.viewport,
173 );
174 }
175 break;
176 }
177 }
178
179 let mut scratch: Vec<SeparatorMark> = Vec::new();
183 for pane in panes.iter() {
184 fill_visible_separator_marks(
185 &mut scratch,
186 &pane.marks,
187 pane.scrollback_len,
188 pane.scroll_offset,
189 pane.grid_size.1,
190 );
191 self.cell_renderer.render_pane_to_view(
192 &intermediate_view,
193 crate::cell_renderer::PaneRenderViewParams {
194 viewport: &pane.viewport,
195 cells: pane.cells,
196 cols: pane.grid_size.0,
197 rows: pane.grid_size.1,
198 cursor_pos: pane.cursor_pos,
199 cursor_opacity: pane.cursor_opacity,
200 show_scrollbar: pane.show_scrollbar,
201 clear_first: false,
202 skip_background_image: true, fill_default_bg_cells: false, separator_marks: &scratch,
205 pane_background: pane.background.as_ref(),
206 },
207 )?;
208 }
209
210 for pane in panes.iter() {
212 if !pane.graphics.is_empty() {
213 self.render_pane_sixel_graphics(
214 &intermediate_view,
215 &pane.viewport,
216 &pane.graphics,
217 pane.scroll_offset,
218 pane.scrollback_len,
219 pane.grid_size.1,
220 )?;
221 }
222 }
223 }
224
225 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
228 if !full_content_mode {
229 custom_shader.clear_intermediate_texture(
232 self.cell_renderer.device(),
233 self.cell_renderer.queue(),
234 );
235 }
236
237 custom_shader.render_with_clear_color(
241 self.cell_renderer.device(),
242 self.cell_renderer.queue(),
243 content_view,
244 !use_cursor_shader, clear_color,
246 )?;
247 } else {
248 let mut encoder = self.cell_renderer.device().create_command_encoder(
250 &wgpu::CommandEncoderDescriptor {
251 label: Some("split pane clear encoder"),
252 },
253 );
254
255 {
256 let _clear_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
257 label: Some("surface clear pass"),
258 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
259 view: content_view,
260 resolve_target: None,
261 ops: wgpu::Operations {
262 load: wgpu::LoadOp::Clear(clear_color),
263 store: wgpu::StoreOp::Store,
264 },
265 depth_slice: None,
266 })],
267 depth_stencil_attachment: None,
268 timestamp_writes: None,
269 occlusion_query_set: None,
270 });
271 }
272
273 self.cell_renderer
274 .queue()
275 .submit(std::iter::once(encoder.finish()));
276 }
277
278 let any_pane_has_background = panes.iter().any(|p| p.background.is_some());
283 let has_background_image = if !has_custom_shader && !any_pane_has_background {
284 self.cell_renderer
285 .render_background_only(content_view, false)?
286 } else {
287 false
288 };
289
290 if !full_content_mode {
294 for pane in panes.iter() {
300 if pane.viewport.focused && pane.show_scrollbar {
301 let total_lines = pane.scrollback_len + pane.grid_size.1;
302 let new_state = (
303 pane.scroll_offset,
304 pane.grid_size.1,
305 total_lines,
306 pane.marks.len(),
307 self.cell_renderer.config.width,
308 self.cell_renderer.config.height,
309 pane.viewport.x.to_bits(),
312 pane.viewport.y.to_bits(),
313 pane.viewport.width.to_bits(),
314 pane.viewport.height.to_bits(),
315 );
316 if new_state != self.last_scrollbar_state {
317 self.last_scrollbar_state = new_state;
318 self.cell_renderer.update_scrollbar_for_pane(
319 pane.scroll_offset,
320 pane.grid_size.1,
321 total_lines,
322 &pane.marks,
323 &pane.viewport,
324 );
325 }
326 break;
327 }
328 }
329
330 let mut scratch: Vec<SeparatorMark> = Vec::new();
334 for pane in panes {
335 fill_visible_separator_marks(
336 &mut scratch,
337 &pane.marks,
338 pane.scrollback_len,
339 pane.scroll_offset,
340 pane.grid_size.1,
341 );
342 self.cell_renderer.render_pane_to_view(
343 content_view,
344 crate::cell_renderer::PaneRenderViewParams {
345 viewport: &pane.viewport,
346 cells: pane.cells,
347 cols: pane.grid_size.0,
348 rows: pane.grid_size.1,
349 cursor_pos: pane.cursor_pos,
350 cursor_opacity: pane.cursor_opacity,
351 show_scrollbar: pane.show_scrollbar,
352 clear_first: false, skip_background_image: has_background_image || has_custom_shader,
354 fill_default_bg_cells: has_background_image, separator_marks: &scratch,
356 pane_background: pane.background.as_ref(),
357 },
358 )?;
359 }
360
361 for pane in panes {
363 if !pane.graphics.is_empty() {
364 self.render_pane_sixel_graphics(
365 content_view,
366 &pane.viewport,
367 &pane.graphics,
368 pane.scroll_offset,
369 pane.scrollback_len,
370 pane.grid_size.1,
371 )?;
372 }
373 }
374 }
375
376 if !dividers.is_empty() {
378 self.render_dividers(content_view, dividers, divider_settings)?;
379 }
380
381 if !pane_titles.is_empty() {
383 self.render_pane_titles(content_view, pane_titles)?;
384 }
385
386 if self.cell_renderer.visual_bell_intensity > 0.0 {
388 let uniforms: [f32; 8] = [
389 -1.0, -1.0, 2.0, 2.0, self.cell_renderer.visual_bell_color[0], self.cell_renderer.visual_bell_color[1], self.cell_renderer.visual_bell_color[2], self.cell_renderer.visual_bell_intensity, ];
398 self.cell_renderer.queue().write_buffer(
399 &self.cell_renderer.buffers.visual_bell_uniform_buffer,
400 0,
401 bytemuck::cast_slice(&uniforms),
402 );
403
404 let mut encoder = self.cell_renderer.device().create_command_encoder(
405 &wgpu::CommandEncoderDescriptor {
406 label: Some("visual bell encoder"),
407 },
408 );
409 {
410 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
411 label: Some("visual bell pass"),
412 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
413 view: content_view,
414 resolve_target: None,
415 ops: wgpu::Operations {
416 load: wgpu::LoadOp::Load,
417 store: wgpu::StoreOp::Store,
418 },
419 depth_slice: None,
420 })],
421 depth_stencil_attachment: None,
422 timestamp_writes: None,
423 occlusion_query_set: None,
424 });
425 render_pass.set_pipeline(&self.cell_renderer.pipelines.visual_bell_pipeline);
426 render_pass.set_bind_group(
427 0,
428 &self.cell_renderer.pipelines.visual_bell_bind_group,
429 &[],
430 );
431 render_pass.draw(0..4, 0..1); }
433 self.cell_renderer
434 .queue()
435 .submit(std::iter::once(encoder.finish()));
436 }
437
438 if panes.len() > 1
440 && let Some(viewport) = focused_viewport
441 {
442 self.render_focus_indicator(content_view, viewport, divider_settings)?;
443 }
444
445 if use_cursor_shader {
447 self.cursor_shader_renderer
448 .as_mut()
449 .expect("cursor_shader_renderer must be Some when use_cursor_shader is true")
450 .render(
451 self.cell_renderer.device(),
452 self.cell_renderer.queue(),
453 &surface_view,
454 true, )?;
456 }
457
458 if let Some((egui_output, egui_ctx)) = egui_data {
460 self.render_egui(&surface_texture, egui_output, egui_ctx, force_egui_opaque)?;
461 }
462
463 self.cell_renderer.render_opaque_alpha(&surface_texture)?;
465
466 surface_texture.present();
468
469 self.dirty = false;
470 Ok(true)
471 }
472
473 pub fn take_screenshot(&mut self) -> Result<image::RgbaImage, crate::error::RenderError> {
478 log::info!(
479 "take_screenshot: Starting screenshot capture ({}x{})",
480 self.size.width,
481 self.size.height
482 );
483
484 let width = self.size.width;
485 let height = self.size.height;
486 let format = self.cell_renderer.surface_format();
488 log::info!("take_screenshot: Using texture format {:?}", format);
489
490 let screenshot_texture =
492 self.cell_renderer
493 .device()
494 .create_texture(&wgpu::TextureDescriptor {
495 label: Some("screenshot texture"),
496 size: wgpu::Extent3d {
497 width,
498 height,
499 depth_or_array_layers: 1,
500 },
501 mip_level_count: 1,
502 sample_count: 1,
503 dimension: wgpu::TextureDimension::D2,
504 format,
505 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
506 view_formats: &[],
507 });
508
509 let screenshot_view =
510 screenshot_texture.create_view(&wgpu::TextureViewDescriptor::default());
511
512 log::info!("take_screenshot: Rendering composited frame...");
514
515 let has_custom_shader = self.custom_shader_renderer.is_some();
517 let use_cursor_shader =
518 self.cursor_shader_renderer.is_some() && !self.cursor_shader_disabled_for_alt_screen;
519
520 if has_custom_shader {
521 let intermediate_view = self
523 .custom_shader_renderer
524 .as_ref()
525 .expect("Custom shader renderer must be Some when has_custom_shader is true")
526 .intermediate_texture_view()
527 .clone();
528 self.cell_renderer
529 .render_to_texture(&intermediate_view, true)
530 .map_err(|e| {
531 crate::error::RenderError::ScreenshotMap(format!("Render failed: {:#}", e))
532 })?;
533
534 if use_cursor_shader {
535 let cursor_intermediate = self
537 .cursor_shader_renderer
538 .as_ref()
539 .expect("Cursor shader renderer must be Some when use_cursor_shader is true")
540 .intermediate_texture_view()
541 .clone();
542 self.custom_shader_renderer
543 .as_mut()
544 .expect("Custom shader renderer must be Some when has_custom_shader is true")
545 .render(
546 self.cell_renderer.device(),
547 self.cell_renderer.queue(),
548 &cursor_intermediate,
549 false,
550 )
551 .map_err(|e| {
552 crate::error::RenderError::ScreenshotMap(format!("Render failed: {:#}", e))
553 })?;
554 self.cursor_shader_renderer
556 .as_mut()
557 .expect("Cursor shader renderer must be Some when use_cursor_shader is true")
558 .render(
559 self.cell_renderer.device(),
560 self.cell_renderer.queue(),
561 &screenshot_view,
562 true,
563 )
564 .map_err(|e| {
565 crate::error::RenderError::ScreenshotMap(format!("Render failed: {:#}", e))
566 })?;
567 } else {
568 self.custom_shader_renderer
570 .as_mut()
571 .expect("Custom shader renderer must be Some when has_custom_shader is true")
572 .render(
573 self.cell_renderer.device(),
574 self.cell_renderer.queue(),
575 &screenshot_view,
576 true,
577 )
578 .map_err(|e| {
579 crate::error::RenderError::ScreenshotMap(format!("Render failed: {:#}", e))
580 })?;
581 }
582 } else if use_cursor_shader {
583 let cursor_intermediate = self
585 .cursor_shader_renderer
586 .as_ref()
587 .expect("Cursor shader renderer must be Some when use_cursor_shader is true")
588 .intermediate_texture_view()
589 .clone();
590 self.cell_renderer
591 .render_to_texture(&cursor_intermediate, true)
592 .map_err(|e| {
593 crate::error::RenderError::ScreenshotMap(format!("Render failed: {:#}", e))
594 })?;
595 self.cursor_shader_renderer
597 .as_mut()
598 .expect("Cursor shader renderer must be Some when use_cursor_shader is true")
599 .render(
600 self.cell_renderer.device(),
601 self.cell_renderer.queue(),
602 &screenshot_view,
603 true,
604 )
605 .map_err(|e| {
606 crate::error::RenderError::ScreenshotMap(format!("Render failed: {:#}", e))
607 })?;
608 } else {
609 self.cell_renderer
611 .render_to_view(&screenshot_view)
612 .map_err(|e| {
613 crate::error::RenderError::ScreenshotMap(format!("Render failed: {:#}", e))
614 })?;
615 }
616
617 log::info!("take_screenshot: Render complete");
618
619 let device = self.cell_renderer.device();
621 let queue = self.cell_renderer.queue();
622
623 let bytes_per_pixel = 4u32;
625 let unpadded_bytes_per_row = width * bytes_per_pixel;
626 let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
628 let padded_bytes_per_row = unpadded_bytes_per_row.div_ceil(align) * align;
629 let buffer_size = (padded_bytes_per_row * height) as u64;
630
631 let output_buffer = device.create_buffer(&wgpu::BufferDescriptor {
632 label: Some("screenshot buffer"),
633 size: buffer_size,
634 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
635 mapped_at_creation: false,
636 });
637
638 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
640 label: Some("screenshot encoder"),
641 });
642
643 encoder.copy_texture_to_buffer(
644 wgpu::TexelCopyTextureInfo {
645 texture: &screenshot_texture,
646 mip_level: 0,
647 origin: wgpu::Origin3d::ZERO,
648 aspect: wgpu::TextureAspect::All,
649 },
650 wgpu::TexelCopyBufferInfo {
651 buffer: &output_buffer,
652 layout: wgpu::TexelCopyBufferLayout {
653 offset: 0,
654 bytes_per_row: Some(padded_bytes_per_row),
655 rows_per_image: Some(height),
656 },
657 },
658 wgpu::Extent3d {
659 width,
660 height,
661 depth_or_array_layers: 1,
662 },
663 );
664
665 queue.submit(std::iter::once(encoder.finish()));
666 log::info!("take_screenshot: Texture copy submitted");
667
668 let buffer_slice = output_buffer.slice(..);
670 let (tx, rx) = std::sync::mpsc::channel();
671 buffer_slice.map_async(wgpu::MapMode::Read, move |result| {
672 let _ = tx.send(result);
673 });
674
675 log::info!("take_screenshot: Waiting for GPU...");
677 if let Err(e) = device.poll(wgpu::PollType::wait_indefinitely()) {
678 log::warn!("take_screenshot: GPU poll returned error: {:?}", e);
679 }
680 log::info!("take_screenshot: GPU poll complete, waiting for buffer map...");
681 rx.recv()
682 .map_err(|e| {
683 crate::error::RenderError::ScreenshotMap(format!(
684 "Failed to receive map result: {}",
685 e
686 ))
687 })?
688 .map_err(|e| {
689 crate::error::RenderError::ScreenshotMap(format!("Failed to map buffer: {:?}", e))
690 })?;
691 log::info!("take_screenshot: Buffer mapped successfully");
692
693 let data = buffer_slice.get_mapped_range();
695 let mut pixels = Vec::with_capacity((width * height * 4) as usize);
696
697 let is_bgra = matches!(
699 format,
700 wgpu::TextureFormat::Bgra8Unorm | wgpu::TextureFormat::Bgra8UnormSrgb
701 );
702
703 for y in 0..height {
705 let row_start = (y * padded_bytes_per_row) as usize;
706 let row_end = row_start + (width * bytes_per_pixel) as usize;
707 let row = &data[row_start..row_end];
708
709 if is_bgra {
710 for chunk in row.chunks(4) {
712 pixels.push(chunk[2]); pixels.push(chunk[1]); pixels.push(chunk[0]); pixels.push(chunk[3]); }
717 } else {
718 pixels.extend_from_slice(row);
720 }
721 }
722
723 drop(data);
724 output_buffer.unmap();
725
726 image::RgbaImage::from_raw(width, height, pixels)
728 .ok_or(crate::error::RenderError::ScreenshotImageAssembly)
729 }
730}