SDL: rwlock: Added SDL_rwlock API for shared locks.

From e474047ff8bf076c86699cc9bf33e9320f2eb05a Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Mon, 24 Apr 2023 01:07:59 -0400
Subject: [PATCH] rwlock: Added SDL_rwlock API for shared locks.

---
 CMakeLists.txt                                |  10 +-
 VisualC-GDK/SDL/SDL.vcxproj.filters           |   6 +
 VisualC-WinRT/SDL-UWP.vcxproj                 |  18 ++
 VisualC-WinRT/SDL-UWP.vcxproj.filters         |   3 +
 VisualC/SDL/SDL.vcxproj                       |   3 +
 VisualC/SDL/SDL.vcxproj.filters               |   3 +
 Xcode/SDL/SDL.xcodeproj/project.pbxproj       |  20 ++
 cmake/sdlchecks.cmake                         |   1 +
 include/SDL3/SDL_mutex.h                      | 226 ++++++++++++++-
 include/build_config/SDL_build_config.h.cmake |   1 +
 .../build_config/SDL_build_config_windows.h   |   1 +
 .../build_config/SDL_build_config_wingdk.h    |   1 +
 include/build_config/SDL_build_config_winrt.h |   1 +
 include/build_config/SDL_build_config_xbox.h  |   1 +
 src/dynapi/SDL_dynapi.sym                     |   7 +
 src/dynapi/SDL_dynapi_overrides.h             |   7 +
 src/dynapi/SDL_dynapi_procs.h                 |   7 +
 src/thread/generic/SDL_sysmutex.c             |   1 +
 src/thread/generic/SDL_sysrwlock.c            | 185 +++++++++++++
 src/thread/generic/SDL_sysrwlock_c.h          |  38 +++
 src/thread/ngage/SDL_sysmutex.cpp             |   1 +
 src/thread/ngage/SDL_syssem.cpp               |   1 +
 src/thread/pthread/SDL_sysmutex.c             |  16 +-
 src/thread/pthread/SDL_sysmutex_c.h           |   9 +
 src/thread/pthread/SDL_sysrwlock.c            | 127 +++++++++
 src/thread/stdcpp/SDL_sysmutex.cpp            |   1 +
 src/thread/stdcpp/SDL_sysmutex_c.h            |   1 +
 src/thread/stdcpp/SDL_sysrwlock.cpp           | 130 +++++++++
 src/thread/windows/SDL_sysrwlock_srw.c        | 258 ++++++++++++++++++
 test/CMakeLists.txt                           |   1 +
 test/testrwlock.c                             | 179 ++++++++++++
 31 files changed, 1244 insertions(+), 20 deletions(-)
 create mode 100644 src/thread/generic/SDL_sysrwlock.c
 create mode 100644 src/thread/generic/SDL_sysrwlock_c.h
 create mode 100644 src/thread/pthread/SDL_sysrwlock.c
 create mode 100644 src/thread/stdcpp/SDL_sysrwlock.cpp
 create mode 100644 src/thread/windows/SDL_sysrwlock_srw.c
 create mode 100644 test/testrwlock.c

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 407f7e46ab15..003b7bce6d5f 100644
--- a/CMakeLists.txt
+++ b/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()
 
diff --git a/VisualC-GDK/SDL/SDL.vcxproj.filters b/VisualC-GDK/SDL/SDL.vcxproj.filters
index 9e7e0b72faba..71ab29e44dfa 100644
--- a/VisualC-GDK/SDL/SDL.vcxproj.filters
+++ b/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>
diff --git a/VisualC-WinRT/SDL-UWP.vcxproj b/VisualC-WinRT/SDL-UWP.vcxproj
index 8fde0dadf00a..3a503634fb13 100644
--- a/VisualC-WinRT/SDL-UWP.vcxproj
+++ b/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>
diff --git a/VisualC-WinRT/SDL-UWP.vcxproj.filters b/VisualC-WinRT/SDL-UWP.vcxproj.filters
index e51068b41304..26bb708b761c 100644
--- a/VisualC-WinRT/SDL-UWP.vcxproj.filters
+++ b/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>
diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj
index 55c4c72cd36f..a111aaeb3359 100644
--- a/VisualC/SDL/SDL.vcxproj
+++ b/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" />
diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters
index 3a2919fcc41a..cb03064bfe91 100644
--- a/VisualC/SDL/SDL.vcxproj.filters
+++ b/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>
diff --git a/Xcode/SDL/SDL.xcodeproj/project.pbxproj b/Xcode/SDL/SDL.xcodeproj/project.pbxproj
index f587aa6d7b34..862ee33949e2 100644
--- a/Xcode/SDL/SDL.xcodeproj/project.pbxproj
+++ b/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 */,
diff --git a/cmake/sdlchecks.cmake b/cmake/sdlchecks.cmake
index bab63865874b..0aec8607aec6 100644
--- a/cmake/sdlchecks.cmake
+++ b/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)
diff --git a/include/SDL3/SDL_mutex.h b/include/SDL3/SDL_mutex.h
index b242dd74e364..8f6131020c0c 100644
--- a/include/SDL3/SDL_mutex.h
+++ b/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
  */
diff --git a/include/build_config/SDL_build_config.h.cmake b/include/build_config/SDL_build_config.h.cmake
index 29fdae642eec..13005525cb04 100644
--- a/include/build_config/SDL_build_config.h.cmake
+++ b/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@
diff --git a/include/build_config/SDL_build_config_windows.h b/include/build_config/SDL_build_config_windows.h
index 18cf96e917ca..8a29bab171bb 100644
--- a/include/build_config/SDL_build_config_windows.h
+++ b/include/build_config/SDL_build_config_windows.h
@@ -256,6 +256,7 @@ typedef unsigned int uintptr_t;
 
 /* Enable various thread

(Patch may be truncated, please check the link at the top of this post.)