use super::raster::Dem; use { bytemuck::{Pod, Zeroable}, std::{borrow::Cow, num::NonZero, rc::Rc}, wgpu::util::DeviceExt, }; pub struct DemRenderer { source: Rc, pipeline: wgpu::RenderPipeline, bind_group: wgpu::BindGroup, vertex_buffer: wgpu::Buffer, index_buffer: wgpu::Buffer, index_count: usize, camera: Camera, uniforms: UniformBufferManager, animation_start: std::time::Instant, } #[repr(C)] #[derive(Clone, Copy, Pod, Zeroable)] struct Vertex { position: [f32; 4], } impl Vertex { fn new(x: f32, y: f32, z: f32) -> Self { Self { position: [x, y, z, 1.0], } } } struct Camera { projection_matrix: glam::Mat4, view_matrix: glam::Mat4, } impl Camera { fn new(viewport_aspect_ratio: f32) -> Self { let projection_matrix = glam::Mat4::perspective_rh( std::f32::consts::FRAC_PI_4, viewport_aspect_ratio, 1.0, 10000.0, ); let view_matrix = glam::Mat4::look_at_rh(glam::Vec3::ZERO, glam::Vec3::ZERO, glam::Vec3::Z); Self { projection_matrix, view_matrix, } } fn set_position(&mut self, camera_position: glam::Vec3, camera_look_at: glam::Vec3) { self.view_matrix = glam::Mat4::look_at_rh(camera_position, camera_look_at, glam::Vec3::Z); } fn set_aspect_ratio(&mut self, ratio: f32) { self.projection_matrix = glam::Mat4::perspective_rh(std::f32::consts::FRAC_PI_4, ratio, 1.0, 10.0); } fn get_matrix(&self) -> glam::Mat4 { self.projection_matrix * self.view_matrix } } #[repr(C)] #[derive(Clone, Copy, Pod, Zeroable)] struct UniformBufferContents { camera_matrix: [f32; 16], dem_texture_size: [u32; 2], } // This struct is 72 bytes but WGSL expects the buffer size to be be a multiple // of the alignment of the member with the largest alignment. The mat4x4 // type has a 16-byte alignment, so we round up to 16 * 5 = 80. const UNIFORM_BUFFER_SIZE: u64 = 80; struct UniformBufferManager { cpu_buffer: UniformBufferContents, gpu_buffer: wgpu::Buffer, } impl UniformBufferManager { fn new(device: &wgpu::Device) -> Self { let cpu_buffer = UniformBufferContents::zeroed(); let gpu_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Uniform Buffer"), contents: &[0u8; UNIFORM_BUFFER_SIZE as usize], usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, }); Self { cpu_buffer, gpu_buffer, } } fn set_camera_matrix(&mut self, camera_matrix: glam::Mat4) { self.cpu_buffer .camera_matrix .clone_from_slice(&camera_matrix.to_cols_array()) } fn set_dem_texture_size(&mut self, dem_texture_size: glam::UVec2) { self.cpu_buffer .dem_texture_size .clone_from_slice(&dem_texture_size.to_array()); } fn get_binding(&self) -> wgpu::BindingResource<'_> { wgpu::BindingResource::Buffer(wgpu::BufferBinding { buffer: &self.gpu_buffer, offset: 0, size: NonZero::new(UNIFORM_BUFFER_SIZE), }) } fn update_buffer(&self, queue: &wgpu::Queue) { queue.write_buffer(&self.gpu_buffer, 0, bytemuck::bytes_of(&self.cpu_buffer)); } } impl DemRenderer { pub fn new( source: Rc, device: &wgpu::Device, surface_config: &wgpu::SurfaceConfiguration, queue: &wgpu::Queue, ) -> Self { let (vertex_data, index_data) = create_vertices(&source); let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("DemRenderer Vertex Buffer"), contents: bytemuck::cast_slice(&vertex_data), usage: wgpu::BufferUsages::VERTEX, }); let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("DemRenderer Index Buffer"), contents: bytemuck::cast_slice(&index_data), usage: wgpu::BufferUsages::INDEX, }); let index_count = index_data.len(); let (dem_texture_view, dem_texture_size) = load_dem_texture(&source, device, queue); let camera = Camera::new(surface_config.width as f32 / surface_config.height as f32); let mut uniforms = UniformBufferManager::new(device); uniforms.set_dem_texture_size(dem_texture_size); let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: None, entries: &[ wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: wgpu::BufferSize::new(UNIFORM_BUFFER_SIZE), }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Texture { multisampled: false, sample_type: wgpu::TextureSampleType::Uint, view_dimension: wgpu::TextureViewDimension::D2, }, count: None, }, ], }); let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: uniforms.get_binding(), }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(&dem_texture_view), }, ], label: Some("DemRendererBindGroup"), }); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, bind_group_layouts: &[&bind_group_layout], push_constant_ranges: &[], }); let vertex_buffers = [wgpu::VertexBufferLayout { array_stride: size_of::() as wgpu::BufferAddress, step_mode: wgpu::VertexStepMode::Vertex, attributes: &[wgpu::VertexAttribute { format: wgpu::VertexFormat::Float32x4, offset: 0, shader_location: 0, }], }]; let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: None, source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("dem_renderer.wgsl"))), }); let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("DemRendererPipeline"), layout: Some(&pipeline_layout), vertex: wgpu::VertexState { module: &shader, entry_point: Some("vs_main"), compilation_options: Default::default(), buffers: &vertex_buffers, }, fragment: Some(wgpu::FragmentState { module: &shader, entry_point: Some("fs_solid"), compilation_options: Default::default(), targets: &[Some(surface_config.view_formats[0].into())], }), primitive: wgpu::PrimitiveState { cull_mode: Some(wgpu::Face::Back), ..Default::default() }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), multiview: None, cache: None, }); DemRenderer { source, pipeline, bind_group, vertex_buffer, index_buffer, index_count, animation_start: std::time::Instant::now(), camera, uniforms, } } pub fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("DemRendererCommandEncoder"), }); self.camera.set_position( get_animated_camera_position( self.animation_start.elapsed(), self.get_max_dem_dimension(), ), self.get_dem_centre(), ); self.uniforms.set_camera_matrix(self.camera.get_matrix()); self.uniforms.update_buffer(queue); { let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("DemRendererRenderPass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { view, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, }); rpass.set_pipeline(&self.pipeline); rpass.set_bind_group(0, &self.bind_group, &[]); rpass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16); rpass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); rpass.draw_indexed(0..self.index_count as u32, 0, 0..1); } queue.submit(Some(encoder.finish())); } fn get_max_dem_dimension(&self) -> f32 { let dem = &self.source; (dem.x_max - dem.x_min) .max(dem.y_max - dem.y_min) .max(dem.z_max - dem.x_min) } fn get_dem_centre(&self) -> glam::Vec3 { let dem = &self.source; let min_corner = glam::Vec3::new(dem.x_min, dem.y_min, dem.z_min); let max_corner = glam::Vec3::new(dem.x_max, dem.y_max, dem.z_max); min_corner + (max_corner - min_corner) / 2.0 } } fn create_vertices(dem: &Rc) -> (Vec, Vec) { let x_min = dem.x_min; let x_max = dem.x_max; let y_min = dem.y_min; let y_max = dem.y_max; let z_min = dem.z_min; let z_max = dem.z_max; let vertex_data = [ // top (0, 0, 1) Vertex::new(x_min, y_min, z_max), Vertex::new(x_max, y_min, z_max), Vertex::new(x_max, y_max, z_max), Vertex::new(x_min, y_max, z_max), // bottom (0, 0, -1) Vertex::new(x_min, y_max, z_min), Vertex::new(x_max, y_max, z_min), Vertex::new(x_max, y_min, z_min), Vertex::new(x_min, y_min, z_min), // right (1, 0, 0) Vertex::new(x_max, y_min, z_min), Vertex::new(x_max, y_max, z_min), Vertex::new(x_max, y_max, z_max), Vertex::new(x_max, y_min, z_max), // left (-1, 0, 0) Vertex::new(x_min, y_min, z_max), Vertex::new(x_min, y_max, z_max), Vertex::new(x_min, y_max, z_min), Vertex::new(x_min, y_min, z_min), // front (0, 1, 0) Vertex::new(x_max, y_max, z_min), Vertex::new(x_min, y_max, z_min), Vertex::new(x_min, y_max, z_max), Vertex::new(x_max, y_max, z_max), // back (0, -1, 0) Vertex::new(x_max, y_min, z_max), Vertex::new(x_min, y_min, z_max), Vertex::new(x_min, y_min, z_min), Vertex::new(x_max, y_min, z_min), ]; let index_data: &[u16] = &[ 0, 1, 2, 2, 3, 0, // top 4, 5, 6, 6, 7, 4, // bottom 8, 9, 10, 10, 11, 8, // right 12, 13, 14, 14, 15, 12, // left 16, 17, 18, 18, 19, 16, // front 20, 21, 22, 22, 23, 20, // back ]; (vertex_data.to_vec(), index_data.to_vec()) } fn load_dem_texture( source: &Dem, device: &wgpu::Device, queue: &wgpu::Queue, ) -> (wgpu::TextureView, glam::UVec2) { let texture_size = wgpu::Extent3d { width: source.num_cells_x, height: source.num_cells_y, depth_or_array_layers: 1, }; let texture = device.create_texture(&wgpu::TextureDescriptor { label: Some("Dem Texture"), size: texture_size, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::R16Uint, usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, view_formats: &[], }); queue.write_texture( wgpu::ImageCopyTexture { texture: &texture, mip_level: 0, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All, }, bytemuck::cast_slice(&source.grid[..]), wgpu::ImageDataLayout { offset: 0, bytes_per_row: Some(std::mem::size_of::() as u32 * source.num_cells_x), rows_per_image: Some(source.num_cells_y), }, texture_size, ); ( texture.create_view(&wgpu::TextureViewDescriptor::default()), glam::UVec2::new(source.num_cells_x, source.num_cells_y), ) } fn get_animated_camera_position(animation_time: std::time::Duration, dem_size: f32) -> glam::Vec3 { let animation_phase = 2.0 * std::f32::consts::PI * (animation_time.as_secs_f32() % 10.0) / 10.0; glam::Vec3::new( dem_size * f32::sin(animation_phase), dem_size * f32::cos(animation_phase), dem_size * 0.7, ) }