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            let shade = alpha * viewport.brightness as f64;
128            wgpu::Color {
129                r: base[0] as f64 * shade,
130                g: base[1] as f64 * shade,
131                b: base[2] as f64 * shade,
132                a: alpha,
133            }
134        });
135
136        Ok(PreparedPane {
137            scissor: viewport.to_scissor_rect(),
138            clear_color,
139            pane_bg_index,
140            ranges: ThreePhaseRanges::for_pane(ranges.bg, ranges.cursor_overlays, ranges.text),
141            scrollbar_slot: show_scrollbar.then_some(pane_index),
142        })
143    }
144
145    /// Draw a frame's prepared panes to `surface_view` from a single command encoder.
146    ///
147    /// One render pass per pane keeps each pane's scissor rect scoped to its own
148    /// pass; passes run in order and load the target, so the result is identical to
149    /// the per-pane submits this replaces, minus N-1 submits per frame.
150    pub(crate) fn draw_prepared_panes(
151        &self,
152        surface_view: &wgpu::TextureView,
153        panes: &[PreparedPane],
154    ) {
155        if panes.is_empty() {
156            return;
157        }
158
159        let mut encoder = self
160            .device
161            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
162                label: Some("pane batch encoder"),
163            });
164
165        for pane in panes {
166            let load = match pane.clear_color {
167                Some(color) => wgpu::LoadOp::Clear(color),
168                None => wgpu::LoadOp::Load,
169            };
170
171            let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
172                label: Some("pane render pass"),
173                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
174                    view: surface_view,
175                    resolve_target: None,
176                    ops: wgpu::Operations {
177                        load,
178                        store: wgpu::StoreOp::Store,
179                    },
180                    depth_slice: None,
181                })],
182                depth_stencil_attachment: None,
183                timestamp_writes: None,
184                occlusion_query_set: None,
185                multiview_mask: None,
186            });
187
188            // Set scissor rect to clip rendering to pane bounds
189            let (sx, sy, sw, sh) = pane.scissor;
190            render_pass.set_scissor_rect(sx, sy, sw, sh);
191
192            // Render per-pane background image within scissor rect.
193            // Per-pane backgrounds are explicit user overrides and always render,
194            // even when a custom shader or global background is active.
195            if let Some(index) = pane.pane_bg_index
196                && let Some(cached) = self.bg_state.pane_bg_uniform_cache.get(&index)
197            {
198                render_pass.set_pipeline(&self.pipelines.bg_image_pipeline);
199                render_pass.set_bind_group(0, &cached.bind_group, &[]);
200                render_pass.set_vertex_buffer(0, self.buffers.vertex_buffer.slice(..));
201                render_pass.draw(0..4, 0..1);
202            }
203
204            self.emit_three_phase_draw_calls(&mut render_pass, &pane.ranges);
205
206            // Render scrollbar if requested (uses its own scissor rect internally)
207            if let Some(slot) = pane.scrollbar_slot {
208                // Reset scissor to full surface for scrollbar
209                render_pass.set_scissor_rect(0, 0, self.config.width, self.config.height);
210                self.scrollbar.render(&mut render_pass, slot);
211            }
212        }
213
214        self.queue.submit(std::iter::once(encoder.finish()));
215    }
216
217    /// Render a single pane's content within a viewport to an existing surface texture.
218    ///
219    /// Convenience wrapper over [`CellRenderer::prepare_pane`] +
220    /// [`CellRenderer::draw_prepared_panes`] for callers that render one pane on its
221    /// own. Multi-pane frames should drive the two phases directly so the whole
222    /// frame shares one encoder.
223    pub fn render_pane_to_view(
224        &mut self,
225        surface_view: &wgpu::TextureView,
226        p: PaneRenderViewParams<'_>,
227    ) -> Result<()> {
228        let (bg, text) = super::pane_instance_capacity(p.cols, p.rows);
229        self.begin_pane_batch(bg, text);
230        let prepared = self.prepare_pane(0, p)?;
231        self.draw_prepared_panes(surface_view, std::slice::from_ref(&prepared));
232        Ok(())
233    }
234}