|
| 1 | +#![allow(clippy::needless_return)] |
| 2 | + |
| 3 | +//! Example: Instanced 2D quads with per-instance vertex data. |
| 4 | +//! |
| 5 | +//! This example renders a grid of quads that all share the same geometry |
| 6 | +//! but use per-instance offsets and colors supplied from a second vertex |
| 7 | +//! buffer. It exercises `RenderPipelineBuilder::with_instance_buffer` and |
| 8 | +//! `RenderCommand::DrawIndexed` with a non-trivial instance range. |
| 9 | +
|
| 10 | +use lambda::{ |
| 11 | + component::Component, |
| 12 | + events::WindowEvent, |
| 13 | + logging, |
| 14 | + render::{ |
| 15 | + buffer::{ |
| 16 | + BufferBuilder, |
| 17 | + BufferType, |
| 18 | + Properties, |
| 19 | + Usage, |
| 20 | + }, |
| 21 | + command::{ |
| 22 | + IndexFormat, |
| 23 | + RenderCommand, |
| 24 | + }, |
| 25 | + pipeline::{ |
| 26 | + CullingMode, |
| 27 | + RenderPipelineBuilder, |
| 28 | + }, |
| 29 | + render_pass::RenderPassBuilder, |
| 30 | + shader::{ |
| 31 | + Shader, |
| 32 | + ShaderBuilder, |
| 33 | + ShaderKind, |
| 34 | + VirtualShader, |
| 35 | + }, |
| 36 | + vertex::{ |
| 37 | + ColorFormat, |
| 38 | + VertexAttribute, |
| 39 | + VertexElement, |
| 40 | + }, |
| 41 | + viewport, |
| 42 | + RenderContext, |
| 43 | + ResourceId, |
| 44 | + }, |
| 45 | + runtime::start_runtime, |
| 46 | + runtimes::{ |
| 47 | + application::ComponentResult, |
| 48 | + ApplicationRuntime, |
| 49 | + ApplicationRuntimeBuilder, |
| 50 | + }, |
| 51 | +}; |
| 52 | + |
| 53 | +// ------------------------------ SHADER SOURCE -------------------------------- |
| 54 | + |
| 55 | +const VERTEX_SHADER_SOURCE: &str = r#" |
| 56 | +#version 450 |
| 57 | +
|
| 58 | +layout (location = 0) in vec3 vertex_position; |
| 59 | +layout (location = 1) in vec3 instance_offset; |
| 60 | +layout (location = 2) in vec3 instance_color; |
| 61 | +
|
| 62 | +layout (location = 0) out vec3 frag_color; |
| 63 | +
|
| 64 | +void main() { |
| 65 | + vec3 position = vertex_position + instance_offset; |
| 66 | + gl_Position = vec4(position, 1.0); |
| 67 | + frag_color = instance_color; |
| 68 | +} |
| 69 | +
|
| 70 | +"#; |
| 71 | + |
| 72 | +const FRAGMENT_SHADER_SOURCE: &str = r#" |
| 73 | +#version 450 |
| 74 | +
|
| 75 | +layout (location = 0) in vec3 frag_color; |
| 76 | +layout (location = 0) out vec4 fragment_color; |
| 77 | +
|
| 78 | +void main() { |
| 79 | + fragment_color = vec4(frag_color, 1.0); |
| 80 | +} |
| 81 | +
|
| 82 | +"#; |
| 83 | + |
| 84 | +// ------------------------------- VERTEX TYPES -------------------------------- |
| 85 | + |
| 86 | +#[repr(C)] |
| 87 | +#[derive(Clone, Copy, Debug)] |
| 88 | +struct QuadVertex { |
| 89 | + position: [f32; 3], |
| 90 | +} |
| 91 | + |
| 92 | +#[repr(C)] |
| 93 | +#[derive(Clone, Copy, Debug)] |
| 94 | +struct InstanceData { |
| 95 | + offset: [f32; 3], |
| 96 | + color: [f32; 3], |
| 97 | +} |
| 98 | + |
| 99 | +// --------------------------------- COMPONENT --------------------------------- |
| 100 | + |
| 101 | +/// Component that renders a grid of instanced quads. |
| 102 | +pub struct InstancedQuadsExample { |
| 103 | + vertex_shader: Shader, |
| 104 | + fragment_shader: Shader, |
| 105 | + render_pass_id: Option<ResourceId>, |
| 106 | + render_pipeline_id: Option<ResourceId>, |
| 107 | + index_buffer_id: Option<ResourceId>, |
| 108 | + index_count: u32, |
| 109 | + instance_count: u32, |
| 110 | + width: u32, |
| 111 | + height: u32, |
| 112 | +} |
| 113 | + |
| 114 | +impl Component<ComponentResult, String> for InstancedQuadsExample { |
| 115 | + fn on_attach( |
| 116 | + &mut self, |
| 117 | + render_context: &mut RenderContext, |
| 118 | + ) -> Result<ComponentResult, String> { |
| 119 | + let render_pass = RenderPassBuilder::new().build(render_context); |
| 120 | + |
| 121 | + // Quad geometry in clip space centered at the origin. |
| 122 | + let quad_vertices: Vec<QuadVertex> = vec![ |
| 123 | + QuadVertex { |
| 124 | + position: [-0.05, -0.05, 0.0], |
| 125 | + }, |
| 126 | + QuadVertex { |
| 127 | + position: [0.05, -0.05, 0.0], |
| 128 | + }, |
| 129 | + QuadVertex { |
| 130 | + position: [0.05, 0.05, 0.0], |
| 131 | + }, |
| 132 | + QuadVertex { |
| 133 | + position: [-0.05, 0.05, 0.0], |
| 134 | + }, |
| 135 | + ]; |
| 136 | + |
| 137 | + // Two triangles forming a quad. |
| 138 | + let indices: Vec<u16> = vec![0, 1, 2, 2, 3, 0]; |
| 139 | + let index_count = indices.len() as u32; |
| 140 | + |
| 141 | + // Build a grid of instance offsets and colors. |
| 142 | + let grid_size: u32 = 10; |
| 143 | + let spacing: f32 = 0.2; |
| 144 | + let start: f32 = -0.9; |
| 145 | + |
| 146 | + let mut instances: Vec<InstanceData> = Vec::new(); |
| 147 | + for y in 0..grid_size { |
| 148 | + for x in 0..grid_size { |
| 149 | + let offset_x = start + (x as f32) * spacing; |
| 150 | + let offset_y = start + (y as f32) * spacing; |
| 151 | + |
| 152 | + // Simple color gradient across the grid. |
| 153 | + let color_r = (x as f32) / ((grid_size - 1) as f32); |
| 154 | + let color_g = (y as f32) / ((grid_size - 1) as f32); |
| 155 | + let color_b = 0.5; |
| 156 | + |
| 157 | + instances.push(InstanceData { |
| 158 | + offset: [offset_x, offset_y, 0.0], |
| 159 | + color: [color_r, color_g, color_b], |
| 160 | + }); |
| 161 | + } |
| 162 | + } |
| 163 | + let instance_count = instances.len() as u32; |
| 164 | + |
| 165 | + // Build vertex, instance, and index buffers. |
| 166 | + let vertex_buffer = BufferBuilder::new() |
| 167 | + .with_usage(Usage::VERTEX) |
| 168 | + .with_properties(Properties::DEVICE_LOCAL) |
| 169 | + .with_buffer_type(BufferType::Vertex) |
| 170 | + .with_label("instanced-quads-vertices") |
| 171 | + .build(render_context, quad_vertices) |
| 172 | + .map_err(|error| error.to_string())?; |
| 173 | + |
| 174 | + let instance_buffer = BufferBuilder::new() |
| 175 | + .with_usage(Usage::VERTEX) |
| 176 | + .with_properties(Properties::DEVICE_LOCAL) |
| 177 | + .with_buffer_type(BufferType::Vertex) |
| 178 | + .with_label("instanced-quads-instances") |
| 179 | + .build(render_context, instances) |
| 180 | + .map_err(|error| error.to_string())?; |
| 181 | + |
| 182 | + let index_buffer = BufferBuilder::new() |
| 183 | + .with_usage(Usage::INDEX) |
| 184 | + .with_properties(Properties::DEVICE_LOCAL) |
| 185 | + .with_buffer_type(BufferType::Index) |
| 186 | + .with_label("instanced-quads-indices") |
| 187 | + .build(render_context, indices) |
| 188 | + .map_err(|error| error.to_string())?; |
| 189 | + |
| 190 | + // Vertex attributes for per-vertex positions in slot 0. |
| 191 | + let vertex_attributes = vec![VertexAttribute { |
| 192 | + location: 0, |
| 193 | + offset: 0, |
| 194 | + element: VertexElement { |
| 195 | + format: ColorFormat::Rgb32Sfloat, |
| 196 | + offset: 0, |
| 197 | + }, |
| 198 | + }]; |
| 199 | + |
| 200 | + // Instance attributes in slot 1: offset and color. |
| 201 | + let instance_attributes = vec![ |
| 202 | + VertexAttribute { |
| 203 | + location: 1, |
| 204 | + offset: 0, |
| 205 | + element: VertexElement { |
| 206 | + format: ColorFormat::Rgb32Sfloat, |
| 207 | + offset: 0, |
| 208 | + }, |
| 209 | + }, |
| 210 | + VertexAttribute { |
| 211 | + location: 2, |
| 212 | + offset: 0, |
| 213 | + element: VertexElement { |
| 214 | + format: ColorFormat::Rgb32Sfloat, |
| 215 | + offset: 12, |
| 216 | + }, |
| 217 | + }, |
| 218 | + ]; |
| 219 | + |
| 220 | + let pipeline = RenderPipelineBuilder::new() |
| 221 | + .with_culling(CullingMode::Back) |
| 222 | + .with_buffer(vertex_buffer, vertex_attributes) |
| 223 | + .with_instance_buffer(instance_buffer, instance_attributes) |
| 224 | + .build( |
| 225 | + render_context, |
| 226 | + &render_pass, |
| 227 | + &self.vertex_shader, |
| 228 | + Some(&self.fragment_shader), |
| 229 | + ); |
| 230 | + |
| 231 | + self.render_pass_id = Some(render_context.attach_render_pass(render_pass)); |
| 232 | + self.render_pipeline_id = Some(render_context.attach_pipeline(pipeline)); |
| 233 | + self.index_buffer_id = Some(render_context.attach_buffer(index_buffer)); |
| 234 | + self.index_count = index_count; |
| 235 | + self.instance_count = instance_count; |
| 236 | + |
| 237 | + logging::info!( |
| 238 | + "Instanced quads example attached with {} instances", |
| 239 | + self.instance_count |
| 240 | + ); |
| 241 | + return Ok(ComponentResult::Success); |
| 242 | + } |
| 243 | + |
| 244 | + fn on_detach( |
| 245 | + &mut self, |
| 246 | + _render_context: &mut RenderContext, |
| 247 | + ) -> Result<ComponentResult, String> { |
| 248 | + logging::info!("Instanced quads example detached"); |
| 249 | + return Ok(ComponentResult::Success); |
| 250 | + } |
| 251 | + |
| 252 | + fn on_event( |
| 253 | + &mut self, |
| 254 | + event: lambda::events::Events, |
| 255 | + ) -> Result<ComponentResult, String> { |
| 256 | + match event { |
| 257 | + lambda::events::Events::Window { event, .. } => match event { |
| 258 | + WindowEvent::Resize { width, height } => { |
| 259 | + self.width = width; |
| 260 | + self.height = height; |
| 261 | + logging::info!("Window resized to {}x{}", width, height); |
| 262 | + } |
| 263 | + _ => {} |
| 264 | + }, |
| 265 | + _ => {} |
| 266 | + } |
| 267 | + return Ok(ComponentResult::Success); |
| 268 | + } |
| 269 | + |
| 270 | + fn on_update( |
| 271 | + &mut self, |
| 272 | + _last_frame: &std::time::Duration, |
| 273 | + ) -> Result<ComponentResult, String> { |
| 274 | + // This example uses static instance data; no per-frame updates required. |
| 275 | + return Ok(ComponentResult::Success); |
| 276 | + } |
| 277 | + |
| 278 | + fn on_render( |
| 279 | + &mut self, |
| 280 | + _render_context: &mut RenderContext, |
| 281 | + ) -> Vec<RenderCommand> { |
| 282 | + let viewport = |
| 283 | + viewport::ViewportBuilder::new().build(self.width, self.height); |
| 284 | + |
| 285 | + let render_pass_id = self |
| 286 | + .render_pass_id |
| 287 | + .expect("Render pass must be attached before rendering"); |
| 288 | + let pipeline_id = self |
| 289 | + .render_pipeline_id |
| 290 | + .expect("Pipeline must be attached before rendering"); |
| 291 | + let index_buffer_id = self |
| 292 | + .index_buffer_id |
| 293 | + .expect("Index buffer must be attached before rendering"); |
| 294 | + |
| 295 | + return vec![ |
| 296 | + RenderCommand::BeginRenderPass { |
| 297 | + render_pass: render_pass_id, |
| 298 | + viewport: viewport.clone(), |
| 299 | + }, |
| 300 | + RenderCommand::SetPipeline { |
| 301 | + pipeline: pipeline_id, |
| 302 | + }, |
| 303 | + RenderCommand::SetViewports { |
| 304 | + start_at: 0, |
| 305 | + viewports: vec![viewport.clone()], |
| 306 | + }, |
| 307 | + RenderCommand::SetScissors { |
| 308 | + start_at: 0, |
| 309 | + viewports: vec![viewport.clone()], |
| 310 | + }, |
| 311 | + RenderCommand::BindVertexBuffer { |
| 312 | + pipeline: pipeline_id, |
| 313 | + buffer: 0, |
| 314 | + }, |
| 315 | + RenderCommand::BindVertexBuffer { |
| 316 | + pipeline: pipeline_id, |
| 317 | + buffer: 1, |
| 318 | + }, |
| 319 | + RenderCommand::BindIndexBuffer { |
| 320 | + buffer: index_buffer_id, |
| 321 | + format: IndexFormat::Uint16, |
| 322 | + }, |
| 323 | + RenderCommand::DrawIndexed { |
| 324 | + indices: 0..self.index_count, |
| 325 | + base_vertex: 0, |
| 326 | + instances: 0..self.instance_count, |
| 327 | + }, |
| 328 | + RenderCommand::EndRenderPass, |
| 329 | + ]; |
| 330 | + } |
| 331 | +} |
| 332 | + |
| 333 | +impl Default for InstancedQuadsExample { |
| 334 | + fn default() -> Self { |
| 335 | + let vertex_virtual_shader = VirtualShader::Source { |
| 336 | + source: VERTEX_SHADER_SOURCE.to_string(), |
| 337 | + kind: ShaderKind::Vertex, |
| 338 | + entry_point: "main".to_string(), |
| 339 | + name: "instanced_quads".to_string(), |
| 340 | + }; |
| 341 | + |
| 342 | + let fragment_virtual_shader = VirtualShader::Source { |
| 343 | + source: FRAGMENT_SHADER_SOURCE.to_string(), |
| 344 | + kind: ShaderKind::Fragment, |
| 345 | + entry_point: "main".to_string(), |
| 346 | + name: "instanced_quads".to_string(), |
| 347 | + }; |
| 348 | + |
| 349 | + let mut shader_builder = ShaderBuilder::new(); |
| 350 | + let vertex_shader = shader_builder.build(vertex_virtual_shader); |
| 351 | + let fragment_shader = shader_builder.build(fragment_virtual_shader); |
| 352 | + |
| 353 | + return Self { |
| 354 | + vertex_shader, |
| 355 | + fragment_shader, |
| 356 | + render_pass_id: None, |
| 357 | + render_pipeline_id: None, |
| 358 | + index_buffer_id: None, |
| 359 | + index_count: 0, |
| 360 | + instance_count: 0, |
| 361 | + width: 800, |
| 362 | + height: 600, |
| 363 | + }; |
| 364 | + } |
| 365 | +} |
| 366 | + |
| 367 | +fn main() { |
| 368 | + let runtime: ApplicationRuntime = |
| 369 | + ApplicationRuntimeBuilder::new("Instanced Quads Example") |
| 370 | + .with_window_configured_as(|window_builder| { |
| 371 | + return window_builder |
| 372 | + .with_dimensions(800, 600) |
| 373 | + .with_name("Instanced Quads Example"); |
| 374 | + }) |
| 375 | + .with_renderer_configured_as(|render_builder| { |
| 376 | + return render_builder.with_render_timeout(1_000_000_000); |
| 377 | + }) |
| 378 | + .with_component(|runtime, example: InstancedQuadsExample| { |
| 379 | + return (runtime, example); |
| 380 | + }) |
| 381 | + .build(); |
| 382 | + |
| 383 | + start_runtime(runtime); |
| 384 | +} |
0 commit comments