diff --git a/Cargo.lock b/Cargo.lock index 498584f..30f43a5 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,6 +56,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "aligned-vec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" + [[package]] name = "android-activity" version = "0.4.2" @@ -1075,6 +1081,7 @@ dependencies = [ name = "lyra-engine" version = "0.1.0" dependencies = [ + "aligned-vec", "anyhow", "async-std", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index f969378..036146a 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,3 +29,4 @@ syn = "2.0.26" quote = "1.0.29" edict = "0.5.0" atomicell = "0.1.9" +aligned-vec = "0.5.0" diff --git a/src/main.rs b/src/main.rs index 0e51c2b..04ec870 100755 --- a/src/main.rs +++ b/src/main.rs @@ -79,11 +79,22 @@ async fn main() { indices: Some(crate::render::vertex::INDICES.to_vec()) }, Material { shader_id: 0, - texture: diffuse_texture + texture: diffuse_texture.clone() }), TransformComponent::from(Transform::from_xyz(0.005, 0.0, 0.0)), )); + world.spawn((MeshComponent::new( + render::mesh::Mesh { + vertices: crate::render::vertex::VERTICES.to_vec(), + indices: Some(crate::render::vertex::INDICES.to_vec()) + }, Material { + shader_id: 0, + texture: diffuse_texture + }), + TransformComponent::from(Transform::from_xyz(0.005, 0.7, 0.0)), + )); + let mut camera = CameraComponent::new(); camera.transform.translation += glam::Vec3::new(0.0, 0.0, 2.0); //camera.transform.rotate_y(Angle::Degrees(-25.0)); @@ -123,23 +134,14 @@ async fn main() { for transform in world.query_mut::<(&mut TransformComponent,)>().iter_mut() { let t = &mut transform.transform; - //debug!("Translation: {}", t.translation); + debug!("Translation: {}", t.translation); /* t.translation += glam::Vec3::new(0.0, 0.001, 0.0); t.translation.x *= -1.0; */ t.translation.x += dir_x; t.translation.y += dir_y; } - /* for (transform,) in world.query_mut::<(TransformComponent, )>().iter() { - let t = &mut transform.transform; - - /* debug!("Translation: {}", t.translation); - - t.translation += glam::Vec3::new(0.0, 0.001, 0.0); - t.translation.x *= -1.0 */ - t.translation.x += dir_x; - t.translation.y += dir_y; - } */ + debug!("end"); Ok(()) }; diff --git a/src/render/renderer.rs b/src/render/renderer.rs index 049eaf2..ecfaf81 100755 --- a/src/render/renderer.rs +++ b/src/render/renderer.rs @@ -1,15 +1,19 @@ use std::cell::RefCell; use std::collections::{HashMap, VecDeque}; +use std::mem; +use std::num::NonZeroU64; use std::sync::Arc; use std::borrow::Cow; +use aligned_vec::AVec; use async_std::sync::Mutex; use async_trait::async_trait; use atomicell::{AtomicCell, RefMut}; use edict::{EntityId, Entities}; +use glam::Mat4; use tracing::{debug, warn}; -use wgpu::{BindGroup, BindGroupLayout}; +use wgpu::{BindGroup, BindGroupLayout, Limits, BufferBinding}; use wgpu::util::DeviceExt; use winit::window::Window; @@ -39,7 +43,75 @@ struct RenderBufferStorage { render_texture: Option, texture_bindgroup: Option, - texture_layout: Option + texture_layout: Option, + + /// The index of the transform for this entity. + /// The tuple is structured like this: (transform index, index of transform inside the buffer) + transform_index: TransformBufferIndices, +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +struct TransformBufferIndices { + buffer_index: usize, + transform_index: usize, +} + +struct TransformBuffers { + /// A vector storing the EntityId and + just_updated: HashMap, + not_updated: HashMap, + dead_indices: VecDeque, + next_indices: TransformBufferIndices, + buffer_bindgroups: Vec<(wgpu::Buffer, wgpu::BindGroup)>, +} + +impl TransformBuffers { + /// Update an entity's buffer with the new transform. Will panic if the entity isn't stored + fn update_entity(&mut self, queue: &wgpu::Queue, limits: &Limits, entity: EntityId, transform: glam::Mat4) { + let indices = self.not_updated.remove(&entity) + .expect("Use 'insert_entity' for new entities"); + self.just_updated.insert(entity, indices); + + let (buffer, _) = self.buffer_bindgroups.get(indices.buffer_index).unwrap(); + queue.write_buffer(buffer, indices.transform_index as u64 * limits.min_uniform_buffer_offset_alignment as u64, bytemuck::bytes_of(&transform)); + } + + /// Insert a new entity into the buffer, returns where it was stored. + fn insert_entity(&mut self, queue: &wgpu::Queue, limits: &Limits, entity: EntityId, transform: glam::Mat4) -> TransformBufferIndices { + // get a dead index, or create a new one + let indices = if let Some(index) = self.dead_indices.pop_front() { + index + } else { + // TODO: Create new buffer if this one is full + let indices = &mut self.next_indices; + let new = indices.clone(); + indices.transform_index += 1; + + new + }; + + let (buffer, _) = self.buffer_bindgroups.get(indices.buffer_index).unwrap(); + queue.write_buffer(buffer, Self::get_offset_for(limits, indices), bytemuck::bytes_of(&transform)); + + self.just_updated.insert(entity, indices); + indices + } + + /// Collect the dead entities, mark entities and not updated for next updates. + fn tick(&mut self) { + // take the dead entities, these were ones that were not updated this tick + let dead: VecDeque = self.not_updated.values() + .map(|t| t.clone()).collect(); + self.dead_indices = dead; + + // swap just_updated into not_updated + self.not_updated = self.just_updated.clone(); + self.just_updated.clear(); + } + + fn get_offset_for(limits: &Limits, indices: TransformBufferIndices) -> u64 { + indices.transform_index as u64 * limits.min_uniform_buffer_offset_alignment as u64 + } } pub struct BasicRenderer { @@ -57,8 +129,11 @@ pub struct BasicRenderer { buffer_storage: HashMap, // TODO: clean up left over buffers from deleted entities/components - transform_buffer: wgpu::Buffer, - transform_bind_group: wgpu::BindGroup, + transform_buffers: TransformBuffers, + transform_bind_group_layout: BindGroupLayout, + //transform_bind_group: wgpu::BindGroup, + + render_limits: Limits, inuse_camera: RenderCamera, camera_buffer: wgpu::Buffer, @@ -102,6 +177,7 @@ impl BasicRenderer { None, ).await.unwrap(); + let render_limits = device.limits(); let surface_caps = surface.get_capabilities(&adapter); let present_mode = surface_caps.present_modes[0]; /* match surface_caps.present_modes.contains(&wgpu::PresentMode::Immediate) { @@ -158,14 +234,6 @@ impl BasicRenderer { source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(&shader_src)), }); - let transform_buffer = device.create_buffer_init( - &wgpu::util::BufferInitDescriptor { - label: Some("Transform Buffer"), - contents: bytemuck::cast_slice(&[glam::Mat4::IDENTITY]), - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - } - ); - let transform_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { entries: &[ wgpu::BindGroupLayoutEntry { @@ -173,7 +241,7 @@ impl BasicRenderer { visibility: wgpu::ShaderStages::VERTEX, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, + has_dynamic_offset: true, min_binding_size: None, }, count: None, @@ -182,17 +250,43 @@ impl BasicRenderer { label: Some("transform_bind_group_layout"), }); + let transform_buffer = device.create_buffer( + &wgpu::BufferDescriptor { + label: Some("Transform Buffer 0"), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + size: render_limits.max_uniform_buffer_binding_size as u64, + mapped_at_creation: false, + } + ); + + let stride = render_limits.min_uniform_buffer_offset_alignment as usize + mem::size_of::(); let transform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &transform_bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, - resource: transform_buffer.as_entire_binding(), + resource: wgpu::BindingResource::Buffer( + wgpu::BufferBinding { + buffer: &transform_buffer, + offset: 0, + size: Some(NonZeroU64::new(stride as u64).unwrap()) + } + ) } ], label: Some("transform_bind_group"), }); + // create create the transform buffer storage + //let transforms = AVec::new(render_limits.min_uniform_buffer_offset_alignment as usize); + let transform_buffers = TransformBuffers { + buffer_bindgroups: vec![( transform_buffer, transform_bind_group )], + just_updated: HashMap::new(), + not_updated: HashMap::new(), + dead_indices: VecDeque::new(), + next_indices: TransformBufferIndices { buffer_index: 0, transform_index: 0 }, + }; + let camera_buffer = device.create_buffer_init( &wgpu::util::BufferInitDescriptor { label: Some("Camera Buffer"), @@ -226,7 +320,7 @@ impl BasicRenderer { } ], label: Some("camera_bind_group"), - }); + }); let mut pipelines = HashMap::new(); pipelines.insert(0, Arc::new(FullRenderPipeline::new(&device, &config, &shader, @@ -250,8 +344,9 @@ impl BasicRenderer { render_jobs: VecDeque::new(), buffer_storage: HashMap::new(), - transform_buffer, - transform_bind_group, + render_limits, + transform_buffers, + transform_bind_group_layout, inuse_camera: RenderCamera::new(size), camera_buffer, @@ -259,7 +354,7 @@ impl BasicRenderer { } } - fn create_model_buffers(&mut self, model_2d: &MeshComponent) -> RenderBufferStorage { + fn create_model_buffers(&mut self, model_2d: &MeshComponent, transform_indices: TransformBufferIndices) -> RenderBufferStorage { let mesh = &model_2d.mesh; let vertex_buffer = self.device.create_buffer_init( @@ -340,24 +435,34 @@ impl BasicRenderer { render_texture: None, texture_layout: None, texture_bindgroup: Some(diffuse_bind_group), + transform_index: transform_indices } } } impl Renderer for BasicRenderer { fn prepare(&mut self, main_world: &mut edict::World) { - + // render_limits.max_uniform_buffer_binding_size for (entity, model, transform) in main_world.query::<(Entities, &MeshComponent, &TransformComponent)>().iter() { // Create the render job and push it to the queue let job = RenderJob::new(model.mesh.clone(), model.material.clone(), entity, transform.transform, None); self.render_jobs.push_back(job); if self.buffer_storage.get(&entity).is_none() { - let buffers = self.create_model_buffers(model); + let indices = self.transform_buffers.insert_entity(&self.queue, &self.render_limits, + entity, transform.transform.calculate_mat4()); + + let buffers = self.create_model_buffers(model, indices); self.buffer_storage.insert(entity, buffers); + } else { + self.transform_buffers.update_entity(&self.queue, &self.render_limits, + entity, transform.transform.calculate_mat4()); } } + // collect dead entities + self.transform_buffers.tick(); + if let Some(camera) = main_world.query_mut::<(&mut CameraComponent,)>().into_iter().next() { let view_proj = self.inuse_camera.update_view_projection(camera); self.queue.write_buffer(&self.camera_buffer, 0, bytemuck::cast_slice(&[view_proj.clone()])); @@ -393,22 +498,28 @@ impl Renderer for BasicRenderer { // Pop off jobs from the queue as they're being processed while let Some(job) = self.render_jobs.pop_front() { if let Some(pipeline) = self.render_pipelines.get(&job.material().shader_id) { + // specify to use this pipeline render_pass.set_pipeline(pipeline.get_wgpu_pipeline()); + // get the mesh (containing vertices) and the buffers from storage let mesh = job.mesh(); let buffers = self.buffer_storage.get(&job.entity()).unwrap(); + // Bind the optional texture if let Some(tex) = buffers.texture_bindgroup.as_ref() { render_pass.set_bind_group(0, &tex, &[]); } - // Update transform buffer, and bind to the bind group - self.queue.write_buffer(&self.transform_buffer, 0, bytemuck::cast_slice(&[job.transform().calculate_mat4()])); - render_pass.set_bind_group(1, &self.transform_bind_group, &[]); + // Get the bindgroup for job's transform and bind to it using an offset. + let transform_indices = buffers.transform_index; + let (_, bindgroup) = self.transform_buffers.buffer_bindgroups.get(transform_indices.buffer_index).unwrap(); + let offset = TransformBuffers::get_offset_for(&self.render_limits, transform_indices); + render_pass.set_bind_group(1, bindgroup, &[ offset as u32, ]); - // There will always be a camera (hopefully) + // Bind camera render_pass.set_bind_group(2, &self.camera_bind_group, &[]); + // if this mesh uses indices, use them to draw the mesh if let Some(indices) = buffers.buffer_indices.as_ref() { let indices_len = indices.count().unwrap(); // index buffers will have count, if not thats a bug