From 24cc810eb664b6eaa24d236f859085baf6980a6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ed=C3=AAnis=20Freindorfer=20Azevedo?= Date: Wed, 8 Jul 2020 21:43:01 -0300 Subject: [PATCH] [WIP-THREAD] Separate thread for emulation. --- src/wx/CMakeLists.txt | 6 + src/wx/cmdevents.cpp | 37 +++--- src/wx/guiinit.cpp | 3 + src/wx/panel.cpp | 255 +++++++++++++++++++++++++++++++++++++++--- src/wx/sys.cpp | 12 +- src/wx/wxvbam.cpp | 32 ++++-- src/wx/wxvbam.h | 31 ++++- 7 files changed, 329 insertions(+), 47 deletions(-) diff --git a/src/wx/CMakeLists.txt b/src/wx/CMakeLists.txt index b6fa48df..ab8f21a8 100644 --- a/src/wx/CMakeLists.txt +++ b/src/wx/CMakeLists.txt @@ -22,6 +22,12 @@ include(VbamFunctions) set(VBAM_LIBS ${VBAMCORE_LIBS}) +option(ENABLE_THREAD_MAINLOOP "Enable separate thread for emulator main loop for WX port" OFF) + +if(NOT ENABLE_THREAD_MAINLOOP) + add_definitions(-DNO_THREAD_MAINLOOP) +endif() + if(WIN32) # not yet implemented option(ENABLE_DIRECT3D "Enable Direct3D rendering for the wxWidgets port" OFF) diff --git a/src/wx/cmdevents.cpp b/src/wx/cmdevents.cpp index 3b5da32d..87a0c942 100644 --- a/src/wx/cmdevents.cpp +++ b/src/wx/cmdevents.cpp @@ -2297,6 +2297,10 @@ EVT_HANDLER(GeneralConfigure, "General options...") if (ShowModal(dlg) == wxID_OK) update_opts(); +#ifndef NO_THREAD_MAINLOOP + wxCriticalSectionLocker lock(MainFrame::emulationCS); +#endif + if (panel->game_type() != IMAGE_UNKNOWN) soundSetThrottle(throttle); @@ -2598,6 +2602,10 @@ EVT_HANDLER_MASK(DisplayConfigure, "Display options...", CMDEN_NREC_ANY) if (ShowModal(dlg) != wxID_OK) return; +#ifndef NO_THREAD_MAINLOOP + wxCriticalSectionLocker lock(MainFrame::emulationCS); +#endif + if (frameSkip >= 0) systemFrameSkip = frameSkip; @@ -2609,16 +2617,16 @@ EVT_HANDLER_MASK(DisplayConfigure, "Display options...", CMDEN_NREC_ANY) panel->ShowFullScreen(true); } - if (panel->panel) { - panel->panel->Destroy(); - panel->panel = NULL; - } + panel->DestroyDrawingPanel(); update_opts(); } EVT_HANDLER_MASK(ChangeFilter, "Change Pixel Filter", CMDEN_NREC_ANY) { +#ifndef NO_THREAD_MAINLOOP + wxCriticalSectionLocker lock(MainFrame::emulationCS); +#endif int filt = gopts.filter; if ((filt == FF_PLUGIN || ++gopts.filter == FF_PLUGIN) && gopts.filter_plugin.empty()) { @@ -2627,10 +2635,7 @@ EVT_HANDLER_MASK(ChangeFilter, "Change Pixel Filter", CMDEN_NREC_ANY) update_opts(); - if (panel->panel) { - panel->panel->Destroy(); - panel->panel = NULL; - } + panel->DestroyDrawingPanel(); wxString msg; msg.Printf(_("Using pixel filter #%d"), gopts.filter); @@ -2639,13 +2644,13 @@ EVT_HANDLER_MASK(ChangeFilter, "Change Pixel Filter", CMDEN_NREC_ANY) EVT_HANDLER_MASK(ChangeIFB, "Change Interframe Blending", CMDEN_NREC_ANY) { +#ifndef NO_THREAD_MAINLOOP + wxCriticalSectionLocker lock(MainFrame::emulationCS); +#endif gopts.ifb = (gopts.ifb + 1) % 3; update_opts(); - if (panel->panel) { - panel->panel->Destroy(); - panel->panel = NULL; - } + panel->DestroyDrawingPanel(); wxString msg; msg.Printf(_("Using interframe blending #%d"), gopts.ifb); @@ -2833,13 +2838,13 @@ EVT_HANDLER(Bilinear, "Use bilinear filter with 3d renderer") EVT_HANDLER(RetainAspect, "Retain aspect ratio when resizing") { +#ifndef NO_THREAD_MAINLOOP + wxCriticalSectionLocker lock(MainFrame::emulationCS); +#endif GetMenuOptionBool("RetainAspect", gopts.retain_aspect); // Force new panel with new aspect ratio options. - if (panel->panel) { - panel->panel->Destroy(); - panel->panel = nullptr; - } + panel->DestroyDrawingPanel(); update_opts(); } diff --git a/src/wx/guiinit.cpp b/src/wx/guiinit.cpp index 2677be6b..3965e843 100644 --- a/src/wx/guiinit.cpp +++ b/src/wx/guiinit.cpp @@ -4028,5 +4028,8 @@ bool MainFrame::BindControls() panel->SetFrameTitle(); // All OK; activate idle loop panel->SetExtraStyle(panel->GetExtraStyle() | wxWS_EX_PROCESS_IDLE); +#ifndef NO_THREAD_MAINLOOP + panel->StartEmulationThread(); +#endif return true; } diff --git a/src/wx/panel.cpp b/src/wx/panel.cpp index e9ebec81..685154b7 100644 --- a/src/wx/panel.cpp +++ b/src/wx/panel.cpp @@ -53,10 +53,19 @@ GameArea::GameArea() systemColorDepth = 32; hq2x_init(32); Init_2xSaI(32); + +#ifndef NO_THREAD_MAINLOOP + Bind(WX_THREAD_REQUEST_UPDATEDRAWPANEL, &GameArea::RequestUpdateDrawPanel, this); + Bind(WX_THREAD_REQUEST_DRAWFRAME, &GameArea::RequestDrawFrame, this); + Bind(WX_THREAD_REQUEST_UPDATESTATUSBAR, &GameArea::RequestUpdateStatusBar, this); +#endif } void GameArea::LoadGame(const wxString& name) { +#ifndef NO_THREAD_MAINLOOP + wxCriticalSectionLocker lock(MainFrame::emulationCS); +#endif rom_scene_rls = wxT("-"); rom_scene_rls_name = wxT("-"); rom_name = wxT(""); @@ -553,10 +562,7 @@ void GameArea::UnloadGame(bool destruct) // in destructor, panel should be auto-deleted by wx since all panels // are derived from a window attached as child to GameArea - if (panel) - panel->Destroy(); - - panel = NULL; + DestroyDrawingPanel(); // close any game-related viewer windows // in destructor, viewer windows are in process of being deleted anyway @@ -595,6 +601,9 @@ bool GameArea::LoadState(int slot) bool GameArea::LoadState(const wxFileName& fname) { +#ifndef NO_THREAD_MAINLOOP + wxCriticalSectionLocker lock(MainFrame::emulationCS); +#endif // FIXME: first save to backup state if not backup state bool ret = emusys->emuReadState(UTF8(fname.GetFullPath())); @@ -642,6 +651,9 @@ bool GameArea::SaveState(int slot) bool GameArea::SaveState(const wxFileName& fname) { +#ifndef NO_THREAD_MAINLOOP + wxCriticalSectionLocker lock(MainFrame::emulationCS); +#endif // FIXME: first copy to backup state if not backup state bool ret = emusys->emuWriteState(UTF8(fname.GetFullPath())); wxGetApp().frame->update_state_ts(true); @@ -681,6 +693,9 @@ void GameArea::SaveBattery() void GameArea::AddBorder() { +#ifndef NO_THREAD_MAINLOOP + wxCriticalSectionLocker lock(MainFrame::emulationCS); +#endif if (basic_width != GBWidth) return; @@ -693,14 +708,14 @@ void GameArea::AddBorder() wxGetApp().frame->Fit(); GetSizer()->Detach(panel->GetWindow()); - if (panel) - panel->Destroy(); - - panel = NULL; + DestroyDrawingPanel(); } void GameArea::DelBorder() { +#ifndef NO_THREAD_MAINLOOP + wxCriticalSectionLocker lock(MainFrame::emulationCS); +#endif if (basic_width != SGBWidth) return; @@ -712,10 +727,7 @@ void GameArea::DelBorder() wxGetApp().frame->Fit(); GetSizer()->Detach(panel->GetWindow()); - if (panel) - panel->Destroy(); - - panel = NULL; + DestroyDrawingPanel(); } void GameArea::AdjustMinSize() @@ -751,6 +763,9 @@ void GameArea::LowerMinSize() void GameArea::AdjustSize(bool force) { +#ifndef NO_THREAD_MAINLOOP + wxCriticalSectionLocker lock(MainFrame::emulationCS); +#endif AdjustMinSize(); if (fullscreen) @@ -774,6 +789,9 @@ void GameArea::AdjustSize(bool force) void GameArea::ShowFullScreen(bool full) { +#ifndef NO_THREAD_MAINLOOP + wxCriticalSectionLocker lock(MainFrame::emulationCS); +#endif if (full == fullscreen) { // in case the tlw somehow lost its mind, force it to proper mode if (wxGetApp().frame->IsFullScreen() != fullscreen) @@ -791,10 +809,7 @@ void GameArea::ShowFullScreen(bool full) // just in case screen mode is going to change, go ahead and preemptively // delete panel to be recreated immediately after resize - if (panel) { - panel->Destroy(); - panel = NULL; - } + DestroyDrawingPanel(); // Windows does not restore old window size/pos // at least under Wine @@ -939,6 +954,9 @@ void GameArea::OnKillFocus(wxFocusEvent& ev) void GameArea::Pause() { +#ifndef NO_THREAD_MAINLOOP + wxCriticalSectionLocker lock(MainFrame::emulationCS); +#endif if (paused) return; @@ -961,6 +979,9 @@ void GameArea::Pause() void GameArea::Resume() { +#ifndef NO_THREAD_MAINLOOP + wxCriticalSectionLocker lock(MainFrame::emulationCS); +#endif if (!paused) return; @@ -973,6 +994,206 @@ void GameArea::Resume() SetFocus(); } +#ifndef NO_THREAD_MAINLOOP +wxCriticalSection MainFrame::emulationCS; + +wxThread::ExitCode GameArea::Entry() +{ + MainFrame* mf = wxGetApp().frame; + while (!GetThread()->TestDestroy()) { + { + wxCriticalSectionLocker lock(MainFrame::emulationCS); + if (emusys) { + if (!panel) + wxQueueEvent(GetEventHandler(), new wxThreadEvent(WX_THREAD_REQUEST_UPDATEDRAWPANEL)); + mf->PollJoysticks(); + if (!paused) { + emusys->emuMain(emusys->emuCount); + } + } + } + wxMilliSleep(5); + } + return (wxThread::ExitCode)0; +} + +void GameArea::StopEmulationThread() +{ + if (GetThread() && GetThread()->IsRunning()) + GetThread()->Delete(); // it will exit after next `TestDestroy()` +} + +void GameArea::StartEmulationThread() +{ + if (CreateThread(wxTHREAD_JOINABLE) != wxTHREAD_NO_ERROR) + { + wxLogError(_("Could not create emulation thread!")); + return; + } + if (GetThread()->Run() != wxTHREAD_NO_ERROR) + { + wxLogError(_("Could not run emulation thread!")); + return; + } +} + +void GameArea::RequestUpdateDrawPanel(wxThreadEvent& WXUNUSED(event)) +{ + wxCriticalSectionLocker lock(MainFrame::emulationCS); + MainFrame* mf = wxGetApp().frame; + if (!panel) { + switch (gopts.render_method) { + case RND_SIMPLE: + panel = new BasicDrawingPanel(this, basic_width, basic_height); + break; +#ifdef __WXMAC__ + case RND_QUARTZ2D: + panel = new Quartz2DDrawingPanel(this, basic_width, basic_height); + break; +#endif +#ifndef NO_OGL + case RND_OPENGL: + panel = new GLDrawingPanel(this, basic_width, basic_height); + break; +#endif +#if defined(__WXMSW__) && !defined(NO_D3D) + case RND_DIRECT3D: + panel = new DXDrawingPanel(this, basic_width, basic_height); + break; +#endif + } + + wxWindow* w = panel->GetWindow(); + + // set up event handlers + w->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(GameArea::OnKeyDown), NULL, this); + w->Connect(wxEVT_KEY_UP, wxKeyEventHandler(GameArea::OnKeyUp), NULL, this); + w->Connect(wxEVT_PAINT, wxPaintEventHandler(GameArea::PaintEv), NULL, this); + w->Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(GameArea::EraseBackground), NULL, this); + + // set userdata so we know it's the panel and not the frame being resized + // the userdata is freed on disconnect/destruction + this->Connect(wxEVT_SIZE, wxSizeEventHandler(GameArea::OnSize), NULL, this); + + // We need to check if the buttons stayed pressed when focus the panel. + w->Connect(wxEVT_KILL_FOCUS, wxFocusEventHandler(GameArea::OnKillFocus), NULL, this); + + // Update mouse last-used timers on mouse events etc.. + w->Connect(wxEVT_MOTION, wxMouseEventHandler(GameArea::MouseEvent), NULL, this); + w->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(GameArea::MouseEvent), NULL, this); + w->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(GameArea::MouseEvent), NULL, this); + w->Connect(wxEVT_MIDDLE_DOWN, wxMouseEventHandler(GameArea::MouseEvent), NULL, this); + w->Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler(GameArea::MouseEvent), NULL, this); + + w->SetBackgroundStyle(wxBG_STYLE_CUSTOM); + w->SetSize(wxSize(basic_width, basic_height)); + + if (maxScale) + w->SetMaxSize(wxSize(basic_width * maxScale, + basic_height * maxScale)); + + // if user changed Display/Scale config, this needs to run + AdjustMinSize(); + AdjustSize(false); + + unsigned frame_priority = gopts.retain_aspect ? 0 : 1; + + GetSizer()->Clear(); + + // add spacers on top and bottom to center panel vertically + // but not on 2.8 which does not handle this correctly + if (gopts.retain_aspect) +#if wxCHECK_VERSION(2, 9, 0) + GetSizer()->Add(0, 0, wxEXPAND); +#else + frame_priority = 1; +#endif + + // this triggers an assertion dialog in <= 3.1.2 in debug mode + GetSizer()->Add(w, frame_priority, gopts.retain_aspect ? (wxSHAPED | wxALIGN_CENTER | wxEXPAND) : wxEXPAND); + +#if wxCHECK_VERSION(2, 9, 0) + if (gopts.retain_aspect) + GetSizer()->Add(0, 0, wxEXPAND); +#endif + + Layout(); + +#if wxCHECK_VERSION(2, 9, 0) + SendSizeEvent(); +#endif + + if (pointer_blanked) + w->SetCursor(wxCursor(wxCURSOR_BLANK)); + + // set focus to panel + w->SetFocus(); + + // generate system color maps (after output module init) + if (loaded == IMAGE_GBA) utilUpdateSystemColorMaps(gbaLcdFilter); + else if (loaded == IMAGE_GB) utilUpdateSystemColorMaps(gbLcdFilter); + else utilUpdateSystemColorMaps(false); + } +} + +void GameArea::RequestDraw() +{ + wxQueueEvent(this, new wxThreadEvent(WX_THREAD_REQUEST_DRAWFRAME)); +} + +void GameArea::RequestStatusBar(int speed, int frames) +{ + wxThreadEvent *event = new wxThreadEvent(WX_THREAD_REQUEST_UPDATESTATUSBAR); + event->SetPayload(speed); + event->SetExtraLong(frames); // should probably use payload too + wxQueueEvent(this, event); +} + +void GameArea::RequestDrawFrame(wxThreadEvent& WXUNUSED(event)) +{ + wxCriticalSectionLocker lock(MainFrame::emulationCS); + MainFrame* mf = wxGetApp().frame; + mf->UpdateViewers(); + if (panel) { + panel->DrawArea(&pix); + } +} + +void GameArea::RequestUpdateStatusBar(wxThreadEvent& event) +{ + //wxCriticalSectionLocker lock(MainFrame::emulationCS); + MainFrame* f = wxGetApp().frame; + int speed = event.GetPayload(); + int frames = event.GetExtraLong(); // should probably use payload too + wxString s; + s.Printf(_("%d%%(%d, %d fps)"), speed, systemFrameSkip, frames * speed / 100); + + switch (showSpeed) { + case SS_NONE: + f->GetPanel()->osdstat.clear(); + break; + + case SS_PERCENT: + f->GetPanel()->osdstat.Printf(_("%d%%"), speed); + break; + + case SS_DETAILED: + f->GetPanel()->osdstat = s; + break; + } + + wxGetApp().frame->SetStatusText(s, 1); +} +#endif // NO_THREAD_MAINLOOP + +void GameArea::DestroyDrawingPanel() +{ + if (panel) { + panel->Destroy(); + panel = nullptr; + } +} + void GameArea::OnIdle(wxIdleEvent& event) { wxString pl = wxGetApp().pending_load; @@ -1416,7 +1637,9 @@ void GameArea::OnSDLJoy(wxSDLJoyEvent& ev) } BEGIN_EVENT_TABLE(GameArea, wxPanel) +#ifdef NO_THREAD_MAINLOOP EVT_IDLE(GameArea::OnIdle) +#endif EVT_SDLJOY(GameArea::OnSDLJoy) // FIXME: wxGTK does not generate motion events in MainFrame (not sure // what to do about it) diff --git a/src/wx/sys.cpp b/src/wx/sys.cpp index 06b41e92..f607f3fe 100644 --- a/src/wx/sys.cpp +++ b/src/wx/sys.cpp @@ -86,18 +86,20 @@ void systemDrawScreen() { frames++; MainFrame* mf = wxGetApp().frame; - mf->UpdateViewers(); // FIXME: Sm60FPS crap and sondBufferLow crap GameArea* ga = mf->GetPanel(); #ifndef NO_FFMPEG - if (ga) ga->AddFrame(pix); - #endif +#ifndef NO_THREAD_MAINLOOP + ga->RequestDraw(); +#else + mf->UpdateViewers(); if (ga && ga->panel) ga->panel->DrawArea(&pix); +#endif } // record a game "movie" @@ -337,6 +339,9 @@ uint32_t systemReadJoypad(int joy) void systemShowSpeed(int speed) { MainFrame* f = wxGetApp().frame; +#ifndef NO_THREAD_MAINLOOP + f->GetPanel()->RequestStatusBar(speed, frames); +#else wxString s; s.Printf(_("%d%%(%d, %d fps)"), speed, systemFrameSkip, frames * speed / 100); @@ -355,6 +360,7 @@ void systemShowSpeed(int speed) } wxGetApp().frame->SetStatusText(s, 1); +#endif // NO_THREAD_MAINLOOP frames = 0; } diff --git a/src/wx/wxvbam.cpp b/src/wx/wxvbam.cpp index 5a4130a3..036ceb6f 100644 --- a/src/wx/wxvbam.cpp +++ b/src/wx/wxvbam.cpp @@ -726,6 +726,9 @@ MainFrame::MainFrame() MainFrame::~MainFrame() { +#ifndef NO_THREAD_MAINLOOP + GetPanel()->StopEmulationThread(); +#endif #ifndef NO_LINK CloseLink(); #endif @@ -772,6 +775,10 @@ void MainFrame::OnDropFile(wxDropFilesEvent& event) wxString* f = event.GetFiles(); // ignore all but last wxGetApp().pending_load = f[event.GetNumberOfFiles() - 1]; + +#ifndef NO_THREAD_MAINLOOP + GetPanel()->LoadGame(wxGetApp().pending_load); +#endif } void MainFrame::OnMenu(wxContextMenuEvent& event) @@ -845,6 +852,9 @@ void MainFrame::OnSize(wxSizeEvent& event) int MainFrame::FilterEvent(wxEvent& event) { +#ifndef NO_THREAD_MAINLOOP + wxCriticalSectionLocker lock(MainFrame::emulationCS); +#endif if (event.GetEventType() == wxEVT_KEY_DOWN && !menus_opened && !dialog_opened) { wxKeyEvent& ke = (wxKeyEvent&)event; @@ -1101,17 +1111,17 @@ void MainFrame::MenuPopped(wxMenuEvent& evt) void MainFrame::SetMenusOpened(bool state) { if ((menus_opened = state)) { -#ifdef __WXMSW__ - paused = true; - panel->Pause(); -#endif - } - else { -#ifdef __WXMSW__ - paused = false; - pause_next = false; - panel->Resume(); -#endif +//#ifdef __WXMSW__ +// paused = true; +// panel->Pause(); +//#endif +// } +// else { +//#ifdef __WXMSW__ +// paused = false; +// pause_next = false; +// panel->Resume(); +//#endif } } diff --git a/src/wx/wxvbam.h b/src/wx/wxvbam.h index 7138d94d..36a95df8 100644 --- a/src/wx/wxvbam.h +++ b/src/wx/wxvbam.h @@ -214,6 +214,10 @@ public: MainFrame(); ~MainFrame(); +#ifndef NO_THREAD_MAINLOOP + static wxCriticalSection emulationCS; +#endif + bool BindControls(); void MenuOptionIntMask(const char* menuName, int& field, int mask); void MenuOptionIntRadioValue(const char* menuName, int& field, int mask); @@ -500,11 +504,36 @@ class DrawingPanelBase; #include #endif -class GameArea : public wxPanel, public HiDPIAware { +#ifndef NO_THREAD_MAINLOOP +#include + +wxDEFINE_EVENT(WX_THREAD_REQUEST_UPDATEDRAWPANEL, wxThreadEvent); +wxDEFINE_EVENT(WX_THREAD_REQUEST_DRAWFRAME, wxThreadEvent); +wxDEFINE_EVENT(WX_THREAD_REQUEST_UPDATESTATUSBAR, wxThreadEvent); +#endif // NO_THREAD_MAINLOOP + +class GameArea : public wxPanel, public HiDPIAware +#ifndef NO_THREAD_MAINLOOP + , public wxThreadHelper +#endif +{ public: GameArea(); virtual ~GameArea(); +#ifndef NO_THREAD_MAINLOOP + virtual wxThread::ExitCode Entry(); + void StartEmulationThread(); + void StopEmulationThread(); + void RequestUpdateDrawPanel(wxThreadEvent&); + void RequestDrawFrame(wxThreadEvent&); + void RequestUpdateStatusBar(wxThreadEvent&); + void RequestDraw(); + void RequestStatusBar(int speed, int frames); +#endif + + void DestroyDrawingPanel(); + virtual void SetMainFrame(MainFrame* parent) { main_frame = parent; } // set to game title + link info