use wgpu::{
Adapter, Device, Features, Instance, Limits, MemoryHints, Queue, Surface, SurfaceTarget,
};
mod buffer_renderer;
mod error;
mod surface_renderer;
mod util;
pub use buffer_renderer::{BufferRenderer, BufferRendererConfig};
pub use error::WgpuContextError;
pub use surface_renderer::{SurfaceRenderer, SurfaceRendererConfiguration, TextureConfiguration};
pub use util::block_on_wgpu;
#[derive(Clone, Debug)]
pub struct DeviceHandle {
pub instance: Instance,
pub adapter: Adapter,
pub device: Device,
pub queue: Queue,
}
pub struct WGPUContext {
pub instance: Instance,
pub device_pool: Vec<DeviceHandle>,
extra_features: Option<Features>,
override_limits: Option<Limits>,
}
impl Default for WGPUContext {
fn default() -> Self {
Self::new()
}
}
impl WGPUContext {
pub fn new() -> Self {
Self::with_features_and_limits(None, None)
}
pub fn with_features_and_limits(
extra_features: Option<Features>,
override_limits: Option<Limits>,
) -> Self {
Self {
instance: Instance::new(&wgpu::InstanceDescriptor {
backends: wgpu::Backends::from_env().unwrap_or_default(),
flags: wgpu::InstanceFlags::from_build_config().with_env(),
backend_options: wgpu::BackendOptions::from_env_or_default(),
memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
}),
device_pool: Vec::new(),
extra_features,
override_limits,
}
}
pub async fn create_surface<'w>(
&mut self,
window: impl Into<SurfaceTarget<'w>>,
surface_config: SurfaceRendererConfiguration,
intermediate_texture_config: Option<TextureConfiguration>,
) -> Result<SurfaceRenderer<'w>, WgpuContextError> {
let surface = self.instance.create_surface(window.into())?;
let dev_id = self
.find_or_create_device(Some(&surface))
.await
.or(Err(WgpuContextError::NoCompatibleDevice))?;
let device_handle = self.device_pool[dev_id].clone();
SurfaceRenderer::new(
surface,
surface_config,
intermediate_texture_config,
device_handle,
dev_id,
)
.await
}
pub async fn create_buffer_renderer(
&mut self,
config: BufferRendererConfig,
) -> Result<BufferRenderer, WgpuContextError> {
let dev_id = self
.find_or_create_device(None)
.await
.or(Err(WgpuContextError::NoCompatibleDevice))?;
let device_handle = self.device_pool[dev_id].clone();
Ok(BufferRenderer::new(config, device_handle, dev_id))
}
pub async fn find_or_create_device(
&mut self,
compatible_surface: Option<&Surface<'_>>,
) -> Result<usize, WgpuContextError> {
match self.find_existing_device(compatible_surface) {
Some(device_id) => Ok(device_id),
None => self.create_device(compatible_surface).await,
}
}
fn find_existing_device(&self, compatible_surface: Option<&Surface<'_>>) -> Option<usize> {
match compatible_surface {
Some(s) => self
.device_pool
.iter()
.enumerate()
.find(|(_, d)| d.adapter.is_surface_supported(s))
.map(|(i, _)| i),
None => (!self.device_pool.is_empty()).then_some(0),
}
}
async fn create_device(
&mut self,
compatible_surface: Option<&Surface<'_>>,
) -> Result<usize, WgpuContextError> {
let instance = self.instance.clone();
let adapter =
wgpu::util::initialize_adapter_from_env_or_default(&instance, compatible_surface)
.await?;
let requested_features = self.extra_features.unwrap_or(Features::empty());
let available_features = adapter.features();
let required_features = requested_features & available_features;
let required_limits = self.override_limits.clone().unwrap_or_default();
let descripter = wgpu::DeviceDescriptor {
label: None,
required_features,
required_limits,
memory_hints: MemoryHints::MemoryUsage,
trace: wgpu::Trace::default(),
experimental_features: wgpu::ExperimentalFeatures::default(),
};
let (device, queue) = adapter.request_device(&descripter).await?;
let device_handle = DeviceHandle {
instance,
adapter,
device,
queue,
};
self.device_pool.push(device_handle);
Ok(self.device_pool.len() - 1)
}
}