use crate::core::Color;
use crate::graphics::color;
use crate::graphics::compositor;
use crate::graphics::error;
use crate::graphics::{self, Shell, Viewport};
use crate::settings::{self, Settings};
use crate::{Engine, Renderer};
pub struct Compositor {
instance: wgpu::Instance,
adapter: wgpu::Adapter,
format: wgpu::TextureFormat,
alpha_mode: wgpu::CompositeAlphaMode,
engine: Engine,
settings: Settings,
}
#[derive(Debug, Clone, thiserror::Error)]
pub enum Error {
#[error("the surface creation failed: {0}")]
SurfaceCreationFailed(#[from] wgpu::CreateSurfaceError),
#[error("the surface is not compatible")]
IncompatibleSurface,
#[error("no adapter was found for the options requested: {0:?}")]
NoAdapterFound(String),
#[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 {
pub async fn request<W: compositor::Window>(
settings: Settings,
compatible_window: Option<W>,
shell: Shell,
) -> Result<Self, Error> {
let instance = wgpu::util::new_instance_with_webgpu_detection(
&wgpu::InstanceDescriptor {
backends: settings.backends,
flags: if cfg!(feature = "strict-assertions") {
wgpu::InstanceFlags::debugging()
} else {
wgpu::InstanceFlags::empty()
},
..Default::default()
},
)
.await;
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(wgpu::PowerPreference::HighPerformance),
compatible_surface: compatible_surface.as_ref(),
force_fallback_adapter: false,
};
let adapter =
instance.request_adapter(&adapter_options).await.map_err(
|_error| 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 formats = capabilities.formats.iter().copied();
log::info!("Available formats: {formats:#?}");
let mut formats = formats.filter(|format| {
format.required_features() == wgpu::Features::empty()
});
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,
max_non_sampler_bindings: 2048,
..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,
trace: wgpu::Trace::Off,
experimental_features: wgpu::ExperimentalFeatures::disabled(
),
})
.await;
match result {
Ok((device, queue)) => {
let engine = Engine::new(
&adapter,
device,
queue,
format,
settings.antialiasing,
shell,
);
return Ok(Compositor {
instance,
adapter,
format,
alpha_mode,
engine,
settings,
});
}
Err(error) => {
errors.push((required_limits, error));
}
}
}
Err(Error::RequestDeviceFailed(errors))
}
}
pub async fn new<W: compositor::Window>(
settings: Settings,
compatible_window: W,
shell: Shell,
) -> Result<Compositor, Error> {
Compositor::request(settings, Some(compatible_window), shell).await
}
pub fn present(
renderer: &mut Renderer,
surface: &mut wgpu::Surface<'static>,
viewport: &Viewport,
background_color: Color,
on_pre_present: impl FnOnce(),
) -> Result<(), compositor::SurfaceError> {
match surface.get_current_texture() {
Ok(frame) => {
let view = &frame
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
let _submission = renderer.present(
Some(background_color),
frame.texture.format(),
view,
viewport,
);
on_pre_present();
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(
settings: graphics::Settings,
_display: impl compositor::Display,
compatible_window: impl compositor::Window,
shell: Shell,
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, shell).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.engine.clone(),
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.engine.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 information(&self) -> compositor::Information {
let information = self.adapter.get_info();
compositor::Information {
adapter: information.name,
backend: format!("{:?}", information.backend),
}
}
fn present(
&mut self,
renderer: &mut Self::Renderer,
surface: &mut Self::Surface,
viewport: &Viewport,
background_color: Color,
on_pre_present: impl FnOnce(),
) -> Result<(), compositor::SurfaceError> {
present(
renderer,
surface,
viewport,
background_color,
on_pre_present,
)
}
fn screenshot(
&mut self,
renderer: &mut Self::Renderer,
viewport: &Viewport,
background_color: Color,
) -> Vec<u8> {
renderer.screenshot(viewport, background_color)
}
}