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, Shell, 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        shell: Shell,
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            max_non_sampler_bindings: 2048,
159            ..limits
160        });
161
162        let mut errors = Vec::new();
163
164        for required_limits in limits {
165            let result = adapter
166                .request_device(&wgpu::DeviceDescriptor {
167                    label: Some(
168                        "iced_wgpu::window::compositor device descriptor",
169                    ),
170                    required_features: wgpu::Features::empty(),
171                    required_limits: required_limits.clone(),
172                    memory_hints: wgpu::MemoryHints::MemoryUsage,
173                    trace: wgpu::Trace::Off,
174                })
175                .await;
176
177            match result {
178                Ok((device, queue)) => {
179                    let engine = Engine::new(
180                        &adapter,
181                        device,
182                        queue,
183                        format,
184                        settings.antialiasing,
185                        shell,
186                    );
187
188                    return Ok(Compositor {
189                        instance,
190                        adapter,
191                        format,
192                        alpha_mode,
193                        engine,
194                        settings,
195                    });
196                }
197                Err(error) => {
198                    errors.push((required_limits, error));
199                }
200            }
201        }
202
203        Err(Error::RequestDeviceFailed(errors))
204    }
205}
206
207/// Creates a [`Compositor`] with the given [`Settings`] and window.
208pub async fn new<W: compositor::Window>(
209    settings: Settings,
210    compatible_window: W,
211    shell: Shell,
212) -> Result<Compositor, Error> {
213    Compositor::request(settings, Some(compatible_window), shell).await
214}
215
216/// Presents the given primitives with the given [`Compositor`].
217pub fn present(
218    renderer: &mut Renderer,
219    surface: &mut wgpu::Surface<'static>,
220    viewport: &Viewport,
221    background_color: Color,
222    on_pre_present: impl FnOnce(),
223) -> Result<(), compositor::SurfaceError> {
224    match surface.get_current_texture() {
225        Ok(frame) => {
226            let view = &frame
227                .texture
228                .create_view(&wgpu::TextureViewDescriptor::default());
229
230            let _submission = renderer.present(
231                Some(background_color),
232                frame.texture.format(),
233                view,
234                viewport,
235            );
236
237            // Present the frame
238            on_pre_present();
239            frame.present();
240
241            Ok(())
242        }
243        Err(error) => match error {
244            wgpu::SurfaceError::Timeout => {
245                Err(compositor::SurfaceError::Timeout)
246            }
247            wgpu::SurfaceError::Outdated => {
248                Err(compositor::SurfaceError::Outdated)
249            }
250            wgpu::SurfaceError::Lost => Err(compositor::SurfaceError::Lost),
251            wgpu::SurfaceError::OutOfMemory => {
252                Err(compositor::SurfaceError::OutOfMemory)
253            }
254            wgpu::SurfaceError::Other => Err(compositor::SurfaceError::Other),
255        },
256    }
257}
258
259impl graphics::Compositor for Compositor {
260    type Renderer = Renderer;
261    type Surface = wgpu::Surface<'static>;
262
263    async fn with_backend<W: compositor::Window>(
264        settings: graphics::Settings,
265        compatible_window: W,
266        shell: Shell,
267        backend: Option<&str>,
268    ) -> Result<Self, graphics::Error> {
269        match backend {
270            None | Some("wgpu") => {
271                let mut settings = Settings::from(settings);
272
273                if let Some(backends) = wgpu::Backends::from_env() {
274                    settings.backends = backends;
275                }
276
277                if let Some(present_mode) = settings::present_mode_from_env() {
278                    settings.present_mode = present_mode;
279                }
280
281                Ok(new(settings, compatible_window, shell).await?)
282            }
283            Some(backend) => Err(graphics::Error::GraphicsAdapterNotFound {
284                backend: "wgpu",
285                reason: error::Reason::DidNotMatch {
286                    preferred_backend: backend.to_owned(),
287                },
288            }),
289        }
290    }
291
292    fn create_renderer(&self) -> Self::Renderer {
293        Renderer::new(
294            self.engine.clone(),
295            self.settings.default_font,
296            self.settings.default_text_size,
297        )
298    }
299
300    fn create_surface<W: compositor::Window>(
301        &mut self,
302        window: W,
303        width: u32,
304        height: u32,
305    ) -> Self::Surface {
306        let mut surface = self
307            .instance
308            .create_surface(window)
309            .expect("Create surface");
310
311        if width > 0 && height > 0 {
312            self.configure_surface(&mut surface, width, height);
313        }
314
315        surface
316    }
317
318    fn configure_surface(
319        &mut self,
320        surface: &mut Self::Surface,
321        width: u32,
322        height: u32,
323    ) {
324        surface.configure(
325            &self.engine.device,
326            &wgpu::SurfaceConfiguration {
327                usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
328                format: self.format,
329                present_mode: self.settings.present_mode,
330                width,
331                height,
332                alpha_mode: self.alpha_mode,
333                view_formats: vec![],
334                desired_maximum_frame_latency: 1,
335            },
336        );
337    }
338
339    fn information(&self) -> compositor::Information {
340        let information = self.adapter.get_info();
341
342        compositor::Information {
343            adapter: information.name,
344            backend: format!("{:?}", information.backend),
345        }
346    }
347
348    fn present(
349        &mut self,
350        renderer: &mut Self::Renderer,
351        surface: &mut Self::Surface,
352        viewport: &Viewport,
353        background_color: Color,
354        on_pre_present: impl FnOnce(),
355    ) -> Result<(), compositor::SurfaceError> {
356        present(
357            renderer,
358            surface,
359            viewport,
360            background_color,
361            on_pre_present,
362        )
363    }
364
365    fn screenshot(
366        &mut self,
367        renderer: &mut Self::Renderer,
368        viewport: &Viewport,
369        background_color: Color,
370    ) -> Vec<u8> {
371        renderer.screenshot(viewport, background_color)
372    }
373}