From b1f4cab902102c532e9383f28329f1ed61c663be Mon Sep 17 00:00:00 2001 From: hikari_no_yume Date: Thu, 31 Aug 2023 18:07:43 +0200 Subject: [PATCH] Add FPS counter option (--print-fps) --- CHANGELOG.md | 1 + OPTIONS_HELP.txt | 3 +++ src/frameworks/core_animation/composition.rs | 12 ++++++++- src/frameworks/opengles/eagl.rs | 13 ++++++++- src/gles/present.rs | 28 ++++++++++++++++++++ src/options.rs | 4 +++ 6 files changed, 59 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c4c87e04..65b7240f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ Usability: - The new `--fullscreen` option lets you display an app in fullscreen rather than in a window. This is independent of the internal resolution/scale hack and supports both upscaling and downscaling. (@hikari-no-yume) - touchHLE now has a built-in app picker with a pretty icon grid. Specifying an app on the command-line bypasses it. (@hikari-no-yume) - The new `--button-to-touch=` option lets you map a button on your game controller to a point on the touch screen. touchHLE also now includes default button mappings for some games. (@hikari-no-yume) +- The new `--print-fps` option lets you monitor an touchHLE's framerate from the console. (@hikari-no-yume) Other: diff --git a/OPTIONS_HELP.txt b/OPTIONS_HELP.txt index 9ee358d50..5d671b8ab 100644 --- a/OPTIONS_HELP.txt +++ b/OPTIONS_HELP.txt @@ -124,3 +124,6 @@ Other options: --headless Run in headless mode. touchHLE will not create a window, so there will be no graphical output and no input. Only useful for command-line apps. + + --print-fps + Logs the current framerate (FPS) to the console once per second. diff --git a/src/frameworks/core_animation/composition.rs b/src/frameworks/core_animation/composition.rs index 2d3c4eceb..43bd48cbf 100644 --- a/src/frameworks/core_animation/composition.rs +++ b/src/frameworks/core_animation/composition.rs @@ -17,7 +17,7 @@ use crate::frameworks::core_graphics::{ use crate::frameworks::uikit::ui_color; use crate::gles::gles11_raw as gles11; // constants only use crate::gles::gles11_raw::types::*; -use crate::gles::present::present_frame; +use crate::gles::present::{present_frame, FpsCounter}; use crate::gles::GLES; use crate::mem::Mem; use crate::objc::{id, msg, msg_class, nil, ObjC}; @@ -28,6 +28,7 @@ use std::time::{Duration, Instant}; pub(super) struct State { texture_framebuffer: Option<(GLuint, GLuint)>, recomposite_next: Option, + fps_counter: Option, } /// For use by `NSRunLoop`: call this 60 times per second. Composites the app's @@ -51,6 +52,15 @@ pub fn recomposite_if_necessary(env: &mut Environment) -> Option { return None; } + if env.options.print_fps { + env.framework_state + .core_animation + .composition + .fps_counter + .get_or_insert_with(FpsCounter::start) + .count_frame(format_args!("Core Animation compositor")); + } + let now = Instant::now(); let interval = 1.0 / 60.0; // 60Hz let new_recomposite_next = if let Some(recomposite_next) = env diff --git a/src/frameworks/opengles/eagl.rs b/src/frameworks/opengles/eagl.rs index 07c86582d..6c922cc6c 100644 --- a/src/frameworks/opengles/eagl.rs +++ b/src/frameworks/opengles/eagl.rs @@ -13,7 +13,7 @@ use crate::frameworks::foundation::ns_string::get_static_str; use crate::frameworks::foundation::NSUInteger; use crate::gles::gles11_raw as gles11; // constants only use crate::gles::gles11_raw::types::*; -use crate::gles::present::present_frame; +use crate::gles::present::{present_frame, FpsCounter}; use crate::gles::{create_gles1_ctx, gles1_on_gl2, GLES}; use crate::objc::{id, msg, nil, objc_classes, release, retain, ClassExports, HostObject}; use crate::window::Window; @@ -59,6 +59,7 @@ pub(super) struct EAGLContextHostObject { /// Mapping of OpenGL ES renderbuffer names to `EAGLDrawable` instances /// (always `CAEAGLLayer*`). Retains the instance so it won't dangle. renderbuffer_drawable_bindings: HashMap, + fps_counter: Option, } impl HostObject for EAGLContextHostObject {} @@ -72,6 +73,7 @@ pub const CLASSES: ClassExports = objc_classes! { let host_object = Box::new(EAGLContextHostObject { gles_ctx: None, renderbuffer_drawable_bindings: HashMap::new(), + fps_counter: None, }); env.objc.alloc_object(this, host_object, &mut env.mem) } @@ -190,6 +192,15 @@ pub const CLASSES: ClassExports = objc_classes! { - (bool)presentRenderbuffer:(NSUInteger)target { assert!(target == gles11::RENDERBUFFER_OES); + if env.options.print_fps { + env + .objc + .borrow_mut::(this) + .fps_counter + .get_or_insert_with(FpsCounter::start) + .count_frame(format_args!("EAGLContext {:?}", this)); + } + let fullscreen_layer = find_fullscreen_eagl_layer(env); // Unclear from documentation if this method requires the context to be diff --git a/src/gles/present.rs b/src/gles/present.rs index 597f5c5d3..230ce8a22 100644 --- a/src/gles/present.rs +++ b/src/gles/present.rs @@ -9,6 +9,34 @@ use super::gles11_raw as gles11; // constants and types only use super::GLES; use crate::matrix::Matrix; +use std::time::{Duration, Instant}; + +pub struct FpsCounter { + time: std::time::Instant, + frames: u32, +} +impl FpsCounter { + pub fn start() -> Self { + FpsCounter { + time: Instant::now(), + frames: 0, + } + } + + pub fn count_frame(&mut self, label: std::fmt::Arguments<'_>) { + self.frames += 1; + let now = Instant::now(); + let duration = now - self.time; + if duration >= Duration::from_secs(1) { + self.time = now; + echo!( + "touchHLE: {} FPS: {:.2}", + label, + std::mem::take(&mut self.frames) as f32 / duration.as_secs_f32() + ); + } + } +} /// Present the the latest frame (e.g. the app's splash screen or rendering /// output), provided as a texture bound to `GL_TEXTURE_2D`, by drawing it on diff --git a/src/options.rs b/src/options.rs index d01be6381..c36552bd1 100644 --- a/src/options.rs +++ b/src/options.rs @@ -39,6 +39,7 @@ pub struct Options { pub direct_memory_access: bool, pub gdb_listen_addrs: Option>, pub headless: bool, + pub print_fps: bool, } impl Default for Options { @@ -57,6 +58,7 @@ impl Default for Options { direct_memory_access: true, gdb_listen_addrs: None, headless: false, + print_fps: false, } } } @@ -132,6 +134,8 @@ impl Options { self.gdb_listen_addrs = Some(addrs); } else if arg == "--headless" { self.headless = true; + } else if arg == "--print-fps" { + self.print_fps = true; } else { return Ok(false); };