summaryrefslogblamecommitdiffstats
path: root/wgpu/src/window/compositor.rs
blob: bc932b446b3017c5f56879b2470aaf220cc53b93 (plain) (tree)
1
2
3
4
5
6
7
8
9
                                     
                               
                           
                                

                                      

                                      
 
                                                         
                                       
                       
                             
                           

                         
                                
                                         

                       

 

























                                                                       
                 


                                                                        
                                                
                           
                                     
                              
                                                                      
                                        




                                                           

                                
 
                                    

                                           
                                                       
                                                     
                                                      
                       
                                             
                           
                                                                      

         

                                                  
                                                                     
 
                                                           

                                                                          


                                                          

                  



                                                            
                              


                                                                             
 

                                                          


                                                     



                                                                       

                                                              











                                                                                




                                                                      


                                                                        



                                                                       




                                                  

                                                



                                                                         
 
                                      

                                                               

                                           


                                                                          
                                                                   



                               




                                       



                                                                              
                                                                   
                                                                 
                                                                     
                      
                         




                                        







                                              

                                          




                                   


                                 
                 






                                                          
     

 
                                                                    
                                        
                       
                         
                                
                                                                

 
                                                                

                                
                            
                                         















                                                                       

                                       



                                       
                                       
                     



                         


                                                                         














                                                                            
                                                                              



          
                                          
                             
                                          
 
                                                 
                                     
                             


                                        


                                                            
                                                                    








                                                                               






                                                                           

     
                                                 
                      
                         



                                            

     
                                             
                  
                  

                    
                        


                                   
                                      
 


                                                                

               

     
                         
                  
                                    

                    
       



                                                              
                                    
                                                         

                       
                                            
                                     
                                                 
              
          

     
                                                            

                                                  
                                 




                                                          
                              

                                      
                                    
                            
                                
                      
                                               
                                                                             
     



                                      



                                
                                                                       





                                                       
                                 

                                



                            






















                                                                             







                                                               



                               
                         
              

                 










                                               











                                                                                
                                
                                   
                                   
                                                 







                                                                            
                                                                     










































                                                                                                    
 
//! Connect a window with a renderer.
use crate::core::{Color, Size};
use crate::graphics::color;
use crate::graphics::compositor;
use crate::graphics::error;
use crate::graphics::{self, Viewport};
use crate::settings::{self, Settings};
use crate::{Engine, Renderer};

/// A window graphics backend for iced powered by `wgpu`.
#[allow(missing_debug_implementations)]
pub struct Compositor {
    instance: wgpu::Instance,
    adapter: wgpu::Adapter,
    device: wgpu::Device,
    queue: wgpu::Queue,
    format: wgpu::TextureFormat,
    alpha_mode: wgpu::CompositeAlphaMode,
    engine: Engine,
    settings: Settings,
}

/// A compositor error.
#[derive(Debug, Clone, thiserror::Error)]
pub enum Error {
    /// The surface creation failed.
    #[error("the surface creation failed: {0}")]
    SurfaceCreationFailed(#[from] wgpu::CreateSurfaceError),
    /// The surface is not compatible.
    #[error("the surface is not compatible")]
    IncompatibleSurface,
    /// No adapter was found for the options requested.
    #[error("no adapter was found for the options requested: {0:?}")]
    NoAdapterFound(String),
    /// No device request succeeded.
    #[error("no device request succeeded: {0:?}")]
    RequestDeviceFailed(Vec<(wgpu::Limits, wgpu::RequestDeviceError)>),
}

impl From<Error> for graphics::Error {
    fn from(error: Error) -> Self {
        Self::GraphicsAdapterNotFound {
            backend: "wgpu",
            reason: error::Reason::RequestFailed(error.to_string()),
        }
    }
}

impl Compositor {
    /// Requests a new [`Compositor`] with the given [`Settings`].
    ///
    /// Returns `None` if no compatible graphics adapter could be found.
    pub async fn request<W: compositor::Window>(
        settings: Settings,
        compatible_window: Option<W>,
    ) -> Result<Self, Error> {
        let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
            backends: settings.backends,
            flags: if cfg!(feature = "strict-assertions") {
                wgpu::InstanceFlags::debugging()
            } else {
                wgpu::InstanceFlags::empty()
            },
            ..Default::default()
        });

