Skip to main content

par_term_render/renderer/
egui_render.rs

1use anyhow::Result;
2
3use super::Renderer;
4
5impl Renderer {
6    /// Upload and free egui texture deltas, then clear them so epaint 0.36
7    /// does not panic on drop.
8    ///
9    /// Must run even when the overlay is not drawn (occluded/lost surface,
10    /// skipped pane gather). Clearing without applying drops font-atlas
11    /// reallocations (`pos = None`); later frames tessellate against a new
12    /// atlas while the GPU still holds the old one, which draws garbage
13    /// chrome text.
14    pub fn apply_egui_texture_deltas(
15        &mut self,
16        egui_output: &mut egui::FullOutput,
17        egui_ctx: &egui::Context,
18    ) {
19        // Since egui 0.36 each texture id carries a SmallVec of deltas (the
20        // font atlas can deliver several patches per frame), so apply them in
21        // order.
22        for (id, image_deltas) in &egui_output.textures_delta.set {
23            for image_delta in image_deltas {
24                // egui 0.34 can deliver a partial font-atlas patch before this
25                // renderer has allocated the font texture — e.g. when an earlier
26                // frame ran egui but skipped the GPU render, dropping the full
27                // (pos = None) font upload. egui-wgpu panics on a partial update
28                // of an unallocated texture (emilk/egui#8228), so pre-allocate
29                // it with the complete current font atlas before applying the
30                // patch.
31                if image_delta.pos.is_some() && self.egui_renderer.texture(id).is_none() {
32                    let full_image = egui_ctx
33                        .fonts(|f| egui::epaint::ImageData::Color(std::sync::Arc::new(f.image())));
34                    self.egui_renderer.update_texture(
35                        self.cell_renderer.device(),
36                        self.cell_renderer.queue(),
37                        *id,
38                        &egui::epaint::ImageDelta {
39                            pos: None,
40                            image: full_image,
41                            options: image_delta.options,
42                        },
43                    );
44                }
45                self.egui_renderer.update_texture(
46                    self.cell_renderer.device(),
47                    self.cell_renderer.queue(),
48                    *id,
49                    image_delta,
50                );
51            }
52        }
53
54        for id in &egui_output.textures_delta.free {
55            self.egui_renderer.free_texture(id);
56        }
57        // epaint 0.36 panics if a consumed delta is dropped uncleared.
58        egui_output.textures_delta.clear();
59    }
60
61    /// Render egui overlay on top of the terminal
62    pub(crate) fn render_egui(
63        &mut self,
64        surface_texture: &wgpu::SurfaceTexture,
65        mut egui_output: egui::FullOutput,
66        egui_ctx: &egui::Context,
67        force_opaque: bool,
68    ) -> Result<()> {
69        use wgpu::TextureViewDescriptor;
70
71        // Create view of the surface texture
72        let view = surface_texture
73            .texture
74            .create_view(&TextureViewDescriptor::default());
75
76        // Create command encoder for egui
77        let mut encoder =
78            self.cell_renderer
79                .device()
80                .create_command_encoder(&wgpu::CommandEncoderDescriptor {
81                    label: Some("egui encoder"),
82                });
83
84        // Convert egui output to screen descriptor
85        let screen_descriptor = egui_wgpu::ScreenDescriptor {
86            size_in_pixels: [self.size.width, self.size.height],
87            pixels_per_point: egui_output.pixels_per_point,
88        };
89
90        self.apply_egui_texture_deltas(&mut egui_output, egui_ctx);
91
92        // Tessellate egui shapes into paint jobs
93        let mut paint_jobs = egui_ctx.tessellate(egui_output.shapes, egui_output.pixels_per_point);
94
95        // If requested, force all egui vertices to full opacity so UI stays solid
96        if force_opaque {
97            for job in paint_jobs.iter_mut() {
98                match &mut job.primitive {
99                    egui::epaint::Primitive::Mesh(mesh) => {
100                        for v in mesh.vertices.iter_mut() {
101                            v.color[3] = 255;
102                        }
103                    }
104                    egui::epaint::Primitive::Callback(_) => {}
105                }
106            }
107        }
108
109        // Update egui buffers
110        self.egui_renderer.update_buffers(
111            self.cell_renderer.device(),
112            self.cell_renderer.queue(),
113            &mut encoder,
114            &paint_jobs,
115            &screen_descriptor,
116        );
117
118        // Render egui on top of the terminal content
119        {
120            let render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
121                label: Some("egui render pass"),
122                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
123                    view: &view,
124                    resolve_target: None,
125                    ops: wgpu::Operations {
126                        load: wgpu::LoadOp::Load, // Don't clear - render on top of terminal
127                        store: wgpu::StoreOp::Store,
128                    },
129                    depth_slice: None,
130                })],
131                depth_stencil_attachment: None,
132                timestamp_writes: None,
133                occlusion_query_set: None,
134                multiview_mask: None,
135            });
136
137            // Convert to 'static lifetime as required by egui_renderer.render()
138            let mut render_pass = render_pass.forget_lifetime();
139
140            self.egui_renderer
141                .render(&mut render_pass, &paint_jobs, &screen_descriptor);
142        } // render_pass dropped here
143
144        // Submit egui commands
145        self.cell_renderer
146            .queue()
147            .submit(std::iter::once(encoder.finish()));
148
149        Ok(())
150    }
151}