Skip to main content

par_term_render/renderer/
render_passes.rs

1use anyhow::Result;
2
3use super::{DividerRenderInfo, PaneDividerSettings, PaneTitleInfo, Renderer};
4use crate::cell_renderer::PaneViewport;
5use crate::cell_renderer::pane_render::ATLAS_SIZE;
6
7impl Renderer {
8    /// Render pane dividers on top of pane content
9    ///
10    /// This should be called after rendering pane content but before egui.
11    ///
12    /// # Arguments
13    /// * `surface_view` - The texture view to render to
14    /// * `dividers` - List of dividers to render with hover state
15    /// * `settings` - Divider appearance settings
16    pub fn render_dividers(
17        &mut self,
18        surface_view: &wgpu::TextureView,
19        dividers: &[DividerRenderInfo],
20        settings: &PaneDividerSettings,
21    ) -> Result<()> {
22        if dividers.is_empty() {
23            return Ok(());
24        }
25
26        // Build divider instances using the cell renderer's background pipeline.
27        // We take the scratch buffer from self so the borrow checker allows us to
28        // also borrow self.cell_renderer mutably later in this method.
29        let mut instances = std::mem::take(&mut self.scratch_divider_instances);
30        instances.clear();
31
32        let w = self.size.width as f32;
33        let h = self.size.height as f32;
34
35        for divider in dividers {
36            let color = if divider.hovered {
37                settings.hover_color
38            } else {
39                settings.divider_color
40            };
41
42            use par_term_config::DividerStyle;
43            match settings.divider_style {
44                DividerStyle::Solid => {
45                    let x_ndc = divider.x / w * 2.0 - 1.0;
46                    let y_ndc = 1.0 - (divider.y / h * 2.0);
47                    let w_ndc = divider.width / w * 2.0;
48                    let h_ndc = divider.height / h * 2.0;
49
50                    instances.push(crate::cell_renderer::types::BackgroundInstance {
51                        position: [x_ndc, y_ndc],
52                        size: [w_ndc, h_ndc],
53                        color: [color[0], color[1], color[2], 1.0],
54                    });
55                }
56                DividerStyle::Double => {
57                    // Two parallel lines with a visible gap between them
58                    let is_horizontal = divider.width > divider.height;
59                    let thickness = if is_horizontal {
60                        divider.height
61                    } else {
62                        divider.width
63                    };
64
65                    if thickness >= 4.0 {
66                        // Enough space for two 1px lines with visible gap
67                        if is_horizontal {
68                            // Top line
69                            instances.push(crate::cell_renderer::types::BackgroundInstance {
70                                position: [divider.x / w * 2.0 - 1.0, 1.0 - (divider.y / h * 2.0)],
71                                size: [divider.width / w * 2.0, 1.0 / h * 2.0],
72                                color: [color[0], color[1], color[2], 1.0],
73                            });
74                            // Bottom line (gap in between shows background)
75                            let bottom_y = divider.y + divider.height - 1.0;
76                            instances.push(crate::cell_renderer::types::BackgroundInstance {
77                                position: [divider.x / w * 2.0 - 1.0, 1.0 - (bottom_y / h * 2.0)],
78                                size: [divider.width / w * 2.0, 1.0 / h * 2.0],
79                                color: [color[0], color[1], color[2], 1.0],
80                            });
81                        } else {
82                            // Left line
83                            instances.push(crate::cell_renderer::types::BackgroundInstance {
84                                position: [divider.x / w * 2.0 - 1.0, 1.0 - (divider.y / h * 2.0)],
85                                size: [1.0 / w * 2.0, divider.height / h * 2.0],
86                                color: [color[0], color[1], color[2], 1.0],
87                            });
88                            // Right line
89                            let right_x = divider.x + divider.width - 1.0;
90                            instances.push(crate::cell_renderer::types::BackgroundInstance {
91                                position: [right_x / w * 2.0 - 1.0, 1.0 - (divider.y / h * 2.0)],
92                                size: [1.0 / w * 2.0, divider.height / h * 2.0],
93                                color: [color[0], color[1], color[2], 1.0],
94                            });
95                        }
96                    } else {
97                        // Divider too thin for double lines — render centered 1px line
98                        // (visibly thinner than Solid to differentiate)
99                        if is_horizontal {
100                            let center_y = divider.y + (divider.height - 1.0) / 2.0;
101                            instances.push(crate::cell_renderer::types::BackgroundInstance {
102                                position: [divider.x / w * 2.0 - 1.0, 1.0 - (center_y / h * 2.0)],
103                                size: [divider.width / w * 2.0, 1.0 / h * 2.0],
104                                color: [color[0], color[1], color[2], 1.0],
105                            });
106                        } else {
107                            let center_x = divider.x + (divider.width - 1.0) / 2.0;
108                            instances.push(crate::cell_renderer::types::BackgroundInstance {
109                                position: [center_x / w * 2.0 - 1.0, 1.0 - (divider.y / h * 2.0)],
110                                size: [1.0 / w * 2.0, divider.height / h * 2.0],
111                                color: [color[0], color[1], color[2], 1.0],
112                            });
113                        }
114                    }
115                }
116                DividerStyle::Dashed => {
117                    // Dashed line effect using segments
118                    let is_horizontal = divider.width > divider.height;
119                    let dash_len: f32 = 6.0;
120                    let gap_len: f32 = 4.0;
121
122                    if is_horizontal {
123                        let mut x = divider.x;
124                        while x < divider.x + divider.width {
125                            let seg_w = dash_len.min(divider.x + divider.width - x);
126                            instances.push(crate::cell_renderer::types::BackgroundInstance {
127                                position: [x / w * 2.0 - 1.0, 1.0 - (divider.y / h * 2.0)],
128                                size: [seg_w / w * 2.0, divider.height / h * 2.0],
129                                color: [color[0], color[1], color[2], 1.0],
130                            });
131                            x += dash_len + gap_len;
132                        }
133                    } else {
134                        let mut y = divider.y;
135                        while y < divider.y + divider.height {
136                            let seg_h = dash_len.min(divider.y + divider.height - y);
137                            instances.push(crate::cell_renderer::types::BackgroundInstance {
138                                position: [divider.x / w * 2.0 - 1.0, 1.0 - (y / h * 2.0)],
139                                size: [divider.width / w * 2.0, seg_h / h * 2.0],
140                                color: [color[0], color[1], color[2], 1.0],
141                            });
142                            y += dash_len + gap_len;
143                        }
144                    }
145                }
146                DividerStyle::Shadow => {
147                    // Beveled/embossed effect — all rendering stays within divider bounds
148                    // Highlight on top/left edge, shadow on bottom/right edge
149                    let is_horizontal = divider.width > divider.height;
150                    let thickness = if is_horizontal {
151                        divider.height
152                    } else {
153                        divider.width
154                    };
155
156                    // Brighter highlight color
157                    let highlight = [
158                        (color[0] + 0.3).min(1.0),
159                        (color[1] + 0.3).min(1.0),
160                        (color[2] + 0.3).min(1.0),
161                        1.0,
162                    ];
163                    // Darker shadow color
164                    let shadow = [(color[0] * 0.3), (color[1] * 0.3), (color[2] * 0.3), 1.0];
165
166                    if thickness >= 3.0 {
167                        // 3+ px: highlight line / main body / shadow line
168                        let edge = 1.0_f32;
169                        if is_horizontal {
170                            // Top highlight
171                            instances.push(crate::cell_renderer::types::BackgroundInstance {
172                                position: [divider.x / w * 2.0 - 1.0, 1.0 - (divider.y / h * 2.0)],
173                                size: [divider.width / w * 2.0, edge / h * 2.0],
174                                color: highlight,
175                            });
176                            // Main body (middle portion)
177                            let body_y = divider.y + edge;
178                            let body_h = divider.height - edge * 2.0;
179                            if body_h > 0.0 {
180                                instances.push(crate::cell_renderer::types::BackgroundInstance {
181                                    position: [divider.x / w * 2.0 - 1.0, 1.0 - (body_y / h * 2.0)],
182                                    size: [divider.width / w * 2.0, body_h / h * 2.0],
183                                    color: [color[0], color[1], color[2], 1.0],
184                                });
185                            }
186                            // Bottom shadow
187                            let shadow_y = divider.y + divider.height - edge;
188                            instances.push(crate::cell_renderer::types::BackgroundInstance {
189                                position: [divider.x / w * 2.0 - 1.0, 1.0 - (shadow_y / h * 2.0)],
190                                size: [divider.width / w * 2.0, edge / h * 2.0],
191                                color: shadow,
192                            });
193                        } else {
194                            // Left highlight
195                            instances.push(crate::cell_renderer::types::BackgroundInstance {
196                                position: [divider.x / w * 2.0 - 1.0, 1.0 - (divider.y / h * 2.0)],
197                                size: [edge / w * 2.0, divider.height / h * 2.0],
198                                color: highlight,
199                            });
200                            // Main body
201                            let body_x = divider.x + edge;
202                            let body_w = divider.width - edge * 2.0;
203                            if body_w > 0.0 {
204                                instances.push(crate::cell_renderer::types::BackgroundInstance {
205                                    position: [body_x / w * 2.0 - 1.0, 1.0 - (divider.y / h * 2.0)],
206                                    size: [body_w / w * 2.0, divider.height / h * 2.0],
207                                    color: [color[0], color[1], color[2], 1.0],
208                                });
209                            }
210                            // Right shadow
211                            let shadow_x = divider.x + divider.width - edge;
212                            instances.push(crate::cell_renderer::types::BackgroundInstance {
213                                position: [shadow_x / w * 2.0 - 1.0, 1.0 - (divider.y / h * 2.0)],
214                                size: [edge / w * 2.0, divider.height / h * 2.0],
215                                color: shadow,
216                            });
217                        }
218                    } else {
219                        // 2px or less: top/left half highlight, bottom/right half shadow
220                        if is_horizontal {
221                            let half = (divider.height / 2.0).max(1.0);
222                            instances.push(crate::cell_renderer::types::BackgroundInstance {
223                                position: [divider.x / w * 2.0 - 1.0, 1.0 - (divider.y / h * 2.0)],
224                                size: [divider.width / w * 2.0, half / h * 2.0],
225                                color: highlight,
226                            });
227                            let bottom_y = divider.y + half;
228                            let bottom_h = divider.height - half;
229                            if bottom_h > 0.0 {
230                                instances.push(crate::cell_renderer::types::BackgroundInstance {
231                                    position: [
232                                        divider.x / w * 2.0 - 1.0,
233                                        1.0 - (bottom_y / h * 2.0),
234                                    ],
235                                    size: [divider.width / w * 2.0, bottom_h / h * 2.0],
236                                    color: shadow,
237                                });
238                            }
239                        } else {
240                            let half = (divider.width / 2.0).max(1.0);
241                            instances.push(crate::cell_renderer::types::BackgroundInstance {
242                                position: [divider.x / w * 2.0 - 1.0, 1.0 - (divider.y / h * 2.0)],
243                                size: [half / w * 2.0, divider.height / h * 2.0],
244                                color: highlight,
245                            });
246                            let right_x = divider.x + half;
247                            let right_w = divider.width - half;
248                            if right_w > 0.0 {
249                                instances.push(crate::cell_renderer::types::BackgroundInstance {
250                                    position: [
251                                        right_x / w * 2.0 - 1.0,
252                                        1.0 - (divider.y / h * 2.0),
253                                    ],
254                                    size: [right_w / w * 2.0, divider.height / h * 2.0],
255                                    color: shadow,
256                                });
257                            }
258                        }
259                    }
260                }
261            }
262        }
263
264        // Write instances to GPU buffer
265        self.cell_renderer.queue().write_buffer(
266            &self.cell_renderer.buffers.bg_instance_buffer,
267            0,
268            bytemuck::cast_slice(&instances),
269        );
270
271        // Render dividers
272        let mut encoder =
273            self.cell_renderer
274                .device()
275                .create_command_encoder(&wgpu::CommandEncoderDescriptor {
276                    label: Some("divider render encoder"),
277                });
278
279        {
280            let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
281                label: Some("divider render pass"),
282                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
283                    view: surface_view,
284                    resolve_target: None,
285                    ops: wgpu::Operations {
286                        load: wgpu::LoadOp::Load, // Don't clear - render on top
287                        store: wgpu::StoreOp::Store,
288                    },
289                    depth_slice: None,
290                })],
291                depth_stencil_attachment: None,
292                timestamp_writes: None,
293                occlusion_query_set: None,
294            });
295
296            render_pass.set_pipeline(&self.cell_renderer.pipelines.bg_pipeline);
297            render_pass.set_vertex_buffer(0, self.cell_renderer.buffers.vertex_buffer.slice(..));
298            render_pass
299                .set_vertex_buffer(1, self.cell_renderer.buffers.bg_instance_buffer.slice(..));
300            render_pass.draw(0..4, 0..instances.len() as u32);
301        }
302
303        self.cell_renderer
304            .queue()
305            .submit(std::iter::once(encoder.finish()));
306
307        // Restore the scratch buffer so its capacity is retained for the next frame.
308        instances.clear();
309        self.scratch_divider_instances = instances;
310
311        Ok(())
312    }
313
314    /// Render focus indicator around a pane
315    ///
316    /// This draws a colored border around the focused pane to highlight it.
317    ///
318    /// # Arguments
319    /// * `surface_view` - The texture view to render to
320    /// * `viewport` - The focused pane's viewport
321    /// * `settings` - Divider/focus settings
322    pub fn render_focus_indicator(
323        &mut self,
324        surface_view: &wgpu::TextureView,
325        viewport: &PaneViewport,
326        settings: &PaneDividerSettings,
327    ) -> Result<()> {
328        if !settings.show_focus_indicator {
329            return Ok(());
330        }
331
332        let border_w = settings.focus_width;
333        let color = [
334            settings.focus_color[0],
335            settings.focus_color[1],
336            settings.focus_color[2],
337            1.0,
338        ];
339
340        // Create 4 border rectangles (top, bottom, left, right)
341        let instances = vec![
342            // Top border
343            crate::cell_renderer::types::BackgroundInstance {
344                position: [
345                    viewport.x / self.size.width as f32 * 2.0 - 1.0,
346                    1.0 - (viewport.y / self.size.height as f32 * 2.0),
347                ],
348                size: [
349                    viewport.width / self.size.width as f32 * 2.0,
350                    border_w / self.size.height as f32 * 2.0,
351                ],
352                color,
353            },
354            // Bottom border
355            crate::cell_renderer::types::BackgroundInstance {
356                position: [
357                    viewport.x / self.size.width as f32 * 2.0 - 1.0,
358                    1.0 - ((viewport.y + viewport.height - border_w) / self.size.height as f32
359                        * 2.0),
360                ],
361                size: [
362                    viewport.width / self.size.width as f32 * 2.0,
363                    border_w / self.size.height as f32 * 2.0,
364                ],
365                color,
366            },
367            // Left border (between top and bottom)
368            crate::cell_renderer::types::BackgroundInstance {
369                position: [
370                    viewport.x / self.size.width as f32 * 2.0 - 1.0,
371                    1.0 - ((viewport.y + border_w) / self.size.height as f32 * 2.0),
372                ],
373                size: [
374                    border_w / self.size.width as f32 * 2.0,
375                    (viewport.height - border_w * 2.0) / self.size.height as f32 * 2.0,
376                ],
377                color,
378            },
379            // Right border (between top and bottom)
380            crate::cell_renderer::types::BackgroundInstance {
381                position: [
382                    (viewport.x + viewport.width - border_w) / self.size.width as f32 * 2.0 - 1.0,
383                    1.0 - ((viewport.y + border_w) / self.size.height as f32 * 2.0),
384                ],
385                size: [
386                    border_w / self.size.width as f32 * 2.0,
387                    (viewport.height - border_w * 2.0) / self.size.height as f32 * 2.0,
388                ],
389                color,
390            },
391        ];
392
393        // Write instances to GPU buffer
394        self.cell_renderer.queue().write_buffer(
395            &self.cell_renderer.buffers.bg_instance_buffer,
396            0,
397            bytemuck::cast_slice(&instances),
398        );
399
400        // Render focus indicator
401        let mut encoder =
402            self.cell_renderer
403                .device()
404                .create_command_encoder(&wgpu::CommandEncoderDescriptor {
405                    label: Some("focus indicator encoder"),
406                });
407
408        {
409            let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
410                label: Some("focus indicator pass"),
411                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
412                    view: surface_view,
413                    resolve_target: None,
414                    ops: wgpu::Operations {
415                        load: wgpu::LoadOp::Load, // Don't clear - render on top
416                        store: wgpu::StoreOp::Store,
417                    },
418                    depth_slice: None,
419                })],
420                depth_stencil_attachment: None,
421                timestamp_writes: None,
422                occlusion_query_set: None,
423            });
424
425            render_pass.set_pipeline(&self.cell_renderer.pipelines.bg_pipeline);
426            render_pass.set_vertex_buffer(0, self.cell_renderer.buffers.vertex_buffer.slice(..));
427            render_pass
428                .set_vertex_buffer(1, self.cell_renderer.buffers.bg_instance_buffer.slice(..));
429            render_pass.draw(0..4, 0..instances.len() as u32);
430        }
431
432        self.cell_renderer
433            .queue()
434            .submit(std::iter::once(encoder.finish()));
435        Ok(())
436    }
437
438    /// Render pane title bars (background rectangles + text)
439    ///
440    /// Title bars are rendered on top of pane content and dividers.
441    /// Each title bar consists of a colored background rectangle and centered text.
442    pub fn render_pane_titles(
443        &mut self,
444        surface_view: &wgpu::TextureView,
445        titles: &[PaneTitleInfo],
446    ) -> Result<()> {
447        if titles.is_empty() {
448            return Ok(());
449        }
450
451        let width = self.size.width as f32;
452        let height = self.size.height as f32;
453
454        // Phase 1: Render title bar backgrounds
455        let mut bg_instances = Vec::with_capacity(titles.len());
456        for title in titles {
457            let x_ndc = title.x / width * 2.0 - 1.0;
458            let y_ndc = 1.0 - (title.y / height * 2.0);
459            let w_ndc = title.width / width * 2.0;
460            let h_ndc = title.height / height * 2.0;
461
462            // Title bar must be fully opaque (alpha=1.0) to cover the background.
463            // Differentiate focused/unfocused by lightening/darkening the color.
464            let brightness = if title.focused { 1.0 } else { 0.7 };
465
466            bg_instances.push(crate::cell_renderer::types::BackgroundInstance {
467                position: [x_ndc, y_ndc],
468                size: [w_ndc, h_ndc],
469                color: [
470                    title.bg_color[0] * brightness,
471                    title.bg_color[1] * brightness,
472                    title.bg_color[2] * brightness,
473                    1.0, // Always fully opaque
474                ],
475            });
476        }
477
478        // Write background instances to GPU buffer
479        self.cell_renderer.queue().write_buffer(
480            &self.cell_renderer.buffers.bg_instance_buffer,
481            0,
482            bytemuck::cast_slice(&bg_instances),
483        );
484
485        // Render title backgrounds
486        let mut encoder =
487            self.cell_renderer
488                .device()
489                .create_command_encoder(&wgpu::CommandEncoderDescriptor {
490                    label: Some("pane title bg encoder"),
491                });
492
493        {
494            let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
495                label: Some("pane title bg pass"),
496                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
497                    view: surface_view,
498                    resolve_target: None,
499                    ops: wgpu::Operations {
500                        load: wgpu::LoadOp::Load,
501                        store: wgpu::StoreOp::Store,
502                    },
503                    depth_slice: None,
504                })],
505                depth_stencil_attachment: None,
506                timestamp_writes: None,
507                occlusion_query_set: None,
508            });
509
510            render_pass.set_pipeline(&self.cell_renderer.pipelines.bg_pipeline);
511            render_pass.set_vertex_buffer(0, self.cell_renderer.buffers.vertex_buffer.slice(..));
512            render_pass
513                .set_vertex_buffer(1, self.cell_renderer.buffers.bg_instance_buffer.slice(..));
514            render_pass.draw(0..4, 0..bg_instances.len() as u32);
515        }
516
517        self.cell_renderer
518            .queue()
519            .submit(std::iter::once(encoder.finish()));
520
521        // Phase 2: Render title text using glyph atlas
522        let mut text_instances = Vec::new();
523        let baseline_y = self.cell_renderer.font.font_ascent;
524
525        for title in titles {
526            let title_text = &title.title;
527            if title_text.is_empty() {
528                continue;
529            }
530
531            // Calculate starting X position (centered in title bar with left padding)
532            let padding_x = 8.0;
533            let mut x_pos = title.x + padding_x;
534            let y_base = title.y + (title.height - self.cell_renderer.grid.cell_height) / 2.0;
535
536            let text_color = [
537                title.text_color[0],
538                title.text_color[1],
539                title.text_color[2],
540                if title.focused { 1.0 } else { 0.8 },
541            ];
542
543            // Truncate title if it would overflow the title bar
544            let max_chars =
545                ((title.width - padding_x * 2.0) / self.cell_renderer.grid.cell_width) as usize;
546            let display_text: String = if title_text.len() > max_chars && max_chars > 3 {
547                let truncated: String = title_text.chars().take(max_chars - 1).collect();
548                format!("{}\u{2026}", truncated) // ellipsis
549            } else {
550                title_text.clone()
551            };
552
553            for ch in display_text.chars() {
554                if x_pos >= title.x + title.width - padding_x {
555                    break;
556                }
557
558                if let Some((font_idx, glyph_id)) =
559                    self.cell_renderer.font_manager.find_glyph(ch, false, false)
560                {
561                    let cache_key = ((font_idx as u64) << 32) | (glyph_id as u64);
562                    // Check if this character should be rendered as a monochrome symbol
563                    let force_monochrome = crate::cell_renderer::atlas::should_render_as_symbol(ch);
564                    let info = if self
565                        .cell_renderer
566                        .atlas
567                        .glyph_cache
568                        .contains_key(&cache_key)
569                    {
570                        self.cell_renderer.lru_remove(cache_key);
571                        self.cell_renderer.lru_push_front(cache_key);
572                        self.cell_renderer
573                            .atlas
574                            .glyph_cache
575                            .get(&cache_key)
576                            .expect("Glyph cache entry must exist after contains_key check")
577                            .clone()
578                    } else if let Some(raster) =
579                        self.cell_renderer
580                            .rasterize_glyph(font_idx, glyph_id, force_monochrome)
581                    {
582                        let info = self.cell_renderer.upload_glyph(cache_key, &raster);
583                        self.cell_renderer
584                            .atlas
585                            .glyph_cache
586                            .insert(cache_key, info.clone());
587                        self.cell_renderer.lru_push_front(cache_key);
588                        info
589                    } else {
590                        x_pos += self.cell_renderer.grid.cell_width;
591                        continue;
592                    };
593
594                    let glyph_left = x_pos + info.bearing_x;
595                    let glyph_top = y_base + (baseline_y - info.bearing_y);
596
597                    text_instances.push(crate::cell_renderer::types::TextInstance {
598                        position: [
599                            glyph_left / width * 2.0 - 1.0,
600                            1.0 - (glyph_top / height * 2.0),
601                        ],
602                        size: [
603                            info.width as f32 / width * 2.0,
604                            info.height as f32 / height * 2.0,
605                        ],
606                        tex_offset: [info.x as f32 / ATLAS_SIZE, info.y as f32 / ATLAS_SIZE],
607                        tex_size: [
608                            info.width as f32 / ATLAS_SIZE,
609                            info.height as f32 / ATLAS_SIZE,
610                        ],
611                        color: text_color,
612                        is_colored: if info.is_colored { 1 } else { 0 },
613                    });
614                }
615
616                x_pos += self.cell_renderer.grid.cell_width;
617            }
618        }
619
620        if text_instances.is_empty() {
621            return Ok(());
622        }
623
624        // Write text instances to GPU buffer
625        self.cell_renderer.queue().write_buffer(
626            &self.cell_renderer.buffers.text_instance_buffer,
627            0,
628            bytemuck::cast_slice(&text_instances),
629        );
630
631        // Render title text
632        let mut encoder =
633            self.cell_renderer
634                .device()
635                .create_command_encoder(&wgpu::CommandEncoderDescriptor {
636                    label: Some("pane title text encoder"),
637                });
638
639        {
640            let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
641                label: Some("pane title text pass"),
642                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
643                    view: surface_view,
644                    resolve_target: None,
645                    ops: wgpu::Operations {
646                        load: wgpu::LoadOp::Load,
647                        store: wgpu::StoreOp::Store,
648                    },
649                    depth_slice: None,
650                })],
651                depth_stencil_attachment: None,
652                timestamp_writes: None,
653                occlusion_query_set: None,
654            });
655
656            render_pass.set_pipeline(&self.cell_renderer.pipelines.text_pipeline);
657            render_pass.set_bind_group(0, &self.cell_renderer.pipelines.text_bind_group, &[]);
658            render_pass.set_vertex_buffer(0, self.cell_renderer.buffers.vertex_buffer.slice(..));
659            render_pass
660                .set_vertex_buffer(1, self.cell_renderer.buffers.text_instance_buffer.slice(..));
661            render_pass.draw(0..4, 0..text_instances.len() as u32);
662        }
663
664        self.cell_renderer
665            .queue()
666            .submit(std::iter::once(encoder.finish()));
667
668        Ok(())
669    }
670}