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