mac: Handle SDL init and events on main thread

This commit is contained in:
Exzap 2026-05-08 15:31:35 +02:00
parent f0b44e7dca
commit 4f2d77e12e
4 changed files with 127 additions and 53 deletions

View File

@ -9,6 +9,7 @@
#include "config/LaunchSettings.h" #include "config/LaunchSettings.h"
#include "wxgui/GettingStartedDialog.h" #include "wxgui/GettingStartedDialog.h"
#include "input/InputManager.h" #include "input/InputManager.h"
#include "input/api/SDL/SDLControllerProvider.h"
#include "wxgui/helpers/wxHelpers.h" #include "wxgui/helpers/wxHelpers.h"
#include "Cemu/ncrypto/ncrypto.h" #include "Cemu/ncrypto/ncrypto.h"
#include "wxgui/input/HotkeySettings.h" #include "wxgui/input/HotkeySettings.h"
@ -26,6 +27,7 @@
#include <wx/filename.h> #include <wx/filename.h>
#include <wx/stdpaths.h> #include <wx/stdpaths.h>
#include <wx/clipbrd.h> #include <wx/clipbrd.h>
#include <wx/timer.h>
#include "wxHelper.h" #include "wxHelper.h"
#include "Cafe/TitleList/TitleList.h" #include "Cafe/TitleList/TitleList.h"
@ -332,8 +334,18 @@ bool CemuApp::OnInit()
#ifdef CEMU_DEBUG_ASSERT #ifdef CEMU_DEBUG_ASSERT
UnitTests(); UnitTests();
#endif #endif
#if BOOST_OS_MACOS
SDLControllerProvider::InitSDL();
#endif
CemuCommonInit(); CemuCommonInit();
#if BOOST_OS_MACOS
m_sdlEventPumpTimer = new wxTimer(this);
Bind(wxEVT_TIMER, &CemuApp::OnSDLEventPumpTimer, this);
m_sdlEventPumpTimer->Start(5, wxTIMER_CONTINUOUS);
#endif
wxInitAllImageHandlers(); wxInitAllImageHandlers();
// fill colour db // fill colour db
@ -389,9 +401,22 @@ bool CemuApp::OnInit()
int CemuApp::OnExit() int CemuApp::OnExit()
{ {
#if BOOST_OS_MACOS
if (m_sdlEventPumpTimer)
{
m_sdlEventPumpTimer->Stop();
Unbind(wxEVT_TIMER, &CemuApp::OnSDLEventPumpTimer, this);
delete m_sdlEventPumpTimer;
m_sdlEventPumpTimer = nullptr;
}
#endif
wxApp::OnExit(); wxApp::OnExit();
wxTheClipboard->Flush(); wxTheClipboard->Flush();
InputManager::instance().Shutdown(); InputManager::instance().Shutdown();
#if BOOST_OS_MACOS
SDLControllerProvider::ShutdownSDL();
#endif
#if BOOST_OS_WINDOWS #if BOOST_OS_WINDOWS
ExitProcess(0); ExitProcess(0);
#else #else
@ -399,6 +424,15 @@ int CemuApp::OnExit()
#endif #endif
} }
#if BOOST_OS_MACOS
void CemuApp::OnSDLEventPumpTimer(wxTimerEvent& event)
{
// this callback is only used on macOS where SDL event functions need to be called on the main thread
// on other platforms SDLControllerProvider creates a separate thread for SDL event polling
SDLControllerProvider::PumpSDLEvents();
}
#endif
#if BOOST_OS_WINDOWS #if BOOST_OS_WINDOWS
void DumpThreadStackTrace(); void DumpThreadStackTrace();
#endif #endif

View File

