use std::sync::Arc; use thiserror::Error; use winit::{ dpi::PhysicalSize, error::{EventLoopError, OsError}, event::WindowEvent, event_loop::{EventLoop, EventLoopWindowTarget}, window::{Window, WindowBuilder}, }; #[derive(Error, Debug)] pub enum ContextError { #[error("Surface creation failed: {0}")] Surface(#[from] wgpu::CreateSurfaceError), #[error("Surface configuration failed: {0}")] SurfaceConfig(String), #[error("No compatible adapter found")] Adapter, #[error("Device request failed: {0}")] Device(#[from] wgpu::RequestDeviceError), #[error("Event loop creation failed: {0}")] EventLoop(#[from] EventLoopError), #[error("Window creation failed: {0}")] Os(#[from] OsError), } pub struct Context<'window> { pub window: Arc, pub instance: wgpu::Instance, pub size: PhysicalSize, pub surface: wgpu::Surface<'window>, pub surface_config: wgpu::SurfaceConfiguration, pub adapter: wgpu::Adapter, pub device: wgpu::Device, pub queue: wgpu::Queue, } impl<'window> Context<'window> { pub async fn new( window: Arc, backends: wgpu::Backends, required_features: wgpu::Features, required_limits: wgpu::Limits, ) -> Result { log::info!("Initialising WGPU context..."); let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { backends, dx12_shader_compiler: Default::default(), ..Default::default() }); // To be able to start drawing we need a few things: // - A surface // - A GPU device to draw to the surface // - A draw command queue log::info!("Initialising window surface..."); let surface = instance.create_surface(window.clone())?; log::info!("Requesting GPU adapter..."); let adapter = match instance .request_adapter(&wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::HighPerformance, force_fallback_adapter: false, compatible_surface: Some(&surface), }) .await { Some(val) => val, None => return Err(ContextError::Adapter), }; log::info!("Checking GPU adapter meets requirements"); log::info!("Requesting GPU device..."); let (device, queue) = adapter .request_device( &wgpu::DeviceDescriptor { label: None, required_features, required_limits, }, None, ) .await?; log::info!("Configuring window surface..."); let size = window.inner_size(); let surface_config = match surface.get_default_config(&adapter, size.width, size.height) { Some(val) => val, None => { return Err(ContextError::SurfaceConfig( "Surface configuration unsupported by adapter".to_string(), )) } }; surface.configure(&device, &surface_config); Ok(Self { window, instance, size, surface, surface_config, adapter, device, queue, }) } pub fn resize_surface(&mut self, new_size: PhysicalSize) { if new_size.width > 0 && new_size.height > 0 { self.size = new_size; self.surface_config.width = new_size.width; self.surface_config.height = new_size.height; self.surface.configure(&self.device, &self.surface_config); } } pub fn handle_window_event( &mut self, event: &WindowEvent, elwt: &EventLoopWindowTarget<()>, ) -> bool { let mut handled = true; match event { WindowEvent::CloseRequested => { elwt.exit(); } WindowEvent::Resized(physical_size) => { self.resize_surface(*physical_size); } WindowEvent::ScaleFactorChanged { .. } => { self.resize_surface(self.window.inner_size()); } _ => handled = false, } handled } } pub struct ContextBuilder { title: String, vsync: bool, resolution: PhysicalSize, backends: wgpu::Backends, features: wgpu::Features, limits: wgpu::Limits, } impl Default for ContextBuilder { fn default() -> Self { Self { title: "Crawl".to_string(), vsync: true, resolution: PhysicalSize::new(1280, 720), backends: Default::default(), features: Default::default(), limits: Default::default(), } } } impl ContextBuilder { pub fn new() -> Self { Self::default() } pub fn with_title(mut self, title: &str) -> Self { self.title = title.to_string(); self } pub fn with_vsync(mut self, vsync: bool) -> Self { self.vsync = vsync; self } pub fn with_resolution(mut self, width: u32, height: u32) -> Self { self.resolution = PhysicalSize::new(width, height); self } pub fn with_backends(mut self, backends: wgpu::Backends) -> Self { self.backends = backends; self } pub fn with_features(mut self, features: wgpu::Features) -> Self { self.features = features; self } pub fn with_limits(mut self, limits: wgpu::Limits) -> Self { self.limits = limits; self } pub async fn build(self) -> Result<(Context<'static>, EventLoop<()>), ContextError> { let event_loop = EventLoop::new()?; let window = Arc::new( WindowBuilder::new() .with_title(self.title) .with_inner_size(self.resolution) .build(&event_loop)?, ); let context = Context::new(window, self.backends, self.features, self.limits).await?; Ok((context, event_loop)) } }