[WIP-THREAD] Separate thread for emulation.

This commit is contained in:
Edênis Freindorfer Azevedo 2020-07-08 21:43:01 -03:00
parent d5d01a8b68
commit 24cc810eb6
No known key found for this signature in database
GPG Key ID: 968FB6EC280C7222
7 changed files with 329 additions and 47 deletions

View File

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

View File

@ -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();
}

View File

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

View File

@ -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>();
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)

View File

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

View File

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

View File

@ -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 <windows.h>
#endif
class GameArea : public wxPanel, public HiDPIAware {
#ifndef NO_THREAD_MAINLOOP
#include <wx/thread.h>
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