Commit 548cb9089353d07323bb079976524562c1a5c8ed

Joel Linn 2020-12-23T13:33:36

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

diff --git a/include/SDL_hints.h b/include/SDL_hints.h
index a2fb2fa..0302b8d 100644
--- a/include/SDL_hints.h
+++ b/include/SDL_hints.h
@@ -1197,6 +1197,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
  *
  * Also known as Z-order. The variable can take a negative or positive value.
diff --git a/src/thread/windows/SDL_sysmutex.c b/src/thread/windows/SDL_sysmutex.c
index 4e39374..7076239 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,
 };
 
+
+/**
+ * Fallback Mutex implementation using Critical Sections (before Win 7)
+ */
+
+typedef struct SDL_mutex_cs
+{
+    CRITICAL_SECTION cs;
+} SDL_mutex_cs;
+
 /* Create a mutex */
-SDL_mutex *
-SDL_CreateMutex(void)
+static SDL_mutex *
+SDL_CreateMutex_cs(void)
 {
-    SDL_mutex *mutex;
+    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 */