Skip to main content

gltf_viewer_lib/
utils.rs

1#![macro_use]
2
3use std::ffi::CStr;
4use std::time::{Duration, Instant};
5
6use gl;
7use gl::types::GLubyte;
8
9use log::{debug, info, error};
10
11pub fn elapsed(start_time: Instant) -> String {
12    let elapsed = start_time.elapsed();
13    format_duration(elapsed)
14}
15
16fn format_duration(duration: Duration) -> String {
17    let secs = duration.as_secs();
18    let nanos = duration.subsec_nanos();
19    let ms = f64::from(nanos) / 1_000_000.0;
20    if secs > 0 {
21        let secs = secs as f64 + ms / 1000.0;
22        format!("{:<4.*} s", 1, secs)
23    }
24    else {
25        let places =
26            if ms >= 20.0      { 0 }
27            else if ms >= 1.0  { 1 }
28            else {
29                let micros = f64::from(nanos) / 1000.0;
30                let places = if micros >= 10.0 { 0 } else { 2 };
31                return format!("{:>3.*} µs", places, micros)
32            };
33        format!("{:>3.*} ms", places, ms)
34    }
35}
36
37pub fn print_elapsed(message: &str, start_time: Instant) {
38    info!("{:<25}{}", message, elapsed(start_time));
39}
40
41pub struct FrameTimer {
42    message: String,
43    averaging_window: usize,
44    current_frame_start: Instant,
45    pub frame_times: Vec<Duration>,
46}
47
48/// Timing helper that averages timings over `averaging_window`
49// frames and then prints avg/min/max
50impl FrameTimer {
51    pub fn new(message: &str, averaging_window: usize) -> FrameTimer {
52        FrameTimer {
53            message: message.to_owned(),
54            averaging_window,
55            current_frame_start: Instant::now(),
56            frame_times: Vec::with_capacity(averaging_window),
57        }
58    }
59
60    pub fn start(&mut self) {
61        self.current_frame_start = Instant::now();
62    }
63
64    pub fn end(&mut self) {
65        self.frame_times.push(self.current_frame_start.elapsed());
66        if self.frame_times.len() == self.averaging_window {
67            self.print_and_reset();
68        }
69    }
70
71    pub fn print_and_reset(&mut self) {
72        {
73            let avg = self.frame_times.iter().sum::<Duration>() / self.frame_times.len() as u32;
74            let min = self.frame_times.iter().min().unwrap();
75            let max = self.frame_times.iter().max().unwrap();
76            info!("{:<15}{} (min: {}, max: {})", self.message,
77                format_duration(avg), format_duration(*min), format_duration(*max));
78        }
79        self.frame_times.clear();
80    }
81}
82
83pub unsafe fn gl_check_error(file: &str, line: u32) -> u32 {
84    let mut error_code = gl::GetError();
85    while error_code != gl::NO_ERROR {
86        let error = match error_code {
87            gl::INVALID_ENUM => "INVALID_ENUM",
88            gl::INVALID_VALUE => "INVALID_VALUE",
89            gl::INVALID_OPERATION => "INVALID_OPERATION",
90            gl::STACK_OVERFLOW => "STACK_OVERFLOW",
91            gl::STACK_UNDERFLOW => "STACK_UNDERFLOW",
92            gl::OUT_OF_MEMORY => "OUT_OF_MEMORY",
93            gl::INVALID_FRAMEBUFFER_OPERATION => "INVALID_FRAMEBUFFER_OPERATION",
94            _ => "unknown GL error code"
95        };
96
97        error!("{} | {} ({})", error, file, line);
98
99        error_code = gl::GetError();
100    }
101    error_code
102}
103
104#[allow(unused_macros)]
105macro_rules! gl_check_error {
106    () => (
107        gl_check_error(file!(), line!())
108    )
109}
110
111/// Prints information about the current OpenGL context
112/// Based on glium's `context::capabilities::get_capabilities`
113pub unsafe fn print_context_info() {
114    debug!("Renderer     : {}", gl_string(gl::GetString(gl::RENDERER)));
115    debug!("Vendor       : {}", gl_string(gl::GetString(gl::VENDOR)));
116    debug!("Version      : {}", gl_string(gl::GetString(gl::VERSION)));
117    debug!("GLSL         : {}", gl_string(gl::GetString(gl::SHADING_LANGUAGE_VERSION)));
118
119    let mut val = 0;
120    gl::GetIntegerv(gl::CONTEXT_PROFILE_MASK, &mut val);
121    let val = val as gl::types::GLenum;
122    let profile = if (val & gl::CONTEXT_COMPATIBILITY_PROFILE_BIT) != 0 {
123        "Compatibility"
124    } else if (val & gl::CONTEXT_CORE_PROFILE_BIT) != 0 {
125        "Core"
126    } else {
127        "None"
128    };
129    debug!("Profile      : {}", profile);
130
131    let (debug, forward_compatible) = {
132        let mut val = 0;
133        gl::GetIntegerv(gl::CONTEXT_FLAGS, &mut val);
134        let val = val as gl::types::GLenum;
135        ((val & gl::CONTEXT_FLAG_DEBUG_BIT) != 0,
136         (val & gl::CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT) != 0)
137    };
138    debug!("Context Flags: Debug: {}, Forward Compatible: {}", debug, forward_compatible);
139
140    let mut num_extensions = 0;
141    gl::GetIntegerv(gl::NUM_EXTENSIONS, &mut num_extensions);
142    let extensions: Vec<_> = (0 .. num_extensions).map(|num| {
143        gl_string(gl::GetStringi(gl::EXTENSIONS, num as gl::types::GLuint))
144    }).collect();
145    debug!("Extensions   : {}", extensions.join(", "))
146}
147
148pub unsafe fn gl_string(raw_string: *const GLubyte) -> String {
149    if raw_string.is_null() { return "(NULL)".into() }
150    String::from_utf8(CStr::from_ptr(raw_string as *const _).to_bytes().to_vec())
151                                .expect("gl_string: non-UTF8 string")
152}