use self::{
belt::Belt,
catcher::Catcher,
surface::{ISurface, Surface},
};
use crate::{label, DeviceStorage, Frame};
use colorful::Colorful;
use std::sync::Arc;
use wgpu::{
util::power_preference_from_env, Adapter, Device, DeviceDescriptor, Features, Instance, Limits,
PowerPreference, Queue, RequestAdapterOptionsBase, TextureFormat,
};
use winit::window::Window;
pub mod prelude;
pub mod surface;
mod belt;
mod catcher;
pub struct Target {
pub(crate) device: Arc<Device>,
pub(crate) queue: Arc<Queue>,
pub(crate) surface: Option<Surface>,
pub(crate) belt: Belt,
catcher: Catcher,
active: bool,
init: bool,
}
impl Target {
pub async fn new(
instance: Arc<Instance>,
window: Arc<Window>,
device_storage: DeviceStorage,
) -> Self {
let surface = ISurface::new(window, instance.clone());
let (adapter, device, queue) =
Self::new_with_opt(instance, Some(&surface), device_storage).await;
let surface = Some(surface.complete(&adapter, device.clone()));
let belt = Belt::new(device.clone());
let catcher = Catcher::new(&device);
Self {
device,
queue,
surface,
belt,
catcher,
active: false,
init: true,
}
}
pub async fn new_headless(instance: Arc<Instance>, device_storage: DeviceStorage) -> Self {
let (_, device, queue) = Self::new_with_opt(instance, None, device_storage).await;
let belt = Belt::new(device.clone());
let catcher = Catcher::new(&device);
Self {
device,
queue,
surface: None,
belt,
catcher,
active: false,
init: true,
}
}
async fn new_with_opt(
instance: Arc<Instance>,
surface: Option<&wgpu::Surface>,
device_storage: DeviceStorage,
) -> (Arc<Adapter>, Arc<Device>, Arc<Queue>) {
if let Some(pre_existing) = Self::try_borrow_device(surface, device_storage.clone()) {
pre_existing
} else {
let adapter = Self::make_adapter(surface, &instance).await;
Self::debug_report(&adapter);
let (device, queue) = Self::make_device(&adapter).await;
if let Ok(mut write) = device_storage.write() {
write.push((adapter.clone(), device.clone(), queue.clone()));
}
(adapter, device, queue)
}
}
pub fn compatible_with(&self, other: &Target) -> bool {
Arc::ptr_eq(&self.device, &other.device) && Arc::ptr_eq(&self.queue, &other.queue)
}
#[must_use]
pub fn get_frame(&mut self) -> Frame {
if self.active {
panic!("Earlier frame was not finished before starting a new one");
}
if self.init {
self.init = false;
self.get_window().unwrap().set_visible(true);
}
Frame::new(
&self.device,
self.queue.clone(),
self.surface.as_mut().expect("TODO: Draw in headless mode"),
self.belt.get(),
)
}
pub fn finish_frame(&mut self, frame: Frame) {
self.belt.set(frame.finish())
}
pub fn set_vsync(&mut self, on: bool) {
if let Some(s) = self.surface.as_mut() {
s.set_vsync(on);
}
}
pub fn get_vsync(&self) -> Option<bool> {
self.surface.as_ref().map(|s| s.get_vsync())
}
pub fn get_window(&self) -> Option<Arc<Window>> {
self.surface.as_ref().map(|surface| surface.get_window())
}
pub fn get_format(&self) -> TextureFormat {
self.surface
.as_ref()
.map(|surface| surface.format())
.unwrap_or(TextureFormat::Rgba8Unorm)
}
pub fn get_device(&self) -> Arc<Device> {
self.device.clone()
}
pub fn catch_error<T, F: FnOnce(&Self) -> T>(&self, f: F) -> Result<T, String> {
Catcher::catch_error(self, f)
}
fn try_borrow_device(
compatible_surface: Option<&wgpu::Surface>,
device_storage: DeviceStorage,
) -> Option<(Arc<Adapter>, Arc<Device>, Arc<Queue>)> {
device_storage
.read()
.ok()?
.iter()
.find(|(adapter, _, _)| {
if let Some(surface) = compatible_surface {
adapter.is_surface_supported(surface)
} else {
true
}
})
.cloned()
}
async fn make_adapter(
compatible_surface: Option<&wgpu::Surface>,
instance: &Instance,
) -> Arc<Adapter> {
Arc::new(
instance
.request_adapter(&RequestAdapterOptionsBase {
power_preference: power_preference_from_env()
.unwrap_or(PowerPreference::HighPerformance),
force_fallback_adapter: false,
compatible_surface,
})
.await
.expect("No suitable GPUs"),
)
}
fn debug_report(adapter: &Adapter) {
if log::log_enabled!(log::Level::Debug) {
let gpu_info = adapter.get_info();
let api = format!("{:?}", gpu_info.backend).red();
let name = gpu_info.name.blue();
let ty = format!("{:?}", gpu_info.device_type).green();
log::debug!("GPU API: {api}");
log::debug!("GPU: {name} ({ty})");
}
}
async fn make_device(adapter: &Adapter) -> (Arc<Device>, Arc<Queue>) {
let (device, queue) = adapter
.request_device(
&DeviceDescriptor {
label: label!(),
features: Features::empty(),
limits: Limits {
..Limits::downlevel_webgl2_defaults()
},
},
None,
)
.await
.unwrap();
(Arc::new(device), Arc::new(queue))
}
}