iced_wgpu/window/
compositor.rs

1//! Connect a window with a renderer.
2use crate::core::Color;
3use crate::graphics::color;
4use crate::graphics::compositor;
5use crate::graphics::error;
6use crate::graphics::{self, Viewport};
7use crate::settings::{self, Settings};
8use crate::{Engine, Renderer};
9
10/// A window graphics backend for iced powered by `wgpu`.
11#[allow(missing_debug_implementations)]
12pub struct Compositor {
13    instance: wgpu::Instance,
14    adapter: wgpu::Adapter,
15    format: wgpu::TextureFormat,
16    alpha_mode: wgpu::CompositeAlphaMode,
17    engine: Engine,
18    settings: Settings,
19}
20
21/// A compositor error.
22#[derive(Debug, Clone, thiserror::Error)]
23pub enum Error {
24    /// The surface creation failed.
25    #[error("the surface creation failed: {0}")]
26    SurfaceCreationFailed(#[from] wgpu::CreateSurfaceError),
27    /// The surface is not compatible.
28    #[error("the surface is not compatible")]
29    IncompatibleSurface,
30    /// No adapter was found for the options requested.
31    #[error("no adapter was found for the options requested: {0:?}")]
32    NoAdapterFound(String),
33    /// No device request succeeded.
34    #[error("no device request succeeded: {0:?}")]
35    RequestDeviceFailed(Vec<(wgpu::Limits, wgpu::RequestDeviceError)>),
36}
37
38impl From<Error> for graphics::Error {
39    fn from(error: Error) -> Self {
40        Self::GraphicsAdapterNotFound {
41            backend: "wgpu",
42            reason: error::Reason::RequestFailed(error.to_string()),
43        }
44    }
45}
46
47impl Compositor {
48    /// Requests a new [`Compositor`] with the given [`Settings`].
49    ///
50    /// Returns `None` if no compatible graphics adapter could be found.
51    pub async fn request<W: compositor::Window>(
52        settings: Settings,
53        compatible_window: Option<W>,
54    ) -> Result<Self, Error> {
55        let instance = wgpu::util::new_instance_with_webgpu_detection(
56            &wgpu::InstanceDescriptor {
57                backends: settings.backends,
58                flags: if cfg!(feature = "strict-assertions") {
59                    wgpu::InstanceFlags::debugging()
60                } else {
61                    wgpu::InstanceFlags::empty()
62                },
63                ..Default::default()
64            },
65        )
66        .await;
67
68        log::info!("{settings:#?}");
69
70        #[cfg(not(target_arch = "wasm32"))]
71        if log::max_level() >= log::LevelFilter::Info {
72            let available_adapters: Vec<_> = instance
73                .enumerate_adapters(settings.backends)
74                .iter()
75                .map(wgpu::Adapter::get_info)
76                .collect();
77            log::info!("Available adapters: {available_adapters:#?}");
78        }
79
80        #[allow(unsafe_code)]
81        let compatible_surface = compatible_window
82            .and_then(|window| instance.create_surface(window).ok());
83
84        let adapter_options = wgpu::RequestAdapterOptions {
85            power_preference: wgpu::PowerPreference::from_env().unwrap_or(
86                if settings.antialiasing.is_none() {
87                    wgpu::PowerPreference::LowPower
88                } else {
89                    wgpu::PowerPreference::HighPerformance
90                },
91            ),
92            compatible_surface: compatible_surface.as_ref(),
93            force_fallback_adapter: false,
94        };
95
96        let adapter =
97            instance.request_adapter(&adapter_options).await.map_err(
98                |_error| Error::NoAdapterFound(format!("{adapter_options:?}")),
99            )?;
100
101        log::info!("Selected: {:#?}", adapter.get_info());
102
103        let (format, alpha_mode) = compatible_surface
104            .as_ref()
105            .and_then(|surface| {
106                let capabilities = surface.get_capabilities(&adapter);
107
108                let mut formats = capabilities.formats.iter().copied();
109
110                log::info!("Available formats: {formats:#?}");
111
112                let format = if color::GAMMA_CORRECTION {
113                    formats.find(wgpu::TextureFormat::is_srgb)
114                } else {
115                    formats.find(|format| !wgpu::TextureFormat::is_srgb(format))
116                };
117
118                let format = format.or_else(|| {
119                    log::warn!("No format found!");
120
121                    capabilities.formats.first().copied()
122                });
123
124                let alpha_modes = capabilities.alpha_modes;
125
126                log::info!("Available alpha modes: {alpha_modes:#?}");
127
128                let preferred_alpha = if alpha_modes
129                    .contains(&wgpu::CompositeAlphaMode::PostMultiplied)
130                {
131                    wgpu::CompositeAlphaMode::PostMultiplied
132                } else if alpha_modes
133                    .contains(&wgpu::CompositeAlphaMode::PreMultiplied)
134                {
135                    wgpu::CompositeAlphaMode::PreMultiplied
136                } else {
137                    wgpu::CompositeAlphaMode::Auto
138                };
139
140                format.zip(Some(preferred_alpha))
141            })
142            .ok_or(Error::IncompatibleSurface)?;
143
144        log::info!(
145            "Selected format: {format:?} with alpha mode: {alpha_mode:?}"
146        );
147
148        #[cfg(target_arch = "wasm32")]
149        let limits = [wgpu::Limits::downlevel_webgl2_defaults()
150            .using_resolution(adapter.limits())];
151
152        #[cfg(not(target_arch = "wasm32"))]
153        let limits =
154            [wgpu::Limits::default(), wgpu::Limits::downlevel_defaults()];
155
156        let limits = limits.into_iter().map(|limits| wgpu::Limits {
157            max_bind_groups: 2,
158            ..limits
159        });
160
161        let mut errors = Vec::new();
162
163        for required_limits in limits {
164            let result = adapter
165                .request_device(&wgpu::DeviceDescriptor {
166                    label: Some(
167                        "iced_wgpu::window::compositor device descriptor",
168                    ),
169                    required_features: wgpu::Features::empty(),
170                    required_limits: required_limits.clone(),
171                    memory_hints: wgpu::MemoryHints::MemoryUsage,
172                    trace: wgpu::Trace::Off,
173                })
174                .await;
175
176            match result {
177                Ok((device, queue)) => {
178                    let engine = Engine::new(
179                        &adapter,
180                        device,
181                        queue,
182                        format,
183                        settings.antialiasing,
184                    );
185
186                    return Ok(Compositor {
187                        instance,
188                        adapter,
189                        format,
190                        alpha_mode,
191                        engine,
192                        settings,
193                    });
194                }
195                Err(error) => {
196                    errors.push((required_limits, error));
197                }
198            }
199        }
200
201        Err(Error::RequestDeviceFailed(errors))
202    }
203}
204
205/// Creates a [`Compositor`] with the given [`Settings`] and window.
206pub async fn new<W: compositor::Window>(
207    settings: Settings,
208    compatible_window: W,
209) -> Result<Compositor, Error> {
210    Compositor::request(settings, Some(compatible_window)).await
211}
212
213/// Presents the given primitives with the given [`Compositor`].
214pub fn present(
215    renderer: &mut Renderer,
216    surface: &mut wgpu::Surface<'static>,
217    viewport: &Viewport,
218    background_color: Color,
219    on_pre_present: impl FnOnce(),
220) -> Result<(), compositor::SurfaceError> {
221    match surface.get_current_texture() {
222        Ok(frame) => {
223            let view = &frame
224                .texture
225                .create_view(&wgpu::TextureViewDescriptor::default());
226
227            let _submission = renderer.present(
228                Some(background_color),
229                frame.texture.format(),
230                view,
231                viewport,
232            );
233
234            // Present the frame
235            on_pre_present();
236            frame.present();
237
238            Ok(())
239        }
240        Err(error) => match error {
241            wgpu::SurfaceError::Timeout => {
242                Err(compositor::SurfaceError::Timeout)
243            }
244            wgpu::SurfaceError::Outdated => {
245                Err(compositor::SurfaceError::Outdated)
246            }
247            wgpu::SurfaceError::Lost => Err(compositor::SurfaceError::Lost),
248            wgpu::SurfaceError::OutOfMemory => {
249                Err(compositor::SurfaceError::OutOfMemory)
250            }
251            wgpu::SurfaceError::Other => Err(compositor::SurfaceError::Other),
252        },
253    }
254}
255
256impl graphics::Compositor for Compositor {
257    type Renderer = Renderer;
258    type Surface = wgpu::Surface<'static>;
259
260    async fn with_backend<W: compositor::Window>(
261        settings: graphics::Settings,
262        compatible_window: W,
263        backend: Option<&str>,
264    ) -> Result<Self, graphics::Error> {
265        match backend {
266            None | Some("wgpu") => {
267                let mut settings = Settings::from(settings);
268
269                if let Some(backends) = wgpu::Backends::from_env() {
270                    settings.backends = backends;
271                }
272
273                if let Some(present_mode) = settings::present_mode_from_env() {
274                    settings.present_mode = present_mode;
275                }
276
277                Ok(new(settings, compatible_window).await?)
278            }
279            Some(backend) => Err(graphics::Error::GraphicsAdapterNotFound {
280                backend: "wgpu",
281                reason: error::Reason::DidNotMatch {
282                    preferred_backend: backend.to_owned(),
283                },
284            }),
285        }
286    }
287
288    fn create_renderer(&self) -> Self::Renderer {
289        Renderer::new(
290            self.engine.clone(),
291            self.settings.default_font,
292            self.settings.default_text_size,
293        )
294    }
295
296    fn create_surface<W: compositor::Window>(
297        &mut self,
298        window: W,
299        width: u32,
300        height: u32,
301    ) -> Self::Surface {
302        let mut surface = self
303            .instance
304            .create_surface(window)
305            .expect("Create surface");
306
307        if width > 0 && height > 0 {
308            self.configure_surface(&mut surface, width, height);
309        }
310
311        surface
312    }
313
314    fn configure_surface(
315        &mut self,
316        surface: &mut Self::Surface,
317        width: u32,
318        height: u32,
319    ) {
320        surface.configure(
321            &self.engine.device,
322            &wgpu::SurfaceConfiguration {
323                usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
324                format: self.format,
325                present_mode: self.settings.present_mode,
326                width,
327                height,
328                alpha_mode: self.alpha_mode,
329                view_formats: vec![],
330                desired_maximum_frame_latency: 1,
331            },
332        );
333    }
334
335    fn fetch_information(&self) -> compositor::Information {
336        let information = self.adapter.get_info();
337
338        compositor::Information {
339            adapter: information.name,
340            backend: format!("{:?}", information.backend),
341        }
342    }
343
344    fn present(
345        &mut self,
346        renderer: &mut Self::Renderer,
347        surface: &mut Self::Surface,
348        viewport: &Viewport,
349        background_color: Color,
350        on_pre_present: impl FnOnce(),
351    ) -> Result<(), compositor::SurfaceError> {
352        present(
353            renderer,
354            surface,
355            viewport,
356            background_color,
357            on_pre_present,
358        )
359    }
360
361    fn screenshot(
362        &mut self,
363        renderer: &mut Self::Renderer,
364        viewport: &Viewport,
365        background_color: Color,
366    ) -> Vec<u8> {
367        renderer.screenshot(viewport, background_color)
368    }
369}