use std::error::Error; use glam::{UVec2, Vec4, vec4}; mod mesh; const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth16Unorm; const OUTPUT_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Bgra8UnormSrgb; #[derive(Debug, Clone, Copy)] #[repr(C)] pub struct RedrawArgs { pub background: Vec4, } impl Default for RedrawArgs { fn default() -> Self { Self { background: vec4(0.05, 0.20, 0.85, 1.00), } } } pub struct Gpu { device: wgpu::Device, queue: wgpu::Queue, surface: wgpu::Surface<'static>, } pub struct Core { device: wgpu::Device, queue: wgpu::Queue, surface: wgpu::Surface<'static>, depth: wgpu::Texture, } impl Core { pub fn new(gpu: Gpu, pixel_size: UVec2) -> Self { let Gpu { device, queue, surface, } = gpu; let depth = Self::create_depth_buffer(&device, pixel_size); queue.submit([]); // flush buffer updates Self::configure_surface(&surface, &device, pixel_size); Self { device, queue, surface, depth, } } fn render(&self, output: &wgpu::Texture, args: &RedrawArgs) { let aspect = { let size = output.size(); let w = size.width as f32; let h = size.height as f32; w / h }; self.queue.submit([]); // flush buffer updates let view = output.create_view(&wgpu::TextureViewDescriptor::default()); let depth_view = self .depth .create_view(&wgpu::TextureViewDescriptor::default()); let mut encoder = self .device .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &view, depth_slice: None, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color { r: args.background.x.into(), g: args.background.y.into(), b: args.background.z.into(), a: args.background.w.into(), }), store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { view: &depth_view, depth_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Clear(1.), store: wgpu::StoreOp::Discard, }), stencil_ops: None, }), ..Default::default() }); drop(pass); self.queue.submit(std::iter::once(encoder.finish())); } fn create_depth_buffer(device: &wgpu::Device, pixel_size: UVec2) -> wgpu::Texture { device.create_texture(&wgpu::TextureDescriptor { label: Some("depth buffer"), size: wgpu::Extent3d { width: pixel_size.x, height: pixel_size.y, depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: DEPTH_FORMAT, usage: wgpu::TextureUsages::RENDER_ATTACHMENT, view_formats: &[], }) } fn configure_surface(surface: &wgpu::Surface, device: &wgpu::Device, pixel_size: UVec2) { surface.configure( device, &wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST, format: OUTPUT_FORMAT, width: pixel_size.x, height: pixel_size.y, present_mode: wgpu::PresentMode::Fifo, alpha_mode: wgpu::CompositeAlphaMode::Auto, view_formats: vec![], desired_maximum_frame_latency: 2, }, ); } /// Configures the renderer for a given target size. pub fn configure(&mut self, pixel_size: UVec2) { Self::configure_surface(&self.surface, &self.device, pixel_size); self.depth = Self::create_depth_buffer(&self.device, pixel_size); } /// Redraws the entire surface. /// /// [`Self::configure`] must be called at least once before this. pub fn redraw(&mut self, args: &RedrawArgs) { let output = self.surface.get_current_texture().unwrap(); self.render(&output.texture, args); output.present(); } } pub async fn init_gpu_inner( make_surface: impl FnOnce(&wgpu::Instance) -> Result, E>, ) -> Result> { let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { backends: wgpu::Backends::PRIMARY, ..Default::default() }); let surface = make_surface(&instance)?; let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::default(), compatible_surface: Some(&surface), force_fallback_adapter: false, }) .await .unwrap(); let (device, queue) = adapter .request_device(&wgpu::DeviceDescriptor::default()) .await .unwrap(); Ok(Gpu { device, queue, surface, }) }