logo
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// Copyright © SixtyFPS GmbH <info@sixtyfps.io>
// SPDX-License-Identifier: (GPL-3.0-only OR LicenseRef-SixtyFPS-commercial)

/*!
This module contains a simple helper type to measure the average number of frames rendered per second.
*/

use crate::timers::*;
use std::cell::RefCell;
use std::convert::TryFrom;
use std::rc::Rc;

enum RefreshMode {
    Lazy,      // render only when necessary (default)
    FullSpeed, // continuously render to the screen
}

impl<'a> TryFrom<&Vec<&'a str>> for RefreshMode {
    type Error = ();

    fn try_from(options: &Vec<&'a str>) -> Result<Self, Self::Error> {
        if options.contains(&"refresh_lazy") {
            Ok(Self::Lazy)
        } else if options.contains(&"refresh_full_speed") {
            Ok(Self::FullSpeed)
        } else {
            Err(())
        }
    }
}

/// Helper class that rendering backends can use to provide an FPS counter
pub struct FPSCounter {
    frame_times: RefCell<Vec<instant::Instant>>,
    update_timer: Timer,
    refresh_mode: RefreshMode,
    output_console: bool,
    output_overlay: bool,
}

impl FPSCounter {
    /// Returns a new instance of the counter if requested by the user (via `SIXTYFPS_DEBUG_PERFORMANCE` environment variable).
    /// The environment variable holds a comma separated list of options:
    ///     * `refresh_lazy`: selects the lazy refresh mode, where measurements are only taken when a frame is rendered (due to user input or animations)
    ///     * `refresh_full_speed`: frames are continuously rendered
    ///     * `console`: the measurement is printed to the console
    ///     * `overlay`: the measurement is drawn as overlay on top of the scene
    pub fn new() -> Option<Rc<Self>> {
        let options = match std::env::var("SIXTYFPS_DEBUG_PERFORMANCE") {
            Ok(var) => var,
            _ => return None,
        };
        let options: Vec<&str> = options.split(',').collect();

        let refresh_mode = match RefreshMode::try_from(&options) {
            Ok(mode) => mode,
            Err(_) => {
                eprintln!("Missing refresh mode in SIXTYFPS_DEBUG_PERFORMANCE. Please specify either refresh_full_speed or refresh_lazy");
                return None;
            }
        };

        let output_console = options.contains(&"console");
        let output_overlay = options.contains(&"overlay");

        if !output_console && !output_overlay {
            eprintln!("Missing output mode in SIXTYFPS_DEBUG_PERFORMANCE. Please specify either console or overlay (or both)");
            return None;
        }

        Some(Rc::new(Self {
            frame_times: Default::default(),
            update_timer: Default::default(),
            refresh_mode,
            output_console,
            output_overlay,
        }))
    }

    /// Call this function if you want to start measurements. This will also print out some system information such as whether
    /// this is a debug or release build, as well as the provided winsys_info string.
    pub fn start(self: &Rc<Self>, winsys_info: &str) {
        #[cfg(debug_assertions)]
        let build_config = "debug";
        #[cfg(not(debug_assertions))]
        let build_config = "release";

        eprintln!("SixtyFPS: Build config: {}; Backend: {}", build_config, winsys_info);

        let this = self.clone();
        self.update_timer.stop();
        self.update_timer.start(TimerMode::Repeated, std::time::Duration::from_secs(1), move || {
            this.trim_frame_times();
            if this.output_console {
                eprintln!("average frames per second: {}", this.frame_times.borrow().len());
            }
        })
    }

    fn trim_frame_times(self: &Rc<Self>) {
        let mut i = 0;
        let mut frame_times = self.frame_times.borrow_mut();
        while i < frame_times.len() {
            if frame_times[i].elapsed() > std::time::Duration::from_secs(1) {
                frame_times.remove(i);
            } else {
                i += 1
            }
        }
    }

    /// Call this function every time you've completed the rendering of a frame.
    pub fn measure_frame_rendered(
        self: &Rc<Self>,
        renderer_for_overlay: &mut dyn crate::item_rendering::ItemRenderer,
    ) {
        self.frame_times.borrow_mut().push(instant::Instant::now());
        if matches!(self.refresh_mode, RefreshMode::FullSpeed) {
            crate::animations::CURRENT_ANIMATION_DRIVER
                .with(|driver| driver.set_has_active_animations());
        }
        self.trim_frame_times();

        if self.output_overlay {
            renderer_for_overlay.draw_string(
                &format!("FPS: {}", self.frame_times.borrow().len()),
                crate::Color::from_rgb_u8(0, 128, 128),
            );
        }
    }
}