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