use std::error::Error; use glam::{Mat4, UVec2, Vec3, Vec4, vec3, vec4}; mod camera; mod render; #[derive(Debug, Clone, Copy)] #[repr(C)] pub struct RedrawArgs { pub background: Vec4, pub cam_yaw: f32, pub cam_pitch: f32, pub cam_distance: f32, pub cam_fovy: f32, } impl Default for RedrawArgs { fn default() -> Self { Self { background: vec4(0.05, 0.20, 0.85, 1.00), cam_yaw: std::f32::consts::FRAC_PI_4, cam_pitch: std::f32::consts::FRAC_PI_6, cam_distance: 3., cam_fovy: std::f32::consts::FRAC_PI_2, } } } 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>, mesh_pipeline: render::mesh::Pipeline, mesh1: render::mesh::GpuMeshIndexed, depth: wgpu::Texture, } fn cube() -> render::mesh::CpuMeshIndexed { let mut mesh = render::mesh::CpuMeshIndexed::default(); for (u, v, w) in [ (Vec3::X, Vec3::Y, Vec3::Z), (Vec3::Y, Vec3::Z, Vec3::X), (Vec3::Z, Vec3::X, Vec3::Y), ] { for (m, color) in [(1., w), (-1., 1. - w)] { let base = mesh.vertices.len() as u16; for offset in [m * v + u, -m * v + u, -m * v - u, m * v - u] { let vertex = render::mesh::Vertex { position: (m * w + offset), color, normal: m * w, }; mesh.vertices.push(vertex); } mesh.faces.push([base, base + 1, base + 2]); mesh.faces.push([base, base + 2, base + 3]); } } mesh } 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); let mesh_pipeline = render::mesh::Pipeline::new(&device); let mesh1 = cube(); let mesh1 = render::mesh::GpuMeshIndexed::new(&device, &mesh1); Self::configure_surface(&surface, &device, pixel_size); Self { device, queue, surface, mesh_pipeline, mesh1, 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 }; let view = Mat4::from(camera::view( args.cam_yaw, args.cam_pitch, args.cam_distance, )); let proj = camera::projection(args.cam_fovy, aspect); self.mesh_pipeline.set_params( &self.queue, render::mesh::Params { mvp: proj * view, light: vec3(1., 0.5, 0.5).normalize(), _zero: 0., }, ); 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() }); self.mesh1.render(&self.mesh_pipeline, &mut pass); 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: render::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: render::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, }) }