Fix rendering multiple entities

this is done by using a large dynamic uniform buffer for storing all transforms of entities
This commit is contained in:
SeanOMik 2023-09-04 11:37:56 -04:00
parent 3068710ba4
commit ec960b8f94
Signed by: SeanOMik
GPG Key ID: 568F326C7EB33ACB
4 changed files with 157 additions and 36 deletions

7
Cargo.lock generated
View File

@ -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",

View File

@ -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"

View File

@ -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(())
};

View File

@ -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<RenderTexture>,
texture_bindgroup: Option<BindGroup>,
texture_layout: Option<BindGroupLayout>
texture_layout: Option<BindGroupLayout>,
/// 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<EntityId, TransformBufferIndices>,
not_updated: HashMap<EntityId, TransformBufferIndices>,
dead_indices: VecDeque<TransformBufferIndices>,
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<TransformBufferIndices> = 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<EntityId, RenderBufferStorage>, // 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::<glam::Mat4>();
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