diff --git a/Cargo.lock b/Cargo.lock index a31c01c..f421cbe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -227,7 +227,7 @@ dependencies = [ [[package]] name = "blue_engine" -version = "0.4.29" +version = "0.4.30" dependencies = [ "android_logger", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index 6f17147..31d7016 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "blue_engine" -version = "0.4.29" +version = "0.4.30" authors = ["Elham Aryanpur "] edition = "2021" description = "General-Purpose, Easy-to-use, Fast, and Portable graphics engine" @@ -63,6 +63,10 @@ path = "examples/camera/rotate_around.rs" name = "resource_sharing" path = "examples/utils/resource_sharing.rs" +[[example]] +name = "instancing" +path = "examples/utils/instancing.rs" + # Development ONLY [[example]] name = "dev" diff --git a/examples/dev/dev.rs b/examples/dev/dev.rs index 1a39d91..f1ba9e7 100644 --- a/examples/dev/dev.rs +++ b/examples/dev/dev.rs @@ -2,13 +2,16 @@ use blue_engine::{ primitive_shapes::{cube, square, triangle, uv_sphere}, uniform_type::Matrix, utils::default_resources::DEFAULT_MATRIX_4, - Engine, ObjectSettings, PolygonMode, PowerPreference, RotateAxis, ShaderSettings, TextureData, - Vertex, WindowDescriptor, + Engine, Instance, ObjectSettings, PolygonMode, PowerPreference, RotateAxis, ShaderSettings, + TextureData, Vertex, WindowDescriptor, }; fn main() { let mut engine = Engine::new().expect("win"); + //let test_instance = Instance::default(); + //println!("{:?}", test_instance.to_raw()); + let texture = engine .renderer .build_texture( @@ -17,8 +20,16 @@ fn main() { blue_engine::TextureMode::Clamp, ) .unwrap(); + let texture2 = engine + .renderer + .build_texture( + "background", + TextureData::Path("resources/SimpleJacob.png"), + blue_engine::TextureMode::Clamp, + ) + .unwrap(); - let first = square( + square( "main", ObjectSettings::default(), &mut engine.renderer, @@ -32,159 +43,70 @@ fn main() { .unwrap() .set_position(-1f32, 0f32, 0f32); - let second = square( + square( "alt", ObjectSettings::default(), &mut engine.renderer, &mut engine.objects, ); - + engine.objects.get_mut("alt").unwrap().set_texture(texture2); engine .objects .get_mut("alt") .unwrap() - .reference_texture("main"); - engine - .objects - .get_mut("alt") - .unwrap() - .set_position(1f32, 0f32, 0f32); - - // =============================== - /* - //let triangle_id = triangle(Some("Triangleee"), &mut engine, camera).unwrap(); - let window_size = engine.window.inner_size(); - - uv_sphere( - "cube", - (18, 36, 1f32), - &mut engine.renderer, - &mut engine.objects, - ) - .unwrap(); - // engine.objects.get_mut("cube").unwrap().scale(0.6, 0.6, 0.6); - engine - .objects - .get_mut("cube") - .unwrap() - .set_color(1f32, 0f32, 0f32, 1f32); - //cube.scale(0.3, 0.3, 0.3); + .set_position(0.2f32, 0f32, 0.001f32); - engine - .objects - .get_mut("monke") - .unwrap() - .set_color(0.051f32, 0.533f32, 0.898f32, 1f32); - //engine.objects[test].rotate(90f32, RotateAxis::Y); - - /*let sphere_1 = uv_sphere(Some("SPHERE1"), &mut engine, (18, 36, 1f32)).unwrap(); - engine.objects[sphere_1].scale(2f32, 2f32, 2f32); - engine.objects[sphere_1].set_color(0.051f32, 0.533f32, 0.898f32, 1f32); - - let sphere_1 = uv_sphere(Some("SPHERE1"), &mut engine, (18, 36, 1f32)).unwrap(); - engine.objects[sphere_1].position(2f32, 1f32, 0f32); - engine.objects[sphere_1].set_color(1.0f32, 0.5f32, 0.31f32, 1f32); - let sphere_2 = uv_sphere(Some("SPHERE2"), &mut engine, (18, 36, 1f32)).unwrap(); - engine.objects[sphere_2].position(-2f32, 1f32, 0f32); - engine.objects[sphere_2].set_color(1.0f32, 0.5f32, 0.31f32, 1f32); - let sphere_3 = uv_sphere(Some("SPHERE3"), &mut engine, (18, 36, 1f32)).unwrap(); - engine.objects[sphere_3].position(2f32, -1f32, 0f32); - engine.objects[sphere_3].set_color(1.0f32, 0.5f32, 0.31f32, 1f32); - let sphere_4 = uv_sphere(Some("SPHERE4"), &mut engine, (18, 36, 1f32)).unwrap(); - engine.objects[sphere_4].position(-2f32, -1f32, 0f32); - engine.objects[sphere_4].set_color(1.0f32, 0.5f32, 0.31f32, 1f32); */ - - //let window_size = engine.window.inner_size(); - /*let change_texture = engine - .renderer - .build_and_append_texture( - "name", - TextureData::Bytes(include_bytes!("resource/BlueLogoDiscord.png").to_vec()), - blue_engine::header::TextureMode::Clamp, - //blue_engine::header::TextureFormat::PNG, - ) - .unwrap();*/ - - //let square = engine.get_object(square_id).unwrap(); - - //square.change_color(0.0, 0.0, 1.0, 0.7).unwrap(); - //square.change_texture(change_texture); - //square.resize(100.0, 100.0, 0.0, window_size); - - //let square = engine.objects.get_mut(square_id).unwrap(); - - //square.no_stretch_update(&mut engine.renderer, engine.window.inner_size()).unwrap(); - //font.draw("Hello_World", (-100, 50), &mut engine).unwrap(); - - let radius = 10f32; - let start = std::time::SystemTime::now(); - let mut rotation = 0f32; - let speed = -0.05; - - let mut has_border = false; - let mut val = 0f32; - */ + let speed = -0.05; engine .update_loop(move |renderer, _window, objects, input, camera, plugins| { - /*let o = - renderer.build_object("haha", Vec::new(), Vec::new(), ObjectSettings::default()); - - let camx = start.elapsed().unwrap().as_secs_f32().sin() * radius; - let camy = start.elapsed().unwrap().as_secs_f32().sin() * radius; - let camz = start.elapsed().unwrap().as_secs_f32().cos() * radius; - - objects.get_mut("cube").unwrap().position(camx, camy, camz); - - //cube.translate(1f32, 1f32, 1f32); - - let sprite = objects.get_mut("cube").unwrap(); + let sprite = objects.get_mut("alt").unwrap(); if input.key_held(blue_engine::VirtualKeyCode::Up) { - sprite.position( - sprite.position.0, - sprite.position.1 + speed, - sprite.position.2, + sprite.set_position( + sprite.position.x, + sprite.position.y - speed, + sprite.position.z, ); //lm.ambient_color.data = [1f32, 1f32, 1f32, 1f32]; } if input.key_held(blue_engine::VirtualKeyCode::Down) { - sprite.position( - sprite.position.0, - sprite.position.1 - speed, - sprite.position.2, + sprite.set_position( + sprite.position.x, + sprite.position.y + speed, + sprite.position.z, ); //lm.ambient_color.data = [0.1f32, 0.1f32, 0.1f32, 1f32]; } if input.key_held(blue_engine::VirtualKeyCode::Left) { - sprite.position( - sprite.position.0 - speed, - sprite.position.1, - sprite.position.2, + sprite.set_position( + sprite.position.x + speed, + sprite.position.y, + sprite.position.z, ); } if input.key_held(blue_engine::VirtualKeyCode::Right) { - sprite.position( - sprite.position.0 + speed, - sprite.position.1, - sprite.position.2, + sprite.set_position( + sprite.position.x - speed, + sprite.position.y, + sprite.position.z, ); } if input.key_held(blue_engine::VirtualKeyCode::E) { - sprite.position( - sprite.position.0, - sprite.position.1, - sprite.position.2 - speed, + sprite.set_position( + sprite.position.x, + sprite.position.y, + sprite.position.z + speed, ); } if input.key_held(blue_engine::VirtualKeyCode::Q) { - sprite.position( - sprite.position.0, - sprite.position.1, - sprite.position.2 + speed, + sprite.set_position( + sprite.position.x, + sprite.position.y, + sprite.position.z - speed, ); - } */ + } }) .expect("Error during update loop"); } diff --git a/examples/utils/instancing.rs b/examples/utils/instancing.rs new file mode 100644 index 0000000..4800b15 --- /dev/null +++ b/examples/utils/instancing.rs @@ -0,0 +1,63 @@ +/* + * Blue Engine by Elham Aryanpur + * + * Triangle example using pre-defined shapes + * + * The license is same as the one on the root. +*/ + +use blue_engine::{ + header::{Engine, ObjectSettings}, + primitive_shapes::triangle, + Instance, +}; + +pub fn main() { + // start the engine + let mut engine = Engine::new().expect("window not created"); + + // create a triangle + triangle( + "Triangle", + ObjectSettings::default(), + &mut engine.renderer, + &mut engine.objects, + ) + .unwrap(); + + // update the triangle + engine.objects.update_object("Triangle", |object| { + // set the position of the main triangle + object.set_position(0f32, 0f32, -3f32); + + // a function to make instance creation easier + let create_instance = |x: f32, y: f32, z: f32| { + Instance::new( + [x, y, z].into(), + [0f32, 0f32, 0f32].into(), + [1f32, 1f32, 1f32].into(), + ) + }; + + // add an instance + object.add_instance(create_instance(2f32, 1f32, -2f32)); + object.add_instance(create_instance(2f32, -1f32, -2f32)); + object.add_instance(create_instance(-2f32, 1f32, -2f32)); + object.add_instance(create_instance(-2f32, -1f32, -2f32)); + }); + + // we manually update the instance buffer before the next frame starts + // this is due to the object updates happening after every frame, hence + // for the first frame, we need to update it ourselves. + engine + .objects + .get_mut("Triangle") + .expect("Couldn't get the triangle") + .update_instance_buffer(&mut engine.renderer) + .expect("Couldn't update instance buffer"); + + // run the loop as normal + engine + .update_loop(move |_, _, _, _, _, _| {}) + .expect("Error during update loop"); +} diff --git a/resources/SimpleJacob.png b/resources/SimpleJacob.png new file mode 100644 index 0000000..d217092 Binary files /dev/null and b/resources/SimpleJacob.png differ diff --git a/src/definition.rs b/src/definition.rs index f354f7e..b66fd7a 100644 --- a/src/definition.rs +++ b/src/definition.rs @@ -7,9 +7,12 @@ use image::GenericImageView; use wgpu::{util::DeviceExt, BindGroupLayout, Sampler, Texture, TextureView}; -use crate::header::{ - Pipeline, PipelineData, ShaderSettings, Shaders, StringBuffer, TextureData, TextureMode, - Textures, UniformBuffers, Vertex, VertexBuffers, +use crate::{ + header::{ + Pipeline, PipelineData, ShaderSettings, Shaders, StringBuffer, TextureData, TextureMode, + Textures, UniformBuffers, Vertex, VertexBuffers, + }, + InstanceRaw, }; impl crate::header::Renderer { @@ -68,7 +71,7 @@ impl crate::header::Renderer { vertex: wgpu::VertexState { module: &shader, entry_point: "vs_main", - buffers: &[Vertex::desc()], + buffers: &[Vertex::desc(), InstanceRaw::desc()], }, fragment: Some(wgpu::FragmentState { module: &shader, @@ -326,4 +329,13 @@ impl crate::header::Renderer { length: indicies.len() as u32, }) } + + pub fn build_instance(&self, instance_data: Vec) -> wgpu::Buffer { + self.device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Instance Buffer"), + contents: bytemuck::cast_slice(&instance_data), + usage: wgpu::BufferUsages::VERTEX, + }) + } } diff --git a/src/header.rs b/src/header.rs index 6d5c116..0c64b07 100644 --- a/src/header.rs +++ b/src/header.rs @@ -83,6 +83,10 @@ pub struct Object { pub uniform_layout: wgpu::BindGroupLayout, /// Pipeline holds all the data that is sent to GPU, including shaders and textures pub pipeline: Pipeline, + /// List of instances of this object + pub instances: Vec, + /// instance buffer + pub instance_buffer: wgpu::Buffer, /// Dictates the size of your object in pixels pub size: glm::Vec3, pub scale: glm::Vec3, @@ -94,7 +98,11 @@ pub struct Object { /// Transformation matricies helps to apply changes to your object, including position, orientation, ... /// Best choice is to let the Object system handle it pub position_matrix: nalgebra_glm::Mat4, + /// Transformation matricies helps to apply changes to your object, including position, orientation, ... + /// Best choice is to let the Object system handle it pub scale_matrix: nalgebra_glm::Mat4, + /// Transformation matricies helps to apply changes to your object, including position, orientation, ... + /// Best choice is to let the Object system handle it pub rotation_matrix: nalgebra_glm::Mat4, /// Transformation matrix, but inversed pub inverse_transformation_matrix: crate::uniform_type::Matrix, @@ -369,7 +377,7 @@ unsafe impl Sync for ShaderSettings {} #[repr(C)] #[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] pub struct InstanceRaw { - pub model: uniform_type::Matrix, + pub model: [[f32; 4]; 4], } /// Instance buffer data storage diff --git a/src/objects.rs b/src/objects.rs index 84b3450..f9ce464 100644 --- a/src/objects.rs +++ b/src/objects.rs @@ -46,6 +46,14 @@ impl Renderer { //crate::header::TextureFormat::PNG )?; + let instance = Instance::new( + [0f32, 0f32, 0f32].into(), + [0f32, 0f32, 0f32].into(), + [1f32, 1f32, 1f32].into(), + ); + + let instance_buffer = self.build_instance(vec![instance.to_raw()]); + Ok(Object { name: name.as_string(), vertices: verticies, @@ -56,6 +64,8 @@ impl Renderer { texture: PipelineData::Data(texture), uniform: PipelineData::Data(Some(uniform.0)), }, + instances: vec![instance], + instance_buffer, uniform_layout: uniform.1, size: glm::vec3(100f32, 100f32, 100f32), scale: glm::vec3(1f32, 1f32, 1f32), @@ -328,6 +338,7 @@ impl Object { self.update_vertex_buffer(renderer)?; self.update_uniform_buffer(renderer)?; self.update_shader(renderer)?; + self.update_instance_buffer(renderer)?; self.changed = false; Ok(()) } @@ -436,6 +447,33 @@ impl Object { Ok(updated_buffer2.0) } + pub fn update_instance_buffer(&mut self, renderer: &mut Renderer) -> anyhow::Result<()> { + let instance_data = self + .instances + .iter() + .map(Instance::to_raw) + .collect::>(); + let instance_buffer = renderer.build_instance(instance_data); + self.instance_buffer = instance_buffer; + Ok(()) + } + + pub fn update_instance_buffer_and_return( + &mut self, + renderer: &mut Renderer, + ) -> anyhow::Result { + let instance_data = self + .instances + .iter() + .map(Instance::to_raw) + .collect::>(); + let instance_buffer = renderer.build_instance(instance_data.clone()); + let instance_buffer2 = renderer.build_instance(instance_data); + + self.instance_buffer = instance_buffer; + Ok(instance_buffer2) + } + // ============================= FOR COPY OF PIPELINES ============================= /// References another object's vertices pub fn reference_vertices(&mut self, object_id: impl StringBuffer) { @@ -456,6 +494,12 @@ impl Object { pub fn reference_uniform_buffer(&mut self, object_id: impl StringBuffer) { self.pipeline.uniform = PipelineData::Copy(object_id.as_string()); } + + // ============================= Instances ============================= + pub fn add_instance(&mut self, instance: Instance) { + self.instances.push(instance); + self.changed = true; + } } #[derive(Debug)] @@ -507,6 +551,13 @@ struct VertexOutput { @location(0) texture_coordinates: vec2, }; +struct InstanceInput { + @location(3) model_matrix_0: vec4, + @location(4) model_matrix_1: vec4, + @location(5) model_matrix_2: vec4, + @location(6) model_matrix_3: vec4, +}; + @group(0) @binding(0) var texture_diffuse: texture_2d; @@ -517,13 +568,20 @@ var sampler_diffuse: sampler;"# // step 4 vertex stage according to data before "\n// ===== VERTEX STAGE ===== //\n{}\n{}\n{}", r#"@vertex -fn vs_main(input: VertexInput) -> VertexOutput { +fn vs_main(input: VertexInput, instance: InstanceInput,) -> VertexOutput { + let model_matrix = mat4x4( + instance.model_matrix_0, + instance.model_matrix_1, + instance.model_matrix_2, + instance.model_matrix_3, + ); + var out: VertexOutput; out.texture_coordinates = input.texture_coordinates;"#, if camera_effect { - "out.position = camera_uniform.camera_matrix * (transform_uniform.transform_matrix * vec4(input.position, 1.0));" + "out.position = camera_uniform.camera_matrix * model_matrix * (transform_uniform.transform_matrix * vec4(input.position, 1.0));" } else { - "out.position = transform_uniform.transform_matrix * vec4(input.position, 1.0);" + "out.position = model_matrix * (transform_uniform.transform_matrix * vec4(input.position, 1.0));" }, r#"return out; } @@ -551,11 +609,11 @@ impl Instance { /// Gathers all information and builds a Raw Instance to be sent to GPU pub fn to_raw(&self) -> InstanceRaw { - let position_matrix = glm::Mat4::from_vec(self.position.as_slice().to_vec()); - let rotation_matrix = glm::Mat4::from_vec(self.rotation.as_slice().to_vec()); - let scale_matrix = glm::Mat4::from_vec(self.scale.as_slice().to_vec()); + let position_matrix = glm::translate(&DEFAULT_MATRIX_4.to_im(), &self.position); + let rotation_matrix = nalgebra_glm::rotate(&DEFAULT_MATRIX_4.to_im(), 0f32, &self.rotation); + let scale_matrix = glm::scale(&DEFAULT_MATRIX_4.to_im(), &self.scale); InstanceRaw { - model: Matrix::from_im(position_matrix * rotation_matrix * scale_matrix), + model: (position_matrix * rotation_matrix * scale_matrix).into(), } } @@ -584,3 +642,40 @@ impl Default for Instance { } } } + +impl InstanceRaw { + pub fn desc() -> wgpu::VertexBufferLayout<'static> { + use std::mem; + wgpu::VertexBufferLayout { + array_stride: mem::size_of::() as wgpu::BufferAddress, + // We need to switch from using a step mode of Vertex to Instance + // This means that our shaders will only change to use the next + // instance when the shader starts processing a new instance + step_mode: wgpu::VertexStepMode::Instance, + attributes: &[ + // A mat4 takes up 4 vertex slots as it is technically 4 vec4s. We need to define a slot + // for each vec4. We'll have to reassemble the mat4 in the shader. + wgpu::VertexAttribute { + offset: 0, + shader_location: 3, + format: wgpu::VertexFormat::Float32x4, + }, + wgpu::VertexAttribute { + offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress, + shader_location: 4, + format: wgpu::VertexFormat::Float32x4, + }, + wgpu::VertexAttribute { + offset: mem::size_of::<[f32; 8]>() as wgpu::BufferAddress, + shader_location: 5, + format: wgpu::VertexFormat::Float32x4, + }, + wgpu::VertexAttribute { + offset: mem::size_of::<[f32; 12]>() as wgpu::BufferAddress, + shader_location: 6, + format: wgpu::VertexFormat::Float32x4, + }, + ], + } + } +} diff --git a/src/render.rs b/src/render.rs index c73eeaa..77f9651 100644 --- a/src/render.rs +++ b/src/render.rs @@ -283,6 +283,7 @@ impl Renderer { if vertex_buffer.is_some() { let vertex_buffer = vertex_buffer.unwrap(); render_pass.set_vertex_buffer(0, vertex_buffer.vertex_buffer.slice(..)); + render_pass.set_vertex_buffer(1, i.instance_buffer.slice(..)); render_pass.set_index_buffer( vertex_buffer.index_buffer.slice(..), wgpu::IndexFormat::Uint16, @@ -303,7 +304,7 @@ impl Renderer { render_pass.set_bind_group(2, uniform.as_ref().unwrap(), &[]); } } - render_pass.draw_indexed(0..vertex_buffer.length, 0, 0..1); + render_pass.draw_indexed(0..vertex_buffer.length, 0, 0..i.instances.len() as _); } } } diff --git a/src/utils/default_resources.rs b/src/utils/default_resources.rs index 9948fa4..1af6fa9 100644 --- a/src/utils/default_resources.rs +++ b/src/utils/default_resources.rs @@ -31,11 +31,25 @@ struct VertexOutput { @location(0) texture_coordinates: vec2, }; +struct InstanceInput { + @location(3) model_matrix_0: vec4, + @location(4) model_matrix_1: vec4, + @location(5) model_matrix_2: vec4, + @location(6) model_matrix_3: vec4, +}; + @vertex -fn vs_main(input: VertexInput) -> VertexOutput { +fn vs_main(input: VertexInput, instance: InstanceInput) -> VertexOutput { + let model_matrix = mat4x4( + instance.model_matrix_0, + instance.model_matrix_1, + instance.model_matrix_2, + instance.model_matrix_3, + ); + var out: VertexOutput; - out.position = camera_uniform.camera_matrix * (transform_uniform.transform_matrix * vec4(input.position, 1.0)); out.texture_coordinates = input.texture_coordinates; + out.position = camera_uniform.camera_matrix * model_matrix * (transform_uniform.transform_matrix * vec4(input.position, 1.0)); return out; }