Browse Source

rwlock: Added SDL_rwlock API for shared locks.

Ryan C. Gordon 2 years ago
parent
commit
e474047ff8

+ 7 - 3
CMakeLists.txt

@@ -2008,11 +2008,14 @@ elseif(WINDOWS)
 
   if(SDL_THREADS)
     set(SDL_THREAD_GENERIC_COND_SUFFIX 1)
+    set(SDL_THREAD_GENERIC_RWLOCK_SUFFIX 1)
     set(SDL_THREAD_WINDOWS 1)
     list(APPEND SOURCE_FILES
       ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_syscond.c
+      ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock.c
       ${SDL3_SOURCE_DIR}/src/thread/windows/SDL_syscond_cv.c
       ${SDL3_SOURCE_DIR}/src/thread/windows/SDL_sysmutex.c
+      ${SDL3_SOURCE_DIR}/src/thread/windows/SDL_sysrwlock_srw.c
       ${SDL3_SOURCE_DIR}/src/thread/windows/SDL_syssem.c
       ${SDL3_SOURCE_DIR}/src/thread/windows/SDL_systhread.c
       ${SDL3_SOURCE_DIR}/src/thread/windows/SDL_systls.c)
@@ -2597,6 +2600,7 @@ elseif(VITA)
       ${SDL3_SOURCE_DIR}/src/thread/vita/SDL_syssem.c
       ${SDL3_SOURCE_DIR}/src/thread/vita/SDL_systhread.c
       ${SDL3_SOURCE_DIR}/src/thread/vita/SDL_syscond.c
+      ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock.c
       ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_systls.c)
     set(HAVE_SDL_THREADS TRUE)
   endif()
@@ -2732,7 +2736,7 @@ elseif(PSP)
   endif()
   if(SDL_THREADS)
     set(SDL_THREAD_PSP 1)
-    file(GLOB PSP_THREAD_SOURCES ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_systls.c ${SDL3_SOURCE_DIR}/src/thread/psp/*.c)
+    file(GLOB PSP_THREAD_SOURCES ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_systls.c ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock.c ${SDL3_SOURCE_DIR}/src/thread/psp/*.c)
     list(APPEND SOURCE_FILES ${PSP_THREAD_SOURCES})
     set(HAVE_SDL_THREADS TRUE)
   endif()
@@ -2791,7 +2795,7 @@ elseif(PS2)
   endif()
   if(SDL_THREADS)
     set(SDL_THREAD_PS2 1)
-    file(GLOB PS2_THREAD_SOURCES ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_syscond.c ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysmutex.c ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_systls.c ${SDL3_SOURCE_DIR}/src/thread/ps2/*.c)
+    file(GLOB PS2_THREAD_SOURCES ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_syscond.c ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysmutex.c ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock.c ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_systls.c ${SDL3_SOURCE_DIR}/src/thread/ps2/*.c)
     list(APPEND SOURCE_FILES ${PS2_THREAD_SOURCES})
     set(HAVE_SDL_THREADS TRUE)
   endif()
@@ -2852,7 +2856,7 @@ elseif(N3DS)
   if(SDL_THREADS)
     set(SDL_THREAD_N3DS 1)
     file(GLOB N3DS_THREAD_SOURCES ${SDL3_SOURCE_DIR}/src/thread/n3ds/*.c)
-    list(APPEND SOURCE_FILES ${N3DS_THREAD_SOURCES} ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_systls.c)
+    list(APPEND SOURCE_FILES ${N3DS_THREAD_SOURCES} ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_systls.c ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock.c)
     set(HAVE_SDL_THREADS TRUE)
   endif()
 

+ 6 - 0
VisualC-GDK/SDL/SDL.vcxproj.filters

@@ -1225,6 +1225,9 @@
     <ClCompile Include="..\..\src\thread\windows\SDL_sysmutex.c">
       <Filter>thread\windows</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\src\thread\windows\SDL_sysrwlock_srw.c">
+      <Filter>thread\windows</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\src\thread\windows\SDL_syssem.c">
       <Filter>thread\windows</Filter>
     </ClCompile>
@@ -1237,6 +1240,9 @@
     <ClCompile Include="..\..\src\thread\generic\SDL_syscond.c">
       <Filter>thread\generic</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\src\thread\generic\SDL_sysrwlock.c">
+      <Filter>thread\generic</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\src\stdlib\SDL_crc16.c">
       <Filter>stdlib</Filter>
     </ClCompile>

+ 18 - 0
VisualC-WinRT/SDL-UWP.vcxproj

@@ -444,6 +444,24 @@
       <CompileAsWinRT Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</CompileAsWinRT>
       <CompileAsWinRT Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</CompileAsWinRT>
     </ClCompile>
+    <ClCompile Include="..\src\thread\stdcpp\SDL_sysrwlock.cpp">
+      <PrecompiledHeaderOutputFile Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)$(TargetName)_cpp.pch</PrecompiledHeaderOutputFile>
+      <PrecompiledHeaderOutputFile Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(IntDir)$(TargetName)_cpp.pch</PrecompiledHeaderOutputFile>
+      <PrecompiledHeaderOutputFile Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(IntDir)$(TargetName)_cpp.pch</PrecompiledHeaderOutputFile>
+      <PrecompiledHeaderOutputFile Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(IntDir)$(TargetName)_cpp.pch</PrecompiledHeaderOutputFile>
+      <PrecompiledHeaderOutputFile Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">$(IntDir)$(TargetName)_cpp.pch</PrecompiledHeaderOutputFile>
+      <PrecompiledHeaderOutputFile Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">$(IntDir)$(TargetName)_cpp.pch</PrecompiledHeaderOutputFile>
+      <PrecompiledHeaderOutputFile Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)$(TargetName)_cpp.pch</PrecompiledHeaderOutputFile>
+      <PrecompiledHeaderOutputFile Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)$(TargetName)_cpp.pch</PrecompiledHeaderOutputFile>
+      <CompileAsWinRT Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</CompileAsWinRT>
+      <CompileAsWinRT Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</CompileAsWinRT>
+      <CompileAsWinRT Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</CompileAsWinRT>
+      <CompileAsWinRT Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</CompileAsWinRT>
+      <CompileAsWinRT Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</CompileAsWinRT>
+      <CompileAsWinRT Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</CompileAsWinRT>
+      <CompileAsWinRT Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</CompileAsWinRT>
+      <CompileAsWinRT Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</CompileAsWinRT>
+    </ClCompile>
     <ClCompile Include="..\src\thread\stdcpp\SDL_systhread.cpp">
       <PrecompiledHeaderOutputFile Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)$(TargetName)_cpp.pch</PrecompiledHeaderOutputFile>
       <PrecompiledHeaderOutputFile Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(IntDir)$(TargetName)_cpp.pch</PrecompiledHeaderOutputFile>

+ 3 - 0
VisualC-WinRT/SDL-UWP.vcxproj.filters

@@ -678,6 +678,9 @@
     <ClCompile Include="..\src\thread\stdcpp\SDL_sysmutex.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\src\thread\stdcpp\SDL_sysrwlock.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="..\src\thread\stdcpp\SDL_systhread.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>

+ 3 - 0
VisualC/SDL/SDL.vcxproj

@@ -402,6 +402,7 @@
     <ClInclude Include="..\..\src\thread\SDL_thread_c.h" />
     <ClInclude Include="..\..\src\thread\generic\SDL_syscond_c.h" />
     <ClInclude Include="..\..\src\thread\windows\SDL_sysmutex_c.h" />
+    <ClInclude Include="..\..\src\thread\generic\SDL_sysrwlock_c.h" />
     <ClInclude Include="..\..\src\thread\windows\SDL_systhread_c.h" />
     <ClInclude Include="..\..\src\timer\SDL_timer_c.h" />
     <ClInclude Include="..\..\src\video\dummy\SDL_nullevents_c.h" />
@@ -599,9 +600,11 @@
     <ClCompile Include="..\..\src\stdlib\SDL_string.c" />
     <ClCompile Include="..\..\src\stdlib\SDL_strtokr.c" />
     <ClCompile Include="..\..\src\thread\generic\SDL_syscond.c" />
+    <ClCompile Include="..\..\src\thread\generic\SDL_sysrwlock.c" />
     <ClCompile Include="..\..\src\thread\SDL_thread.c" />
     <ClCompile Include="..\..\src\thread\windows\SDL_syscond_cv.c" />
     <ClCompile Include="..\..\src\thread\windows\SDL_sysmutex.c" />
+    <ClCompile Include="..\..\src\thread\windows\SDL_sysrwlock_srw.c" />
     <ClCompile Include="..\..\src\thread\windows\SDL_syssem.c" />
     <ClCompile Include="..\..\src\thread\windows\SDL_systhread.c" />
     <ClCompile Include="..\..\src\thread\windows\SDL_systls.c" />

+ 3 - 0
VisualC/SDL/SDL.vcxproj.filters

@@ -1213,6 +1213,9 @@
     <ClCompile Include="..\..\src\thread\windows\SDL_sysmutex.c">
       <Filter>thread\windows</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\src\thread\windows\SDL_sysrwlock_srw.c">
+      <Filter>thread\windows</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\src\thread\windows\SDL_syssem.c">
       <Filter>thread\windows</Filter>
     </ClCompile>

+ 20 - 0
Xcode/SDL/SDL.xcodeproj/project.pbxproj

@@ -83,6 +83,15 @@
 		566E26CF246274CC00718109 /* SDL_syslocale.m in Sources */ = {isa = PBXBuildFile; fileRef = 566E26CC246274CB00718109 /* SDL_syslocale.m */; };
 		566E26D8246274CC00718109 /* SDL_locale.c in Sources */ = {isa = PBXBuildFile; fileRef = 566E26CD246274CB00718109 /* SDL_locale.c */; };
 		566E26E1246274CC00718109 /* SDL_syslocale.h in Headers */ = {isa = PBXBuildFile; fileRef = 566E26CE246274CC00718109 /* SDL_syslocale.h */; };
+		56A2373329F9C113003CCA5F /* SDL_sysrwlock.c in Sources */ = {isa = PBXBuildFile; fileRef = 56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */; };
+		56A2373429F9C113003CCA5F /* SDL_sysrwlock.c in Sources */ = {isa = PBXBuildFile; fileRef = 56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */; };
+		56A2373529F9C113003CCA5F /* SDL_sysrwlock.c in Sources */ = {isa = PBXBuildFile; fileRef = 56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */; };
+		56A2373629F9C113003CCA5F /* SDL_sysrwlock.c in Sources */ = {isa = PBXBuildFile; fileRef = 56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */; };
+		56A2373729F9C113003CCA5F /* SDL_sysrwlock.c in Sources */ = {isa = PBXBuildFile; fileRef = 56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */; };
+		56A2373829F9C113003CCA5F /* SDL_sysrwlock.c in Sources */ = {isa = PBXBuildFile; fileRef = 56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */; };
+		56A2373929F9C113003CCA5F /* SDL_sysrwlock.c in Sources */ = {isa = PBXBuildFile; fileRef = 56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */; };
+		56A2373A29F9C113003CCA5F /* SDL_sysrwlock.c in Sources */ = {isa = PBXBuildFile; fileRef = 56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */; };
+		56A2373B29F9C113003CCA5F /* SDL_sysrwlock.c in Sources */ = {isa = PBXBuildFile; fileRef = 56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */; };
 		56C5237F1D8F4985001F2F30 /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7381E951D8B69D600B177DD /* CoreAudio.framework */; };
 		56C523811D8F498C001F2F30 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00D0D08310675DD9004B05EF /* CoreFoundation.framework */; };
 		75E0915A241EA924004729E1 /* SDL_virtualjoystick.c in Sources */ = {isa = PBXBuildFile; fileRef = 75E09158241EA924004729E1 /* SDL_virtualjoystick.c */; };
