diff --git a/Cargo.lock b/Cargo.lock index df8f811ae..951848259 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1251,6 +1251,7 @@ dependencies = [ "epaint", "log", "puffin", + "thiserror", "type-map", "wgpu", "winit", diff --git a/Cargo.toml b/Cargo.toml index 30eac61c3..62d3fc4f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,3 +33,6 @@ opt-level = 2 # fast and small wasm, basically same as `opt-level = 's'` # Optimize all dependencies even in debug builds (does not affect workspace packages): [profile.dev.package."*"] opt-level = 2 + +[workspace.dependencies] +thiserror = "1.0.37" diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index 5eee9709f..e270fd734 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -80,7 +80,7 @@ egui = { version = "0.21.0", path = "../egui", default-features = false, feature "log", ] } log = { version = "0.4", features = ["std"] } -thiserror = "1.0.37" +thiserror.workspace = true #! ### Optional dependencies ## Enable this when generating docs. diff --git a/crates/eframe/src/epi/mod.rs b/crates/eframe/src/epi/mod.rs index dff018735..85d102775 100644 --- a/crates/eframe/src/epi/mod.rs +++ b/crates/eframe/src/epi/mod.rs @@ -312,10 +312,6 @@ pub struct NativeOptions { /// Sets the number of bits in the depth buffer. /// /// `egui` doesn't need the depth buffer, so the default value is 0. - /// - /// On `wgpu` backends, due to limited depth texture format options, this - /// will be interpreted as a boolean (non-zero = true) for whether or not - /// specifically a `Depth32Float` buffer is used. pub depth_buffer: u8, /// Sets the number of bits in the stencil buffer. @@ -475,6 +471,12 @@ pub struct WebOptions { /// Default: `Theme::Dark`. pub default_theme: Theme, + /// Sets the number of bits in the depth buffer. + /// + /// `egui` doesn't need the depth buffer, so the default value is 0. + /// Unused by webgl context as of writing. + pub depth_buffer: u8, + /// Which version of WebGl context to select /// /// Default: [`WebGlContextOption::BestFirst`]. @@ -492,6 +494,7 @@ impl Default for WebOptions { Self { follow_system_theme: true, default_theme: Theme::Dark, + depth_buffer: 0, #[cfg(feature = "glow")] webgl_context_option: WebGlContextOption::BestFirst, diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index 15a89e3e1..e4942be80 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -1122,9 +1122,7 @@ mod wgpu_integration { ) -> std::result::Result<(), egui_wgpu::WgpuError> { self.window = Some(window); if let Some(running) = &mut self.running { - unsafe { - pollster::block_on(running.painter.set_window(self.window.as_ref()))?; - } + pollster::block_on(running.painter.set_window(self.window.as_ref()))?; } Ok(()) } @@ -1134,9 +1132,7 @@ mod wgpu_integration { fn drop_window(&mut self) -> std::result::Result<(), egui_wgpu::WgpuError> { self.window = None; if let Some(running) = &mut self.running { - unsafe { - pollster::block_on(running.painter.set_window(None))?; - } + pollster::block_on(running.painter.set_window(None))?; } Ok(()) } @@ -1148,16 +1144,16 @@ mod wgpu_integration { window: winit::window::Window, ) -> std::result::Result<(), egui_wgpu::WgpuError> { #[allow(unsafe_code, unused_mut, unused_unsafe)] - let painter = unsafe { - let mut painter = egui_wgpu::winit::Painter::new( - self.native_options.wgpu_options.clone(), - self.native_options.multisampling.max(1) as _, + let mut painter = egui_wgpu::winit::Painter::new( + self.native_options.wgpu_options.clone(), + self.native_options.multisampling.max(1) as _, + egui_wgpu::depth_format_from_bits( self.native_options.depth_buffer, - self.native_options.transparent, - ); - pollster::block_on(painter.set_window(Some(&window)))?; - painter - }; + self.native_options.stencil_buffer, + ), + self.native_options.transparent, + ); + pollster::block_on(painter.set_window(Some(&window)))?; let wgpu_render_state = painter.render_state(); diff --git a/crates/eframe/src/web/web_painter_wgpu.rs b/crates/eframe/src/web/web_painter_wgpu.rs index 1dcaf3ecb..80abb5bf7 100644 --- a/crates/eframe/src/web/web_painter_wgpu.rs +++ b/crates/eframe/src/web/web_painter_wgpu.rs @@ -3,7 +3,6 @@ use std::sync::Arc; use wasm_bindgen::JsValue; use web_sys::HtmlCanvasElement; -use egui::mutex::RwLock; use egui_wgpu::{renderer::ScreenDescriptor, RenderState, SurfaceErrorAction}; use crate::WebOptions; @@ -99,43 +98,20 @@ impl WebPainterWgpu { } .map_err(|err| format!("failed to create wgpu surface: {err}"))?; - let adapter = instance - .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: options.wgpu_options.power_preference, - force_fallback_adapter: false, - compatible_surface: None, - }) - .await - .ok_or_else(|| "No suitable GPU adapters found on the system".to_owned())?; - - let (device, queue) = adapter - .request_device( - &(*options.wgpu_options.device_descriptor)(&adapter), - None, // Capture doesn't work in the browser environment. - ) - .await - .map_err(|err| format!("Failed to find wgpu device: {}", err))?; - - let target_format = - egui_wgpu::preferred_framebuffer_format(&surface.get_capabilities(&adapter).formats); - - let depth_format = options.wgpu_options.depth_format; - let renderer = egui_wgpu::Renderer::new(&device, target_format, depth_format, 1); - let render_state = RenderState { - device: Arc::new(device), - queue: Arc::new(queue), - target_format, - renderer: Arc::new(RwLock::new(renderer)), - }; + let depth_format = egui_wgpu::depth_format_from_bits(options.depth_buffer, 0); + let render_state = + RenderState::create(&options.wgpu_options, &instance, &surface, depth_format, 1) + .await + .map_err(|err| err.to_string())?; let surface_configuration = wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: target_format, + format: render_state.target_format, width: 0, height: 0, present_mode: options.wgpu_options.present_mode, alpha_mode: wgpu::CompositeAlphaMode::Auto, - view_formats: vec![target_format], + view_formats: vec![render_state.target_format], }; log::debug!("wgpu painter initialized."); diff --git a/crates/egui-wgpu/Cargo.toml b/crates/egui-wgpu/Cargo.toml index 098bc15f5..ddbf34765 100644 --- a/crates/egui-wgpu/Cargo.toml +++ b/crates/egui-wgpu/Cargo.toml @@ -42,6 +42,7 @@ epaint = { version = "0.21.0", path = "../epaint", default-features = false, fea bytemuck = "1.7" log = { version = "0.4", features = ["std"] } +thiserror.workspace = true type-map = "0.5.0" wgpu = "0.16.0" diff --git a/crates/egui-wgpu/src/lib.rs b/crates/egui-wgpu/src/lib.rs index 2e76b4571..485e00af7 100644 --- a/crates/egui-wgpu/src/lib.rs +++ b/crates/egui-wgpu/src/lib.rs @@ -21,15 +21,80 @@ use std::sync::Arc; use epaint::mutex::RwLock; +#[derive(thiserror::Error, Debug)] +pub enum WgpuError { + #[error("Failed to create wgpu adapter, no suitable adapter found.")] + NoSuitableAdapterFound, + + #[error("There was no valid format for the surface at all.")] + NoSurfaceFormatsAvailable, + + #[error(transparent)] + RequestDeviceError(#[from] wgpu::RequestDeviceError), + + #[error(transparent)] + CreateSurfaceError(#[from] wgpu::CreateSurfaceError), +} + /// Access to the render state for egui. #[derive(Clone)] pub struct RenderState { + /// Wgpu adapter used for rendering. + pub adapter: Arc, + + /// Wgpu device used for rendering, created from the adapter. pub device: Arc, + + /// Wgpu queue used for rendering, created from the adapter. pub queue: Arc, + + /// The target texture format used for presenting to the window. pub target_format: wgpu::TextureFormat, + + /// Egui renderer responsible for drawing the UI. pub renderer: Arc>, } +impl RenderState { + /// Creates a new `RenderState`, containing everything needed for drawing egui with wgpu. + /// + /// # Errors + /// Wgpu initialization may fail due to incompatible hardware or driver for a given config. + pub async fn create( + config: &WgpuConfiguration, + instance: &wgpu::Instance, + surface: &wgpu::Surface, + depth_format: Option, + msaa_samples: u32, + ) -> Result { + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: config.power_preference, + compatible_surface: Some(surface), + force_fallback_adapter: false, + }) + .await + .ok_or(WgpuError::NoSuitableAdapterFound)?; + + let target_format = + crate::preferred_framebuffer_format(&surface.get_capabilities(&adapter).formats)?; + + let (device, queue) = adapter + .request_device(&(*config.device_descriptor)(&adapter), None) + .await?; + + let renderer = Renderer::new(&device, target_format, depth_format, msaa_samples); + + Ok(RenderState { + adapter: Arc::new(adapter), + device: Arc::new(device), + queue: Arc::new(queue), + target_format, + renderer: Arc::new(RwLock::new(renderer)), + }) + } +} + /// Specifies which action should be taken as consequence of a [`wgpu::SurfaceError`] pub enum SurfaceErrorAction { /// Do nothing and skip the current frame. @@ -56,8 +121,6 @@ pub struct WgpuConfiguration { /// Callback for surface errors. pub on_surface_error: Arc SurfaceErrorAction>, - - pub depth_format: Option, } impl Default for WgpuConfiguration { @@ -88,7 +151,6 @@ impl Default for WgpuConfiguration { present_mode: wgpu::PresentMode::AutoVsync, power_preference: wgpu::util::power_preference_from_env() .unwrap_or(wgpu::PowerPreference::HighPerformance), - depth_format: None, on_surface_error: Arc::new(|err| { if err == wgpu::SurfaceError::Outdated { @@ -105,50 +167,40 @@ impl Default for WgpuConfiguration { } /// Find the framebuffer format that egui prefers -pub fn preferred_framebuffer_format(formats: &[wgpu::TextureFormat]) -> wgpu::TextureFormat { +/// +/// # Errors +/// Returns [`WgpuError::NoSurfaceFormatsAvailable`] if the given list of formats is empty. +pub fn preferred_framebuffer_format( + formats: &[wgpu::TextureFormat], +) -> Result { for &format in formats { if matches!( format, wgpu::TextureFormat::Rgba8Unorm | wgpu::TextureFormat::Bgra8Unorm ) { - return format; + return Ok(format); } } - formats[0] // take the first -} -// maybe use this-error? -#[derive(Debug)] -pub enum WgpuError { - DeviceError(wgpu::RequestDeviceError), - SurfaceError(wgpu::CreateSurfaceError), -} - -impl std::fmt::Display for WgpuError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - std::fmt::Debug::fmt(self, f) - } -} -impl std::error::Error for WgpuError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - WgpuError::DeviceError(e) => e.source(), - WgpuError::SurfaceError(e) => e.source(), - } - } + formats + .get(0) + .copied() + .ok_or(WgpuError::NoSurfaceFormatsAvailable) } -impl From for WgpuError { - fn from(e: wgpu::RequestDeviceError) -> Self { - Self::DeviceError(e) +/// Take's epi's depth/stencil bits and returns the corresponding wgpu format. +pub fn depth_format_from_bits(depth_buffer: u8, stencil_buffer: u8) -> Option { + match (depth_buffer, stencil_buffer) { + (0, 8) => Some(wgpu::TextureFormat::Stencil8), + (16, 0) => Some(wgpu::TextureFormat::Depth16Unorm), + (24, 0) => Some(wgpu::TextureFormat::Depth24Plus), + (24, 8) => Some(wgpu::TextureFormat::Depth24PlusStencil8), + (32, 0) => Some(wgpu::TextureFormat::Depth32Float), + (32, 8) => Some(wgpu::TextureFormat::Depth32FloatStencil8), + _ => None, } } -impl From for WgpuError { - fn from(e: wgpu::CreateSurfaceError) -> Self { - Self::SurfaceError(e) - } -} // --------------------------------------------------------------------------- /// Profiling macro for feature "puffin" diff --git a/crates/egui-wgpu/src/winit.rs b/crates/egui-wgpu/src/winit.rs index 4210c2f99..3e5781d4e 100644 --- a/crates/egui-wgpu/src/winit.rs +++ b/crates/egui-wgpu/src/winit.rs @@ -1,8 +1,6 @@ use std::sync::Arc; -use epaint::{self, mutex::RwLock}; - -use crate::{renderer, RenderState, Renderer, SurfaceErrorAction, WgpuConfiguration}; +use crate::{renderer, RenderState, SurfaceErrorAction, WgpuConfiguration}; struct SurfaceState { surface: wgpu::Surface, @@ -83,7 +81,6 @@ pub struct Painter { screen_capture_state: Option, instance: wgpu::Instance, - adapter: Option, render_state: Option, surface_state: Option, } @@ -104,7 +101,7 @@ impl Painter { pub fn new( configuration: WgpuConfiguration, msaa_samples: u32, - depth_bits: u8, + depth_format: Option, support_transparent_backbuffer: bool, ) -> Self { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { @@ -116,12 +113,11 @@ impl Painter { configuration, msaa_samples, support_transparent_backbuffer, - depth_format: (depth_bits > 0).then_some(wgpu::TextureFormat::Depth32Float), + depth_format, depth_texture_view: None, screen_capture_state: None, instance, - adapter: None, render_state: None, surface_state: None, msaa_texture_view: None, @@ -135,60 +131,6 @@ impl Painter { self.render_state.clone() } - async fn init_render_state( - &self, - adapter: &wgpu::Adapter, - target_format: wgpu::TextureFormat, - ) -> Result { - adapter - .request_device(&(*self.configuration.device_descriptor)(adapter), None) - .await - .map(|(device, queue)| { - let renderer = - Renderer::new(&device, target_format, self.depth_format, self.msaa_samples); - RenderState { - device: Arc::new(device), - queue: Arc::new(queue), - target_format, - renderer: Arc::new(RwLock::new(renderer)), - } - }) - } - - // We want to defer the initialization of our render state until we have a surface - // so we can take its format into account. - // - // After we've initialized our render state once though we expect all future surfaces - // will have the same format and so this render state will remain valid. - async fn ensure_render_state_for_surface( - &mut self, - surface: &wgpu::Surface, - ) -> Result<(), wgpu::RequestDeviceError> { - if self.adapter.is_none() { - self.adapter = self - .instance - .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: self.configuration.power_preference, - compatible_surface: Some(surface), - force_fallback_adapter: false, - }) - .await; - } - if self.render_state.is_none() { - match &self.adapter { - Some(adapter) => { - let swapchain_format = crate::preferred_framebuffer_format( - &surface.get_capabilities(adapter).formats, - ); - let rs = self.init_render_state(adapter, swapchain_format).await?; - self.render_state = Some(rs); - } - None => return Err(wgpu::RequestDeviceError {}), - } - } - Ok(()) - } - fn configure_surface( surface_state: &SurfaceState, render_state: &RenderState, @@ -235,20 +177,31 @@ impl Painter { /// /// # Errors /// If the provided wgpu configuration does not match an available device. - pub async unsafe fn set_window( + pub async fn set_window( &mut self, window: Option<&winit::window::Window>, ) -> Result<(), crate::WgpuError> { match window { Some(window) => { - let surface = self.instance.create_surface(&window)?; + let surface = unsafe { self.instance.create_surface(&window)? }; - self.ensure_render_state_for_surface(&surface).await?; + let render_state = if let Some(render_state) = &self.render_state { + render_state + } else { + let render_state = RenderState::create( + &self.configuration, + &self.instance, + &surface, + self.depth_format, + self.msaa_samples, + ) + .await?; + self.render_state.get_or_insert(render_state) + }; let alpha_mode = if self.support_transparent_backbuffer { - let supported_alpha_modes = surface - .get_capabilities(self.adapter.as_ref().unwrap()) - .alpha_modes; + let supported_alpha_modes = + surface.get_capabilities(&render_state.adapter).alpha_modes; // Prefer pre multiplied over post multiplied! if supported_alpha_modes.contains(&wgpu::CompositeAlphaMode::PreMultiplied) {