1use super::CellRenderer;
2use anyhow::Result;
3use std::ops::Range;
4
5pub use super::instance_buffers::CURSOR_OVERLAY_SLOTS;
11
12#[derive(Clone, Copy, PartialEq, Eq, Debug)]
14pub enum CellPipeline {
15 Background,
17 Text,
19}
20
21#[derive(Clone, Copy, PartialEq, Eq, Debug)]
27pub enum CellDrawPhase {
28 Background,
30 ExtraBackground,
32 Text,
34 CursorOverlays,
36}
37
38impl CellDrawPhase {
39 pub fn pipeline(self) -> CellPipeline {
41 match self {
42 Self::Background | Self::ExtraBackground | Self::CursorOverlays => {
43 CellPipeline::Background
44 }
45 Self::Text => CellPipeline::Text,
46 }
47 }
48}
49
50#[derive(Clone, PartialEq, Eq, Debug)]
57pub struct SingleGridLayout {
58 pub cells: Range<usize>,
60 pub cursor_overlays: Range<usize>,
62 pub separators: Range<usize>,
64 pub gutters: Range<usize>,
66}
67
68impl SingleGridLayout {
69 pub fn new(cols: usize, rows: usize) -> Self {
71 let cells = 0..cols * rows;
72 let cursor_overlays = cells.end..cells.end + CURSOR_OVERLAY_SLOTS;
73 let separators = cursor_overlays.end..cursor_overlays.end + rows;
74 let gutters = separators.end..separators.end + rows;
75 Self {
76 cells,
77 cursor_overlays,
78 separators,
79 gutters,
80 }
81 }
82
83 pub fn bg_instances(&self) -> usize {
85 self.gutters.end
86 }
87}
88
89#[derive(Clone, PartialEq, Eq, Debug)]
96pub struct ThreePhaseRanges {
97 pub bg: Range<u32>,
99 pub extra_bg: Range<u32>,
102 pub text: Range<u32>,
104 pub cursor_overlays: Range<u32>,
106}
107
108impl ThreePhaseRanges {
109 pub fn draw_sequence(&self) -> [(CellDrawPhase, Range<u32>); 4] {
116 [
117 (CellDrawPhase::Background, self.bg.clone()),
118 (CellDrawPhase::ExtraBackground, self.extra_bg.clone()),
119 (CellDrawPhase::Text, self.text.clone()),
120 (CellDrawPhase::CursorOverlays, self.cursor_overlays.clone()),
121 ]
122 }
123
124 pub fn for_single_grid(
129 layout: &SingleGridLayout,
130 actual_bg_instances: usize,
131 actual_text_instances: usize,
132 ) -> Self {
133 let overlay_end = layout.cursor_overlays.end;
134 let bg_end = actual_bg_instances.max(overlay_end);
135 Self {
136 bg: layout.cells.start as u32..layout.cells.end as u32,
137 extra_bg: overlay_end as u32..bg_end as u32,
138 text: 0..actual_text_instances as u32,
139 cursor_overlays: layout.cursor_overlays.start as u32..overlay_end as u32,
140 }
141 }
142
143 pub fn for_pane(bg: Range<usize>, cursor_overlays: Range<usize>, text: Range<usize>) -> Self {
149 debug_assert_eq!(
150 bg.end, cursor_overlays.start,
151 "a pane's cursor overlays must directly follow its background run"
152 );
153 Self {
154 bg: bg.start as u32..bg.end as u32,
155 extra_bg: 0..0,
156 text: text.start as u32..text.end as u32,
157 cursor_overlays: cursor_overlays.start as u32..cursor_overlays.end as u32,
158 }
159 }
160}
161
162impl CellRenderer {
163 pub(crate) fn emit_three_phase_draw_calls(
180 &self,
181 render_pass: &mut wgpu::RenderPass<'_>,
182 ranges: &ThreePhaseRanges,
183 ) {
184 let mut bound: Option<CellPipeline> = None;
185
186 for (phase, range) in ranges.draw_sequence() {
187 if range.is_empty() {
188 continue;
189 }
190
191 let pipeline = phase.pipeline();
192 if bound != Some(pipeline) {
193 match pipeline {
194 CellPipeline::Background => {
195 render_pass.set_pipeline(&self.pipelines.bg_pipeline);
196 render_pass.set_vertex_buffer(0, self.buffers.vertex_buffer.slice(..));
197 render_pass.set_vertex_buffer(1, self.buffers.bg_instance_buffer.slice(..));
198 }
199 CellPipeline::Text => {
200 render_pass.set_pipeline(&self.pipelines.text_pipeline);
201 render_pass.set_bind_group(0, &self.pipelines.text_bind_group, &[]);
202 render_pass.set_vertex_buffer(0, self.buffers.vertex_buffer.slice(..));
203 render_pass
204 .set_vertex_buffer(1, self.buffers.text_instance_buffer.slice(..));
205 }
206 }
207 bound = Some(pipeline);
208 }
209
210 render_pass.draw(0..4, range);
211 }
212 }
213
214 fn single_grid_ranges(&self) -> ThreePhaseRanges {
216 ThreePhaseRanges::for_single_grid(
217 &SingleGridLayout::new(self.grid.cols, self.grid.rows),
218 self.buffers.actual_bg_instances,
219 self.buffers.actual_text_instances,
220 )
221 }
222
223 pub fn render_to_texture(
238 &mut self,
239 target_view: &wgpu::TextureView,
240 skip_background_image: bool,
241 ) -> Result<()> {
242 self.build_instance_buffers()?;
243
244 let render_background_image =
247 !skip_background_image && self.pipelines.bg_image_bind_group.is_some();
248
249 if render_background_image {
250 self.update_bg_image_uniforms(Some(1.0));
255 }
256
257 let mut encoder = self
258 .device
259 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
260 label: Some("render to texture encoder"),
261 });
262
263 let clear_color = wgpu::Color::TRANSPARENT;
265
266 {
267 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
268 label: Some("render pass"),
269 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
270 view: target_view,
271 resolve_target: None,
272 ops: wgpu::Operations {
273 load: wgpu::LoadOp::Clear(clear_color),
274 store: wgpu::StoreOp::Store,
275 },
276 depth_slice: None,
277 })],
278 depth_stencil_attachment: None,
279 timestamp_writes: None,
280 occlusion_query_set: None,
281 multiview_mask: None,
282 });
283
284 if render_background_image
286 && let Some(ref bg_bind_group) = self.pipelines.bg_image_bind_group
287 {
288 render_pass.set_pipeline(&self.pipelines.bg_image_pipeline);
289 render_pass.set_bind_group(0, bg_bind_group, &[]);
290 render_pass.set_vertex_buffer(0, self.buffers.vertex_buffer.slice(..));
291 render_pass.draw(0..4, 0..1);
292 }
293
294 self.emit_three_phase_draw_calls(&mut render_pass, &self.single_grid_ranges());
295 }
296
297 self.queue.submit(std::iter::once(encoder.finish()));
298
299 if render_background_image {
303 self.update_bg_image_uniforms(None);
304 }
305
306 Ok(())
307 }
308
309 pub fn render_background_only(
321 &self,
322 target_view: &wgpu::TextureView,
323 clear_first: bool,
324 ) -> Result<bool> {
325 let mut encoder = self
326 .device
327 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
328 label: Some("background only encoder"),
329 });
330
331 let use_bg_image_pipeline = self.pipelines.bg_image_bind_group.is_some();
334 let clear_color = if use_bg_image_pipeline {
335 wgpu::Color::TRANSPARENT
336 } else {
337 wgpu::Color {
338 r: self.background_color[0] as f64 * self.window_opacity as f64,
339 g: self.background_color[1] as f64 * self.window_opacity as f64,
340 b: self.background_color[2] as f64 * self.window_opacity as f64,
341 a: self.window_opacity as f64,
342 }
343 };
344
345 let load_op = if clear_first {
346 wgpu::LoadOp::Clear(clear_color)
347 } else {
348 wgpu::LoadOp::Load
349 };
350
351 {
352 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
353 label: Some("background only render pass"),
354 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
355 view: target_view,
356 resolve_target: None,
357 ops: wgpu::Operations {
358 load: load_op,
359 store: wgpu::StoreOp::Store,
360 },
361 depth_slice: None,
362 })],
363 depth_stencil_attachment: None,
364 timestamp_writes: None,
365 occlusion_query_set: None,
366 multiview_mask: None,
367 });
368
369 if use_bg_image_pipeline
371 && let Some(ref bg_bind_group) = self.pipelines.bg_image_bind_group
372 {
373 render_pass.set_pipeline(&self.pipelines.bg_image_pipeline);
374 render_pass.set_bind_group(0, bg_bind_group, &[]);
375 render_pass.set_vertex_buffer(0, self.buffers.vertex_buffer.slice(..));
376 render_pass.draw(0..4, 0..1);
377 }
378 }
379
380 self.queue.submit(std::iter::once(encoder.finish()));
381 Ok(use_bg_image_pipeline)
382 }
383
384 pub fn render_to_view(&mut self, target_view: &wgpu::TextureView) -> Result<()> {
395 self.build_instance_buffers()?;
396
397 let mut encoder = self
398 .device
399 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
400 label: Some("screenshot render encoder"),
401 });
402
403 let use_bg_image_pipeline = self.pipelines.bg_image_bind_group.is_some();
405 let clear_color = if use_bg_image_pipeline {
406 wgpu::Color::TRANSPARENT
407 } else {
408 wgpu::Color {
409 r: self.background_color[0] as f64 * self.window_opacity as f64,
410 g: self.background_color[1] as f64 * self.window_opacity as f64,
411 b: self.background_color[2] as f64 * self.window_opacity as f64,
412 a: self.window_opacity as f64,
413 }
414 };
415
416 {
417 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
418 label: Some("screenshot render pass"),
419 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
420 view: target_view,
421 resolve_target: None,
422 ops: wgpu::Operations {
423 load: wgpu::LoadOp::Clear(clear_color),
424 store: wgpu::StoreOp::Store,
425 },
426 depth_slice: None,
427 })],
428 depth_stencil_attachment: None,
429 timestamp_writes: None,
430 occlusion_query_set: None,
431 multiview_mask: None,
432 });
433
434 if use_bg_image_pipeline
436 && let Some(ref bg_bind_group) = self.pipelines.bg_image_bind_group
437 {
438 render_pass.set_pipeline(&self.pipelines.bg_image_pipeline);
439 render_pass.set_bind_group(0, bg_bind_group, &[]);
440 render_pass.set_vertex_buffer(0, self.buffers.vertex_buffer.slice(..));
441 render_pass.draw(0..4, 0..1);
442 }
443
444 self.emit_three_phase_draw_calls(&mut render_pass, &self.single_grid_ranges());
445
446 self.scrollbar.render(&mut render_pass, 0);
448 }
449
450 self.queue.submit(std::iter::once(encoder.finish()));
451 Ok(())
452 }
453
454 pub fn render_overlays(
455 &mut self,
456 surface_texture: &wgpu::SurfaceTexture,
457 show_scrollbar: bool,
458 ) -> Result<()> {
459 if !show_scrollbar && self.visual_bell_intensity <= 0.0 {
461 return Ok(());
462 }
463
464 let view = surface_texture
465 .texture
466 .create_view(&wgpu::TextureViewDescriptor::default());
467 let mut encoder = self
468 .device
469 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
470 label: Some("overlay encoder"),
471 });
472
473 {
474 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
475 label: Some("overlay pass"),
476 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
477 view: &view,
478 resolve_target: None,
479 ops: wgpu::Operations {
480 load: wgpu::LoadOp::Load,
481 store: wgpu::StoreOp::Store,
482 },
483 depth_slice: None,
484 })],
485 depth_stencil_attachment: None,
486 timestamp_writes: None,
487 occlusion_query_set: None,
488 multiview_mask: None,
489 });
490
491 if show_scrollbar {
492 self.scrollbar.render(&mut render_pass, 0);
494 }
495
496 if self.visual_bell_intensity > 0.0 {
497 let uniforms: [f32; 8] = [
500 -1.0, -1.0, 2.0, 2.0, self.visual_bell_color[0], self.visual_bell_color[1], self.visual_bell_color[2], self.visual_bell_intensity, ];
509 self.queue.write_buffer(
510 &self.buffers.visual_bell_uniform_buffer,
511 0,
512 bytemuck::cast_slice(&uniforms),
513 );
514
515 render_pass.set_pipeline(&self.pipelines.visual_bell_pipeline);
516 render_pass.set_bind_group(0, &self.pipelines.visual_bell_bind_group, &[]);
517 render_pass.draw(0..4, 0..1); }
519 }
520
521 self.queue.submit(std::iter::once(encoder.finish()));
522 Ok(())
523 }
524
525 pub fn render_opaque_alpha(&self, surface_texture: &wgpu::SurfaceTexture) -> Result<()> {
534 if self.window_opacity < 1.0 {
538 return Ok(());
539 }
540 let view = surface_texture
541 .texture
542 .create_view(&wgpu::TextureViewDescriptor::default());
543 self.render_opaque_alpha_to_view(&view)
544 }
545
546 pub fn render_opaque_alpha_to_view(&self, view: &wgpu::TextureView) -> Result<()> {
552 if self.window_opacity < 1.0 {
553 return Ok(());
554 }
555
556 let mut encoder = self
557 .device
558 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
559 label: Some("opaque alpha encoder"),
560 });
561
562 {
563 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
564 label: Some("opaque alpha pass"),
565 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
566 view,
567 resolve_target: None,
568 ops: wgpu::Operations {
569 load: wgpu::LoadOp::Load,
570 store: wgpu::StoreOp::Store,
571 },
572 depth_slice: None,
573 })],
574 depth_stencil_attachment: None,
575 timestamp_writes: None,
576 occlusion_query_set: None,
577 multiview_mask: None,
578 });
579
580 render_pass.set_pipeline(&self.pipelines.opaque_alpha_pipeline);
581 render_pass.draw(0..3, 0..1);
582 }
583
584 self.queue.submit(std::iter::once(encoder.finish()));
585 Ok(())
586 }
587}
588
589#[cfg(test)]
590mod tests {
591 use super::*;
592
593 fn phase_index(ranges: &ThreePhaseRanges, phase: CellDrawPhase) -> usize {
595 ranges
596 .draw_sequence()
597 .iter()
598 .position(|(candidate, _)| *candidate == phase)
599 .unwrap_or_else(|| panic!("{phase:?} is missing from the draw sequence"))
600 }
601
602 fn pane_ranges(bg_base: usize, bg_len: usize, overlays: usize) -> ThreePhaseRanges {
606 let bg = bg_base..bg_base + bg_len;
607 ThreePhaseRanges::for_pane(bg.clone(), bg.end..bg.end + overlays, 40..90)
608 }
609
610 #[test]
616 fn cursor_overlays_are_drawn_after_text() {
617 let single_grid = ThreePhaseRanges::for_single_grid(
618 &SingleGridLayout::new(80, 24),
619 80 * 24 + CURSOR_OVERLAY_SLOTS + 24 + 24,
620 80 * 24 * 2,
621 );
622 let pane = pane_ranges(0, 1 + 80 * 24, CURSOR_OVERLAY_SLOTS);
623
624 for ranges in [single_grid, pane] {
625 assert!(
626 phase_index(&ranges, CellDrawPhase::CursorOverlays)
627 > phase_index(&ranges, CellDrawPhase::Text),
628 "cursor overlays must be emitted after the text: {ranges:?}"
629 );
630 }
631 }
632
633 #[test]
636 fn backgrounds_are_drawn_before_text() {
637 let ranges = ThreePhaseRanges::for_single_grid(
638 &SingleGridLayout::new(10, 4),
639 SingleGridLayout::new(10, 4).bg_instances(),
640 80,
641 );
642 let text = phase_index(&ranges, CellDrawPhase::Text);
643 assert!(phase_index(&ranges, CellDrawPhase::Background) < text);
644 assert!(phase_index(&ranges, CellDrawPhase::ExtraBackground) < text);
645 }
646
647 #[test]
650 fn overlays_use_the_background_pipeline_in_a_later_phase() {
651 assert_eq!(
652 CellDrawPhase::CursorOverlays.pipeline(),
653 CellDrawPhase::Background.pipeline()
654 );
655 assert_ne!(
656 CellDrawPhase::CursorOverlays.pipeline(),
657 CellDrawPhase::Text.pipeline()
658 );
659 }
660
661 #[test]
663 fn the_sequence_covers_each_phase_once() {
664 let ranges = pane_ranges(7, 30, CURSOR_OVERLAY_SLOTS);
665 let sequence = ranges.draw_sequence();
666 for phase in [
667 CellDrawPhase::Background,
668 CellDrawPhase::ExtraBackground,
669 CellDrawPhase::Text,
670 CellDrawPhase::CursorOverlays,
671 ] {
672 assert_eq!(
673 sequence
674 .iter()
675 .filter(|(candidate, _)| *candidate == phase)
676 .count(),
677 1,
678 "{phase:?} appears more than once"
679 );
680 }
681 }
682
683 #[test]
686 fn the_single_grid_regions_tile_the_background_buffer() {
687 let (cols, rows) = (80usize, 24usize);
688 let layout = SingleGridLayout::new(cols, rows);
689
690 assert_eq!(layout.cells, 0..cols * rows);
691 assert_eq!(layout.cursor_overlays.start, layout.cells.end);
692 assert_eq!(layout.cursor_overlays.len(), CURSOR_OVERLAY_SLOTS);
693 assert_eq!(layout.separators.start, layout.cursor_overlays.end);
694 assert_eq!(layout.separators.len(), rows);
695 assert_eq!(layout.gutters.start, layout.separators.end);
696 assert_eq!(layout.gutters.len(), rows);
697 assert_eq!(
698 layout.bg_instances(),
699 cols * rows + CURSOR_OVERLAY_SLOTS + rows + rows
700 );
701 }
702
703 #[test]
706 fn single_grid_overlays_do_not_overlap_the_cells() {
707 let layout = SingleGridLayout::new(80, 24);
708 let ranges = ThreePhaseRanges::for_single_grid(&layout, layout.bg_instances(), 3840);
709
710 assert!(ranges.bg.end <= ranges.cursor_overlays.start);
711 assert!(ranges.extra_bg.start >= ranges.cursor_overlays.end);
712 assert_eq!(ranges.cursor_overlays.len(), CURSOR_OVERLAY_SLOTS);
713 }
714
715 #[test]
718 fn an_unbuilt_single_grid_yields_an_empty_extra_phase() {
719 let layout = SingleGridLayout::new(80, 24);
720 let ranges = ThreePhaseRanges::for_single_grid(&layout, 0, 0);
721 assert!(ranges.extra_bg.is_empty());
722 assert!(ranges.extra_bg.start <= ranges.extra_bg.end);
723 }
724
725 #[test]
728 fn a_pane_packs_its_overlays_directly_after_its_backgrounds() {
729 let ranges = pane_ranges(500, 120, CURSOR_OVERLAY_SLOTS);
730
731 assert_eq!(ranges.bg, 500..620);
732 assert_eq!(ranges.cursor_overlays.start, ranges.bg.end);
733 assert!(
734 ranges.extra_bg.is_empty(),
735 "the pane path has nothing to draw after its overlays"
736 );
737 }
738
739 #[test]
742 fn a_pane_without_a_cursor_has_an_empty_overlay_phase() {
743 let ranges = pane_ranges(0, 60, 0);
744 let sequence = ranges.draw_sequence();
745 let drawn: Vec<CellDrawPhase> = sequence
746 .iter()
747 .filter(|(_, range)| !range.is_empty())
748 .map(|(phase, _)| *phase)
749 .collect();
750 assert_eq!(drawn, vec![CellDrawPhase::Background, CellDrawPhase::Text]);
751 }
752
753 #[test]
761 fn a_shaded_pane_with_no_background_quads_still_draws_overlays_last() {
762 let ranges = ThreePhaseRanges::for_pane(300..300, 300..300 + CURSOR_OVERLAY_SLOTS, 90..140);
763 assert!(ranges.bg.is_empty(), "the shader path emits no bg quads");
764
765 let sequence = ranges.draw_sequence();
766 let drawn: Vec<CellDrawPhase> = sequence
767 .iter()
768 .filter(|(_, range)| !range.is_empty())
769 .map(|(phase, _)| *phase)
770 .collect();
771 assert_eq!(
772 drawn,
773 vec![CellDrawPhase::Text, CellDrawPhase::CursorOverlays],
774 "the overlays must follow the text even when nothing precedes them"
775 );
776
777 assert_ne!(
780 CellDrawPhase::Text.pipeline(),
781 CellDrawPhase::CursorOverlays.pipeline()
782 );
783 }
784}