Skip to main content

iced_wgpu/window/
compositor.rs

1//! Connect a window with a renderer.
2use crate::core::Color;
3use crate::core::renderer;
4use crate::graphics::color;
5use crate::graphics::compositor;
6use crate::graphics::error;
7use crate::graphics::{self, Antialiasing, Shell, Viewport};
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                .await
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 =
81            compatible_window.and_then(|window| instance.create_surface(window).ok());
82
83        let adapter_options = wgpu::RequestAdapterOptions {
84            power_preference: wgpu::PowerPreference::from_env()
85                .unwrap_or(wgpu::PowerPreference::HighPerformance),
86            compatible_surface: compatible_surface.as_ref(),
87            force_fallback_adapter: false,
88        };
89
90        let adapter = instance
91            .request_adapter(&adapter_options)
92            .await
93            .map_err(|_error| Error::NoAdapterFound(format!("{adapter_options:?}")))?;
94
95        log::info!("Selected: {:#?}", adapter.get_info());
96
97        let (format, alpha_mode) = compatible_surface
98            .as_ref()
99            .and_then(|surface| {
100                let capabilities = surface.get_capabilities(&adapter);
101
102                let formats = capabilities.formats.iter().copied();
103
104                log::info!("Available formats: {formats:#?}");
105
106                const BLACKLIST: &[wgpu::TextureFormat] = &[
107                    wgpu::TextureFormat::Rgb10a2Unorm,
108                    wgpu::TextureFormat::Rgb10a2Uint,
109                ];
110
111                let mut formats = formats.filter(|format| {
112                    format.required_features() == wgpu::Features::empty()
113                        && !BLACKLIST.contains(format)
114                });
115
116                let format = if color::GAMMA_CORRECTION {
117                    formats.find(wgpu::TextureFormat::is_srgb)
118                } else {
119                    formats.find(|format| !wgpu::TextureFormat::is_srgb(format))
120                };
121
122                let format = format.or_else(|| {
123                    log::warn!("No format found!");
124
125                    capabilities.formats.first().copied()
126                });
127
128                let alpha_modes = capabilities.alpha_modes;
129
130                log::info!("Available alpha modes: {alpha_modes:#?}");
131
132                let preferred_alpha =
133                    if alpha_modes.contains(&wgpu::CompositeAlphaMode::PreMultiplied) {
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!("Selected format: {format:?} with alpha mode: {alpha_mode:?}");
144
145        #[cfg(target_arch = "wasm32")]
146        let limits = [wgpu::Limits::downlevel_webgl2_defaults().using_resolution(adapter.limits())];
147
148        #[cfg(not(target_arch = "wasm32"))]
149        let limits = [wgpu::Limits::default(), wgpu::Limits::downlevel_defaults()];
150
151        let limits = limits.into_iter().map(|limits| wgpu::Limits {
152            max_bind_groups: 2,
153            max_non_sampler_bindings: 2048,
154            ..limits
155        });
156
157        // Request SHADER_F16 only if the adapter supports it (e.g., not available in WebGL2)
158        let required_features = if adapter.features().contains(wgpu::Features::SHADER_F16) {
159            wgpu::Features::SHADER_F16
160        } else {
161            wgpu::Features::empty()
162        };
163
164        let mut errors = Vec::new();
165
166        for required_limits in limits {
167            let result = adapter
168                .request_device(&wgpu::DeviceDescriptor {
169                    label: Some("iced_wgpu::window::compositor device descriptor"),
170                    required_features,
171                    required_limits: required_limits.clone(),
172                    memory_hints: wgpu::MemoryHints::MemoryUsage,
173                    trace: wgpu::Trace::Off,
174                    experimental_features: wgpu::ExperimentalFeatures::disabled(),
175                })
176                .await;
177
178            match result {
179                Ok((device, queue)) => {
180                    let engine = Engine::new(
181                        &adapter,
182                        device,
183                        queue,
184                        format,
185                        settings.antialiasing,
186                        shell,
187                    );
188
189                    return Ok(Compositor {
190                        instance,
191                        adapter,
192                        format,
193                        alpha_mode,
194                        engine,
195                        settings,
196                    });
197                }
198                Err(error) => {
199                    errors.push((required_limits, error));
200                }
201            }
202        }
203
204        Err(Error::RequestDeviceFailed(errors))
205    }
206}
207
208/// Creates a [`Compositor`] with the given [`Settings`] and window.
209pub async fn new<W: compositor::Window>(
210    settings: Settings,
211    compatible_window: W,
212    shell: Shell,
213) -> Result<Compositor, Error> {
214    Compositor::request(settings, Some(compatible_window), shell).await
215}
216
217/// Presents the given primitives with the given [`Compositor`].
218pub fn present(
219    renderer: &mut Renderer,
220    surface: &mut wgpu::Surface<'static>,
221    viewport: &Viewport,
222    background_color: Color,
223    on_pre_present: impl FnOnce(),
224) -> Result<(), compositor::SurfaceError> {
225    match surface.get_current_texture() {
226        Ok(frame) => {
227            let view = &frame
228                .texture
229                .create_view(&wgpu::TextureViewDescriptor::default());
230
231            let _submission = renderer.present(
232                Some(background_color),
233                frame.texture.format(),
234                view,
235                viewport,
236            );
237
238            // Present the frame
239            on_pre_present();
240            frame.present();
241
242            Ok(())
243        }
244        Err(error) => match error {
245            wgpu::SurfaceError::Timeout => Err(compositor::SurfaceError::Timeout),
246            wgpu::SurfaceError::Outdated => Err(compositor::SurfaceError::Outdated),
247            wgpu::SurfaceError::Lost => Err(compositor::SurfaceError::Lost),
248            wgpu::SurfaceError::OutOfMemory => Err(compositor::SurfaceError::OutOfMemory),
249            wgpu::SurfaceError::Other => Err(compositor::SurfaceError::Other),
250        },
251    }
252}
253
254impl graphics::Compositor for Compositor {
255    type Renderer = Renderer;
256    type Surface = wgpu::Surface<'static>;
257
258    async fn with_backend(
259        settings: compositor::Settings,
260        _display: impl compositor::Display,
261        compatible_window: impl compositor::Window,
262        shell: Shell,
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) = present_mode_from_env() {
274                    settings.present_mode = present_mode;
275                }
276
277                Ok(new(settings, compatible_window, shell).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, settings: renderer::Settings) -> Self::Renderer {
289        Renderer::new(self.engine.clone(), settings)
290    }
291
292    fn create_surface<W: compositor::Window>(
293        &mut self,
294        window: W,
295        width: u32,
296        height: u32,
297    ) -> Self::Surface {
298        let mut surface = self
299            .instance
300            .create_surface(window)
301            .expect("Create surface");
302
303        if width > 0 && height > 0 {
304            self.configure_surface(&mut surface, width, height);
305        }
306
307        surface
308    }
309
310    fn configure_surface(&mut self, surface: &mut Self::Surface, width: u32, height: u32) {
311        surface.configure(
312            &self.engine.device,
313            &wgpu::SurfaceConfiguration {
314                usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
315                format: self.format,
316                present_mode: self.settings.present_mode,
317                width,
318                height,
319                alpha_mode: self.alpha_mode,
320                view_formats: vec![],
321                desired_maximum_frame_latency: 1,
322            },
323        );
324    }
325
326    fn information(&self) -> compositor::Information {
327        let information = self.adapter.get_info();
328
329        compositor::Information {
330            adapter: information.name,
331            backend: format!("{:?}", information.backend),
332        }
333    }
334
335    fn present(
336        &mut self,
337        renderer: &mut Self::Renderer,
338        surface: &mut Self::Surface,
339        viewport: &Viewport,
340        background_color: Color,
341        on_pre_present: impl FnOnce(),
342    ) -> Result<(), compositor::SurfaceError> {
343        present(
344            renderer,
345            surface,
346            viewport,
347            background_color,
348            on_pre_present,
349        )
350    }
351
352    fn screenshot(
353        &mut self,
354        renderer: &mut Self::Renderer,
355        viewport: &Viewport,
356        background_color: Color,
357    ) -> Vec<u8> {
358        renderer.screenshot(viewport, background_color)
359    }
360}
361
362/// The settings of a [`Compositor`].
363#[derive(Debug, Clone, Copy, PartialEq)]
364pub struct Settings {
365    /// The present mode of the [`Renderer`].
366    ///
367    /// [`Renderer`]: crate::Renderer
368    pub present_mode: wgpu::PresentMode,
369
370    /// The graphics backends to use.
371    pub backends: wgpu::Backends,
372
373    /// The antialiasing strategy that will be used for triangle primitives.
374    ///
375    /// By default, it is `None`.
376    pub antialiasing: Option<Antialiasing>,
377}
378
379impl Default for Settings {
380    fn default() -> Settings {
381        Settings {
382            present_mode: wgpu::PresentMode::AutoVsync,
383            backends: wgpu::Backends::all(),
384            antialiasing: None,
385        }
386    }
387}
388
389impl From<compositor::Settings> for Settings {
390    fn from(settings: compositor::Settings) -> Self {
391        Self {
392            present_mode: if settings.vsync {
393                wgpu::PresentMode::AutoVsync
394            } else {
395                wgpu::PresentMode::AutoNoVsync
396            },
397            antialiasing: settings.antialiasing,
398            ..Settings::default()
399        }
400    }
401}
402
403/// Obtains a [`wgpu::PresentMode`] from the current environment
404/// configuration, if set.
405///
406/// The value returned by this function can be changed by setting
407/// the `ICED_PRESENT_MODE` env variable. The possible values are:
408///
409/// - `vsync` → [`wgpu::PresentMode::AutoVsync`]
410/// - `no_vsync` → [`wgpu::PresentMode::AutoNoVsync`]
411/// - `immediate` → [`wgpu::PresentMode::Immediate`]
412/// - `fifo` → [`wgpu::PresentMode::Fifo`]
413/// - `fifo_relaxed` → [`wgpu::PresentMode::FifoRelaxed`]
414/// - `mailbox` → [`wgpu::PresentMode::Mailbox`]
415pub fn present_mode_from_env() -> Option<wgpu::PresentMode> {
416    let present_mode = std::env::var("ICED_PRESENT_MODE").ok()?;
417
418    match present_mode.to_lowercase().as_str() {
419        "vsync" => Some(wgpu::PresentMode::AutoVsync),
420        "no_vsync" => Some(wgpu::PresentMode::AutoNoVsync),
421        "immediate" => Some(wgpu::PresentMode::Immediate),
422        "fifo" => Some(wgpu::PresentMode::Fifo),
423        "fifo_relaxed" => Some(wgpu::PresentMode::FifoRelaxed),
424        "mailbox" => Some(wgpu::PresentMode::Mailbox),
425        _ => None,
426    }
427}