Skip to main content

par_term_render/cell_renderer/pane_render/
render_to_view.rs

1//! Pane surface rendering entry points.
2//!
3//! ARC-004: rendering a pane is split into two phases.
4//!
5//! * [`CellRenderer::prepare_pane`] does everything that needs `&mut self` — building
6//!   the pane's instances into its own slice of the shared buffers, updating its
7//!   scrollbar slot, and preparing its background-image bind group — and returns the
8//!   absolute draw ranges as a [`PreparedPane`].
9//! * [`CellRenderer::draw_prepared_panes`] takes `&self`, and turns a whole frame's
10//!   worth of prepared panes into **one** command encoder with one render pass per
11//!   pane, submitted once.
12//!
13//! The phases have to stay separate: the draw phase holds a render pass borrowed
14//! from the encoder, so it cannot allocate or write anything. Every write is queued
15//! before the single submit, which is exactly why each pane needs its own instance
16//! range, scrollbar slot, and background uniform — one submit no longer separates
17//! one pane's writes from the next's.
18
19use super::super::CellRenderer;
20use super::super::render::ThreePhaseRanges;
21use super::{PaneInstanceBuildParams, PaneRenderViewParams};
22use anyhow::Result;
23
24/// One pane's GPU state for the frame, ready to be drawn.
25pub(crate) struct PreparedPane {
26    /// Scissor rect clipping the pane to its viewport.
27    scissor: (u32, u32, u32, u32),
28    /// `Some(color)` clears the pane's region before drawing; `None` loads it.
29    clear_color: Option<wgpu::Color>,
30    /// Key into `bg_state.pane_bg_uniform_cache` when this pane has a background image.
31    pane_bg_index: Option<usize>,
32    /// Absolute instance ranges for the three-phase cell draw.
33    ranges: ThreePhaseRanges,
34    /// Scrollbar slot to draw after the cells, if this pane shows one.
35    scrollbar_slot: Option<usize>,
36}
37
38impl CellRenderer {
39    /// Build one pane's GPU state for this frame.
40    ///
41    /// `pane_index` identifies the pane within the current batch and keys both its
42    /// scrollbar slot and its background-image uniform. Callers must have started
43    /// the batch with [`CellRenderer::begin_pane_batch`] and must pass indices
44    /// `0..n` in order.
45    ///
46    /// # Arguments
47    /// * `viewport` - The pane's viewport (position, size, focus state, opacity)
48    /// * `cells` - The cells to render (should match viewport grid size)
49    /// * `cols` / `rows` - The pane's cell grid dimensions
50    /// * `cursor_pos` - Cursor position (col, row) within this pane, or None if no cursor
51    /// * `cursor_opacity` - Cursor opacity (0.0 = hidden, 1.0 = fully visible)
52    /// * `show_scrollbar` - Whether to render the scrollbar for this pane
53    /// * `clear_first` - If true, clears the viewport region before rendering
54    /// * `skip_background_image` - If true, skip rendering the background image. Use this
55    ///   when the background image has already been rendered full-screen (for split panes).
56    pub(crate) fn prepare_pane(
57        &mut self,
58        pane_index: usize,
59        p: PaneRenderViewParams<'_>,
60    ) -> Result<PreparedPane> {
61        let PaneRenderViewParams {
62            viewport,
63            cells,
64            cols,
65            rows,
66            cursor_pos,
67            cursor_opacity,
68            show_scrollbar,
69            clear_first,
70            skip_background_image,
71            fill_default_bg_cells,
72            separator_marks,
73            pane_background,
74        } = p;
75        // Build this pane's slice of the shared instance buffers. The returned
76        // ranges are absolute, so several panes stay resident at once.
77        let ranges = self.build_pane_instance_buffers(PaneInstanceBuildParams {
78            viewport,
79            cells,
80            cols,
81            rows,
82            cursor_pos,
83            cursor_opacity,
84            skip_solid_background: skip_background_image,
85            fill_default_bg_cells,
86            separator_marks,
87        })?;
88
89        // Pre-update per-pane background uniform buffer and bind group if needed (must happen
90        // before the render pass). Buffers are allocated once and reused across frames.
91        // Per-pane backgrounds are explicit user overrides and always prepared, even when a
92        // custom shader or global background would normally be skipped.
93        let pane_bg_index = if let Some(pane_bg) = pane_background
94            && let Some(ref path) = pane_bg.image_path
95            && self.bg_state.pane_bg_cache.contains_key(path.as_str())
96        {
97            self.prepare_pane_bg_bind_group(
98                pane_index,
99                path.as_str(),
100                super::super::background::PaneBgBindGroupParams {
101                    pane_x: viewport.x,
102                    pane_y: viewport.y,
103                    pane_width: viewport.width,
104                    pane_height: viewport.height,
105                    mode: pane_bg.mode,
106                    opacity: pane_bg.opacity,
107                    darken: pane_bg.darken,
108                },
109            );
110            Some(pane_index)
111        } else {
112            None
113        };
114
115        // Determine load operation and clear color
116        let clear_color = clear_first.then(|| {
117            let base: [f32; 3] = if self.bg_state.bg_is_solid_color {
118                self.bg_state.solid_bg_color
119            } else {
120                [
121                    self.background_color[0],
122                    self.background_color[1],
123                    self.background_color[2],
124                ]
125            };
126            let alpha = self.window_opacity as f64 * viewport.opacity as f64;
127            wgpu::Color {
128                r: base[0] as f64 * alpha,
129                g: base[1] as f64 * alpha,
130                b: base[2] as f64 * alpha,
131                a: alpha,
132            }
133        });
134
135        Ok(PreparedPane {
136            scissor: viewport.to_scissor_rect(),
137            clear_color,
138            pane_bg_index,
139            ranges: ThreePhaseRanges::for_pane(ranges.bg, ranges.cursor_overlays, ranges.text),
140            scrollbar_slot: show_scrollbar.then_some(pane_index),
141        })
142    }
143
144    /// Draw a frame's prepared panes to `surface_view` from a single command encoder.
145    ///
146    /// One render pass per pane keeps each pane's scissor rect scoped to its own
147    /// pass; passes run in order and load the target, so the result is identical to
148    /// the per-pane submits this replaces, minus N-1 submits per frame.
149    pub(crate) fn draw_prepared_panes(
150        &self,
151        surface_view: &wgpu::TextureView,
152        panes: &[PreparedPane],
153    ) {
154        if panes.is_empty() {
155            return;
156        }
157
158        let mut encoder = self
159            .device
160            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
161                label: Some("pane batch encoder"),
162            });
163
164        for pane in panes {
165            let load = match pane.clear_color {
166                Some(color) => wgpu::LoadOp::Clear(color),
167                None => wgpu::LoadOp::Load,
168            };
169
170            let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
171                label: Some("pane render pass"),
172                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
173                    view: surface_view,
174                    resolve_target: None,
175                    ops: wgpu::Operations {
176                        load,
177                        store: wgpu::StoreOp::Store,
178                    },
179                    depth_slice: None,
180                })],
181                depth_stencil_attachment: None,
182                timestamp_writes: None,
183                occlusion_query_set: None,
184                multiview_mask: None,
185            });
186
187            // Set scissor rect to clip rendering to pane bounds
188            let (sx, sy, sw, sh) = pane.scissor;
189            render_pass.set_scissor_rect(sx, sy, sw, sh);
190
191            // Render per-pane background image within scissor rect.
192            // Per-pane backgrounds are explicit user overrides and always render,
193            // even when a custom shader or global background is active.
194            if let Some(index) = pane.pane_bg_index
195                && let Some(cached) = self.bg_state.pane_bg_uniform_cache.get(&index)
196            {
197                render_pass.set_pipeline(&self.pipelines.bg_image_pipeline);
198                render_pass.set_bind_group(0, &cached.bind_group, &[]);
199                render_pass.set_vertex_buffer(0, self.buffers.vertex_buffer.slice(..));
200                render_pass.draw(0..4, 0..1);
201            }
202
203            self.emit_three_phase_draw_calls(&mut render_pass, &pane.ranges);
204
205            // Render scrollbar if requested (uses its own scissor rect internally)
206            if let Some(slot) = pane.scrollbar_slot {
207                // Reset scissor to full surface for scrollbar
208                render_pass.set_scissor_rect(0, 0, self.config.width, self.config.height);
209                self.scrollbar.render(&mut render_pass, slot);
210            }
211        }
212
213        self.queue.submit(std::iter::once(encoder.finish()));
214    }
215
216    /// Render a single pane's content within a viewport to an existing surface texture.
217    ///
218    /// Convenience wrapper over [`CellRenderer::prepare_pane`] +
219    /// [`CellRenderer::draw_prepared_panes`] for callers that render one pane on its
220    /// own. Multi-pane frames should drive the two phases directly so the whole
221    /// frame shares one encoder.
222    pub fn render_pane_to_view(
223        &mut self,
224        surface_view: &wgpu::TextureView,
225        p: PaneRenderViewParams<'_>,
226    ) -> Result<()> {
227        let (bg, text) = super::pane_instance_capacity(p.cols, p.rows);
228        self.begin_pane_batch(bg, text);
229        let prepared = self.prepare_pane(0, p)?;
230        self.draw_prepared_panes(surface_view, std::slice::from_ref(&prepared));
231        Ok(())
232    }
233}