egui_wgpu/
lib.rs

1//! This crates provides bindings between [`egui`](https://github.com/emilk/egui) and [wgpu](https://crates.io/crates/wgpu).
2//!
3//! If you're targeting WebGL you also need to turn on the
4//! `webgl` feature of the `wgpu` crate:
5//!
6//! ```toml
7//! # Enable both WebGL and WebGPU backends on web.
8//! wgpu = { version = "*", features = ["webgpu", "webgl"] }
9//! ```
10//!
11//! You can control whether WebGL or WebGPU will be picked at runtime by configuring
12//! [`WgpuConfiguration::wgpu_setup`].
13//! The default is to prefer WebGPU and fall back on WebGL.
14//!
15//! ## Feature flags
16#![doc = document_features::document_features!()]
17//!
18
19#![allow(unsafe_code)]
20
21pub use wgpu;
22
23/// Low-level painting of [`egui`](https://github.com/emilk/egui) on [`wgpu`].
24mod renderer;
25
26mod setup;
27
28pub use renderer::*;
29pub use setup::{NativeAdapterSelectorMethod, WgpuSetup, WgpuSetupCreateNew, WgpuSetupExisting};
30
31/// Helpers for capturing screenshots of the UI.
32pub mod capture;
33
34/// Module for painting [`egui`](https://github.com/emilk/egui) with [`wgpu`] on [`winit`].
35#[cfg(feature = "winit")]
36pub mod winit;
37
38use std::sync::Arc;
39
40use epaint::mutex::RwLock;
41
42/// An error produced by egui-wgpu.
43#[derive(thiserror::Error, Debug)]
44pub enum WgpuError {
45    #[error("Failed to create wgpu adapter, no suitable adapter found: {0}")]
46    NoSuitableAdapterFound(String),
47
48    #[error("There was no valid format for the surface at all.")]
49    NoSurfaceFormatsAvailable,
50
51    #[error(transparent)]
52    RequestDeviceError(#[from] wgpu::RequestDeviceError),
53
54    #[error(transparent)]
55    CreateSurfaceError(#[from] wgpu::CreateSurfaceError),
56
57    #[cfg(feature = "winit")]
58    #[error(transparent)]
59    HandleError(#[from] ::winit::raw_window_handle::HandleError),
60}
61
62/// Access to the render state for egui.
63#[derive(Clone)]
64pub struct RenderState {
65    /// Wgpu adapter used for rendering.
66    pub adapter: wgpu::Adapter,
67
68    /// All the available adapters.
69    ///
70    /// This is not available on web.
71    /// On web, we always select WebGPU is available, then fall back to WebGL if not.
72    #[cfg(not(target_arch = "wasm32"))]
73    pub available_adapters: Vec<wgpu::Adapter>,
74
75    /// Wgpu device used for rendering, created from the adapter.
76    pub device: wgpu::Device,
77
78    /// Wgpu queue used for rendering, created from the adapter.
79    pub queue: wgpu::Queue,
80
81    /// The target texture format used for presenting to the window.
82    pub target_format: wgpu::TextureFormat,
83
84    /// Egui renderer responsible for drawing the UI.
85    pub renderer: Arc<RwLock<Renderer>>,
86}
87
88async fn request_adapter(
89    instance: &wgpu::Instance,
90    power_preference: wgpu::PowerPreference,
91    compatible_surface: Option<&wgpu::Surface<'_>>,
92    _available_adapters: &[wgpu::Adapter],
93) -> Result<wgpu::Adapter, WgpuError> {
94    profiling::function_scope!();
95
96    let adapter = instance
97        .request_adapter(&wgpu::RequestAdapterOptions {
98            power_preference,
99            compatible_surface,
100            // We don't expose this as an option right now since it's fairly rarely useful:
101            // * only has an effect on native
102            // * fails if there's no software rasterizer available
103            // * can achieve the same with `native_adapter_selector`
104            force_fallback_adapter: false,
105        })
106        .await
107        .ok_or_else(|| {
108            #[cfg(not(target_arch = "wasm32"))]
109            if _available_adapters.is_empty() {
110                log::info!("No wgpu adapters found");
111            } else if _available_adapters.len() == 1 {
112                log::info!(
113                    "The only available wgpu adapter was not suitable: {}",
114                    adapter_info_summary(&_available_adapters[0].get_info())
115                );
116            } else {
117                log::info!(
118                    "No suitable wgpu adapter found out of the {} available ones: {}",
119                    _available_adapters.len(),
120                    describe_adapters(_available_adapters)
121                );
122            }
123
124            WgpuError::NoSuitableAdapterFound("`request_adapters` returned `None`".to_owned())
125        })?;
126
127    #[cfg(target_arch = "wasm32")]
128    log::debug!(
129        "Picked wgpu adapter: {}",
130        adapter_info_summary(&adapter.get_info())
131    );
132
133    #[cfg(not(target_arch = "wasm32"))]
134    if _available_adapters.len() == 1 {
135        log::debug!(
136            "Picked the only available wgpu adapter: {}",
137            adapter_info_summary(&adapter.get_info())
138        );
139    } else {
140        log::info!(
141            "There were {} available wgpu adapters: {}",
142            _available_adapters.len(),
143            describe_adapters(_available_adapters)
144        );
145        log::debug!(
146            "Picked wgpu adapter: {}",
147            adapter_info_summary(&adapter.get_info())
148        );
149    }
150
151    Ok(adapter)
152}
153
154impl RenderState {
155    /// Creates a new [`RenderState`], containing everything needed for drawing egui with wgpu.
156    ///
157    /// # Errors
158    /// Wgpu initialization may fail due to incompatible hardware or driver for a given config.
159    pub async fn create(
160        config: &WgpuConfiguration,
161        instance: &wgpu::Instance,
162        compatible_surface: Option<&wgpu::Surface<'static>>,
163        depth_format: Option<wgpu::TextureFormat>,
164        msaa_samples: u32,
165        dithering: bool,
166    ) -> Result<Self, WgpuError> {
167        profiling::scope!("RenderState::create"); // async yield give bad names using `profile_function`
168
169        // This is always an empty list on web.
170        #[cfg(not(target_arch = "wasm32"))]
171        let available_adapters = {
172            let backends = if let WgpuSetup::CreateNew(create_new) = &config.wgpu_setup {
173                create_new.instance_descriptor.backends
174            } else {
175                wgpu::Backends::all()
176            };
177
178            instance.enumerate_adapters(backends)
179        };
180
181        let (adapter, device, queue) = match config.wgpu_setup.clone() {
182            WgpuSetup::CreateNew(WgpuSetupCreateNew {
183                instance_descriptor: _,
184                power_preference,
185                native_adapter_selector: _native_adapter_selector,
186                device_descriptor,
187                trace_path,
188            }) => {
189                let adapter = {
190                    #[cfg(target_arch = "wasm32")]
191                    {
192                        request_adapter(instance, power_preference, compatible_surface, &[]).await
193                    }
194                    #[cfg(not(target_arch = "wasm32"))]
195                    if let Some(native_adapter_selector) = _native_adapter_selector {
196                        native_adapter_selector(&available_adapters, compatible_surface)
197                            .map_err(WgpuError::NoSuitableAdapterFound)
198                    } else {
199                        request_adapter(
200                            instance,
201                            power_preference,
202                            compatible_surface,
203                            &available_adapters,
204                        )
205                        .await
206                    }
207                }?;
208
209                let (device, queue) = {
210                    profiling::scope!("request_device");
211                    adapter
212                        .request_device(&(*device_descriptor)(&adapter), trace_path.as_deref())
213                        .await?
214                };
215
216                (adapter, device, queue)
217            }
218            WgpuSetup::Existing(WgpuSetupExisting {
219                instance: _,
220                adapter,
221                device,
222                queue,
223            }) => (adapter, device, queue),
224        };
225
226        let surface_formats = {
227            profiling::scope!("get_capabilities");
228            compatible_surface.map_or_else(
229                || vec![wgpu::TextureFormat::Rgba8Unorm],
230                |s| s.get_capabilities(&adapter).formats,
231            )
232        };
233        let target_format = crate::preferred_framebuffer_format(&surface_formats)?;
234
235        let renderer = Renderer::new(
236            &device,
237            target_format,
238            depth_format,
239            msaa_samples,
240            dithering,
241        );
242
243        // On wasm, depending on feature flags, wgpu objects may or may not implement sync.
244        // It doesn't make sense to switch to Rc for that special usecase, so simply disable the lint.
245        #[allow(clippy::arc_with_non_send_sync)]
246        Ok(Self {
247            adapter,
248            #[cfg(not(target_arch = "wasm32"))]
249            available_adapters,
250            device,
251            queue,
252            target_format,
253            renderer: Arc::new(RwLock::new(renderer)),
254        })
255    }
256}
257
258#[cfg(not(target_arch = "wasm32"))]
259fn describe_adapters(adapters: &[wgpu::Adapter]) -> String {
260    if adapters.is_empty() {
261        "(none)".to_owned()
262    } else if adapters.len() == 1 {
263        adapter_info_summary(&adapters[0].get_info())
264    } else {
265        adapters
266            .iter()
267            .map(|a| format!("{{{}}}", adapter_info_summary(&a.get_info())))
268            .collect::<Vec<_>>()
269            .join(", ")
270    }
271}
272
273/// Specifies which action should be taken as consequence of a [`wgpu::SurfaceError`]
274pub enum SurfaceErrorAction {
275    /// Do nothing and skip the current frame.
276    SkipFrame,
277
278    /// Instructs egui to recreate the surface, then skip the current frame.
279    RecreateSurface,
280}
281
282/// Configuration for using wgpu with eframe or the egui-wgpu winit feature.
283#[derive(Clone)]
284pub struct WgpuConfiguration {
285    /// Present mode used for the primary surface.
286    pub present_mode: wgpu::PresentMode,
287
288    /// Desired maximum number of frames that the presentation engine should queue in advance.
289    ///
290    /// Use `1` for low-latency, and `2` for high-throughput.
291    ///
292    /// See [`wgpu::SurfaceConfiguration::desired_maximum_frame_latency`] for details.
293    ///
294    /// `None` = `wgpu` default.
295    pub desired_maximum_frame_latency: Option<u32>,
296
297    /// How to create the wgpu adapter & device
298    pub wgpu_setup: WgpuSetup,
299
300    /// Callback for surface errors.
301    pub on_surface_error: Arc<dyn Fn(wgpu::SurfaceError) -> SurfaceErrorAction + Send + Sync>,
302}
303
304#[test]
305fn wgpu_config_impl_send_sync() {
306    fn assert_send_sync<T: Send + Sync>() {}
307    assert_send_sync::<WgpuConfiguration>();
308}
309
310impl std::fmt::Debug for WgpuConfiguration {
311    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
312        let Self {
313            present_mode,
314            desired_maximum_frame_latency,
315            wgpu_setup,
316            on_surface_error: _,
317        } = self;
318        f.debug_struct("WgpuConfiguration")
319            .field("present_mode", &present_mode)
320            .field(
321                "desired_maximum_frame_latency",
322                &desired_maximum_frame_latency,
323            )
324            .field("wgpu_setup", &wgpu_setup)
325            .finish_non_exhaustive()
326    }
327}
328
329impl Default for WgpuConfiguration {
330    fn default() -> Self {
331        Self {
332            present_mode: wgpu::PresentMode::AutoVsync,
333            desired_maximum_frame_latency: None,
334            wgpu_setup: Default::default(),
335            on_surface_error: Arc::new(|err| {
336                if err == wgpu::SurfaceError::Outdated {
337                    // This error occurs when the app is minimized on Windows.
338                    // Silently return here to prevent spamming the console with:
339                    // "The underlying surface has changed, and therefore the swap chain must be updated"
340                } else {
341                    log::warn!("Dropped frame with error: {err}");
342                }
343                SurfaceErrorAction::SkipFrame
344            }),
345        }
346    }
347}
348
349/// Find the framebuffer format that egui prefers
350///
351/// # Errors
352/// Returns [`WgpuError::NoSurfaceFormatsAvailable`] if the given list of formats is empty.
353pub fn preferred_framebuffer_format(
354    formats: &[wgpu::TextureFormat],
355) -> Result<wgpu::TextureFormat, WgpuError> {
356    for &format in formats {
357        if matches!(
358            format,
359            wgpu::TextureFormat::Rgba8Unorm | wgpu::TextureFormat::Bgra8Unorm
360        ) {
361            return Ok(format);
362        }
363    }
364
365    formats
366        .first()
367        .copied()
368        .ok_or(WgpuError::NoSurfaceFormatsAvailable)
369}
370
371/// Take's epi's depth/stencil bits and returns the corresponding wgpu format.
372pub fn depth_format_from_bits(depth_buffer: u8, stencil_buffer: u8) -> Option<wgpu::TextureFormat> {
373    match (depth_buffer, stencil_buffer) {
374        (0, 8) => Some(wgpu::TextureFormat::Stencil8),
375        (16, 0) => Some(wgpu::TextureFormat::Depth16Unorm),
376        (24, 0) => Some(wgpu::TextureFormat::Depth24Plus),
377        (24, 8) => Some(wgpu::TextureFormat::Depth24PlusStencil8),
378        (32, 0) => Some(wgpu::TextureFormat::Depth32Float),
379        (32, 8) => Some(wgpu::TextureFormat::Depth32FloatStencil8),
380        _ => None,
381    }
382}
383
384// ---------------------------------------------------------------------------
385
386/// A human-readable summary about an adapter
387pub fn adapter_info_summary(info: &wgpu::AdapterInfo) -> String {
388    let wgpu::AdapterInfo {
389        name,
390        vendor,
391        device,
392        device_type,
393        driver,
394        driver_info,
395        backend,
396    } = &info;
397
398    // Example values:
399    // > name: "llvmpipe (LLVM 16.0.6, 256 bits)", device_type: Cpu, backend: Vulkan, driver: "llvmpipe", driver_info: "Mesa 23.1.6-arch1.4 (LLVM 16.0.6)"
400    // > name: "Apple M1 Pro", device_type: IntegratedGpu, backend: Metal, driver: "", driver_info: ""
401    // > name: "ANGLE (Apple, Apple M1 Pro, OpenGL 4.1)", device_type: IntegratedGpu, backend: Gl, driver: "", driver_info: ""
402
403    let mut summary = format!("backend: {backend:?}, device_type: {device_type:?}");
404
405    if !name.is_empty() {
406        summary += &format!(", name: {name:?}");
407    }
408    if !driver.is_empty() {
409        summary += &format!(", driver: {driver:?}");
410    }
411    if !driver_info.is_empty() {
412        summary += &format!(", driver_info: {driver_info:?}");
413    }
414    if *vendor != 0 {
415        #[cfg(not(target_arch = "wasm32"))]
416        {
417            summary += &format!(", vendor: {} (0x{vendor:04X})", parse_vendor_id(*vendor));
418        }
419        #[cfg(target_arch = "wasm32")]
420        {
421            summary += &format!(", vendor: 0x{vendor:04X}");
422        }
423    }
424    if *device != 0 {
425        summary += &format!(", device: 0x{device:02X}");
426    }
427
428    summary
429}
430
431/// Tries to parse the adapter's vendor ID to a human-readable string.
432#[cfg(not(target_arch = "wasm32"))]
433pub fn parse_vendor_id(vendor_id: u32) -> &'static str {
434    match vendor_id {
435        wgpu::hal::auxil::db::amd::VENDOR => "AMD",
436        wgpu::hal::auxil::db::apple::VENDOR => "Apple",
437        wgpu::hal::auxil::db::arm::VENDOR => "ARM",
438        wgpu::hal::auxil::db::broadcom::VENDOR => "Broadcom",
439        wgpu::hal::auxil::db::imgtec::VENDOR => "Imagination Technologies",
440        wgpu::hal::auxil::db::intel::VENDOR => "Intel",
441        wgpu::hal::auxil::db::mesa::VENDOR => "Mesa",
442        wgpu::hal::auxil::db::nvidia::VENDOR => "NVIDIA",
443        wgpu::hal::auxil::db::qualcomm::VENDOR => "Qualcomm",
444        _ => "Unknown",
445    }
446}