487 lines
16 KiB
Rust
487 lines
16 KiB
Rust
//! Connect a window with a renderer.
|
|
use wgpu::Backends;
|
|
|
|
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};
|
|
|
|
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))]
|
|
use super::wayland::get_wayland_device_ids;
|
|
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))]
|
|
use super::x11::get_x11_device_ids;
|
|
use std::future::Future;
|
|
|
|
/// A window graphics backend for iced powered by `wgpu`.
|
|
pub struct Compositor {
|
|
instance: wgpu::Instance,
|
|
adapter: wgpu::Adapter,
|
|
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 + Clone>(
|
|
settings: Settings,
|
|
compatible_window: Option<W>,
|
|
shell: Shell,
|
|
) -> Result<Self, Error> {
|
|
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))]
|
|
let ids = compatible_window.as_ref().and_then(|window| {
|
|
get_wayland_device_ids(window)
|
|
.or_else(|| get_x11_device_ids(window))
|
|
});
|
|
|
|
// HACK:
|
|
// 1. If we specifically didn't select an nvidia gpu
|
|
// 2. and the user didn't specifically request an nvidia gpu
|
|
// 3. and the user didn't set an adapter name,
|
|
// 4. and the user didn't request the high power pref
|
|
// => don't load the nvidia icd, as it might power on the gpu in hybrid setups causing severe delays
|
|
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))]
|
|
if !matches!(ids, Some((0x10de, _)))
|
|
&& std::env::var_os("__NV_PRIME_RENDER_OFFLOAD")
|
|
.is_none_or(|var| var == "0")
|
|
&& std::env::var_os("WGPU_ADAPTER_NAME").is_none()
|
|
&& std::env::var("WGPU_POWER_PREF").as_deref() != Ok("high")
|
|
{
|
|
unsafe {
|
|
std::env::set_var("VK_LOADER_DRIVERS_DISABLE", "nvidia*");
|
|
}
|
|
}
|
|
|
|
// XXX: try getting non-gl adapter first
|
|
// Otherwise it will panic when it drops the available adapters!
|
|
let non_gl_backends = settings.backends.difference(Backends::GL);
|
|
// only load the instance after setting environment variables, this initializes the vulkan loader
|
|
let mut instance = wgpu::util::new_instance_with_webgpu_detection(
|
|
&wgpu::InstanceDescriptor {
|
|
backends: non_gl_backends,
|
|
flags: if cfg!(feature = "strict-assertions") {
|
|
wgpu::InstanceFlags::debugging()
|
|
} else {
|
|
wgpu::InstanceFlags::empty()
|
|
},
|
|
..Default::default()
|
|
},
|
|
)
|
|
.await;
|
|
|
|
log::info!("{settings:#?}");
|
|
|
|
unsafe {
|
|
std::env::remove_var("VK_LOADER_DRIVERS_DISABLE");
|
|
}
|
|
|
|
// #[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 mut compatible_surface = compatible_window
|
|
.clone()
|
|
.and_then(|window| instance.create_surface(window).ok());
|
|
|
|
let mut 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 mut adapter = None;
|
|
#[cfg_attr(not(unix), allow(dead_code))]
|
|
if std::env::var_os("WGPU_ADAPTER_NAME").is_none() {
|
|
#[cfg(all(
|
|
unix,
|
|
not(target_os = "macos"),
|
|
not(target_os = "redox")
|
|
))]
|
|
if let Some((vendor_id, device_id)) = ids {
|
|
adapter = instance
|
|
.enumerate_adapters(settings.backends)
|
|
.into_iter()
|
|
.filter(|adapter| {
|
|
let info = adapter.get_info();
|
|
info.device == device_id as u32
|
|
&& info.vendor == vendor_id as u32
|
|
})
|
|
.find(|adapter| {
|
|
if let Some(surface) = compatible_surface.as_ref() {
|
|
adapter.is_surface_supported(surface)
|
|
} else {
|
|
true
|
|
}
|
|
});
|
|
}
|
|
} else if let Ok(name) = std::env::var("WGPU_ADAPTER_NAME") {
|
|
adapter = instance
|
|
.enumerate_adapters(settings.backends)
|
|
.into_iter()
|
|
.filter(|adapter| {
|
|
let info = adapter.get_info();
|
|
info.name == name
|
|
})
|
|
.find(|adapter| {
|
|
if let Some(surface) = compatible_surface.as_ref() {
|
|
adapter.is_surface_supported(surface)
|
|
} else {
|
|
true
|
|
}
|
|
});
|
|
}
|
|
|
|
let adapter = match adapter {
|
|
Some(adapter) => adapter,
|
|
None => {
|
|
// fall back to allowing GL backend if enabled
|
|
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;
|
|
compatible_surface = compatible_window
|
|
.and_then(|window| instance.create_surface(window).ok());
|
|
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,
|
|
};
|
|
instance.request_adapter(&adapter_options).await.map_err(
|
|
|_| Error::NoAdapterFound(format!("{:?}", adapter_options)),
|
|
)?
|
|
}
|
|
};
|
|
log::info!("Selected: {:#?}", adapter.get_info());
|
|
|
|
let (format, alpha_mode) = compatible_surface
|
|
.as_ref()
|
|
.and_then(|surface: &wgpu::Surface<'_>| {
|
|
let capabilities = surface.get_capabilities(&adapter);
|
|
|
|
let formats = capabilities.formats.iter().copied();
|
|
|
|
log::info!("Available formats: {formats:#?}");
|
|
|
|
const BLACKLIST: &[wgpu::TextureFormat] = &[
|
|
wgpu::TextureFormat::Rgb10a2Unorm,
|
|
wgpu::TextureFormat::Rgb10a2Uint,
|
|
];
|
|
|
|
let mut formats = formats.filter(|format| {
|
|
format.required_features() == wgpu::Features::empty()
|
|
&& !BLACKLIST.contains(format)
|
|
});
|
|
|
|
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))
|
|
}
|
|
}
|
|
|
|
/// Creates a [`Compositor`] with the given [`Settings`] and window.
|
|
pub async fn new<W: compositor::Window + Clone>(
|
|
settings: Settings,
|
|
compatible_window: W,
|
|
shell: Shell,
|
|
) -> Result<Compositor, Error> {
|
|
Compositor::request(settings, Some(compatible_window), shell).await
|
|
}
|
|
|
|
/// Presents the given primitives with the given [`Compositor`].
|
|
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,
|
|
);
|
|
|
|
// Present the frame
|
|
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 + Clone,
|
|
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)
|
|
}
|
|
}
|