From 3837213f941a150bc7a3c26c9e38b75f4ef12952 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 15 Jan 2014 23:46:47 -0800 Subject: [PATCH] Support Win32 threads on Vista and higher --- CMakeLists.txt | 2 + src/gba/gba-thread.c | 185 ++++++++++++++++++++-------------- src/gba/gba-thread.h | 18 ++-- src/platform/sdl/sdl-events.c | 6 +- src/util/threading.h | 120 ++++++++++++++++++++++ 5 files changed, 244 insertions(+), 87 deletions(-) create mode 100644 src/util/threading.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 7bff3c966..b7347456c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,8 +17,10 @@ include_directories(${CMAKE_SOURCE_DIR}/src/util) find_package(SDL 1.2 REQUIRED) file(GLOB PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/sdl-*.c) if(WIN32) + add_definitions(-D_WIN32_WINNT=0x0600) file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/windows/*.c) else() + add_definitions(-DUSE_PTHREADS) file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/posix/*.c) endif() include_directories(${CMAKE_SOURCE_DIR}/src/platform/sdl) diff --git a/src/gba/gba-thread.c b/src/gba/gba-thread.c index 52ac93e6a..b15a9d7fc 100644 --- a/src/gba/gba-thread.c +++ b/src/gba/gba-thread.c @@ -7,15 +7,41 @@ #include #include +#ifdef USE_PTHREADS static pthread_key_t _contextKey; static pthread_once_t _contextOnce = PTHREAD_ONCE_INIT; static void _createTLS(void) { pthread_key_create(&_contextKey, 0); } +#else +static DWORD _contextKey; +static INIT_ONCE _contextOnce = INIT_ONCE_STATIC_INIT; -static void* _GBAThreadRun(void* context) { +static BOOL CALLBACK _createTLS(PINIT_ONCE once, PVOID param, PVOID* context) { + (void) (once); + (void) (param); + (void) (context); + _contextKey = TlsAlloc(); + return TRUE; +} +#endif + +static void _changeState(struct GBAThread* threadContext, enum ThreadState newState, int broadcast) { + MutexLock(&threadContext->stateMutex); + threadContext->state = newState; + if (broadcast) { + ConditionWake(&threadContext->stateCond); + } + MutexUnlock(&threadContext->stateMutex); +} + +static THREAD_ENTRY _GBAThreadRun(void* context) { +#ifdef USE_PTHREADS pthread_once(&_contextOnce, _createTLS); +#else + InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0); +#endif #ifdef USE_DEBUGGER struct ARMDebugger debugger; @@ -24,7 +50,7 @@ static void* _GBAThreadRun(void* context) { struct GBAThread* threadContext = context; char* savedata = 0; -#ifndef _WIN32 +#if !defined(_WIN32) && defined(USE_PTHREADS) sigset_t signals; sigfillset(&signals); pthread_sigmask(SIG_UNBLOCK, &signals, 0); @@ -33,7 +59,11 @@ static void* _GBAThreadRun(void* context) { GBAInit(&gba); threadContext->gba = &gba; gba.sync = &threadContext->sync; +#ifdef USE_PTHREADS pthread_setspecific(_contextKey, threadContext); +#else + TlsSetValue(_contextKey, threadContext); +#endif if (threadContext->renderer) { GBAVideoAssociateRenderer(&gba.video, threadContext->renderer); } @@ -79,19 +109,14 @@ static void* _GBAThreadRun(void* context) { threadContext->startCallback(threadContext); } - pthread_mutex_lock(&threadContext->stateMutex); - threadContext->state = THREAD_RUNNING; - pthread_cond_broadcast(&threadContext->stateCond); - pthread_mutex_unlock(&threadContext->stateMutex); + _changeState(threadContext, THREAD_RUNNING, 1); while (threadContext->state < THREAD_EXITING) { #ifdef USE_DEBUGGER if (threadContext->useDebugger) { ARMDebuggerRun(&debugger); if (debugger.state == DEBUGGER_SHUTDOWN) { - pthread_mutex_lock(&threadContext->stateMutex); - threadContext->state = THREAD_EXITING; - pthread_mutex_unlock(&threadContext->stateMutex); + _changeState(threadContext, THREAD_EXITING, 0); } } else { #endif @@ -101,17 +126,15 @@ static void* _GBAThreadRun(void* context) { #ifdef USE_DEBUGGER } #endif + MutexLock(&threadContext->stateMutex); while (threadContext->state == THREAD_PAUSED) { - pthread_mutex_lock(&threadContext->stateMutex); - pthread_cond_wait(&threadContext->stateCond, &threadContext->stateMutex); - pthread_mutex_unlock(&threadContext->stateMutex); + ConditionWait(&threadContext->stateCond, &threadContext->stateMutex); } + MutexUnlock(&threadContext->stateMutex); } while (threadContext->state != THREAD_SHUTDOWN) { - pthread_mutex_lock(&threadContext->stateMutex); - threadContext->state = THREAD_SHUTDOWN; - pthread_mutex_unlock(&threadContext->stateMutex); + _changeState(threadContext, THREAD_SHUTDOWN, 0); } if (threadContext->cleanCallback) { @@ -120,8 +143,8 @@ static void* _GBAThreadRun(void* context) { GBADeinit(&gba); - pthread_cond_broadcast(&threadContext->sync.videoFrameAvailableCond); - pthread_cond_broadcast(&threadContext->sync.audioRequiredCond); + ConditionWake(&threadContext->sync.videoFrameAvailableCond); + ConditionWake(&threadContext->sync.audioRequiredCond); free(savedata); return 0; @@ -129,52 +152,55 @@ static void* _GBAThreadRun(void* context) { int GBAThreadStart(struct GBAThread* threadContext) { // TODO: error check - pthread_mutex_init(&threadContext->stateMutex, 0); - pthread_cond_init(&threadContext->stateCond, 0); - - pthread_mutex_init(&threadContext->sync.videoFrameMutex, 0); - pthread_cond_init(&threadContext->sync.videoFrameAvailableCond, 0); - pthread_cond_init(&threadContext->sync.videoFrameRequiredCond, 0); - pthread_mutex_init(&threadContext->sync.audioBufferMutex, 0); - pthread_cond_init(&threadContext->sync.audioRequiredCond, 0); - - pthread_mutex_lock(&threadContext->stateMutex); threadContext->activeKeys = 0; threadContext->state = THREAD_INITIALIZED; threadContext->sync.videoFrameOn = 1; threadContext->sync.videoFrameSkip = 0; - pthread_create(&threadContext->thread, 0, _GBAThreadRun, threadContext); - pthread_cond_wait(&threadContext->stateCond, &threadContext->stateMutex); - pthread_mutex_unlock(&threadContext->stateMutex); + + MutexInit(&threadContext->stateMutex); + ConditionInit(&threadContext->stateCond); + + MutexInit(&threadContext->sync.videoFrameMutex); + ConditionInit(&threadContext->sync.videoFrameAvailableCond); + ConditionInit(&threadContext->sync.videoFrameRequiredCond); + MutexInit(&threadContext->sync.audioBufferMutex); + ConditionInit(&threadContext->sync.audioRequiredCond); + + MutexLock(&threadContext->stateMutex); + ThreadCreate(&threadContext->thread, _GBAThreadRun, threadContext); + while (threadContext->state < THREAD_RUNNING) { + ConditionWait(&threadContext->stateCond, &threadContext->stateMutex); + } + MutexUnlock(&threadContext->stateMutex); return 0; } void GBAThreadJoin(struct GBAThread* threadContext) { - pthread_mutex_lock(&threadContext->sync.videoFrameMutex); + MutexLock(&threadContext->sync.videoFrameMutex); threadContext->sync.videoFrameWait = 0; - pthread_cond_broadcast(&threadContext->sync.videoFrameRequiredCond); - pthread_mutex_unlock(&threadContext->sync.videoFrameMutex); + ConditionWake(&threadContext->sync.videoFrameRequiredCond); + MutexUnlock(&threadContext->sync.videoFrameMutex); - pthread_join(threadContext->thread, 0); + ThreadJoin(threadContext->thread); - pthread_mutex_destroy(&threadContext->stateMutex); - pthread_cond_destroy(&threadContext->stateCond); + MutexDeinit(&threadContext->stateMutex); + ConditionDeinit(&threadContext->stateCond); - pthread_mutex_destroy(&threadContext->sync.videoFrameMutex); - pthread_cond_broadcast(&threadContext->sync.videoFrameAvailableCond); - pthread_cond_destroy(&threadContext->sync.videoFrameAvailableCond); - pthread_cond_broadcast(&threadContext->sync.videoFrameRequiredCond); - pthread_cond_destroy(&threadContext->sync.videoFrameRequiredCond); + MutexDeinit(&threadContext->sync.videoFrameMutex); + ConditionWake(&threadContext->sync.videoFrameAvailableCond); + ConditionDeinit(&threadContext->sync.videoFrameAvailableCond); + ConditionWake(&threadContext->sync.videoFrameRequiredCond); + ConditionDeinit(&threadContext->sync.videoFrameRequiredCond); - pthread_cond_broadcast(&threadContext->sync.audioRequiredCond); - pthread_cond_destroy(&threadContext->sync.audioRequiredCond); - pthread_mutex_destroy(&threadContext->sync.audioBufferMutex); + ConditionWake(&threadContext->sync.audioRequiredCond); + ConditionDeinit(&threadContext->sync.audioRequiredCond); + MutexDeinit(&threadContext->sync.audioBufferMutex); } void GBAThreadPause(struct GBAThread* threadContext) { int frameOn = 1; - pthread_mutex_lock(&threadContext->stateMutex); + MutexLock(&threadContext->stateMutex); if (threadContext->state == THREAD_RUNNING) { if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) { threadContext->debugger->state = DEBUGGER_EXITING; @@ -182,37 +208,37 @@ void GBAThreadPause(struct GBAThread* threadContext) { threadContext->state = THREAD_PAUSED; frameOn = 0; } - pthread_mutex_unlock(&threadContext->stateMutex); - pthread_mutex_lock(&threadContext->sync.videoFrameMutex); + MutexUnlock(&threadContext->stateMutex); + MutexLock(&threadContext->sync.videoFrameMutex); if (frameOn != threadContext->sync.videoFrameOn) { threadContext->sync.videoFrameOn = frameOn; - pthread_cond_broadcast(&threadContext->sync.videoFrameAvailableCond); + ConditionWake(&threadContext->sync.videoFrameAvailableCond); } - pthread_mutex_unlock(&threadContext->sync.videoFrameMutex); + MutexUnlock(&threadContext->sync.videoFrameMutex); } void GBAThreadUnpause(struct GBAThread* threadContext) { int frameOn = 1; - pthread_mutex_lock(&threadContext->stateMutex); + MutexLock(&threadContext->stateMutex); if (threadContext->state == THREAD_PAUSED) { threadContext->state = THREAD_RUNNING; - pthread_cond_broadcast(&threadContext->stateCond); + ConditionWake(&threadContext->stateCond); } - pthread_mutex_unlock(&threadContext->stateMutex); - pthread_mutex_lock(&threadContext->sync.videoFrameMutex); + MutexUnlock(&threadContext->stateMutex); + MutexLock(&threadContext->sync.videoFrameMutex); if (frameOn != threadContext->sync.videoFrameOn) { threadContext->sync.videoFrameOn = frameOn; - pthread_cond_broadcast(&threadContext->sync.videoFrameAvailableCond); + ConditionWake(&threadContext->sync.videoFrameAvailableCond); } - pthread_mutex_unlock(&threadContext->sync.videoFrameMutex); + MutexUnlock(&threadContext->sync.videoFrameMutex); } void GBAThreadTogglePause(struct GBAThread* threadContext) { int frameOn = 1; - pthread_mutex_lock(&threadContext->stateMutex); + MutexLock(&threadContext->stateMutex); if (threadContext->state == THREAD_PAUSED) { threadContext->state = THREAD_RUNNING; - pthread_cond_broadcast(&threadContext->stateCond); + ConditionWake(&threadContext->stateCond); } else if (threadContext->state == THREAD_RUNNING) { if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) { threadContext->debugger->state = DEBUGGER_EXITING; @@ -220,35 +246,43 @@ void GBAThreadTogglePause(struct GBAThread* threadContext) { threadContext->state = THREAD_PAUSED; frameOn = 0; } - pthread_mutex_unlock(&threadContext->stateMutex); - pthread_mutex_lock(&threadContext->sync.videoFrameMutex); + MutexUnlock(&threadContext->stateMutex); + MutexLock(&threadContext->sync.videoFrameMutex); if (frameOn != threadContext->sync.videoFrameOn) { threadContext->sync.videoFrameOn = frameOn; - pthread_cond_broadcast(&threadContext->sync.videoFrameAvailableCond); + ConditionWake(&threadContext->sync.videoFrameAvailableCond); } - pthread_mutex_unlock(&threadContext->sync.videoFrameMutex); + MutexUnlock(&threadContext->sync.videoFrameMutex); } + +#ifdef USE_PTHREADS struct GBAThread* GBAThreadGetContext(void) { pthread_once(&_contextOnce, _createTLS); return pthread_getspecific(_contextKey); } +#else +struct GBAThread* GBAThreadGetContext(void) { + InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0); + return TlsGetValue(_contextKey); +} +#endif void GBASyncPostFrame(struct GBASync* sync) { if (!sync) { return; } - pthread_mutex_lock(&sync->videoFrameMutex); + MutexLock(&sync->videoFrameMutex); ++sync->videoFramePending; --sync->videoFrameSkip; if (sync->videoFrameSkip < 0) { - pthread_cond_broadcast(&sync->videoFrameAvailableCond); - if (sync->videoFrameWait) { - pthread_cond_wait(&sync->videoFrameRequiredCond, &sync->videoFrameMutex); + ConditionWake(&sync->videoFrameAvailableCond); + while (sync->videoFrameWait && sync->videoFramePending) { + ConditionWait(&sync->videoFrameRequiredCond, &sync->videoFrameMutex); } } - pthread_mutex_unlock(&sync->videoFrameMutex); + MutexUnlock(&sync->videoFrameMutex); } int GBASyncWaitFrameStart(struct GBASync* sync, int frameskip) { @@ -256,12 +290,12 @@ int GBASyncWaitFrameStart(struct GBASync* sync, int frameskip) { return 1; } - pthread_mutex_lock(&sync->videoFrameMutex); - pthread_cond_broadcast(&sync->videoFrameRequiredCond); + MutexLock(&sync->videoFrameMutex); + ConditionWake(&sync->videoFrameRequiredCond); if (!sync->videoFrameOn) { return 0; } - pthread_cond_wait(&sync->videoFrameAvailableCond, &sync->videoFrameMutex); + ConditionWait(&sync->videoFrameAvailableCond, &sync->videoFrameMutex); sync->videoFramePending = 0; sync->videoFrameSkip = frameskip; return 1; @@ -272,7 +306,7 @@ void GBASyncWaitFrameEnd(struct GBASync* sync) { return; } - pthread_mutex_unlock(&sync->videoFrameMutex); + MutexUnlock(&sync->videoFrameMutex); } int GBASyncDrawingFrame(struct GBASync* sync) { @@ -281,16 +315,17 @@ int GBASyncDrawingFrame(struct GBASync* sync) { void GBASyncProduceAudio(struct GBASync* sync, int wait) { if (sync->audioWait && wait) { - pthread_cond_wait(&sync->audioRequiredCond, &sync->audioBufferMutex); + // TODO loop properly in event of spurious wakeups + ConditionWait(&sync->audioRequiredCond, &sync->audioBufferMutex); } - pthread_mutex_unlock(&sync->audioBufferMutex); + MutexUnlock(&sync->audioBufferMutex); } void GBASyncLockAudio(struct GBASync* sync) { - pthread_mutex_lock(&sync->audioBufferMutex); + MutexLock(&sync->audioBufferMutex); } void GBASyncConsumeAudio(struct GBASync* sync) { - pthread_cond_broadcast(&sync->audioRequiredCond); - pthread_mutex_unlock(&sync->audioBufferMutex); + ConditionWake(&sync->audioRequiredCond); + MutexUnlock(&sync->audioBufferMutex); } diff --git a/src/gba/gba-thread.h b/src/gba/gba-thread.h index 0d80f2cff..82bcc4b92 100644 --- a/src/gba/gba-thread.h +++ b/src/gba/gba-thread.h @@ -1,7 +1,7 @@ #ifndef GBA_THREAD_H #define GBA_THREAD_H -#include +#include "threading.h" struct GBAThread; typedef void (*ThreadCallback)(struct GBAThread* threadContext); @@ -29,10 +29,10 @@ struct GBAThread { int frameskip; // Threading state - pthread_t thread; + Thread thread; - pthread_mutex_t stateMutex; - pthread_cond_t stateCond; + Mutex stateMutex; + Condition stateCond; ThreadCallback startCallback; ThreadCallback cleanCallback; @@ -43,13 +43,13 @@ struct GBAThread { int videoFrameWait; int videoFrameSkip; int videoFrameOn; - pthread_mutex_t videoFrameMutex; - pthread_cond_t videoFrameAvailableCond; - pthread_cond_t videoFrameRequiredCond; + Mutex videoFrameMutex; + Condition videoFrameAvailableCond; + Condition videoFrameRequiredCond; int audioWait; - pthread_cond_t audioRequiredCond; - pthread_mutex_t audioBufferMutex; + Condition audioRequiredCond; + Mutex audioBufferMutex; } sync; }; diff --git a/src/platform/sdl/sdl-events.c b/src/platform/sdl/sdl-events.c index 33e8eb661..dfd05a35f 100644 --- a/src/platform/sdl/sdl-events.c +++ b/src/platform/sdl/sdl-events.c @@ -133,10 +133,10 @@ void GBASDLHandleEvent(struct GBAThread* context, const union SDL_Event* event) if (context->debugger) { context->debugger->state = DEBUGGER_EXITING; } - pthread_mutex_lock(&context->stateMutex); + MutexLock(&context->stateMutex); context->state = THREAD_EXITING; - pthread_cond_broadcast(&context->stateCond); - pthread_mutex_unlock(&context->stateMutex); + ConditionWake(&context->stateCond); + MutexUnlock(&context->stateMutex); break; case SDL_KEYDOWN: case SDL_KEYUP: diff --git a/src/util/threading.h b/src/util/threading.h new file mode 100644 index 000000000..8488ede5a --- /dev/null +++ b/src/util/threading.h @@ -0,0 +1,120 @@ +#ifndef THREADING_H +#define THREADING_H + + +#ifdef USE_PTHREADS +#include + +#define THREAD_ENTRY void* +typedef THREAD_ENTRY (*ThreadEntry)(void*); + +typedef pthread_t Thread; +typedef pthread_mutex_t Mutex; +typedef pthread_cond_t Condition; + +static inline int MutexInit(Mutex* mutex) { + return pthread_mutex_init(mutex, 0); +} + +static inline int MutexDeinit(Mutex* mutex) { + return pthread_mutex_destroy(mutex); +} + +static inline int MutexLock(Mutex* mutex) { + return pthread_mutex_lock(mutex); +} + +static inline int MutexUnlock(Mutex* mutex) { + return pthread_mutex_unlock(mutex); +} + +static inline int ConditionInit(Condition* cond) { + return pthread_cond_init(cond, 0); +} + +static inline int ConditionDeinit(Condition* cond) { + return pthread_cond_destroy(cond); +} + +static inline int ConditionWait(Condition* cond, Mutex* mutex) { + return pthread_cond_wait(cond, mutex); +} + +static inline int ConditionWake(Condition* cond) { + return pthread_cond_broadcast(cond); +} + +static inline int ThreadCreate(Thread* thread, ThreadEntry entry, void* context) { + return pthread_create(thread, 0, entry, context); +} + +static inline int ThreadJoin(Thread thread) { + return pthread_join(thread, 0); +} + +#else +#define _WIN32_WINNT 0x0600 +#include +#define THREAD_ENTRY DWORD WINAPI +typedef THREAD_ENTRY ThreadEntry(LPVOID); + +typedef HANDLE Thread; +typedef CRITICAL_SECTION Mutex; +typedef CONDITION_VARIABLE Condition; + +static inline int MutexInit(Mutex* mutex) { + InitializeCriticalSection(mutex); + return GetLastError(); +} + +static inline int MutexDeinit(Mutex* mutex) { + DeleteCriticalSection(mutex); + return GetLastError(); +} + +static inline int MutexLock(Mutex* mutex) { + EnterCriticalSection(mutex); + return GetLastError(); +} + +static inline int MutexUnlock(Mutex* mutex) { + LeaveCriticalSection(mutex); + return GetLastError(); +} + +static inline int ConditionInit(Condition* cond) { + InitializeConditionVariable(cond); + return GetLastError(); +} + +static inline int ConditionDeinit(Condition* cond) { + // This is a no-op on Windows + (void)(cond); + return 0; +} + +static inline int ConditionWait(Condition* cond, Mutex* mutex) { + SleepConditionVariableCS(cond, mutex, INFINITE); + return GetLastError(); +} + +static inline int ConditionWake(Condition* cond) { + WakeAllConditionVariable(cond); + return GetLastError(); +} + +static inline int ThreadCreate(Thread* thread, ThreadEntry entry, void* context) { + *thread = CreateThread(NULL, 0, entry, context, 0, 0); + return GetLastError(); +} + +static inline int ThreadJoin(Thread thread) { + DWORD error = WaitForSingleObject(thread, INFINITE); + if (error == WAIT_FAILED) { + return GetLastError(); + } + return 0; +} +#endif + +#endif