diff --git a/Cargo.lock b/Cargo.lock index 50f6e993..6b765ace 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1393,6 +1393,17 @@ dependencies = [ "winit 0.20.0-alpha4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "tutorial7-instancing" +version = "0.1.0" +dependencies = [ + "cgmath 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glsl-to-spirv 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "image 0.22.3 (registry+https://github.com/rust-lang/crates.io-index)", + "wgpu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winit 0.20.0-alpha4 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "typenum" version = "1.11.2" diff --git a/code/beginner/tutorial7-instancing/Cargo.toml b/code/beginner/tutorial7-instancing/Cargo.toml new file mode 100644 index 00000000..5005f21a --- /dev/null +++ b/code/beginner/tutorial7-instancing/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "tutorial7-instancing" +version = "0.1.0" +authors = ["Ben Hansen "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +image = "0.22" +winit = "0.20.0-alpha4" +glsl-to-spirv = "0.1" +wgpu = "0.4" +cgmath = "0.17" + +[[bin]] +name = "tutorial7-instancing" +path = "src/main.rs" + + +[[bin]] +name = "tutorial7-challenge" +path = "src/challenge.rs" \ No newline at end of file diff --git a/code/beginner/tutorial7-instancing/src/arrays.rs b/code/beginner/tutorial7-instancing/src/arrays.rs new file mode 100644 index 00000000..659634ca --- /dev/null +++ b/code/beginner/tutorial7-instancing/src/arrays.rs @@ -0,0 +1,617 @@ +use winit::{ + event::*, + event_loop::{EventLoop, ControlFlow}, + window::{Window, WindowBuilder}, +}; +use cgmath::prelude::*; + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +struct Vertex { + position: [f32; 3], + tex_coords: [f32; 2], +} + +impl Vertex { + fn desc<'a>() -> wgpu::VertexBufferDescriptor<'a> { + use std::mem; + wgpu::VertexBufferDescriptor { + stride: mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::InputStepMode::Vertex, + attributes: &[ + wgpu::VertexAttributeDescriptor { + offset: 0, + shader_location: 0, + format: wgpu::VertexFormat::Float3, + }, + wgpu::VertexAttributeDescriptor { + offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress, + shader_location: 1, + format: wgpu::VertexFormat::Float2, + }, + ] + } + } +} + +const VERTICES: &[Vertex] = &[ + Vertex { position: [-0.0868241, -0.49240386, 0.0], tex_coords: [1.0 - 0.4131759, 1.0 - 0.00759614], }, // A + Vertex { position: [-0.49513406, -0.06958647, 0.0], tex_coords: [1.0 - 0.0048659444, 1.0 - 0.43041354], }, // B + Vertex { position: [-0.21918549, 0.44939706, 0.0], tex_coords: [1.0 - 0.28081453, 1.0 - 0.949397057], }, // C + Vertex { position: [0.35966998, 0.3473291, 0.0], tex_coords: [1.0 - 0.85967, 1.0 - 0.84732911], }, // D + Vertex { position: [0.44147372, -0.2347359, 0.0], tex_coords: [1.0 - 0.9414737, 1.0 - 0.2652641], }, // E +]; + +const INDICES: &[u16] = &[ + 0, 1, 4, + 1, 2, 4, + 2, 3, 4, +]; + +#[cfg_attr(rustfmt, rustfmt_skip)] +pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4 = cgmath::Matrix4::new( + 1.0, 0.0, 0.0, 0.0, + 0.0, -1.0, 0.0, 0.0, + 0.0, 0.0, 0.5, 0.0, + 0.0, 0.0, 0.5, 1.0, +); + +const NUM_INSTANCES_PER_ROW: u32 = 10; +const NUM_INSTANCES: u32 = NUM_INSTANCES_PER_ROW * NUM_INSTANCES_PER_ROW; +const INSTANCE_DISPLACEMENT: cgmath::Vector3 = cgmath::Vector3::new(NUM_INSTANCES_PER_ROW as f32 * 0.5, 0.0, NUM_INSTANCES_PER_ROW as f32 * 0.5); + + +struct Camera { + eye: cgmath::Point3, + target: cgmath::Point3, + up: cgmath::Vector3, + aspect: f32, + fovy: f32, + znear: f32, + zfar: f32, +} + +impl Camera { + fn build_view_projection_matrix(&self) -> cgmath::Matrix4 { + let view = cgmath::Matrix4::look_at(self.eye, self.target, self.up); + let proj = cgmath::perspective(cgmath::Deg(self.fovy), self.aspect, self.znear, self.zfar); + return proj * view; + } +} + +#[repr(C)] +#[derive(Copy, Clone)] +struct Uniforms { + view_proj: cgmath::Matrix4, + model: [cgmath::Matrix4; NUM_INSTANCES as usize], +} + +impl Uniforms { + fn new() -> Self { + Self { + view_proj: cgmath::Matrix4::identity(), + model: [cgmath::Matrix4::identity(); NUM_INSTANCES as usize], + } + } + + fn update_view_proj(&mut self, camera: &Camera) { + self.view_proj = OPENGL_TO_WGPU_MATRIX * camera.build_view_projection_matrix(); + } +} + +struct CameraController { + speed: f32, + is_up_pressed: bool, + is_down_pressed: bool, + is_forward_pressed: bool, + is_backward_pressed: bool, + is_left_pressed: bool, + is_right_pressed: bool, +} + +impl CameraController { + fn new(speed: f32) -> Self { + Self { + speed, + is_up_pressed: false, + is_down_pressed: false, + is_forward_pressed: false, + is_backward_pressed: false, + is_left_pressed: false, + is_right_pressed: false, + } + } + + fn process_events(&mut self, event: &WindowEvent) -> bool { + match event { + WindowEvent::KeyboardInput { + input: KeyboardInput { + state, + virtual_keycode: Some(keycode), + .. + }, + .. + } => { + let is_pressed = *state == ElementState::Pressed; + match keycode { + VirtualKeyCode::Space => { + self.is_up_pressed = is_pressed; + true + } + VirtualKeyCode::LShift => { + self.is_down_pressed = is_pressed; + true + } + VirtualKeyCode::W | VirtualKeyCode::Up => { + self.is_forward_pressed = is_pressed; + true + } + VirtualKeyCode::A | VirtualKeyCode::Left => { + self.is_left_pressed = is_pressed; + true + } + VirtualKeyCode::S | VirtualKeyCode::Down => { + self.is_backward_pressed = is_pressed; + true + } + VirtualKeyCode::D | VirtualKeyCode::Right => { + self.is_right_pressed = is_pressed; + true + } + _ => false, + } + } + _ => false, + } + } + + fn update_camera(&self, camera: &mut Camera) { + let forward = (camera.target - camera.eye).normalize(); + + if self.is_forward_pressed { + camera.eye += forward * self.speed; + } + if self.is_backward_pressed { + camera.eye -= forward * self.speed; + } + + let right = forward.cross(camera.up); + + if self.is_right_pressed { + camera.eye += right * self.speed; + } + if self.is_left_pressed { + camera.eye -= right * self.speed; + } + } +} + +struct Instance { + position: cgmath::Vector3, + rotation: cgmath::Quaternion, +} + +impl Instance { + fn to_matrix(&self) -> cgmath::Matrix4 { + cgmath::Matrix4::from_translation(self.position) * cgmath::Matrix4::from(self.rotation) + } +} + +struct State { + surface: wgpu::Surface, + device: wgpu::Device, + queue: wgpu::Queue, + sc_desc: wgpu::SwapChainDescriptor, + swap_chain: wgpu::SwapChain, + + render_pipeline: wgpu::RenderPipeline, + + vertex_buffer: wgpu::Buffer, + index_buffer: wgpu::Buffer, + num_indices: u32, + + diffuse_texture: wgpu::Texture, + diffuse_texture_view: wgpu::TextureView, + diffuse_sampler: wgpu::Sampler, + diffuse_bind_group: wgpu::BindGroup, + + camera: Camera, + camera_controller: CameraController, + uniforms: Uniforms, + uniform_buffer: wgpu::Buffer, + uniform_bind_group: wgpu::BindGroup, + + hidpi_factor: f64, + size: winit::dpi::LogicalSize, + + instances: Vec, +} + +impl State { + fn new(window: &Window) -> Self { + let hidpi_factor = window.hidpi_factor(); + let size = window.inner_size(); + let physical_size = size.to_physical(hidpi_factor); + + let surface = wgpu::Surface::create(window); + + let adapter = wgpu::Adapter::request(&Default::default()).unwrap(); + + let (device, mut queue) = adapter.request_device(&Default::default()); + + let sc_desc = wgpu::SwapChainDescriptor { + usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, + format: wgpu::TextureFormat::Bgra8UnormSrgb, + width: physical_size.width.round() as u32, + height: physical_size.height.round() as u32, + present_mode: wgpu::PresentMode::Vsync, + }; + let swap_chain = device.create_swap_chain(&surface, &sc_desc); + + let diffuse_bytes = include_bytes!("happy-tree.png"); + let diffuse_image = image::load_from_memory(diffuse_bytes).unwrap(); + let diffuse_rgba = diffuse_image.as_rgba8().unwrap(); + + use image::GenericImageView; + let dimensions = diffuse_image.dimensions(); + + let size3d = wgpu::Extent3d { + width: dimensions.0, + height: dimensions.1, + depth: 1, + }; + let diffuse_texture = device.create_texture(&wgpu::TextureDescriptor { + size: size3d, + array_layer_count: 1, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8UnormSrgb, + usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST, + }); + + let diffuse_buffer = device + .create_buffer_mapped(diffuse_rgba.len(), wgpu::BufferUsage::COPY_SRC) + .fill_from_slice(&diffuse_rgba); + + let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + todo: 0, + }); + + encoder.copy_buffer_to_texture( + wgpu::BufferCopyView { + buffer: &diffuse_buffer, + offset: 0, + row_pitch: 4 * dimensions.0, + image_height: dimensions.1, + }, + wgpu::TextureCopyView { + texture: &diffuse_texture, + mip_level: 0, + array_layer: 0, + origin: wgpu::Origin3d::ZERO, + }, + size3d, + ); + + queue.submit(&[encoder.finish()]); + + let diffuse_texture_view = diffuse_texture.create_default_view(); + let diffuse_sampler = device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + lod_min_clamp: -100.0, + lod_max_clamp: 100.0, + compare_function: wgpu::CompareFunction::Always, + }); + + let texture_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + bindings: &[ + wgpu::BindGroupLayoutBinding { + binding: 0, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::SampledTexture { + multisampled: false, + dimension: wgpu::TextureViewDimension::D2, + }, + }, + wgpu::BindGroupLayoutBinding { + binding: 1, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Sampler, + }, + ], + }); + + let diffuse_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &texture_bind_group_layout, + bindings: &[ + wgpu::Binding { + binding: 0, + resource: wgpu::BindingResource::TextureView(&diffuse_texture_view), + }, + wgpu::Binding { + binding: 1, + resource: wgpu::BindingResource::Sampler(&diffuse_sampler), + } + ], + }); + + let camera = Camera { + eye: (0.0, 5.0, -10.0).into(), + target: (0.0, 0.0, 0.0).into(), + up: cgmath::Vector3::unit_y(), + aspect: sc_desc.width as f32 / sc_desc.height as f32, + fovy: 45.0, + znear: 0.1, + zfar: 100.0, + }; + let camera_controller = CameraController::new(0.2); + + let mut uniforms = Uniforms::new(); + uniforms.update_view_proj(&camera); + + let uniform_buffer = device + .create_buffer_mapped(1, wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST) + .fill_from_slice(&[uniforms]); + + let uniform_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + bindings: &[ + wgpu::BindGroupLayoutBinding { + binding: 0, + visibility: wgpu::ShaderStage::VERTEX, + ty: wgpu::BindingType::UniformBuffer { + dynamic: false, + }, + } + ] + }); + + let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &uniform_bind_group_layout, + bindings: &[ + wgpu::Binding { + binding: 0, + resource: wgpu::BindingResource::Buffer { + buffer: &uniform_buffer, + range: 0..std::mem::size_of_val(&uniforms) as wgpu::BufferAddress, + } + } + ], + }); + + let vs_src = include_str!("arrays.vert"); + let fs_src = include_str!("shader.frag"); + let vs_spirv = glsl_to_spirv::compile(vs_src, glsl_to_spirv::ShaderType::Vertex).unwrap(); + let fs_spirv = glsl_to_spirv::compile(fs_src, glsl_to_spirv::ShaderType::Fragment).unwrap(); + let vs_data = wgpu::read_spirv(vs_spirv).unwrap(); + let fs_data = wgpu::read_spirv(fs_spirv).unwrap(); + let vs_module = device.create_shader_module(&vs_data); + let fs_module = device.create_shader_module(&fs_data); + + let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + bind_group_layouts: &[&texture_bind_group_layout, &uniform_bind_group_layout], + }); + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + layout: &render_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: sc_desc.format, + color_blend: wgpu::BlendDescriptor::REPLACE, + alpha_blend: wgpu::BlendDescriptor::REPLACE, + write_mask: wgpu::ColorWrite::ALL, + }, + ], + depth_stencil_state: None, + index_format: wgpu::IndexFormat::Uint16, + vertex_buffers: &[ + Vertex::desc(), + ], + sample_count: 1, + sample_mask: !0, + alpha_to_coverage_enabled: false, + }); + + let vertex_buffer = device + .create_buffer_mapped(VERTICES.len(), wgpu::BufferUsage::VERTEX) + .fill_from_slice(VERTICES); + let index_buffer = device + .create_buffer_mapped(INDICES.len(), wgpu::BufferUsage::INDEX) + .fill_from_slice(INDICES); + let num_indices = INDICES.len() as u32; + + let instances = (0..NUM_INSTANCES_PER_ROW).flat_map(|z| { + (0..NUM_INSTANCES_PER_ROW).map(move |x| { + let position = cgmath::Vector3 { x: x as f32, y: 0.0, z: z as f32 } - INSTANCE_DISPLACEMENT; + + let rotation = if position.is_zero() { + // this is needed so an object at (0, 0, 0) won't get scaled to zero + // as Quaternions can effect scale if they're not create correctly + cgmath::Quaternion::from_axis_angle(cgmath::Vector3::unit_z(), cgmath::Deg(0.0)) + } else { + cgmath::Quaternion::from_axis_angle(position.clone().normalize(), cgmath::Deg(45.0)) + }; + + Instance { + position, rotation, + } + }) + }).collect(); + + Self { + surface, + device, + queue, + sc_desc, + swap_chain, + render_pipeline, + vertex_buffer, + index_buffer, + num_indices, + diffuse_texture, + diffuse_texture_view, + diffuse_sampler, + diffuse_bind_group, + camera, + camera_controller, + uniform_buffer, + uniform_bind_group, + uniforms, + hidpi_factor, + size, + instances, + } + } + + fn update_hidpi_and_resize(&mut self, new_hidpi_factor: f64) { + self.hidpi_factor = new_hidpi_factor; + self.resize(self.size); + } + + fn resize(&mut self, new_size: winit::dpi::LogicalSize) { + let physical_size = new_size.to_physical(self.hidpi_factor); + self.size = new_size; + self.sc_desc.width = physical_size.width.round() as u32; + self.sc_desc.height = physical_size.height.round() as u32; + self.swap_chain = self.device.create_swap_chain(&self.surface, &self.sc_desc); + + self.camera.aspect = self.sc_desc.width as f32 / self.sc_desc.height as f32; + } + + fn input(&mut self, event: &WindowEvent) -> bool { + self.camera_controller.process_events(event) + } + + fn update(&mut self) { + self.camera_controller.update_camera(&mut self.camera); + self.uniforms.update_view_proj(&self.camera); + + for (i, instance) in self.instances.iter().enumerate() { + self.uniforms.model[i] = instance.to_matrix(); + } + + let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + todo: 0, + }); + + let staging_buffer = self.device + .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC) + .fill_from_slice(&[self.uniforms]); + + encoder.copy_buffer_to_buffer(&staging_buffer, 0, &self.uniform_buffer, 0, std::mem::size_of::() as wgpu::BufferAddress); + + self.queue.submit(&[encoder.finish()]); + } + + fn render(&mut self) { + let frame = self.swap_chain.get_next_texture(); + + let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + todo: 0, + }); + + { + let mut render_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.1, + g: 0.2, + b: 0.3, + a: 1.0, + }, + } + ], + depth_stencil_attachment: None, + }); + + render_pass.set_pipeline(&self.render_pipeline); + render_pass.set_bind_group(0, &self.diffuse_bind_group, &[]); + render_pass.set_bind_group(1, &self.uniform_bind_group, &[]); + render_pass.set_vertex_buffers(0, &[(&self.vertex_buffer, 0)]); + render_pass.set_index_buffer(&self.index_buffer, 0); + render_pass.draw_indexed(0..self.num_indices, 0, 0..NUM_INSTANCES); + } + + self.queue.submit(&[ + encoder.finish() + ]); + } +} + +fn main() { + let event_loop = EventLoop::new(); + let window = WindowBuilder::new() + .build(&event_loop) + .unwrap(); + + let mut state = State::new(&window); + + event_loop.run(move |event, _, control_flow| { + match event { + Event::WindowEvent { + ref event, + window_id, + } if window_id == window.id() => if state.input(event) { + *control_flow = ControlFlow::Wait; + } else { + match event { + WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + WindowEvent::KeyboardInput { + input, + .. + } => { + match input { + KeyboardInput { + state: ElementState::Pressed, + virtual_keycode: Some(VirtualKeyCode::Escape), + .. + } => *control_flow = ControlFlow::Exit, + _ => *control_flow = ControlFlow::Wait, + } + } + WindowEvent::Resized(logical_size) => { + state.resize(*logical_size); + *control_flow = ControlFlow::Wait; + } + WindowEvent::HiDpiFactorChanged(new_hidpi_factor) => { + state.update_hidpi_and_resize(*new_hidpi_factor); + *control_flow = ControlFlow::Wait; + } + _ => *control_flow = ControlFlow::Wait, + } + } + Event::EventsCleared => { + state.update(); + state.render(); + *control_flow = ControlFlow::Wait; + } + _ => *control_flow = ControlFlow::Wait, + } + }); +} \ No newline at end of file diff --git a/code/beginner/tutorial7-instancing/src/arrays.vert b/code/beginner/tutorial7-instancing/src/arrays.vert new file mode 100644 index 00000000..881796a3 --- /dev/null +++ b/code/beginner/tutorial7-instancing/src/arrays.vert @@ -0,0 +1,17 @@ +#version 450 + +layout(location=0) in vec3 a_position; +layout(location=1) in vec2 a_tex_coords; + +layout(location=0) out vec2 v_tex_coords; + +layout(set=1, binding=0) +uniform Uniforms { + mat4 u_view_proj; + mat4 u_model[100]; +}; + +void main() { + v_tex_coords = a_tex_coords; + gl_Position = u_view_proj * u_model[gl_InstanceIndex] * vec4(a_position, 1.0); +} \ No newline at end of file diff --git a/code/beginner/tutorial7-instancing/src/challenge.rs b/code/beginner/tutorial7-instancing/src/challenge.rs new file mode 100644 index 00000000..cfbd8cbb --- /dev/null +++ b/code/beginner/tutorial7-instancing/src/challenge.rs @@ -0,0 +1,609 @@ +use winit::{ + event::*, + event_loop::{EventLoop, ControlFlow}, + window::{Window, WindowBuilder}, +}; + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +struct Vertex { + position: [f32; 3], + tex_coords: [f32; 2], +} + +impl Vertex { + fn desc<'a>() -> wgpu::VertexBufferDescriptor<'a> { + use std::mem; + wgpu::VertexBufferDescriptor { + stride: mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::InputStepMode::Vertex, + attributes: &[ + wgpu::VertexAttributeDescriptor { + offset: 0, + shader_location: 0, + format: wgpu::VertexFormat::Float3, + }, + wgpu::VertexAttributeDescriptor { + offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress, + shader_location: 1, + format: wgpu::VertexFormat::Float2, + }, + ] + } + } +} + +const VERTICES: &[Vertex] = &[ + Vertex { position: [-0.0868241, -0.49240386, 0.0], tex_coords: [1.0 - 0.4131759, 1.0 - 0.00759614], }, // A + Vertex { position: [-0.49513406, -0.06958647, 0.0], tex_coords: [1.0 - 0.0048659444, 1.0 - 0.43041354], }, // B + Vertex { position: [-0.21918549, 0.44939706, 0.0], tex_coords: [1.0 - 0.28081453, 1.0 - 0.949397057], }, // C + Vertex { position: [0.35966998, 0.3473291, 0.0], tex_coords: [1.0 - 0.85967, 1.0 - 0.84732911], }, // D + Vertex { position: [0.44147372, -0.2347359, 0.0], tex_coords: [1.0 - 0.9414737, 1.0 - 0.2652641], }, // E +]; + +const INDICES: &[u16] = &[ + 0, 1, 4, + 1, 2, 4, + 2, 3, 4, +]; + +#[cfg_attr(rustfmt, rustfmt_skip)] +pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4 = cgmath::Matrix4::new( + 1.0, 0.0, 0.0, 0.0, + 0.0, -1.0, 0.0, 0.0, + 0.0, 0.0, 0.5, 0.0, + 0.0, 0.0, 0.5, 1.0, +); + +struct Camera { + eye: cgmath::Point3, + target: cgmath::Point3, + up: cgmath::Vector3, + aspect: f32, + fovy: f32, + znear: f32, + zfar: f32, +} + +impl Camera { + fn build_view_projection_matrix(&self) -> cgmath::Matrix4 { + let view = cgmath::Matrix4::look_at(self.eye, self.target, self.up); + let proj = cgmath::perspective(cgmath::Deg(self.fovy), self.aspect, self.znear, self.zfar); + return proj * view; + } +} + +struct UniformStaging { + camera: Camera, + model_rotation: cgmath::Deg, +} + +impl UniformStaging { + fn new(camera: Camera) -> Self { + Self { camera, model_rotation: cgmath::Deg(0.0) } + } + fn update_uniforms(&self, uniforms: &mut Uniforms) { + uniforms.model_view_proj = + OPENGL_TO_WGPU_MATRIX + * self.camera.build_view_projection_matrix() + * cgmath::Matrix4::from_angle_z(self.model_rotation); + } +} + + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +struct Uniforms { + model_view_proj: cgmath::Matrix4, +} + +impl Uniforms { + fn new() -> Self { + use cgmath::SquareMatrix; + Self { + model_view_proj: cgmath::Matrix4::identity(), + } + } +} + +struct CameraController { + speed: f32, + is_up_pressed: bool, + is_down_pressed: bool, + is_forward_pressed: bool, + is_backward_pressed: bool, + is_left_pressed: bool, + is_right_pressed: bool, +} + +impl CameraController { + fn new(speed: f32) -> Self { + Self { + speed, + is_up_pressed: false, + is_down_pressed: false, + is_forward_pressed: false, + is_backward_pressed: false, + is_left_pressed: false, + is_right_pressed: false, + } + } + + fn process_events(&mut self, event: &WindowEvent) -> bool { + match event { + WindowEvent::KeyboardInput { + input: KeyboardInput { + state, + virtual_keycode: Some(keycode), + .. + }, + .. + } => { + let is_pressed = *state == ElementState::Pressed; + match keycode { + VirtualKeyCode::Space => { + self.is_up_pressed = is_pressed; + true + } + VirtualKeyCode::LShift => { + self.is_down_pressed = is_pressed; + true + } + VirtualKeyCode::W | VirtualKeyCode::Up => { + self.is_forward_pressed = is_pressed; + true + } + VirtualKeyCode::A | VirtualKeyCode::Left => { + self.is_left_pressed = is_pressed; + true + } + VirtualKeyCode::S | VirtualKeyCode::Down => { + self.is_backward_pressed = is_pressed; + true + } + VirtualKeyCode::D | VirtualKeyCode::Right => { + self.is_right_pressed = is_pressed; + true + } + _ => false, + } + } + _ => false, + } + } + + fn update_camera(&self, camera: &mut Camera) { + use cgmath::InnerSpace; + let forward = (camera.target - camera.eye).normalize(); + + if self.is_forward_pressed { + camera.eye += forward * self.speed; + } + if self.is_backward_pressed { + camera.eye -= forward * self.speed; + } + + let right = forward.cross(camera.up); + + if self.is_right_pressed { + camera.eye += right * self.speed; + } + if self.is_left_pressed { + camera.eye -= right * self.speed; + } + } +} + +struct State { + surface: wgpu::Surface, + device: wgpu::Device, + queue: wgpu::Queue, + sc_desc: wgpu::SwapChainDescriptor, + swap_chain: wgpu::SwapChain, + + render_pipeline: wgpu::RenderPipeline, + + vertex_buffer: wgpu::Buffer, + index_buffer: wgpu::Buffer, + num_indices: u32, + + diffuse_texture: wgpu::Texture, + diffuse_texture_view: wgpu::TextureView, + diffuse_sampler: wgpu::Sampler, + diffuse_bind_group: wgpu::BindGroup, + + camera_controller: CameraController, + uniforms: Uniforms, + uniform_staging: UniformStaging, + uniform_buffer: wgpu::Buffer, + uniform_bind_group: wgpu::BindGroup, + + hidpi_factor: f64, + size: winit::dpi::LogicalSize, +} + +impl State { + fn new(window: &Window) -> Self { + let hidpi_factor = window.hidpi_factor(); + let size = window.inner_size(); + let physical_size = size.to_physical(hidpi_factor); + + let surface = wgpu::Surface::create(window); + + let adapter = wgpu::Adapter::request(&wgpu::RequestAdapterOptions { + ..Default::default() + }).unwrap(); + + let (device, mut queue) = adapter.request_device(&wgpu::DeviceDescriptor { + extensions: wgpu::Extensions { + anisotropic_filtering: false, + }, + limits: Default::default(), + }); + + let sc_desc = wgpu::SwapChainDescriptor { + usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, + format: wgpu::TextureFormat::Bgra8UnormSrgb, + width: physical_size.width.round() as u32, + height: physical_size.height.round() as u32, + present_mode: wgpu::PresentMode::Vsync, + }; + let swap_chain = device.create_swap_chain(&surface, &sc_desc); + + let diffuse_bytes = include_bytes!("happy-tree.png"); + let diffuse_image = image::load_from_memory(diffuse_bytes).unwrap(); + let diffuse_rgba = diffuse_image.as_rgba8().unwrap(); + + use image::GenericImageView; + let dimensions = diffuse_image.dimensions(); + + let size3d = wgpu::Extent3d { + width: dimensions.0, + height: dimensions.1, + depth: 1, + }; + let diffuse_texture = device.create_texture(&wgpu::TextureDescriptor { + size: size3d, + array_layer_count: 1, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8UnormSrgb, + usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST, + }); + + let diffuse_buffer = device + .create_buffer_mapped(diffuse_rgba.len(), wgpu::BufferUsage::COPY_SRC) + .fill_from_slice(&diffuse_rgba); + + let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + todo: 0, + }); + + encoder.copy_buffer_to_texture( + wgpu::BufferCopyView { + buffer: &diffuse_buffer, + offset: 0, + row_pitch: 4 * dimensions.0, + image_height: dimensions.1, + }, + wgpu::TextureCopyView { + texture: &diffuse_texture, + mip_level: 0, + array_layer: 0, + origin: wgpu::Origin3d::ZERO, + }, + size3d, + ); + + queue.submit(&[encoder.finish()]); + + let diffuse_texture_view = diffuse_texture.create_default_view(); + let diffuse_sampler = device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + lod_min_clamp: -100.0, + lod_max_clamp: 100.0, + compare_function: wgpu::CompareFunction::Always, + }); + + let texture_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + bindings: &[ + wgpu::BindGroupLayoutBinding { + binding: 0, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::SampledTexture { + multisampled: false, + dimension: wgpu::TextureViewDimension::D2, + }, + }, + wgpu::BindGroupLayoutBinding { + binding: 1, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Sampler, + }, + ], + }); + + let diffuse_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &texture_bind_group_layout, + bindings: &[ + wgpu::Binding { + binding: 0, + resource: wgpu::BindingResource::TextureView(&diffuse_texture_view), + }, + wgpu::Binding { + binding: 1, + resource: wgpu::BindingResource::Sampler(&diffuse_sampler), + } + ], + }); + + let camera = Camera { + eye: (0.0, 1.0, -2.0).into(), + target: (0.0, 0.0, 0.0).into(), + up: cgmath::Vector3::unit_y(), + aspect: sc_desc.width as f32 / sc_desc.height as f32, + fovy: 45.0, + znear: 0.1, + zfar: 100.0, + }; + let camera_controller = CameraController::new(0.2); + + let mut uniforms = Uniforms::new(); + let uniform_staging = UniformStaging::new(camera); + uniform_staging.update_uniforms(&mut uniforms); + + let uniform_buffer = device + .create_buffer_mapped(1, wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST) + .fill_from_slice(&[uniforms]); + + let uniform_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + bindings: &[ + wgpu::BindGroupLayoutBinding { + binding: 0, + visibility: wgpu::ShaderStage::VERTEX, + ty: wgpu::BindingType::UniformBuffer { + dynamic: false, + }, + } + ] + }); + + let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &uniform_bind_group_layout, + bindings: &[ + wgpu::Binding { + binding: 0, + resource: wgpu::BindingResource::Buffer { + buffer: &uniform_buffer, + range: 0..std::mem::size_of_val(&uniforms) as wgpu::BufferAddress, + } + } + ], + }); + + let vs_src = include_str!("shader.vert"); + let fs_src = include_str!("shader.frag"); + let vs_spirv = glsl_to_spirv::compile(vs_src, glsl_to_spirv::ShaderType::Vertex).unwrap(); + let fs_spirv = glsl_to_spirv::compile(fs_src, glsl_to_spirv::ShaderType::Fragment).unwrap(); + let vs_data = wgpu::read_spirv(vs_spirv).unwrap(); + let fs_data = wgpu::read_spirv(fs_spirv).unwrap(); + let vs_module = device.create_shader_module(&vs_data); + let fs_module = device.create_shader_module(&fs_data); + + let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + bind_group_layouts: &[&texture_bind_group_layout, &uniform_bind_group_layout], + }); + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + layout: &render_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: sc_desc.format, + color_blend: wgpu::BlendDescriptor::REPLACE, + alpha_blend: wgpu::BlendDescriptor::REPLACE, + write_mask: wgpu::ColorWrite::ALL, + }, + ], + depth_stencil_state: None, + index_format: wgpu::IndexFormat::Uint16, + vertex_buffers: &[ + Vertex::desc(), + ], + sample_count: 1, + sample_mask: !0, + alpha_to_coverage_enabled: false, + }); + + let vertex_buffer = device + .create_buffer_mapped(VERTICES.len(), wgpu::BufferUsage::VERTEX) + .fill_from_slice(VERTICES); + let index_buffer = device + .create_buffer_mapped(INDICES.len(), wgpu::BufferUsage::INDEX) + .fill_from_slice(INDICES); + let num_indices = INDICES.len() as u32; + + Self { + surface, + device, + queue, + sc_desc, + swap_chain, + render_pipeline, + vertex_buffer, + index_buffer, + num_indices, + diffuse_texture, + diffuse_texture_view, + diffuse_sampler, + diffuse_bind_group, + camera_controller, + uniform_staging, + uniform_buffer, + uniform_bind_group, + uniforms, + hidpi_factor, + size, + } + } + + fn update_hidpi_and_resize(&mut self, new_hidpi_factor: f64) { + self.hidpi_factor = new_hidpi_factor; + self.resize(self.size); + } + + fn resize(&mut self, new_size: winit::dpi::LogicalSize) { + let physical_size = new_size.to_physical(self.hidpi_factor); + self.size = new_size; + self.sc_desc.width = physical_size.width.round() as u32; + self.sc_desc.height = physical_size.height.round() as u32; + self.swap_chain = self.device.create_swap_chain(&self.surface, &self.sc_desc); + + self.uniform_staging.camera.aspect = self.sc_desc.width as f32 / self.sc_desc.height as f32; + } + + fn input(&mut self, event: &WindowEvent) -> bool { + self.camera_controller.process_events(event) + } + + fn update(&mut self) { + self.camera_controller.update_camera(&mut self.uniform_staging.camera); + self.uniform_staging.model_rotation += cgmath::Deg(2.0); + self.uniform_staging.update_uniforms(&mut self.uniforms); + + let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + todo: 0, + }); + + let staging_buffer = self.device + .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC) + .fill_from_slice(&[self.uniforms]); + + encoder.copy_buffer_to_buffer(&staging_buffer, 0, &self.uniform_buffer, 0, std::mem::size_of::() as wgpu::BufferAddress); + + self.queue.submit(&[encoder.finish()]); + } + + fn render(&mut self) { + let frame = self.swap_chain.get_next_texture(); + + let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + todo: 0, + }); + + { + let mut render_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.1, + g: 0.2, + b: 0.3, + a: 1.0, + }, + } + ], + depth_stencil_attachment: None, + }); + + render_pass.set_pipeline(&self.render_pipeline); + render_pass.set_bind_group(0, &self.diffuse_bind_group, &[]); + render_pass.set_bind_group(1, &self.uniform_bind_group, &[]); + render_pass.set_vertex_buffers(0, &[(&self.vertex_buffer, 0)]); + render_pass.set_index_buffer(&self.index_buffer, 0); + render_pass.draw_indexed(0..self.num_indices, 0, 0..1); + } + + self.queue.submit(&[ + encoder.finish() + ]); + } +} + +fn main() { + let event_loop = EventLoop::new(); + let window = WindowBuilder::new() + .build(&event_loop) + .unwrap(); + + let mut state = State::new(&window); + + let mut old_time = std::time::Instant::now(); + const MSPT: std::time::Duration = std::time::Duration::from_millis(20); + + event_loop.run(move |event, _, control_flow| { + match event { + Event::WindowEvent { + ref event, + window_id, + } if window_id == window.id() => if state.input(event) { + () + } else { + match event { + WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + WindowEvent::KeyboardInput { + input, + .. + } => { + match input { + KeyboardInput { + state: ElementState::Pressed, + virtual_keycode: Some(VirtualKeyCode::Escape), + .. + } => *control_flow = ControlFlow::Exit, + _ => (), + } + } + WindowEvent::Resized(logical_size) => { + state.resize(*logical_size); + () + } + WindowEvent::HiDpiFactorChanged(new_hidpi_factor) => { + state.update_hidpi_and_resize(*new_hidpi_factor); + () + } + _ => (), + } + } + Event::EventsCleared => { + state.update(); + state.render(); + + let new_time = std::time::Instant::now(); + let delta_time = new_time - old_time; + *control_flow = if delta_time > MSPT { + ControlFlow::Poll + } else { + ControlFlow::WaitUntil(old_time + MSPT) + }; + old_time = new_time; + } + _ => (), + } + }); +} \ No newline at end of file diff --git a/code/beginner/tutorial7-instancing/src/happy-tree.png b/code/beginner/tutorial7-instancing/src/happy-tree.png new file mode 100644 index 00000000..fc86db34 Binary files /dev/null and b/code/beginner/tutorial7-instancing/src/happy-tree.png differ diff --git a/code/beginner/tutorial7-instancing/src/main.rs b/code/beginner/tutorial7-instancing/src/main.rs new file mode 100644 index 00000000..d61ce7cb --- /dev/null +++ b/code/beginner/tutorial7-instancing/src/main.rs @@ -0,0 +1,624 @@ +use winit::{ + event::*, + event_loop::{EventLoop, ControlFlow}, + window::{Window, WindowBuilder}, +}; +use cgmath::prelude::*; + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +struct Vertex { + position: [f32; 3], + tex_coords: [f32; 2], +} + +impl Vertex { + fn desc<'a>() -> wgpu::VertexBufferDescriptor<'a> { + use std::mem; + wgpu::VertexBufferDescriptor { + stride: mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::InputStepMode::Vertex, + attributes: &[ + wgpu::VertexAttributeDescriptor { + offset: 0, + shader_location: 0, + format: wgpu::VertexFormat::Float3, + }, + wgpu::VertexAttributeDescriptor { + offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress, + shader_location: 1, + format: wgpu::VertexFormat::Float2, + }, + ] + } + } +} + +const VERTICES: &[Vertex] = &[ + Vertex { position: [-0.0868241, -0.49240386, 0.0], tex_coords: [1.0 - 0.4131759, 1.0 - 0.00759614], }, // A + Vertex { position: [-0.49513406, -0.06958647, 0.0], tex_coords: [1.0 - 0.0048659444, 1.0 - 0.43041354], }, // B + Vertex { position: [-0.21918549, 0.44939706, 0.0], tex_coords: [1.0 - 0.28081453, 1.0 - 0.949397057], }, // C + Vertex { position: [0.35966998, 0.3473291, 0.0], tex_coords: [1.0 - 0.85967, 1.0 - 0.84732911], }, // D + Vertex { position: [0.44147372, -0.2347359, 0.0], tex_coords: [1.0 - 0.9414737, 1.0 - 0.2652641], }, // E +]; + +const INDICES: &[u16] = &[ + 0, 1, 4, + 1, 2, 4, + 2, 3, 4, +]; + +#[cfg_attr(rustfmt, rustfmt_skip)] +pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4 = cgmath::Matrix4::new( + 1.0, 0.0, 0.0, 0.0, + 0.0, -1.0, 0.0, 0.0, + 0.0, 0.0, 0.5, 0.0, + 0.0, 0.0, 0.5, 1.0, +); + +const NUM_INSTANCES_PER_ROW: u32 = 10; +const NUM_INSTANCES: u32 = NUM_INSTANCES_PER_ROW * NUM_INSTANCES_PER_ROW; +const INSTANCE_DISPLACEMENT: cgmath::Vector3 = cgmath::Vector3::new(NUM_INSTANCES_PER_ROW as f32 * 0.5, 0.0, NUM_INSTANCES_PER_ROW as f32 * 0.5); + + +struct Camera { + eye: cgmath::Point3, + target: cgmath::Point3, + up: cgmath::Vector3, + aspect: f32, + fovy: f32, + znear: f32, + zfar: f32, +} + +impl Camera { + fn build_view_projection_matrix(&self) -> cgmath::Matrix4 { + let view = cgmath::Matrix4::look_at(self.eye, self.target, self.up); + let proj = cgmath::perspective(cgmath::Deg(self.fovy), self.aspect, self.znear, self.zfar); + return proj * view; + } +} + +#[repr(C)] +#[derive(Copy, Clone)] +struct Uniforms { + view_proj: cgmath::Matrix4, + model: [cgmath::Matrix4; NUM_INSTANCES as usize], +} + +impl Uniforms { + fn new() -> Self { + Self { + view_proj: cgmath::Matrix4::identity(), + model: [cgmath::Matrix4::identity(); NUM_INSTANCES as usize], + } + } + + fn update_view_proj(&mut self, camera: &Camera) { + self.view_proj = OPENGL_TO_WGPU_MATRIX * camera.build_view_projection_matrix(); + } +} + +struct CameraController { + speed: f32, + is_up_pressed: bool, + is_down_pressed: bool, + is_forward_pressed: bool, + is_backward_pressed: bool, + is_left_pressed: bool, + is_right_pressed: bool, +} + +impl CameraController { + fn new(speed: f32) -> Self { + Self { + speed, + is_up_pressed: false, + is_down_pressed: false, + is_forward_pressed: false, + is_backward_pressed: false, + is_left_pressed: false, + is_right_pressed: false, + } + } + + fn process_events(&mut self, event: &WindowEvent) -> bool { + match event { + WindowEvent::KeyboardInput { + input: KeyboardInput { + state, + virtual_keycode: Some(keycode), + .. + }, + .. + } => { + let is_pressed = *state == ElementState::Pressed; + match keycode { + VirtualKeyCode::Space => { + self.is_up_pressed = is_pressed; + true + } + VirtualKeyCode::LShift => { + self.is_down_pressed = is_pressed; + true + } + VirtualKeyCode::W | VirtualKeyCode::Up => { + self.is_forward_pressed = is_pressed; + true + } + VirtualKeyCode::A | VirtualKeyCode::Left => { + self.is_left_pressed = is_pressed; + true + } + VirtualKeyCode::S | VirtualKeyCode::Down => { + self.is_backward_pressed = is_pressed; + true + } + VirtualKeyCode::D | VirtualKeyCode::Right => { + self.is_right_pressed = is_pressed; + true + } + _ => false, + } + } + _ => false, + } + } + + fn update_camera(&self, camera: &mut Camera) { + let forward = (camera.target - camera.eye).normalize(); + + if self.is_forward_pressed { + camera.eye += forward * self.speed; + } + if self.is_backward_pressed { + camera.eye -= forward * self.speed; + } + + let right = forward.cross(camera.up); + + if self.is_right_pressed { + camera.eye += right * self.speed; + } + if self.is_left_pressed { + camera.eye -= right * self.speed; + } + } +} + +struct Instance { + position: cgmath::Vector3, + rotation: cgmath::Quaternion, +} + +impl Instance { + fn to_matrix(&self) -> cgmath::Matrix4 { + cgmath::Matrix4::from_translation(self.position) * cgmath::Matrix4::from(self.rotation) + } +} + +struct State { + surface: wgpu::Surface, + device: wgpu::Device, + queue: wgpu::Queue, + sc_desc: wgpu::SwapChainDescriptor, + swap_chain: wgpu::SwapChain, + + render_pipeline: wgpu::RenderPipeline, + + vertex_buffer: wgpu::Buffer, + index_buffer: wgpu::Buffer, + num_indices: u32, + + diffuse_texture: wgpu::Texture, + diffuse_texture_view: wgpu::TextureView, + diffuse_sampler: wgpu::Sampler, + diffuse_bind_group: wgpu::BindGroup, + + camera: Camera, + camera_controller: CameraController, + uniforms: Uniforms, + uniform_buffer: wgpu::Buffer, + uniform_bind_group: wgpu::BindGroup, + + hidpi_factor: f64, + size: winit::dpi::LogicalSize, + + instances: Vec, +} + +impl State { + fn new(window: &Window) -> Self { + let hidpi_factor = window.hidpi_factor(); + let size = window.inner_size(); + let physical_size = size.to_physical(hidpi_factor); + + let surface = wgpu::Surface::create(window); + + let adapter = wgpu::Adapter::request(&wgpu::RequestAdapterOptions { + ..Default::default() + }).unwrap(); + + let (device, mut queue) = adapter.request_device(&wgpu::DeviceDescriptor { + extensions: wgpu::Extensions { + anisotropic_filtering: false, + }, + limits: Default::default(), + }); + + let sc_desc = wgpu::SwapChainDescriptor { + usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, + format: wgpu::TextureFormat::Bgra8UnormSrgb, + width: physical_size.width.round() as u32, + height: physical_size.height.round() as u32, + present_mode: wgpu::PresentMode::Vsync, + }; + let swap_chain = device.create_swap_chain(&surface, &sc_desc); + + let diffuse_bytes = include_bytes!("happy-tree.png"); + let diffuse_image = image::load_from_memory(diffuse_bytes).unwrap(); + let diffuse_rgba = diffuse_image.as_rgba8().unwrap(); + + use image::GenericImageView; + let dimensions = diffuse_image.dimensions(); + + let size3d = wgpu::Extent3d { + width: dimensions.0, + height: dimensions.1, + depth: 1, + }; + let diffuse_texture = device.create_texture(&wgpu::TextureDescriptor { + size: size3d, + array_layer_count: 1, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8UnormSrgb, + usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST, + }); + + let diffuse_buffer = device + .create_buffer_mapped(diffuse_rgba.len(), wgpu::BufferUsage::COPY_SRC) + .fill_from_slice(&diffuse_rgba); + + let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + todo: 0, + }); + + encoder.copy_buffer_to_texture( + wgpu::BufferCopyView { + buffer: &diffuse_buffer, + offset: 0, + row_pitch: 4 * dimensions.0, + image_height: dimensions.1, + }, + wgpu::TextureCopyView { + texture: &diffuse_texture, + mip_level: 0, + array_layer: 0, + origin: wgpu::Origin3d::ZERO, + }, + size3d, + ); + + queue.submit(&[encoder.finish()]); + + let diffuse_texture_view = diffuse_texture.create_default_view(); + let diffuse_sampler = device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + lod_min_clamp: -100.0, + lod_max_clamp: 100.0, + compare_function: wgpu::CompareFunction::Always, + }); + + let texture_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + bindings: &[ + wgpu::BindGroupLayoutBinding { + binding: 0, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::SampledTexture { + multisampled: false, + dimension: wgpu::TextureViewDimension::D2, + }, + }, + wgpu::BindGroupLayoutBinding { + binding: 1, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Sampler, + }, + ], + }); + + let diffuse_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &texture_bind_group_layout, + bindings: &[ + wgpu::Binding { + binding: 0, + resource: wgpu::BindingResource::TextureView(&diffuse_texture_view), + }, + wgpu::Binding { + binding: 1, + resource: wgpu::BindingResource::Sampler(&diffuse_sampler), + } + ], + }); + + let camera = Camera { + eye: (0.0, 5.0, -10.0).into(), + target: (0.0, 0.0, 0.0).into(), + up: cgmath::Vector3::unit_y(), + aspect: sc_desc.width as f32 / sc_desc.height as f32, + fovy: 45.0, + znear: 0.1, + zfar: 100.0, + }; + let camera_controller = CameraController::new(0.2); + + let mut uniforms = Uniforms::new(); + uniforms.update_view_proj(&camera); + + let uniform_buffer = device + .create_buffer_mapped(1, wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST) + .fill_from_slice(&[uniforms]); + + let uniform_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + bindings: &[ + wgpu::BindGroupLayoutBinding { + binding: 0, + visibility: wgpu::ShaderStage::VERTEX, + ty: wgpu::BindingType::UniformBuffer { + dynamic: false, + }, + } + ] + }); + + let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &uniform_bind_group_layout, + bindings: &[ + wgpu::Binding { + binding: 0, + resource: wgpu::BindingResource::Buffer { + buffer: &uniform_buffer, + range: 0..std::mem::size_of_val(&uniforms) as wgpu::BufferAddress, + } + } + ], + }); + + let vs_src = include_str!("shader.vert"); + let fs_src = include_str!("shader.frag"); + let vs_spirv = glsl_to_spirv::compile(vs_src, glsl_to_spirv::ShaderType::Vertex).unwrap(); + let fs_spirv = glsl_to_spirv::compile(fs_src, glsl_to_spirv::ShaderType::Fragment).unwrap(); + let vs_data = wgpu::read_spirv(vs_spirv).unwrap(); + let fs_data = wgpu::read_spirv(fs_spirv).unwrap(); + let vs_module = device.create_shader_module(&vs_data); + let fs_module = device.create_shader_module(&fs_data); + + let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + bind_group_layouts: &[&texture_bind_group_layout, &uniform_bind_group_layout], + }); + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + layout: &render_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: sc_desc.format, + color_blend: wgpu::BlendDescriptor::REPLACE, + alpha_blend: wgpu::BlendDescriptor::REPLACE, + write_mask: wgpu::ColorWrite::ALL, + }, + ], + depth_stencil_state: None, + index_format: wgpu::IndexFormat::Uint16, + vertex_buffers: &[ + Vertex::desc(), + ], + sample_count: 1, + sample_mask: !0, + alpha_to_coverage_enabled: false, + }); + + let vertex_buffer = device + .create_buffer_mapped(VERTICES.len(), wgpu::BufferUsage::VERTEX) + .fill_from_slice(VERTICES); + let index_buffer = device + .create_buffer_mapped(INDICES.len(), wgpu::BufferUsage::INDEX) + .fill_from_slice(INDICES); + let num_indices = INDICES.len() as u32; + + let instances = (0..NUM_INSTANCES_PER_ROW).flat_map(|z| { + (0..NUM_INSTANCES_PER_ROW).map(move |x| { + let position = cgmath::Vector3 { x: x as f32, y: 0.0, z: z as f32 } - INSTANCE_DISPLACEMENT; + + let rotation = if position.is_zero() { + // this is needed so an object at (0, 0, 0) won't get scaled to zero + // as Quaternions can effect scale if they're not create correctly + cgmath::Quaternion::from_axis_angle(cgmath::Vector3::unit_z(), cgmath::Deg(0.0)) + } else { + cgmath::Quaternion::from_axis_angle(position.clone().normalize(), cgmath::Deg(45.0)) + }; + + Instance { + position, rotation, + } + }) + }).collect(); + + Self { + surface, + device, + queue, + sc_desc, + swap_chain, + render_pipeline, + vertex_buffer, + index_buffer, + num_indices, + diffuse_texture, + diffuse_texture_view, + diffuse_sampler, + diffuse_bind_group, + camera, + camera_controller, + uniform_buffer, + uniform_bind_group, + uniforms, + hidpi_factor, + size, + instances, + } + } + + fn update_hidpi_and_resize(&mut self, new_hidpi_factor: f64) { + self.hidpi_factor = new_hidpi_factor; + self.resize(self.size); + } + + fn resize(&mut self, new_size: winit::dpi::LogicalSize) { + let physical_size = new_size.to_physical(self.hidpi_factor); + self.size = new_size; + self.sc_desc.width = physical_size.width.round() as u32; + self.sc_desc.height = physical_size.height.round() as u32; + self.swap_chain = self.device.create_swap_chain(&self.surface, &self.sc_desc); + + self.camera.aspect = self.sc_desc.width as f32 / self.sc_desc.height as f32; + } + + fn input(&mut self, event: &WindowEvent) -> bool { + self.camera_controller.process_events(event) + } + + fn update(&mut self) { + self.camera_controller.update_camera(&mut self.camera); + self.uniforms.update_view_proj(&self.camera); + + for (i, instance) in self.instances.iter().enumerate() { + self.uniforms.model[i] = instance.to_matrix(); + } + + let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + todo: 0, + }); + + let staging_buffer = self.device + .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC) + .fill_from_slice(&[self.uniforms]); + + encoder.copy_buffer_to_buffer(&staging_buffer, 0, &self.uniform_buffer, 0, std::mem::size_of::() as wgpu::BufferAddress); + + self.queue.submit(&[encoder.finish()]); + } + + fn render(&mut self) { + let frame = self.swap_chain.get_next_texture(); + + let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + todo: 0, + }); + + { + let mut render_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.1, + g: 0.2, + b: 0.3, + a: 1.0, + }, + } + ], + depth_stencil_attachment: None, + }); + + render_pass.set_pipeline(&self.render_pipeline); + render_pass.set_bind_group(0, &self.diffuse_bind_group, &[]); + render_pass.set_bind_group(1, &self.uniform_bind_group, &[]); + render_pass.set_vertex_buffers(0, &[(&self.vertex_buffer, 0)]); + render_pass.set_index_buffer(&self.index_buffer, 0); + render_pass.draw_indexed(0..self.num_indices, 0, 0..NUM_INSTANCES); + } + + self.queue.submit(&[ + encoder.finish() + ]); + } +} + +fn main() { + let event_loop = EventLoop::new(); + let window = WindowBuilder::new() + .build(&event_loop) + .unwrap(); + + let mut state = State::new(&window); + + event_loop.run(move |event, _, control_flow| { + match event { + Event::WindowEvent { + ref event, + window_id, + } if window_id == window.id() => if state.input(event) { + *control_flow = ControlFlow::Wait; + } else { + match event { + WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + WindowEvent::KeyboardInput { + input, + .. + } => { + match input { + KeyboardInput { + state: ElementState::Pressed, + virtual_keycode: Some(VirtualKeyCode::Escape), + .. + } => *control_flow = ControlFlow::Exit, + _ => *control_flow = ControlFlow::Wait, + } + } + WindowEvent::Resized(logical_size) => { + state.resize(*logical_size); + *control_flow = ControlFlow::Wait; + } + WindowEvent::HiDpiFactorChanged(new_hidpi_factor) => { + state.update_hidpi_and_resize(*new_hidpi_factor); + *control_flow = ControlFlow::Wait; + } + _ => *control_flow = ControlFlow::Wait, + } + } + Event::EventsCleared => { + state.update(); + state.render(); + *control_flow = ControlFlow::Wait; + } + _ => *control_flow = ControlFlow::Wait, + } + }); +} \ No newline at end of file diff --git a/code/beginner/tutorial7-instancing/src/naive.rs b/code/beginner/tutorial7-instancing/src/naive.rs new file mode 100644 index 00000000..20a420c6 --- /dev/null +++ b/code/beginner/tutorial7-instancing/src/naive.rs @@ -0,0 +1,644 @@ +use winit::{ + event::*, + event_loop::{EventLoop, ControlFlow}, + window::{Window, WindowBuilder}, +}; +use cgmath::prelude::*; + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +struct Vertex { + position: [f32; 3], + tex_coords: [f32; 2], +} + +impl Vertex { + fn desc<'a>() -> wgpu::VertexBufferDescriptor<'a> { + use std::mem; + wgpu::VertexBufferDescriptor { + stride: mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::InputStepMode::Vertex, + attributes: &[ + wgpu::VertexAttributeDescriptor { + offset: 0, + shader_location: 0, + format: wgpu::VertexFormat::Float3, + }, + wgpu::VertexAttributeDescriptor { + offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress, + shader_location: 1, + format: wgpu::VertexFormat::Float2, + }, + ] + } + } +} + +const VERTICES: &[Vertex] = &[ + Vertex { position: [-0.0868241, -0.49240386, 0.0], tex_coords: [1.0 - 0.4131759, 1.0 - 0.00759614], }, // A + Vertex { position: [-0.49513406, -0.06958647, 0.0], tex_coords: [1.0 - 0.0048659444, 1.0 - 0.43041354], }, // B + Vertex { position: [-0.21918549, 0.44939706, 0.0], tex_coords: [1.0 - 0.28081453, 1.0 - 0.949397057], }, // C + Vertex { position: [0.35966998, 0.3473291, 0.0], tex_coords: [1.0 - 0.85967, 1.0 - 0.84732911], }, // D + Vertex { position: [0.44147372, -0.2347359, 0.0], tex_coords: [1.0 - 0.9414737, 1.0 - 0.2652641], }, // E +]; + +const INDICES: &[u16] = &[ + 0, 1, 4, + 1, 2, 4, + 2, 3, 4, +]; + +#[cfg_attr(rustfmt, rustfmt_skip)] +pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4 = cgmath::Matrix4::new( + 1.0, 0.0, 0.0, 0.0, + 0.0, -1.0, 0.0, 0.0, + 0.0, 0.0, 0.5, 0.0, + 0.0, 0.0, 0.5, 1.0, +); + +struct Camera { + eye: cgmath::Point3, + target: cgmath::Point3, + up: cgmath::Vector3, + aspect: f32, + fovy: f32, + znear: f32, + zfar: f32, +} + +impl Camera { + fn build_view_projection_matrix(&self) -> cgmath::Matrix4 { + let view = cgmath::Matrix4::look_at(self.eye, self.target, self.up); + let proj = cgmath::perspective(cgmath::Deg(self.fovy), self.aspect, self.znear, self.zfar); + return proj * view; + } +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +struct Uniforms { + view_proj: cgmath::Matrix4, + model: cgmath::Matrix4, +} + +impl Uniforms { + fn new() -> Self { + Self { + view_proj: cgmath::Matrix4::identity(), + model: cgmath::Matrix4::identity(), + } + } + + fn update_view_proj(&mut self, camera: &Camera) { + self.view_proj = OPENGL_TO_WGPU_MATRIX * camera.build_view_projection_matrix(); + } +} + +struct CameraController { + speed: f32, + is_up_pressed: bool, + is_down_pressed: bool, + is_forward_pressed: bool, + is_backward_pressed: bool, + is_left_pressed: bool, + is_right_pressed: bool, +} + +impl CameraController { + fn new(speed: f32) -> Self { + Self { + speed, + is_up_pressed: false, + is_down_pressed: false, + is_forward_pressed: false, + is_backward_pressed: false, + is_left_pressed: false, + is_right_pressed: false, + } + } + + fn process_events(&mut self, event: &WindowEvent) -> bool { + match event { + WindowEvent::KeyboardInput { + input: KeyboardInput { + state, + virtual_keycode: Some(keycode), + .. + }, + .. + } => { + let is_pressed = *state == ElementState::Pressed; + match keycode { + VirtualKeyCode::Space => { + self.is_up_pressed = is_pressed; + true + } + VirtualKeyCode::LShift => { + self.is_down_pressed = is_pressed; + true + } + VirtualKeyCode::W | VirtualKeyCode::Up => { + self.is_forward_pressed = is_pressed; + true + } + VirtualKeyCode::A | VirtualKeyCode::Left => { + self.is_left_pressed = is_pressed; + true + } + VirtualKeyCode::S | VirtualKeyCode::Down => { + self.is_backward_pressed = is_pressed; + true + } + VirtualKeyCode::D | VirtualKeyCode::Right => { + self.is_right_pressed = is_pressed; + true + } + _ => false, + } + } + _ => false, + } + } + + fn update_camera(&self, camera: &mut Camera) { + let forward = (camera.target - camera.eye).normalize(); + + if self.is_forward_pressed { + camera.eye += forward * self.speed; + } + if self.is_backward_pressed { + camera.eye -= forward * self.speed; + } + + let right = forward.cross(camera.up); + + if self.is_right_pressed { + camera.eye += right * self.speed; + } + if self.is_left_pressed { + camera.eye -= right * self.speed; + } + } +} + +struct Instance { + position: cgmath::Vector3, + rotation: cgmath::Quaternion, +} + +impl Instance { + fn to_matrix(&self) -> cgmath::Matrix4 { + cgmath::Matrix4::from_translation(self.position) * cgmath::Matrix4::from(self.rotation) + } +} + +struct State { + surface: wgpu::Surface, + device: wgpu::Device, + queue: wgpu::Queue, + sc_desc: wgpu::SwapChainDescriptor, + swap_chain: wgpu::SwapChain, + + render_pipeline: wgpu::RenderPipeline, + + vertex_buffer: wgpu::Buffer, + index_buffer: wgpu::Buffer, + num_indices: u32, + + diffuse_texture: wgpu::Texture, + diffuse_texture_view: wgpu::TextureView, + diffuse_sampler: wgpu::Sampler, + diffuse_bind_group: wgpu::BindGroup, + + camera: Camera, + camera_controller: CameraController, + uniforms: Uniforms, + uniform_buffer: wgpu::Buffer, + uniform_bind_group: wgpu::BindGroup, + + hidpi_factor: f64, + size: winit::dpi::LogicalSize, + + instances: Vec, +} + +impl State { + fn new(window: &Window) -> Self { + let hidpi_factor = window.hidpi_factor(); + let size = window.inner_size(); + let physical_size = size.to_physical(hidpi_factor); + + let surface = wgpu::Surface::create(window); + + let adapter = wgpu::Adapter::request(&wgpu::RequestAdapterOptions { + ..Default::default() + }).unwrap(); + + let (device, mut queue) = adapter.request_device(&wgpu::DeviceDescriptor { + extensions: wgpu::Extensions { + anisotropic_filtering: false, + }, + limits: Default::default(), + }); + + let sc_desc = wgpu::SwapChainDescriptor { + usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, + format: wgpu::TextureFormat::Bgra8UnormSrgb, + width: physical_size.width.round() as u32, + height: physical_size.height.round() as u32, + present_mode: wgpu::PresentMode::Vsync, + }; + let swap_chain = device.create_swap_chain(&surface, &sc_desc); + + let diffuse_bytes = include_bytes!("happy-tree.png"); + let diffuse_image = image::load_from_memory(diffuse_bytes).unwrap(); + let diffuse_rgba = diffuse_image.as_rgba8().unwrap(); + + use image::GenericImageView; + let dimensions = diffuse_image.dimensions(); + + let size3d = wgpu::Extent3d { + width: dimensions.0, + height: dimensions.1, + depth: 1, + }; + let diffuse_texture = device.create_texture(&wgpu::TextureDescriptor { + size: size3d, + array_layer_count: 1, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8UnormSrgb, + usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST, + }); + + let diffuse_buffer = device + .create_buffer_mapped(diffuse_rgba.len(), wgpu::BufferUsage::COPY_SRC) + .fill_from_slice(&diffuse_rgba); + + let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + todo: 0, + }); + + encoder.copy_buffer_to_texture( + wgpu::BufferCopyView { + buffer: &diffuse_buffer, + offset: 0, + row_pitch: 4 * dimensions.0, + image_height: dimensions.1, + }, + wgpu::TextureCopyView { + texture: &diffuse_texture, + mip_level: 0, + array_layer: 0, + origin: wgpu::Origin3d::ZERO, + }, + size3d, + ); + + queue.submit(&[encoder.finish()]); + + let diffuse_texture_view = diffuse_texture.create_default_view(); + let diffuse_sampler = device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + lod_min_clamp: -100.0, + lod_max_clamp: 100.0, + compare_function: wgpu::CompareFunction::Always, + }); + + let texture_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + bindings: &[ + wgpu::BindGroupLayoutBinding { + binding: 0, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::SampledTexture { + multisampled: false, + dimension: wgpu::TextureViewDimension::D2, + }, + }, + wgpu::BindGroupLayoutBinding { + binding: 1, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Sampler, + }, + ], + }); + + let diffuse_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &texture_bind_group_layout, + bindings: &[ + wgpu::Binding { + binding: 0, + resource: wgpu::BindingResource::TextureView(&diffuse_texture_view), + }, + wgpu::Binding { + binding: 1, + resource: wgpu::BindingResource::Sampler(&diffuse_sampler), + } + ], + }); + + let camera = Camera { + eye: (0.0, 5.0, -10.0).into(), + target: (0.0, 0.0, 0.0).into(), + up: cgmath::Vector3::unit_y(), + aspect: sc_desc.width as f32 / sc_desc.height as f32, + fovy: 45.0, + znear: 0.1, + zfar: 100.0, + }; + let camera_controller = CameraController::new(0.2); + + let mut uniforms = Uniforms::new(); + uniforms.update_view_proj(&camera); + + let uniform_buffer = device + .create_buffer_mapped(1, wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST) + .fill_from_slice(&[uniforms]); + + let uniform_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + bindings: &[ + wgpu::BindGroupLayoutBinding { + binding: 0, + visibility: wgpu::ShaderStage::VERTEX, + ty: wgpu::BindingType::UniformBuffer { + dynamic: false, + }, + } + ] + }); + + let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &uniform_bind_group_layout, + bindings: &[ + wgpu::Binding { + binding: 0, + resource: wgpu::BindingResource::Buffer { + buffer: &uniform_buffer, + range: 0..std::mem::size_of_val(&uniforms) as wgpu::BufferAddress, + } + } + ], + }); + + let vs_src = include_str!("naive.vert"); + let fs_src = include_str!("shader.frag"); + let vs_spirv = glsl_to_spirv::compile(vs_src, glsl_to_spirv::ShaderType::Vertex).unwrap(); + let fs_spirv = glsl_to_spirv::compile(fs_src, glsl_to_spirv::ShaderType::Fragment).unwrap(); + let vs_data = wgpu::read_spirv(vs_spirv).unwrap(); + let fs_data = wgpu::read_spirv(fs_spirv).unwrap(); + let vs_module = device.create_shader_module(&vs_data); + let fs_module = device.create_shader_module(&fs_data); + + let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + bind_group_layouts: &[&texture_bind_group_layout, &uniform_bind_group_layout], + }); + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + layout: &render_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: sc_desc.format, + color_blend: wgpu::BlendDescriptor::REPLACE, + alpha_blend: wgpu::BlendDescriptor::REPLACE, + write_mask: wgpu::ColorWrite::ALL, + }, + ], + depth_stencil_state: None, + index_format: wgpu::IndexFormat::Uint16, + vertex_buffers: &[ + Vertex::desc(), + ], + sample_count: 1, + sample_mask: !0, + alpha_to_coverage_enabled: false, + }); + + let vertex_buffer = device + .create_buffer_mapped(VERTICES.len(), wgpu::BufferUsage::VERTEX) + .fill_from_slice(VERTICES); + let index_buffer = device + .create_buffer_mapped(INDICES.len(), wgpu::BufferUsage::INDEX) + .fill_from_slice(INDICES); + let num_indices = INDICES.len() as u32; + + const NUM_INSTANCES_PER_ROW: u32 = 10; + const INSTANCE_DISPLACEMENT: cgmath::Vector3 = cgmath::Vector3::new(NUM_INSTANCES_PER_ROW as f32 * 0.5, 0.0, NUM_INSTANCES_PER_ROW as f32 * 0.5); + + let instances = (0..NUM_INSTANCES_PER_ROW).flat_map(|z| { + (0..NUM_INSTANCES_PER_ROW).map(move |x| { + let position = cgmath::Vector3 { x: x as f32, y: 0.0, z: z as f32 } - INSTANCE_DISPLACEMENT; + + let rotation = if position.is_zero() { + // this is needed so an object at (0, 0, 0) won't get scaled to zero + // as Quaternions can effect scale if they're not create correctly + cgmath::Quaternion::from_axis_angle(cgmath::Vector3::unit_z(), cgmath::Deg(0.0)) + } else { + cgmath::Quaternion::from_axis_angle(position.clone().normalize(), cgmath::Deg(45.0)) + }; + + Instance { + position, rotation, + } + }) + }).collect(); + + Self { + surface, + device, + queue, + sc_desc, + swap_chain, + render_pipeline, + vertex_buffer, + index_buffer, + num_indices, + diffuse_texture, + diffuse_texture_view, + diffuse_sampler, + diffuse_bind_group, + camera, + camera_controller, + uniform_buffer, + uniform_bind_group, + uniforms, + hidpi_factor, + size, + instances, + } + } + + fn update_hidpi_and_resize(&mut self, new_hidpi_factor: f64) { + self.hidpi_factor = new_hidpi_factor; + self.resize(self.size); + } + + fn resize(&mut self, new_size: winit::dpi::LogicalSize) { + let physical_size = new_size.to_physical(self.hidpi_factor); + self.size = new_size; + self.sc_desc.width = physical_size.width.round() as u32; + self.sc_desc.height = physical_size.height.round() as u32; + self.swap_chain = self.device.create_swap_chain(&self.surface, &self.sc_desc); + + self.camera.aspect = self.sc_desc.width as f32 / self.sc_desc.height as f32; + } + + fn input(&mut self, event: &WindowEvent) -> bool { + self.camera_controller.process_events(event) + } + + fn update(&mut self) { + self.camera_controller.update_camera(&mut self.camera); + self.uniforms.update_view_proj(&self.camera); + + let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + todo: 0, + }); + + let staging_buffer = self.device + .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC) + .fill_from_slice(&[self.uniforms]); + + encoder.copy_buffer_to_buffer(&staging_buffer, 0, &self.uniform_buffer, 0, std::mem::size_of::() as wgpu::BufferAddress); + + self.queue.submit(&[encoder.finish()]); + } + + fn render(&mut self) { + let frame = self.swap_chain.get_next_texture(); + + let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + todo: 0, + }); + + { + 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.1, + g: 0.2, + b: 0.3, + a: 1.0, + }, + } + ], + depth_stencil_attachment: None, + }); + } + + for instance in &self.instances { + self.uniforms.model = instance.to_matrix(); + let staging_buffer = self.device + .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC) + .fill_from_slice(&[self.uniforms]); + encoder.copy_buffer_to_buffer(&staging_buffer, 0, &self.uniform_buffer, 0, std::mem::size_of::() as wgpu::BufferAddress); + + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[ + wgpu::RenderPassColorAttachmentDescriptor { + attachment: &frame.view, + resolve_target: None, + load_op: wgpu::LoadOp::Load, + store_op: wgpu::StoreOp::Store, + clear_color: wgpu::Color { + r: 0.1, + g: 0.2, + b: 0.3, + a: 1.0, + }, + } + ], + depth_stencil_attachment: None, + }); + + render_pass.set_pipeline(&self.render_pipeline); + render_pass.set_bind_group(0, &self.diffuse_bind_group, &[]); + render_pass.set_bind_group(1, &self.uniform_bind_group, &[]); + render_pass.set_vertex_buffers(0, &[(&self.vertex_buffer, 0)]); + render_pass.set_index_buffer(&self.index_buffer, 0); + render_pass.draw_indexed(0..self.num_indices, 0, 0..1); + } + + self.queue.submit(&[ + encoder.finish() + ]); + } +} + +fn main() { + let event_loop = EventLoop::new(); + let window = WindowBuilder::new() + .build(&event_loop) + .unwrap(); + + let mut state = State::new(&window); + + event_loop.run(move |event, _, control_flow| { + match event { + Event::WindowEvent { + ref event, + window_id, + } if window_id == window.id() => if state.input(event) { + *control_flow = ControlFlow::Wait; + } else { + match event { + WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + WindowEvent::KeyboardInput { + input, + .. + } => { + match input { + KeyboardInput { + state: ElementState::Pressed, + virtual_keycode: Some(VirtualKeyCode::Escape), + .. + } => *control_flow = ControlFlow::Exit, + _ => *control_flow = ControlFlow::Wait, + } + } + WindowEvent::Resized(logical_size) => { + state.resize(*logical_size); + *control_flow = ControlFlow::Wait; + } + WindowEvent::HiDpiFactorChanged(new_hidpi_factor) => { + state.update_hidpi_and_resize(*new_hidpi_factor); + *control_flow = ControlFlow::Wait; + } + _ => *control_flow = ControlFlow::Wait, + } + } + Event::EventsCleared => { + state.update(); + state.render(); + *control_flow = ControlFlow::Wait; + } + _ => *control_flow = ControlFlow::Wait, + } + }); +} \ No newline at end of file diff --git a/code/beginner/tutorial7-instancing/src/naive.vert b/code/beginner/tutorial7-instancing/src/naive.vert new file mode 100644 index 00000000..da0db4d1 --- /dev/null +++ b/code/beginner/tutorial7-instancing/src/naive.vert @@ -0,0 +1,17 @@ +#version 450 + +layout(location=0) in vec3 a_position; +layout(location=1) in vec2 a_tex_coords; + +layout(location=0) out vec2 v_tex_coords; + +layout(set=1, binding=0) +uniform Uniforms { + mat4 u_view_proj; + mat4 u_model; +}; + +void main() { + v_tex_coords = a_tex_coords; + gl_Position = u_view_proj * u_model * vec4(a_position, 1.0); +} \ No newline at end of file diff --git a/code/beginner/tutorial7-instancing/src/shader.frag b/code/beginner/tutorial7-instancing/src/shader.frag new file mode 100644 index 00000000..9b033114 --- /dev/null +++ b/code/beginner/tutorial7-instancing/src/shader.frag @@ -0,0 +1,11 @@ +#version 450 + +layout(location=0) in vec2 v_tex_coords; +layout(location=0) out vec4 f_color; + +layout(set = 0, binding = 0) uniform texture2D t_diffuse; +layout(set = 0, binding = 1) uniform sampler s_diffuse; + +void main() { + f_color = texture(sampler2D(t_diffuse, s_diffuse), v_tex_coords); +} \ No newline at end of file diff --git a/code/beginner/tutorial7-instancing/src/shader.vert b/code/beginner/tutorial7-instancing/src/shader.vert new file mode 100644 index 00000000..881796a3 --- /dev/null +++ b/code/beginner/tutorial7-instancing/src/shader.vert @@ -0,0 +1,17 @@ +#version 450 + +layout(location=0) in vec3 a_position; +layout(location=1) in vec2 a_tex_coords; + +layout(location=0) out vec2 v_tex_coords; + +layout(set=1, binding=0) +uniform Uniforms { + mat4 u_view_proj; + mat4 u_model[100]; +}; + +void main() { + v_tex_coords = a_tex_coords; + gl_Position = u_view_proj * u_model[gl_InstanceIndex] * vec4(a_position, 1.0); +} \ No newline at end of file diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 6e06f2ad..c6f52531 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -27,6 +27,7 @@ module.exports = { '/beginner/tutorial4-buffer/', '/beginner/tutorial5-textures/', '/beginner/tutorial6-uniforms/', + '/beginner/tutorial7-instancing/', ], }, { diff --git a/docs/beginner/tutorial7-instancing/README.md b/docs/beginner/tutorial7-instancing/README.md new file mode 100644 index 00000000..d5502e43 --- /dev/null +++ b/docs/beginner/tutorial7-instancing/README.md @@ -0,0 +1,252 @@ +# Instancing + +Up to this point we've been drawing just one object. Most games have hundreds of objects on screen at the same time. If we wanted to draw multiple instances of our model, we could copy the vertex buffer and modify it's vertices to be in the right place, but this would be hilariously inefficient. We have our model, and we now how to position it in 3d space with a matrix, like we did the camera, so all we have to do is change the matrix we're using when we draw. + +## The naive method + +First let's modify `Uniforms` to include a `model` property. + +```rust +#[repr(C)] +#[derive(Debug, Copy, Clone)] +struct Uniforms { + view_proj: cgmath::Matrix4, + model: cgmath::Matrix4, // NEW! +} + +impl Uniforms { + fn new() -> Self { + use cgmath::SquareMatrix; + Self { + view_proj: cgmath::Matrix4::identity(), + model: cgmath::Matrix4::identity(), // NEW! + } + } + + fn update_view_proj(&mut self, camera: &Camera) { + self.view_proj = OPENGL_TO_WGPU_MATRIX * camera.build_view_projection_matrix(); + } +} +``` + +With that let's introduce another struct for our instances. We'll use it to store the position and rotation of our instances. We'll also have a method to convert our instance data into a matrix that we can give to `Uniforms`. + +```rust +struct Instance { + position: cgmath::Vector3, + rotation: cgmath::Quaternion, +} + +impl Instance { + fn to_matrix(&self) -> cgmath::Matrix4 { + cgmath::Matrix4::from_translation(self.position) + * cgmath::Matrix4::from(self.rotation) + } +} +``` + +Next we'll add `instances: Vec,` to `State` and create our instances in new with the following in `new()`. + +```rust +// ... + +// add these at the top of the file +const NUM_INSTANCES_PER_ROW: u32 = 10; +const NUM_INSTANCES: u32 = NUM_INSTANCES_PER_ROW * NUM_INSTANCES_PER_ROW; +const INSTANCE_DISPLACEMENT: cgmath::Vector3 = cgmath::Vector3::new(NUM_INSTANCES_PER_ROW as f32 * 0.5, 0.0, NUM_INSTANCES_PER_ROW as f32 * 0.5); + +// make a 10 by 10 grid of objects +let instances = (0..NUM_INSTANCES_PER_ROW).flat_map(|z| { + (0..NUM_INSTANCES_PER_ROW).map(move |x| { + let position = cgmath::Vector3 { x: x as f32, y: 0.0, z: z as f32 } - INSTANCE_DISPLACEMENT; + + let rotation = if position.is_zero() { + // this is needed so an object at (0, 0, 0) won't get scaled to zero + // as Quaternions can effect scale if they're not create correctly + cgmath::Quaternion::from_axis_angle(cgmath::Vector3::unit_z(), cgmath::Deg(0.0)) + } else { + cgmath::Quaternion::from_axis_angle(position.clone().normalize(), cgmath::Deg(45.0)) + }; + + Instance { + position, rotation, + } + }) +}).collect(); + +// ... + +Self { + // ... + instances, +} +``` + +Now that that's done, we need to update `shader.vert` to use the model matrix passed in through `Uniforms`. + +```glsl +#version 450 + +layout(location=0) in vec3 a_position; +layout(location=1) in vec2 a_tex_coords; + +layout(location=0) out vec2 v_tex_coords; + +layout(set=1, binding=0) +uniform Uniforms { + mat4 u_view_proj; + mat4 u_model; // NEW! +}; + +void main() { + v_tex_coords = a_tex_coords; + gl_Position = u_view_proj * u_model * vec4(a_position, 1.0); // UPDATED! +} +``` + +If you run the program now, you won't see anything different. That's because we aren't actually updating the uniform buffer at all. Using our current method, we need to update the uniform buffer for every instance we draw. We'll do this in `render()` with something like the following. + +```rust +for instance in &self.instances { + // 1. + self.uniforms.model = instance.to_matrix(); + let staging_buffer = self.device + .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC) + .fill_from_slice(&[self.uniforms]); + encoder.copy_buffer_to_buffer(&staging_buffer, 0, &self.uniform_buffer, 0, std::mem::size_of::() as wgpu::BufferAddress); + + // 2. + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[ + wgpu::RenderPassColorAttachmentDescriptor { + attachment: &frame.view, + resolve_target: None, + load_op: wgpu::LoadOp::Load, // 3. + store_op: wgpu::StoreOp::Store, + clear_color: wgpu::Color { + r: 0.1, + g: 0.2, + b: 0.3, + a: 1.0, + }, + } + ], + depth_stencil_attachment: None, + }); + + render_pass.set_pipeline(&self.render_pipeline); + render_pass.set_bind_group(0, &self.diffuse_bind_group, &[]); + render_pass.set_bind_group(1, &self.uniform_bind_group, &[]); + render_pass.set_vertex_buffers(0, &[(&self.vertex_buffer, 0)]); + render_pass.set_index_buffer(&self.index_buffer, 0); + render_pass.draw_indexed(0..self.num_indices, 0, 0..1); +} +``` + +Some things to note: +1. We're creating a hundred buffers a frame. This is inefficent, but we'll cover better ways of doing this later in this tutorial. +2. We have to create a new render pass per instance, as we can't modify the uniform buffer while we have one active. +3. We use `LoadOp::Load` here to prevent the render pass from clearing the entire screen after each draw. This means we lose our clear color. This makes the background black on my machine, but it may be filled with garbage data on yours. We can fix this by added another render pass before the loop. + +```rust +{ + 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.1, + g: 0.2, + b: 0.3, + a: 1.0, + }, + } + ], + depth_stencil_attachment: None, + }); +} +``` + +We should get something that looks like this when we're done. + +![A beautiful forest](./forest.png) + +If you haven't guessed already, this way of instancing is not the best. It requires hundreds of render passes, hundereds of staging buffers, and an extra render pass just to get the clear color working again. Cleary there must be a better way. + +## A better way - uniform arrays + +Since GLSL is based on C, it supports arrays. We can leverage this by store *all* of the instance matrices in the `Uniforms` struct. We need to make this change on the Rust side, as well as in our shader. + +```rust +#[repr(C)] +#[derive(Copy, Clone)] +struct Uniforms { + view_proj: cgmath::Matrix4, + model: [cgmath::Matrix4; NUM_INSTANCES as usize], +} +``` + +```glsl +#version 450 + +layout(location=0) in vec3 a_position; +layout(location=1) in vec2 a_tex_coords; + +layout(location=0) out vec2 v_tex_coords; + +layout(set=1, binding=0) +uniform Uniforms { + mat4 u_view_proj; + mat4 u_model[100]; +}; + +void main() { + v_tex_coords = a_tex_coords; + // gl_InstanceIndex what index we're currently on + gl_Position = u_view_proj * u_model[gl_InstanceIndex] * vec4(a_position, 1.0); +} +``` + +Note that we're using an array, *not a `Vec`*. `Vec`s are basically pointers to an array on the heap. Our graphics card doesn't know how follow a pointer to the heap, so our data needs to be stored inline. + +`Uniforms::new()` will change slightly as well. + +```rust +fn new() -> Self { + Self { + view_proj: cgmath::Matrix4::identity(), + model: [cgmath::Matrix4::identity(); NUM_INSTANCES as usize], + } +} +``` + +We need to update our model matrices in `State::update()` before we create the `staging_buffer`. + +```rust +for (i, instance) in self.instances.iter().enumerate() { + self.uniforms.model[i] = instance.to_matrix(); +} +``` + +Lastly we need to change our render code. Fortunately, it's a lot simpler than the before. In fact we can use the code from last tutorial and just change our draw call. + +```rust +render_pass.draw_indexed(0..self.num_indices, 0, 0..NUM_INSTANCES); +``` + +You'll remember that the 3rd parameter in `draw_indexed` is the instance range. This controls how many times our object will be drawn. This is where our shader gets the value for `gl_InstanceIndex`. + +Running the program now won't change anything visually from our last example, but the framerate will be better. + +This technique has its drawbacks. +1. We can't use a `Vec` like we've mentioned before +2. We're limited in the number of instances we can process at a time requiring use to cap it at some abitrary number, or render things in "batches" + +## Another better way - instance buffers + +When we created the `VertexBufferDescriptor` for our model, it required a `step_mode` field. We specified + + \ No newline at end of file diff --git a/docs/beginner/tutorial7-instancing/forest.png b/docs/beginner/tutorial7-instancing/forest.png new file mode 100644 index 00000000..e74a9e74 Binary files /dev/null and b/docs/beginner/tutorial7-instancing/forest.png differ