@ -3,6 +3,8 @@
#include <wx/app.h> #include <wx/app.h>
class MainWindow; class MainWindow;
class wxTimer;
class wxTimerEvent;
class CemuApp : public wxApp class CemuApp : public wxApp
{ {
@ -30,6 +32,10 @@ private:
static std::vector<const wxLanguageInfo*> GetAvailableTranslationLanguages(wxTranslations* translationsMgr); static std::vector<const wxLanguageInfo*> GetAvailableTranslationLanguages(wxTranslations* translationsMgr);
MainWindow* m_mainFrame = nullptr; MainWindow* m_mainFrame = nullptr;
#if BOOST_OS_MACOS
void OnSDLEventPumpTimer(wxTimerEvent& event);
wxTimer* m_sdlEventPumpTimer = nullptr;
#endif
wxLocale m_locale; wxLocale m_locale;
std::vector<const wxLanguageInfo*> m_availableTranslations; std::vector<const wxLanguageInfo*> m_availableTranslations;

View File

@ -16,28 +16,43 @@ struct SDL_JoystickGUIDHash
SDLControllerProvider::SDLControllerProvider() SDLControllerProvider::SDLControllerProvider()
{ {
m_motion_states.reserve(8); #if !BOOST_OS_MACOS
std::scoped_lock _l(s_mutex);
m_running = true; if (s_initCount.fetch_add(1) == 0)
m_thread = std::thread(&SDLControllerProvider::event_thread, this); {
s_running = true;
s_thread = std::thread(&SDLControllerProvider::event_thread, this);
}
#endif
} }
SDLControllerProvider::~SDLControllerProvider() SDLControllerProvider::~SDLControllerProvider()
{ {
if (m_running) #if !BOOST_OS_MACOS
bool shutdownSDL = false;
{
std::scoped_lock _l(s_mutex);
if (s_initCount.fetch_sub(1) == 1)
{
cemu_assert_debug(s_running);
s_running = false;
shutdownSDL = true;
}
}
if (shutdownSDL)
{ {
m_running = false;
// wake the thread with a quit event if it's currently waiting for events // wake the thread with a quit event if it's currently waiting for events
SDL_Event evt; SDL_Event evt;
SDL_zero(evt); SDL_zero(evt);
evt.type = SDL_EVENT_QUIT; evt.type = SDL_EVENT_QUIT;
SDL_PushEvent(&evt); SDL_PushEvent(&evt);
if (s_thread.joinable())
if (m_thread.joinable())
{ {
m_thread.join(); s_thread.join();
} }
} }
#endif
} }
std::vector<std::shared_ptr<ControllerBase>> SDLControllerProvider::get_controllers() std::vector<std::shared_ptr<ControllerBase>> SDLControllerProvider::get_controllers()
@ -47,34 +62,22 @@ std::vector<std::shared_ptr<ControllerBase>> SDLControllerProvider::get_controll
std::unordered_map<SDL_GUID, size_t, SDL_JoystickGUIDHash> guid_counter; std::unordered_map<SDL_GUID, size_t, SDL_JoystickGUIDHash> guid_counter;
TempState lock(SDL_LockJoysticks, SDL_UnlockJoysticks); TempState lock(SDL_LockJoysticks, SDL_UnlockJoysticks);
int gamepad_count = 0; int gamepad_count = 0;
SDL_JoystickID *gamepad_ids = SDL_GetGamepads(&gamepad_count); SDL_JoystickID *gamepad_ids = SDL_GetGamepads(&gamepad_count);
if (gamepad_ids) if (gamepad_ids)
{ {
for (size_t i = 0; i < gamepad_count; ++i) for (size_t i = 0; i < gamepad_count; ++i)
{ {
const auto guid = SDL_GetGamepadGUIDForID(gamepad_ids[i]); const auto guid = SDL_GetGamepadGUIDForID(gamepad_ids[i]);
const auto it = guid_counter.try_emplace(guid, 0); const auto it = guid_counter.try_emplace(guid, 0);
if (const char* name = SDL_GetGamepadNameForID(gamepad_ids[i])) if (const char* name = SDL_GetGamepadNameForID(gamepad_ids[i]))
{
result.emplace_back(std::make_shared<SDLController>(guid, it.first->second, name)); result.emplace_back(std::make_shared<SDLController>(guid, it.first->second, name));
}
else else
{
result.emplace_back(std::make_shared<SDLController>(guid, it.first->second)); result.emplace_back(std::make_shared<SDLController>(guid, it.first->second));
}
++it.first->second; ++it.first->second;
} }
SDL_free(gamepad_ids); SDL_free(gamepad_ids);
} }
return result; return result;
} }
@ -82,11 +85,8 @@ int SDLControllerProvider::get_index(size_t guid_index, const SDL_GUID& guid) co
{ {
size_t index = 0; size_t index = 0;
int gamepad_count = 0; int gamepad_count = 0;
TempState lock(SDL_LockJoysticks, SDL_UnlockJoysticks); TempState lock(SDL_LockJoysticks, SDL_UnlockJoysticks);
SDL_JoystickID *gamepad_ids = SDL_GetGamepads(&gamepad_count); SDL_JoystickID *gamepad_ids = SDL_GetGamepads(&gamepad_count);
if (gamepad_ids) if (gamepad_ids)
{ {
for (size_t i = 0; i < gamepad_count; ++i) for (size_t i = 0; i < gamepad_count; ++i)
@ -98,30 +98,23 @@ int SDLControllerProvider::get_index(size_t guid_index, const SDL_GUID& guid) co
SDL_free(gamepad_ids); SDL_free(gamepad_ids);
return i; return i;
} }
++index; ++index;
} }
} }
SDL_free(gamepad_ids); SDL_free(gamepad_ids);
} }
return -1; return -1;
} }
MotionSample SDLControllerProvider::motion_sample(SDL_JoystickID diid) MotionSample SDLControllerProvider::motion_sample(SDL_JoystickID diid)
{ {
std::shared_lock lock(m_mutex); std::shared_lock lock(s_mutex);
auto it = s_motion_states.find(diid);
auto it = m_motion_states.find(diid); return (it != s_motion_states.end()) ? it->second.data : MotionSample{};
return (it != m_motion_states.end()) ? it->second.data : MotionSample{};
} }
void SDLControllerProvider::event_thread() void SDLControllerProvider::InitSDL()
{ {
SetThreadName("SDL_events");
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4, "1");
@ -144,17 +137,30 @@ void SDLControllerProvider::event_thread()
{ {
cemuLog_log(LogType::Force, "Couldn't enable SDL gamecontroller event polling: {}", SDL_GetError()); cemuLog_log(LogType::Force, "Couldn't enable SDL gamecontroller event polling: {}", SDL_GetError());
} }
}
while (m_running.load(std::memory_order_relaxed)) void SDLControllerProvider::ShutdownSDL()
{ {
SDL_Event event{}; SDL_QuitSubSystem(SDL_INIT_GAMEPAD | SDL_INIT_HAPTIC);
SDL_WaitEvent(&event); }
#if BOOST_OS_MACOS
void SDLControllerProvider::PumpSDLEvents()
{
SDL_Event event;
while (SDL_PollEvent(&event))
HandleSDLEvent(event);
}
#endif
void SDLControllerProvider::HandleSDLEvent(SDL_Event& event)
{
switch (event.type) switch (event.type)
{ {
case SDL_EVENT_QUIT: case SDL_EVENT_QUIT:
{ {
m_running = false; std::scoped_lock _l(s_mutex);
s_running = false;
break; break;
} }
case SDL_EVENT_GAMEPAD_AXIS_MOTION: /**< Game controller axis motion */ case SDL_EVENT_GAMEPAD_AXIS_MOTION: /**< Game controller axis motion */
@ -171,13 +177,15 @@ void SDLControllerProvider::event_thread()
} }
case SDL_EVENT_GAMEPAD_ADDED: /**< A new Game controller has been inserted into the system */ case SDL_EVENT_GAMEPAD_ADDED: /**< A new Game controller has been inserted into the system */
{ {
std::scoped_lock _l(s_mutex);
InputManager::instance().on_device_changed(); InputManager::instance().on_device_changed();
break; break;
} }
case SDL_EVENT_GAMEPAD_REMOVED: /**< An opened Game controller has been removed */ case SDL_EVENT_GAMEPAD_REMOVED: /**< An opened Game controller has been removed */
{ {
std::scoped_lock _l(s_mutex);
InputManager::instance().on_device_changed(); InputManager::instance().on_device_changed();
m_motion_states.erase(event.gdevice.which); s_motion_states.erase(event.gdevice.which);
break; break;
} }
case SDL_EVENT_GAMEPAD_REMAPPED: /**< The controller mapping was updated */ case SDL_EVENT_GAMEPAD_REMAPPED: /**< The controller mapping was updated */
@ -200,10 +208,8 @@ void SDLControllerProvider::event_thread()
{ {
SDL_JoystickID id = event.gsensor.which; SDL_JoystickID id = event.gsensor.which;
uint64_t ts = event.gsensor.timestamp; uint64_t ts = event.gsensor.timestamp;
std::scoped_lock _l(s_mutex);
std::scoped_lock lock(m_mutex); auto& state = s_motion_states[id];
auto& state = m_motion_states[id];
auto& tracking = state.tracking; auto& tracking = state.tracking;
if (event.gsensor.sensor == SDL_SENSOR_ACCEL) if (event.gsensor.sensor == SDL_SENSOR_ACCEL)
@ -275,6 +281,20 @@ void SDLControllerProvider::event_thread()
break; break;
} }
} }
} }
SDL_QuitSubSystem(SDL_INIT_GAMEPAD | SDL_INIT_HAPTIC);
void SDLControllerProvider::event_thread()
{
#if BOOST_OS_MACOS
cemu_assert(false);
#endif
SetThreadName("SDL_events");
InitSDL();
while (s_running.load(std::memory_order_relaxed))
{
SDL_Event event{};
SDL_WaitEvent(&event);
HandleSDLEvent(event);
}
ShutdownSDL();
} }

View File

@ -28,12 +28,26 @@ public:
MotionSample motion_sample(SDL_JoystickID diid); MotionSample motion_sample(SDL_JoystickID diid);
// exposed for manual event handling on macOS
#if BOOST_OS_MACOS
static void InitSDL();
static void ShutdownSDL();
static void PumpSDLEvents();
#endif
private: private:
void event_thread(); void event_thread();
static void HandleSDLEvent(union SDL_Event& event);
#if !BOOST_OS_MACOS
static void InitSDL();
static void ShutdownSDL();
#endif
std::atomic_bool m_running = false; // there is only one SDL instance, for this reason all of our state can be static
std::thread m_thread; inline static std::atomic_int s_initCount{0};
mutable std::shared_mutex m_mutex; inline static std::shared_mutex s_mutex;
inline static std::atomic_bool s_running = false;
inline static std::thread s_thread;
struct MotionInfoTracking struct MotionInfoTracking
{ {
@ -55,5 +69,5 @@ private:
MotionState() = default; MotionState() = default;
}; };
std::unordered_map<SDL_JoystickID, MotionState> m_motion_states{}; inline static std::unordered_map<SDL_JoystickID, MotionState> s_motion_states{};
}; };