        log::info!("{settings:#?}");

        #[cfg(not(target_arch = "wasm32"))]
        if log::max_level() >= log::LevelFilter::Info {
            let available_adapters: Vec<_> = instance
                .enumerate_adapters(settings.backends)
                .iter()
                .map(wgpu::Adapter::get_info)
                .collect();
            log::info!("Available adapters: {available_adapters:#?}");
        }

        #[allow(unsafe_code)]
        let compatible_surface = compatible_window
            .and_then(|window| instance.create_surface(window).ok());

        let adapter_options = wgpu::RequestAdapterOptions {
            power_preference: wgpu::PowerPreference::from_env().unwrap_or(
                if settings.antialiasing.is_none() {
                    wgpu::PowerPreference::LowPower
                } else {
                    wgpu::PowerPreference::HighPerformance
                },
            ),
            compatible_surface: compatible_surface.as_ref(),
            force_fallback_adapter: false,
        };

        let adapter = instance
            .request_adapter(&adapter_options)
            .await
            .ok_or(Error::NoAdapterFound(format!("{:?}", adapter_options)))?;

        log::info!("Selected: {:#?}", adapter.get_info());

        let (format, alpha_mode) = compatible_surface
            .as_ref()
            .and_then(|surface| {
                let capabilities = surface.get_capabilities(&adapter);

                let mut formats = capabilities.formats.iter().copied();

                log::info!("Available formats: {formats:#?}");

                let format = if color::GAMMA_CORRECTION {
                    formats.find(wgpu::TextureFormat::is_srgb)
                } else {
                    formats.find(|format| !wgpu::TextureFormat::is_srgb(format))
                };

                let format = format.or_else(|| {
                    log::warn!("No format found!");

                    capabilities.formats.first().copied()
                });

                let alpha_modes = capabilities.alpha_modes;

                log::info!("Available alpha modes: {alpha_modes:#?}");

                let preferred_alpha = if alpha_modes
                    .contains(&wgpu::CompositeAlphaMode::PostMultiplied)
                {
                    wgpu::CompositeAlphaMode::PostMultiplied
                } else if alpha_modes
                    .contains(&wgpu::CompositeAlphaMode::PreMultiplied)
                {
                    wgpu::CompositeAlphaMode::PreMultiplied
                } else {
                    wgpu::CompositeAlphaMode::Auto
                };

                format.zip(Some(preferred_alpha))
            })
            .ok_or(Error::IncompatibleSurface)?;

        log::info!(
            "Selected format: {format:?} with alpha mode: {alpha_mode:?}"
        );

        #[cfg(target_arch = "wasm32")]
        let limits = [wgpu::Limits::downlevel_webgl2_defaults()
            .using_resolution(adapter.limits())];

        #[cfg(not(target_arch = "wasm32"))]
        let limits =
            [wgpu::Limits::default(), wgpu::Limits::downlevel_defaults()];

        let limits = limits.into_iter().map(|limits| wgpu::Limits {
            max_bind_groups: 2,
            ..limits
        });

        let mut errors = Vec::new();

        for required_limits in limits {
            let result = adapter
                .request_device(
                    &wgpu::DeviceDescriptor {
                        label: Some(
                            "iced_wgpu::window::compositor device descriptor",
                        ),
                        required_features: wgpu::Features::empty(),
                        required_limits: required_limits.clone(),
                        memory_hints: wgpu::MemoryHints::MemoryUsage,
                    },
                    None,
                )
                .await;

            match result {
                Ok((device, queue)) => {
                    let engine = Engine::new(
                        &adapter,
                        &device,
                        &queue,
                        format,
                        settings.antialiasing,
                    );

                    return Ok(Compositor {
                        instance,
                        adapter,
                        device,
                        queue,
                        format,
                        alpha_mode,
                        engine,
                        settings,
                    });
                }
                Err(error) => {
                    errors.push((required_limits, error));
                }
            }
        }

        Err(Error::RequestDeviceFailed(errors))
    }
}

