diff --git a/build.rs b/build.rs index fe85e5b3..ce72218a 100644 --- a/build.rs +++ b/build.rs @@ -198,6 +198,7 @@ fn main() { .allowlist_function("rdp_state_size") .allowlist_function("rdp_save_state") .allowlist_function("rdp_load_state") + .allowlist_function("rdp_set_fps") .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) .generate() .expect("Unable to generate bindings"); diff --git a/parallel-rdp/interface.cpp b/parallel-rdp/interface.cpp index 0f784726..58f57bbb 100644 --- a/parallel-rdp/interface.cpp +++ b/parallel-rdp/interface.cpp @@ -75,6 +75,11 @@ typedef struct { FrameBufferInfo frame_buffer_info; } RDP_DEVICE; +typedef struct { + uint32_t fps; + uint32_t vis; +} FPS_DATA; + typedef struct { std::string message; Vulkan::ImageHandle image; @@ -106,6 +111,9 @@ static Vulkan::ImageHandle achievement_progress_indicator_image; static std::map leaderboard_trackers; static bool display_challenge_indicator; +static bool display_fps; +static Vulkan::ImageHandle fps_image; + #define MESSAGE_TIME 3000 // 3 seconds typedef struct { @@ -147,6 +155,9 @@ bool sdl_event_filter(void *userdata, SDL_Event *event) { if (gfx_info.fullscreen) callback.emu_running = false; break; + case SDL_SCANCODE_F1: + display_fps = !display_fps; + break; case SDL_SCANCODE_F4: crop_letterbox = !crop_letterbox; break; @@ -331,12 +342,15 @@ void rdp_init(void *_window, GFX_INFO _gfx_info, const void *font, leaderboard_trackers.clear(); achievement_challenge_indicator_image = Vulkan::ImageHandle(); achievement_progress_indicator_image = Vulkan::ImageHandle(); + fps_image = Vulkan::ImageHandle(); + display_fps = false; } void rdp_close() { messages = std::queue(); achievement_challenge_indicator_image = Vulkan::ImageHandle(); achievement_progress_indicator_image = Vulkan::ImageHandle(); + fps_image = Vulkan::ImageHandle(); if (wsi) wsi->end_frame(); @@ -421,6 +435,18 @@ static void draw_indicator(CommandBufferHandle cmd, cmd->draw(3); } +static void draw_fps(CommandBufferHandle cmd, Vulkan::ImageHandle fps_image, + VkViewport &vp) { + cmd->set_texture(0, 0, fps_image->get_view(), + Vulkan::StockSampler::NearestClamp); + vp.y = vp.y + vp.height - fps_image->get_height(); + vp.height = fps_image->get_height(); + vp.width = fps_image->get_width(); + cmd->set_viewport(vp); + + cmd->draw(3); +} + static void render_frame(Vulkan::Device &device) { RDP::ScanoutOptions options = {}; options.persist_frame_on_invalid_input = true; @@ -517,6 +543,10 @@ static void render_frame(Vulkan::Device &device) { display_challenge_indicator) { draw_indicator(cmd, achievement_challenge_indicator_image, vp); } + + if (messages.empty() && display_fps && fps_image) { + draw_fps(cmd, fps_image, vp); + } } cmd->end_render_pass(); @@ -822,6 +852,18 @@ uint64_t rdp_process_commands() { return interrupt_timer; } +void rdp_set_fps_callback(void *userdata) { + FPS_DATA *data = (FPS_DATA *)userdata; + fps_image = create_message_image( + wsi->get_device(), 0, message_font, + std::format("FPS: {} VI/S: {}", data->fps, data->vis).c_str()); +} + +void rdp_set_fps(uint32_t fps, uint32_t vis) { + FPS_DATA data = {fps, vis}; + SDL_RunOnMainThread(rdp_set_fps_callback, &data, true); +} + static void update_challenge_indicator() { std::string message; for (const auto &leaderboard_tracker : leaderboard_trackers) { diff --git a/parallel-rdp/interface.hpp b/parallel-rdp/interface.hpp index 7182a6ba..28bcb228 100644 --- a/parallel-rdp/interface.hpp +++ b/parallel-rdp/interface.hpp @@ -50,6 +50,7 @@ void rdp_check_framebuffers(uint32_t address, uint32_t length); size_t rdp_state_size(); void rdp_save_state(uint8_t *state); void rdp_load_state(const uint8_t *state); +void rdp_set_fps(uint32_t fps, uint32_t vis); void achievement_challenge_indicator_add(const char *achievement_title); void achievement_challenge_indicator_remove(const char *achievement_title); diff --git a/src/device.rs b/src/device.rs index c4895556..26cf8a83 100644 --- a/src/device.rs +++ b/src/device.rs @@ -482,8 +482,6 @@ impl Device { enable_speed_limiter: true, next_pace_deadline: None, vi_counter: 0, - last_origin: 0, - internal_frame_counter: 0, min_wait_time: std::time::Duration::from_secs(1), frame_time: 0.0, limit_freq: 2, diff --git a/src/device/vi.rs b/src/device/vi.rs index b75723b6..6a6b90a1 100644 --- a/src/device/vi.rs +++ b/src/device/vi.rs @@ -27,8 +27,6 @@ pub struct Vi { pub count_per_scanline: u64, pub enable_speed_limiter: bool, pub vi_counter: u64, - pub last_origin: u32, - pub internal_frame_counter: u64, pub min_wait_time: std::time::Duration, pub frame_time: f64, pub limit_freq: u64, @@ -110,10 +108,10 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u } } VI_ORIGIN_REG => { + let current_origin = device.vi.regs[reg as usize]; device::memory::masked_write_32(&mut device.vi.regs[reg as usize], value, mask); - if device.vi.regs[reg as usize] != device.vi.last_origin { - device.vi.last_origin = device.vi.regs[reg as usize]; - device.vi.internal_frame_counter += 1; + if current_origin != device.vi.regs[reg as usize] { + let _ = device.ui.video.fps_tx.try_send(true); } } _ => { @@ -129,6 +127,7 @@ pub fn vertical_interrupt_event(device: &mut device::Device) { } ui::video::render_frame(); + let _ = device.ui.video.vis_tx.try_send(true); retroachievements::do_frame(); @@ -159,14 +158,6 @@ pub fn vertical_interrupt_event(device: &mut device::Device) { unsafe { sdl3_sys::events::SDL_PumpEvents() }; - /* - let vis = if device.cart.pal { 50 } else { 60 }; - if device.vi.vi_counter % vis == 0 { - println!("FPS: {}", device.vi.internal_frame_counter); - device.vi.internal_frame_counter = 0; - } - */ - /* toggle vi field if in interlaced mode */ device.vi.field ^= (device.vi.regs[VI_STATUS_REG as usize] >> 6) & 0x1; diff --git a/src/ui.rs b/src/ui.rs index 19ad3432..4b955943 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -36,6 +36,10 @@ pub struct Storage { pub struct Video { pub window: *mut sdl3_sys::video::SDL_Window, pub fullscreen: bool, + pub fps_tx: tokio::sync::mpsc::Sender, + pub fps_rx: Option>, + pub vis_tx: tokio::sync::mpsc::Sender, + pub vis_rx: Option>, } pub struct Usb { @@ -113,6 +117,8 @@ impl Ui { fn construct_ui(joysticks: Vec, with_sdl: bool) -> Ui { let dirs = get_dirs(); + let (fps_tx, fps_rx) = tokio::sync::mpsc::channel(1000); + let (vis_tx, vis_rx) = tokio::sync::mpsc::channel(1000); Ui { input: Input { controllers: [ @@ -190,6 +196,10 @@ impl Ui { video: Video { window: std::ptr::null_mut(), fullscreen: false, + fps_tx, + fps_rx: Some(fps_rx), + vis_tx, + vis_rx: Some(vis_rx), }, usb: Usb { usb_tx: None, diff --git a/src/ui/video.rs b/src/ui/video.rs index b67e6561..3cb7e9d0 100644 --- a/src/ui/video.rs +++ b/src/ui/video.rs @@ -102,6 +102,35 @@ pub fn init(device: &mut device::Device) { device.netplay.is_some(), ) } + + fps_counter(&mut device.ui); +} + +fn fps_counter(ui: &mut ui::Ui) { + let mut fps_rx = ui.video.fps_rx.take().unwrap(); + let mut vis_rx = ui.video.vis_rx.take().unwrap(); + tokio::spawn(async move { + loop { + let mut fps: u32 = 0; + let mut vis: u32 = 0; + loop { + match fps_rx.try_recv() { + Ok(_fps_update) => fps += 1, + Err(tokio::sync::mpsc::error::TryRecvError::Empty) => break, + Err(tokio::sync::mpsc::error::TryRecvError::Disconnected) => return, + } + } + loop { + match vis_rx.try_recv() { + Ok(_vis_update) => vis += 1, + Err(tokio::sync::mpsc::error::TryRecvError::Empty) => break, + Err(tokio::sync::mpsc::error::TryRecvError::Disconnected) => return, + } + } + unsafe { rdp_set_fps(fps, vis) }; + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + } + }); } pub fn close(ui: &ui::Ui) {