#![doc = document_features::document_features!()]
pub use wgpu;
mod renderer;
mod setup;
pub use renderer::*;
pub use setup::{
EguiDisplayHandle, NativeAdapterSelectorMethod, WgpuSetup, WgpuSetupCreateNew,
WgpuSetupExisting,
};
#[cfg(feature = "capture")]
pub mod capture;
#[cfg(feature = "winit")]
pub mod winit;
use std::sync::Arc;
use epaint::mutex::RwLock;
#[derive(thiserror::Error, Debug)]
pub enum WgpuError {
#[error(transparent)]
RequestAdapterError(#[from] wgpu::RequestAdapterError),
#[error("Adapter selection failed: {0}")]
CustomNativeAdapterSelectionError(String),
#[error("There was no valid format for the surface at all.")]
NoSurfaceFormatsAvailable,
#[error(transparent)]
RequestDeviceError(#[from] wgpu::RequestDeviceError),
#[error(transparent)]
CreateSurfaceError(#[from] wgpu::CreateSurfaceError),
#[cfg(feature = "winit")]
#[error(transparent)]
HandleError(#[from] ::winit::raw_window_handle::HandleError),
}
#[derive(Clone)]
pub struct RenderState {
pub adapter: wgpu::Adapter,
#[cfg(not(target_arch = "wasm32"))]
pub available_adapters: Vec<wgpu::Adapter>,
pub device: wgpu::Device,
pub queue: wgpu::Queue,
pub target_format: wgpu::TextureFormat,
pub renderer: Arc<RwLock<Renderer>>,
}
async fn request_adapter(
instance: &wgpu::Instance,
power_preference: wgpu::PowerPreference,
compatible_surface: Option<&wgpu::Surface<'_>>,
available_adapters: &[wgpu::Adapter],
) -> Result<wgpu::Adapter, WgpuError> {
profiling::function_scope!();
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference,
compatible_surface,
force_fallback_adapter: false,
})
.await
.inspect_err(|_err| {
if cfg!(target_arch = "wasm32") {
} else if available_adapters.is_empty() {
if std::env::var("DYLD_LIBRARY_PATH").is_ok() {
log::warn!(
"No wgpu adapter found. This could be because DYLD_LIBRARY_PATH causes dylibs to be loaded that interfere with Metal device creation. Try restarting with DYLD_LIBRARY_PATH=''"
);
} else {
log::info!("No wgpu adapter found");
}
} else if available_adapters.len() == 1 {
log::info!(
"The only available wgpu adapter was not suitable: {}",
adapter_info_summary(&available_adapters[0].get_info())
);
} else {
log::info!(
"No suitable wgpu adapter found out of the {} available ones: {}",
available_adapters.len(),
describe_adapters(available_adapters)
);
}
})?;
if cfg!(target_arch = "wasm32") {
log::debug!(
"Picked wgpu adapter: {}",
adapter_info_summary(&adapter.get_info())
);
} else {
if available_adapters.len() == 1 {
log::debug!(
"Picked the only available wgpu adapter: {}",
adapter_info_summary(&adapter.get_info())
);
} else {
log::info!(
"There were {} available wgpu adapters: {}",
available_adapters.len(),
describe_adapters(available_adapters)
);
log::debug!(
"Picked wgpu adapter: {}",
adapter_info_summary(&adapter.get_info())
);
}
}
Ok(adapter)
}
impl RenderState {
pub async fn create(
config: &WgpuConfiguration,
instance: &wgpu::Instance,
compatible_surface: Option<&wgpu::Surface<'static>>,
options: RendererOptions,
) -> Result<Self, WgpuError> {
profiling::scope!("RenderState::create");
#[cfg(not(target_arch = "wasm32"))]
let available_adapters = {
let backends = if let WgpuSetup::CreateNew(create_new) = &config.wgpu_setup {
create_new.instance_descriptor.backends
} else {
wgpu::Backends::all()
};
instance.enumerate_adapters(backends).await
};
let (adapter, device, queue) = match config.wgpu_setup.clone() {
WgpuSetup::CreateNew(WgpuSetupCreateNew {
instance_descriptor: _,
display_handle: _,
power_preference,
native_adapter_selector: _native_adapter_selector,
device_descriptor,
}) => {
let adapter = {
#[cfg(target_arch = "wasm32")]
{
request_adapter(instance, power_preference, compatible_surface, &[]).await
}
#[cfg(not(target_arch = "wasm32"))]
if let Some(native_adapter_selector) = _native_adapter_selector {
native_adapter_selector(&available_adapters, compatible_surface)
.map_err(WgpuError::CustomNativeAdapterSelectionError)
} else {
request_adapter(
instance,
power_preference,
compatible_surface,
&available_adapters,
)
.await
}
}?;
let (device, queue) = {
profiling::scope!("request_device");
adapter
.request_device(&(*device_descriptor)(&adapter))
.await?
};
(adapter, device, queue)
}
WgpuSetup::Existing(WgpuSetupExisting {
instance: _,
adapter,
device,
queue,
}) => (adapter, device, queue),
};
let surface_formats = {
profiling::scope!("get_capabilities");
compatible_surface.map_or_else(
|| vec![wgpu::TextureFormat::Rgba8Unorm],
|s| s.get_capabilities(&adapter).formats,
)
};
let target_format = crate::preferred_framebuffer_format(&surface_formats)?;
let renderer = Renderer::new(&device, target_format, options);
#[allow(clippy::allow_attributes, clippy::arc_with_non_send_sync)] Ok(Self {
adapter,
#[cfg(not(target_arch = "wasm32"))]
available_adapters,
device,
queue,
target_format,
renderer: Arc::new(RwLock::new(renderer)),
})
}
}
fn describe_adapters(adapters: &[wgpu::Adapter]) -> String {
if adapters.is_empty() {
"(none)".to_owned()
} else if adapters.len() == 1 {
adapter_info_summary(&adapters[0].get_info())
} else {
adapters
.iter()
.map(|a| format!("{{{}}}", adapter_info_summary(&a.get_info())))
.collect::<Vec<_>>()
.join(", ")
}
}
pub enum SurfaceErrorAction {
SkipFrame,
RecreateSurface,
}
#[derive(Clone)]
pub struct WgpuConfiguration {
pub present_mode: wgpu::PresentMode,
pub desired_maximum_frame_latency: Option<u32>,
pub wgpu_setup: WgpuSetup,
pub on_surface_status:
Arc<dyn Fn(&wgpu::CurrentSurfaceTexture) -> SurfaceErrorAction + Send + Sync>,
}
#[test]
fn wgpu_config_impl_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<WgpuConfiguration>();
}
impl std::fmt::Debug for WgpuConfiguration {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self {
present_mode,
desired_maximum_frame_latency,
wgpu_setup,
on_surface_status: _,
} = self;
f.debug_struct("WgpuConfiguration")
.field("present_mode", &present_mode)
.field(
"desired_maximum_frame_latency",
&desired_maximum_frame_latency,
)
.field("wgpu_setup", &wgpu_setup)
.finish_non_exhaustive()
}
}
impl Default for WgpuConfiguration {
fn default() -> Self {
Self {
present_mode: wgpu::PresentMode::AutoVsync,
desired_maximum_frame_latency: None,
wgpu_setup: WgpuSetup::without_display_handle(),
on_surface_status: Arc::new(|status| {
match status {
wgpu::CurrentSurfaceTexture::Outdated => {
}
wgpu::CurrentSurfaceTexture::Occluded => {
log::debug!("Dropped frame with error: {status:?}");
}
_ => {
log::warn!("Dropped frame with error: {status:?}");
}
}
SurfaceErrorAction::SkipFrame
}),
}
}
}
pub fn preferred_framebuffer_format(
formats: &[wgpu::TextureFormat],
) -> Result<wgpu::TextureFormat, WgpuError> {
for &format in formats {
if matches!(
format,
wgpu::TextureFormat::Rgba8Unorm | wgpu::TextureFormat::Bgra8Unorm
) {
return Ok(format);
}
}
formats
.first()
.copied()
.ok_or(WgpuError::NoSurfaceFormatsAvailable)
}
pub fn depth_format_from_bits(depth_buffer: u8, stencil_buffer: u8) -> Option<wgpu::TextureFormat> {
match (depth_buffer, stencil_buffer) {
(0, 8) => Some(wgpu::TextureFormat::Stencil8),
(16, 0) => Some(wgpu::TextureFormat::Depth16Unorm),
(24, 0) => Some(wgpu::TextureFormat::Depth24Plus),
(24, 8) => Some(wgpu::TextureFormat::Depth24PlusStencil8),
(32, 0) => Some(wgpu::TextureFormat::Depth32Float),
(32, 8) => Some(wgpu::TextureFormat::Depth32FloatStencil8),
_ => None,
}
}
pub fn adapter_info_summary(info: &wgpu::AdapterInfo) -> String {
let wgpu::AdapterInfo {
name,
vendor,
device,
device_type,
driver,
driver_info,
backend,
device_pci_bus_id,
subgroup_min_size,
subgroup_max_size,
transient_saves_memory,
} = &info;
let mut summary = format!("backend: {backend:?}, device_type: {device_type:?}");
if !name.is_empty() {
summary += &format!(", name: {name:?}");
}
if !driver.is_empty() {
summary += &format!(", driver: {driver:?}");
}
if !driver_info.is_empty() {
summary += &format!(", driver_info: {driver_info:?}");
}
if *vendor != 0 {
#[cfg(not(target_arch = "wasm32"))]
{
summary += &format!(", vendor: {} (0x{vendor:04X})", parse_vendor_id(*vendor));
}
#[cfg(target_arch = "wasm32")]
{
summary += &format!(", vendor: 0x{vendor:04X}");
}
}
if *device != 0 {
summary += &format!(", device: 0x{device:02X}");
}
if !device_pci_bus_id.is_empty() {
summary += &format!(", pci_bus_id: {device_pci_bus_id:?}");
}
if *subgroup_min_size != 0 || *subgroup_max_size != 0 {
summary += &format!(", subgroup_size: {subgroup_min_size}..={subgroup_max_size}");
}
summary += &format!(", transient_saves_memory: {transient_saves_memory}");
summary
}
#[cfg(not(target_arch = "wasm32"))]
pub fn parse_vendor_id(vendor_id: u32) -> &'static str {
match vendor_id {
wgpu::hal::auxil::db::amd::VENDOR => "AMD",
wgpu::hal::auxil::db::apple::VENDOR => "Apple",
wgpu::hal::auxil::db::arm::VENDOR => "ARM",
wgpu::hal::auxil::db::broadcom::VENDOR => "Broadcom",
wgpu::hal::auxil::db::imgtec::VENDOR => "Imagination Technologies",
wgpu::hal::auxil::db::intel::VENDOR => "Intel",
wgpu::hal::auxil::db::mesa::VENDOR => "Mesa",
wgpu::hal::auxil::db::nvidia::VENDOR => "NVIDIA",
wgpu::hal::auxil::db::qualcomm::VENDOR => "Qualcomm",
_ => "Unknown",
}
}