diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 826272b29..4755e32ee 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -81,6 +81,9 @@ path = "draw/draw_mesh.rs" name = "draw_polygon" path = "draw/draw_polygon.rs" [[example]] +name = "draw_instanced_polygon" +path = "draw/draw_instanced_polygon.rs" +[[example]] name = "draw_polyline" path = "draw/draw_polyline.rs" [[example]] diff --git a/examples/draw/draw_instanced_polygon.rs b/examples/draw/draw_instanced_polygon.rs new file mode 100644 index 000000000..906462ae8 --- /dev/null +++ b/examples/draw/draw_instanced_polygon.rs @@ -0,0 +1,45 @@ +use nannou::draw::renderer::Instance; +use nannou::prelude::*; + +fn main() { + nannou::sketch(view).run() +} + +fn view(app: &App, frame: Frame) { + // Begin drawing + let win = app.window_rect(); + let t = app.time; + let draw = app.draw(); + let radius = win.w().min(win.h()) * 0.25; + let dim_x = win.w() / 2.0; + let n_instances = 10; + + + + // Clear the background to blue. + draw.background().color(BLACK); + + let instances = (0 .. n_instances).map(|row|{ + Instance {transform: Mat4::from_translation(Vec3::new(-dim_x + row as f32 * (2.0 * dim_x / n_instances as f32), 0.0, 0.0))} + }).collect(); + + // Do the same, but give each point a unique colour. + let n_points = 7; + let points_colored = (0..n_points).map(|i| { + let fract = i as f32 / n_points as f32; + let phase = fract; + let x = radius * (TAU * phase).cos(); + let y = radius * (TAU * phase).sin(); + let r = fract; + let g = 1.0 - fract; + let b = (0.5 + fract) % 1.0; + (pt2(x, y), rgb(r, g, b)) + }); + + draw.instances(instances).polygon() + .rotate(t * 0.2) + .points_colored(points_colored); + + // Write the result of our drawing to the window's frame. + draw.to_frame(app, &frame).unwrap(); +} diff --git a/nannou/src/draw/mod.rs b/nannou/src/draw/mod.rs index aee06422c..eae33dfa2 100644 --- a/nannou/src/draw/mod.rs +++ b/nannou/src/draw/mod.rs @@ -19,6 +19,7 @@ pub use self::mesh::Mesh; use self::primitive::Primitive; pub use self::renderer::{Builder as RendererBuilder, Renderer}; pub use self::theme::Theme; +use crate::draw::renderer::Instance; pub mod background; mod drawing; @@ -62,6 +63,7 @@ pub struct Draw { #[derive(Clone, Debug, PartialEq)] pub struct Context { pub transform: Mat4, + pub instances: Vec, pub blend: wgpu::BlendState, pub scissor: Scissor, // TODO: Consider changing `PolygonMode` (added as of wgpu 0.7) rather than `PrimitiveTopology` @@ -204,6 +206,12 @@ impl Draw { self.context(context) } + pub fn instances (&self, instances: Vec) -> Self { + let mut context = self.context.clone(); + context.instances = instances; + self.context(context) + } + /// Translate the position of the origin by the given translation vector. pub fn translate(&self, v: Vec3) -> Self { self.transform(Mat4::from_translation(v)) @@ -642,6 +650,7 @@ impl Default for Context { fn default() -> Self { Self { transform: Mat4::IDENTITY, + instances: vec![Instance{transform: Mat4::IDENTITY}], blend: wgpu::BlendState { color: wgpu::RenderPipelineBuilder::DEFAULT_COLOR_BLEND, alpha: wgpu::RenderPipelineBuilder::DEFAULT_ALPHA_BLEND, diff --git a/nannou/src/draw/renderer/mod.rs b/nannou/src/draw/renderer/mod.rs index 7c978435e..69752b68d 100644 --- a/nannou/src/draw/renderer/mod.rs +++ b/nannou/src/draw/renderer/mod.rs @@ -11,9 +11,20 @@ use lyon::tessellation::{FillTessellator, StrokeTessellator}; use std::collections::HashMap; use std::fmt; use std::hash::{Hash, Hasher}; -use std::ops::{Deref, DerefMut}; +use std::ops::{Deref, DerefMut, Range}; use wgpu::util::{BufferInitDescriptor, DeviceExt}; + +#[repr(C)] +#[derive(Copy, Clone, PartialEq, Debug)] +pub struct Instance { + pub transform: Mat4 +} + +fn instances_as_bytes(data: &[Instance]) -> &[u8] { + unsafe { wgpu::bytes::from_slice(data) } +} + /// Draw API primitives that may be rendered via the **Renderer** type. pub trait RenderPrimitive { /// Render self into the given mesh. @@ -98,6 +109,7 @@ pub struct Renderer { mesh: draw::Mesh, vertex_mode_buffer: Vec, uniform_buffer: wgpu::Buffer, + instances: Vec } /// A type aimed at simplifying construction of a `draw::Renderer`. @@ -119,9 +131,11 @@ enum RenderCommand { /// Set the rectangular scissor. SetScissor(Scissor), /// Draw the given vertex range. + DrawIndexed { start_vertex: i32, index_range: std::ops::Range, + instance_range: std::ops::Range }, } @@ -454,6 +468,7 @@ impl Renderer { let render_commands = vec![]; let mesh = Default::default(); let vertex_mode_buffer = vec![]; + let instances = vec![Instance{transform: Mat4::IDENTITY}]; Self { vs_mod, @@ -479,6 +494,7 @@ impl Renderer { mesh, vertex_mode_buffer, uniform_buffer, + instances } } @@ -487,6 +503,7 @@ impl Renderer { self.render_commands.clear(); self.mesh.clear(); self.vertex_mode_buffer.clear(); + self.instances = vec![Instance{transform: Mat4::IDENTITY}]; } /// Generate a list of `RenderCommand`s from the given **Draw** instance and prepare any @@ -508,6 +525,7 @@ impl Renderer { curr_start_index: &mut u32, end_index: u32, render_commands: &mut Vec, + instance_range: Range ) -> bool { let index_range = *curr_start_index..end_index; if index_range.len() != 0 { @@ -516,6 +534,7 @@ impl Renderer { let cmd = RenderCommand::DrawIndexed { start_vertex, index_range, + instance_range }; render_commands.push(cmd); true @@ -551,14 +570,37 @@ impl Renderer { let mut curr_pipeline_id = None; let mut curr_scissor = None; let mut curr_tex_sampler_id = None; + let mut curr_instancing_range = 0..1u32; // Collect all draw commands to avoid borrow errors. let draw_cmds: Vec<_> = draw.drain_commands().collect(); let draw_state = draw.state.borrow_mut(); let intermediary_state = draw_state.intermediary_state.borrow(); + for cmd in draw_cmds { match cmd { - draw::DrawCommand::Context(ctxt) => curr_ctxt = ctxt, + draw::DrawCommand::Context(ctxt) => { + if ctxt.instances != curr_ctxt.instances { + + let old_instancing_range = curr_instancing_range.clone(); + push_draw_cmd( + &mut curr_start_index, + self.mesh.indices().len() as u32, + &mut self.render_commands, + old_instancing_range.clone() + ); + + if ctxt.instances.len() == 1 { + curr_instancing_range = 0..1u32; + } else { + let instance_len = self.instances.len() as u32; + curr_instancing_range = instance_len .. instance_len + ctxt.instances.len() as u32; + self.instances.append(&mut ctxt.instances.clone()); + } + } + + curr_ctxt = ctxt; + }, draw::DrawCommand::Primitive(prim) => { // Track the prev index and vertex counts. let prev_index_count = self.mesh.indices().len() as u32; @@ -638,6 +680,7 @@ impl Renderer { &mut curr_start_index, prev_index_count, &mut self.render_commands, + curr_instancing_range.clone() ); } @@ -699,6 +742,7 @@ impl Renderer { &mut curr_start_index, self.mesh.indices().len() as u32, &mut self.render_commands, + curr_instancing_range.clone() ); // Clear out unnecessary pipelines. @@ -763,6 +807,8 @@ impl Renderer { } } + + /// Encode a render pass with the given **Draw**ing to the given `output_attachment`. /// /// If the **Draw**ing has been scaled for handling DPI, specify the necessary `scale_factor` @@ -891,6 +937,14 @@ impl Renderer { encoder.copy_buffer_to_buffer(&new_uniform_buffer, 0, uniform_buffer, 0, uniforms_size); } + let instances_bytes = instances_as_bytes(&self.instances[..]); + let usage = wgpu::BufferUsages::VERTEX; + let instance_buffer = device.create_buffer_init(&BufferInitDescriptor { + label: None, + contents: instances_bytes, + usage, + }); + // Encode the render pass. let mut render_pass = render_pass_builder.begin(encoder); @@ -900,6 +954,8 @@ impl Renderer { render_pass.set_vertex_buffer(1, color_buffer.slice(..)); render_pass.set_vertex_buffer(2, tex_coords_buffer.slice(..)); render_pass.set_vertex_buffer(3, mode_buffer.slice(..)); + render_pass.set_vertex_buffer(4, instance_buffer.slice(..)); + // Set the uniform and text bind groups here. render_pass.set_bind_group(0, uniform_bind_group, &[]); @@ -930,9 +986,9 @@ impl Renderer { RenderCommand::DrawIndexed { start_vertex, index_range, + instance_range } => { - let instance_range = 0..1u32; - render_pass.draw_indexed(index_range, start_vertex, instance_range); + render_pass.draw_indexed(index_range, start_vertex, instance_range.clone()); } } } @@ -1131,6 +1187,28 @@ fn create_render_pipeline( &wgpu::vertex_attr_array![2 => Float32x2], ) .add_vertex_buffer::(&wgpu::vertex_attr_array![3 => Uint32]) + .add_instance_buffer::(&[ + wgpu::VertexAttribute { + shader_location: 4, + format: wgpu::VertexFormat::Float32x4, + offset: 0, + }, + wgpu::VertexAttribute { + shader_location: 5, + format: wgpu::VertexFormat::Float32x4, + offset: std::mem::size_of::<[f32; 4]>() as u64 * 1, + }, + wgpu::VertexAttribute { + shader_location: 6, + format: wgpu::VertexFormat::Float32x4, + offset: std::mem::size_of::<[f32; 4]>() as u64 * 2, + }, + wgpu::VertexAttribute { + shader_location: 7, + format: wgpu::VertexFormat::Float32x4, + offset: std::mem::size_of::<[f32; 4]>() as u64 * 3, + } + ]) .depth_format(depth_format) .sample_count(sample_count) .color_blend(color_blend) diff --git a/nannou/src/draw/renderer/shaders/vs.wgsl b/nannou/src/draw/renderer/shaders/vs.wgsl index c8d8923c0..22cac39ca 100644 --- a/nannou/src/draw/renderer/shaders/vs.wgsl +++ b/nannou/src/draw/renderer/shaders/vs.wgsl @@ -19,7 +19,12 @@ fn main( [[location(1)]] color: vec4, [[location(2)]] tex_coords: vec2, [[location(3)]] mode: u32, + [[location(4)]] mat0: vec4, + [[location(5)]] mat1: vec4, + [[location(6)]] mat2: vec4, + [[location(7)]] mat3: vec4, ) -> VertexOutput { - let out_pos: vec4 = uniforms.proj * vec4(position, 1.0); + let instance_transform: mat4x4 = mat4x4(mat0, mat1, mat2, mat3); + let out_pos: vec4 = uniforms.proj * instance_transform * vec4(position, 1.0); return VertexOutput(color, tex_coords, mode, out_pos); }