#![allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
#[allow(dead_code)]
pub struct GpuTimeSample {
pub pass_name: String,
pub start_ns: u64,
pub end_ns: u64,
pub frame: u64,
}
impl GpuTimeSample {
#[allow(dead_code)]
pub fn duration_ns(&self) -> u64 {
self.end_ns.saturating_sub(self.start_ns)
}
#[allow(dead_code)]
pub fn duration_ms(&self) -> f32 {
self.duration_ns() as f32 / 1_000_000.0
}
}
#[derive(Debug, Clone, PartialEq)]
#[allow(dead_code)]
pub struct GpuTimelineConfig {
pub max_samples: usize,
pub enabled: bool,
}
impl Default for GpuTimelineConfig {
fn default() -> Self {
Self {
max_samples: 4096,
enabled: true,
}
}
}
#[derive(Debug, Clone, Default)]
#[allow(dead_code)]
pub struct GpuTimeline {
pub config: GpuTimelineConfig,
pub samples: Vec<GpuTimeSample>,
pub current_frame: u64,
}
#[allow(dead_code)]
pub fn new_gpu_timeline(cfg: GpuTimelineConfig) -> GpuTimeline {
GpuTimeline {
config: cfg,
samples: Vec::new(),
current_frame: 0,
}
}
#[allow(dead_code)]
pub fn begin_frame(t: &mut GpuTimeline) {
t.current_frame += 1;
}
#[allow(dead_code)]
pub fn record_sample(t: &mut GpuTimeline, name: &str, start_ns: u64, end_ns: u64) -> bool {
if t.samples.len() >= t.config.max_samples {
return false;
}
t.samples.push(GpuTimeSample {
pass_name: name.to_string(),
start_ns,
end_ns,
frame: t.current_frame,
});
true
}
#[allow(dead_code)]
pub fn sample_count(t: &GpuTimeline) -> usize {
t.samples.len()
}
#[allow(dead_code)]
pub fn clear_timeline(t: &mut GpuTimeline) {
t.samples.clear();
}
#[allow(dead_code)]
pub fn frame_total_ns(t: &GpuTimeline) -> u64 {
t.samples
.iter()
.filter(|s| s.frame == t.current_frame)
.map(|s| s.duration_ns())
.sum()
}
#[allow(dead_code)]
pub fn slowest_pass(t: &GpuTimeline) -> Option<&str> {
t.samples
.iter()
.max_by_key(|s| s.duration_ns())
.map(|s| s.pass_name.as_str())
}
#[allow(dead_code)]
pub fn average_duration_ms(t: &GpuTimeline) -> f32 {
if t.samples.is_empty() {
return 0.0;
}
let total: u64 = t.samples.iter().map(|s| s.duration_ns()).sum();
(total as f32 / t.samples.len() as f32) / 1_000_000.0
}
#[allow(dead_code)]
pub fn has_spike(t: &GpuTimeline, threshold_ms: f32) -> bool {
t.samples.iter().any(|s| s.duration_ms() > threshold_ms)
}
#[allow(dead_code)]
pub fn gpu_timeline_to_json(t: &GpuTimeline) -> String {
format!(
r#"{{"sample_count":{},"frame":{},"avg_ms":{:.4}}}"#,
t.samples.len(),
t.current_frame,
average_duration_ms(t)
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_timeline_empty() {
let t = new_gpu_timeline(GpuTimelineConfig::default());
assert_eq!(sample_count(&t), 0);
}
#[test]
fn record_sample_ok() {
let mut t = new_gpu_timeline(GpuTimelineConfig::default());
assert!(record_sample(&mut t, "shadow", 0, 1_000_000));
}
#[test]
fn sample_duration_ns() {
let s = GpuTimeSample {
pass_name: "test".to_string(),
start_ns: 100,
end_ns: 200,
frame: 0,
};
assert_eq!(s.duration_ns(), 100);
}
#[test]
fn duration_ms() {
let s = GpuTimeSample {
pass_name: "p".to_string(),
start_ns: 0,
end_ns: 1_000_000,
frame: 0,
};
assert!((s.duration_ms() - 1.0).abs() < 1e-4);
}
#[test]
fn frame_increments() {
let mut t = new_gpu_timeline(GpuTimelineConfig::default());
begin_frame(&mut t);
assert_eq!(t.current_frame, 1);
}
#[test]
fn slowest_pass_found() {
let mut t = new_gpu_timeline(GpuTimelineConfig::default());
record_sample(&mut t, "fast", 0, 100);
record_sample(&mut t, "slow", 0, 1_000_000);
assert_eq!(slowest_pass(&t), Some("slow"));
}
#[test]
fn average_ms_correct() {
let mut t = new_gpu_timeline(GpuTimelineConfig::default());
record_sample(&mut t, "a", 0, 2_000_000);
record_sample(&mut t, "b", 0, 2_000_000);
assert!((average_duration_ms(&t) - 2.0).abs() < 1e-3);
}
#[test]
fn has_spike_detects() {
let mut t = new_gpu_timeline(GpuTimelineConfig::default());
record_sample(&mut t, "heavy", 0, 20_000_000);
assert!(has_spike(&t, 10.0));
}
#[test]
fn clear_removes_all() {
let mut t = new_gpu_timeline(GpuTimelineConfig::default());
record_sample(&mut t, "x", 0, 1);
clear_timeline(&mut t);
assert_eq!(sample_count(&t), 0);
}
#[test]
fn json_contains_sample_count() {
let t = new_gpu_timeline(GpuTimelineConfig::default());
assert!(gpu_timeline_to_json(&t).contains("sample_count"));
}
}