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}