From bee1b66b55d633c1dc814831f53e113f56f617c8 Mon Sep 17 00:00:00 2001 From: Asuro Date: Sun, 31 Oct 2021 16:12:32 +0100 Subject: [PATCH] perf counter macros --- .gitignore | 1 + Cargo.toml | 1 + rust-engine-proc/Cargo.toml | 13 ++++++++ rust-engine-proc/src/lib.rs | 28 ++++++++++++++++ src/game/entities.rs | 24 ++++++++------ src/game/mod.rs | 7 ++-- src/input.rs | 2 ++ src/main.rs | 1 + src/perf.rs | 64 +++++++++++++++++++++++++++++++++++++ src/vulkan/mod.rs | 29 ++++++----------- 10 files changed, 136 insertions(+), 34 deletions(-) create mode 100644 rust-engine-proc/Cargo.toml create mode 100644 rust-engine-proc/src/lib.rs create mode 100644 src/perf.rs diff --git a/.gitignore b/.gitignore index d67d11d..89eb01d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Generated by Cargo # will have compiled files and executables /target/ +/rust-engine-proc/target/ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html diff --git a/Cargo.toml b/Cargo.toml index 9588f42..94e785e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ gilrs = "0.7" gltf = "0.15" glyph_brush = "0.7" winapi = "0.3" +rust-engine-proc = { path = "rust-engine-proc" } [profile.release] debug = 1 diff --git a/rust-engine-proc/Cargo.toml b/rust-engine-proc/Cargo.toml new file mode 100644 index 0000000..ab0723f --- /dev/null +++ b/rust-engine-proc/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "rust-engine-proc" +version = "0.1.0" +edition = "2018" + +[lib] +proc-macro = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +syn = { version = "1.0", features = ["full"] } +quote = "1.0" \ No newline at end of file diff --git a/rust-engine-proc/src/lib.rs b/rust-engine-proc/src/lib.rs new file mode 100644 index 0000000..55c96e8 --- /dev/null +++ b/rust-engine-proc/src/lib.rs @@ -0,0 +1,28 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use quote::quote; +use syn::{AttributeArgs, ItemFn, Lit, Meta, parse_macro_input, parse_quote}; + +#[proc_macro_attribute] +pub fn perf(attr: TokenStream, item: TokenStream) -> TokenStream { + let attrs = parse_macro_input!(attr as AttributeArgs); + let name = match &attrs[0] { + syn::NestedMeta::Lit(Lit::Str(l)) => l.value(), + _ => panic!("Invalid attribute"), + }; + let counter_type = match &attrs[1] { + syn::NestedMeta::Meta(Meta::Path(p)) => p, + _ => panic!("Invalid attribute"), + }; + let mut function = parse_macro_input!(item as ItemFn); + function.block.stmts.insert(0, parse_quote! { + let __perf_start = std::time::Instant::now(); + }); + function.block.stmts.push(parse_quote! { + unsafe { + #counter_type::write_perf(#name, __perf_start.elapsed().as_micros()); + } + }); + quote!(#function).into() +} diff --git a/src/game/entities.rs b/src/game/entities.rs index b0a8e8c..08ca2f4 100644 --- a/src/game/entities.rs +++ b/src/game/entities.rs @@ -1,17 +1,18 @@ use cgmath::{Vector4, vec4}; -use crate::{input::InputState, text::{create_text_object, update_text}, vulkan::{MeshHandle, PERF_COUNTER_SIZE, TextVertex, Vertex, VulkanRenderer, gameobject::{GameObject, GameObjectHandle, Updatable}, mesh::{CPUMesh, CPUVertexList}}}; +use crate::{input::InputState, text::{create_text_object, update_text}, vulkan::{MeshHandle, TextVertex, Vertex, VulkanRenderer, gameobject::{GameObject, GameObjectHandle, Updatable}, mesh::{CPUMesh, CPUVertexList}}}; use super::{GameState, TestGame}; pub struct FpsCounter { - pub game_object: GameObjectHandle + pub game_object: GameObjectHandle, + pub text: String, } impl FpsCounter { pub fn new(game: &mut TestGame, renderer: &mut VulkanRenderer) -> FpsCounter { let text_mesh = create_text_object(&mut game.game_state.brush, renderer, "", 30.); - FpsCounter { game_object: game.add_game_object(renderer, text_mesh) } + FpsCounter { game_object: game.add_game_object(renderer, text_mesh), text: String::new() } } } @@ -22,12 +23,17 @@ impl Updatable for FpsCounter { go.visible = !go.visible; } - let update_duration = renderer.game_data.update_perf_counters.iter().sum::() / PERF_COUNTER_SIZE as u128; - let render_duration = renderer.game_data.render_perf_counters.iter().sum::() / PERF_COUNTER_SIZE as u128; - let other_duration = renderer.game_data.other_perf_counters.iter().sum::() / PERF_COUNTER_SIZE as u128; - let other_variance = renderer.game_data.other_perf_counters.iter().fold(0, |acc, b| acc + (*b as i128 - other_duration as i128) * (*b as i128 - other_duration as i128)) / PERF_COUNTER_SIZE as i128; - let text = format!("Update: {:.0}ms\nRender: {:.0}ms\nTotal: {:.0}ms (±{:.1})\nDelta: {:.0}ms", update_duration as f64 / 1000., render_duration as f64 / 1000., other_duration as f64 / 1000., f64::sqrt(other_variance as f64) / 1000., _delta_time * 1000.); - update_text(self.game_object, &text, 30., renderer, &mut game_state.brush, game_objects); + self.text.clear(); + unsafe { + if let Some(pcs) = &crate::perf::PERF_COUNTERS { + for (name, pc) in pcs { + let mean = pc.mean(); + let stddev = pc.stddev(mean as i128); + self.text.push_str(&format!("{}: {:.1}ms (±{:.1})\n", name, (mean as f64) / 1000., stddev / 1000.)); + } + } + } + update_text(self.game_object, &self.text, 30., renderer, &mut game_state.brush, game_objects); } } diff --git a/src/game/mod.rs b/src/game/mod.rs index 8366764..d163da7 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -1,6 +1,7 @@ use std::time::Instant; use cgmath::{Deg, Euler, Quaternion, vec3}; use glyph_brush::GlyphBrush; +use rust_engine_proc::perf; use winit::event::Event; use level::{load_level, save_level}; @@ -56,9 +57,8 @@ impl Game for TestGame { self.input.on_window_event(event); } + #[perf("update", crate::perf::PerformanceCounter)] fn update(self: &mut Self, renderer: &mut VulkanRenderer) { - let precise_start = Instant::now(); - // Input and timing self.input.frame_start(); let time = (renderer.game_data.start_time.elapsed().unwrap().as_micros() as f64 / 1000000.0) as f32; @@ -126,9 +126,6 @@ impl Game for TestGame { _dummy1: [0; 4], _dummy2: [0; 4], }; - - let precise_duration = precise_start.elapsed().as_micros(); - renderer.game_data.update_perf_counters[renderer.game_data.performance_counter_index] = precise_duration; } fn get_ubo(&self) -> &vs::ty::ObjectUniformData { diff --git a/src/input.rs b/src/input.rs index c266459..504a27c 100644 --- a/src/input.rs +++ b/src/input.rs @@ -8,6 +8,7 @@ use std::iter::FromIterator; use cgmath::{InnerSpace, Vector2, Zero, vec2}; use gilrs; use gilrs::{EventType, Gilrs}; +use rust_engine_proc::perf; use serde_derive::{Deserialize, Serialize}; use toml; use winapi::shared::minwindef::UINT; @@ -233,6 +234,7 @@ impl InputState { return state; } + #[perf("input_events", crate::perf::PerformanceCounter)] pub fn on_window_event(self: &mut Self, event: &Event<()>) { match event { Event::WindowEvent { event: WindowEvent::KeyboardInput { device_id, input, .. }, .. } => { diff --git a/src/main.rs b/src/main.rs index 8b258e8..d09be00 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,7 @@ mod game; mod tests; mod text; mod util; +mod perf; fn main() { let log_config = LogConfig::from_file("config/log.toml"); diff --git a/src/perf.rs b/src/perf.rs new file mode 100644 index 0000000..458f692 --- /dev/null +++ b/src/perf.rs @@ -0,0 +1,64 @@ +use std::collections::HashMap; + +pub const PERF_COUNTER_SIZE: usize = 100; +pub static mut PERF_COUNTERS: Option> = None; + +pub struct PerformanceCounter { + pub values: [u128; PERF_COUNTER_SIZE], + pub index: usize, +} + +impl PerformanceCounter { + pub fn mean(&self) -> u128 { + self.values.iter().sum::() / PERF_COUNTER_SIZE as u128 + } + + pub fn stddev(&self, mean: i128) -> f64 { + f64::sqrt(self.values.iter().fold(0, |acc, b| acc + (*b as i128 - mean) * (*b as i128 - mean)) as f64 / PERF_COUNTER_SIZE as f64) + } + + pub fn init_perf() { + unsafe { + if PERF_COUNTERS.is_none() { PERF_COUNTERS = Some(HashMap::new()); } + } + } + + pub fn write_perf(name: &str, value: u128) { + unsafe { + if let Some(pcs) = &mut PERF_COUNTERS { + if let Some(pc) = pcs.get_mut(name) { + pc.values[pc.index] = value; + } else { + let mut pc = PerformanceCounter { values: [0; PERF_COUNTER_SIZE], index: 0 }; + pc.values[0] = value; + pcs.insert(name.to_string(), pc); + } + } + } + } + + pub fn _get_perf(name: &str) -> u128 { + unsafe { + if let Some(pcs) = &mut PERF_COUNTERS { + if let Some(pc) = pcs.get_mut(name) { + pc.mean() + } else { + 0 + } + } else { + 0 + } + } + } + + pub fn perf_next_frame(name: &str) { + unsafe { + if let Some(pcs) = &mut PERF_COUNTERS { + if let Some(pc) = pcs.get_mut(name) { + pc.index = (pc.index + 1) % PERF_COUNTER_SIZE; + pc.values[pc.index] = 0; + } + } + } + } +} \ No newline at end of file diff --git a/src/vulkan/mod.rs b/src/vulkan/mod.rs index 3e65544..31723df 100644 --- a/src/vulkan/mod.rs +++ b/src/vulkan/mod.rs @@ -1,8 +1,9 @@ -use std::{sync::Arc, time::Instant}; +use std::sync::Arc; use std::time::SystemTime; use cgmath::{Matrix4, SquareMatrix}; use dds::get_block_size; +use rust_engine_proc::perf; use vulkano::device::physical::PhysicalDevice; use vulkano::pipeline::viewport::Viewport; use vulkano::render_pass::{FramebufferAbstract, RenderPass}; @@ -29,6 +30,7 @@ use pipelines::vs; use winit::window::{Window, WindowBuilder}; use crate::config::RenderConfig; +use crate::perf::PerformanceCounter; use crate::vulkan::gameobject::GameObject; use self::mesh::CPUVertexList; @@ -44,8 +46,6 @@ const VALIDATION_LAYERS: &[&str] = &[ "VK_LAYER_KHRONOS_validation", ]; -pub const PERF_COUNTER_SIZE: usize = 1000; - #[derive(Default, Debug, Clone)] pub struct Vertex { pub position: [f32; 3], @@ -114,11 +114,6 @@ pub struct GameData { pub meshes: Vec>, pub meshes_text: Vec>, pub textures: Vec, - pub update_perf_counters: [u128; PERF_COUNTER_SIZE], - pub render_perf_counters: [u128; PERF_COUNTER_SIZE], - pub other_perf_counters: [u128; PERF_COUNTER_SIZE], - pub other_perf_instant: Instant, - pub performance_counter_index: usize, } pub struct VulkanRenderer { @@ -149,11 +144,6 @@ impl VulkanRenderer { meshes: vec![], meshes_text: vec![], textures: vec![], - update_perf_counters: [0; PERF_COUNTER_SIZE], - render_perf_counters: [0; PERF_COUNTER_SIZE], - other_perf_counters: [0; PERF_COUNTER_SIZE], - performance_counter_index: 0, - other_perf_instant: Instant::now(), }; // Create basic vulkan instance with layers and info @@ -342,9 +332,8 @@ impl VulkanRenderer { Arc::new(builder.build().unwrap()) } + #[perf("renderer", crate::perf::PerformanceCounter)] pub fn render_loop(self: &mut Self, new_ubo: &vs::ty::ObjectUniformData, game_objects: &Vec) { - let precise_start = Instant::now(); - // cleanup previous frame self.previous_frame_end.as_mut().unwrap().cleanup_finished(); @@ -438,8 +427,6 @@ impl VulkanRenderer { self.previous_frame_end = Some(Box::new(sync::now(self.device.clone())) as Box<_>); } }; - - self.game_data.render_perf_counters[self.game_data.performance_counter_index] = precise_start.elapsed().as_micros(); } pub fn upload_mesh(self: &mut Self, mesh: CPUMesh, original_path: Option) -> usize { @@ -676,6 +663,8 @@ impl VulkanRenderer { } pub fn start_event_loop(mut renderer: VulkanRenderer, mut game: Box, event_loop: EventLoop<()>) { + PerformanceCounter::init_perf(); + event_loop.run(move |event, _, control_flow| { game.on_window_event(&event); @@ -691,12 +680,12 @@ pub fn start_event_loop(mut renderer: VulkanRenderer, mut game: Box, e renderer.recreate_swapchain = true; }, Event::RedrawRequested(..) => { + PerformanceCounter::perf_next_frame("renderer"); renderer.render_loop(game.get_ubo(), &game.get_game_objects()); - renderer.game_data.other_perf_counters[renderer.game_data.performance_counter_index] = renderer.game_data.other_perf_instant.elapsed().as_micros(); - renderer.game_data.other_perf_instant = Instant::now(); - renderer.game_data.performance_counter_index = (renderer.game_data.performance_counter_index + 1) % PERF_COUNTER_SIZE; }, Event::MainEventsCleared => { + PerformanceCounter::perf_next_frame("update"); + PerformanceCounter::perf_next_frame("input_events"); game.update(&mut renderer); renderer.surface.window().request_redraw(); },