@@ -3482,6 +3491,7 @@
 		566E26CC246274CB00718109 /* SDL_syslocale.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDL_syslocale.m; path = locale/macos/SDL_syslocale.m; sourceTree = "<group>"; };
 		566E26CD246274CB00718109 /* SDL_locale.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = SDL_locale.c; path = locale/SDL_locale.c; sourceTree = "<group>"; };
 		566E26CE246274CC00718109 /* SDL_syslocale.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_syslocale.h; path = locale/SDL_syslocale.h; sourceTree = "<group>"; };
+		56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_sysrwlock.c; sourceTree = "<group>"; };
 		75E09158241EA924004729E1 /* SDL_virtualjoystick.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_virtualjoystick.c; sourceTree = "<group>"; };
 		75E09159241EA924004729E1 /* SDL_virtualjoystick_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_virtualjoystick_c.h; sourceTree = "<group>"; };
 		9846B07B287A9020000C35C8 /* SDL_hidapi_shield.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_shield.c; sourceTree = "<group>"; };
@@ -4691,6 +4701,7 @@
 		A7D8A78123E2513E00DCD162 /* pthread */ = {
 			isa = PBXGroup;
 			children = (
+				56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */,
 				A7D8A78523E2513E00DCD162 /* SDL_syscond.c */,
 				A7D8A78823E2513E00DCD162 /* SDL_sysmutex_c.h */,
 				A7D8A78723E2513E00DCD162 /* SDL_sysmutex.c */,
@@ -7328,6 +7339,7 @@
 				A75FCE7023E25AB700529352 /* SDL_hidapi_xboxone.c in Sources */,
 				A75FCE7123E25AB700529352 /* SDL_blit_auto.c in Sources */,
 				A75FCE7323E25AB700529352 /* SDL_keyboard.c in Sources */,
+				56A2373A29F9C113003CCA5F /* SDL_sysrwlock.c in Sources */,
 				A75FCE7523E25AB700529352 /* SDL_rect.c in Sources */,
 				A75FCE7623E25AB700529352 /* SDL_cocoaopengles.m in Sources */,
 				A75FCE7723E25AB700529352 /* SDL_qsort.c in Sources */,
@@ -7523,6 +7535,7 @@
 				A75FD02923E25AC700529352 /* SDL_hidapi_xboxone.c in Sources */,
 				A75FD02A23E25AC700529352 /* SDL_blit_auto.c in Sources */,
 				A75FD02C23E25AC700529352 /* SDL_keyboard.c in Sources */,
+				56A2373B29F9C113003CCA5F /* SDL_sysrwlock.c in Sources */,
 				A75FD02E23E25AC700529352 /* SDL_rect.c in Sources */,
 				A75FD02F23E25AC700529352 /* SDL_cocoaopengles.m in Sources */,
 				A75FD03023E25AC700529352 /* SDL_qsort.c in Sources */,
@@ -7718,6 +7731,7 @@
 				A769B20423E259AE00872273 /* SDL_hidapi_switch.c in Sources */,
 				F3984CD525BCC92900374F43 /* SDL_hidapi_stadia.c in Sources */,
 				A769B20523E259AE00872273 /* SDL_strtokr.c in Sources */,
+				56A2373829F9C113003CCA5F /* SDL_sysrwlock.c in Sources */,
 				5605720B2473687A00B46B66 /* SDL_syslocale.m in Sources */,
 				F3820718284F3609004DD584 /* controller_type.c in Sources */,
 				A769B20623E259AE00872273 /* SDL_clipboardevents.c in Sources */,
@@ -7913,6 +7927,7 @@
 				A7D8BB6A23E2514500DCD162 /* SDL_keyboard.c in Sources */,
 				A7D8ACE823E2514100DCD162 /* SDL_rect.c in Sources */,
 				A7D8AE9B23E2514100DCD162 /* SDL_cocoaopengles.m in Sources */,
+				56A2373429F9C113003CCA5F /* SDL_sysrwlock.c in Sources */,
 				A7D8B96923E2514400DCD162 /* SDL_qsort.c in Sources */,
 				A7D8B55223E2514300DCD162 /* SDL_hidapi_switch.c in Sources */,
 				A7D8B96323E2514400DCD162 /* SDL_strtokr.c in Sources */,
@@ -8108,6 +8123,7 @@
 				A7D8BB6B23E2514500DCD162 /* SDL_keyboard.c in Sources */,
 				A7D8ACE923E2514100DCD162 /* SDL_rect.c in Sources */,
 				A7D8AE9C23E2514100DCD162 /* SDL_cocoaopengles.m in Sources */,
+				56A2373529F9C113003CCA5F /* SDL_sysrwlock.c in Sources */,
 				A7D8B96A23E2514400DCD162 /* SDL_qsort.c in Sources */,
 				A7D8B55323E2514300DCD162 /* SDL_hidapi_switch.c in Sources */,
 				A7D8B96423E2514400DCD162 /* SDL_strtokr.c in Sources */,
@@ -8303,6 +8319,7 @@
 				A7D8B55523E2514300DCD162 /* SDL_hidapi_switch.c in Sources */,
 				F3984CD425BCC92900374F43 /* SDL_hidapi_stadia.c in Sources */,
 				A7D8B96623E2514400DCD162 /* SDL_strtokr.c in Sources */,
+				56A2373729F9C113003CCA5F /* SDL_sysrwlock.c in Sources */,
 				560572092473687900B46B66 /* SDL_syslocale.m in Sources */,
 				F3820717284F3609004DD584 /* controller_type.c in Sources */,
 				A7D8BB7923E2514500DCD162 /* SDL_clipboardevents.c in Sources */,
@@ -8469,6 +8486,7 @@
 				A7D8ADE623E2514100DCD162 /* SDL_blit_0.c in Sources */,
 				A7D8BB0923E2514500DCD162 /* k_tan.c in Sources */,
 				A7D8B8A823E2514400DCD162 /* SDL_diskaudio.c in Sources */,
+				56A2373329F9C113003CCA5F /* SDL_sysrwlock.c in Sources */,
 				566E26CF246274CC00718109 /* SDL_syslocale.m in Sources */,
 				A7D8AFC023E2514200DCD162 /* SDL_egl.c in Sources */,
 				A7D8AC3323E2514100DCD162 /* SDL_RLEaccel.c in Sources */,
@@ -8663,6 +8681,7 @@
 				A7D8BBF223E2574800DCD162 /* SDL_uikitevents.m in Sources */,
 				A7D8BBB923E2560500DCD162 /* SDL_steamcontroller.c in Sources */,
 				A7D8B8AB23E2514400DCD162 /* SDL_diskaudio.c in Sources */,
+				56A2373629F9C113003CCA5F /* SDL_sysrwlock.c in Sources */,
 				A7D8AFC323E2514200DCD162 /* SDL_egl.c in Sources */,
 				A7D8AC3623E2514100DCD162 /* SDL_RLEaccel.c in Sources */,
 				A7D8BBB423E2514500DCD162 /* SDL_assert.c in Sources */,
@@ -8857,6 +8876,7 @@
 				A7D8ADEB23E2514100DCD162 /* SDL_blit_0.c in Sources */,
 				A7D8BB0E23E2514500DCD162 /* k_tan.c in Sources */,
 				A7D8B8AD23E2514400DCD162 /* SDL_diskaudio.c in Sources */,
+				56A2373929F9C113003CCA5F /* SDL_sysrwlock.c in Sources */,
 				A7D8AFC523E2514200DCD162 /* SDL_egl.c in Sources */,
 				A7D8AC3823E2514100DCD162 /* SDL_RLEaccel.c in Sources */,
 				A7D8BBB623E2514500DCD162 /* SDL_assert.c in Sources */,

+ 1 - 0
cmake/sdlchecks.cmake

@@ -860,6 +860,7 @@ macro(CheckPTHREAD)
           ${SDL3_SOURCE_DIR}/src/thread/pthread/SDL_systhread.c
           ${SDL3_SOURCE_DIR}/src/thread/pthread/SDL_sysmutex.c   # Can be faked, if necessary
           ${SDL3_SOURCE_DIR}/src/thread/pthread/SDL_syscond.c    # Can be faked, if necessary
+          ${SDL3_SOURCE_DIR}/src/thread/pthread/SDL_sysrwlock.c   # Can be faked, if necessary
           ${SDL3_SOURCE_DIR}/src/thread/pthread/SDL_systls.c
           )
       if(HAVE_PTHREADS_SEM)

+ 223 - 3
include/SDL3/SDL_mutex.h

@@ -203,11 +203,9 @@ extern DECLSPEC int SDLCALL SDL_TryLockMutex(SDL_mutex * mutex) SDL_TRY_ACQUIRE(
  * unlock it the same number of times before it is actually made available for
  * other threads in the system (this is known as a "recursive mutex").
  *
- * It is an error to unlock a mutex that has not been locked by the current
+ * It is illegal to unlock a mutex that has not been locked by the current
  * thread, and doing so results in undefined behavior.
  *
- * It is also an error to unlock a mutex that isn't locked at all.
- *
  * \param mutex the mutex to unlock.
  * \returns 0 on success or a negative error code on failure; call
  *          SDL_GetError() for more information.
@@ -240,6 +238,228 @@ extern DECLSPEC void SDLCALL SDL_DestroyMutex(SDL_mutex * mutex);
 /* @} *//* Mutex functions */
 
 
+/**
+ *  \name Read/write lock functions
+ */
+/* @{ */
+
+/* The SDL read/write lock structure, defined in SDL_sysrwlock.c */
+struct SDL_rwlock;
+typedef struct SDL_rwlock SDL_rwlock;
+
+/*
+ *  Synchronization functions which can time out return this value
+ *  if they time out.
+ */
+#define SDL_RWLOCK_TIMEDOUT SDL_MUTEX_TIMEDOUT
+
+
+/**
+ * Create a new read/write lock.
+ *
+ * A read/write lock is useful for situations where you have multiple
+ * threads trying to access a resource that is rarely updated. All threads
+ * requesting a read-only lock will be allowed to run in parallel; if a
+ * thread requests a write lock, it will be provided exclusive access.
+ * This makes it safe for multiple threads to use a resource at the same
+ * time if they promise not to change it, and when it has to be changed,
+ * the rwlock will serve as a gateway to make sure those changes can be
+ * made safely.
+ *
+ * In the right situation, a rwlock can be more efficient than a mutex,
+ * which only lets a single thread proceed at a time, even if it won't be
+ * modifying the data.
+ *
+ * All newly-created read/write locks begin in the _unlocked_ state.
+ *
+ * Calls to SDL_LockRWLockForReading() and SDL_LockRWLockForWriting will
+ * not return while the rwlock is locked _for writing_ by another thread.
+ * See SDL_TryLockRWLockForReading() and SDL_TryLockRWLockForWriting() to
+ * attempt to lock without blocking.
+ *
+ * SDL read/write locks are only recursive for read-only locks! They
+ * are not guaranteed to be fair, or provide access in a FIFO manner! They
+ * are not guaranteed to favor writers. You may not lock a rwlock for
+ * both read-only and write access at the same time from the same thread
+ * (so you can't promote your read-only lock to a write lock without
+ * unlocking first).
+ *
+ * \returns the initialized and unlocked read/write lock or NULL on
+ *          failure; call SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_DestroyRWLock
+ * \sa SDL_LockRWLockForReading
+ * \sa SDL_TryLockRWLockForReading
+ * \sa SDL_LockRWLockForWriting
+ * \sa SDL_TryLockRWLockForWriting
+ * \sa SDL_UnlockRWLock
+ */
+extern DECLSPEC SDL_rwlock *SDLCALL SDL_CreateRWLock(void);
+
+/**
+ * Lock the read/write lock for _read only_ operations.
+ *
+ * This will block until the rwlock is available, which is to say it is
+ * not locked for writing by any other thread. Of all threads waiting to
+ * lock the rwlock, all may do so at the same time as long as they are
+ * requesting read-only access; if a thread wants to lock for writing,
+ * only one may do so at a time, and no other threads, read-only or not,
+ * may hold the lock at the same time.
+ *
+ * It is legal for the owning thread to lock an already-locked rwlock
+ * for reading. It must unlock it the same number of times before it is
+ * actually made available for other threads in the system (this is known
+ * as a "recursive rwlock").
+ *
+ * Note that locking for writing is not recursive (this is only available
+ * to read-only locks).
+ *
+ * It is illegal to request a read-only lock from a thread that already
+ * holds the write lock. Doing so results in undefined behavior. Unlock the
+ * write lock before requesting a read-only lock. (But, of course, if you
+ * have the write lock, you don't need further locks to read in any case.)
+ *
+ * \param rwlock the read/write lock to lock
+ * \returns 0 on success or a negative error code on failure; call
+ *          SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_UnlockRWLock
+ */
+extern DECLSPEC int SDLCALL SDL_LockRWLockForReading(SDL_rwlock * rwlock) SDL_ACQUIRE_SHARED(rwlock);
+
+/**
+ * Lock the read/write lock for _write_ operations.
+ *
+ * This will block until the rwlock is available, which is to say it is
+ * not locked for reading or writing by any other thread. Only one thread
+ * may hold the lock when it requests write access; all other threads,
+ * whether they also want to write or only want read-only access, must wait
+ * until the writer thread has released the lock.
+ *
+ * It is illegal for the owning thread to lock an already-locked rwlock
+ * for writing (read-only may be locked recursively, writing can not). Doing
+ * so results in undefined behavior.
+ *
+ * It is illegal to request a write lock from a thread that already holds
+ * a read-only lock. Doing so results in undefined behavior. Unlock the
+ * read-only lock before requesting a write lock.
+ *
+ * \param rwlock the read/write lock to lock
+ * \returns 0 on success or a negative error code on failure; call
+ *          SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_UnlockRWLock
+ */
+extern DECLSPEC int SDLCALL SDL_LockRWLockForWriting(SDL_rwlock * rwlock) SDL_ACQUIRE(rwlock);
+
+/**
+ * Try to lock a read/write lock _for reading_ without blocking.
+ *
+ * This works just like SDL_LockRWLockForReading(), but if the rwlock is not
+ * available, then this function returns `SDL_RWLOCK_TIMEDOUT` immediately.
+ *
+ * This technique is useful if you need access to a resource but
+ * don't want to wait for it, and will return to it to try again later.
+ *
+ * Trying to lock for read-only access can succeed if other threads are
+ * holding read-only locks, as this won't prevent access.
+ *
+ * \param rwlock the rwlock to try to lock
+ * \returns 0, `SDL_RWLOCK_TIMEDOUT`, or -1 on error; call SDL_GetError() for
+ *          more information.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_CreateRWLock
+ * \sa SDL_DestroyRWLock
+ * \sa SDL_TryLockRWLockForReading
+ * \sa SDL_UnlockRWLock
+ */
+extern DECLSPEC int SDLCALL SDL_TryLockRWLockForReading(SDL_rwlock * rwlock) SDL_TRY_ACQUIRE_SHARED(0, rwlock);
+
+/**
+ * Try to lock a read/write lock _for writing_ without blocking.
+ *
+ * This works just like SDL_LockRWLockForWriting(), but if the rwlock is not available,
+ * this function returns `SDL_RWLOCK_TIMEDOUT` immediately.
+ *
+ * This technique is useful if you need exclusive access to a resource but
+ * don't want to wait for it, and will return to it to try again later.
+ *
+ * It is illegal for the owning thread to lock an already-locked rwlock
+ * for writing (read-only may be locked recursively, writing can not). Doing
+ * so results in undefined behavior.
+ *
+ * It is illegal to request a write lock from a thread that already holds
+ * a read-only lock. Doing so results in undefined behavior. Unlock the
+ * read-only lock before requesting a write lock.
+ *
+ * \param rwlock the rwlock to try to lock
+ * \returns 0, `SDL_RWLOCK_TIMEDOUT`, or -1 on error; call SDL_GetError() for
+ *          more information.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_CreateRWLock
+ * \sa SDL_DestroyRWLock
+ * \sa SDL_TryLockRWLockForWriting
+ * \sa SDL_UnlockRWLock
+ */
+extern DECLSPEC int SDLCALL SDL_TryLockRWLockForWriting(SDL_rwlock * rwlock) SDL_TRY_ACQUIRE(0, rwlock);
+
+/**
+ * Unlock the read/write lock.
+ *
+ * Use this function to unlock the rwlock, whether it was locked for read-only
+ * or write operations.
+ *
+ * It is legal for the owning thread to lock an already-locked read-only lock.
+ * It must unlock it the same number of times before it is actually made
+ * available for other threads in the system (this is known as a "recursive
+ * rwlock").
+ *
+ * It is illegal to unlock a rwlock that has not been locked by the current
+ * thread, and doing so results in undefined behavior.
+ *
+ * \param rwlock the rwlock to unlock.
+ * \returns 0 on success or a negative error code on failure; call
+ *          SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern DECLSPEC int SDLCALL SDL_UnlockRWLock(SDL_rwlock * rwlock) SDL_RELEASE_SHARED(rwlock);
+
+/**
+ * Destroy a read/write lock created with SDL_CreateRWLock().
+ *
+ * This function must be called on any read/write lock that is no longer needed.
+ * Failure to destroy a rwlock will result in a system memory or resource leak. While
+ * it is safe to destroy a rwlock that is _unlocked_, it is not safe to attempt
+ * to destroy a locked rwlock, and may result in undefined behavior depending
+ * on the platform.
+ *
+ * \param rwlock the rwlock to destroy
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_CreateRWLock
+ * \sa SDL_LockRWLockForReading
+ * \sa SDL_LockRWLockForWriting
+ * \sa SDL_TryLockRWLockForReading
+ * \sa SDL_TryLockRWLockForWriting
+ * \sa SDL_UnlockRWLock
+ */
+extern DECLSPEC void SDLCALL SDL_DestroyRWLock(SDL_rwlock * rwlock);
+
+/* @} *//* Read/write lock functions */
+
+
 /**
  *  \name Semaphore functions
  */

+ 1 - 0
include/build_config/SDL_build_config.h.cmake

@@ -348,6 +348,7 @@
 
 /* Enable various threading systems */
 #cmakedefine SDL_THREAD_GENERIC_COND_SUFFIX @SDL_THREAD_GENERIC_COND_SUFFIX@
+#cmakedefine SDL_THREAD_GENERIC_RWLOCK_SUFFIX @SDL_THREAD_GENERIC_RWLOCK_SUFFIX@
 #cmakedefine SDL_THREAD_PTHREAD @SDL_THREAD_PTHREAD@
 #cmakedefine SDL_THREAD_PTHREAD_RECURSIVE_MUTEX @SDL_THREAD_PTHREAD_RECURSIVE_MUTEX@
 #cmakedefine SDL_THREAD_PTHREAD_RECURSIVE_MUTEX_NP @SDL_THREAD_PTHREAD_RECURSIVE_MUTEX_NP@

+ 1 - 0
include/build_config/SDL_build_config_windows.h

@@ -256,6 +256,7 @@ typedef unsigned int uintptr_t;
 
 /* Enable various threading systems */
 #define SDL_THREAD_GENERIC_COND_SUFFIX 1
+#define SDL_THREAD_GENERIC_RWLOCK_SUFFIX 1
 #define SDL_THREAD_WINDOWS  1
 
 /* Enable various timer systems */

+ 1 - 0
include/build_config/SDL_build_config_wingdk.h

@@ -196,6 +196,7 @@
 
 /* Enable various threading systems */
 #define SDL_THREAD_GENERIC_COND_SUFFIX 1
+#define SDL_THREAD_GENERIC_RWLOCK_SUFFIX 1
 #define SDL_THREAD_WINDOWS  1
 
 /* Enable various timer systems */

+ 1 - 0
include/build_config/SDL_build_config_winrt.h

@@ -184,6 +184,7 @@
 /* Enable various threading systems */
 #if (NTDDI_VERSION >= NTDDI_WINBLUE)
 #define SDL_THREAD_GENERIC_COND_SUFFIX 1
+#define SDL_THREAD_GENERIC_RWLOCK_SUFFIX 1
 #define SDL_THREAD_WINDOWS  1
 #else
 /* WinRT on Windows 8.0 and Windows Phone 8.0 don't support CreateThread() */

+ 1 - 0
include/build_config/SDL_build_config_xbox.h

@@ -196,6 +196,7 @@
 
 /* Enable various threading systems */
 #define SDL_THREAD_GENERIC_COND_SUFFIX 1
+#define SDL_THREAD_GENERIC_RWLOCK_SUFFIX 1
 #define SDL_THREAD_WINDOWS  1
 
 /* Enable various timer systems */

+ 7 - 0
src/dynapi/SDL_dynapi.sym

@@ -844,6 +844,13 @@ SDL3_0.0.0 {
     SDL_CreateWindowWithPosition;
     SDL_GetAudioStreamFormat;
     SDL_SetAudioStreamFormat;
+    SDL_CreateRWLock;
+    SDL_LockRWLockForReading;
+    SDL_LockRWLockForWriting;
+    SDL_TryLockRWLockForReading;
+    SDL_TryLockRWLockForWriting;
+    SDL_UnlockRWLock;
+    SDL_DestroyRWLock;
     # extra symbols go here (don't modify this line)
   local: *;
 };

+ 7 - 0
src/dynapi/SDL_dynapi_overrides.h

@@ -870,3 +870,10 @@
 #define SDL_CreateWindowWithPosition SDL_CreateWindowWithPosition_REAL
 #define SDL_GetAudioStreamFormat SDL_GetAudioStreamFormat_REAL
 #define SDL_SetAudioStreamFormat SDL_SetAudioStreamFormat_REAL
+#define SDL_CreateRWLock SDL_CreateRWLock_REAL
+#define SDL_LockRWLockForReading SDL_LockRWLockForReading_REAL
+#define SDL_LockRWLockForWriting SDL_LockRWLockForWriting_REAL
+#define SDL_TryLockRWLockForReading SDL_TryLockRWLockForReading_REAL
+#define SDL_TryLockRWLockForWriting SDL_TryLockRWLockForWriting_REAL
+#define SDL_UnlockRWLock SDL_UnlockRWLock_REAL
+#define SDL_DestroyRWLock SDL_DestroyRWLock_REAL

+ 7 - 0
src/dynapi/SDL_dynapi_procs.h

@@ -915,3 +915,10 @@ SDL_DYNAPI_PROC(SDL_Window*,SDL_GetWindowParent,(SDL_Window *a),(a),return)
 SDL_DYNAPI_PROC(SDL_Window*,SDL_CreateWindowWithPosition,(const char *a, int b, int c, int d, int e, Uint32 f),(a,b,c,d,e,f),return)
 SDL_DYNAPI_PROC(int,SDL_GetAudioStreamFormat,(SDL_AudioStream *a, SDL_AudioFormat *b, int *c, int *d, SDL_AudioFormat *e, int *f, int *g),(a,b,c,d,e,f,g),return)
 SDL_DYNAPI_PROC(int,SDL_SetAudioStreamFormat,(SDL_AudioStream *a, SDL_AudioFormat b, int c, int d, SDL_AudioFormat e, int f, int g),(a,b,c,d,e,f,g),return)
+SDL_DYNAPI_PROC(SDL_rwlock*,SDL_CreateRWLock,(void),(),return)
+SDL_DYNAPI_PROC(int,SDL_LockRWLockForReading,(SDL_rwlock *a),(a),return)
+SDL_DYNAPI_PROC(int,SDL_LockRWLockForWriting,(SDL_rwlock *a),(a),return)
+SDL_DYNAPI_PROC(int,SDL_TryLockRWLockForReading,(SDL_rwlock *a),(a),return)
+SDL_DYNAPI_PROC(int,SDL_TryLockRWLockForWriting,(SDL_rwlock *a),(a),return)
+SDL_DYNAPI_PROC(int,SDL_UnlockRWLock,(SDL_rwlock *a),(a),return)
+SDL_DYNAPI_PROC(void,SDL_DestroyRWLock,(SDL_rwlock *a),(a),)

+ 1 - 0
src/thread/generic/SDL_sysmutex.c

@@ -159,3 +159,4 @@ int SDL_UnlockMutex(SDL_mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doe
     return 0;
 #endif /* SDL_THREADS_DISABLED */
 }
+

+ 185 - 0
src/thread/generic/SDL_sysrwlock.c

@@ -0,0 +1,185 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+#include "SDL_internal.h"
+
+/* An implementation of rwlocks using mutexes, condition variables, and atomics. */
+
+#include "SDL_systhread_c.h"
+
+#include "../generic/SDL_sysrwlock_c.h"
+
+/* If two implementations are to be compiled into SDL (the active one
+ * will be chosen at runtime), the function names need to be
+ * suffixed
+ */
+/* !!! FIXME: this is quite a tapdance with macros and the build system, maybe we can simplify how we do this. --ryan. */
+#ifndef SDL_THREAD_GENERIC_RWLOCK_SUFFIX
+#define SDL_CreateRWLock_generic SDL_CreateRWLock
+#define SDL_DestroyRWLock_generic SDL_DestroyRWLock
+#define SDL_LockRWLockForReading_generic SDL_LockRWLockForReading
+#define SDL_LockRWLockForWriting_generic SDL_LockRWLockForWriting
+#define SDL_TryLockRWLockForReading_generic SDL_TryLockRWLockForReading
+#define SDL_TryLockRWLockForWriting_generic SDL_TryLockRWLockForWriting
+#define SDL_UnlockRWLock_generic SDL_UnlockRWLock
+#endif
+
+struct SDL_rwlock
+{
+    SDL_mutex *lock;
+    SDL_cond *condition;
+    SDL_threadID writer_thread;
+    SDL_AtomicInt reader_count;
+    SDL_AtomicInt writer_count;
+};
+
+SDL_rwlock *SDL_CreateRWLock_generic(void)
+{
+    SDL_rwlock *rwlock = (SDL_rwlock *) SDL_malloc(sizeof (*rwlock));
+
+    if (!rwlock) {
+        SDL_OutOfMemory();
+        return NULL;
+    }
+
+    rwlock->lock = SDL_CreateMutex();
+    if (!rwlock->lock) {
+        SDL_free(rwlock);
+        return NULL;
+    }
+
+    rwlock->condition = SDL_CreateCond();
+    if (!rwlock->condition) {
+        SDL_DestroyMutex(rwlock->lock);
+        SDL_free(rwlock);
+        return NULL;
+    }
+
+    SDL_AtomicSet(&rwlock->reader_count, 0);
+    SDL_AtomicSet(&rwlock->writer_count, 0);
+
+    return rwlock;
+}
+
+void SDL_DestroyRWLock_generic(SDL_rwlock *rwlock)
+{
+    if (rwlock) {
+        SDL_DestroyMutex(rwlock->lock);
+        SDL_DestroyCond(rwlock->condition);
+        SDL_free(rwlock);
+    }
+}
+
+int SDL_LockRWLockForReading_generic(SDL_rwlock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */
+{
+    if (!rwlock) {
+        return SDL_InvalidParamError("rwlock");
+    } else if (SDL_LockMutex(rwlock->lock) == -1) {
+        return -1;
+    }
+
+    SDL_assert(SDL_AtomicGet(&rwlock->writer_count) == 0);  /* shouldn't be able to grab lock if there's a writer! */
+
+    SDL_AtomicAdd(&rwlock->reader_count, 1);
+    SDL_UnlockMutex(rwlock->lock);   /* other readers can attempt to share the lock. */
+    return 0;
+}
+
+int SDL_LockRWLockForWriting_generic(SDL_rwlock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */
+{
+    if (!rwlock) {
+        return SDL_InvalidParamError("rwlock");
+    } else if (SDL_LockMutex(rwlock->lock) == -1) {
+        return -1;
+    }
+
+    while (SDL_AtomicGet(&rwlock->reader_count) > 0) {  /* while something is holding the shared lock, keep waiting. */
+        SDL_CondWait(rwlock->condition, rwlock->lock);  /* release the lock and wait for readers holding the shared lock to release it, regrab the lock. */
+    }
+
+    /* we hold the lock! */
+    SDL_AtomicAdd(&rwlock->writer_count, 1);  /* we let these be recursive, but the API doesn't require this. It _does_ trust you unlock correctly! */
+
+    return 0;
+}
+
+int SDL_TryLockRWLockForReading_generic(SDL_rwlock *rwlock)
+{
+    int rc;
+
+    if (!rwlock) {
+        return SDL_InvalidParamError("rwlock");
+    }
+
+    rc = SDL_TryLockMutex(rwlock->lock);
+    if (rc != 0) {
+        /* !!! FIXME: there is a small window where a reader has to lock the mutex, and if we hit that, we will return SDL_RWLOCK_TIMEDOUT even though we could have shared the lock. */
+        return rc;
+    }
+
+    SDL_assert(SDL_AtomicGet(&rwlock->writer_count) == 0);  /* shouldn't be able to grab lock if there's a writer! */
+
+    SDL_AtomicAdd(&rwlock->reader_count, 1);
+    SDL_UnlockMutex(rwlock->lock);   /* other readers can attempt to share the lock. */
+    return 0;
+}
+
+int SDL_TryLockRWLockForWriting_generic(SDL_rwlock *rwlock)
+{
+    int rc;
+
+    if (!rwlock) {
+        return SDL_InvalidParamError("rwlock");
+    } else if ((rc = SDL_TryLockMutex(rwlock->lock)) != 0) {
+        return rc;
+    }
+
+    if (SDL_AtomicGet(&rwlock->reader_count) > 0) {  /* a reader is using the shared lock, treat it as unavailable. */
+        SDL_UnlockMutex(rwlock->lock);
+        return SDL_RWLOCK_TIMEDOUT;
+    }
+
+    /* we hold the lock! */
+    SDL_AtomicAdd(&rwlock->writer_count, 1);  /* we let these be recursive, but the API doesn't require this. It _does_ trust you unlock correctly! */
+
+    return 0;
+}
+
+int SDL_UnlockRWLock_generic(SDL_rwlock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */
+{
+    if (!rwlock) {
+        return SDL_InvalidParamError("rwlock");
+    }
+
+    SDL_LockMutex(rwlock->lock);  /* recursive lock for writers, readers grab lock to make sure things are sane. */
+
+    if (SDL_AtomicGet(&rwlock->reader_count) > 0) {  /* we're a reader */
+        SDL_AtomicAdd(&rwlock->reader_count, -1);
+        SDL_CondBroadcast(rwlock->condition);  /* alert any pending writers to attempt to try to grab the lock again. */
+    } else if (SDL_AtomicGet(&rwlock->writer_count) > 0) {  /* we're a writer */
+        SDL_AtomicAdd(&rwlock->writer_count, -1);
+        SDL_UnlockMutex(rwlock->lock);  /* recursive unlock. */
+    }
+
+    SDL_UnlockMutex(rwlock->lock);
+
+    return 0;
+}
+

+ 38 - 0
src/thread/generic/SDL_sysrwlock_c.h

@@ -0,0 +1,38 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+#include "SDL_internal.h"
+
+#ifndef SDL_sysrwlock_c_h_
+#define SDL_sysrwlock_c_h_
+
+#ifdef SDL_THREAD_GENERIC_RWLOCK_SUFFIX
+
+SDL_rwlock *SDL_CreateRWLock_generic(void);
+void SDL_DestroyRWLock_generic(SDL_rwlock *rwlock);
+int SDL_LockRWLockForReading_generic(SDL_rwlock *rwlock);
+int SDL_LockRWLockForWriting_generic(SDL_rwlock *rwlock);
+int SDL_TryLockRWLockForReading_generic(SDL_rwlock *rwlock);
+int SDL_TryLockRWLockForWriting_generic(SDL_rwlock *rwlock);
+int SDL_UnlockRWLock_generic(SDL_rwlock *rwlock);
+
+#endif /* SDL_THREAD_GENERIC_RWLOCK_SUFFIX */
+
+#endif /* SDL_sysrwlock_c_h_ */

+ 1 - 0
src/thread/ngage/SDL_sysmutex.cpp

@@ -109,3 +109,4 @@ int SDL_UnlockMutex(SDL_mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doe
 
     return 0;
 }
+

+ 1 - 0
src/thread/ngage/SDL_syssem.cpp

@@ -24,6 +24,7 @@
 
 #include <e32std.h>
 
+/* !!! FIXME: Should this be SDL_MUTEX_TIMEDOUT? */
 #define SDL_MUTEX_TIMEOUT -2
 
 struct SDL_semaphore

+ 2 - 14
src/thread/pthread/SDL_sysmutex.c

@@ -23,20 +23,7 @@
 #include <errno.h>
 #include <pthread.h>
 
-
-#if !(defined(SDL_THREAD_PTHREAD_RECURSIVE_MUTEX) || \
-    defined(SDL_THREAD_PTHREAD_RECURSIVE_MUTEX_NP))
-#define FAKE_RECURSIVE_MUTEX
-#endif
-
-struct SDL_mutex
-{
-    pthread_mutex_t id;
-#ifdef FAKE_RECURSIVE_MUTEX
-    int recursive;
-    pthread_t owner;
-#endif
-};
+#include "SDL_sysmutex_c.h"
 
 SDL_mutex *
 SDL_CreateMutex(void)
@@ -186,3 +173,4 @@ int SDL_UnlockMutex(SDL_mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doe
 
     return 0;
 }
+

+ 9 - 0
src/thread/pthread/SDL_sysmutex_c.h

@@ -23,9 +23,18 @@
 #ifndef SDL_mutex_c_h_
 #define SDL_mutex_c_h_
 
+#if !(defined(SDL_THREAD_PTHREAD_RECURSIVE_MUTEX) || \
+    defined(SDL_THREAD_PTHREAD_RECURSIVE_MUTEX_NP))
+#define FAKE_RECURSIVE_MUTEX
+#endif
+
 struct SDL_mutex
 {
     pthread_mutex_t id;
+#ifdef FAKE_RECURSIVE_MUTEX
+    int recursive;
+    pthread_t owner;
+#endif
 };
 
 #endif /* SDL_mutex_c_h_ */

+ 127 - 0
src/thread/pthread/SDL_sysrwlock.c

@@ -0,0 +1,127 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+#include "SDL_internal.h"
+
+#include <errno.h>
+#include <pthread.h>
+
+struct SDL_rwlock
+{
+    pthread_rwlock_t id;
+};
+
+
+SDL_rwlock *
+SDL_CreateRWLock(void)
+{
+    SDL_rwlock *rwlock;
+
+    /* Allocate the structure */
+    rwlock = (SDL_rwlock *)SDL_calloc(1, sizeof(*rwlock));
+    if (rwlock) {
+        if (pthread_rwlock_init(&rwlock->id, NULL) != 0) {
+            SDL_SetError("pthread_rwlock_init() failed");
+            SDL_free(rwlock);
+            rwlock = NULL;
+        }
+    } else {
+        SDL_OutOfMemory();
+    }
+    return rwlock;
+}
+
+void SDL_DestroyRWLock(SDL_rwlock *rwlock)
+{
+    if (rwlock) {
+        pthread_rwlock_destroy(&rwlock->id);
+        SDL_free(rwlock);
+    }
+}
+
+int SDL_LockRWLockForReading(SDL_rwlock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */
+{
+    if (rwlock == NULL) {
+        return SDL_InvalidParamError("rwlock");
+    } else if (pthread_rwlock_rdlock(&rwlock->id) != 0) {
+        return SDL_SetError("pthread_rwlock_rdlock() failed");
+    }
+    return 0;
+}
+
+int SDL_LockRWLockForWriting(SDL_rwlock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */
+{
+    if (rwlock == NULL) {
+        return SDL_InvalidParamError("rwlock");
+    } else if (pthread_rwlock_wrlock(&rwlock->id) != 0) {
+        return SDL_SetError("pthread_rwlock_wrlock() failed");
+    }
+    return 0;
+}
+
+int SDL_TryLockRWLockForReading(SDL_rwlock *rwlock)
+{
+    int retval = 0;
+
+    if (rwlock == NULL) {
+        retval = SDL_InvalidParamError("rwlock");
+    } else {
+        const int result = pthread_rwlock_tryrdlock(&rwlock->id);
+        if (result != 0) {
+            if (result == EBUSY) {
+                retval = SDL_RWLOCK_TIMEDOUT;
+            } else {
+                retval = SDL_SetError("pthread_rwlock_tryrdlock() failed");
+            }
+        }
+    }
+    return retval;
+}
+
+int SDL_TryLockRWLockForWriting(SDL_rwlock *rwlock)
+{
+    int retval = 0;
+
+    if (rwlock == NULL) {
+        retval = SDL_InvalidParamError("rwlock");
+    } else {
+        const int result = pthread_rwlock_trywrlock(&rwlock->id);
+        if (result != 0) {
+            if (result == EBUSY) {
+                retval = SDL_RWLOCK_TIMEDOUT;
+            } else {
+                retval = SDL_SetError("pthread_rwlock_tryrdlock() failed");
+            }
+        }
+    }
+
+    return retval;
+}
+
+int SDL_UnlockRWLock(SDL_rwlock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */
+{
+    if (rwlock == NULL) {
+        return SDL_InvalidParamError("rwlock");
+    } else if (pthread_rwlock_unlock(&rwlock->id) != 0) {
+        return SDL_SetError("pthread_rwlock_unlock() failed");
+    }
+    return 0;
+}
+

+ 1 - 0
src/thread/stdcpp/SDL_sysmutex.cpp

@@ -97,3 +97,4 @@ SDL_UnlockMutex(SDL_mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't
     mutex->cpp_mutex.unlock();
     return 0;
 }
+

+ 1 - 0
src/thread/stdcpp/SDL_sysmutex_c.h

@@ -26,3 +26,4 @@ struct SDL_mutex
 {
     std::recursive_mutex cpp_mutex;
 };
+

+ 130 - 0
src/thread/stdcpp/SDL_sysrwlock.cpp

@@ -0,0 +1,130 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+#include "SDL_internal.h"
+
+#include <shared_mutex>
+#include <system_error>
+#include <Windows.h>
+
+struct SDL_rwlock
+{
+    std::shared_mutex cpp_mutex;
+    SDL_threadID write_owner;
+};
+
+/* Create a rwlock */
+extern "C" SDL_rwlock *SDL_CreateRWLock(void)
+{
+    /* Allocate and initialize the rwlock */
+    try {
+        SDL_rwlock *rwlock = new SDL_rwlock;
+        return rwlock;
+    } catch (std::system_error &ex) {
+        SDL_SetError("unable to create a C++ rwlock: code=%d; %s", ex.code(), ex.what());
+        return NULL;
+    } catch (std::bad_alloc &) {
+        SDL_OutOfMemory();
+        return NULL;
+    }
+}
+
+/* Free the rwlock */
+extern "C" void SDL_DestroyRWLock(SDL_rwlock *rwlock)
+{
+    if (rwlock != NULL) {
+        delete rwlock;
+    }
+}
+
+/* Lock the rwlock */
+extern "C" int SDL_LockRWLockForReading(SDL_rwlock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */
+{
+    if (!rwlock) {
+        return SDL_InvalidParamError("rwlock");
+    }
+
+    try {
+        rwlock->cpp_mutex.lock_shared();
+        return 0;
+    } catch (std::system_error &ex) {
+        return SDL_SetError("unable to lock a C++ rwlock: code=%d; %s", ex.code(), ex.what());
+    }
+}
+
+/* Lock the rwlock for writing */
+extern "C" int SDL_LockRWLockForWriting(SDL_rwlock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */
+{
+    if (!rwlock) {
+        return SDL_InvalidParamError("rwlock");
+    }
+
+    try {
+        rwlock->cpp_mutex.lock();
+        rwlock->write_owner = SDL_ThreadID();
+        return 0;
+    } catch (std::system_error &ex) {
+        return SDL_SetError("unable to lock a C++ rwlock: code=%d; %s", ex.code(), ex.what());
+    }
+}
+
+/* TryLock the rwlock for reading */
+int SDL_TryLockRWLockForReading(SDL_rwlock *rwlock)
+{
+    int retval = 0;
+
+    if (!rwlock) {
+        retval = SDL_InvalidParamError("rwlock");
+    } else if (rwlock->cpp_mutex.try_lock_shared() == false) {
+        retval = SDL_RWLOCK_TIMEDOUT;
+    }
+    return retval;
+}
+
+/* TryLock the rwlock for writing */
+int SDL_TryLockRWLockForWriting(SDL_rwlock *rwlock)
+{
+    int retval = 0;
+
+    if (!rwlock) {
+        retval = SDL_InvalidParamError("rwlock");
+    } else if (rwlock->cpp_mutex.try_lock() == false) {
+        retval = SDL_RWLOCK_TIMEDOUT;
+    } else {
+        rwlock->write_owner = SDL_ThreadID();
+    }
+    return retval;
+}
+
+/* Unlock the rwlock */
+extern "C" int
+SDL_UnlockRWLock(SDL_rwlock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */
+{
+    if (!rwlock) {
+        return SDL_InvalidParamError("rwlock");
+    } else if (rwlock->write_owner == SDL_ThreadID()) {
+        rwlock->write_owner = 0;
+        rwlock->cpp_mutex.unlock();
+    } else {
+        rwlock->cpp_mutex.unlock_shared();
+    }
+    return 0;
+}
+

+ 258 - 0
src/thread/windows/SDL_sysrwlock_srw.c

@@ -0,0 +1,258 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+#include "SDL_internal.h"
+
+/**
+ * Implementation based on Slim Reader/Writer (SRW) Locks for Win 7 and newer.
+ */
+
+/* This header makes sure SRWLOCK is actually declared, even on ancient WinSDKs. */
+#include "SDL_sysmutex_c.h"
+
+typedef VOID(WINAPI *pfnInitializeSRWLock)(PSRWLOCK);
+typedef VOID(WINAPI *pfnReleaseSRWLockShared)(PSRWLOCK);
+typedef VOID(WINAPI *pfnAcquireSRWLockShared)(PSRWLOCK);
+typedef BOOLEAN(WINAPI *pfnTryAcquireSRWLockShared)(PSRWLOCK);
+typedef VOID(WINAPI *pfnReleaseSRWLockExclusive)(PSRWLOCK);
+typedef VOID(WINAPI *pfnAcquireSRWLockExclusive)(PSRWLOCK);
+typedef BOOLEAN(WINAPI *pfnTryAcquireSRWLockExclusive)(PSRWLOCK);
+
+#ifdef __WINRT__
+/* Functions are guaranteed to be available */
+#define pTryAcquireSRWLockExclusive TryAcquireSRWLockExclusive
+#define pInitializeSRWLock InitializeSRWLock
+#define pReleaseSRWLockShared ReleaseSRWLockShared
+#define pAcquireSRWLockShared AcquireSRWLockShared
+#define pTryAcquireSRWLockShared TryAcquireSRWLockShared
+#define pReleaseSRWLockExclusive ReleaseSRWLockExclusive
+#define pAcquireSRWLockExclusive AcquireSRWLockExclusive
+#define pTryAcquireSRWLockExclusive TryAcquireSRWLockExclusive
+#else
+static pfnInitializeSRWLock pInitializeSRWLock = NULL;
+static pfnReleaseSRWLockShared pReleaseSRWLockShared = NULL;
+static pfnAcquireSRWLockShared pAcquireSRWLockShared = NULL;
+static pfnTryAcquireSRWLockShared  pTryAcquireSRWLockShared = NULL;
+static pfnReleaseSRWLockExclusive pReleaseSRWLockExclusive = NULL;
+static pfnAcquireSRWLockExclusive pAcquireSRWLockExclusive = NULL;
+static pfnTryAcquireSRWLockExclusive pTryAcquireSRWLockExclusive = NULL;
+#endif
+
+typedef SDL_rwlock *(*pfnSDL_CreateRWLock)(void);
+typedef void (*pfnSDL_DestroyRWLock)(SDL_rwlock *);
+typedef int (*pfnSDL_LockRWLockForReading)(SDL_rwlock *);
+typedef int (*pfnSDL_LockRWLockForWriting)(SDL_rwlock *);
+typedef int (*pfnSDL_TryLockRWLockForReading)(SDL_rwlock *);
+typedef int (*pfnSDL_TryLockRWLockForWriting)(SDL_rwlock *);
+typedef int (*pfnSDL_UnlockRWLock)(SDL_rwlock *);
+
+typedef struct SDL_rwlock_impl_t
+{
+    pfnSDL_CreateRWLock Create;
+    pfnSDL_DestroyRWLock Destroy;
+    pfnSDL_LockRWLockForReading LockForReading;
+    pfnSDL_LockRWLockForWriting LockForWriting;
+    pfnSDL_TryLockRWLockForReading TryLockForReading;
+    pfnSDL_TryLockRWLockForWriting TryLockForWriting;
+    pfnSDL_UnlockRWLock Unlock;
+} SDL_rwlock_impl_t;
+
+/* Implementation will be chosen at runtime based on available Kernel features */
+static SDL_rwlock_impl_t SDL_rwlock_impl_active = { 0 };
+
+/* rwlock implementation using Win7+ slim read/write locks (SRWLOCK) */
+
+typedef struct SDL_rwlock_srw
+{
+    SRWLOCK srw;
+    SDL_threadID write_owner;
+} SDL_rwlock_srw;
+
+static SDL_rwlock *SDL_CreateRWLock_srw(void)
+{
+    SDL_rwlock_srw *rwlock = (SDL_rwlock_srw *)SDL_calloc(1, sizeof(*rwlock));
+    if (rwlock == NULL) {
+        SDL_OutOfMemory();
+    }
+    pInitializeSRWLock(&rwlock->srw);
+    return (SDL_rwlock *)rwlock;
+}
+
+static void SDL_DestroyRWLock_srw(SDL_rwlock *_rwlock)
+{
+    SDL_rwlock_srw *rwlock = (SDL_rwlock_srw *) _rwlock;
+    if (rwlock) {
+        /* There are no kernel allocated resources */
+        SDL_free(rwlock);
+    }
+}
+
+static int SDL_LockRWLockForReading_srw(SDL_rwlock *_rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */
+{
+    SDL_rwlock_srw *rwlock = (SDL_rwlock_srw *) _rwlock;
+    if (rwlock == NULL) {
+        return SDL_InvalidParamError("rwlock");
+    }
+    pAcquireSRWLockShared(&rwlock->srw);
+    return 0;
+}
+
+static int SDL_LockRWLockForWriting_srw(SDL_rwlock *_rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */
+{
+    SDL_rwlock_srw *rwlock = (SDL_rwlock_srw *) _rwlock;
+    if (rwlock == NULL) {
+        return SDL_InvalidParamError("rwlock");
+    }
+    pAcquireSRWLockExclusive(&rwlock->srw);
+    rwlock->write_owner = SDL_ThreadID();
+    return 0;
+}
+
+static int SDL_TryLockRWLockForReading_srw(SDL_rwlock *_rwlock)
+{
+    SDL_rwlock_srw *rwlock = (SDL_rwlock_srw *) _rwlock;
+    int retval = 0;
+
+    if (rwlock == NULL) {
+        retval = SDL_InvalidParamError("rwlock");
+    } else {
+        retval = pTryAcquireSRWLockShared(&rwlock->srw) ? 0 : SDL_RWLOCK_TIMEDOUT;
+    }
+    return retval;
+}
+
+static int SDL_TryLockRWLockForWriting_srw(SDL_rwlock *_rwlock)
+{
+    SDL_rwlock_srw *rwlock = (SDL_rwlock_srw *) _rwlock;
+    int retval = 0;
+
+    if (rwlock == NULL) {
+        retval = SDL_InvalidParamError("rwlock");
+    } else {
+        retval = pTryAcquireSRWLockExclusive(&rwlock->srw) ? 0 : SDL_RWLOCK_TIMEDOUT;
+    }
+    return retval;
+}
+
+static int SDL_UnlockRWLock_srw(SDL_rwlock *_rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */
+{
+    SDL_rwlock_srw *rwlock = (SDL_rwlock_srw *) _rwlock;
+    if (rwlock == NULL) {
+        return SDL_InvalidParamError("rwlock");
+    } else if (rwlock->write_owner == SDL_ThreadID()) {
+        rwlock->write_owner = 0;
+        pReleaseSRWLockExclusive(&rwlock->srw);
+    } else {
+        pReleaseSRWLockShared(&rwlock->srw);
+    }
+    return 0;
+}
+
+static const SDL_rwlock_impl_t SDL_rwlock_impl_srw = {
+    &SDL_CreateRWLock_srw,
+    &SDL_DestroyRWLock_srw,
+    &SDL_LockRWLockForReading_srw,
+    &SDL_LockRWLockForWriting_srw,
+    &SDL_TryLockRWLockForReading_srw,
+    &SDL_TryLockRWLockForWriting_srw,
+    &SDL_UnlockRWLock_srw
+};
+
+#ifndef __WINRT__
+
+#include "../generic/SDL_sysrwlock_c.h"
+
+/* Generic rwlock implementation using SDL_mutex, SDL_cond, and SDL_AtomicInt */
+static const SDL_rwlock_impl_t SDL_rwlock_impl_generic = {
+    &SDL_CreateRWLock_generic,
+    &SDL_DestroyRWLock_generic,
+    &SDL_LockRWLockForReading_generic,
+    &SDL_LockRWLockForWriting_generic,
+    &SDL_TryLockRWLockForReading_generic,
+    &SDL_TryLockRWLockForWriting_generic,
+    &SDL_UnlockRWLock_generic
+};
+#endif
+
+SDL_rwlock *SDL_CreateRWLock(void)
+{
+    if (SDL_rwlock_impl_active.Create == NULL) {
+        const SDL_rwlock_impl_t *impl;
+
+#ifdef __WINRT__
+        /* Link statically on this platform */
+        impl = &SDL_rwlock_impl_srw;
+#else
+        /* Default to generic implementation, works with all mutex implementations */
+        impl = &SDL_rwlock_impl_generic;
+        {
+            HMODULE kernel32 = GetModuleHandle(TEXT("kernel32.dll"));
+            if (kernel32) {
+                SDL_bool okay = SDL_TRUE;
+                #define LOOKUP_SRW_SYM(sym) if (okay) { if ((p##sym = (pfn##sym)GetProcAddress(kernel32, #sym)) == NULL) { okay = SDL_FALSE; } }
+                LOOKUP_SRW_SYM(InitializeSRWLock);
+                LOOKUP_SRW_SYM(ReleaseSRWLockShared);
+                LOOKUP_SRW_SYM(AcquireSRWLockShared);
+                LOOKUP_SRW_SYM(TryAcquireSRWLockShared);
+                LOOKUP_SRW_SYM(ReleaseSRWLockExclusive);
+                LOOKUP_SRW_SYM(AcquireSRWLockExclusive);
+                LOOKUP_SRW_SYM(TryAcquireSRWLockExclusive);
+                #undef LOOKUP_SRW_SYM
+                if (okay) {
+                    impl = &SDL_rwlock_impl_srw;  /* Use the Windows provided API instead of generic fallback */
+                }
+            }
+        }
+#endif
+
+        SDL_copyp(&SDL_rwlock_impl_active, impl);
+    }
+    return SDL_rwlock_impl_active.Create();
+}
+
+void SDL_DestroyRWLock(SDL_rwlock *rwlock)
+{
+    SDL_rwlock_impl_active.Destroy(rwlock);
+}
+
+int SDL_LockRWLockForReading(SDL_rwlock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */
+{
+    return SDL_rwlock_impl_active.LockForReading(rwlock);
+}
+
+int SDL_LockRWLockForWriting(SDL_rwlock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */
+{
+    return SDL_rwlock_impl_active.LockForWriting(rwlock);
+}
+
+int SDL_TryLockRWLockForReading(SDL_rwlock *rwlock)
+{
+    return SDL_rwlock_impl_active.TryLockForReading(rwlock);
+}
+
+int SDL_TryLockRWLockForWriting(SDL_rwlock *rwlock)
+{
+    return SDL_rwlock_impl_active.TryLockForWriting(rwlock);
+}
+
+int SDL_UnlockRWLock(SDL_rwlock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */
+{
+    return SDL_rwlock_impl_active.Unlock(rwlock);
+}

+ 1 - 0
test/CMakeLists.txt

@@ -225,6 +225,7 @@ add_sdl_test_executable(testkeys SOURCES testkeys.c)
 add_sdl_test_executable(testloadso SOURCES testloadso.c)
 add_sdl_test_executable(testlocale NONINTERACTIVE SOURCES testlocale.c)
 add_sdl_test_executable(testlock SOURCES testlock.c)
+add_sdl_test_executable(testrwlock SOURCES testrwlock.c)
 add_sdl_test_executable(testmouse SOURCES testmouse.c)
 
 add_sdl_test_executable(testoverlay NEEDS_RESOURCES TESTUTILS SOURCES testoverlay.c)

+ 179 - 0
test/testrwlock.c

@@ -0,0 +1,179 @@
+/*
+  Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely.
+*/
+
+/* Test the thread and rwlock locking functions
+   Also exercises the system's signal/thread interaction
+*/
+
+#include <SDL3/SDL.h>
+#include <SDL3/SDL_main.h>
+#include <SDL3/SDL_test.h>
+
+static SDL_rwlock *rwlock = NULL;
+static SDL_threadID mainthread;
+static SDL_AtomicInt doterminate;
+static int nb_threads = 6;
+static SDL_Thread **threads;
+static int worktime = 1000;
+static int writerworktime = 100;
+static int timeout = 10000;
+static SDLTest_CommonState *state;
+
+static void DoWork(const int workticks)  /* "Work" */
+{
+    const SDL_threadID tid = SDL_ThreadID();
+    const SDL_bool is_reader = tid != mainthread;
+    const char *typestr = is_reader ? "Reader" : "Writer";
+    int rc;
+
+    SDL_Log("%s Thread %lu: ready to work\n", typestr, (unsigned long) tid);
+    rc = is_reader ? SDL_LockRWLockForReading(rwlock) : SDL_LockRWLockForWriting(rwlock);
+    if (rc < 0) {
+        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s Thread %lu: Couldn't lock rwlock: %s", typestr, (unsigned long) tid, SDL_GetError());
+    } else {
+        SDL_Log("%s Thread %lu: start work!\n", typestr, (unsigned long) tid);
+        SDL_Delay(workticks);
+        SDL_Log("%s Thread %lu: work done!\n", typestr, (unsigned long) tid);
+        if (SDL_UnlockRWLock(rwlock) < 0) {
+            SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s Thread %lu: Couldn't unlock rwlock: %s", typestr, (unsigned long) tid, SDL_GetError());
+        }
+        /* If this sleep isn't done, then threads may starve */
+        SDL_Delay(10);
+    }
+}
+
+static int SDLCALL
+ReaderRun(void *data)
+{
+    SDL_Log("Reader Thread %lu: starting up", SDL_ThreadID());
+    while (!SDL_AtomicGet(&doterminate)) {
+        DoWork(worktime);
+    }
+    SDL_Log("Reader Thread %lu: exiting!\n", SDL_ThreadID());
+    return 0;
+}
+
+int main(int argc, char *argv[])
+{
+    int i;
+
+    /* Initialize test framework */
+    state = SDLTest_CommonCreateState(argv, 0);
+    if (state == NULL) {
+        return 1;
+    }
+
+    SDL_AtomicSet(&doterminate, 0);
+
+    /* Enable standard application logging */
+    SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO);
+    /* Parse commandline */
+    for (i = 1; i < argc;) {
+        int consumed;
+
+        consumed = SDLTest_CommonArg(state, i);
+        if (!consumed) {
+            if (SDL_strcmp(argv[i], "--nbthreads") == 0) {
+                if (argv[i + 1]) {
+                    char *endptr;
+                    nb_threads = SDL_strtol(argv[i + 1], &endptr, 0);
+                    if (endptr != argv[i + 1] && *endptr == '\0' && nb_threads > 0) {
+                        consumed = 2;
+                    }
+                }
+            } else if (SDL_strcmp(argv[i], "--worktime") == 0) {
+                if (argv[i + 1]) {
+                    char *endptr;
+                    worktime = SDL_strtol(argv[i + 1], &endptr, 0);
+                    if (endptr != argv[i + 1] && *endptr == '\0' && worktime > 0) {
+                        consumed = 2;
+                    }
+                }
+            } else if (SDL_strcmp(argv[i], "--writerworktime") == 0) {
+                if (argv[i + 1]) {
+                    char *endptr;
+                    writerworktime = SDL_strtol(argv[i + 1], &endptr, 0);
+                    if (endptr != argv[i + 1] && *endptr == '\0' && writerworktime > 0) {
+                        consumed = 2;
+                    }
+                }
+            } else if (SDL_strcmp(argv[i], "--timeout") == 0) {
+                if (argv[i + 1]) {
+                    char *endptr;
+                    timeout = (Uint64) SDL_strtol(argv[i + 1], &endptr, 0);
+                    if (endptr != argv[i + 1] && *endptr == '\0' && timeout > 0) {
+                        consumed = 2;
+                    }
+                }
+            }
+        }
+        if (consumed <= 0) {
+            static const char *options[] = {
+                "[--nbthreads NB]",
+                "[--worktime ms]",
+                "[--writerworktime ms]",
+                "[--timeout ms]",
+                NULL,
+            };
+            SDLTest_CommonLogUsage(state, argv[0], options);
+            return 1;
+        }
+
+        i += consumed;
+    }
+
+    threads = SDL_malloc(nb_threads * sizeof(SDL_Thread*));
+
+    /* Load the SDL library */
+    if (SDL_Init(0) < 0) {
+        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s\n", SDL_GetError());
+        return 1;
+    }
+
+    SDL_AtomicSet(&doterminate, 0);
+
+    rwlock = SDL_CreateRWLock();
+    if (rwlock == NULL) {
+        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create rwlock: %s\n", SDL_GetError());
+        SDL_Quit();
+        SDLTest_CommonDestroyState(state);
+        return 1;
+    }
+
+    mainthread = SDL_ThreadID();
+    SDL_Log("Writer thread: %lu\n", mainthread);
+    for (i = 0; i < nb_threads; ++i) {
+        char name[64];
+        (void)SDL_snprintf(name, sizeof(name), "Reader%d", i);
+        threads[i] = SDL_CreateThread(ReaderRun, name, NULL);
+        if (threads[i] == NULL) {
+            SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create reader thread! %s\n", SDL_GetError());
+        }
+    }
+
+    while (!SDL_AtomicGet(&doterminate) && (SDL_GetTicks() < ((Uint64) timeout))) {
+        DoWork(writerworktime);
+    }
+
+    SDL_AtomicSet(&doterminate, 1);
+    SDL_Log("Waiting on reader threads to terminate...");
+    for (i = 0; i < nb_threads; ++i) {
+        SDL_WaitThread(threads[i], NULL);
+    }
+
+    SDL_Log("Reader threads have terminated, quitting!");
+    SDL_DestroyRWLock(rwlock);
+    SDLTest_CommonDestroyState(state);
+    SDL_Quit();
+
+    return 0;
+}