Skip to main content

editor/
egui_layer.rs

1use std::sync::Arc;
2
3use libdqg::renderer::Renderer;
4
5/// Bundles the `egui`/`egui-winit`/`egui-wgpu` plumbing (window handle, input state, the wgpu
6/// renderer, and the frame's pending output) that every `egui`-driven `Scene` in this editor
7/// needs, so scenes don't each reimplement lazy init and overlay compositing.
8pub struct EguiLayer {
9    window: Option<Arc<winit::window::Window>>,
10    ctx: egui::Context,
11    state: Option<egui_winit::State>,
12    renderer: Option<egui_wgpu::Renderer>,
13    pending_output: Option<egui::FullOutput>,
14}
15
16impl EguiLayer {
17    pub fn new() -> Self {
18        Self {
19            window: None,
20            ctx: egui::Context::default(),
21            state: None,
22            renderer: None,
23            pending_output: None,
24        }
25    }
26
27    fn ensure_init(&mut self, renderer: &Renderer) {
28        if self.window.is_some() {
29            return;
30        }
31
32        let window = renderer.window();
33        self.state = Some(egui_winit::State::new(
34            self.ctx.clone(),
35            egui::ViewportId::ROOT,
36            window.as_ref(),
37            Some(window.scale_factor() as f32),
38            None,
39            None,
40        ));
41        self.renderer = Some(egui_wgpu::Renderer::new(
42            renderer.device(),
43            renderer.wgpu_surface_format(),
44            egui_wgpu::RendererOptions::default(),
45        ));
46        self.window = Some(window);
47    }
48
49    /// Runs one egui frame, invoking `run_ui` to build the UI, and stashes the output for
50    /// [`Self::render_overlay`] to composite later in the same frame.
51    pub fn run(&mut self, renderer: &Renderer, run_ui: impl FnMut(&mut egui::Ui)) {
52        self.ensure_init(renderer);
53
54        let window = self.window.clone().unwrap();
55        let state = self.state.as_mut().unwrap();
56        let raw_input = state.take_egui_input(&window);
57        let full_output = self.ctx.run_ui(raw_input, run_ui);
58        state.handle_platform_output(&window, full_output.platform_output.clone());
59        self.pending_output = Some(full_output);
60    }
61
62    pub fn wants_pointer_input(&self) -> bool {
63        self.ctx.egui_wants_pointer_input()
64    }
65
66    pub fn on_window_event(&mut self, event: &winit::event::WindowEvent) {
67        if let (Some(window), Some(state)) = (self.window.as_ref(), self.state.as_mut()) {
68            let _ = state.on_window_event(window, event);
69        }
70    }
71
72    pub fn render_overlay(
73        &mut self,
74        device: &wgpu::Device,
75        queue: &wgpu::Queue,
76        encoder: &mut wgpu::CommandEncoder,
77        view: &wgpu::TextureView,
78    ) {
79        let (Some(mut full_output), Some(egui_renderer), Some(window)) =
80            (self.pending_output.take(), self.renderer.as_mut(), self.window.as_ref())
81        else {
82            return;
83        };
84
85        let pixels_per_point = full_output.pixels_per_point;
86        let paint_jobs = self.ctx.tessellate(std::mem::take(&mut full_output.shapes), pixels_per_point);
87
88        // `TexturesDelta` panics on drop if `set`/`free` aren't fully drained, so consume them
89        // rather than iterating by reference.
90        for (id, image_deltas) in full_output.textures_delta.set.drain() {
91            for image_delta in &image_deltas {
92                egui_renderer.update_texture(device, queue, id, image_delta);
93            }
94        }
95
96        let size = window.inner_size();
97        let screen_descriptor = egui_wgpu::ScreenDescriptor {
98            size_in_pixels: [size.width, size.height],
99            pixels_per_point,
100        };
101
102        egui_renderer.update_buffers(device, queue, encoder, &paint_jobs, &screen_descriptor);
103
104        let mut rpass = encoder
105            .begin_render_pass(&wgpu::RenderPassDescriptor {
106                label: Some("egui overlay pass"),
107                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
108                    view,
109                    resolve_target: None,
110                    ops: wgpu::Operations {
111                        load: wgpu::LoadOp::Load,
112                        store: wgpu::StoreOp::Store,
113                    },
114                    depth_slice: None,
115                })],
116                depth_stencil_attachment: None,
117                timestamp_writes: None,
118                occlusion_query_set: None,
119                multiview_mask: None,
120            })
121            .forget_lifetime();
122
123        egui_renderer.render(&mut rpass, &paint_jobs, &screen_descriptor);
124        drop(rpass);
125
126        for id in full_output.textures_delta.free.drain() {
127            egui_renderer.free_texture(&id);
128        }
129    }
130}