From 548cb9089353d07323bb079976524562c1a5c8ed Mon Sep 17 00:00:00 2001 From: Joel Linn Date: Wed, 23 Dec 2020 13:33:36 -0800 Subject: [PATCH] Add SDL_mutex implementation using Windows Slim Reader/Writer Locks Keep Critical Section impl for Windows XP/Vista - choose at runtime v2: - Add SRW definitions as suggested by Ozkan Sezer Allows building against older platform headers. - Rename "hidden" function parameter `mutex_` to `_mutex` v3: - Use GetModuleHandle instead of LoadLibrary - Fix typo in comment --- include/SDL_hints.h | 14 ++ src/thread/windows/SDL_sysmutex.c | 264 +++++++++++++++++++++++++++--- 2 files changed, 259 insertions(+), 19 deletions(-) diff --git a/include/SDL_hints.h b/include/SDL_hints.h index a2fb2fa2a..0302b8d8f 100644 --- a/include/SDL_hints.h +++ b/include/SDL_hints.h @@ -1196,6 +1196,20 @@ extern "C" { */ #define SDL_HINT_WINDOWS_DISABLE_THREAD_NAMING "SDL_WINDOWS_DISABLE_THREAD_NAMING" +/** + * \brief Force SDL to use Critical Sections for mutexes on Windows. + * On Windows 7 and newer, Slim Reader/Writer Locks are available. + * They offer better performance, allocate no kernel ressources and + * use less memory. SDL will fall back to Critical Sections on older + * OS versions or if forced to by this hint. + * + * This variable can be set to the following values: + * "0" - Use SRW Locks when available. If not, fall back to Critical Sections. (default) + * "1" - Force the use of Critical Sections in all cases. + * + */ +#define SDL_HINT_WINDOWS_FORCE_MUTEX_CRITICAL_SECTIONS "SDL_WINDOWS_FORCE_MUTEX_CRITICAL_SECTIONS" + /** * \brief Tell SDL which Dispmanx layer to use on a Raspberry PI * diff --git a/src/thread/windows/SDL_sysmutex.c b/src/thread/windows/SDL_sysmutex.c index 4e393742a..7076239e7 100644 --- a/src/thread/windows/SDL_sysmutex.c +++ b/src/thread/windows/SDL_sysmutex.c @@ -22,26 +22,186 @@ #if SDL_THREAD_WINDOWS -/* Mutex functions using the Win32 API */ +/** + * Mutex functions using the Win32 API + * There are two implementations available based on: + * - Critical Sections. Available on all OS versions since Windows XP. + * - Slim Reader/Writer Locks. Requires Windows 7 or newer. + * which are chosen at runtime. + */ + #include "../../core/windows/SDL_windows.h" +#include "SDL_hints.h" #include "SDL_mutex.h" +typedef SDL_mutex * (*pfnSDL_CreateMutex)(void); +typedef int (*pfnSDL_LockMutex)(SDL_mutex *); +typedef int (*pfnSDL_TryLockMutex)(SDL_mutex *); +typedef int (*pfnSDL_UnlockMutex)(SDL_mutex *); +typedef void (*pfnSDL_DestroyMutex)(SDL_mutex *); -struct SDL_mutex +typedef struct SDL_mutex_impl_t { - CRITICAL_SECTION cs; + pfnSDL_CreateMutex Create; + pfnSDL_DestroyMutex Destroy; + pfnSDL_LockMutex Lock; + pfnSDL_TryLockMutex TryLock; + pfnSDL_UnlockMutex Unlock; +} SDL_mutex_impl_t; + +/* Implementation will be chosen at runtime based on available Kernel features */ +static SDL_mutex_impl_t SDL_mutex_impl_active = {0}; + + +/** + * Implementation based on Slim Reader/Writer (SRW) Locks for Win 7 and newer. + */ + +#ifndef SRWLOCK_INIT +#define SRWLOCK_INIT {0} +typedef struct _SRWLOCK { + PVOID Ptr; +} SRWLOCK, *PSRWLOCK; +#endif + +typedef VOID(WINAPI *pfnReleaseSRWLockExclusive)(PSRWLOCK); +typedef VOID(WINAPI *pfnAcquireSRWLockExclusive)(PSRWLOCK); +typedef BOOLEAN(WINAPI *pfnTryAcquireSRWLockExclusive)(PSRWLOCK); +static pfnReleaseSRWLockExclusive pReleaseSRWLockExclusive = NULL; +static pfnAcquireSRWLockExclusive pAcquireSRWLockExclusive = NULL; +static pfnTryAcquireSRWLockExclusive pTryAcquireSRWLockExclusive = NULL; + +typedef struct SDL_mutex_srw +{ + SRWLOCK srw; + /* SRW Locks are not recursive, that has to be handled by SDL: */ + DWORD count; + DWORD owner; +} SDL_mutex_srw; + +static SDL_mutex * +SDL_CreateMutex_srw(void) +{ + SDL_mutex_srw *mutex; + + /* Relies on SRWLOCK_INIT == 0. */ + mutex = (SDL_mutex_srw *) SDL_calloc(1, sizeof(*mutex)); + if (!mutex) { + SDL_OutOfMemory(); + } + + return (SDL_mutex *)mutex; +} + +static void +SDL_DestroyMutex_srw(SDL_mutex * mutex) +{ + if (mutex) { + /* There are no kernel allocated resources */ + SDL_free(mutex); + } +} + +static int +SDL_LockMutex_srw(SDL_mutex * _mutex) +{ + SDL_mutex_srw *mutex = (SDL_mutex_srw *)_mutex; + DWORD this_thread; + + if (mutex == NULL) { + return SDL_SetError("Passed a NULL mutex"); + } + + this_thread = GetCurrentThreadId(); + if (mutex->owner == this_thread) { + ++mutex->count; + } else { + /* The order of operations is important. + We set the locking thread id after we obtain the lock + so unlocks from other threads will fail. + */ + pAcquireSRWLockExclusive(&mutex->srw); + mutex->owner = this_thread; + ++mutex->count; + } + return 0; +} + +static int +SDL_TryLockMutex_srw(SDL_mutex * _mutex) +{ + SDL_mutex_srw *mutex = (SDL_mutex_srw *)_mutex; + DWORD this_thread; + int retval = 0; + + if (mutex == NULL) { + return SDL_SetError("Passed a NULL mutex"); + } + + this_thread = GetCurrentThreadId(); + if (mutex->owner == this_thread) { + ++mutex->count; + } else { + if (pTryAcquireSRWLockExclusive(&mutex->srw) != 0) { + mutex->owner = this_thread; + ++mutex->count; + } else { + retval = SDL_MUTEX_TIMEDOUT; + } + } + return retval; +} + +static int +SDL_UnlockMutex_srw(SDL_mutex * _mutex) +{ + SDL_mutex_srw *mutex = (SDL_mutex_srw *)_mutex; + + if (mutex == NULL) { + return SDL_SetError("Passed a NULL mutex"); + } + + if (mutex->owner == GetCurrentThreadId()) { + if (--mutex->count == 0) { + mutex->owner = 0; + pReleaseSRWLockExclusive(&mutex->srw); + } + } else { + return SDL_SetError("mutex not owned by this thread"); + } + + return 0; +} + +static const SDL_mutex_impl_t SDL_mutex_impl_srw = +{ + &SDL_CreateMutex_srw, + &SDL_DestroyMutex_srw, + &SDL_LockMutex_srw, + &SDL_TryLockMutex_srw, + &SDL_UnlockMutex_srw, }; -/* Create a mutex */ -SDL_mutex * -SDL_CreateMutex(void) + +/** + * Fallback Mutex implementation using Critical Sections (before Win 7) + */ + +typedef struct SDL_mutex_cs { - SDL_mutex *mutex; + CRITICAL_SECTION cs; +} SDL_mutex_cs; + +/* Create a mutex */ +static SDL_mutex * +SDL_CreateMutex_cs(void) +{ + SDL_mutex_cs *mutex; /* Allocate mutex memory */ - mutex = (SDL_mutex *) SDL_malloc(sizeof(*mutex)); + mutex = (SDL_mutex_cs *) SDL_malloc(sizeof(*mutex)); if (mutex) { /* Initialize */ /* On SMP systems, a non-zero spin count generally helps performance */ @@ -53,13 +213,14 @@ SDL_CreateMutex(void) } else { SDL_OutOfMemory(); } - return (mutex); + return (SDL_mutex *)mutex; } /* Free the mutex */ -void -SDL_DestroyMutex(SDL_mutex * mutex) +static void +SDL_DestroyMutex_cs(SDL_mutex * mutex_) { + SDL_mutex_cs *mutex = (SDL_mutex_cs *)mutex_; if (mutex) { DeleteCriticalSection(&mutex->cs); SDL_free(mutex); @@ -67,21 +228,23 @@ SDL_DestroyMutex(SDL_mutex * mutex) } /* Lock the mutex */ -int -SDL_LockMutex(SDL_mutex * mutex) +static int +SDL_LockMutex_cs(SDL_mutex * mutex_) { + SDL_mutex_cs *mutex = (SDL_mutex_cs *)mutex_; if (mutex == NULL) { return SDL_SetError("Passed a NULL mutex"); } EnterCriticalSection(&mutex->cs); - return (0); + return 0; } /* TryLock the mutex */ -int -SDL_TryLockMutex(SDL_mutex * mutex) +static int +SDL_TryLockMutex_cs(SDL_mutex * mutex_) { + SDL_mutex_cs *mutex = (SDL_mutex_cs *)mutex_; int retval = 0; if (mutex == NULL) { return SDL_SetError("Passed a NULL mutex"); @@ -94,15 +257,78 @@ SDL_TryLockMutex(SDL_mutex * mutex) } /* Unlock the mutex */ -int -SDL_UnlockMutex(SDL_mutex * mutex) +static int +SDL_UnlockMutex_cs(SDL_mutex * mutex_) { + SDL_mutex_cs *mutex = (SDL_mutex_cs *)mutex_; if (mutex == NULL) { return SDL_SetError("Passed a NULL mutex"); } LeaveCriticalSection(&mutex->cs); - return (0); + return 0; +} + +static const SDL_mutex_impl_t SDL_mutex_impl_cs = +{ + &SDL_CreateMutex_cs, + &SDL_DestroyMutex_cs, + &SDL_LockMutex_cs, + &SDL_TryLockMutex_cs, + &SDL_UnlockMutex_cs, +}; + + +/** + * Runtime selection and redirection + */ + +SDL_mutex * +SDL_CreateMutex(void) +{ + if (SDL_mutex_impl_active.Create == NULL) { + /* Default to fallback implementation */ + const SDL_mutex_impl_t * impl = &SDL_mutex_impl_cs; + + if (!SDL_GetHintBoolean(SDL_HINT_WINDOWS_FORCE_MUTEX_CRITICAL_SECTIONS, SDL_FALSE)) { + /* Try faster implementation for Windows 7 and newer */ + HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll"); + if (kernel32) { + /* Requires Vista: */ + pReleaseSRWLockExclusive = (pfnReleaseSRWLockExclusive) GetProcAddress(kernel32, "ReleaseSRWLockExclusive"); + pAcquireSRWLockExclusive = (pfnAcquireSRWLockExclusive) GetProcAddress(kernel32, "AcquireSRWLockExclusive"); + /* Requires 7: */ + pTryAcquireSRWLockExclusive = (pfnTryAcquireSRWLockExclusive) GetProcAddress(kernel32, "TryAcquireSRWLockExclusive"); + if (pReleaseSRWLockExclusive && pAcquireSRWLockExclusive && pTryAcquireSRWLockExclusive) { + impl = &SDL_mutex_impl_srw; + } + } + } + + /* Copy instead of using pointer to save one level of indirection */ + SDL_memcpy(&SDL_mutex_impl_active, impl, sizeof(SDL_mutex_impl_active)); + } + return SDL_mutex_impl_active.Create(); +} + +void +SDL_DestroyMutex(SDL_mutex * mutex) { + SDL_mutex_impl_active.Destroy(mutex); +} + +int +SDL_LockMutex(SDL_mutex * mutex) { + return SDL_mutex_impl_active.Lock(mutex); +} + +int +SDL_TryLockMutex(SDL_mutex * mutex) { + return SDL_mutex_impl_active.TryLock(mutex); +} + +int +SDL_UnlockMutex(SDL_mutex * mutex) { + return SDL_mutex_impl_active.Unlock(mutex); } #endif /* SDL_THREAD_WINDOWS */