Skip to main content

profiling_demo/
profiling_demo.rs

1//! Profiling Demo — CPU + GPU Profiling with Puffin
2//!
3//! Demonstrates comprehensive profiling of the render pipeline:
4//! - CPU profiling via `puffin` (profile_function / profile_scope macros)
5//! - GPU profiling via `wgpu-profiler` (timestamp queries reported to puffin)
6//!
7//! Run with GPU profiling:
8//!   cargo run -p astrelis-render --example profiling_demo --features gpu-profiling
9//!
10//! Run without GPU profiling (CPU only):
11//!   cargo run -p astrelis-render --example profiling_demo
12//!
13//! Then open puffin_viewer and connect to 127.0.0.1:8585 to see the flame graph.
14//! Install puffin_viewer with: cargo install puffin_viewer
15
16use std::sync::Arc;
17
18use astrelis_core::logging;
19use astrelis_core::profiling::{
20    ProfilingBackend, init_profiling, new_frame, profile_function, profile_scope,
21};
22use astrelis_render::{
23    Color, GraphicsContext, GraphicsContextDescriptor, RenderWindow, RenderWindowBuilder, wgpu,
24};
25use astrelis_winit::{
26    FrameTime, WindowId,
27    app::{App, AppCtx, run_app},
28    event::EventBatch,
29    window::{WindowDescriptor, WinitPhysicalSize},
30};
31
32struct ProfilingDemo {
33    #[allow(dead_code)]
34    context: Arc<GraphicsContext>,
35    window: RenderWindow,
36    window_id: WindowId,
37    frame_count: u64,
38}
39
40fn main() {
41    logging::init();
42    init_profiling(ProfilingBackend::PuffinHttp);
43
44    run_app(|ctx| {
45        profile_function!();
46
47        // Request TIMESTAMP_QUERY for GPU profiling (best-effort, won't fail if unavailable)
48        let graphics_ctx = pollster::block_on(GraphicsContext::new_owned_with_descriptor(
49            GraphicsContextDescriptor::new()
50                .request_capability::<astrelis_render::gpu_profiling::GpuFrameProfiler>(),
51        ))
52        .expect("Failed to create graphics context");
53
54        let window = ctx
55            .create_window(WindowDescriptor {
56                title: "Profiling Demo — CPU + GPU".to_string(),
57                size: Some(WinitPhysicalSize::new(800.0, 600.0)),
58                ..Default::default()
59            })
60            .expect("Failed to create window");
61
62        #[allow(unused_mut)]
63        let mut window = RenderWindowBuilder::new()
64            .color_format(wgpu::TextureFormat::Bgra8UnormSrgb)
65            .with_depth_default()
66            .build(window, graphics_ctx.clone())
67            .expect("Failed to create render window");
68
69        let window_id = window.id();
70
71        // Attach GPU profiler to the window — all frames will be automatically profiled
72        let has_gpu_profiling;
73        #[cfg(feature = "gpu-profiling")]
74        {
75            match astrelis_render::gpu_profiling::GpuFrameProfiler::new(&graphics_ctx) {
76                Ok(profiler) => {
77                    let has_timestamps = profiler.has_timestamp_queries();
78                    window.set_gpu_profiler(Arc::new(profiler));
79                    has_gpu_profiling = true;
80                    if has_timestamps {
81                        println!("  GPU profiling: enabled with TIMESTAMP_QUERY (full timing)");
82                    } else {
83                        println!("  GPU profiling: enabled (debug groups only, no timing data)");
84                        println!("                 TIMESTAMP_QUERY not supported by this GPU");
85                    }
86                }
87                Err(e) => {
88                    has_gpu_profiling = false;
89                    tracing::warn!("Failed to create GPU profiler: {e}. GPU profiling disabled.");
90                    println!("  GPU profiling: failed to create profiler");
91                }
92            }
93        }
94        #[cfg(not(feature = "gpu-profiling"))]
95        {
96            has_gpu_profiling = false;
97        }
98
99        println!();
100        println!("═══════════════════════════════════════════════════");
101        println!("  PROFILING DEMO — CPU + GPU");
102        println!("═══════════════════════════════════════════════════");
103        println!();
104        println!("  CPU profiling: enabled (puffin)");
105        if !has_gpu_profiling {
106            #[cfg(not(feature = "gpu-profiling"))]
107            println!("  GPU profiling: disabled (compile with --features gpu-profiling)");
108        }
109        println!();
110        println!("  Open puffin_viewer at 127.0.0.1:8585 to see the flame graph.");
111        println!("  Install with: cargo install puffin_viewer");
112        println!("═══════════════════════════════════════════════════");
113        println!();
114
115        Box::new(ProfilingDemo {
116            context: graphics_ctx,
117            window,
118            window_id,
119            frame_count: 0,
120        })
121    });
122}
123
124impl App for ProfilingDemo {
125    fn update(&mut self, _ctx: &mut AppCtx, _time: &FrameTime) {
126        profile_function!();
127
128        // Simulate some work to show up in the profiler
129        {
130            profile_scope!("simulate_game_logic");
131            let mut _sum = 0.0f64;
132            for i in 0..1000 {
133                _sum += (i as f64).sin();
134            }
135        }
136
137        self.frame_count += 1;
138        if self.frame_count.is_multiple_of(300) {
139            tracing::info!("Frame {}", self.frame_count);
140        }
141    }
142
143    fn render(&mut self, _ctx: &mut AppCtx, window_id: WindowId, events: &mut EventBatch) {
144        new_frame();
145        profile_function!();
146
147        if window_id != self.window_id {
148            return;
149        }
150
151        events.dispatch(|event| {
152            if let astrelis_winit::event::Event::WindowResized(size) = event {
153                self.window.resized(*size);
154                astrelis_winit::event::HandleStatus::consumed()
155            } else {
156                astrelis_winit::event::HandleStatus::ignored()
157            }
158        });
159
160        // Cycle the background color to visualize frames
161        let t = (self.frame_count as f32 * 0.01).sin() * 0.5 + 0.5;
162        let clear_color = Color::rgb(0.05 + t * 0.1, 0.05, 0.15 + (1.0 - t) * 0.1);
163
164        // GPU profiling is now automatic — no special handling needed!
165        // If a profiler is attached, render passes auto-create GPU scopes,
166        // and Frame::Drop auto-resolves queries and ends the profiler frame.
167        let Some(frame) = self.window.begin_frame() else {
168            return; // Surface not available
169        };
170
171        {
172            profile_scope!("render_frame");
173            let _pass = frame
174                .render_pass()
175                .clear_color(clear_color)
176                .label("profiling_pass")
177                .build();
178            profile_scope!("draw_commands");
179            // In a real app, you would issue draw calls here.
180        }
181        // Frame auto-submits on drop
182
183        // Simulate some post-render work
184        {
185            profile_scope!("post_render");
186            let _ = &self.context;
187        }
188    }
189}