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