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}