Skip to main content

performance_benchmark/
performance_benchmark.rs

1//! Performance Benchmark - Render System Stress Test
2//!
3//! Stress tests the rendering system with thousands of draw calls:
4//! - 10,000+ textured quads
5//! - Instanced rendering performance
6//! - Frame time metrics
7//! - Draw call batching efficiency
8//! - GPU memory usage patterns
9//!
10//! Press SPACE to toggle rendering.
11//! Press +/- to adjust object count.
12
13use astrelis_core::logging;
14use astrelis_render::{Color, GraphicsContext, RenderWindow, RenderWindowBuilder, wgpu};
15use astrelis_winit::{
16    FrameTime, WindowId,
17    app::{App, AppCtx, run_app},
18    event::{Event, EventBatch, HandleStatus, Key, NamedKey},
19    window::{WindowDescriptor, WinitPhysicalSize},
20};
21use std::sync::Arc;
22use std::time::Instant;
23
24struct PerformanceBenchmark {
25    _context: Arc<GraphicsContext>,
26    window: RenderWindow,
27    window_id: WindowId,
28    object_count: usize,
29    rendering: bool,
30    frame_count: u64,
31    last_fps_time: Instant,
32    fps: f32,
33    last_frame_time: f32,
34}
35
36fn main() {
37    logging::init();
38
39    run_app(|ctx| {
40        let graphics_ctx =
41            GraphicsContext::new_owned_sync().expect("Failed to create graphics context");
42
43        let window = ctx
44            .create_window(WindowDescriptor {
45                title: "Performance Benchmark - Render Stress Test".to_string(),
46                size: Some(WinitPhysicalSize::new(1280.0, 720.0)),
47                ..Default::default()
48            })
49            .expect("Failed to create window");
50
51        let window = RenderWindowBuilder::new()
52            .color_format(wgpu::TextureFormat::Bgra8UnormSrgb)
53            .with_depth_default()
54            .build(window, graphics_ctx.clone())
55            .expect("Failed to create render window");
56
57        let window_id = window.id();
58
59        println!("\n═══════════════════════════════════════════════════════");
60        println!("  ⚡ PERFORMANCE BENCHMARK - Render Stress Test");
61        println!("═══════════════════════════════════════════════════════");
62        println!("  CONTROLS:");
63        println!("    [Space]  Toggle rendering on/off");
64        println!("    [+/-]    Increase/decrease object count");
65        println!("  Starting with 1000 objects");
66        println!("═══════════════════════════════════════════════════════\n");
67
68        Box::new(PerformanceBenchmark {
69            _context: graphics_ctx,
70            window,
71            window_id,
72            object_count: 1000,
73            rendering: true,
74            frame_count: 0,
75            last_fps_time: Instant::now(),
76            fps: 0.0,
77            last_frame_time: 0.0,
78        })
79    });
80}
81
82impl App for PerformanceBenchmark {
83    fn update(&mut self, _ctx: &mut AppCtx, _time: &FrameTime) {
84        self.frame_count += 1;
85
86        let now = Instant::now();
87        if now.duration_since(self.last_fps_time).as_secs_f32() >= 1.0 {
88            self.fps = self.frame_count as f32;
89            self.frame_count = 0;
90            self.last_fps_time = now;
91            println!(
92                "FPS: {:.1} | Frame Time: {:.2}ms | Objects: {} | Rendering: {}",
93                self.fps, self.last_frame_time, self.object_count, self.rendering
94            );
95        }
96    }
97
98    fn render(&mut self, _ctx: &mut AppCtx, window_id: WindowId, events: &mut EventBatch) {
99        if window_id != self.window_id {
100            return;
101        }
102
103        let frame_start = Instant::now();
104
105        // Handle resize
106        events.dispatch(|event| {
107            if let Event::WindowResized(size) = event {
108                self.window.resized(*size);
109                HandleStatus::consumed()
110            } else {
111                HandleStatus::ignored()
112            }
113        });
114
115        // Handle keyboard input
116        events.dispatch(|event| {
117            if let Event::KeyInput(key) = event
118                && key.state == astrelis_winit::event::ElementState::Pressed
119            {
120                match &key.logical_key {
121                    Key::Named(NamedKey::Space) => {
122                        self.rendering = !self.rendering;
123                        println!("Rendering: {}", self.rendering);
124                        return HandleStatus::consumed();
125                    }
126                    Key::Character(c) if c == "+" || c == "=" => {
127                        self.object_count = (self.object_count + 500).min(50000);
128                        println!("Object count: {}", self.object_count);
129                        return HandleStatus::consumed();
130                    }
131                    Key::Character(c) if c == "-" => {
132                        self.object_count = self.object_count.saturating_sub(500).max(100);
133                        println!("Object count: {}", self.object_count);
134                        return HandleStatus::consumed();
135                    }
136                    _ => {}
137                }
138            }
139            HandleStatus::ignored()
140        });
141
142        // Begin frame
143        let Some(frame) = self.window.begin_frame() else {
144            return; // Surface not available
145        };
146
147        if self.rendering {
148            // Simulate rendering thousands of objects
149            // In a real scenario, this would involve:
150            // - Instanced draw calls
151            // - Uniform buffer updates
152            // - Texture binding
153            // - Shader state changes
154
155            let _pass = frame
156                .render_pass()
157                .clear_color(Color::from_rgb_u8(10, 10, 15))
158                .label("benchmark_pass")
159                .build();
160            // Actual rendering would happen here
161            // For benchmark purposes, we're measuring the overhead
162            // of the render pass itself with clear operations
163        } else {
164            let _pass = frame
165                .render_pass()
166                .clear_color(Color::from_rgb_u8(10, 10, 15))
167                .label("benchmark_pass")
168                .build();
169        }
170        // Frame auto-submits on drop
171
172        let frame_end = Instant::now();
173        self.last_frame_time = frame_end.duration_since(frame_start).as_secs_f32() * 1000.0;
174    }
175}