diff --git a/Cargo.toml b/Cargo.toml index 1fa0b42..4dd158e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +bytemuck = { version = "^1.0", features = ["derive"] } multimap = "0.8" pollster = "0.2" rayon = "1" diff --git a/src/lib.rs b/src/lib.rs index 25071fd..887cd70 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,11 +36,11 @@ impl Renderer { } pub fn add_pass(&mut self, pass: T) { - let pass = Arc::new(RwLock::new(pass)); + let pass = Arc::new(pass); self.add_pass_arc(pass); } - pub fn add_pass_arc(&mut self, pass: Arc>) { + pub fn add_pass_arc(&mut self, pass: Arc) { let pass = RenderPassBox::new(pass, self.frames_in_flight); self.add_pass_box(pass); } @@ -99,32 +99,26 @@ impl Renderer { }); } + let opaque_cmds = Mutex::new(Vec::new()); + if let Some(opaque) = phase_passes.get_vec(&Phase::Opaque) { + opaque.par_iter().for_each(|pass_index| { + let pass = &self.render_passes[*pass_index]; + + let frame_data = IndexedPhaseData { + phase: Phase::Opaque, + frame_data: frame_index, + viewport: &viewport, + }; + + if let Some(cmd) = pass.record_render(frame_data) { + opaque_cmds.lock().unwrap().push(cmd); + } + }) + } + + let opaque_cmds = opaque_cmds.into_inner().unwrap(); + { - let mut opaque_cmds = Vec::new(); - if let Some(opaque) = phase_passes.get_vec(&Phase::Opaque) { - opaque.iter().for_each(|pass_index| { - let frame_data = IndexedPhaseData { - phase: Phase::Opaque, - frame_data: frame_index, - viewport: &viewport, - }; - - let pass = &self.render_passes[*pass_index]; - let mut cmds = self.device.create_render_bundle_encoder( - &wgpu::RenderBundleEncoderDescriptor { - label: Some("Opaque Pass Render Bundle"), - color_formats: &[format], - depth_stencil: None, - sample_count: 1, - multiview: None, - }, - ); - - pass.record_render(frame_data, &mut cmds); - opaque_cmds.push(cmds.finish(&wgpu::RenderBundleDescriptor::default())); - }) - } - let mut rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("Render Pass"), color_attachments: &[wgpu::RenderPassColorAttachment { diff --git a/src/main.rs b/src/main.rs index 7b8113a..2071069 100644 --- a/src/main.rs +++ b/src/main.rs @@ -73,7 +73,7 @@ fn main() { let (mut renderer, mut viewport) = pollster::block_on(make_window_renderer(&window)); let device = renderer.get_device(); - let mesh_pass = pass::mesh::MeshPass::new(device.to_owned()); + let mesh_pass = pass::mesh::MeshPass::new(device.to_owned(), viewport.config.format); renderer.add_pass(mesh_pass); event_loop.run(move |event, _, control_flow| match event { diff --git a/src/mesh.rs b/src/mesh.rs index cef1596..e22e5fb 100644 --- a/src/mesh.rs +++ b/src/mesh.rs @@ -133,10 +133,10 @@ pub struct AttrAllocKey { /// Info about an array of attributes that has been allocated in an [AttrPool]. #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] pub struct AttrAlloc { - offset: usize, - count: usize, - offset_bytes: usize, - count_bytes: usize, + pub offset: usize, + pub count: usize, + pub offset_bytes: usize, + pub count_bytes: usize, } /// An unused space range in an [AttrPool]. @@ -305,17 +305,21 @@ impl AttrPool { Ok(()) } - /// Gets the offset (in elements) of an allocation, passed by key. - pub fn get_offset(&self, key: usize) -> Result { - let alloc = self.allocs.get(key).ok_or(PoolError::InvalidIndex)?; - Ok(alloc.offset) + /// Retrieves an [AttrAlloc] by key. + pub fn get(&self, key: usize) -> Option { + self.allocs.get(key).copied() + } + + /// Gets this pool's internal GPU buffer. + pub fn get_buffer(&self) -> &wgpu::Buffer { + &self.buffer } /// Gets a [CopyDest] for an allocation, by key. pub fn get_copy_dest(&self, key: usize) -> Result { - let offset = self.get_offset(key)?; + let offset = self.get(key).ok_or(PoolError::InvalidIndex)?.offset_bytes; Ok(CopyDest { - buffer: &self.buffer, + buffer: self.get_buffer(), offset, }) } @@ -347,17 +351,31 @@ pub type MeshLayoutDesc = smallmap::Set; #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] pub struct MeshLayoutId(usize); +/// Mappings of the attributes in a [MeshLayout] to specific pool indices. +pub type MeshLayoutBindingIndices = smallmap::Map; + /// Mappings of the attributes in a [MeshLayout] to specific pools. -pub type MeshLayoutBindings = smallmap::Map; +pub struct MeshLayoutBindings<'a> { + lock: std::sync::RwLockReadGuard<'a, HashMap>>, + indices: MeshLayoutBindingIndices, +} + +impl<'a> MeshLayoutBindings<'a> { + pub fn get(&self, attr: AttrId) -> Option<&AttrPool> { + let pool_id = self.indices.get(&attr)?; + let pools = self.lock.get(&attr)?; + pools.get(*pool_id) + } +} /// Mappings of the attributes in a [MeshAlloc] to specific pool offsets. -pub type MeshAllocOffsets = SmallVec<[(AttrId, usize); MAX_MESH_INLINE_ATTRIBUTES]>; +pub type MeshAllocInfos = SmallVec<[(AttrId, AttrAlloc); MAX_MESH_INLINE_ATTRIBUTES]>; /// A set of mesh instances fitting a common [MeshLayoutDesc]. #[derive(Default)] pub struct MeshLayoutInstances { - pub bindings: MeshLayoutBindings, - pub instances: Vec<(T, MeshAllocOffsets)>, + pub bindings: MeshLayoutBindingIndices, + pub instances: Vec<(T, MeshAllocInfos)>, } /// A mesh data pool. @@ -476,7 +494,7 @@ impl MeshPool { ) -> Result>, PoolError> where I: Iterator, - F: Fn(&T) -> &'static MeshHandle, + F: Fn(&T) -> &MeshHandle, { let layout = self .mesh_layouts @@ -511,8 +529,8 @@ impl MeshPool { continue; } - let mut layout_bindings = MeshLayoutBindings::default(); - let mut alloc_offsets = MeshAllocOffsets::default(); + let mut layout_bindings = MeshLayoutBindingIndices::default(); + let mut alloc_infos = MeshAllocInfos::default(); for alloc in attr_allocs.iter() { let pools_read = self.pools.read().unwrap(); let pools = pools_read @@ -520,13 +538,13 @@ impl MeshPool { .ok_or(PoolError::AttrUnregistered)?; let pool = pools.get(alloc.pool).ok_or(PoolError::InvalidIndex)?; - let alloc_offset = pool.get_offset(alloc.alloc)?; + let alloc_info = pool.get(alloc.alloc).ok_or(PoolError::InvalidIndex)?; layout_bindings.insert(alloc.attr, alloc.pool); - alloc_offsets.push((alloc.attr, alloc_offset)); + alloc_infos.push((alloc.attr, alloc_info)); } - let instance = (mesh, alloc_offsets); + let instance = (mesh, alloc_infos); match layouts .iter_mut() @@ -542,4 +560,9 @@ impl MeshPool { Ok(layouts) } + + pub fn get_bindings<'a>(&'a self, indices: MeshLayoutBindingIndices) -> MeshLayoutBindings<'a> { + let lock = self.pools.read().unwrap(); + MeshLayoutBindings { lock, indices } + } } diff --git a/src/pass.rs b/src/pass.rs index 90d3dcb..7ec67c2 100644 --- a/src/pass.rs +++ b/src/pass.rs @@ -32,24 +32,24 @@ pub trait RenderPass: Send + Sync { /// Sets up a new instance of [Self::FrameData]. /// /// Called when a render pass is added to [crate::Renderer]. - fn create_frame_data(&mut self) -> Self::FrameData; + fn create_frame_data(&self) -> Self::FrameData; /// Initializes this frame's [Self::FrameData], and queries the pass for /// which phases to execute. /// /// This is the only opportunity the render pass has to mutate this frame's /// data this frame, so all setup for future phases should be done here. - fn begin_frame(&mut self, data: &mut Self::FrameData, phases: &mut Vec); + fn begin_frame(&self, data: &mut Self::FrameData, phases: &mut Vec); fn record_commands(&self, data: PhaseData<&Self::FrameData>, cmds: &mut wgpu::CommandEncoder); - fn record_compute(&self, data: PhaseData<&Self::FrameData>, cmds: &mut wgpu::ComputePass); - - fn record_render( - &self, + fn record_compute<'a>( + &'a self, data: PhaseData<&Self::FrameData>, - cmds: &mut wgpu::RenderBundleEncoder, + cmds: &mut wgpu::ComputePass<'a>, ); + + fn record_render(&self, data: PhaseData<&Self::FrameData>) -> Option; } /// The interface trait for [RenderPassBox], allowing use of a statically-sized @@ -62,14 +62,14 @@ pub trait RenderPassBoxTrait: Send + Sync { fn record_commands(&self, data: IndexedPhaseData, cmds: &mut wgpu::CommandEncoder); - fn record_compute(&self, data: IndexedPhaseData, cmds: &mut wgpu::ComputePass); + fn record_compute<'a>(&'a self, data: IndexedPhaseData, cmds: &mut wgpu::ComputePass<'a>); - fn record_render(&self, data: IndexedPhaseData, cmds: &mut wgpu::RenderBundleEncoder); + fn record_render(&self, data: IndexedPhaseData) -> Option; } /// A container for a reference-counted render pass instance and its frame data. pub struct RenderPassBox { - render_pass: Arc>, + render_pass: Arc, frame_data: Vec, } @@ -78,10 +78,11 @@ impl RenderPassBox { /// /// Calls to [RenderPassBoxTrait] functions with frame indices greater /// than or equal to `frame_num` are out-of-bounds and will panic. - pub fn new(render_pass: Arc>, frame_num: usize) -> Box { + pub fn new(render_pass: Arc, frame_num: usize) -> Box { let frame_data = { - let mut lock = render_pass.write().unwrap(); - (0..frame_num).map(|_| lock.create_frame_data()).collect() + (0..frame_num) + .map(|_| render_pass.create_frame_data()) + .collect() }; Box::new(Self { @@ -115,25 +116,22 @@ impl RenderPassBox { impl RenderPassBoxTrait for RenderPassBox { fn begin_frame(&mut self, data_index: usize, phases: &mut Vec) { let frame_data = &mut self.frame_data[data_index]; - let mut render_pass = self.render_pass.write().unwrap(); - render_pass.begin_frame(frame_data, phases) + self.render_pass.begin_frame(frame_data, phases) } fn record_commands(&self, data: IndexedPhaseData, cmds: &mut wgpu::CommandEncoder) { let frame_data = self.get_frame_data(data); - let render_pass = self.render_pass.read().unwrap(); - render_pass.record_commands(frame_data, cmds) + self.render_pass.record_commands(frame_data, cmds) } - fn record_compute(&self, data: IndexedPhaseData, cmds: &mut wgpu::ComputePass) { + fn record_compute<'a>(&'a self, data: IndexedPhaseData, cmds: &mut wgpu::ComputePass<'a>) { let frame_data = self.get_frame_data(data); - let render_pass = self.render_pass.read().unwrap(); - render_pass.record_compute(frame_data, cmds) + self.render_pass.record_compute(frame_data, cmds) } - fn record_render(&self, data: IndexedPhaseData, cmds: &mut wgpu::RenderBundleEncoder) { + fn record_render(&self, data: IndexedPhaseData) -> Option { let frame_data = self.get_frame_data(data); - let render_pass = self.render_pass.read().unwrap(); - render_pass.record_render(frame_data, cmds) + // let render_pass = self.render_pass.read().unwrap(); + self.render_pass.record_render(frame_data) } } diff --git a/src/pass/mesh.rs b/src/pass/mesh.rs index 62dde58..bd1b471 100644 --- a/src/pass/mesh.rs +++ b/src/pass/mesh.rs @@ -1,21 +1,154 @@ use super::*; use crate::mesh::*; +#[repr(C)] +#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +pub struct Vertex { + pub position: [f32; 3], + pub color: [f32; 3], +} + +impl Attribute for Vertex { + fn get_usages() -> wgpu::BufferUsages { + wgpu::BufferUsages::VERTEX + } +} + +const VERTEX_ATTRS: &[wgpu::VertexAttribute] = + &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3]; + +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; + pub struct FrameData {} pub struct MeshPass { + device: Arc, attr_store: Arc, mesh_pool: Arc, + vertex_attr_id: AttrId, + index_attr_id: AttrId, + mesh_layout_id: MeshLayoutId, + example_mesh: MeshHandle, + opaque_pipeline: wgpu::RenderPipeline, + target_format: wgpu::TextureFormat, } impl MeshPass { - pub fn new(device: Arc) -> Self { + pub fn new(device: Arc, target_format: wgpu::TextureFormat) -> Self { let attr_store = AttrStore::new(); - let mesh_pool = MeshPool::new(device, attr_store.to_owned()); + let mesh_pool = MeshPool::new(device.clone(), attr_store.to_owned()); + + let vertex_attr_id = attr_store.get_type::(); + let index_attr_id = attr_store.add(AttrInfo { + layout: AttrLayout { + size: std::mem::size_of::(), + }, + usages: wgpu::BufferUsages::INDEX, + default_pool_size: 1_000_000, + }); + + let mut mesh_layout = MeshLayoutDesc::new(); + mesh_layout.insert(vertex_attr_id, ()); + mesh_layout.insert(index_attr_id, ()); + let mesh_layout_id = mesh_pool.add_layout(mesh_layout).unwrap(); + + let example_vertices = vec![ + Vertex { + position: [-0.5, 0.5, 0.0], + color: [1.0, 0.0, 0.0], + }, + Vertex { + position: [0.5, 0.5, 0.0], + color: [0.0, 1.0, 0.0], + }, + Vertex { + position: [0.0, -0.5, 0.0], + color: [0.0, 0.0, 1.0], + }, + ]; + + let example_vertices = AttrBuffer { + id: vertex_attr_id, + count: example_vertices.len(), + data: bytemuck::cast_slice(&example_vertices).to_vec(), + }; + + let example_indices = vec![0u32, 1u32, 2u32]; + let example_indices = AttrBuffer { + id: index_attr_id, + count: example_indices.len(), + data: bytemuck::cast_slice(&example_indices).to_vec(), + }; + + let mut example_mesh = MeshBuffer::default(); + example_mesh.attributes.push(example_vertices); + example_mesh.attributes.push(example_indices); + let example_mesh = mesh_pool.load(example_mesh).unwrap(); + + let render_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Render Pipeline Layout"), + bind_group_layouts: &[], + push_constant_ranges: &[], + }); + + let shader = device.create_shader_module(&wgpu::include_wgsl!("mesh_shader.wgsl")); + + let opaque_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Render Pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[Vertex::desc()], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[wgpu::ColorTargetState { + format: target_format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + }], + }), + 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: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + }); Self { + device, attr_store, mesh_pool, + index_attr_id, + vertex_attr_id, + mesh_layout_id, + example_mesh, + opaque_pipeline, + target_format, } } } @@ -23,13 +156,14 @@ impl MeshPass { impl RenderPass for MeshPass { type FrameData = FrameData; - fn create_frame_data(&mut self) -> FrameData { + fn create_frame_data(&self) -> FrameData { FrameData {} } - fn begin_frame(&mut self, data: &mut FrameData, phases: &mut Vec) { + fn begin_frame(&self, data: &mut FrameData, phases: &mut Vec) { println!("MeshPass::begin_frame()"); + phases.push(Phase::Upload); phases.push(Phase::Depth); phases.push(Phase::Opaque); phases.push(Phase::Transparent); @@ -37,13 +171,74 @@ impl RenderPass for MeshPass { fn record_commands(&self, data: PhaseData<&FrameData>, cmds: &mut wgpu::CommandEncoder) { println!("MeshPass::record_commands(phase: {:?})", data.phase); + match data.phase { + Phase::Upload => self.mesh_pool.flush(cmds), + _ => {} + } } fn record_compute(&self, data: PhaseData<&FrameData>, cmds: &mut wgpu::ComputePass) { println!("MeshPass::record_compute(phase: {:?})", data.phase); } - fn record_render(&self, data: PhaseData<&FrameData>, cmds: &mut wgpu::RenderBundleEncoder) { + fn record_render(&self, data: PhaseData<&FrameData>) -> Option { println!("MeshPass::record_render(phase: {:?})", data.phase); + + let mut cmds = + self.device + .create_render_bundle_encoder(&wgpu::RenderBundleEncoderDescriptor { + label: Some("Opaque Pass Render Bundle"), + color_formats: &[self.target_format], + depth_stencil: None, + sample_count: 1, + multiview: None, + }); + + let pipeline = match data.phase { + Phase::Opaque => &self.opaque_pipeline, + _ => return None, + }; + + let meshes = &[&self.example_mesh, &self.example_mesh]; + + // yikes + let mesh_bindings: Vec<(MeshLayoutBindings, Vec<(&&MeshHandle, MeshAllocInfos)>)> = self + .mesh_pool + .iter_meshes(self.mesh_layout_id, meshes.iter(), |v| v) + .unwrap() + .into_iter() + .map( + |MeshLayoutInstances { + bindings, + instances, + }| (self.mesh_pool.get_bindings(bindings), instances), + ) + .collect(); + + cmds.set_pipeline(pipeline); + + for (bindings, instances) in mesh_bindings.iter() { + let vertices_pool = bindings.get(self.vertex_attr_id).unwrap(); + let indices_pool = bindings.get(self.index_attr_id).unwrap(); + + cmds.set_vertex_buffer(0, vertices_pool.get_buffer().slice(..)); + cmds.set_index_buffer( + indices_pool.get_buffer().slice(..), + wgpu::IndexFormat::Uint32, + ); + + for (mesh, infos) in instances { + let vertices = infos.iter().find(|i| i.0 == self.vertex_attr_id).unwrap().1; + let indices = infos.iter().find(|i| i.0 == self.index_attr_id).unwrap().1; + + let is_start = indices.offset as u32; + let is_end = is_start + indices.count as u32; + + cmds.draw_indexed(is_start..is_end, vertices.offset as i32, 0..1); + println!("drew a mesh! {}..{} + {}", is_start, is_end, vertices.offset); + } + } + + Some(cmds.finish(&wgpu::RenderBundleDescriptor::default())) } } diff --git a/src/pass/mesh_shader.wgsl b/src/pass/mesh_shader.wgsl new file mode 100644 index 0000000..93220be --- /dev/null +++ b/src/pass/mesh_shader.wgsl @@ -0,0 +1,30 @@ +struct VertexInput { + [[location(0)]] position: vec3; + [[location(1)]] color: vec3; +}; + +struct VertexOutput { + [[builtin(position)]] clip_position: vec4; + [[location(0)]] position: vec3; + [[location(1)]] color: vec3; +}; + +[[stage(vertex)]] +fn vs_main( + [[builtin(instance_index)]] mesh_idx: u32, + [[builtin(vertex_index)]] vertex_idx: u32, + vertex: VertexInput, +) -> VertexOutput { + var out: VertexOutput; + out.clip_position = vec4(vertex.position, 1.0); + out.position = vertex.position; + out.color = vertex.color; + return out; +} + +[[stage(fragment)]] +fn fs_main( + frag: VertexOutput, +) -> [[location(0)]] vec4 { + return vec4(frag.color, 1.0); +} diff --git a/src/staging.rs b/src/staging.rs index 19a694d..5bb72dd 100644 --- a/src/staging.rs +++ b/src/staging.rs @@ -64,6 +64,9 @@ impl StagingPool { on_complete(copy.target); } } + + drop(src_view); + src.unmap(); } pub fn queue_copies(&self, copies: Vec>) {