iced_wgpu/window/
compositor.rs

1//! Connect a window with a renderer.
2use crate::core::{Color, Size};
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`.
11#[allow(missing_debug_implementations)]
12pub struct Compositor {
13    instance: wgpu::Instance,
14    adapter: wgpu::Adapter,
15    device: wgpu::Device,
16    queue: wgpu::Queue,
17    format: wgpu::TextureFormat,
18    alpha_mode: wgpu::CompositeAlphaMode,
19    engine: Engine,
20    settings: Settings,
21}
22
23/// A compositor error.
24#[derive(Debug, Clone, thiserror::Error)]
25pub enum Error {
26    /// The surface creation failed.
27    #[error("the surface creation failed: {0}")]
28    SurfaceCreationFailed(#[from] wgpu::CreateSurfaceError),
29    /// The surface is not compatible.
30    #[error("the surface is not compatible")]
31    IncompatibleSurface,
32    /// No adapter was found for the options requested.
33    #[error("no adapter was found for the options requested: {0:?}")]
34    NoAdapterFound(String),
35    /// No device request succeeded.
36    #[error("no device request succeeded: {0:?}")]
37    RequestDeviceFailed(Vec<(wgpu::Limits, wgpu::RequestDeviceError)>),
38}
39
40impl From<Error> for graphics::Error {
41    fn from(error: Error) -> Self {
42        Self::GraphicsAdapterNotFound {
43            backend: "wgpu",
44            reason: error::Reason::RequestFailed(error.to_string()),
45        }
46    }
47}
48
49impl Compositor {
50    /// Requests a new [`Compositor`] with the given [`Settings`].
51    ///
52    /// Returns `None` if no compatible graphics adapter could be found.
53    pub async fn request<W: compositor::Window>(
54        settings: Settings,
55        compatible_window: Option<W>,
56    ) -> Result<Self, Error> {
57        let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
58            backends: settings.backends,
59            flags: if cfg!(feature = "strict-assertions") {
60                wgpu::InstanceFlags::debugging()
61            } else {
62                wgpu::InstanceFlags::empty()
63            },
64            ..Default::default()
65        });
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 = instance
96            .request_adapter(&adapter_options)
97            .await
98            .ok_or(Error::NoAdapterFound(format!("{:?}", adapter_options)))?;
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(
165                    &wgpu::DeviceDescriptor {
166                        label: Some(
167                            "iced_wgpu::window::compositor device descriptor",
168                        ),
169                        required_features: wgpu::Features::empty(),
170                        required_limits: required_limits.clone(),
171                        memory_hints: wgpu::MemoryHints::MemoryUsage,
172                    },
173                    None,
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                    );
186
187                    return Ok(Compositor {
188                        instance,
189                        adapter,
190                        device,
191                        queue,
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) -> Result<Compositor, Error> {
213    Compositor::request(settings, Some(compatible_window)).await
214}
215
216/// Presents the given primitives with the given [`Compositor`].
217pub fn present<T: AsRef<str>>(
218    compositor: &mut Compositor,
219    renderer: &mut Renderer,
220    surface: &mut wgpu::Surface<'static>,
221    viewport: &Viewport,
222    background_color: Color,
223    overlay: &[T],
224) -> Result<(), compositor::SurfaceError> {
225    match surface.get_current_texture() {
226        Ok(frame) => {
227            let mut encoder = compositor.device.create_command_encoder(
228                &wgpu::CommandEncoderDescriptor {
229                    label: Some("iced_wgpu encoder"),
230                },
231            );
232
233            let view = &frame
234                .texture
235                .create_view(&wgpu::TextureViewDescriptor::default());
236
237            renderer.present(
238                &mut compositor.engine,
239                &compositor.device,
240                &compositor.queue,
241                &mut encoder,
242                Some(background_color),
243                frame.texture.format(),
244                view,
245                viewport,
246                overlay,
247            );
248
249            let _ = compositor.engine.submit(&compositor.queue, encoder);
250
251            // Present the frame
252            frame.present();
253
254            Ok(())
255        }
256        Err(error) => match error {
257            wgpu::SurfaceError::Timeout => {
258                Err(compositor::SurfaceError::Timeout)
259            }
260            wgpu::SurfaceError::Outdated => {
261                Err(compositor::SurfaceError::Outdated)
262            }
263            wgpu::SurfaceError::Lost => Err(compositor::SurfaceError::Lost),
264            wgpu::SurfaceError::OutOfMemory => {
265                Err(compositor::SurfaceError::OutOfMemory)
266            }
267            wgpu::SurfaceError::Other => Err(compositor::SurfaceError::Other),
268        },
269    }
270}
271
272impl graphics::Compositor for Compositor {
273    type Renderer = Renderer;
274    type Surface = wgpu::Surface<'static>;
275
276    async fn with_backend<W: compositor::Window>(
277        settings: graphics::Settings,
278        compatible_window: W,
279        backend: Option<&str>,
280    ) -> Result<Self, graphics::Error> {
281        match backend {
282            None | Some("wgpu") => {
283                let mut settings = Settings::from(settings);
284
285                if let Some(backends) = wgpu::Backends::from_env() {
286                    settings.backends = backends;
287                }
288
289                if let Some(present_mode) = settings::present_mode_from_env() {
290                    settings.present_mode = present_mode;
291                }
292
293                Ok(new(settings, compatible_window).await?)
294            }
295            Some(backend) => Err(graphics::Error::GraphicsAdapterNotFound {
296                backend: "wgpu",
297                reason: error::Reason::DidNotMatch {
298                    preferred_backend: backend.to_owned(),
299                },
300            }),
301        }
302    }
303
304    fn create_renderer(&self) -> Self::Renderer {
305        Renderer::new(
306            &self.device,
307            &self.engine,
308            self.settings.default_font,
309            self.settings.default_text_size,
310        )
311    }
312
313    fn create_surface<W: compositor::Window>(
314        &mut self,
315        window: W,
316        width: u32,
317        height: u32,
318    ) -> Self::Surface {
319        let mut surface = self
320            .instance
321            .create_surface(window)
322            .expect("Create surface");
323
324        if width > 0 && height > 0 {
325            self.configure_surface(&mut surface, width, height);
326        }
327
328        surface
329    }
330
331    fn configure_surface(
332        &mut self,
333        surface: &mut Self::Surface,
334        width: u32,
335        height: u32,
336    ) {
337        surface.configure(
338            &self.device,
339            &wgpu::SurfaceConfiguration {
340                usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
341                format: self.format,
342                present_mode: self.settings.present_mode,
343                width,
344                height,
345                alpha_mode: self.alpha_mode,
346                view_formats: vec![],
347                desired_maximum_frame_latency: 1,
348            },
349        );
350    }
351
352    fn fetch_information(&self) -> compositor::Information {
353        let information = self.adapter.get_info();
354
355        compositor::Information {
356            adapter: information.name,
357            backend: format!("{:?}", information.backend),
358        }
359    }
360
361    fn present<T: AsRef<str>>(
362        &mut self,
363        renderer: &mut Self::Renderer,
364        surface: &mut Self::Surface,
365        viewport: &Viewport,
366        background_color: Color,
367        overlay: &[T],
368    ) -> Result<(), compositor::SurfaceError> {
369        present(self, renderer, surface, viewport, background_color, overlay)
370    }
371
372    fn screenshot<T: AsRef<str>>(
373        &mut self,
374        renderer: &mut Self::Renderer,
375        viewport: &Viewport,
376        background_color: Color,
377        overlay: &[T],
378    ) -> Vec<u8> {
379        screenshot(self, renderer, viewport, background_color, overlay)
380    }
381}
382
383/// Renders the current surface to an offscreen buffer.
384///
385/// Returns RGBA bytes of the texture data.
386pub fn screenshot<T: AsRef<str>>(
387    compositor: &mut Compositor,
388    renderer: &mut Renderer,
389    viewport: &Viewport,
390    background_color: Color,
391    overlay: &[T],
392) -> Vec<u8> {
393    let dimensions = BufferDimensions::new(viewport.physical_size());
394
395    let texture_extent = wgpu::Extent3d {
396        width: dimensions.width,
397        height: dimensions.height,
398        depth_or_array_layers: 1,
399    };
400
401    let texture = compositor.device.create_texture(&wgpu::TextureDescriptor {
402        label: Some("iced_wgpu.offscreen.source_texture"),
403        size: texture_extent,
404        mip_level_count: 1,
405        sample_count: 1,
406        dimension: wgpu::TextureDimension::D2,
407        format: compositor.format,
408        usage: wgpu::TextureUsages::RENDER_ATTACHMENT
409            | wgpu::TextureUsages::COPY_SRC
410            | wgpu::TextureUsages::TEXTURE_BINDING,
411        view_formats: &[],
412    });
413
414    let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
415
416    let mut encoder = compositor.device.create_command_encoder(
417        &wgpu::CommandEncoderDescriptor {
418            label: Some("iced_wgpu.offscreen.encoder"),
419        },
420    );
421
422    renderer.present(
423        &mut compositor.engine,
424        &compositor.device,
425        &compositor.queue,
426        &mut encoder,
427        Some(background_color),
428        texture.format(),
429        &view,
430        viewport,
431        overlay,
432    );
433
434    let texture = crate::color::convert(
435        &compositor.device,
436        &mut encoder,
437        texture,
438        if color::GAMMA_CORRECTION {
439            wgpu::TextureFormat::Rgba8UnormSrgb
440        } else {
441            wgpu::TextureFormat::Rgba8Unorm
442        },
443    );
444
445    let output_buffer =
446        compositor.device.create_buffer(&wgpu::BufferDescriptor {
447            label: Some("iced_wgpu.offscreen.output_texture_buffer"),
448            size: (dimensions.padded_bytes_per_row * dimensions.height as usize)
449                as u64,
450            usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
451            mapped_at_creation: false,
452        });
453
454    encoder.copy_texture_to_buffer(
455        texture.as_image_copy(),
456        wgpu::TexelCopyBufferInfo {
457            buffer: &output_buffer,
458            layout: wgpu::TexelCopyBufferLayout {
459                offset: 0,
460                bytes_per_row: Some(dimensions.padded_bytes_per_row as u32),
461                rows_per_image: None,
462            },
463        },
464        texture_extent,
465    );
466
467    let index = compositor.engine.submit(&compositor.queue, encoder);
468
469    let slice = output_buffer.slice(..);
470    slice.map_async(wgpu::MapMode::Read, |_| {});
471
472    let _ = compositor
473        .device
474        .poll(wgpu::Maintain::WaitForSubmissionIndex(index));
475
476    let mapped_buffer = slice.get_mapped_range();
477
478    mapped_buffer.chunks(dimensions.padded_bytes_per_row).fold(
479        vec![],
480        |mut acc, row| {
481            acc.extend(&row[..dimensions.unpadded_bytes_per_row]);
482            acc
483        },
484    )
485}
486
487#[derive(Clone, Copy, Debug)]
488struct BufferDimensions {
489    width: u32,
490    height: u32,
491    unpadded_bytes_per_row: usize,
492    padded_bytes_per_row: usize,
493}
494
495impl BufferDimensions {
496    fn new(size: Size<u32>) -> Self {
497        let unpadded_bytes_per_row = size.width as usize * 4; //slice of buffer per row; always RGBA
498        let alignment = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize; //256
499        let padded_bytes_per_row_padding =
500            (alignment - unpadded_bytes_per_row % alignment) % alignment;
501        let padded_bytes_per_row =
502            unpadded_bytes_per_row + padded_bytes_per_row_padding;
503
504        Self {
505            width: size.width,
506            height: size.height,
507            unpadded_bytes_per_row,
508            padded_bytes_per_row,
509        }
510    }
511}