use crate::{EnabledPipelines, GpuRenderer, GraphicsError, parallel::*};
use ahash::AHashSet;
#[cfg(feature = "logging")]
use log::debug;
use std::sync::Arc;
use wgpu::{
Adapter, Backends, CurrentSurfaceTexture, DeviceType, Surface,
TextureFormat,
};
use winit::{dpi::PhysicalSize, event::WindowEvent, window::Window};
#[derive(Debug)]
pub struct GpuDevice {
pub device: wgpu::Device,
pub queue: wgpu::Queue,
}
impl GpuDevice {
pub fn device(&self) -> &wgpu::Device {
&self.device
}
pub fn queue(&self) -> &wgpu::Queue {
&self.queue
}
}
#[derive(Default, Clone, Debug, PartialEq, Eq, Hash)]
pub enum AdapterPowerSettings {
LowPower,
#[default]
HighPower,
}
#[derive(Debug)]
pub struct AdapterOptions {
pub allowed_backends: Backends,
pub power: AdapterPowerSettings,
pub compatible_surface: Option<Surface<'static>>,
}
#[derive(Debug)]
pub struct GpuWindow {
pub(crate) adapter: wgpu::Adapter,
pub(crate) surface: wgpu::Surface<'static>,
pub(crate) window: Arc<Window>,
pub(crate) surface_format: wgpu::TextureFormat,
pub(crate) size: PhysicalSize<f32>,
pub(crate) inner_size: PhysicalSize<u32>,
pub(crate) surface_config: wgpu::SurfaceConfiguration,
}
impl GpuWindow {
pub fn adapter(&self) -> &wgpu::Adapter {
&self.adapter
}
pub fn resize(&mut self, gpu_device: &GpuDevice, size: PhysicalSize<u32>) {
let width = size.width.max(1);
let height = size.height.max(1);
self.surface_config.height = height;
self.surface_config.width = width;
self.surface
.configure(gpu_device.device(), &self.surface_config);
self.size = PhysicalSize::new(width as f32, height as f32);
}
pub fn size(&self) -> PhysicalSize<f32> {
self.size
}
pub fn surface(&self) -> &wgpu::Surface<'_> {
&self.surface
}
pub fn surface_format(&self) -> wgpu::TextureFormat {
self.surface_format
}
pub fn pre_present_notify(&self) {
self.window.pre_present_notify();
}
pub fn update(
&mut self,
instance: &wgpu::Instance,
gpu_device: &GpuDevice,
event: &WindowEvent,
) -> Result<Option<wgpu::SurfaceTexture>, GraphicsError> {
let configure = |surface: &Surface, window: &Window| {
surface.configure(gpu_device.device(), &self.surface_config);
window.request_redraw();
};
match event {
WindowEvent::Resized(physical_size) => {
self.resize(gpu_device, *physical_size);
self.inner_size = self.window.inner_size();
self.window.request_redraw();
}
WindowEvent::RedrawRequested => {
match self.surface.get_current_texture() {
CurrentSurfaceTexture::Success(frame) => {
return Ok(Some(frame));
}
CurrentSurfaceTexture::Lost => {
let surface = instance
.create_surface(self.window.clone())
.unwrap();
self.surface = surface;
configure(&self.surface, &self.window);
}
CurrentSurfaceTexture::Suboptimal(surface) => {
drop(surface);
configure(&self.surface, &self.window);
}
CurrentSurfaceTexture::Outdated => {
configure(&self.surface, &self.window);
}
CurrentSurfaceTexture::Timeout
| CurrentSurfaceTexture::Occluded => {
self.window.request_redraw();
}
CurrentSurfaceTexture::Validation => unreachable!(
"No error scope registered, so validation errors will panic"
),
}
}
WindowEvent::Moved(_)
| WindowEvent::ScaleFactorChanged {
scale_factor: _,
inner_size_writer: _,
}
| WindowEvent::Focused(true)
| WindowEvent::Occluded(false) => {
self.window.request_redraw();
}
_ => (),
}
Ok(None)
}
pub fn window(&self) -> &Window {
&self.window
}
pub fn create_depth_texture(
&self,
gpu_device: &GpuDevice,
) -> wgpu::TextureView {
let size = wgpu::Extent3d {
width: self.size.width as u32,
height: self.size.height as u32,
depth_or_array_layers: 1,
};
let texture =
gpu_device
.device()
.create_texture(&wgpu::TextureDescriptor {
label: Some("depth texture"),
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Depth32Float,
usage: wgpu::TextureUsages::TEXTURE_BINDING
| wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::COPY_DST,
view_formats: &[TextureFormat::Depth32Float],
});
texture.create_view(&wgpu::TextureViewDescriptor::default())
}
}
pub trait AdapterExt {
#[allow(async_fn_in_trait)]
async fn create_renderer(
self,
instance: &wgpu::Instance,
window: &Arc<Window>,
device_descriptor: &wgpu::DeviceDescriptor,
present_mode: wgpu::PresentMode,
pipelined_enabled: EnabledPipelines,
) -> Result<GpuRenderer, GraphicsError>;
}
impl AdapterExt for wgpu::Adapter {
async fn create_renderer(
self,
instance: &wgpu::Instance,
window: &Arc<Window>,
device_descriptor: &wgpu::DeviceDescriptor<'_>,
present_mode: wgpu::PresentMode,
pipelined_enabled: EnabledPipelines,
) -> Result<GpuRenderer, GraphicsError> {
let size = window.inner_size();
let (device, queue) = self.request_device(device_descriptor).await?;
let surface = instance.create_surface(window.clone()).unwrap();
let caps = surface.get_capabilities(&self);
#[cfg(feature = "logging")]
debug!("{:?}", caps.formats);
let caps: AHashSet<TextureFormat> = caps.formats.into_iter().collect();
let format = if caps.get(&TextureFormat::Rgba8UnormSrgb).is_some() {
TextureFormat::Rgba8UnormSrgb
} else if caps.get(&TextureFormat::Bgra8UnormSrgb).is_some() {
TextureFormat::Bgra8UnormSrgb
} else {
panic!(
"Your Rendering Device does not support Bgra8UnormSrgb or Rgba8UnormSrgb"
);
};
#[cfg(feature = "logging")]
debug!("surface format: {format:?}");
let surface_config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format,
width: size.width,
height: size.height,
present_mode,
alpha_mode: wgpu::CompositeAlphaMode::Auto,
view_formats: vec![format],
desired_maximum_frame_latency: 2,
};
surface.configure(&device, &surface_config);
let inner_size = window.inner_size();
let mut renderer = GpuRenderer::new(
GpuWindow {
adapter: self,
surface,
window: window.clone(),
surface_format: format,
size: PhysicalSize::new(size.width as f32, size.height as f32),
surface_config,
inner_size,
},
GpuDevice { device, queue },
);
renderer.create_pipelines(pipelined_enabled, renderer.surface_format());
Ok(renderer)
}
}
pub trait InstanceExt {
#[allow(async_fn_in_trait)]
async fn create_device(
&self,
window: Arc<Window>,
options: AdapterOptions,
device_descriptor: &wgpu::DeviceDescriptor,
present_mode: wgpu::PresentMode,
pipelined_enabled: EnabledPipelines,
) -> Result<GpuRenderer, GraphicsError>;
#[allow(async_fn_in_trait)]
async fn get_adapters(
&self,
options: AdapterOptions,
) -> Vec<(Adapter, u32, u32)>;
}
impl InstanceExt for wgpu::Instance {
async fn get_adapters(
&self,
options: AdapterOptions,
) -> Vec<(Adapter, u32, u32)> {
let adapters = self.enumerate_adapters(options.allowed_backends).await;
let compatible_test = |adapter: Adapter| {
let information = adapter.get_info();
let mut backend = information.backend as u32;
if backend == 0 {
backend = 6; }
let is_low = options.power == AdapterPowerSettings::LowPower;
let device_type = match information.device_type {
DeviceType::IntegratedGpu if is_low => 1,
DeviceType::IntegratedGpu => 2,
DeviceType::DiscreteGpu if is_low => 2,
DeviceType::DiscreteGpu => 1,
DeviceType::Other => 3,
DeviceType::VirtualGpu => 4,
DeviceType::Cpu => 5,
};
if let Some(ref surface) = options.compatible_surface
&& !adapter.is_surface_supported(surface)
{
return None;
}
Some((adapter, device_type, backend))
};
let mut compatible_adapters: Vec<(Adapter, u32, u32)> = adapters
.into_par_iter()
.filter_map(compatible_test)
.collect();
#[cfg(feature = "logging")]
if compatible_adapters.is_empty() {
debug!(
"Unable to find compatible adapters.\nEnsure the backends are set and not Empty."
)
}
#[cfg(not(feature = "logging"))]
if compatible_adapters.is_empty() {
panic!(
"Unable to find compatible adapters.\nEnsure the backends are set and not Empty."
)
}
compatible_adapters.sort_by(|a, b| b.1.cmp(&a.1).then(b.2.cmp(&a.2)));
compatible_adapters
}
async fn create_device(
&self,
window: Arc<Window>,
options: AdapterOptions,
device_descriptor: &wgpu::DeviceDescriptor<'_>,
present_mode: wgpu::PresentMode,
pipelined_enabled: EnabledPipelines,
) -> Result<GpuRenderer, GraphicsError> {
let mut adapters = self.get_adapters(options).await;
while let Some(adapter) = adapters.pop() {
let ret = adapter
.0
.create_renderer(
self,
&window,
device_descriptor,
present_mode,
pipelined_enabled,
)
.await;
if ret.is_ok() {
#[cfg(feature = "logging")]
match adapter.1 {
3 => debug!("A Opengl Or Other Adapter was chosen"),
4 => debug!("A Virtual Adapter was chosen."),
5 => debug!("A Software rendering Adapter was chosen."),
_ => {}
}
#[cfg(feature = "logging")]
match adapter.2 {
1 => debug!("Vulkan Adapter Choosen"),
2 => debug!("Metal Adapter Choosen"),
3 => debug!("DX12 Adapter Choosen"),
4 => debug!("OpenGL Adapter Choosen"),
_ => debug!("WebGPU Adapter Choosen"),
}
return ret;
}
}
Err(GraphicsError::AdapterNotFound)
}
}