214 lines
6.0 KiB
Rust
214 lines
6.0 KiB
Rust
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<Window>,
|
|
pub instance: wgpu::Instance,
|
|
pub size: PhysicalSize<u32>,
|
|
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<Window>,
|
|
backends: wgpu::Backends,
|
|
required_features: wgpu::Features,
|
|
required_limits: wgpu::Limits,
|
|
) -> Result<Self, ContextError> {
|
|
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<u32>) {
|
|
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<u32>,
|
|
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))
|
|
}
|
|
}
|