/// Creates a [`Compositor`] with the given [`Settings`] and window.
pub async fn new<W: compositor::Window>(
    settings: Settings,
    compatible_window: W,
) -> Result<Compositor, Error> {
    Compositor::request(settings, Some(compatible_window)).await
}

/// Presents the given primitives with the given [`Compositor`].
pub fn present<T: AsRef<str>>(
    compositor: &mut Compositor,
    renderer: &mut Renderer,
    surface: &mut wgpu::Surface<'static>,
    viewport: &Viewport,
    background_color: Color,
    overlay: &[T],
) -> Result<(), compositor::SurfaceError> {
    match surface.get_current_texture() {
        Ok(frame) => {
            let mut encoder = compositor.device.create_command_encoder(
                &wgpu::CommandEncoderDescriptor {
                    label: Some("iced_wgpu encoder"),
                },
            );

            let view = &frame
                .texture
                .create_view(&wgpu::TextureViewDescriptor::default());

            renderer.present(
                &mut compositor.engine,
                &compositor.device,
                &compositor.queue,
                &mut encoder,
                Some(background_color),
                frame.texture.format(),
                view,
                viewport,
                overlay,
            );

            let _ = compositor.engine.submit(&compositor.queue, encoder);

            // Present the frame
            frame.present();

            Ok(())
        }
        Err(error) => match error {
            wgpu::SurfaceError::Timeout => {
                Err(compositor::SurfaceError::Timeout)
            }
            wgpu::SurfaceError::Outdated => {
                Err(compositor::SurfaceError::Outdated)
            }
            wgpu::SurfaceError::Lost => Err(compositor::SurfaceError::Lost),
            wgpu::SurfaceError::OutOfMemory => {
                Err(compositor::SurfaceError::OutOfMemory)
            }
            wgpu::SurfaceError::Other => Err(compositor::SurfaceError::Other),
        },
    }
}

impl graphics::Compositor for Compositor {
    type Renderer = Renderer;
    type Surface = wgpu::Surface<'static>;

    async fn with_backend<W: compositor::Window>(
        settings: graphics::Settings,
        compatible_window: W,
        backend: Option<&str>,
    ) -> Result<Self, graphics::Error> {
        match backend {
            None | Some("wgpu") => {
                let mut settings = Settings::from(settings);

                if let Some(backends) = wgpu::Backends::from_env() {
                    settings.backends = backends;
                }

                if let Some(present_mode) = settings::present_mode_from_env() {
                    settings.present_mode = present_mode;
                }

                Ok(new(settings, compatible_window).await?)
            }
            Some(backend) => Err(graphics::Error::GraphicsAdapterNotFound {
                backend: "wgpu",
                reason: error::Reason::DidNotMatch {
                    preferred_backend: backend.to_owned(),
                },
            }),
        }
    }

    fn create_renderer(&self) -> Self::Renderer {
        Renderer::new(
            &self.device,
            &self.engine,
            self.settings.default_font,
            self.settings.default_text_size,
        )
    }

    fn create_surface<W: compositor::Window>(
        &mut self,
        window: W,
        width: u32,
        height: u32,
    ) -> Self::Surface {
        let mut surface = self
            .instance
            .create_surface(window)
            .expect("Create surface");

        if width > 0 && height > 0 {
            self.configure_surface(&mut surface, width, height);
        }

        surface
    }

    fn configure_surface(
        &mut self,
        surface: &mut Self::Surface,
        width: u32,
        height: u32,
    ) {
        surface.configure(
            &self.device,
            &wgpu::SurfaceConfiguration {
                usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
                format: self.format,
                present_mode: self.settings.present_mode,
                width,
                height,
                alpha_mode: self.alpha_mode,
                view_formats: vec![],
                desired_maximum_frame_latency: 1,
            },
        );
    }

    fn fetch_information(&self) -> compositor::Information {
        let information = self.adapter.get_info();

        compositor::Information {
            adapter: information.name,
            backend: format!("{:?}", information.backend),
        }
    }

    fn present<T: AsRef<str>>(
        &mut self,
        renderer: &mut Self::Renderer,
        surface: &mut Self::Surface,
        viewport: &Viewport,
        background_color: Color,
        overlay: &[T],
    ) -> Result<(), compositor::SurfaceError> {
        present(self, renderer, surface, viewport, background_color, overlay)
    }

