Add FPS counter option (--print-fps)

This commit is contained in:
hikari_no_yume
2023-08-31 18:07:43 +02:00
parent 2dc19e0646
commit b1f4cab902
6 changed files with 59 additions and 2 deletions

View File

@@ -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:

View File

@@ -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.

View File

@@ -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<Instant>,
fps_counter: Option<FpsCounter>,
}
/// 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<Instant> {
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

View File

@@ -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<GLuint, id>,
fps_counter: Option<FpsCounter>,
}
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::<EAGLContextHostObject>(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

View File

@@ -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

View File

@@ -39,6 +39,7 @@ pub struct Options {
pub direct_memory_access: bool,
pub gdb_listen_addrs: Option<Vec<SocketAddr>>,
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);
};