Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A suggestion for instanced drawing #858

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]]
Expand Down
45 changes: 45 additions & 0 deletions examples/draw/draw_instanced_polygon.rs
Original file line number Diff line number Diff line change
@@ -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();
}
9 changes: 9 additions & 0 deletions nannou/src/draw/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -62,6 +63,7 @@ pub struct Draw {
#[derive(Clone, Debug, PartialEq)]
pub struct Context {
pub transform: Mat4,
pub instances: Vec<Instance>,
pub blend: wgpu::BlendState,
pub scissor: Scissor,
// TODO: Consider changing `PolygonMode` (added as of wgpu 0.7) rather than `PrimitiveTopology`
Expand Down Expand Up @@ -204,6 +206,12 @@ impl Draw {
self.context(context)
}

pub fn instances (&self, instances: Vec<Instance>) -> 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))
Expand Down Expand Up @@ -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,
Expand Down
86 changes: 82 additions & 4 deletions nannou/src/draw/renderer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -98,6 +109,7 @@ pub struct Renderer {
mesh: draw::Mesh,
vertex_mode_buffer: Vec<VertexMode>,
uniform_buffer: wgpu::Buffer,
instances: Vec<Instance>
}

/// A type aimed at simplifying construction of a `draw::Renderer`.
Expand All @@ -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<u32>,
instance_range: std::ops::Range<u32>
},
}

Expand Down Expand Up @@ -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,
Expand All @@ -479,6 +494,7 @@ impl Renderer {
mesh,
vertex_mode_buffer,
uniform_buffer,
instances
}
}

Expand All @@ -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
Expand All @@ -508,6 +525,7 @@ impl Renderer {
curr_start_index: &mut u32,
end_index: u32,
render_commands: &mut Vec<RenderCommand>,
instance_range: Range<u32>
) -> bool {
let index_range = *curr_start_index..end_index;
if index_range.len() != 0 {
Expand All @@ -516,6 +534,7 @@ impl Renderer {
let cmd = RenderCommand::DrawIndexed {
start_vertex,
index_range,
instance_range
};
render_commands.push(cmd);
true
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -638,6 +680,7 @@ impl Renderer {
&mut curr_start_index,
prev_index_count,
&mut self.render_commands,
curr_instancing_range.clone()
);
}

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -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);

Expand All @@ -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, &[]);
Expand Down Expand Up @@ -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());
}
}
}
Expand Down Expand Up @@ -1131,6 +1187,28 @@ fn create_render_pipeline(
&wgpu::vertex_attr_array![2 => Float32x2],
)
.add_vertex_buffer::<VertexMode>(&wgpu::vertex_attr_array![3 => Uint32])
.add_instance_buffer::<Instance>(&[
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)
Expand Down
7 changes: 6 additions & 1 deletion nannou/src/draw/renderer/shaders/vs.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ fn main(
[[location(1)]] color: vec4<f32>,
[[location(2)]] tex_coords: vec2<f32>,
[[location(3)]] mode: u32,
[[location(4)]] mat0: vec4<f32>,
[[location(5)]] mat1: vec4<f32>,
[[location(6)]] mat2: vec4<f32>,
[[location(7)]] mat3: vec4<f32>,
) -> VertexOutput {
let out_pos: vec4<f32> = uniforms.proj * vec4<f32>(position, 1.0);
let instance_transform: mat4x4<f32> = mat4x4<f32>(mat0, mat1, mat2, mat3);
let out_pos: vec4<f32> = uniforms.proj * instance_transform * vec4<f32>(position, 1.0);
return VertexOutput(color, tex_coords, mode, out_pos);
}