From 7bfafc22bc19ac8408470e1d3493316207c7d0e8 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 27 Dec 2019 15:35:07 -0600 Subject: [PATCH] initial instancing support --- Cargo.toml | 2 +- examples/simple.rs | 78 +++-- src/application.rs | 11 +- src/render/forward/mod.rs | 4 +- .../forward_instanced/forward_instanced.frag | 40 +++ .../forward_instanced/forward_instanced.vert | 26 ++ src/render/forward_instanced/mod.rs | 267 ++++++++++++++++++ src/render/material.rs | 9 + src/render/mod.rs | 4 +- src/render/shadow/mod.rs | 4 +- 10 files changed, 414 insertions(+), 31 deletions(-) create mode 100644 src/render/forward_instanced/forward_instanced.frag create mode 100644 src/render/forward_instanced/forward_instanced.vert create mode 100644 src/render/forward_instanced/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 42b1c59916..61888022f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" legion = { git = "https://github.com/TomGillen/legion.git", rev = "8628b227bcbe57582fffb5e80e73c634ec4eebd9" } legion_transform = { path = "src/transform" } wgpu = { git = "https://github.com/gfx-rs/wgpu-rs.git", rev = "44fa1bc2fa208fa92f80944253e0da56cb7ac1fe"} -glam = "0.8.3" +glam = "0.8.4" winit = "0.20.0-alpha4" glsl-to-spirv = "0.1" zerocopy = "0.2" diff --git a/examples/simple.rs b/examples/simple.rs index 3beaaa5c07..8a6a1c3f22 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -179,19 +179,19 @@ fn main() { scheduler.add_system(ApplicationStage::Update, build_wander_system()); scheduler.add_system(ApplicationStage::Update, build_navigate_system()); scheduler.add_system(ApplicationStage::Update, build_move_system()); - scheduler.add_system(ApplicationStage::Update, build_light_rotator_system()); - scheduler.add_system(ApplicationStage::Update, build_spawner_system(&mut world)); + // scheduler.add_system(ApplicationStage::Update, build_light_rotator_system()); + // scheduler.add_system(ApplicationStage::Update, build_spawner_system(&mut world)); scheduler.add_system(ApplicationStage::Update, build_print_status_system()); - world.insert((), vec![ - // plane - ( - Material::new(math::vec4(0.1, 0.2, 0.1, 1.0)), - plane_handle.clone(), - LocalToWorld::identity(), - Translation::new(0.0, 0.0, 0.0) - ), - ]); + // world.insert((), vec![ + // // plane + // ( + // Material::new(math::vec4(0.1, 0.2, 0.1, 1.0)), + // plane_handle.clone(), + // LocalToWorld::identity(), + // Translation::new(0.0, 0.0, 0.0) + // ), + // ]); let x = *world.insert((), vec![ // lights @@ -232,17 +232,17 @@ fn main() { // ), ]).first().unwrap(); - world.insert((), vec![ - ( - Material::new(math::vec4(1.0, 1.0, 1.0, 1.0)), - _cube_handle.clone(), - LocalToWorld::identity(), - Translation::new(0.0, 0.0, 3.0), - Scale(1.0), - Parent(x), - LocalToParent::identity(), - ) - ]); + // world.insert((), vec![ + // ( + // Material::new(math::vec4(1.0, 1.0, 1.0, 1.0)), + // _cube_handle.clone(), + // LocalToWorld::identity(), + // Translation::new(0.0, 0.0, 3.0), + // Scale(1.0), + // Parent(x), + // LocalToParent::identity(), + // ) + // ]); world.insert((), vec![ @@ -262,6 +262,12 @@ fn main() { ) ]); + let mut rng = StdRng::from_entropy(); + for _ in 0 .. 70000 { + create_person(&mut world, _cube_handle.clone(), + Translation::new(rng.gen_range(-50.0, 50.0), 0.0, rng.gen_range(-50.0, 50.0))); + } + Application::run(universe, world, scheduler); } @@ -281,10 +287,36 @@ fn spawn_person(command_buffer: &mut CommandBuffer, mesh_handle: Handle) { Velocity { value: math::vec3(0.0, 0.0, 0.0), }, + Instanced, Material::new(math::vec4(0.5, 0.3, 0.3, 1.0) * random::()), mesh_handle.clone(), LocalToWorld::identity(), Translation::new(0.0, 0.0, 1.0) ), ]); -} \ No newline at end of file +} + +fn create_person(world: &mut World, mesh_handle: Handle, translation: Translation) { + world.insert((), vec![ + ( + Person{}, + Wander { + duration_bounds: math::vec2(3.0, 10.0), + distance_bounds: math::vec2(-50.0, 50.0), + elapsed: 0.0, + duration: 0.0, + }, + NavigationPoint { + target: math::vec3(0.0, 0.0, 0.0), + }, + Velocity { + value: math::vec3(0.0, 0.0, 0.0), + }, + Instanced, + Material::new(math::vec4(0.5, 0.3, 0.3, 1.0) * random::()), + mesh_handle, + LocalToWorld::identity(), + translation + ), + ]); +} \ No newline at end of file diff --git a/src/application.rs b/src/application.rs index 91ada36135..85e4eb3711 100644 --- a/src/application.rs +++ b/src/application.rs @@ -51,10 +51,12 @@ impl Application { // let shadow_pass = ShadowPass::new(&mut self.device, &mut self.world, &self.render_resources, vertex_buffer_descriptor.clone()); // let forward_shadow_pass = ForwardShadowPass::new(&mut self.device, &self.world, &self.render_resources, &shadow_pass, vertex_buffer_descriptor.clone(), &self.swap_chain_descriptor); - let forward_pass = ForwardPass::new(&mut self.device, &self.world, &self.render_resources, vertex_buffer_descriptor, &self.swap_chain_descriptor); + // let forward_pass = ForwardPass::new(&mut self.device, &self.world, &self.render_resources, vertex_buffer_descriptor.clone(), &self.swap_chain_descriptor); + let forward_instanced_pass = ForwardInstancedPass::new(&mut self.device, &self.world, &self.render_resources, vertex_buffer_descriptor, &self.swap_chain_descriptor); // self.render_passes.push(Box::new(shadow_pass)); // self.render_passes.push(Box::new(forward_shadow_pass)); - self.render_passes.push(Box::new(forward_pass)); + // self.render_passes.push(Box::new(forward_pass)); + self.render_passes.push(Box::new(forward_instanced_pass)); } fn update(&mut self) { @@ -113,7 +115,8 @@ impl Application { let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { todo: 0 }); - let mut entities = <(Write, Read)>::query(); + let mut entities = <(Write, Read)>::query() + .filter(!component::()); let entities_count = entities.iter(&mut self.world).count(); let size = mem::size_of::(); let temp_buf_data = self.device @@ -133,7 +136,7 @@ impl Application { self.render_resources.update_lights(&self.device, &mut encoder, &mut self.world); - for mut material in >::query().iter(&mut self.world) { + for mut material in >::query().filter(!component::()).iter(&mut self.world) { if let None = material.bind_group { let material_uniform_size = mem::size_of::() as wgpu::BufferAddress; let uniform_buf = self.device.create_buffer(&wgpu::BufferDescriptor { diff --git a/src/render/forward/mod.rs b/src/render/forward/mod.rs index a498291033..cd6a843b13 100644 --- a/src/render/forward/mod.rs +++ b/src/render/forward/mod.rs @@ -20,7 +20,9 @@ pub struct ForwardPass { impl Pass for ForwardPass { fn render(&mut self, device: &Device, frame: &SwapChainOutput, encoder: &mut CommandEncoder, world: &mut World, _: &RenderResources) { - let mut mesh_query = <(Read, Read>)>::query(); + let mut mesh_query = + <(Read, Read>)>::query() + .filter(!component::()); let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { attachment: &frame.view, diff --git a/src/render/forward_instanced/forward_instanced.frag b/src/render/forward_instanced/forward_instanced.frag new file mode 100644 index 0000000000..cc854b52d1 --- /dev/null +++ b/src/render/forward_instanced/forward_instanced.frag @@ -0,0 +1,40 @@ +#version 450 + +const int MAX_LIGHTS = 10; + +layout(location = 0) in vec3 v_Normal; +layout(location = 1) in vec4 v_Position; +layout(location = 2) in vec4 v_Color; + +layout(location = 0) out vec4 o_Target; + +struct Light { + mat4 proj; + vec4 pos; + vec4 color; +}; + +layout(set = 0, binding = 0) uniform Globals { + mat4 u_ViewProj; + uvec4 u_NumLights; +}; +layout(set = 0, binding = 1) uniform Lights { + Light u_Lights[MAX_LIGHTS]; +}; + +void main() { + vec3 normal = normalize(v_Normal); + vec3 ambient = vec3(0.05, 0.05, 0.05); + // accumulate color + vec3 color = ambient; + for (int i=0; i, +} + +impl Pass for ForwardInstancedPass { + fn render(&mut self, device: &Device, frame: &SwapChainOutput, encoder: &mut CommandEncoder, world: &mut World, _: &RenderResources) { + self.instance_buffer_infos = ForwardInstancedPass::create_instance_buffer_infos(device, world); + let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { + attachment: &frame.view, + resolve_target: None, + load_op: wgpu::LoadOp::Clear, + store_op: wgpu::StoreOp::Store, + clear_color: wgpu::Color { + r: 0.3, + g: 0.4, + b: 0.5, + a: 1.0, + }, + }], + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachmentDescriptor { + attachment: &self.depth_texture, + depth_load_op: wgpu::LoadOp::Clear, + depth_store_op: wgpu::StoreOp::Store, + stencil_load_op: wgpu::LoadOp::Clear, + stencil_store_op: wgpu::StoreOp::Store, + clear_depth: 1.0, + clear_stencil: 0, + }), + }); + pass.set_pipeline(&self.pipeline); + pass.set_bind_group(0, &self.bind_group, &[]); + + let mut mesh_storage = world.resources.get_mut::>().unwrap(); + for instance_buffer_info in self.instance_buffer_infos.iter() { + if let Some(mesh_asset) = mesh_storage.get(instance_buffer_info.mesh_id) { + mesh_asset.setup_buffers(device); + pass.set_index_buffer(mesh_asset.index_buffer.as_ref().unwrap(), 0); + pass.set_vertex_buffers(0, &[(&mesh_asset.vertex_buffer.as_ref().unwrap(), 0)]); + pass.set_vertex_buffers(1, &[(&instance_buffer_info.buffer, 0)]); + pass.draw_indexed(0 .. mesh_asset.indices.len() as u32, 0, 0 .. instance_buffer_info.instance_count as u32); + }; + } + } + + fn resize(&mut self, device: &Device, frame: &SwapChainDescriptor) { + self.depth_texture = Self::get_depth_texture(device, frame); + } + + fn get_camera_uniform_buffer(&self) -> Option<&Buffer> { + Some(&self.forward_uniform_buffer) + } +} + +impl ForwardInstancedPass { + pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float; + + pub fn new(device: &Device, world: &World, render_resources: &RenderResources, vertex_buffer_descriptor: VertexBufferDescriptor, swap_chain_descriptor: &SwapChainDescriptor) -> Self { + let vs_bytes = shader::load_glsl( + include_str!("forward_instanced.vert"), + shader::ShaderStage::Vertex, + ); + let fs_bytes = shader::load_glsl( + include_str!("forward_instanced.frag"), + shader::ShaderStage::Fragment, + ); + + let bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + bindings: &[ + wgpu::BindGroupLayoutBinding { + binding: 0, // global + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::UniformBuffer { dynamic: false }, + }, + wgpu::BindGroupLayoutBinding { + binding: 1, // lights + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::UniformBuffer { dynamic: false }, + } + ], + }); + + let light_count = >::query().iter_immutable(world).count(); + let forward_uniforms = ForwardUniforms { + proj: math::Mat4::identity().to_cols_array_2d(), + num_lights: [light_count as u32, 0, 0, 0], + }; + + let uniform_size = mem::size_of::() as wgpu::BufferAddress; + let forward_uniform_buffer = device.create_buffer_with_data( + forward_uniforms.as_bytes(), + wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, + ); + + // Create bind group + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &bind_group_layout, + bindings: &[ + wgpu::Binding { + binding: 0, + resource: wgpu::BindingResource::Buffer { + buffer: &forward_uniform_buffer, + range: 0 .. uniform_size, + }, + }, + wgpu::Binding { + binding: 1, + resource: wgpu::BindingResource::Buffer { + buffer: &render_resources.light_uniform_buffer.buffer, + range: 0 .. render_resources.light_uniform_buffer.size, + }, + } + ], + }); + + let simple_material_uniforms_size = mem::size_of::(); + let instance_buffer_descriptor = wgpu::VertexBufferDescriptor { + stride: simple_material_uniforms_size as wgpu::BufferAddress, + step_mode: wgpu::InputStepMode::Instance, + attributes: &[ + wgpu::VertexAttributeDescriptor { + format: wgpu::VertexFormat::Float3, + offset: 0, + shader_location: 2, + }, + wgpu::VertexAttributeDescriptor { + format: wgpu::VertexFormat::Float4, + offset: 3 * 4, + shader_location: 3, + }, + ], + }; + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + bind_group_layouts: &[&bind_group_layout], + }); + + let vs_module = device.create_shader_module(&vs_bytes); + let fs_module = device.create_shader_module(&fs_bytes); + + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + layout: &pipeline_layout, + vertex_stage: wgpu::ProgrammableStageDescriptor { + module: &vs_module, + entry_point: "main", + }, + fragment_stage: Some(wgpu::ProgrammableStageDescriptor { + module: &fs_module, + entry_point: "main", + }), + rasterization_state: Some(wgpu::RasterizationStateDescriptor { + front_face: wgpu::FrontFace::Ccw, + cull_mode: wgpu::CullMode::Back, + depth_bias: 0, + depth_bias_slope_scale: 0.0, + depth_bias_clamp: 0.0, + }), + primitive_topology: wgpu::PrimitiveTopology::TriangleList, + color_states: &[ + wgpu::ColorStateDescriptor { + format: swap_chain_descriptor.format, + color_blend: wgpu::BlendDescriptor::REPLACE, + alpha_blend: wgpu::BlendDescriptor::REPLACE, + write_mask: wgpu::ColorWrite::ALL, + }, + ], + depth_stencil_state: Some(wgpu::DepthStencilStateDescriptor { + format: Self::DEPTH_FORMAT, + depth_write_enabled: true, + depth_compare: wgpu::CompareFunction::Less, + stencil_front: wgpu::StencilStateFaceDescriptor::IGNORE, + stencil_back: wgpu::StencilStateFaceDescriptor::IGNORE, + stencil_read_mask: 0, + stencil_write_mask: 0, + }), + index_format: wgpu::IndexFormat::Uint16, + vertex_buffers: &[vertex_buffer_descriptor, instance_buffer_descriptor], + sample_count: 1, + sample_mask: !0, + alpha_to_coverage_enabled: false, + }); + + let instance_buffer_infos = ForwardInstancedPass::create_instance_buffer_infos(device, world); + + ForwardInstancedPass { + pipeline, + bind_group, + forward_uniform_buffer, + depth_texture: Self::get_depth_texture(device, swap_chain_descriptor), + instance_buffer_infos + } + } + + fn create_instance_buffer_infos(device: &Device, world: &World) -> Vec { + let mut entities = <(Read, Read, Read>, Read)>::query(); + let entities_count = entities.iter_immutable(world).count(); + let size = mem::size_of::(); + + // TODO: use a staging buffer for more efficient gpu reads + let temp_buf_data = device + .create_buffer_mapped(entities_count * size, wgpu::BufferUsage::COPY_SRC | wgpu::BufferUsage::VERTEX); + + // TODO: generate these buffers for multiple meshes + + let mut last_mesh_id = None; + for ((material, transform, mesh, _), slot) in entities.iter_immutable(world) + .zip(temp_buf_data.data.chunks_exact_mut(size)) + { + + last_mesh_id = Some(*mesh.id.read().unwrap()); + let (_, _, translation) = transform.0.to_scale_rotation_translation(); + slot.copy_from_slice( + SimpleMaterialUniforms { + position: translation.into(), + color: material.color.into(), + } + .as_bytes(), + ); + } + + let mut instance_buffer_infos = Vec::new(); + instance_buffer_infos.push(InstanceBufferInfo { + mesh_id: last_mesh_id.unwrap(), + buffer: temp_buf_data.finish(), + instance_count: entities_count, + }); + + instance_buffer_infos + } + + fn get_depth_texture(device: &Device, swap_chain_descriptor: &SwapChainDescriptor) -> wgpu::TextureView { + let texture = device.create_texture(&wgpu::TextureDescriptor { + size: wgpu::Extent3d { + width: swap_chain_descriptor.width, + height: swap_chain_descriptor.height, + depth: 1, + }, + array_layer_count: 1, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: Self::DEPTH_FORMAT, + usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, + }); + + texture.create_default_view() + } +} + diff --git a/src/render/material.rs b/src/render/material.rs index 66a4ce075e..4c976fab13 100644 --- a/src/render/material.rs +++ b/src/render/material.rs @@ -7,6 +7,8 @@ pub struct Material { pub uniform_buf: Option, } +pub struct Instanced; + impl Material { pub fn new(color: math::Vec4) -> Self { Material { @@ -28,4 +30,11 @@ pub struct RenderedUniforms { pub struct MaterialUniforms { pub model: [[f32; 4]; 4], pub color: [f32; 4], +} + +#[repr(C)] +#[derive(Clone, Copy, AsBytes, FromBytes)] +pub struct SimpleMaterialUniforms { + pub position: [f32; 3], + pub color: [f32; 4], } \ No newline at end of file diff --git a/src/render/mod.rs b/src/render/mod.rs index f391e4fc77..82709a2f24 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -3,14 +3,16 @@ pub mod shader; pub mod mesh; mod forward; mod forward_shadow; +mod forward_instanced; mod shadow; mod light; mod pass; mod material; mod render_resources; -pub use forward_shadow::{ForwardShadowPass}; pub use forward::{ForwardPass, ForwardUniforms}; +pub use forward_shadow::{ForwardShadowPass}; +pub use forward_instanced::ForwardInstancedPass; pub use shadow::ShadowPass; pub use light::*; pub use shader::*; diff --git a/src/render/shadow/mod.rs b/src/render/shadow/mod.rs index bf4c2988e1..d0f4f9f8d4 100644 --- a/src/render/shadow/mod.rs +++ b/src/render/shadow/mod.rs @@ -21,7 +21,9 @@ pub struct ShadowUniforms { impl Pass for ShadowPass { fn render(&mut self, device: &Device, _: &SwapChainOutput, encoder: &mut CommandEncoder, world: &mut World, render_resources: &RenderResources) { let mut light_query = <(Read, Read, Read)>::query(); - let mut mesh_query = <(Read, Read>)>::query(); + let mut mesh_query = + <(Read, Read>)>::query() + .filter(!component::()); for (i, (mut light, _)) in <(Write, Read)>::query().iter(world).enumerate() { if let None = light.target_view {