Skip to main content

iced_wgpu/window/
compositor.rs

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