use concinnity_core::profile::{MAX_PASS_TIMINGS, RenderStats};
pub const MAX_SYSTEM_TIMINGS: usize = 32;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct FrameSample {
pub run_seconds: f32,
pub segment: Option<u32>,
pub frame_us: u32,
pub gpu_frame_us: u32,
pub gpu_wait_us: u32,
pub draw_calls: u32,
pub objects: u32,
pub vram_bytes: u64,
pub pass_us: [u32; MAX_PASS_TIMINGS],
pub system_us: [u32; MAX_SYSTEM_TIMINGS],
}
impl FrameSample {
pub fn new(
run_seconds: f32,
segment: Option<u32>,
frame_us: u32,
render: &RenderStats,
systems: &[(&'static str, u32)],
) -> Self {
let mut pass_us = [0_u32; MAX_PASS_TIMINGS];
for (slot, (_, us)) in pass_us.iter_mut().zip(render.pass_times_us.iter()) {
*slot = *us;
}
let mut system_us = [0_u32; MAX_SYSTEM_TIMINGS];
for (slot, (_, us)) in system_us.iter_mut().zip(systems.iter()) {
*slot = *us;
}
Self {
run_seconds,
segment,
frame_us,
gpu_frame_us: render.gpu_frame_us,
gpu_wait_us: render.gpu_wait_us,
draw_calls: render.draw_calls,
objects: render.objects,
vram_bytes: render.vram_bytes,
pass_us,
system_us,
}
}
}
#[derive(Debug, Default)]
pub struct FrameRun {
pub samples: Vec<FrameSample>,
pub segments: Vec<String>,
pub pass_names: Vec<String>,
pub system_names: Vec<String>,
pub completed: bool,
}
impl FrameRun {
pub fn segment_name(&self, index: Option<u32>) -> &str {
match index.and_then(|i| self.segments.get(i as usize)) {
Some(name) => name,
None => "(unnamed)",
}
}
pub fn capture_pass_names(&mut self, render: &RenderStats) {
if !self.pass_names.is_empty() {
return;
}
if render.pass_times_us.iter().all(|(name, _)| name.is_empty()) {
return;
}
self.pass_names = render
.pass_times_us
.iter()
.map(|(name, _)| (*name).to_string())
.collect();
}
pub fn capture_system_names(&mut self, systems: &[(&'static str, u32)]) {
if self.system_names.is_empty() {
self.system_names = systems
.iter()
.map(|(name, _)| (*name).to_string())
.collect();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn render_with(passes: &[(&'static str, u32)]) -> RenderStats {
let mut r = RenderStats {
gpu_frame_us: 8_000,
gpu_wait_us: 500,
draw_calls: 120,
objects: 900,
vram_bytes: 64 << 20,
..Default::default()
};
for (slot, entry) in r.pass_times_us.iter_mut().zip(passes.iter()) {
*slot = *entry;
}
r
}
#[test]
fn a_sample_carries_the_render_stats_and_the_pass_slots() {
let s = FrameSample::new(
1.5,
Some(2),
16_000,
&render_with(&[("main", 4_000)]),
&[("GraphicsSystem", 700)],
);
assert_eq!(s.run_seconds, 1.5);
assert_eq!(s.segment, Some(2));
assert_eq!(s.frame_us, 16_000);
assert_eq!((s.gpu_frame_us, s.gpu_wait_us), (8_000, 500));
assert_eq!((s.draw_calls, s.objects), (120, 900));
assert_eq!(s.vram_bytes, 64 << 20);
assert_eq!(s.pass_us[0], 4_000);
assert_eq!(s.pass_us[1], 0);
assert_eq!(s.system_us[0], 700);
assert_eq!(s.system_us[1], 0);
}
#[test]
fn system_names_are_captured_once_in_schedule_order() {
let mut run = FrameRun::default();
run.capture_system_names(&[]);
assert!(run.system_names.is_empty());
run.capture_system_names(&[("PhysicsSystem", 40), ("GraphicsSystem", 700)]);
assert_eq!(run.system_names, ["PhysicsSystem", "GraphicsSystem"]);
run.capture_system_names(&[("Other", 1)]);
assert_eq!(run.system_names, ["PhysicsSystem", "GraphicsSystem"]);
}
#[test]
fn pass_names_are_captured_once_from_the_first_frame_that_has_them() {
let mut run = FrameRun::default();
run.capture_pass_names(&render_with(&[]));
assert!(run.pass_names.is_empty());
run.capture_pass_names(&render_with(&[("shadow", 1), ("main", 2)]));
assert_eq!(&run.pass_names[..2], ["shadow", "main"]);
run.capture_pass_names(&render_with(&[("other", 9)]));
assert_eq!(&run.pass_names[..2], ["shadow", "main"]);
}
#[test]
fn a_segment_with_no_name_reads_as_a_placeholder_rather_than_panicking() {
let run = FrameRun {
segments: vec!["approach".to_string()],
..Default::default()
};
assert_eq!(run.segment_name(Some(0)), "approach");
assert_eq!(run.segment_name(None), "(unnamed)");
assert_eq!(run.segment_name(Some(7)), "(unnamed)");
}
}