//! Connect a window with a renderer. use crate::{Backend, Color, Error, Primitive, Renderer, Settings, Viewport}; use futures::stream::{self, StreamExt}; use iced_graphics::window::compositor; use iced_native::futures; use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; use std::marker::PhantomData; /// A window graphics backend for iced powered by `wgpu`. #[allow(missing_debug_implementations)] pub struct Compositor { settings: Settings, instance: wgpu::Instance, adapter: wgpu::Adapter, device: wgpu::Device, queue: wgpu::Queue, format: wgpu::TextureFormat, theme: PhantomData, } 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<&W>, ) -> Option { let instance = wgpu::Instance::new(settings.internal_backend); log::info!("{:#?}", settings); #[cfg(not(target_arch = "wasm32"))] if log::max_level() >= log::LevelFilter::Info { let available_adapters: Vec<_> = instance .enumerate_adapters(settings.internal_backend) .map(|adapter| adapter.get_info()) .collect(); log::info!("Available adapters: {:#?}", available_adapters); } #[allow(unsafe_code)] let compatible_surface = compatible_window .map(|window| unsafe { instance.create_surface(window) }); let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions { power_preference: if settings.antialiasing.is_none() { wgpu::PowerPreference::LowPower } else { wgpu::PowerPreference::HighPerformance }, compatible_surface: compatible_surface.as_ref(), force_fallback_adapter: false, }) .await?; log::info!("Selected: {:#?}", adapter.get_info()); let format = compatible_surface.as_ref().and_then(|surface| { surface.get_supported_formats(&adapter).first().copied() })?; log::info!("Selected format: {:?}", format); #[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 (device, queue) = stream::iter(limits) .filter_map(|limits| async { adapter.request_device( &wgpu::DeviceDescriptor { label: Some( "iced_wgpu::window::compositor device descriptor", ), features: wgpu::Features::empty(), limits, }, None, ).await.ok() }) .boxed() .next() .await?; Some(Compositor { instance, settings, adapter, device, queue, format, theme: PhantomData, }) } /// Creates a new rendering [`Backend`] for this [`Compositor`]. pub fn create_backend(&self) -> Backend { Backend::new(&self.device, &self.queue, self.settings, self.format) } } /// Creates a [`Compositor`] and its [`Backend`] for the given [`Settings`] and /// window. pub fn new( settings: Settings, compatible_window: Option<&W>, ) -> Result<(Compositor, Backend), Error> { let compositor = futures::executor::block_on(Compositor::request( settings, compatible_window, )) .ok_or(Error::GraphicsAdapterNotFound)?; let backend = compositor.create_backend(); Ok((compositor, backend)) } /// Presents the given primitives with the given [`Compositor`] and [`Backend`]. pub fn present>( compositor: &mut Compositor, backend: &mut Backend, surface: &mut wgpu::Surface, primitives: &[Primitive], viewport: &Viewport, background_color: Color, overlay: &[T], ) -> Result<(), compositor::SurfaceError> { match surface.get_current_texture() { Ok(frame) => { let mut encoder = compositor.device.create_command_encoder( &wgpu::CommandEncoderDescriptor { label: Some("iced_wgpu encoder"), }, ); let view = &frame .texture .create_view(&wgpu::TextureViewDescriptor::default()); backend.present( &compositor.device, &compositor.queue, &mut encoder, Some(background_color), view, primitives, viewport, overlay, ); // Submit work let _submission = compositor.queue.submit(Some(encoder.finish())); 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) } }, } } impl iced_graphics::window::Compositor for Compositor { type Settings = Settings; type Renderer = Renderer; type Surface = wgpu::Surface; fn new( settings: Self::Settings, compatible_window: Option<&W>, ) -> Result<(Self, Self::Renderer), Error> { let (compositor, backend) = new(settings, compatible_window)?; Ok((compositor, Renderer::new(backend))) } fn create_surface( &mut self, window: &W, ) -> wgpu::Surface { #[allow(unsafe_code)] unsafe { self.instance.create_surface(window) } } fn configure_surface( &mut self, surface: &mut Self::Surface, width: u32, height: u32, ) { surface.configure( &self.device, &wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format: self.format, present_mode: self.settings.present_mode, width, height, alpha_mode: wgpu::CompositeAlphaMode::Auto, }, ); } 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, overlay: &[T], ) -> Result<(), compositor::SurfaceError> { renderer.with_primitives(|backend, primitives| { present( self, backend, surface, primitives, viewport, background_color, overlay, ) }) } }