diff --git a/crates/eframe/src/web/web_painter_wgpu.rs b/crates/eframe/src/web/web_painter_wgpu.rs index 2f4945e5270..0ec0f8239d1 100644 --- a/crates/eframe/src/web/web_painter_wgpu.rs +++ b/crates/eframe/src/web/web_painter_wgpu.rs @@ -87,42 +87,100 @@ impl WebPainterWgpu { pub async fn new(canvas_id: &str, options: &WebOptions) -> Result { log::debug!("Creating wgpu painter"); - { + let mut backends = options.wgpu_options.supported_backends; + + // Don't try WebGPU if we're not in a secure context. + if backends.contains(wgpu::Backends::BROWSER_WEBGPU) { let is_secure_context = web_sys::window().map_or(false, |w| w.is_secure_context()); if !is_secure_context { log::info!( - "WebGPU is only available in secure contexts, i.e. on HTTPS and on localhost" + "WebGPU is only available in secure contexts, i.e. on HTTPS and on localhost." ); + + // Don't try WebGPU since we established now that it will fail. + backends.remove(wgpu::Backends::BROWSER_WEBGPU); + + if backends.is_empty() { + return Err("No available supported graphics backends.".to_owned()); + } } } - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends: options.wgpu_options.supported_backends, + log::debug!("Creating wgpu instance with backends {:?}", backends); + let mut instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends, ..Default::default() }); - let canvas = super::canvas_element_or_die(canvas_id); + // It can happen that a browser advertises WebGPU support, but then fails to create a + // suitable adapter. As of writing this happens for example on Linux with Chrome 121. + // + // Since WebGPU is handled in a special way in wgpu, we have to recreate the instance + // if we instead want to try with WebGL. + // + // To make matters worse, once a canvas has been used with either WebGL or WebGPU, + // we can't go back and change that without replacing the canvas (which is hard to do from here). + // Therefore, we have to create the surface *after* requesting the adapter. + // However, wgpu offers to pass in a surface on adapter creation to ensure it is actually compatible with the chosen backend. + // This in turn isn't all that important on the web, but it still makes sense for the design of + // `egui::RenderState`! + // Therefore, we have to first check if it's possible to create a WebGPU adapter, + // and if it is not, start over with a WebGL instance. + // + // Note that we also might needlessly try this here if wgpu already determined that there's no + // WebGPU support in the first place. This is not a huge problem since it fails very fast, but + // it would be nice to avoid this. See https://github.com/gfx-rs/wgpu/issues/5142 + if backends.contains(wgpu::Backends::BROWSER_WEBGPU) { + log::debug!("Attempting to create WebGPU adapter to check for support."); + if let Some(adapter) = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: options.wgpu_options.power_preference, + compatible_surface: None, + force_fallback_adapter: false, + }) + .await + { + // WebGPU doesn't spec yet a destroy on the adapter, only on the device. + //adapter.destroy(); + log::debug!( + "Successfully created WebGPU adapter, WebGPU confirmed to be supported!" + ); + } else { + log::debug!("Failed to create WebGPU adapter."); + + if backends.contains(wgpu::Backends::GL) { + log::debug!("Recreating wgpu instance with WebGL backend only."); + backends = wgpu::Backends::GL; + instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends, + ..Default::default() + }); + } else { + return Err( + "Failed to create WebGPU adapter and WebGL was not enabled.".to_owned() + ); + } + } + } + let canvas = super::canvas_element_or_die(canvas_id); let surface = instance .create_surface(wgpu::SurfaceTarget::Canvas(canvas.clone())) .map_err(|err| format!("failed to create wgpu surface: {err}"))?; 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 (width, height) = (0, 0); - let surface_configuration = wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format: render_state.target_format, present_mode: options.wgpu_options.present_mode, - alpha_mode: wgpu::CompositeAlphaMode::Auto, view_formats: vec![render_state.target_format], ..surface - .get_default_config(&render_state.adapter, width, height) + .get_default_config(&render_state.adapter, 0, 0) // Width/height is set later. .ok_or("The surface isn't supported by this adapter")? };