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 let mut scratch: Vec<SeparatorMark> = Vec::new();
155 for pane in panes.iter() {
156 if pane.show_scrollbar {
157 let total_lines = pane.scrollback_len + pane.grid_size.1;
158 self.cell_renderer.update_scrollbar_for_pane(
159 pane.scroll_offset,
160 pane.grid_size.1,
161 total_lines,
162 &pane.marks,
163 &pane.viewport,
164 );
165 }
166 fill_visible_separator_marks(
167 &mut scratch,
168 &pane.marks,
169 pane.scrollback_len,
170 pane.scroll_offset,
171 pane.grid_size.1,
172 );
173 self.cell_renderer.render_pane_to_view(
174 &intermediate_view,
175 crate::cell_renderer::PaneRenderViewParams {
176 viewport: &pane.viewport,
177 cells: pane.cells,
178 cols: pane.grid_size.0,
179 rows: pane.grid_size.1,
180 cursor_pos: pane.cursor_pos,
181 cursor_opacity: pane.cursor_opacity,
182 show_scrollbar: pane.show_scrollbar,
183 clear_first: false,
184 skip_background_image: true, fill_default_bg_cells: false, separator_marks: &scratch,
187 pane_background: pane.background.as_ref(),
188 },
189 )?;
190 }
191
192 for pane in panes.iter() {
194 if !pane.graphics.is_empty() {
195 self.render_pane_sixel_graphics(
196 &intermediate_view,
197 &pane.viewport,
198 &pane.graphics,
199 pane.scroll_offset,
200 pane.scrollback_len,
201 pane.grid_size.1,
202 )?;
203 }
204 }
205 }
206
207 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
210 if !full_content_mode {
211 custom_shader.clear_intermediate_texture(
214 self.cell_renderer.device(),
215 self.cell_renderer.queue(),
216 );
217 }
218
219 custom_shader.render_with_clear_color(
223 self.cell_renderer.device(),
224 self.cell_renderer.queue(),
225 content_view,
226 !use_cursor_shader, clear_color,
228 )?;
229 } else {
230 let mut encoder = self.cell_renderer.device().create_command_encoder(
232 &wgpu::CommandEncoderDescriptor {
233 label: Some("split pane clear encoder"),
234 },
235 );
236
237 {
238 let _clear_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
239 label: Some("surface clear pass"),
240 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
241 view: content_view,
242 resolve_target: None,
243 ops: wgpu::Operations {
244 load: wgpu::LoadOp::Clear(clear_color),
245 store: wgpu::StoreOp::Store,
246 },
247 depth_slice: None,
248 })],
249 depth_stencil_attachment: None,
250 timestamp_writes: None,
251 occlusion_query_set: None,
252 });
253 }
254
255 self.cell_renderer
256 .queue()
257 .submit(std::iter::once(encoder.finish()));
258 }
259
260 let any_pane_has_background = panes.iter().any(|p| p.background.is_some());
265 let has_background_image = if !has_custom_shader && !any_pane_has_background {
266 self.cell_renderer
267 .render_background_only(content_view, false)?
268 } else {
269 false
270 };
271
272 if !full_content_mode {
276 let mut scratch: Vec<SeparatorMark> = Vec::new();
282 for pane in panes {
283 if pane.show_scrollbar {
284 let total_lines = pane.scrollback_len + pane.grid_size.1;
285 self.cell_renderer.update_scrollbar_for_pane(
286 pane.scroll_offset,
287 pane.grid_size.1,
288 total_lines,
289 &pane.marks,
290 &pane.viewport,
291 );
292 }
293 fill_visible_separator_marks(
294 &mut scratch,
295 &pane.marks,
296 pane.scrollback_len,
297 pane.scroll_offset,
298 pane.grid_size.1,
299 );
300 self.cell_renderer.render_pane_to_view(
301 content_view,
302 crate::cell_renderer::PaneRenderViewParams {
303 viewport: &pane.viewport,
304 cells: pane.cells,
305 cols: pane.grid_size.0,
306 rows: pane.grid_size.1,
307 cursor_pos: pane.cursor_pos,
308 cursor_opacity: pane.cursor_opacity,
309 show_scrollbar: pane.show_scrollbar,
310 clear_first: false, skip_background_image: has_background_image || has_custom_shader,
312 fill_default_bg_cells: has_background_image, separator_marks: &scratch,
314 pane_background: pane.background.as_ref(),
315 },
316 )?;
317 }
318
319 for pane in panes {
321 if !pane.graphics.is_empty() {
322 self.render_pane_sixel_graphics(
323 content_view,
324 &pane.viewport,
325 &pane.graphics,
326 pane.scroll_offset,
327 pane.scrollback_len,
328 pane.grid_size.1,
329 )?;
330 }
331 }
332 }
333
334 if !dividers.is_empty() {
336 self.render_dividers(content_view, dividers, divider_settings)?;
337 }
338
339 if !pane_titles.is_empty() {
341 self.render_pane_titles(content_view, pane_titles)?;
342 }
343
344 if self.cell_renderer.visual_bell_intensity > 0.0 {
346 let uniforms: [f32; 8] = [
347 -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, ];
356 self.cell_renderer.queue().write_buffer(
357 &self.cell_renderer.buffers.visual_bell_uniform_buffer,
358 0,
359 bytemuck::cast_slice(&uniforms),
360 );
361
362 let mut encoder = self.cell_renderer.device().create_command_encoder(
363 &wgpu::CommandEncoderDescriptor {
364 label: Some("visual bell encoder"),
365 },
366 );
367 {
368 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
369 label: Some("visual bell pass"),
370 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
371 view: content_view,
372 resolve_target: None,
373 ops: wgpu::Operations {
374 load: wgpu::LoadOp::Load,
375 store: wgpu::StoreOp::Store,
376 },
377 depth_slice: None,
378 })],
379 depth_stencil_attachment: None,
380 timestamp_writes: None,
381 occlusion_query_set: None,
382 });
383 render_pass.set_pipeline(&self.cell_renderer.pipelines.visual_bell_pipeline);
384 render_pass.set_bind_group(
385 0,
386 &self.cell_renderer.pipelines.visual_bell_bind_group,
387 &[],
388 );
389 render_pass.draw(0..4, 0..1); }
391 self.cell_renderer
392 .queue()
393 .submit(std::iter::once(encoder.finish()));
394 }
395
396 if panes.len() > 1
398 && let Some(viewport) = focused_viewport
399 {
400 self.render_focus_indicator(content_view, viewport, divider_settings)?;
401 }
402
403 if use_cursor_shader {
405 self.cursor_shader_renderer
406 .as_mut()
407 .expect("cursor_shader_renderer must be Some when use_cursor_shader is true")
408 .render(
409 self.cell_renderer.device(),
410 self.cell_renderer.queue(),
411 &surface_view,
412 true, )?;
414 }
415
416 if let Some((egui_output, egui_ctx)) = egui_data {
418 self.render_egui(&surface_texture, egui_output, egui_ctx, force_egui_opaque)?;
419 }
420
421 self.cell_renderer.render_opaque_alpha(&surface_texture)?;
423
424 surface_texture.present();
426
427 self.dirty = false;
428 Ok(true)
429 }
430
431 pub fn take_screenshot(&mut self) -> Result<image::RgbaImage, crate::error::RenderError> {
436 log::info!(
437 "take_screenshot: Starting screenshot capture ({}x{})",
438 self.size.width,
439 self.size.height
440 );
441
442 let width = self.size.width;
443 let height = self.size.height;
444 let format = self.cell_renderer.surface_format();
446 log::info!("take_screenshot: Using texture format {:?}", format);
447
448 let screenshot_texture =
450 self.cell_renderer
451 .device()
452 .create_texture(&wgpu::TextureDescriptor {
453 label: Some("screenshot texture"),
454 size: wgpu::Extent3d {
455 width,
456 height,
457 depth_or_array_layers: 1,
458 },
459 mip_level_count: 1,
460 sample_count: 1,
461 dimension: wgpu::TextureDimension::D2,
462 format,
463 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
464 view_formats: &[],
465 });
466
467 let screenshot_view =
468 screenshot_texture.create_view(&wgpu::TextureViewDescriptor::default());
469
470 log::info!("take_screenshot: Rendering composited frame...");
472
473 let has_custom_shader = self.custom_shader_renderer.is_some();
475 let use_cursor_shader =
476 self.cursor_shader_renderer.is_some() && !self.cursor_shader_disabled_for_alt_screen;
477
478 if has_custom_shader {
479 let intermediate_view = self
481 .custom_shader_renderer
482 .as_ref()
483 .expect("Custom shader renderer must be Some when has_custom_shader is true")
484 .intermediate_texture_view()
485 .clone();
486 self.cell_renderer
487 .render_to_texture(&intermediate_view, true)
488 .map_err(|e| {
489 crate::error::RenderError::ScreenshotMap(format!("Render failed: {:#}", e))
490 })?;
491
492 if use_cursor_shader {
493 let cursor_intermediate = self
495 .cursor_shader_renderer
496 .as_ref()
497 .expect("Cursor shader renderer must be Some when use_cursor_shader is true")
498 .intermediate_texture_view()
499 .clone();
500 self.custom_shader_renderer
501 .as_mut()
502 .expect("Custom shader renderer must be Some when has_custom_shader is true")
503 .render(
504 self.cell_renderer.device(),
505 self.cell_renderer.queue(),
506 &cursor_intermediate,
507 false,
508 )
509 .map_err(|e| {
510 crate::error::RenderError::ScreenshotMap(format!("Render failed: {:#}", e))
511 })?;
512 self.cursor_shader_renderer
514 .as_mut()
515 .expect("Cursor shader renderer must be Some when use_cursor_shader is true")
516 .render(
517 self.cell_renderer.device(),
518 self.cell_renderer.queue(),
519 &screenshot_view,
520 true,
521 )
522 .map_err(|e| {
523 crate::error::RenderError::ScreenshotMap(format!("Render failed: {:#}", e))
524 })?;
525 } else {
526 self.custom_shader_renderer
528 .as_mut()
529 .expect("Custom shader renderer must be Some when has_custom_shader is true")
530 .render(
531 self.cell_renderer.device(),
532 self.cell_renderer.queue(),
533 &screenshot_view,
534 true,
535 )
536 .map_err(|e| {
537 crate::error::RenderError::ScreenshotMap(format!("Render failed: {:#}", e))
538 })?;
539 }
540 } else if use_cursor_shader {
541 let cursor_intermediate = self
543 .cursor_shader_renderer
544 .as_ref()
545 .expect("Cursor shader renderer must be Some when use_cursor_shader is true")
546 .intermediate_texture_view()
547 .clone();
548 self.cell_renderer
549 .render_to_texture(&cursor_intermediate, true)
550 .map_err(|e| {
551 crate::error::RenderError::ScreenshotMap(format!("Render failed: {:#}", e))
552 })?;
553 self.cursor_shader_renderer
555 .as_mut()
556 .expect("Cursor shader renderer must be Some when use_cursor_shader is true")
557 .render(
558 self.cell_renderer.device(),
559 self.cell_renderer.queue(),
560 &screenshot_view,
561 true,
562 )
563 .map_err(|e| {
564 crate::error::RenderError::ScreenshotMap(format!("Render failed: {:#}", e))
565 })?;
566 } else {
567 self.cell_renderer
569 .render_to_view(&screenshot_view)
570 .map_err(|e| {
571 crate::error::RenderError::ScreenshotMap(format!("Render failed: {:#}", e))
572 })?;
573 }
574
575 log::info!("take_screenshot: Render complete");
576
577 let device = self.cell_renderer.device();
579 let queue = self.cell_renderer.queue();
580
581 let bytes_per_pixel = 4u32;
583 let unpadded_bytes_per_row = width * bytes_per_pixel;
584 let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
586 let padded_bytes_per_row = unpadded_bytes_per_row.div_ceil(align) * align;
587 let buffer_size = (padded_bytes_per_row * height) as u64;
588
589 let output_buffer = device.create_buffer(&wgpu::BufferDescriptor {
590 label: Some("screenshot buffer"),
591 size: buffer_size,
592 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
593 mapped_at_creation: false,
594 });
595
596 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
598 label: Some("screenshot encoder"),
599 });
600
601 encoder.copy_texture_to_buffer(
602 wgpu::TexelCopyTextureInfo {
603 texture: &screenshot_texture,
604 mip_level: 0,
605 origin: wgpu::Origin3d::ZERO,
606 aspect: wgpu::TextureAspect::All,
607 },
608 wgpu::TexelCopyBufferInfo {
609 buffer: &output_buffer,
610 layout: wgpu::TexelCopyBufferLayout {
611 offset: 0,
612 bytes_per_row: Some(padded_bytes_per_row),
613 rows_per_image: Some(height),
614 },
615 },
616 wgpu::Extent3d {
617 width,
618 height,
619 depth_or_array_layers: 1,
620 },
621 );
622
623 queue.submit(std::iter::once(encoder.finish()));
624 log::info!("take_screenshot: Texture copy submitted");
625
626 let buffer_slice = output_buffer.slice(..);
628 let (tx, rx) = std::sync::mpsc::channel();
629 buffer_slice.map_async(wgpu::MapMode::Read, move |result| {
630 let _ = tx.send(result);
631 });
632
633 log::info!("take_screenshot: Waiting for GPU...");
635 if let Err(e) = device.poll(wgpu::PollType::wait_indefinitely()) {
636 log::warn!("take_screenshot: GPU poll returned error: {:?}", e);
637 }
638 log::info!("take_screenshot: GPU poll complete, waiting for buffer map...");
639 rx.recv()
640 .map_err(|e| {
641 crate::error::RenderError::ScreenshotMap(format!(
642 "Failed to receive map result: {}",
643 e
644 ))
645 })?
646 .map_err(|e| {
647 crate::error::RenderError::ScreenshotMap(format!("Failed to map buffer: {:?}", e))
648 })?;
649 log::info!("take_screenshot: Buffer mapped successfully");
650
651 let data = buffer_slice.get_mapped_range();
653 let mut pixels = Vec::with_capacity((width * height * 4) as usize);
654
655 let is_bgra = matches!(
657 format,
658 wgpu::TextureFormat::Bgra8Unorm | wgpu::TextureFormat::Bgra8UnormSrgb
659 );
660
661 for y in 0..height {
663 let row_start = (y * padded_bytes_per_row) as usize;
664 let row_end = row_start + (width * bytes_per_pixel) as usize;
665 let row = &data[row_start..row_end];
666
667 if is_bgra {
668 for chunk in row.chunks(4) {
670 pixels.push(chunk[2]); pixels.push(chunk[1]); pixels.push(chunk[0]); pixels.push(chunk[3]); }
675 } else {
676 pixels.extend_from_slice(row);
678 }
679 }
680
681 drop(data);
682 output_buffer.unmap();
683
684 image::RgbaImage::from_raw(width, height, pixels)
686 .ok_or(crate::error::RenderError::ScreenshotImageAssembly)
687 }
688}