    fn screenshot<T: AsRef<str>>(
        &mut self,
        renderer: &mut Self::Renderer,
        viewport: &Viewport,
        background_color: Color,
        overlay: &[T],
    ) -> Vec<u8> {
        screenshot(self, renderer, viewport, background_color, overlay)
    }
}

/// Renders the current surface to an offscreen buffer.
///
/// Returns RGBA bytes of the texture data.
pub fn screenshot<T: AsRef<str>>(
    compositor: &mut Compositor,
    renderer: &mut Renderer,
    viewport: &Viewport,
    background_color: Color,
    overlay: &[T],
) -> Vec<u8> {
    let dimensions = BufferDimensions::new(viewport.physical_size());

    let texture_extent = wgpu::Extent3d {
        width: dimensions.width,
        height: dimensions.height,
        depth_or_array_layers: 1,
    };

    let texture = compositor.device.create_texture(&wgpu::TextureDescriptor {
        label: Some("iced_wgpu.offscreen.source_texture"),
        size: texture_extent,
        mip_level_count: 1,
        sample_count: 1,
        dimension: wgpu::TextureDimension::D2,
        format: compositor.format,
        usage: wgpu::TextureUsages::RENDER_ATTACHMENT
            | wgpu::TextureUsages::COPY_SRC
            | wgpu::TextureUsages::TEXTURE_BINDING,
        view_formats: &[],
    });

    let view = texture.create_view(&wgpu::TextureViewDescriptor::default());

    let mut encoder = compositor.device.create_command_encoder(
        &wgpu::CommandEncoderDescriptor {
            label: Some("iced_wgpu.offscreen.encoder"),
        },
    );

    renderer.present(
        &mut compositor.engine,
        &compositor.device,
        &compositor.queue,
        &mut encoder,
        Some(background_color),
        texture.format(),
        &view,
        viewport,
        overlay,
    );

    let texture = crate::color::convert(
        &compositor.device,
        &mut encoder,
        texture,
        if color::GAMMA_CORRECTION {
            wgpu::TextureFormat::Rgba8UnormSrgb
        } else {
            wgpu::TextureFormat::Rgba8Unorm
        },
    );

    let output_buffer =
        compositor.device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("iced_wgpu.offscreen.output_texture_buffer"),
            size: (dimensions.padded_bytes_per_row * dimensions.height as usize)
                as u64,
            usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

    encoder.copy_texture_to_buffer(
        texture.as_image_copy(),
        wgpu::TexelCopyBufferInfo {
            buffer: &output_buffer,
            layout: wgpu::TexelCopyBufferLayout {
                offset: 0,
                bytes_per_row: Some(dimensions.padded_bytes_per_row as u32),
                rows_per_image: None,
            },
        },
        texture_extent,
    );

    let index = compositor.engine.submit(&compositor.queue, encoder);

    let slice = output_buffer.slice(..);
    slice.map_async(wgpu::MapMode::Read, |_| {});

    let _ = compositor
        .device
        .poll(wgpu::Maintain::WaitForSubmissionIndex(index));

    let mapped_buffer = slice.get_mapped_range();

    mapped_buffer.chunks(dimensions.padded_bytes_per_row).fold(
        vec![],
        |mut acc, row| {
            acc.extend(&row[..dimensions.unpadded_bytes_per_row]);
            acc
        },
    )
}

#[derive(Clone, Copy, Debug)]
struct BufferDimensions {
    width: u32,
    height: u32,
    unpadded_bytes_per_row: usize,
    padded_bytes_per_row: usize,
}

impl BufferDimensions {
    fn new(size: Size<u32>) -> Self {
        let unpadded_bytes_per_row = size.width as usize * 4; //slice of buffer per row; always RGBA
        let alignment = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize; //256
        let padded_bytes_per_row_padding =
            (alignment - unpadded_bytes_per_row % alignment) % alignment;
        let padded_bytes_per_row =
            unpadded_bytes_per_row + padded_bytes_per_row_padding;

        Self {
            width: size.width,
            height: size.height,
            unpadded_bytes_per_row,
            padded_bytes_per_row,
        }
    }
}