From 65467c50325d6a8257a52ea83dfc6677aec0b696 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sun, 12 Nov 2023 14:56:59 -0500 Subject: [PATCH] Add normal matrix for lighting, fix render multiple entities that use same model --- examples/testbed/src/main.rs | 18 ++- examples/testbed/testbed-renderdoc.cap | 28 ++++ src/render/render_job.rs | 6 +- src/render/renderer.rs | 128 +++------------- src/render/shaders/base.wgsl | 5 +- src/render/transform_buffer_storage.rs | 201 +++++++++++++++++++++---- 6 files changed, 229 insertions(+), 157 deletions(-) create mode 100644 examples/testbed/testbed-renderdoc.cap diff --git a/examples/testbed/src/main.rs b/examples/testbed/src/main.rs index fb9384d..b7dc7cf 100644 --- a/examples/testbed/src/main.rs +++ b/examples/testbed/src/main.rs @@ -77,10 +77,10 @@ async fn main() { TransformComponent::from(Transform::from_xyz(3.0, 0.5, -2.2)), )); */ - world.spawn(( + /* world.spawn(( ModelComponent(antique_camera_model), TransformComponent::from(Transform::from_xyz(0.0, -5.0, -10.0)), - )); + )); */ /* let light = PointLight { color: Vec3::new(1.0, 1.0, 1.0), @@ -90,18 +90,22 @@ async fn main() { quadratic: 0.032, }; world.spawn((light,)); */ + let mut cube_tran = Transform::from_xyz(-3.5, 0.0, -7.0); + cube_tran.rotate_y(math::Angle::Degrees(180.0)); world.spawn(( - PointLight { + /* PointLight { color: Vec3::new(1.0, 1.0, 1.0), intensity: 1.0, constant: 1.0, linear: 0.045, quadratic: 0.0075, - }, - TransformComponent::from(Transform::from_xyz(-2.5, 0.0, -10.0)), + }, */ + TransformComponent::from(cube_tran), ModelComponent(cube_model.clone()), )); + let mut light_tran = Transform::from_xyz(3.5, 0.0, -7.0); + light_tran.scale = Vec3::new(0.5, 0.5, 0.5); world.spawn(( PointLight { color: Vec3::new(0.361, 0.984, 0.0), @@ -110,7 +114,7 @@ async fn main() { linear: 0.045, quadratic: 0.0075, }, - TransformComponent::from(Transform::from_xyz(2.5, 0.0, -10.0)), + TransformComponent::from(light_tran), ModelComponent(cube_model), )); @@ -154,7 +158,7 @@ async fn main() { let mut sys = BatchedSystem::new(); sys.with_criteria(FixedTimestep::new(45)); - sys.with_system(spin_system); + //sys.with_system(spin_system); sys.with_system(fps_system); game.with_system("fixed", sys, &[]); diff --git a/examples/testbed/testbed-renderdoc.cap b/examples/testbed/testbed-renderdoc.cap new file mode 100644 index 0000000..7b81a15 --- /dev/null +++ b/examples/testbed/testbed-renderdoc.cap @@ -0,0 +1,28 @@ +{ + "rdocCaptureSettings": 1, + "settings": { + "autoStart": true, + "commandLine": "", + "environment": [ + ], + "executable": "/media/data_drive/Development/Rust/lyra-test/engine/target/debug/testbed", + "inject": false, + "numQueuedFrames": 1, + "options": { + "allowFullscreen": true, + "allowVSync": true, + "apiValidation": false, + "captureAllCmdLists": false, + "captureCallstacks": false, + "captureCallstacksOnlyDraws": false, + "debugOutputMute": true, + "delayForDebugger": 0, + "hookIntoChildren": false, + "refAllResources": false, + "softMemoryLimit": 0, + "verifyBufferAccess": false + }, + "queuedFrameCap": 5, + "workingDir": "/media/data_drive/Development/Rust/lyra-test/engine/examples/testbed" + } +} diff --git a/src/render/render_job.rs b/src/render/render_job.rs index 1cdc102..a15db01 100755 --- a/src/render/render_job.rs +++ b/src/render/render_job.rs @@ -2,25 +2,21 @@ use edict::EntityId; use crate::math::Transform; -use super::renderer::CachedTransform; - pub struct RenderJob { pub entity: EntityId, pub shader_id: u64, pub mesh_buffer_id: uuid::Uuid, pub transform: Transform, - pub last_transform: Option, // TODO: render interpolation } impl RenderJob { - pub fn new(entity: EntityId, shader_id: u64, mesh_buffer_id: uuid::Uuid, transform: Transform, last_transform: Option) -> Self { + pub fn new(entity: EntityId, shader_id: u64, mesh_buffer_id: uuid::Uuid, transform: Transform) -> Self { Self { entity, shader_id, mesh_buffer_id, transform, - last_transform, } } } \ No newline at end of file diff --git a/src/render/renderer.rs b/src/render/renderer.rs index bc740e1..b726888 100755 --- a/src/render/renderer.rs +++ b/src/render/renderer.rs @@ -47,9 +47,9 @@ struct MeshBufferStorage { render_texture: Option, texture_bindgroup: 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, + // 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(Clone, Debug)] @@ -78,8 +78,6 @@ pub struct BasicRenderer { entity_last_transforms: HashMap, transform_buffers: TransformBuffers, - transform_bind_group_layout: BindGroupLayout, - //transform_bind_group: wgpu::BindGroup, render_limits: Limits, @@ -187,59 +185,7 @@ impl BasicRenderer { source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(shader_src)), }); - let transform_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: true, - min_binding_size: None, - }, - count: None, - } - ], - 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: 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 { - max_transform_count: (render_limits.max_uniform_buffer_binding_size / mem::size_of::() as u32) as usize, - buffer_bindgroups: vec![( 0, 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 transform_buffers = TransformBuffers::new(&device); let camera_buffer = device.create_buffer_init( &wgpu::util::BufferInitDescriptor { @@ -320,7 +266,6 @@ impl BasicRenderer { render_limits, transform_buffers, - transform_bind_group_layout, inuse_camera: RenderCamera::new(size), camera_buffer, @@ -338,7 +283,7 @@ impl BasicRenderer { let mut pipelines = HashMap::new(); pipelines.insert(0, Arc::new(FullRenderPipeline::new(&s.device, &s.config, &shader, vec![super::vertex::Vertex::desc(),], - vec![&s.texture_bind_group_layout, &s.transform_bind_group_layout, &camera_bind_group_layout, + vec![&s.texture_bind_group_layout, &s.transform_buffers.bindgroup_layout, &camera_bind_group_layout, &s.light_buffers.bindgroup_layout]))); s.render_pipelines = pipelines; @@ -465,58 +410,20 @@ impl BasicRenderer { buffer_indices, render_texture: None, texture_bindgroup: diffuse_bindgroup, - transform_index: transform_indices } } - fn expand_transform_buffers(&mut self) -> TransformBufferIndices { - let buffers = &mut self.transform_buffers; - - let transform_buffer = self.device.create_buffer( - &wgpu::BufferDescriptor { - label: Some(&format!("Transform Buffer {}", buffers.buffer_bindgroups.len())), - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - size: self.render_limits.max_uniform_buffer_binding_size as u64, - mapped_at_creation: false, - } - ); - - let stride = self.render_limits.min_uniform_buffer_offset_alignment as usize + mem::size_of::(); - let transform_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &self.transform_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::Buffer( - wgpu::BufferBinding { - buffer: &transform_buffer, - offset: 0, - size: Some(NonZeroU64::new(stride as u64).unwrap()) - } - ) - } - ], - label: Some("transform_bind_group"), - }); - - let indices = TransformBufferIndices { buffer_index: buffers.buffer_bindgroups.len(), transform_index: 0 }; - buffers.buffer_bindgroups.push(( 0, transform_buffer, transform_bind_group )); - buffers.next_indices = indices; - indices - } - /// Processes the mesh for the renderer, storing and creating buffers as needed. Returns true if a new mesh was processed. fn process_mesh(&mut self, entity: EntityId, transform: Transform, mesh: &Mesh) -> bool { + if self.transform_buffers.should_expand() { + self.transform_buffers.expand_buffers(&self.device); + } + let indices = self.transform_buffers.update_or_insert(&self.queue, &self.render_limits, - entity, || transform.calculate_mat4()); + entity, || ( transform.calculate_mat4(), glam::Mat3::from_quat(transform.rotation) )); #[allow(clippy::map_entry)] if !self.mesh_buffers.contains_key(&mesh.uuid) { - // check if the transform buffers need to be expanded - if self.transform_buffers.should_expand() { - self.expand_transform_buffers(); - } - // create the mesh's buffers let buffers = self.create_mesh_buffers(mesh, indices); self.mesh_buffers.insert(mesh.uuid, buffers); @@ -530,7 +437,6 @@ impl BasicRenderer { impl Renderer for BasicRenderer { fn prepare(&mut self, main_world: &mut edict::World) { let last_epoch = main_world.epoch(); - debug!("Last epoch: {last_epoch:?}"); let mut alive_entities = HashSet::new(); @@ -560,6 +466,7 @@ impl Renderer for BasicRenderer { cached } }; + //debug!("Transform: {:?}, comp: {:?}", cached.to_transform.translation, transform.transform.translation); let fixed_time = match cached.last_updated_at { Some(last_updated_at) => cached.cached_at - last_updated_at, @@ -577,7 +484,7 @@ impl Renderer for BasicRenderer { } let shader = mesh.material().shader_uuid.unwrap_or(0); - let job = RenderJob::new(entity, shader, mesh.uuid, transform_val, None); + let job = RenderJob::new(entity, shader, mesh.uuid, transform_val); self.render_jobs.push_back(job); } } @@ -654,7 +561,7 @@ impl Renderer for BasicRenderer { // get the mesh (containing vertices) and the buffers from storage let buffers = self.mesh_buffers.get(&job.mesh_buffer_id).unwrap(); - + // Bind the optional texture if let Some(tex) = buffers.texture_bindgroup.as_ref() { render_pass.set_bind_group(0, tex, &[]); @@ -663,10 +570,11 @@ impl Renderer for BasicRenderer { } // 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, ]); + let transform_indices = *self.transform_buffers.entity_indices(job.entity).unwrap(); + let bindgroup = self.transform_buffers.bind_group(transform_indices).unwrap(); + //let bindgroup = self.transform_buffers.entity_bind_group(job.entity).unwrap(); + let offset = TransformBuffers::index_offset(&self.render_limits, transform_indices) as u32; + render_pass.set_bind_group(1, bindgroup, &[ offset, offset, ]); // Bind camera render_pass.set_bind_group(2, &self.camera_bind_group, &[]); diff --git a/src/render/shaders/base.wgsl b/src/render/shaders/base.wgsl index e97c715..b9d85c5 100755 --- a/src/render/shaders/base.wgsl +++ b/src/render/shaders/base.wgsl @@ -36,6 +36,8 @@ struct Lights { @group(1) @binding(0) var u_model_transform: mat4x4; +@group(1) @binding(1) +var u_model_normal_matrix: mat3x3; @group(2) @binding(0) var u_camera: CameraUniform; @@ -52,9 +54,8 @@ fn vs_main( out.tex_coords = model.tex_coords; out.clip_position = u_camera.view_proj * u_model_transform * vec4(model.position, 1.0); - out.world_normal = (u_model_transform * vec4(model.normal, 0.0)).xyz; + out.world_normal = u_model_normal_matrix * model.normal; - //out.world_normal = model.normal; var world_position: vec4 = u_model_transform * vec4(model.position, 1.0); out.world_position = world_position.xyz; diff --git a/src/render/transform_buffer_storage.rs b/src/render/transform_buffer_storage.rs index 1156bfd..ded1c2d 100644 --- a/src/render/transform_buffer_storage.rs +++ b/src/render/transform_buffer_storage.rs @@ -1,75 +1,129 @@ -use std::collections::{VecDeque, HashMap}; +use std::{collections::{VecDeque, HashMap}, num::NonZeroU64}; use edict::EntityId; use wgpu::Limits; -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +use std::mem; + +#[derive(Default, Copy, Clone, PartialEq, Eq, Debug)] pub(crate) struct TransformBufferIndices { pub buffer_index: usize, pub transform_index: usize, } +/// A struct representing a single transform buffer. There can be multiple of these +pub(crate) struct TransformBufferEntry { + pub len: usize, + pub transform_buf: wgpu::Buffer, + pub normal_mat_buf: wgpu::Buffer, + pub bindgroup: wgpu::BindGroup, +} + pub(crate) struct TransformBuffers { - //transform_layout: wgpu::BindGroupLayout, + pub bindgroup_layout: wgpu::BindGroupLayout, /// A vector storing the EntityId and pub just_updated: HashMap, pub not_updated: HashMap, pub dead_indices: VecDeque, pub next_indices: TransformBufferIndices, /// (transform count, buffer, bindgroup) - pub buffer_bindgroups: Vec<(usize, wgpu::Buffer, wgpu::BindGroup)>, + pub buffer_bindgroups: Vec, /// The max amount of transforms in a buffer pub max_transform_count: usize, } impl TransformBuffers { + pub fn new(device: &wgpu::Device) -> Self { + let limits = device.limits(); + + let bindgroup_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: true, + min_binding_size: None, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::VERTEX, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: true, + min_binding_size: None, + }, + count: None, + } + ], + label: Some("transform_bind_group_layout"), + }); + + let mut s = Self { + max_transform_count: limits.max_uniform_buffer_binding_size as usize / (mem::size_of::() + mem::size_of::()), + buffer_bindgroups: Vec::new(), + bindgroup_layout, + just_updated: HashMap::new(), + not_updated: HashMap::new(), + dead_indices: VecDeque::new(), + next_indices: TransformBufferIndices::default(), + }; + + // create the first uniform buffer + s.expand_buffers(device); + + s + } + /// Update an entity's buffer with the new transform. Will panic if the entity isn't stored - pub fn update_entity(&mut self, queue: &wgpu::Queue, limits: &Limits, entity: EntityId, transform: glam::Mat4) -> TransformBufferIndices { + pub fn update_entity(&mut self, queue: &wgpu::Queue, limits: &Limits, entity: EntityId, transform: glam::Mat4, normal_matrix: glam::Mat3) -> TransformBufferIndices { let indices = self.not_updated.remove(&entity) .or_else(|| self.just_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)); + let buffer = self.buffer_bindgroups.get(indices.buffer_index).unwrap(); + let offset = Self::index_offset(limits, indices); + queue.write_buffer(&buffer.transform_buf, offset, bytemuck::bytes_of(&transform)); + queue.write_buffer(&buffer.normal_mat_buf, offset, bytemuck::bytes_of(&normal_matrix)); indices } /// Insert a new entity into the buffer, returns where it was stored. - pub 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, buffer) = if let Some(index) = self.dead_indices.pop_front() { - let (_, buffer, _) = self.buffer_bindgroups.get(index.buffer_index).unwrap(); - (index, buffer) - } else { - let indices = &mut self.next_indices; - let this_idx = *indices; - let (count, buffer, _) = self.buffer_bindgroups.get_mut(indices.buffer_index).unwrap(); + pub fn insert_entity(&mut self, queue: &wgpu::Queue, limits: &Limits, entity: EntityId, transform: glam::Mat4, normal_matrix: glam::Mat3) -> TransformBufferIndices { + let indices = match self.dead_indices.pop_front() { + Some(indices) => indices, + None => { + let indices = &mut self.next_indices; + let this_idx = *indices; + let entry = self.buffer_bindgroups.get_mut(indices.buffer_index).unwrap(); - if *count >= self.max_transform_count { - panic!("Transform buffer is filled and 'next_indices' was not incremented! Was a new buffer created?"); + if entry.len >= self.max_transform_count { + panic!("Transform buffer is filled and 'next_indices' was not incremented! Was a new buffer created?"); + } + + entry.len += 1; + indices.transform_index += 1; + this_idx } - - *count += 1; - indices.transform_index += 1; - - (this_idx, &*buffer) }; - - queue.write_buffer(buffer, Self::get_offset_for(limits, indices), bytemuck::bytes_of(&transform)); self.just_updated.insert(entity, indices); - indices + self.update_entity(queue, limits, entity, transform, normal_matrix) } /// Update or insert an entities transform pub fn update_or_insert(&mut self, queue: &wgpu::Queue, limits: &Limits, entity: EntityId, transform_fn: TFn) -> TransformBufferIndices - where TFn: Fn() -> glam::Mat4 - { + where TFn: Fn() -> (glam::Mat4, glam::Mat3) + { + let (tran, norm) = transform_fn(); if self.contains(entity) { - self.update_entity(queue, limits, entity, transform_fn()) + self.update_entity(queue, limits, entity, tran, norm) } else { - self.insert_entity(queue, limits, entity, transform_fn()) + self.insert_entity(queue, limits, entity, tran, norm) } } @@ -88,16 +142,97 @@ impl TransformBuffers { self.just_updated.clear(); } - pub fn get_offset_for(limits: &Limits, indices: TransformBufferIndices) -> u64 { + /// Returns the offset for the transform index in the buffer + pub fn index_offset(limits: &Limits, indices: TransformBufferIndices) -> u64 { indices.transform_index as u64 * limits.min_uniform_buffer_offset_alignment as u64 } /// Returns whether or not the transform buffers should be expanded pub fn should_expand(&self) -> bool { - if let Some(( count, _, _ )) = self.buffer_bindgroups.last() { - *count >= self.max_transform_count + if let Some(entry) = self.buffer_bindgroups.last() { + entry.len >= self.max_transform_count } else { true } } + + /// Returns the bind group for the index + pub fn bind_group(&self, index: TransformBufferIndices) -> Option<&wgpu::BindGroup> { + self.buffer_bindgroups.get(index.buffer_index) + .map(|entry| &entry.bindgroup) + } + + pub fn entity_bind_group(&self, entity: EntityId) -> Option<&wgpu::BindGroup> { + self.entity_indices(entity).and_then(|i| self.bind_group(*i)) + } + + /// Expand the Transform buffers by adding another uniform buffer binding + pub fn expand_buffers(&mut self, device: &wgpu::Device) { + let limits = device.limits(); + let max_buffer_sizes = (limits.max_uniform_buffer_binding_size as u64) / 2; + + let transform_buffer = device.create_buffer( + &wgpu::BufferDescriptor { + label: Some(&format!("Transform Buffer {}", self.buffer_bindgroups.len())), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + size: max_buffer_sizes, + mapped_at_creation: false, + } + ); + + let normal_mat_buffer = device.create_buffer( + &wgpu::BufferDescriptor { + label: Some(&format!("Normal Matrix Buffer {}", self.buffer_bindgroups.len())), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + size: max_buffer_sizes, + mapped_at_creation: false, + } + ); + + /* let stride = limits.min_uniform_buffer_offset_alignment as u64 + + mem::size_of::() as u64 + mem::size_of::() as u64; */ + let tran_stride = limits.min_uniform_buffer_offset_alignment as u64 + + mem::size_of::() as u64; + let norm_stride = limits.min_uniform_buffer_offset_alignment as u64 + + mem::size_of::() as u64; + + let transform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &self.bindgroup_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer( + wgpu::BufferBinding { + buffer: &transform_buffer, + offset: 0, + size: Some(NonZeroU64::new(tran_stride).unwrap()) + } + ) + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Buffer( + wgpu::BufferBinding { + buffer: &normal_mat_buffer, + offset: 0, + size: Some(NonZeroU64::new(norm_stride).unwrap()) + } + ) + } + ], + label: Some("transform_bind_group"), + }); + + let entry = TransformBufferEntry { + bindgroup: transform_bind_group, + transform_buf: transform_buffer, + normal_mat_buf: normal_mat_buffer, + len: 0, + }; + self.buffer_bindgroups.push(entry); + } + + pub fn entity_indices(&self, entity: EntityId) -> Option<&TransformBufferIndices> { + self.just_updated.get(&entity).or_else(|| self.not_updated.get(&entity)) + } } \ No newline at end of file