egui_wgpu/
winit.rs

1#![allow(clippy::missing_errors_doc)]
2#![allow(clippy::undocumented_unsafe_blocks)]
3
4use crate::{RenderState, SurfaceErrorAction, WgpuConfiguration, renderer};
5use crate::{
6    RendererOptions,
7    capture::{CaptureReceiver, CaptureSender, CaptureState, capture_channel},
8};
9use egui::{Context, Event, UserData, ViewportId, ViewportIdMap, ViewportIdSet};
10use std::{num::NonZeroU32, sync::Arc};
11
12struct SurfaceState {
13    surface: wgpu::Surface<'static>,
14    alpha_mode: wgpu::CompositeAlphaMode,
15    width: u32,
16    height: u32,
17}
18
19/// Everything you need to paint egui with [`wgpu`] on [`winit`].
20///
21/// Alternatively you can use [`crate::Renderer`] directly.
22///
23/// NOTE: all egui viewports share the same painter.
24pub struct Painter {
25    context: Context,
26    configuration: WgpuConfiguration,
27    options: RendererOptions,
28    support_transparent_backbuffer: bool,
29    screen_capture_state: Option<CaptureState>,
30
31    instance: wgpu::Instance,
32    render_state: Option<RenderState>,
33
34    // Per viewport/window:
35    depth_texture_view: ViewportIdMap<wgpu::TextureView>,
36    msaa_texture_view: ViewportIdMap<wgpu::TextureView>,
37    surfaces: ViewportIdMap<SurfaceState>,
38    capture_tx: CaptureSender,
39    capture_rx: CaptureReceiver,
40}
41
42impl Painter {
43    /// Manages [`wgpu`] state, including surface state, required to render egui.
44    ///
45    /// Only the [`wgpu::Instance`] is initialized here. Device selection and the initialization
46    /// of render + surface state is deferred until the painter is given its first window target
47    /// via [`set_window()`](Self::set_window). (Ensuring that a device that's compatible with the
48    /// native window is chosen)
49    ///
50    /// Before calling [`paint_and_update_textures()`](Self::paint_and_update_textures) a
51    /// [`wgpu::Surface`] must be initialized (and corresponding render state) by calling
52    /// [`set_window()`](Self::set_window) once you have
53    /// a [`winit::window::Window`] with a valid `.raw_window_handle()`
54    /// associated.
55    pub async fn new(
56        context: Context,
57        configuration: WgpuConfiguration,
58        support_transparent_backbuffer: bool,
59        options: RendererOptions,
60    ) -> Self {
61        let (capture_tx, capture_rx) = capture_channel();
62        let instance = configuration.wgpu_setup.new_instance().await;
63
64        Self {
65            context,
66            configuration,
67            options,
68            support_transparent_backbuffer,
69            screen_capture_state: None,
70
71            instance,
72            render_state: None,
73
74            depth_texture_view: Default::default(),
75            surfaces: Default::default(),
76            msaa_texture_view: Default::default(),
77
78            capture_tx,
79            capture_rx,
80        }
81    }
82
83    /// Get the [`RenderState`].
84    ///
85    /// Will return [`None`] if the render state has not been initialized yet.
86    pub fn render_state(&self) -> Option<RenderState> {
87        self.render_state.clone()
88    }
89
90    fn configure_surface(
91        surface_state: &SurfaceState,
92        render_state: &RenderState,
93        config: &WgpuConfiguration,
94    ) {
95        profiling::function_scope!();
96
97        let width = surface_state.width;
98        let height = surface_state.height;
99
100        let mut surf_config = wgpu::SurfaceConfiguration {
101            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
102            format: render_state.target_format,
103            present_mode: config.present_mode,
104            alpha_mode: surface_state.alpha_mode,
105            view_formats: vec![render_state.target_format],
106            ..surface_state
107                .surface
108                .get_default_config(&render_state.adapter, width, height)
109                .expect("The surface isn't supported by this adapter")
110        };
111
112        if let Some(desired_maximum_frame_latency) = config.desired_maximum_frame_latency {
113            surf_config.desired_maximum_frame_latency = desired_maximum_frame_latency;
114        }
115
116        surface_state
117            .surface
118            .configure(&render_state.device, &surf_config);
119    }
120
121    /// Updates (or clears) the [`winit::window::Window`] associated with the [`Painter`]
122    ///
123    /// This creates a [`wgpu::Surface`] for the given Window (as well as initializing render
124    /// state if needed) that is used for egui rendering.
125    ///
126    /// This must be called before trying to render via
127    /// [`paint_and_update_textures`](Self::paint_and_update_textures)
128    ///
129    /// # Portability
130    ///
131    /// _In particular it's important to note that on Android a it's only possible to create
132    /// a window surface between `Resumed` and `Paused` lifecycle events, and Winit will panic on
133    /// attempts to query the raw window handle while paused._
134    ///
135    /// On Android [`set_window`](Self::set_window) should be called with `Some(window)` for each
136    /// `Resumed` event and `None` for each `Paused` event. Currently, on all other platforms
137    /// [`set_window`](Self::set_window) may be called with `Some(window)` as soon as you have a
138    /// valid [`winit::window::Window`].
139    ///
140    /// # Errors
141    /// If the provided wgpu configuration does not match an available device.
142    pub async fn set_window(
143        &mut self,
144        viewport_id: ViewportId,
145        window: Option<Arc<winit::window::Window>>,
146    ) -> Result<(), crate::WgpuError> {
147        profiling::scope!("Painter::set_window"); // profile_function gives bad names for async functions
148
149        if let Some(window) = window {
150            let size = window.inner_size();
151            if !self.surfaces.contains_key(&viewport_id) {
152                let surface = self.instance.create_surface(window)?;
153                self.add_surface(surface, viewport_id, size).await?;
154            }
155        } else {
156            log::warn!("No window - clearing all surfaces");
157            self.surfaces.clear();
158        }
159        Ok(())
160    }
161
162    /// Updates (or clears) the [`winit::window::Window`] associated with the [`Painter`] without taking ownership of the window.
163    ///
164    /// Like [`set_window`](Self::set_window) except:
165    ///
166    /// # Safety
167    /// The user is responsible for ensuring that the window is alive for as long as it is set.
168    pub async unsafe fn set_window_unsafe(
169        &mut self,
170        viewport_id: ViewportId,
171        window: Option<&winit::window::Window>,
172    ) -> Result<(), crate::WgpuError> {
173        profiling::scope!("Painter::set_window_unsafe"); // profile_function gives bad names for async functions
174
175        if let Some(window) = window {
176            let size = window.inner_size();
177            if !self.surfaces.contains_key(&viewport_id) {
178                let surface = unsafe {
179                    self.instance
180                        .create_surface_unsafe(wgpu::SurfaceTargetUnsafe::from_window(&window)?)?
181                };
182                self.add_surface(surface, viewport_id, size).await?;
183            }
184        } else {
185            log::warn!("No window - clearing all surfaces");
186            self.surfaces.clear();
187        }
188        Ok(())
189    }
190
191    async fn add_surface(
192        &mut self,
193        surface: wgpu::Surface<'static>,
194        viewport_id: ViewportId,
195        size: winit::dpi::PhysicalSize<u32>,
196    ) -> Result<(), crate::WgpuError> {
197        let render_state = if let Some(render_state) = &self.render_state {
198            render_state
199        } else {
200            let render_state = RenderState::create(
201                &self.configuration,
202                &self.instance,
203                Some(&surface),
204                self.options,
205            )
206            .await?;
207            self.render_state.get_or_insert(render_state)
208        };
209        let alpha_mode = if self.support_transparent_backbuffer {
210            let supported_alpha_modes = surface.get_capabilities(&render_state.adapter).alpha_modes;
211
212            // Prefer pre multiplied over post multiplied!
213            if supported_alpha_modes.contains(&wgpu::CompositeAlphaMode::PreMultiplied) {
214                wgpu::CompositeAlphaMode::PreMultiplied
215            } else if supported_alpha_modes.contains(&wgpu::CompositeAlphaMode::PostMultiplied) {
216                wgpu::CompositeAlphaMode::PostMultiplied
217            } else {
218                log::warn!(
219                    "Transparent window was requested, but the active wgpu surface does not support a `CompositeAlphaMode` with transparency."
220                );
221                wgpu::CompositeAlphaMode::Auto
222            }
223        } else {
224            wgpu::CompositeAlphaMode::Auto
225        };
226        self.surfaces.insert(
227            viewport_id,
228            SurfaceState {
229                surface,
230                width: size.width,
231                height: size.height,
232                alpha_mode,
233            },
234        );
235        let Some(width) = NonZeroU32::new(size.width) else {
236            log::debug!("The window width was zero; skipping generate textures");
237            return Ok(());
238        };
239        let Some(height) = NonZeroU32::new(size.height) else {
240            log::debug!("The window height was zero; skipping generate textures");
241            return Ok(());
242        };
243        self.resize_and_generate_depth_texture_view_and_msaa_view(viewport_id, width, height);
244        Ok(())
245    }
246
247    /// Returns the maximum texture dimension supported if known
248    ///
249    /// This API will only return a known dimension after `set_window()` has been called
250    /// at least once, since the underlying device and render state are initialized lazily
251    /// once we have a window (that may determine the choice of adapter/device).
252    pub fn max_texture_side(&self) -> Option<usize> {
253        self.render_state
254            .as_ref()
255            .map(|rs| rs.device.limits().max_texture_dimension_2d as usize)
256    }
257
258    fn resize_and_generate_depth_texture_view_and_msaa_view(
259        &mut self,
260        viewport_id: ViewportId,
261        width_in_pixels: NonZeroU32,
262        height_in_pixels: NonZeroU32,
263    ) {
264        profiling::function_scope!();
265
266        let width = width_in_pixels.get();
267        let height = height_in_pixels.get();
268
269        let render_state = self.render_state.as_ref().unwrap();
270        let surface_state = self.surfaces.get_mut(&viewport_id).unwrap();
271
272        surface_state.width = width;
273        surface_state.height = height;
274
275        Self::configure_surface(surface_state, render_state, &self.configuration);
276
277        if let Some(depth_format) = self.options.depth_stencil_format {
278            self.depth_texture_view.insert(
279                viewport_id,
280                render_state
281                    .device
282                    .create_texture(&wgpu::TextureDescriptor {
283                        label: Some("egui_depth_texture"),
284                        size: wgpu::Extent3d {
285                            width,
286                            height,
287                            depth_or_array_layers: 1,
288                        },
289                        mip_level_count: 1,
290                        sample_count: self.options.msaa_samples.max(1),
291                        dimension: wgpu::TextureDimension::D2,
292                        format: depth_format,
293                        usage: wgpu::TextureUsages::RENDER_ATTACHMENT
294                            | wgpu::TextureUsages::TEXTURE_BINDING,
295                        view_formats: &[depth_format],
296                    })
297                    .create_view(&wgpu::TextureViewDescriptor::default()),
298            );
299        }
300
301        if let Some(render_state) = (self.options.msaa_samples > 1)
302            .then_some(self.render_state.as_ref())
303            .flatten()
304        {
305            let texture_format = render_state.target_format;
306            self.msaa_texture_view.insert(
307                viewport_id,
308                render_state
309                    .device
310                    .create_texture(&wgpu::TextureDescriptor {
311                        label: Some("egui_msaa_texture"),
312                        size: wgpu::Extent3d {
313                            width,
314                            height,
315                            depth_or_array_layers: 1,
316                        },
317                        mip_level_count: 1,
318                        sample_count: self.options.msaa_samples.max(1),
319                        dimension: wgpu::TextureDimension::D2,
320                        format: texture_format,
321                        usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
322                        view_formats: &[texture_format],
323                    })
324                    .create_view(&wgpu::TextureViewDescriptor::default()),
325            );
326        }
327    }
328
329    pub fn on_window_resized(
330        &mut self,
331        viewport_id: ViewportId,
332        width_in_pixels: NonZeroU32,
333        height_in_pixels: NonZeroU32,
334    ) {
335        profiling::function_scope!();
336
337        if self.surfaces.contains_key(&viewport_id) {
338            self.resize_and_generate_depth_texture_view_and_msaa_view(
339                viewport_id,
340                width_in_pixels,
341                height_in_pixels,
342            );
343        } else {
344            log::warn!(
345                "Ignoring window resize notification with no surface created via Painter::set_window()"
346            );
347        }
348    }
349
350    /// Returns two things:
351    ///
352    /// The approximate number of seconds spent on vsync-waiting (if any),
353    /// and the captures captured screenshot if it was requested.
354    ///
355    /// If `capture_data` isn't empty, a screenshot will be captured.
356    pub fn paint_and_update_textures(
357        &mut self,
358        viewport_id: ViewportId,
359        pixels_per_point: f32,
360        clear_color: [f32; 4],
361        clipped_primitives: &[epaint::ClippedPrimitive],
362        textures_delta: &epaint::textures::TexturesDelta,
363        capture_data: Vec<UserData>,
364    ) -> f32 {
365        profiling::function_scope!();
366
367        let capture = !capture_data.is_empty();
368        let mut vsync_sec = 0.0;
369
370        let Some(render_state) = self.render_state.as_mut() else {
371            return vsync_sec;
372        };
373        let Some(surface_state) = self.surfaces.get(&viewport_id) else {
374            return vsync_sec;
375        };
376
377        let mut encoder =
378            render_state
379                .device
380                .create_command_encoder(&wgpu::CommandEncoderDescriptor {
381                    label: Some("encoder"),
382                });
383
384        // Upload all resources for the GPU.
385        let screen_descriptor = renderer::ScreenDescriptor {
386            size_in_pixels: [surface_state.width, surface_state.height],
387            pixels_per_point,
388        };
389
390        let user_cmd_bufs = {
391            let mut renderer = render_state.renderer.write();
392            for (id, image_delta) in &textures_delta.set {
393                renderer.update_texture(
394                    &render_state.device,
395                    &render_state.queue,
396                    *id,
397                    image_delta,
398                );
399            }
400
401            renderer.update_buffers(
402                &render_state.device,
403                &render_state.queue,
404                &mut encoder,
405                clipped_primitives,
406                &screen_descriptor,
407            )
408        };
409
410        let output_frame = {
411            profiling::scope!("get_current_texture");
412            // This is what vsync-waiting happens on my Mac.
413            let start = web_time::Instant::now();
414            let output_frame = surface_state.surface.get_current_texture();
415            vsync_sec += start.elapsed().as_secs_f32();
416            output_frame
417        };
418
419        let output_frame = match output_frame {
420            Ok(frame) => frame,
421            Err(err) => match (*self.configuration.on_surface_error)(err) {
422                SurfaceErrorAction::RecreateSurface => {
423                    Self::configure_surface(surface_state, render_state, &self.configuration);
424                    return vsync_sec;
425                }
426                SurfaceErrorAction::SkipFrame => {
427                    return vsync_sec;
428                }
429            },
430        };
431
432        let mut capture_buffer = None;
433        {
434            let renderer = render_state.renderer.read();
435
436            let target_texture = if capture {
437                let capture_state = self.screen_capture_state.get_or_insert_with(|| {
438                    CaptureState::new(&render_state.device, &output_frame.texture)
439                });
440                capture_state.update(&render_state.device, &output_frame.texture);
441
442                &capture_state.texture
443            } else {
444                &output_frame.texture
445            };
446            let target_view = target_texture.create_view(&wgpu::TextureViewDescriptor::default());
447
448            let (view, resolve_target) = (self.options.msaa_samples > 1)
449                .then_some(self.msaa_texture_view.get(&viewport_id))
450                .flatten()
451                .map_or((&target_view, None), |texture_view| {
452                    (texture_view, Some(&target_view))
453                });
454
455            let render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
456                label: Some("egui_render"),
457                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
458                    view,
459                    resolve_target,
460                    ops: wgpu::Operations {
461                        load: wgpu::LoadOp::Clear(wgpu::Color {
462                            r: clear_color[0] as f64,
463                            g: clear_color[1] as f64,
464                            b: clear_color[2] as f64,
465                            a: clear_color[3] as f64,
466                        }),
467                        store: wgpu::StoreOp::Store,
468                    },
469                    depth_slice: None,
470                })],
471                depth_stencil_attachment: self.depth_texture_view.get(&viewport_id).map(|view| {
472                    wgpu::RenderPassDepthStencilAttachment {
473                        view,
474                        depth_ops: Some(wgpu::Operations {
475                            load: wgpu::LoadOp::Clear(1.0),
476                            // It is very unlikely that the depth buffer is needed after egui finished rendering
477                            // so no need to store it. (this can improve performance on tiling GPUs like mobile chips or Apple Silicon)
478                            store: wgpu::StoreOp::Discard,
479                        }),
480                        stencil_ops: None,
481                    }
482                }),
483                timestamp_writes: None,
484                occlusion_query_set: None,
485            });
486
487            // Forgetting the pass' lifetime means that we are no longer compile-time protected from
488            // runtime errors caused by accessing the parent encoder before the render pass is dropped.
489            // Since we don't pass it on to the renderer, we should be perfectly safe against this mistake here!
490            renderer.render(
491                &mut render_pass.forget_lifetime(),
492                clipped_primitives,
493                &screen_descriptor,
494            );
495
496            if capture && let Some(capture_state) = &mut self.screen_capture_state {
497                capture_buffer = Some(capture_state.copy_textures(
498                    &render_state.device,
499                    &output_frame,
500                    &mut encoder,
501                ));
502            }
503        }
504
505        let encoded = {
506            profiling::scope!("CommandEncoder::finish");
507            encoder.finish()
508        };
509
510        // Submit the commands: both the main buffer and user-defined ones.
511        {
512            profiling::scope!("Queue::submit");
513            // wgpu doesn't document where vsync can happen. Maybe here?
514            let start = web_time::Instant::now();
515            render_state
516                .queue
517                .submit(user_cmd_bufs.into_iter().chain([encoded]));
518            vsync_sec += start.elapsed().as_secs_f32();
519        };
520
521        // Free textures marked for destruction **after** queue submit since they might still be used in the current frame.
522        // Calling `wgpu::Texture::destroy` on a texture that is still in use would invalidate the command buffer(s) it is used in.
523        // However, once we called `wgpu::Queue::submit`, it is up for wgpu to determine how long the underlying gpu resource has to live.
524        {
525            let mut renderer = render_state.renderer.write();
526            for id in &textures_delta.free {
527                renderer.free_texture(id);
528            }
529        }
530
531        if let Some(capture_buffer) = capture_buffer
532            && let Some(screen_capture_state) = &mut self.screen_capture_state
533        {
534            screen_capture_state.read_screen_rgba(
535                self.context.clone(),
536                capture_buffer,
537                capture_data,
538                self.capture_tx.clone(),
539                viewport_id,
540            );
541        }
542
543        {
544            profiling::scope!("present");
545            // wgpu doesn't document where vsync can happen. Maybe here?
546            let start = web_time::Instant::now();
547            output_frame.present();
548            vsync_sec += start.elapsed().as_secs_f32();
549        }
550
551        vsync_sec
552    }
553
554    /// Call this at the beginning of each frame to receive the requested screenshots.
555    pub fn handle_screenshots(&self, events: &mut Vec<Event>) {
556        for (viewport_id, user_data, screenshot) in self.capture_rx.try_iter() {
557            let screenshot = Arc::new(screenshot);
558            for data in user_data {
559                events.push(Event::Screenshot {
560                    viewport_id,
561                    user_data: data,
562                    image: screenshot.clone(),
563                });
564            }
565        }
566    }
567
568    pub fn gc_viewports(&mut self, active_viewports: &ViewportIdSet) {
569        self.surfaces.retain(|id, _| active_viewports.contains(id));
570        self.depth_texture_view
571            .retain(|id, _| active_viewports.contains(id));
572        self.msaa_texture_view
573            .retain(|id, _| active_viewports.contains(id));
574    }
575
576    #[expect(clippy::needless_pass_by_ref_mut, clippy::unused_self)]
577    pub fn destroy(&mut self) {
578        // TODO(emilk): something here?
579    }
580}