//! Connect a window with a renderer. use wgpu::Backends; use crate::core::Color; use crate::graphics::color; use crate::graphics::compositor; use crate::graphics::error; use crate::graphics::{self, Shell, Viewport}; use crate::settings::{self, Settings}; use crate::{Engine, Renderer}; #[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))] use super::wayland::get_wayland_device_ids; #[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))] use super::x11::get_x11_device_ids; use std::future::Future; /// A window graphics backend for iced powered by `wgpu`. pub struct Compositor { instance: wgpu::Instance, adapter: wgpu::Adapter, format: wgpu::TextureFormat, alpha_mode: wgpu::CompositeAlphaMode, engine: Engine, settings: Settings, } /// A compositor error. #[derive(Debug, Clone, thiserror::Error)] pub enum Error { /// The surface creation failed. #[error("the surface creation failed: {0}")] SurfaceCreationFailed(#[from] wgpu::CreateSurfaceError), /// The surface is not compatible. #[error("the surface is not compatible")] IncompatibleSurface, /// No adapter was found for the options requested. #[error("no adapter was found for the options requested: {0:?}")] NoAdapterFound(String), /// No device request succeeded. #[error("no device request succeeded: {0:?}")] RequestDeviceFailed(Vec<(wgpu::Limits, wgpu::RequestDeviceError)>), } impl From for graphics::Error { fn from(error: Error) -> Self { Self::GraphicsAdapterNotFound { backend: "wgpu", reason: error::Reason::RequestFailed(error.to_string()), } } } impl Compositor { /// Requests a new [`Compositor`] with the given [`Settings`]. /// /// Returns `None` if no compatible graphics adapter could be found. pub async fn request( settings: Settings, compatible_window: Option, shell: Shell, ) -> Result { #[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))] let ids = compatible_window.as_ref().and_then(|window| { get_wayland_device_ids(window) .or_else(|| get_x11_device_ids(window)) }); // HACK: // 1. If we specifically didn't select an nvidia gpu // 2. and the user didn't specifically request an nvidia gpu // 3. and the user didn't set an adapter name, // 4. and the user didn't request the high power pref // => don't load the nvidia icd, as it might power on the gpu in hybrid setups causing severe delays #[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))] if !matches!(ids, Some((0x10de, _))) && std::env::var_os("__NV_PRIME_RENDER_OFFLOAD") .is_none_or(|var| var == "0") && std::env::var_os("WGPU_ADAPTER_NAME").is_none() && std::env::var("WGPU_POWER_PREF").as_deref() != Ok("high") { unsafe { std::env::set_var("VK_LOADER_DRIVERS_DISABLE", "nvidia*"); } } // XXX: try getting non-gl adapter first // Otherwise it will panic when it drops the available adapters! let non_gl_backends = settings.backends.difference(Backends::GL); // only load the instance after setting environment variables, this initializes the vulkan loader let mut instance = wgpu::util::new_instance_with_webgpu_detection( &wgpu::InstanceDescriptor { backends: non_gl_backends, flags: if cfg!(feature = "strict-assertions") { wgpu::InstanceFlags::debugging() } else { wgpu::InstanceFlags::empty() }, ..Default::default() }, ) .await; log::info!("{settings:#?}"); unsafe { std::env::remove_var("VK_LOADER_DRIVERS_DISABLE"); } // #[cfg(not(target_arch = "wasm32"))] // if log::max_level() >= log::LevelFilter::Info { // let available_adapters: Vec<_> = instance // .enumerate_adapters(settings.backends) // .iter() // .map(wgpu::Adapter::get_info) // .collect(); // log::info!("Available adapters: {available_adapters:#?}"); // } #[allow(unsafe_code)] let mut compatible_surface = compatible_window .clone() .and_then(|window| instance.create_surface(window).ok()); let mut adapter_options = wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::from_env() .unwrap_or(wgpu::PowerPreference::HighPerformance), compatible_surface: compatible_surface.as_ref(), force_fallback_adapter: false, }; let mut adapter = None; #[cfg_attr(not(unix), allow(dead_code))] if std::env::var_os("WGPU_ADAPTER_NAME").is_none() { #[cfg(all( unix, not(target_os = "macos"), not(target_os = "redox") ))] if let Some((vendor_id, device_id)) = ids { adapter = instance .enumerate_adapters(settings.backends) .into_iter() .filter(|adapter| { let info = adapter.get_info(); info.device == device_id as u32 && info.vendor == vendor_id as u32 }) .find(|adapter| { if let Some(surface) = compatible_surface.as_ref() { adapter.is_surface_supported(surface) } else { true } }); } } else if let Ok(name) = std::env::var("WGPU_ADAPTER_NAME") { adapter = instance .enumerate_adapters(settings.backends) .into_iter() .filter(|adapter| { let info = adapter.get_info(); info.name == name }) .find(|adapter| { if let Some(surface) = compatible_surface.as_ref() { adapter.is_surface_supported(surface) } else { true } }); } let adapter = match adapter { Some(adapter) => adapter, None => { // fall back to allowing GL backend if enabled instance = wgpu::util::new_instance_with_webgpu_detection( &wgpu::InstanceDescriptor { backends: settings.backends, flags: if cfg!(feature = "strict-assertions") { wgpu::InstanceFlags::debugging() } else { wgpu::InstanceFlags::empty() }, ..Default::default() }, ) .await; compatible_surface = compatible_window .and_then(|window| instance.create_surface(window).ok()); adapter_options = wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::from_env() .unwrap_or(wgpu::PowerPreference::HighPerformance), compatible_surface: compatible_surface.as_ref(), force_fallback_adapter: false, }; instance.request_adapter(&adapter_options).await.map_err( |_| Error::NoAdapterFound(format!("{:?}", adapter_options)), )? } }; log::info!("Selected: {:#?}", adapter.get_info()); let (format, alpha_mode) = compatible_surface .as_ref() .and_then(|surface: &wgpu::Surface<'_>| { let capabilities = surface.get_capabilities(&adapter); let formats = capabilities.formats.iter().copied(); log::info!("Available formats: {formats:#?}"); const BLACKLIST: &[wgpu::TextureFormat] = &[ wgpu::TextureFormat::Rgb10a2Unorm, wgpu::TextureFormat::Rgb10a2Uint, ]; let mut formats = formats.filter(|format| { format.required_features() == wgpu::Features::empty() && !BLACKLIST.contains(format) }); let format = if color::GAMMA_CORRECTION { formats.find(wgpu::TextureFormat::is_srgb) } else { formats.find(|format| !wgpu::TextureFormat::is_srgb(format)) }; let format = format.or_else(|| { log::warn!("No format found!"); capabilities.formats.first().copied() }); let alpha_modes = capabilities.alpha_modes; log::info!("Available alpha modes: {alpha_modes:#?}"); let preferred_alpha = if alpha_modes .contains(&wgpu::CompositeAlphaMode::PostMultiplied) { wgpu::CompositeAlphaMode::PostMultiplied } else if alpha_modes .contains(&wgpu::CompositeAlphaMode::PreMultiplied) { wgpu::CompositeAlphaMode::PreMultiplied } else { wgpu::CompositeAlphaMode::Auto }; format.zip(Some(preferred_alpha)) }) .ok_or(Error::IncompatibleSurface)?; log::info!( "Selected format: {format:?} with alpha mode: {alpha_mode:?}" ); #[cfg(target_arch = "wasm32")] let limits = [wgpu::Limits::downlevel_webgl2_defaults() .using_resolution(adapter.limits())]; #[cfg(not(target_arch = "wasm32"))] let limits = [wgpu::Limits::default(), wgpu::Limits::downlevel_defaults()]; let limits = limits.into_iter().map(|limits| wgpu::Limits { max_bind_groups: 2, max_non_sampler_bindings: 2048, ..limits }); let mut errors = Vec::new(); for required_limits in limits { let result = adapter .request_device(&wgpu::DeviceDescriptor { label: Some( "iced_wgpu::window::compositor device descriptor", ), required_features: wgpu::Features::empty(), required_limits: required_limits.clone(), memory_hints: wgpu::MemoryHints::MemoryUsage, trace: wgpu::Trace::Off, experimental_features: wgpu::ExperimentalFeatures::disabled( ), }) .await; match result { Ok((device, queue)) => { let engine = Engine::new( &adapter, device, queue, format, settings.antialiasing, shell, ); return Ok(Compositor { instance, adapter, format, alpha_mode, engine, settings, }); } Err(error) => { errors.push((required_limits, error)); } } } Err(Error::RequestDeviceFailed(errors)) } } /// Creates a [`Compositor`] with the given [`Settings`] and window. pub async fn new( settings: Settings, compatible_window: W, shell: Shell, ) -> Result { Compositor::request(settings, Some(compatible_window), shell).await } /// Presents the given primitives with the given [`Compositor`]. pub fn present( renderer: &mut Renderer, surface: &mut wgpu::Surface<'static>, viewport: &Viewport, background_color: Color, on_pre_present: impl FnOnce(), ) -> Result<(), compositor::SurfaceError> { match surface.get_current_texture() { Ok(frame) => { let view = &frame .texture .create_view(&wgpu::TextureViewDescriptor::default()); let _submission = renderer.present( Some(background_color), frame.texture.format(), view, viewport, ); // Present the frame on_pre_present(); frame.present(); Ok(()) } Err(error) => match error { wgpu::SurfaceError::Timeout => { Err(compositor::SurfaceError::Timeout) } wgpu::SurfaceError::Outdated => { Err(compositor::SurfaceError::Outdated) } wgpu::SurfaceError::Lost => Err(compositor::SurfaceError::Lost), wgpu::SurfaceError::OutOfMemory => { Err(compositor::SurfaceError::OutOfMemory) } wgpu::SurfaceError::Other => Err(compositor::SurfaceError::Other), }, } } impl graphics::Compositor for Compositor { type Renderer = Renderer; type Surface = wgpu::Surface<'static>; async fn with_backend( settings: graphics::Settings, _display: impl compositor::Display, compatible_window: impl compositor::Window + Clone, shell: Shell, backend: Option<&str>, ) -> Result { match backend { None | Some("wgpu") => { let mut settings = Settings::from(settings); if let Some(backends) = wgpu::Backends::from_env() { settings.backends = backends; } if let Some(present_mode) = settings::present_mode_from_env() { settings.present_mode = present_mode; } Ok(new(settings, compatible_window, shell).await?) } Some(backend) => Err(graphics::Error::GraphicsAdapterNotFound { backend: "wgpu", reason: error::Reason::DidNotMatch { preferred_backend: backend.to_owned(), }, }), } } fn create_renderer(&self) -> Self::Renderer { Renderer::new( self.engine.clone(), self.settings.default_font, self.settings.default_text_size, ) } fn create_surface( &mut self, window: W, width: u32, height: u32, ) -> Self::Surface { let mut surface = self .instance .create_surface(window) .expect("Create surface"); if width > 0 && height > 0 { self.configure_surface(&mut surface, width, height); } surface } fn configure_surface( &mut self, surface: &mut Self::Surface, width: u32, height: u32, ) { surface.configure( &self.engine.device, &wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format: self.format, present_mode: self.settings.present_mode, width, height, alpha_mode: self.alpha_mode, view_formats: vec![], desired_maximum_frame_latency: 1, }, ); } fn information(&self) -> compositor::Information { let information = self.adapter.get_info(); compositor::Information { adapter: information.name, backend: format!("{:?}", information.backend), } } fn present( &mut self, renderer: &mut Self::Renderer, surface: &mut Self::Surface, viewport: &Viewport, background_color: Color, on_pre_present: impl FnOnce(), ) -> Result<(), compositor::SurfaceError> { present( renderer, surface, viewport, background_color, on_pre_present, ) } fn screenshot( &mut self, renderer: &mut Self::Renderer, viewport: &Viewport, background_color: Color, ) -> Vec { renderer.screenshot(viewport, background_color) } }