//! Connect a window with a renderer. use crate::core::Color; use crate::graphics::color; use crate::graphics::compositor; use crate::graphics::error; use crate::graphics::{self, Viewport}; use crate::settings::{self, Settings}; use crate::{Engine, Renderer}; /// A window graphics backend for iced powered by `wgpu`. #[allow(missing_debug_implementations)] 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, ) -> Result { let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { backends: settings.backends, flags: if cfg!(feature = "strict-assertions") { wgpu::InstanceFlags::debugging() } else { wgpu::InstanceFlags::empty() }, ..Default::default() }); log::info!("{settings:#?}"); #[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 compatible_surface = compatible_window .and_then(|window| instance.create_surface(window).ok()); let adapter_options = wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::from_env().unwrap_or( if settings.antialiasing.is_none() { wgpu::PowerPreference::LowPower } else { wgpu::PowerPreference::HighPerformance }, ), compatible_surface: compatible_surface.as_ref(), force_fallback_adapter: false, }; let adapter = instance .request_adapter(&adapter_options) .await .ok_or(Error::NoAdapterFound(format!("{:?}", adapter_options)))?; log::info!("Selected: {:#?}", adapter.get_info()); let (format, alpha_mode) = compatible_surface .as_ref() .and_then(|surface| { let capabilities = surface.get_capabilities(&adapter); let mut formats = capabilities.formats.iter().copied(); log::info!("Available formats: {formats:#?}"); 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, ..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, }, None, ) .await; match result { Ok((device, queue)) => { let engine = Engine::new( &adapter, device, queue, format, settings.antialiasing, ); 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, ) -> Result { Compositor::request(settings, Some(compatible_window)).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, compatible_window: W, 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).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 fetch_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) } }