use super::*; use crate::shader::{ShaderHandle, ShaderStore}; use crate::storage::mesh::*; use crate::storage::GpuVec; use crate::viewport::ViewportInfo; use crate::RenderLayouts; use parking_lot::RwLock; #[derive(Clone)] pub struct ShaderInfo { pub store: Arc, pub forward: ShaderHandle, pub skinning: ShaderHandle, } #[repr(C)] #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] pub struct Vertex { pub position: [f32; 3], pub tan_frame: u32, } impl Attribute for Vertex { fn get_usages() -> wgpu::BufferUsages { wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::VERTEX } } const VERTEX_ATTRS: &[wgpu::VertexAttribute] = &wgpu::vertex_attr_array![0 => Float32x3, 1 => Uint32]; impl Vertex { pub fn desc() -> wgpu::VertexBufferLayout<'static> { wgpu::VertexBufferLayout { array_stride: std::mem::size_of::() as wgpu::BufferAddress, step_mode: wgpu::VertexStepMode::Vertex, attributes: VERTEX_ATTRS, } } } pub type Index = u32; #[derive(Clone)] pub struct Attributes { pub vertex: AttrId, pub index: AttrId, } impl Attributes { pub fn new(attr_store: impl AsRef) -> Self { Self { vertex: attr_store.as_ref().get_type::(), index: attr_store.as_ref().add(AttrInfo { layout: AttrLayout { size: std::mem::size_of::(), }, usages: wgpu::BufferUsages::INDEX, default_pool_size: 1_000_000, }), } } } #[derive(Clone, Debug)] pub struct TransformedMesh { pub mesh: MeshHandle, pub transform: glam::Mat4, } #[repr(C)] #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] struct SkinningUniform { transform: [[f32; 4]; 4], src_offset: u32, dst_offset: u32, count: u32, _padding: u32, } #[derive(Debug)] struct MeshCommand { vertex_offset: usize, vertex_count: usize, skinned_offset: usize, index_offset: usize, index_count: usize, skinning_index: usize, } #[derive(Debug)] struct MeshGroupCommands { binding_indices: MeshLayoutBindingIndices, bind_group: wgpu::BindGroup, meshes: Vec, } pub struct FrameData { skinned_vertices: GpuVec, skinning_uniforms: GpuVec, groups: Vec, } pub struct MeshPass { device: Arc, layouts: Arc, attr_store: Arc, shader_info: ShaderInfo, mesh_pool: Arc, attributes: Attributes, mesh_layout_id: MeshLayoutId, skinning_bind_group_layout: wgpu::BindGroupLayout, skinning_pipeline: wgpu::ComputePipeline, depth_pipeline: wgpu::RenderPipeline, opaque_pipeline: wgpu::RenderPipeline, target_info: ViewportInfo, transformed_meshes: RwLock>, } impl MeshPass { pub fn new( device: Arc, layouts: Arc, target_info: ViewportInfo, shader_info: ShaderInfo, ) -> Self { let attr_store = AttrStore::new(); let mesh_pool = MeshPool::new(device.clone(), attr_store.to_owned()); let attributes = Attributes::new(&attr_store); let mut mesh_layout = MeshLayoutDesc::new(); mesh_layout.insert(attributes.vertex, ()); mesh_layout.insert(attributes.index, ()); let mesh_layout_id = mesh_pool.add_layout(mesh_layout).unwrap(); let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("MeshPass Pipeline Layout"), bind_group_layouts: &[&layouts.bind_viewport], push_constant_ranges: &[], }); let shader = shader_info.store.get(&shader_info.forward).unwrap(); let targets = &[Some(wgpu::ColorTargetState { format: target_info.output_format, blend: Some(wgpu::BlendState::REPLACE), write_mask: wgpu::ColorWrites::ALL, })]; let mut pipeline_desc = wgpu::RenderPipelineDescriptor { label: Some("Opaque MeshPass Pipeline"), layout: Some(&render_pipeline_layout), vertex: wgpu::VertexState { module: shader.as_ref(), entry_point: "vs_main", buffers: &[Vertex::desc()], }, fragment: Some(wgpu::FragmentState { module: shader.as_ref(), entry_point: "fs_main", targets, }), primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, strip_index_format: None, front_face: wgpu::FrontFace::Ccw, cull_mode: None, // Some(wgpu::Face::Back), polygon_mode: wgpu::PolygonMode::Fill, unclipped_depth: false, conservative: false, }, depth_stencil: Some(wgpu::DepthStencilState { format: target_info.depth_format, depth_write_enabled: true, depth_compare: wgpu::CompareFunction::Less, stencil: Default::default(), bias: Default::default(), }), multisample: wgpu::MultisampleState { count: 1, mask: !0, alpha_to_coverage_enabled: false, }, multiview: None, }; let depth_pipeline = device.create_render_pipeline(&pipeline_desc); pipeline_desc.depth_stencil = Some(wgpu::DepthStencilState { format: target_info.depth_format, depth_write_enabled: false, depth_compare: wgpu::CompareFunction::Equal, stencil: Default::default(), bias: Default::default(), }); let opaque_pipeline = device.create_render_pipeline(&pipeline_desc); drop(shader); let skinning_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("Skinning Bind Group Layout"), entries: &[ wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Storage { read_only: true }, has_dynamic_offset: true, min_binding_size: None, // TODO ??? }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Storage { read_only: false }, has_dynamic_offset: false, min_binding_size: None, }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 2, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Storage { read_only: true }, has_dynamic_offset: false, min_binding_size: None, }, count: None, }, ], }); let skinning_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("Skinning Pipeline Layout"), bind_group_layouts: &[&skinning_bind_group_layout], push_constant_ranges: &[], }); let skinning_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { label: Some("Skinning Pipeline"), layout: Some(&skinning_pipeline_layout), module: shader_info .store .get(&shader_info.skinning) .unwrap() .as_ref(), entry_point: "cs_main", }); Self { device, layouts, attr_store, shader_info, mesh_pool, attributes, mesh_layout_id, skinning_bind_group_layout, skinning_pipeline, depth_pipeline, opaque_pipeline, target_info, transformed_meshes: Default::default(), } } pub fn get_mesh_pool(&self) -> &MeshPool { &self.mesh_pool } pub fn get_attributes(&self) -> &Attributes { &self.attributes } pub fn add_transformed_meshes(&self, meshes: &[TransformedMesh]) { self.transformed_meshes.write().extend_from_slice(meshes); } } impl RenderPass for MeshPass { type FrameData = FrameData; fn create_frame_data(&self) -> FrameData { FrameData { skinned_vertices: GpuVec::new( self.device.clone(), Vertex::get_usages(), 1024 * 128, Some("Skinned Vertices".to_string()), false, ), skinning_uniforms: GpuVec::new( self.device.clone(), wgpu::BufferUsages::STORAGE, 1024 * 128, Some("Skinning Uniforms".to_string()), true, ), groups: Default::default(), } } fn begin_frame(&self, data: &mut FrameData, phases: &mut Vec, queue: &wgpu::Queue) { phases.push(Phase::Upload); phases.push(Phase::Skinning); phases.push(Phase::Depth); phases.push(Phase::Opaque); phases.push(Phase::Transparent); data.groups.clear(); data.skinning_uniforms.clear(); let mut transformed_meshes_lock = self.transformed_meshes.write(); let mesh_bindings = self .mesh_pool .iter_meshes(self.mesh_layout_id, transformed_meshes_lock.iter(), |i| { &i.mesh }) .unwrap(); let mut skinned_cursor = 0; for MeshLayoutInstances { bindings, instances, } in mesh_bindings.iter() { let pools = self.mesh_pool.get_bindings(bindings.clone()); let vertices_pool = pools.get(self.attributes.vertex).unwrap(); // TODO defer bind group creation into separate Vec after GpuVecs have been written let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor { label: None, layout: &self.skinning_bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { buffer: data.skinning_uniforms.as_ref(), offset: 0, // TODO ugly! size: Some( std::num::NonZeroU64::new( std::mem::size_of::() as u64 ) .unwrap(), ), }), }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { buffer: data.skinned_vertices.as_ref(), offset: 0, size: None, }), }, wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { buffer: vertices_pool.get_buffer(), offset: 0, size: None, }), }, ], }); let mut group = MeshGroupCommands { binding_indices: bindings.clone(), meshes: Default::default(), bind_group, }; for (mesh, infos) in instances { let vertices = infos .iter() .find(|i| i.0 == self.attributes.vertex) .unwrap() .1; let indices = infos .iter() .find(|i| i.0 == self.attributes.index) .unwrap() .1; group.meshes.push(MeshCommand { vertex_offset: vertices.offset, vertex_count: vertices.count, skinned_offset: skinned_cursor, index_offset: indices.offset, index_count: indices.count, skinning_index: data.skinning_uniforms.len(), }); data.skinning_uniforms.push(SkinningUniform { transform: mesh.transform.to_cols_array_2d(), src_offset: vertices.offset as u32, dst_offset: skinned_cursor as u32, count: vertices.count as u32, _padding: 0, }); skinned_cursor += vertices.count; } data.groups.push(group); } data.skinned_vertices.reserve(skinned_cursor); data.skinned_vertices.write(&queue); data.skinning_uniforms.write(&queue); transformed_meshes_lock.clear(); } fn record_commands(&self, data: PhaseData<&FrameData>, cmds: &mut wgpu::CommandEncoder) { match data.phase { Phase::Upload => self.mesh_pool.flush(cmds), _ => {} } } fn record_compute<'a>( &'a self, data: PhaseData<&'a FrameData>, cmds: &mut wgpu::ComputePass<'a>, ) { cmds.set_pipeline(&self.skinning_pipeline); for group in data.frame_data.groups.iter() { for mesh in group.meshes.iter() { let ubo_offset = data .frame_data .skinning_uniforms .buf_offset(mesh.skinning_index); cmds.set_bind_group(0, &group.bind_group, &[ubo_offset as u32]); // TODO use div_ceil instead let workgroup_num = if mesh.vertex_count % 64 == 0 { mesh.vertex_count / 64 } else { mesh.vertex_count / 64 + 1 }; cmds.dispatch_workgroups(workgroup_num as u32, 1, 1); } } } fn record_render(&self, data: PhaseData<&FrameData>) -> Option { let pipeline = match data.phase { Phase::Depth => &self.depth_pipeline, Phase::Opaque => &self.opaque_pipeline, _ => return None, }; let mut cmds = self.device .create_render_bundle_encoder(&wgpu::RenderBundleEncoderDescriptor { label: Some("Opaque Pass Render Bundle"), color_formats: &[Some(self.target_info.output_format)], depth_stencil: Some(wgpu::RenderBundleDepthStencil { format: self.target_info.depth_format, depth_read_only: false, // TODO optimize? stencil_read_only: true, }), sample_count: 1, multiview: None, }); // yikes let mesh_bindings: Vec<_> = data .frame_data .groups .iter() .map( |MeshGroupCommands { binding_indices, meshes, .. }| (self.mesh_pool.get_bindings(binding_indices.clone()), meshes), ) .collect(); cmds.set_pipeline(pipeline); cmds.set_bind_group(0, data.bind_viewport, &[]); for (bindings, meshes) in mesh_bindings.iter() { let indices_pool = bindings.get(self.attributes.index).unwrap(); cmds.set_vertex_buffer(0, data.frame_data.skinned_vertices.as_ref().slice(..)); cmds.set_index_buffer( indices_pool.get_buffer().slice(..), wgpu::IndexFormat::Uint32, ); for mesh in meshes.iter() { let is_start = mesh.index_offset as u32; let is_end = is_start + mesh.index_count as u32; let vs_offset = mesh.skinned_offset as i32; cmds.draw_indexed(is_start..is_end, vs_offset, 0..1); } } Some(cmds.finish(&wgpu::RenderBundleDescriptor::default())) } }