Skip to content

Commit 2ff0a58

Browse files
committed
[add] demo showcasing instanced rendering and updated specification.
1 parent 7f8375d commit 2ff0a58

File tree

2 files changed

+390
-5
lines changed

2 files changed

+390
-5
lines changed
Lines changed: 384 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,384 @@
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

Comments
 (0)