Skip to main content

ass_renderer/debug/benchmarking/
mod.rs

1//! Benchmarking and performance analysis for the renderer
2
3use crate::renderer::{RenderContext, Renderer};
4use crate::utils::RenderError;
5use ass_core::parser::Script;
6
7#[cfg(feature = "nostd")]
8use alloc::{format, vec::Vec};
9#[cfg(not(feature = "nostd"))]
10use std::{format, time::Instant, vec::Vec};
11
12mod animation;
13mod report;
14mod types;
15
16pub use types::{
17    BenchmarkConfig, BenchmarkResult, PerformanceMetrics, PerformanceReport, PerformanceSummary,
18};
19
20/// Performance benchmarking system for renderer comparison
21pub struct PerformanceBenchmark {
22    /// Our renderer instance
23    our_renderer: Renderer,
24    /// Benchmark configuration
25    config: BenchmarkConfig,
26    /// Historical results for regression analysis
27    historical_results: Vec<BenchmarkResult>,
28}
29
30impl PerformanceBenchmark {
31    /// Create new performance benchmark
32    pub fn new(context: RenderContext, config: BenchmarkConfig) -> Result<Self, RenderError> {
33        let our_renderer = Renderer::new(crate::backends::BackendType::Software, context)?;
34
35        Ok(Self {
36            our_renderer,
37            config,
38            historical_results: Vec::new(),
39        })
40    }
41
42    /// Run comprehensive benchmark on a script
43    pub fn benchmark_script(
44        &mut self,
45        script: &Script,
46        test_name: &str,
47    ) -> Result<Vec<BenchmarkResult>, RenderError> {
48        let mut results = Vec::new();
49        let test_resolutions = self.config.test_resolutions.clone();
50
51        for &resolution in &test_resolutions {
52            eprintln!(
53                "Benchmarking {test_name} at {}x{}",
54                resolution.0, resolution.1
55            );
56
57            // Update context resolution
58            let context = RenderContext::new(resolution.0, resolution.1);
59            self.our_renderer = Renderer::new(crate::backends::BackendType::Software, context)?;
60
61            let result = self.benchmark_single_resolution(script, test_name, resolution)?;
62            results.push(result);
63        }
64
65        // Store results for regression analysis
66        self.historical_results.extend(results.clone());
67
68        Ok(results)
69    }
70
71    /// Benchmark at a single resolution
72    fn benchmark_single_resolution(
73        &mut self,
74        script: &Script,
75        test_name: &str,
76        resolution: (u32, u32),
77    ) -> Result<BenchmarkResult, RenderError> {
78        let test_time_cs = self.find_representative_time(script);
79
80        // Benchmark our renderer
81        let our_performance = self.benchmark_our_renderer(script, test_time_cs)?;
82
83        // No reference renderer to compare against, so ratios are unavailable.
84        let (performance_ratio, memory_ratio): (Option<f64>, Option<f64>) = (None, None);
85
86        // Calculate compatibility score (simplified - would need actual pixel comparison)
87        let compatibility_score = 0.95; // Placeholder
88
89        Ok(BenchmarkResult {
90            test_name: format!("{test_name}_{}_{}x{}", "single", resolution.0, resolution.1),
91            resolution,
92            our_performance,
93            performance_ratio,
94            memory_ratio,
95            compatibility_score,
96        })
97    }
98
99    /// Benchmark our renderer performance
100    fn benchmark_our_renderer(
101        &mut self,
102        script: &Script,
103        time_cs: u32,
104    ) -> Result<PerformanceMetrics, RenderError> {
105        let mut render_times = Vec::new();
106        let mut memory_usage = Vec::new();
107
108        // Warmup
109        for _ in 0..self.config.warmup_iterations {
110            let _ = self.our_renderer.render_frame(script, time_cs)?;
111        }
112
113        // Actual measurements
114        for _ in 0..self.config.iterations {
115            let start_memory = self.measure_memory_usage();
116
117            #[cfg(not(feature = "nostd"))]
118            let start_time = Instant::now();
119
120            let _frame = self.our_renderer.render_frame(script, time_cs)?;
121
122            #[cfg(not(feature = "nostd"))]
123            let elapsed = start_time.elapsed();
124            #[cfg(feature = "nostd")]
125            let elapsed = std::time::Duration::from_millis(1); // Placeholder for no_std
126
127            let end_memory = self.measure_memory_usage();
128
129            render_times.push(elapsed.as_secs_f64() * 1000.0); // Convert to milliseconds
130
131            if let (Some(start), Some(end)) = (start_memory, end_memory) {
132                memory_usage.push(end.saturating_sub(start));
133            }
134        }
135
136        self.calculate_performance_metrics(render_times, memory_usage)
137    }
138
139    /// Calculate performance metrics from raw measurements
140    fn calculate_performance_metrics(
141        &self,
142        render_times: Vec<f64>,
143        memory_usage: Vec<usize>,
144    ) -> Result<PerformanceMetrics, RenderError> {
145        if render_times.is_empty() {
146            return Err(RenderError::InvalidInput(
147                "No render time measurements".to_string(),
148            ));
149        }
150
151        let avg_render_time = render_times.iter().sum::<f64>() / render_times.len() as f64;
152        let min_render_time = render_times.iter().fold(f64::INFINITY, |a, &b| a.min(b));
153        let max_render_time = render_times
154            .iter()
155            .fold(f64::NEG_INFINITY, |a, &b| a.max(b));
156
157        // Calculate standard deviation
158        let variance = render_times
159            .iter()
160            .map(|&t| (t - avg_render_time).powi(2))
161            .sum::<f64>()
162            / render_times.len() as f64;
163        let std_dev = variance.sqrt();
164
165        // Calculate FPS (frames per second)
166        let fps = if avg_render_time > 0.0 {
167            Some(1000.0 / avg_render_time)
168        } else {
169            None
170        };
171
172        // Memory statistics
173        let (peak_memory, avg_memory) = if !memory_usage.is_empty() {
174            let peak = memory_usage.iter().max().copied();
175            let avg = Some(memory_usage.iter().sum::<usize>() / memory_usage.len());
176            (peak, avg)
177        } else {
178            (None, None)
179        };
180
181        Ok(PerformanceMetrics {
182            avg_render_time_ms: avg_render_time,
183            min_render_time_ms: min_render_time,
184            max_render_time_ms: max_render_time,
185            render_time_std_dev: std_dev,
186            fps,
187            peak_memory_bytes: peak_memory,
188            avg_memory_bytes: avg_memory,
189            cache_hit_rate: None, // TODO: Implement cache metrics
190        })
191    }
192
193    /// Measure current memory usage (simplified)
194    fn measure_memory_usage(&self) -> Option<usize> {
195        // This is a simplified implementation
196        // In practice, you'd want to use a proper memory profiler
197        #[cfg(not(feature = "nostd"))]
198        {
199            // Use system APIs to get memory usage
200            // This is a placeholder - actual implementation would vary by platform
201            None
202        }
203        #[cfg(feature = "nostd")]
204        {
205            None
206        }
207    }
208
209    /// Find representative time for testing
210    fn find_representative_time(&self, script: &Script) -> u32 {
211        // Find middle of first event with text
212        for section in script.sections() {
213            if let ass_core::parser::Section::Events(events) = section {
214                for event in events {
215                    if !event.text.trim().is_empty() {
216                        let start = event.start_time_cs().unwrap_or(0);
217                        let end = event.end_time_cs().unwrap_or(0);
218                        return start + (end - start) / 2;
219                    }
220                }
221            }
222        }
223        100 // Default to 1 second
224    }
225}
226
227/// Quick benchmark function for single script
228pub fn quick_benchmark(script: &Script, test_name: &str) -> Result<BenchmarkResult, RenderError> {
229    let context = RenderContext::new(1920, 1080);
230    let config = BenchmarkConfig {
231        iterations: 5,
232        warmup_iterations: 1,
233        test_resolutions: vec![(1920, 1080)],
234        ..Default::default()
235    };
236
237    let mut benchmark = PerformanceBenchmark::new(context, config)?;
238    let results = benchmark.benchmark_script(script, test_name)?;
239
240    results
241        .into_iter()
242        .next()
243        .ok_or_else(|| RenderError::InvalidState("No benchmark results".to_string()))
244}