FLTK: Add timer sync mode for cases where VSync is unreliable

This commit is contained in:
rdanbrook 2024-12-04 19:01:59 -06:00
parent 8e9b7755e0
commit 1259fe812c
3 changed files with 81 additions and 10 deletions

View file

@ -67,6 +67,7 @@ namespace {
int paused{0}; int paused{0};
int speed{1}; int speed{1};
int video_fullscreen{0}; int video_fullscreen{0};
int syncmode{0};
int refreshrate{60}; int refreshrate{60};
int screennum{0}; int screennum{0};
@ -150,6 +151,13 @@ Fl_Menu_Item *get_menuitem(std::string label) {
void update_refreshrate(void) { void update_refreshrate(void) {
// Get the screen refresh rate using an SDL window // Get the screen refresh rate using an SDL window
if (syncmode) { // Don't use this in "Timer" sync mode
return;
}
#ifdef __APPLE__
refreshrate = 120; // Dirty hack for modern macOS
return;
#endif
SDL_Window *sdlwin; SDL_Window *sdlwin;
sdlwin = SDL_CreateWindow( sdlwin = SDL_CreateWindow(
"refreshrate", "refreshrate",
@ -162,7 +170,7 @@ void update_refreshrate(void) {
SDL_DestroyWindow(sdlwin); SDL_DestroyWindow(sdlwin);
} }
void exec_emu(void*) { void exec_emu_vsync(void*) {
SDL_Event event; SDL_Event event;
while (SDL_PollEvent(&event)) { while (SDL_PollEvent(&event)) {
inputmgr->event(event); inputmgr->event(event);
@ -186,6 +194,22 @@ void exec_emu(void*) {
glarea->redraw(); glarea->redraw();
} }
void exec_emu_timer(void*) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
inputmgr->event(event);
}
if (!paused) {
for (int i = 0; i < speed; i++) {
jgm->exec_frame();
}
}
Fl::repeat_timeout(1.0 / jgm->get_frametime(), exec_emu_timer);
glarea->redraw();
}
} }
void FltkUi::chtwin_open(Fl_Widget *w, void *data) { void FltkUi::chtwin_open(Fl_Widget *w, void *data) {
@ -251,6 +275,8 @@ void FltkUi::rom_open(Fl_Widget *w, void *data) {
fc.type(Fl_Native_File_Chooser::BROWSE_FILE); fc.type(Fl_Native_File_Chooser::BROWSE_FILE);
fc.filter("NES Games\t*.{nes,unf,fds,bin,zip,7z,gz,bz2,xz,xml,zst}"); fc.filter("NES Games\t*.{nes,unf,fds,bin,zip,7z,gz,bz2,xz,xml,zst}");
run_emulation(false);
// Show file chooser // Show file chooser
switch (fc.show()) { switch (fc.show()) {
case -1: case -1:
@ -274,6 +300,8 @@ void FltkUi::rom_open(Fl_Widget *w, void *data) {
} }
break; break;
} }
run_emulation();
} }
void FltkUi::screenshot(std::string filename) { void FltkUi::screenshot(std::string filename) {
@ -295,6 +323,8 @@ void FltkUi::screenshot_save(Fl_Widget *w, void *data) {
return; return;
} }
run_emulation(false);
Fl_Native_File_Chooser fc; Fl_Native_File_Chooser fc;
fc.title("Save Screenshot"); fc.title("Save Screenshot");
fc.type(Fl_Native_File_Chooser::BROWSE_SAVE_FILE); fc.type(Fl_Native_File_Chooser::BROWSE_SAVE_FILE);
@ -307,6 +337,7 @@ void FltkUi::screenshot_save(Fl_Widget *w, void *data) {
} }
screenshot(fc.filename()); screenshot(fc.filename());
run_emulation();
} }
void FltkUi::state_load(Fl_Widget *w, void *userdata) { void FltkUi::state_load(Fl_Widget *w, void *userdata) {
@ -315,6 +346,8 @@ void FltkUi::state_load(Fl_Widget *w, void *userdata) {
return; return;
} }
run_emulation(false);
Fl_Native_File_Chooser fc; Fl_Native_File_Chooser fc;
fc.title("Load State"); fc.title("Load State");
fc.type(Fl_Native_File_Chooser::BROWSE_FILE); fc.type(Fl_Native_File_Chooser::BROWSE_FILE);
@ -334,6 +367,8 @@ void FltkUi::state_load(Fl_Widget *w, void *userdata) {
} }
break; break;
} }
run_emulation();
} }
void FltkUi::state_save(Fl_Widget *w, void *data) { void FltkUi::state_save(Fl_Widget *w, void *data) {
@ -342,6 +377,8 @@ void FltkUi::state_save(Fl_Widget *w, void *data) {
return; return;
} }
run_emulation(false);
Fl_Native_File_Chooser fc; Fl_Native_File_Chooser fc;
fc.title("Save State"); fc.title("Save State");
fc.type(Fl_Native_File_Chooser::BROWSE_SAVE_FILE); fc.type(Fl_Native_File_Chooser::BROWSE_SAVE_FILE);
@ -350,11 +387,13 @@ void FltkUi::state_save(Fl_Widget *w, void *data) {
// Show file chooser // Show file chooser
if (fc.show()) { if (fc.show()) {
run_emulation();
return; return;
} }
std::string statefile{fc.filename()}; std::string statefile{fc.filename()};
jgm->state_save(statefile); jgm->state_save(statefile);
run_emulation();
} }
void FltkUi::palette_open(Fl_Widget *w, void *data) { void FltkUi::palette_open(Fl_Widget *w, void *data) {
@ -481,6 +520,7 @@ void FltkUi::fds_insert(Fl_Widget *w, void *data) {
void FltkUi::about_close(Fl_Widget *w, void *data) { void FltkUi::about_close(Fl_Widget *w, void *data) {
Fl_Window *about = (Fl_Window*)data; Fl_Window *about = (Fl_Window*)data;
about->hide(); about->hide();
run_emulation();
} }
void FltkUi::about(Fl_Widget *w, void *data) { void FltkUi::about(Fl_Widget *w, void *data) {
@ -524,6 +564,7 @@ void FltkUi::about(Fl_Widget *w, void *data) {
close.callback(FltkUi::about_close, (void*)&about); close.callback(FltkUi::about_close, (void*)&about);
about.set_modal(); about.set_modal();
run_emulation(false);
about.show(); about.show();
while (about.shown()) { while (about.shown()) {
Fl::wait(); Fl::wait();
@ -610,6 +651,9 @@ int NstGlArea::handle(int e) {
jgm->setup_video(); jgm->setup_video();
inputmgr->reassign(); inputmgr->reassign();
audiomgr->unpause(); audiomgr->unpause();
// Restart if in timer sync mode
FltkUi::run_emulation(false);
FltkUi::run_emulation();
} }
return 1; return 1;
} }
@ -707,6 +751,25 @@ void FltkUi::set_ffspeed(bool on) {
audiomgr->set_speed(speed); audiomgr->set_speed(speed);
} }
void FltkUi::run_emulation(bool run) {
if (run) {
if (syncmode) {
Fl::add_timeout(0.001, exec_emu_timer);
}
else {
Fl::add_idle(exec_emu_vsync);
}
}
else {
if (syncmode) {
Fl::remove_timeout(exec_emu_timer);
}
else {
Fl::remove_idle(exec_emu_vsync);
}
}
}
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
// Parse command line arguments // Parse command line arguments
std::string filename{}; std::string filename{};
@ -788,12 +851,14 @@ int main(int argc, char *argv[]) {
FltkUi::fullscreen(NULL, NULL); FltkUi::fullscreen(NULL, NULL);
} }
syncmode = setmgr->get_setting("m_syncmode")->val;
LogDriver::log(LogLevel::Debug, syncmode ?
"Synchronization Mode: Timer" :
"Synchronization Mode: VSync");
update_refreshrate(); update_refreshrate();
// Execute emulation using an FLTK idle callback. End when the main window FltkUi::run_emulation();
// is no longer shown. Using the while loop instead of Fl::run will cleanly
// exit the program even when other windows are still shown.
Fl::add_idle(exec_emu);
while (nstwin->shown()) { while (nstwin->shown()) {
Fl::wait(); Fl::wait();
} }

View file

@ -63,5 +63,6 @@ public:
static void setwin_open(Fl_Widget *w = nullptr, void *data = nullptr); static void setwin_open(Fl_Widget *w = nullptr, void *data = nullptr);
static void chtwin_open(Fl_Widget *w = nullptr, void *data = nullptr); static void chtwin_open(Fl_Widget *w = nullptr, void *data = nullptr);
static void nstwin_open(); static void nstwin_open();
static void run_emulation(bool run = true);
static int handle(int e); static int handle(int e);
}; };

View file

@ -37,7 +37,7 @@
namespace { namespace {
jg_setting_t fe_settings[] = { jg_setting_t fe_settings[] = {
{ "v_renderer", "Video Renderer", { "v_renderer", "Video Renderer (Restart)",
"0 = Modern, 1 = Legacy", "0 = Modern, 1 = Legacy",
"Use Modern (Core Profile, GLES) or Legacy (Compatibility Profile) OpenGL. " "Use Modern (Core Profile, GLES) or Legacy (Compatibility Profile) OpenGL. "
"Use Modern unless you are on extremely weak or incapable hardware.", "Use Modern unless you are on extremely weak or incapable hardware.",
@ -83,10 +83,10 @@ jg_setting_t fe_settings[] = {
"Hide the crosshair when a Zapper is present", "Hide the crosshair when a Zapper is present",
0, 0, 1, FLAG_FRONTEND 0, 0, 1, FLAG_FRONTEND
}, },
{ "l_loglevel", "Console Log Level", { "m_syncmode", "Synchronization Mode (Restart)",
"0 = Debug, 1 = Info, 2 = Warn, 3 = Error", "0 = VSync, 1 = Timer",
"Set the level of logs printed to the console. Debug shows all, Error shows only critical errors.", "Set the Synchronization Mode: VSync to sync to VBLANK, Timer in cases where VSync is unreliable",
1, 0, 3, FLAG_FRONTEND 0, 0, 1, FLAG_FRONTEND | JG_SETTING_RESTART
}, },
{ "s_crtmasktype", "CRT Mask Type", { "s_crtmasktype", "CRT Mask Type",
"0 = No Mask, 1 = Aperture Grille Lite, 2 = Aperture Grille, " "0 = No Mask, 1 = Aperture Grille Lite, 2 = Aperture Grille, "
@ -125,6 +125,11 @@ jg_setting_t fe_settings[] = {
"Set the level of Trinitron Curvature, which reduces the curve on the vertical axis", "Set the level of Trinitron Curvature, which reduces the curve on the vertical axis",
10, 0, 10, FLAG_FRONTEND 10, 0, 10, FLAG_FRONTEND
}, },
{ "l_loglevel", "Console Log Level",
"0 = Debug, 1 = Info, 2 = Warn, 3 = Error",
"Set the level of logs printed to the console. Debug shows all, Error shows only critical errors.",
1, 0, 3, FLAG_FRONTEND
},
}; };
jg_setting_t nullsetting; jg_setting_t nullsetting;